// Copyright (c) 2021 The Foundry Visionmongers Ltd. All Rights Reserved. #include "DDImage/Knobs.h" #include "DDImage/SourceGeomOp.h" #include "usg/geom/MeshPrim.h" #include "usg/geom/GeomTokens.h" #include "ndk/geo/utils/MeshUtils.h" #include #include using namespace DD::Image; // This example shows how to create a Tetrahedron 3D object able to be rendered // in a 3D scene with proper UV mapping, proper normals representation and // transformable via Axis_knob transform handles and vertices handles. // // # 3D coordinates Math explanation for Tetrahedron // // The Tetrahedron is circumscribed into a sphere of radius 1, so the // distance between the world center and each vertices is exactly 1. Each // face is a equilateral triangle. // // # '3D' view for x and y axis. Frontal view. // y ^ // | // 1 ................. ^p3 // /|\ // / | \ // 0 / | \ // <-----------.--/---+---\--.--------------> // . / | \ . // -sin(30)....../_____|_____\. // .p0 | .p2 // -cos(30) 0 cos(30) x // // # '3D' view for x and z axis. Top view of the base of the object. // // z ^ // p0 | p2 // sin(30)..... .------+------. // .\ | /. // . \ | / . // <-----------+--\---+---/--+---------------> // . \ | / . x // . \ | / . // . \|/ . // -1 . . . . . . . . .v p1 . // . | . // -cos(30) 0 cos(30) // // ### UV mapping (2D-3D mapping) // // The following mapping describes how a 2D texture is mapped to the // Tetrahedron. // // The 3D coordinates p[0-3] are mapped to faces 0-3 as the following // representation shows: // // V ^ // | p33 // 2 | ^ sqrt(3)/2 // | / \ // | / \ // | / \ // | / 3 \ // | / \ // | / \ // 1 | p0^-------------^p2 sqrt(3)/4 // | / \ / \ // | / \ 0 / \ // | / \ / \ // | / 1 \ / 2 \ // | / \ / \ // 0 |/_____._____\./_____._____\ // +-----------------------------------------> // 0 1 2 3 4 U // p31 p1 p32 // // p31, p32 and p33 represents how the same point p3 maps to form each // face 1, 2 and 3 respectively. // // Given a 2D square image, the area matching the face 0 will be rendered // as the bottom of the Tetrahedron. The faces 1, 2 and 3 will each be // rendered as the sides. namespace NukeExample { static const char* kGeoTetrahedronClass = "GeoTetrahedron"; static const char* kHelp = "Creates a 3D @i;GeoTetrahedron@n;"; static const float kRadians30 = radians(30.0f); static const float kCos30 = cos(kRadians30); static const float kSin30 = sin(kRadians30); // U and V coordinates for texture mapping. static const float kU[5]{0.0f, 0.25f, 0.5f, 0.75f, 1.0f}; static const float kV[3]{0.0f, sqrtf(3.0f) / 4.0f, sqrtf(3.0f) / 2.0f}; // UV texture mapping. It is composed of {kU-index, kV-index} pairs, mapping // 2D to 3D coordinates for each of the faces of the Tetrahedron. static const int kUVMapping[12][2]{{1, 1}, {3, 1}, {2, 0}, // face 0 {1, 1}, {2, 0}, {0, 0}, // face 1 {2, 0}, {3, 1}, {4, 0}, // face 2 {3, 1}, {1, 1}, {2, 2}};// face 3 class GeoTetrahedron : public SourceGeomOp { struct KnobSample : public GeomKnobSample { fdk::Vec3f pointA; fdk::Vec3f pointB; fdk::Vec3f pointC; fdk::Vec3f pointD; } parms; public: class Engine : public SourceEngine { KnobSample parms; public: Engine(Op* parent) : SourceEngine(parent) {} void createPrims(usg::GeomSceneContext& context, const usg::Path& path) override { const auto kPointA = knob("point_a"); if (kPointA) { parms.pointA = kPointA.get(); } const auto kPointB = knob("point_b"); if (kPointB) { parms.pointB = kPointB.get(); } const auto kPointC = knob("point_c"); if (kPointC) { parms.pointC = kPointC.get(); } const auto kPointD = knob("point_d"); if (kPointD) { parms.pointD = kPointD.get(); } if (definesEditLayer()) { usg::LayerRef defineLayer = editLayer(); assert(defineLayer); ndk::MeshSample sample; usg::MeshPrim mesh = usg::MeshPrim::defineInLayer(defineLayer, path.appendChild("Tetrahedron")); sample.addTri(0, 2, 1); sample.addTri(0, 1, 3); sample.addTri(1, 2, 3); sample.addTri(2, 0, 3); sample.points = {parms.pointA, parms.pointB, parms.pointC, parms.pointD}; sample.setMeshPrimFaceTopology(mesh); sample.setMeshPrimPointsAndBounds(mesh, true/*buildBoundsFromPoints*/); sample.setMeshPrimProperties(mesh, true/*setUvs*/, true/*setNormals*/); } } }; static GeomOpEngine* engineBuilder(Op* parent) { return new Engine(parent); } const char* Class() const override { return kGeoTetrahedronClass; } const char* node_help() const override { return kHelp; } GeoTetrahedron(Node* node) : SourceGeomOp(node, BuildEngine()), _aPos(-kCos30, -kSin30, kSin30), _bPos(kCos30, -kSin30, kSin30), _cPos(0, -kSin30, -1), _dPos(0, 1, 0) { } void knobs(Knob_Callback f) override { SourceGeomOp::knobs(f); // Set up the common SourceGeo knobs. XYZ_knob(f, &_aPos[0], "point_a", "a"); KnobDefinesGeometry(f); XYZ_knob(f, &_bPos[0], "point_b", "b"); KnobDefinesGeometry(f); XYZ_knob(f, &_cPos[0], "point_c", "c"); KnobDefinesGeometry(f); XYZ_knob(f, &_dPos[0], "point_d", "d"); KnobDefinesGeometry(f); makeTransformKnob(f); } static Op* Build(Node* node) { return new GeoTetrahedron(node); } static const Op::Description description; private: fdk::Vec3f _aPos; fdk::Vec3f _bPos; fdk::Vec3f _cPos; fdk::Vec3f _dPos; }; const Op::Description GeoTetrahedron::description(kGeoTetrahedronClass, GeoTetrahedron::Build); }