/// \file 11_gear/main.cpp /// \brief Parametric gear generator — SDF → Marching Cubes → STL export /// /// Demonstrates a design-exploration workflow: define gear geometry as an /// implicit surface (SDF), extract a triangle mesh via Marching Cubes, /// and export to STL for 3D printing or further processing. #include #include #include #include #include #include #include using namespace vde::mesh; using namespace vde::foundation; using namespace vde::core; // ═══════════════════════════════════════════════════════════════════════ // Gear SDF // ═══════════════════════════════════════════════════════════════════════ /** * @brief Build a parametric gear SDF function. * * The gear lies in the XZ plane and is extruded along the Y axis. * Teeth are generated as sinusoidal bumps on the pitch circle, * producing a simplified but visually recognizable gear profile. * * @param teeth Number of teeth * @param pitch_r Pitch circle radius * @param addendum Tooth height above pitch circle * @param dedendum Tooth depth below pitch circle * @param thickness Total thickness (Y-axis extrusion) * @return SDF function f(x, y, z) → signed distance */ inline auto make_gear_sdf(int teeth, double pitch_r, double addendum, double dedendum, double thickness) { return [=](double x, double y, double z) -> double { // ── 2D gear profile in XZ plane ── double r = std::sqrt(x * x + z * z); double theta = std::atan2(z, x); // Root circle (inner) double root_r = pitch_r - dedendum; double d_root = r - root_r; // Tooth profile: sinusoidal bumps // cos(N*theta) = +1 at tooth center, -1 at gap center // radius at tooth center = pitch_r + addendum // radius at gap center = pitch_r - dedendum double bump = (addendum + dedendum) * 0.5 * (1.0 + std::cos(teeth * theta)); double prof_r = root_r + bump; double d_tooth = r - prof_r; // Union: point is inside if inside root circle OR inside tooth profile double d_xy = std::min(d_root, d_tooth); // ── Extrude along Y axis ── double d_y = std::abs(y) - thickness * 0.5; return std::max(d_xy, d_y); }; } // ═══════════════════════════════════════════════════════════════════════ // Main — Parametric Gear // ═══════════════════════════════════════════════════════════════════════ int main() { std::cout << std::fixed << std::setprecision(2); std::cout << "╔══════════════════════════════════╗\n" << "║ Gear — Parametric SDF Demo ║\n" << "╚══════════════════════════════════╝\n\n"; // ── Parameters ─────────────────────────────────────────────────── // // Standard involute gear parameters (simplified): // module = pitch diameter / teeth → standard metric sizing // tooth height = 2.25 × module (addendum + dedendum) const int teeth = 16; const double module = 2.0; const double pitch_r = teeth * module / 2.0; // 16 mm const double addendum = module; // 2 mm const double dedendum = 1.25 * module; // 2.5 mm const double thickness = 10.0; std::cout << "Parameters:\n" << " teeth: " << teeth << "\n" << " module: " << module << " mm\n" << " pitch Ø: " << pitch_r * 2.0 << " mm\n" << " outer Ø: " << (pitch_r + addendum) * 2.0 << " mm\n" << " root Ø: " << (pitch_r - dedendum) * 2.0 << " mm\n" << " thickness: " << thickness << " mm\n\n"; // ── Step 1: Marching Cubes — SDF → triangle mesh ───────────────── // // Sample a bounding box slightly larger than the gear, // at 128³ voxel resolution for smooth teeth. const double bb_margin = addendum + dedendum + 2.0; const double bb_r = pitch_r + addendum + bb_margin; const int res = 128; std::cout << "Step 1: Marching Cubes (resolution " << res << "³)\n"; auto gear_sdf = make_gear_sdf(teeth, pitch_r, addendum, dedendum, thickness); MCMesh mc = marching_cubes(gear_sdf, 0.0, Point3D(-bb_r, -thickness - 2, -bb_r), Point3D( bb_r, thickness + 2, bb_r), res); std::cout << " vertices: " << mc.vertices.size() << "\n" << " triangles: " << mc.triangles.size() << "\n"; // ── Step 2: Build half-edge mesh ───────────────────────────────── std::cout << "\nStep 2: Build HalfedgeMesh\n"; HalfedgeMesh mesh; mesh.build_from_triangles(mc.vertices, mc.triangles); mesh.update_normals(); std::cout << " vertices: " << mesh.num_vertices() << "\n" << " faces: " << mesh.num_faces() << "\n" << " edges: " << mesh.num_edges() << "\n"; // ── Step 3: Export to STL ──────────────────────────────────────── std::cout << "\nStep 3: Export STL (binary)\n"; std::vector stl_tris; stl_tris.reserve(mesh.num_faces()); for (size_t fi = 0; fi < mesh.num_faces(); ++fi) { auto verts = mesh.face_vertices(static_cast(fi)); if (verts.size() < 3) continue; const Point3D& a = mesh.vertex(static_cast(verts[0])); const Point3D& b = mesh.vertex(static_cast(verts[1])); const Point3D& c = mesh.vertex(static_cast(verts[2])); Vector3D n = (b - a).cross(c - a).normalized(); StlTriangle t; t.normal = n; t.v0 = a; t.v1 = b; t.v2 = c; stl_tris.push_back(t); } const char* stl_path = "gear.stl"; write_stl(stl_path, stl_tris); std::cout << " → " << stl_path << " (" << stl_tris.size() << " triangles)\n"; // ── Summary ────────────────────────────────────────────────────── std::cout << "\n╔══════════════════════════════════════════╗\n" << "║ Gear Export Summary ║\n" << "╠══════════════════════════════════════════╣\n" << "║ SDF → Marching Cubes → STL ✓ ║\n" << "╚══════════════════════════════════════════╝\n"; std::cout << "\nDone! Parametric gear exported.\n"; return 0; }