175 lines
7.7 KiB
C++
175 lines
7.7 KiB
C++
|
|
/// \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 <vde/mesh/marching_cubes.h>
|
|||
|
|
#include <vde/mesh/halfedge_mesh.h>
|
|||
|
|
#include <vde/foundation/io_stl.h>
|
|||
|
|
|
|||
|
|
#include <iostream>
|
|||
|
|
#include <iomanip>
|
|||
|
|
#include <cmath>
|
|||
|
|
#include <array>
|
|||
|
|
|
|||
|
|
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<StlTriangle> 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<int>(fi));
|
|||
|
|
if (verts.size() < 3) continue;
|
|||
|
|
|
|||
|
|
const Point3D& a = mesh.vertex(static_cast<size_t>(verts[0]));
|
|||
|
|
const Point3D& b = mesh.vertex(static_cast<size_t>(verts[1]));
|
|||
|
|
const Point3D& c = mesh.vertex(static_cast<size_t>(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;
|
|||
|
|
}
|