// Sphere.C // Copyright (c) 2009 The Foundry Visionmongers Ltd. All Rights Reserved. static const char* const CLASS = "Sphere"; static const char* const HELP = "Generates a 3D sphere"; #include "DDImage/SourceGeo.h" #include "DDImage/Scene.h" #include "DDImage/Triangle.h" #include "DDImage/Knobs.h" #include "DDImage/Knob.h" #include "DDImage/Channel3D.h" #include using namespace DD::Image; #ifndef mFnStringize #define mFnStringize2(A) # A #define mFnStringize(A) mFnStringize2(A) #endif #define MAX_SPHERE_TESSELATION 512 class Sphere : public SourceGeo { private: double radius; int columns, rows; double my_u_extent, my_v_extent; bool close_top, close_bottom; // local matrix that Axis_Knob fills in Matrix4 _local; bool fix; Knob* _pAxisKnob; protected: void _validate(bool for_real) override { // Clamp the mesh size to reasonable numbers: columns = MIN(MAX(columns, 3), MAX_SPHERE_TESSELATION); rows = MIN(MAX(rows, 3), MAX_SPHERE_TESSELATION); my_u_extent = clamp( my_u_extent, 0.001, 360.0 ); my_v_extent = clamp( my_v_extent, 0.001, 180.0 ); SourceGeo::_validate(for_real); } public: static const Description description; const char* Class() const override { return CLASS; } const char* node_help() const override { return HELP; } Sphere(Node* node) : SourceGeo(node) { radius = 1.0; rows = columns = 30; close_top = close_bottom = true; my_u_extent = 360.0; my_v_extent = 180.0; _local.makeIdentity(); fix = false; _pAxisKnob = nullptr; } void knobs(Knob_Callback f) override { SourceGeo::knobs(f); Int_knob(f, &rows, IRange(3, MAX_SPHERE_TESSELATION), "rows", "rows/columns"); SetFlags(f, Knob::FORCE_RANGE); Tooltip(f, "The maximum rows is " mFnStringize(MAX_SPHERE_TESSELATION)); Int_knob(f, &columns, IRange(3, MAX_SPHERE_TESSELATION), "columns", ""); SetFlags(f, Knob::FORCE_RANGE); Tooltip(f, "The maximum columns is " mFnStringize(MAX_SPHERE_TESSELATION)); Double_knob(f, &radius, "radius"); Double_knob(f, &my_u_extent, "u_extent", "u extent" ); Double_knob(f, &my_v_extent, "v_extent", "v extent" ); Newline(f); Bool_knob(f, &close_top, "close_top", "close top"); Bool_knob(f, &close_bottom, "close_bottom", "close bottom"); Obsolete_knob(f, "create_uvs", nullptr); Obsolete_knob(f, "create_normals", nullptr); Divider( f); // transform knobs _pAxisKnob = Axis_knob(f, &_local, "transform"); if (_pAxisKnob != nullptr) { if (GeoOp::selectable() == true) _pAxisKnob->enable(); else _pAxisKnob->disable(); } // This knob is set by knob_default so that all new instances execute // the "fix" code, which rotates the sphere 180 degrees so that the // seam is on the far side from the default camera position. Bool_knob(f, &fix, "fix", INVISIBLE); } /*! The will handle the knob changes. */ int knob_changed(Knob* k) override { if (k != nullptr) { if (k->is("selectable")) { if (GeoOp::selectable() == true) _pAxisKnob->enable(); else _pAxisKnob->disable(); return 1; } } return SourceGeo::knob_changed(k); } // Hash up knobs that affect the Sphere: void get_geometry_hash() override { SourceGeo::get_geometry_hash(); // Get all hashes up-to-date // Knobs that change the geometry structure: geo_hash[Group_Primitives].append(columns); geo_hash[Group_Primitives].append(rows); geo_hash[Group_Primitives].append(close_top); geo_hash[Group_Primitives].append(close_bottom); // Knobs that change the point locations: geo_hash[Group_Points].append(radius); geo_hash[Group_Points].append(columns); geo_hash[Group_Points].append(rows); geo_hash[Group_Points].append(close_top); geo_hash[Group_Points].append(close_bottom); // Knobs that change the vertex attributes: geo_hash[Group_Attributes].append(my_u_extent); geo_hash[Group_Attributes].append(my_v_extent); geo_hash[Group_Matrix].append(_local.a00); geo_hash[Group_Matrix].append(_local.a01); geo_hash[Group_Matrix].append(_local.a02); geo_hash[Group_Matrix].append(_local.a03); geo_hash[Group_Matrix].append(_local.a10); geo_hash[Group_Matrix].append(_local.a11); geo_hash[Group_Matrix].append(_local.a12); geo_hash[Group_Matrix].append(_local.a13); geo_hash[Group_Matrix].append(_local.a20); geo_hash[Group_Matrix].append(_local.a21); geo_hash[Group_Matrix].append(_local.a22); geo_hash[Group_Matrix].append(_local.a23); geo_hash[Group_Matrix].append(_local.a30); geo_hash[Group_Matrix].append(_local.a31); geo_hash[Group_Matrix].append(_local.a32); geo_hash[Group_Matrix].append(_local.a33); } // Apply the concat matrix to all the GeoInfos. void geometry_engine(Scene& scene, GeometryList& out) override { SourceGeo::geometry_engine(scene, out); // multiply the node matrix for (unsigned i = 0; i < out.size(); i++) out[i].matrix = _local * out[i].matrix; } void create_geometry(Scene& scene, GeometryList& out) override { int obj = 0; columns = MIN(MAX(columns, 3), MAX_SPHERE_TESSELATION); rows = MIN(MAX(rows, 3), MAX_SPHERE_TESSELATION); my_u_extent = clamp( my_u_extent, 0.001, 360.0 ); my_v_extent = clamp( my_v_extent, 0.001, 180.0 ); unsigned num_points = (close_bottom ? 1 : 0) + (rows - 1) * columns + (close_top ? 1 : 0); //============================================================= // Build the primitives: if (rebuild(Mask_Primitives)) { out.delete_objects(); out.add_object(obj); // Create poly primitives: // Bottom endcap: if (close_bottom) { int j1 = 1; for (int i = 0; i < columns; i++) { int i0 = i % columns; int i1 = (i + 1) % columns; out.add_primitive(obj, new Triangle(0, i1 + j1, i0 + j1)); } } // Create the center poly mesh: for (int j = 0; j < rows - 2; j++) { int j0 = j * columns + (close_bottom ? 1 : 0); int j1 = (j + 1) * columns + (close_bottom ? 1 : 0); for (int i = 0; i < columns; i++) { int i0 = i % columns; int i1 = (i + 1) % columns; // Create 2 triangles: out.add_primitive(obj, new Triangle(i0 + j0, i1 + j0, i0 + j1)); out.add_primitive(obj, new Triangle(i0 + j1, i1 + j0, i1 + j1)); } } // Top endcap: if (close_top) { int top_point = num_points - 1; int j0 = (close_bottom ? 1 : 0) + (rows - 2) * columns; for (int i = 0; i < columns; i++) { int i0 = i % columns; int i1 = (i + 1) % columns; out.add_primitive(obj, new Triangle(i0 + j0, i1 + j0, top_point)); } } // Force points and attributes to update: set_rebuild(Mask_Points | Mask_Attributes); } //============================================================= // Create points and assign their coordinates: if (rebuild(Mask_Points)) { // Generate points: PointList* points = out.writable_points(obj); points->resize(num_points); // Assign the point locations: int p = 0; // Bottom center: if (close_bottom) (*points)[p++].set(0.0f, -radius, 0.0f); // Middle mesh: float drho = M_PI / rows; float dtheta = (2.0 * M_PI) / columns; float fix = this->fix ? -1 : 1; for (int j = 1; j < rows; j++) { float rho = j * drho; for (int i = 0; i < columns; i++) { float theta = i * dtheta; float x = fix * sinf(theta) * sinf(rho); float y = -cosf(rho); float z = fix * cosf(theta) * sinf(rho); (*points)[p].set(x * radius, y * radius, z * radius); ++p; } } // Top center: if (close_top) (*points)[p].set(0.0f, radius, 0.0f); } //============================================================= // Assign the normals and uvs: if (rebuild(Mask_Attributes)) { GeoInfo& info = out[obj]; //--------------------------------------------- // NORMALS: const Vector3* PNTS = info.point_array(); Attribute* N = out.writable_attribute(obj, Group_Points, "N", NORMAL_ATTRIB); assert(N); for (unsigned p = 0; p < num_points; p++) N->normal(p) = PNTS[p] / radius; //--------------------------------------------- // UVs: const Primitive** PRIMS = info.primitive_array(); Attribute* uv = out.writable_attribute(obj, Group_Vertices, "uv", VECTOR4_ATTRIB); assert(uv); float ds = (360.0f / float(my_u_extent)) / float(columns); // U change per column float ss = 0.5f - (360.0f / float(my_u_extent)) / 2.0f; // Starting U float dt = (180.0f / float(my_v_extent)) / float(rows); // V change per row float st = 0.5 - (180.0 / my_v_extent) / 2.0; // Starting V float s, t; // Current UV t = st; // Bottom center: if (close_bottom) { s = ss; for (int i = 0; i < columns; i++) { unsigned v = (*PRIMS++)->vertex_offset(); uv->vector4(v++).set( s, 0.0f, 0.0f, 1.0f); uv->vector4(v++).set(s + ds, t + dt, 0.0f, 1.0f); uv->vector4(v++).set( s, t + dt, 0.0f, 1.0f); s += ds; } t += dt; } // Create the poly mesh in center: for (int j = 0; j < rows - 2; j++) { s = ss; for (int i = 0; i < columns; i++) { unsigned v = (*PRIMS++)->vertex_offset(); uv->vector4(v++).set( s, t, 0.0f, 1.0f); uv->vector4(v++).set(s + ds, t, 0.0f, 1.0f); uv->vector4(v++).set( s, t + dt, 0.0f, 1.0f); v = (*PRIMS++)->vertex_offset(); uv->vector4(v++).set( s, t + dt, 0.0f, 1.0f); uv->vector4(v++).set(s + ds, t, 0.0f, 1.0f); uv->vector4(v++).set(s + ds, t + dt, 0.0f, 1.0f); s += ds; } t += dt; } // Top endcap: if (close_top) { s = ss; for (int i = 0; i < columns; i++) { unsigned v = (*PRIMS++)->vertex_offset(); uv->vector4(v++).set( s, t, 0.0f, 1.0f); uv->vector4(v++).set(s + ds, t, 0.0f, 1.0f); uv->vector4(v++).set( s, 1.0f, 0.0f, 1.0f); s += ds; } } } } // virtual void build_handles(ViewerContext* ctx) override { // call build_matrix_handle to multiply the context model matrix with the local matrix so the // nodes above it will display correctly GeoOp::build_matrix_handles(ctx, &_local); } }; static Op* build(Node* node) { return new Sphere(node); } const Op::Description Sphere::description(CLASS, build); // end of Sphere.C