From acc26e3a4b86b3bbccaba66fd32fb6ea21942075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E4=B9=8B=E9=92=B3?= Date: Fri, 24 Jul 2026 12:42:49 +0000 Subject: [PATCH] feat(v3.3): WebGL 3D viewer + tutorial docs --- docs/tutorial/01-quickstart.md | 86 ++++++++ docs/tutorial/02-sdf-modeling.md | 333 ++++++++++++++++++++++++++++++ docs/tutorial/03-brep-modeling.md | 279 +++++++++++++++++++++++++ viewer/index.html | 132 ++++++++++++ 4 files changed, 830 insertions(+) create mode 100644 docs/tutorial/01-quickstart.md create mode 100644 docs/tutorial/02-sdf-modeling.md create mode 100644 docs/tutorial/03-brep-modeling.md create mode 100644 viewer/index.html diff --git a/docs/tutorial/01-quickstart.md b/docs/tutorial/01-quickstart.md new file mode 100644 index 0000000..aa3d730 --- /dev/null +++ b/docs/tutorial/01-quickstart.md @@ -0,0 +1,86 @@ +# 快速开始 + +5 分钟从零到第一个 GLB 模型。 + +--- + +## 安装 + +```bash +git clone https://github.com/your-org/ViewDesignEngine.git +cd ViewDesignEngine + +mkdir build && cd build +cmake .. -DBUILD_EXAMPLES=ON +cmake --build . -j$(nproc) +``` + +## 第一个 SDF 形状 + +创建文件 `hello_vde.cpp`: + +```cpp +#include +#include +#include +#include +#include +#include +#include + +using namespace vde::core; +using namespace vde::sdf; +using namespace vde::mesh; +using namespace vde::foundation; + +int main() { + // 1. 定义 SDF 函数 + auto shape = [](double x, double y, double z) -> double { + Point3D p(x, y, z); + double d_sphere = sphere(p, 1.2); + double d_box = box(p, Point3D(1.5, 1.5, 0.5)); + // 平滑并集 (k=0.3 控制融合程度) + return op_smooth_union(d_sphere, d_box, 0.3); + }; + + // 2. Marching Cubes → 三角形网格 + Point3D bmin(-2.5, -2.5, -2.5); + Point3D bmax( 2.5, 2.5, 2.5); + auto mc = marching_cubes(shape, 0.0, bmin, bmax, 64); + + // 3. 构建半边网格 + HalfedgeMesh mesh; + mesh.build_from_triangles(mc.vertices, mc.triangles); + + // 4. 导出 GLB + GltfOptions opts; + opts.binary = true; + opts.include_normals = true; + write_gltf("hello_vde.glb", mesh, opts); + + std::cout << "Done! Open hello_vde.glb in viewer/index.html\n"; + return 0; +} +``` + +编译运行: + +```bash +g++ -std=c++17 -I../include hello_vde.cpp -L. -lvde -o hello_vde +./hello_vde +``` + +## 导出 GLB + +GLB(glTF Binary)可直接在以下工具中打开: + +- 本项目自带的 `viewer/index.html`(拖放即可) +- Blender(File → Import → glTF 2.0) +- 在线工具:gltf-viewer.donmccurdy.com +- Windows/macOS 自带 3D Viewer + +## 下一步 + +- [SDF 隐式建模教程](02-sdf-modeling.md) —— 学习更多图元与操作 +- [B-Rep 工业建模教程](03-brep-modeling.md) —— 精确边界表示建模 +- [API 参考](../API-REFERENCE.md) —— 完整 API 文档 diff --git a/docs/tutorial/02-sdf-modeling.md b/docs/tutorial/02-sdf-modeling.md new file mode 100644 index 0000000..a749647 --- /dev/null +++ b/docs/tutorial/02-sdf-modeling.md @@ -0,0 +1,333 @@ +# SDF 隐式建模教程 + +有符号距离函数(Signed Distance Function)是 ViewDesignEngine 的核心建模方式之一。 +通过数学函数直接定义几何体,无需依赖网格或曲面表示。 + +## 核心概念 + +SDF 函数 `f(p)` 返回点 `p` 到最近表面的有符号距离: + +- `f(p) < 0`:点在几何体**内部** +- `f(p) = 0`:点在几何体**表面**上 +- `f(p) > 0`:点在几何体**外部** + +## 基本图元(球、盒、环) + +ViewDesignEngine 提供的内建 SDF 图元,定义在 `vde/sdf/sdf_primitives.h`: + +```cpp +#include +using namespace vde::sdf; +using namespace vde::core; + +// 球体:位于原点,半径 1.0 +double d = sphere(Point3D(1.0, 0.0, 0.0), 1.0); +// d ≈ 0.0(点在球面上) + +// 轴对齐盒子:以原点为中心,半长为 halfExtent +auto box_fn = [](double x, double y, double z) { + return box(Point3D(x, y, z), Point3D(1.5, 1.5, 0.5)); +}; + +// 环面(torus):major_radius = 环半径,minor_radius = 管半径 +auto torus_fn = [](double x, double y, double z) { + return torus(Point3D(x, y, z), 2.0, 0.4); +}; + +// 椭球:指定各轴半径 +auto ellipsoid_fn = [](double x, double y, double z) { + return ellipsoid(Point3D(x, y, z), Point3D(2.0, 1.0, 1.5)); +}; + +// 圆柱(沿 Y 轴) +auto cylinder_fn = [](double x, double y, double z) { + return cylinder(Point3D(x, y, z), 0.5, 3.0); +}; + +// 胶囊体:两点 + 半径 +auto capsule_fn = [](double x, double y, double z) { + return capsule(Point3D(x, y, z), + Point3D(0, -2, 0), Point3D(0, 2, 0), 0.4); +}; + +// 无限长圆柱:沿任意轴 +auto inf_cyl = [](double x, double y, double z) { + return infinite_cylinder(Point3D(x, y, z), + Vector3D(0, 0, 1), 0.5); +}; +``` + +## CSG 操作(并、交、差、光滑并集) + +布尔组合运算定义在 `vde/sdf/sdf_operations.h`: + +```cpp +#include +#include +using namespace vde::sdf; + +// === 标准布尔运算 === + +// 并集 A ∪ B:取最小值(两个几何体的合并) +auto union_shape = [](double x, double y, double z) { + Point3D p(x, y, z); + return op_union(sphere(p, 1.0), + box(p, Point3D(0.8, 0.8, 0.8))); +}; + +// 交集 A ∩ B:取最大值(两个几何体的共同区域) +auto intersect_shape = [](double x, double y, double z) { + Point3D p(x, y, z); + return op_intersection(sphere(p, 1.0), + box(p, Point3D(0.6, 0.6, 0.6))); +}; + +// 差集 A \ B:从 A 中减去 B +auto diff_shape = [](double x, double y, double z) { + Point3D p(x, y, z); + return op_difference(sphere(p, 1.0), + box(p, Point3D(0.4, 0.4, 0.4))); +}; + +// === 光滑布尔运算(带有机融合过渡) === + +// smooth union,k 控制过渡弧度 +auto smooth_union_shape = [](double x, double y, double z) { + Point3D p(x, y, z); + double d1 = sphere(p, 1.0); + double d2 = sphere(p - Point3D(1.5, 0, 0), 1.0); + return op_smooth_union(d1, d2, 0.3); // k=0.3 适中的融合 +}; + +// smooth intersection +auto smooth_isect_shape = [](double x, double y, double z) { + Point3D p(x, y, z); + double d1 = sphere(p, 1.0); + double d2 = box(p, Point3D(0.8, 0.8, 0.8)); + return op_smooth_intersection(d1, d2, 0.2); +}; + +// smooth difference +auto smooth_diff_shape = [](double x, double y, double z) { + Point3D p(x, y, z); + double d1 = box(p, Point3D(1.5, 1.5, 1.5)); + double d2 = sphere(p, 0.8); + return op_smooth_difference(d1, d2, 0.2); +}; +``` + +## 域变形(重复、镜像、扭转) + +域变形算子对采样点做空间变换,再传入 SDF 函数求值: + +```cpp +#include +using namespace vde::sdf; + +// 无限重复:将空间单元化,在每个单元内放置几何体 +auto repeated_shape = [](double x, double y, double z) { + Point3D p(x, y, z); + // 以 (2, 2, 2) 为周期重复 + Point3D q = op_repeat(p, Point3D(2.0, 2.0, 2.0)); + return sphere(q, 0.5); +}; + +// 镜像:沿 YZ 平面对称(x 方向镜像,offset 为对称轴位置) +auto mirrored_shape = [](double x, double y, double z) { + Point3D p(x, y, z); + Point3D q = op_mirror_x(p, 0.0); // 以 x=0 为镜像轴 + return sphere(q - Point3D(1.5, 0, 0), 0.6); +}; + +// 多维镜像组合 +auto quad_mirror = [](double x, double y, double z) { + Point3D p(x, y, z); + Point3D q = op_mirror_x(op_mirror_y(p)); + return sphere(q - Point3D(2.0, 2.0, 0), 0.5); +}; + +// 平移 / 旋转 / 缩放 +auto transformed_shape = [](double x, double y, double z) { + Point3D p(x, y, z); + // sdf_operations.h 提供 op_translate, op_rotate, op_scale 等 + // 注意:是逆变换——要移动几何体,需反向移动采样点 + Point3D q = op_translate(p, Point3D(2.0, 0, 0)); // 几何体向右移 2 + return sphere(q, 1.0); +}; + +// 扭转(沿 Y 轴) +#include +auto twisted_shape = [](double x, double y, double z) -> double { + double angle = y * 0.5; // 扭转角度与 y 坐标成正比 + double sx = std::sin(angle); + double cx = std::cos(angle); + double rx = cx * x - sx * z; + double rz = sx * x + cx * z; + Point3D q(rx, y, rz); + return box(q, Point3D(0.8, 3.0, 0.3)); +}; + +// 修饰算子:圆角、壳 +auto decorated_shape = [](double x, double y, double z) { + Point3D p(x, y, z); + double d = box(p, Point3D(1.0, 1.0, 1.0)); + // op_round: 对所有边做圆角(近似 round_box) + d = op_round(d, 0.1); + // op_onion: 创建壳层(空心厚度) + // d = op_onion(d, 0.05); + return d; +}; +``` + +## 从 SDF 到网格(Marching Cubes) + +```cpp +#include +#include +#include +#include +#include + +using namespace vde::core; +using namespace vde::sdf; +using namespace vde::mesh; +using namespace vde::foundation; + +int main() { + // 定义 SDF 函数 + auto shape = [](double x, double y, double z) -> double { + Point3D p(x, y, z); + double d = box(p, Point3D(2.0, 2.0, 2.0)); + d = op_round(d, 0.15); // 圆角 + return d; + }; + + // 采样区域:[-3,3]³ + Point3D bmin(-3, -3, -3), bmax(3, 3, 3); + + // Marching Cubes:64³ 分辨率 + auto mc = marching_cubes(shape, 0.0, bmin, bmax, 64); + + // 构建半边网格并导出 + HalfedgeMesh mesh; + mesh.build_from_triangles(mc.vertices, mc.triangles); + + GltfOptions opts; + opts.binary = true; + opts.include_normals = true; + write_gltf("rounded_box.glb", mesh, opts); + + std::cout << "Vertices: " << mesh.num_vertices() + << ", Faces: " << mesh.num_faces() << "\n"; + return 0; +} +``` + +**分辨率选择指南:** + +| 分辨率 | 适用场景 | 输出网格规模 | +|--------|----------|-------------| +| 32³ | 快速预览 | ~2K 顶点 | +| 64³ | 一般用途 | ~8K 顶点 | +| 128³ | 精细表面 | ~30K 顶点 | +| 256³ | 高精度需求 | ~100K+ 顶点 | + +## 完整示例:齿轮形状 + +```cpp +#include +#include +#include +#include +#include + +#include +#include + +using namespace vde::core; +using namespace vde::sdf; +using namespace vde::mesh; +using namespace vde::foundation; + +int main() { + const double M_PI = 3.14159265358979323846; + const double gear_radius = 2.0; // 齿轮半径 + const double tooth_count = 12.0; // 齿数 + const double tooth_depth = 0.3; // 齿深 + const double gear_thickness = 0.5; // 齿轮厚度 + + auto gear_sdf = [&](double x, double y, double z) -> double { + Point3D p(x, y, z); + + // 圆柱主体(沿 Y 轴的薄圆盘) + double d_cylinder = cylinder(Point3D(x, y, z), gear_radius, gear_thickness); + + // 中心孔 + double d_hole = cylinder(Point3D(x, y, z), 0.4, gear_thickness * 2); + + // 差集:打孔 + double d = op_difference(d_cylinder, d_hole); + + // 齿轮齿:沿圆周等距分布 + double angle = std::atan2(z, x); + double radius = std::sqrt(x * x + z * z); + + // 每个齿是一个小 Box 沿径向放置 + double teeth_union = 1e10; + for (int i = 0; i < tooth_count; i++) { + double theta = i * 2.0 * M_PI / tooth_count; + double ca = std::cos(theta); + double sa = std::sin(theta); + + // 旋转采样点到该齿的局部空间 + double lx = ca * x + sa * z; + double lz = -sa * x + ca * z; + Point3D lp(lx, y, lz); + + double tooth = box(lp, + Point3D(0.3, 0.6, gear_radius + tooth_depth)); + // 向圆心方向偏移 + double d_tooth = op_translate_along_x(lp, + gear_radius + tooth_depth * 0.5).norm() > 0 + ? box(Point3D(lx - (gear_radius + tooth_depth * 0.5), y, lz), + Point3D(0.15, gear_thickness * 1.1, tooth_depth * 0.5)) + : 1e10; + + // 用旋转矩阵的逆变换做齿的径向放置 + double r = std::sqrt(x * x + z * z); + double dx = r - (gear_radius + tooth_depth * 0.5); + double dy_abs = std::abs(y); + double dz = std::abs(std::atan2(z, x) - theta); + if (dz > M_PI) dz = 2 * M_PI - dz; + dz = dz * gear_radius; + + double tooth_dist = std::sqrt(dx * dx + dy_abs * dy_abs + dz * dz) + - tooth_depth * 0.5; + if (dy_abs <= gear_thickness * 0.55 && r >= gear_radius - 0.05) { + teeth_union = std::min(teeth_union, tooth_dist); + } + } + + d = op_union(d, teeth_union); + return d; + }; + + // Marching Cubes + Point3D bmin(-3, -3, -3), bmax(3, 3, 3); + auto mc = marching_cubes(gear_sdf, 0.0, bmin, bmax, 128); + + HalfedgeMesh mesh; + mesh.build_from_triangles(mc.vertices, mc.triangles); + + GltfOptions opts; + opts.binary = true; + opts.include_normals = true; + write_gltf("gear.glb", mesh, opts); + + std::cout << "Gear exported: " << mesh.num_vertices() + << " vertices, " << mesh.num_faces() << " faces\n"; + return 0; +} +``` + +编译运行,用 `viewer/index.html` 查看效果。 diff --git a/docs/tutorial/03-brep-modeling.md b/docs/tutorial/03-brep-modeling.md new file mode 100644 index 0000000..083d5c7 --- /dev/null +++ b/docs/tutorial/03-brep-modeling.md @@ -0,0 +1,279 @@ +# B-Rep 工业建模教程 + +边界表示(Boundary Representation,B-Rep)是 CAD 系统中最常用的实体表示方法。ViewDesignEngine 提供高层建模 API 用于创建 B-Rep 实体,支持精确的几何计算和工业标准格式导出。 + +## 核心概念 + +B-Rep 通过拓扑层次结构描述三维几何体的边界: + +``` +Body (体) — 一个或多个封闭壳 + └─ Shell (壳) — 一组面的连续集合 + └─ Face (面) — 曲面上由环界定的区域 + └─ Loop (环) — 封闭的边界曲线 + └─ Edge (边) — 曲面的边界段 + └─ Vertex (顶点) — 边的端点 +``` + +## 基础实体(盒、柱、球) + +使用 `vde/brep/modeling.h` 中的高层 API 创建基本体: + +```cpp +#include +#include + +using namespace vde::brep; + +int main() { + // 轴对齐长方体:宽 × 高 × 深 + auto box = make_box(100.0, 50.0, 30.0); + std::cout << "Box faces: " << box.num_faces() + << ", edges: " << box.num_edges() << "\n"; + + // 圆柱体:半径 20,高度 80,64 段(精度) + auto cylinder = make_cylinder(20.0, 80.0, 64); + std::cout << "Cylinder faces: " << cylinder.num_faces() << "\n"; + + // 球体:半径 25,经度 32 段,纬度 32 段 + auto sphere = make_sphere(25.0, 32, 32); + std::cout << "Sphere faces: " << sphere.num_faces() << "\n"; + + return 0; +} +``` + +## 扫掠特征(Extrude, Revolve, Sweep, Loft) + +扫掠操作将二维轮廓沿路径移动生成三维实体: + +```cpp +#include +#include +#include + +using namespace vde::brep; +using namespace vde::curves; +using namespace vde::core; + +int main() { + // 创建一个矩形轮廓(作为 NURBS 曲线:封闭的 4 点控制多边形) + NurbsCurve profile; + profile.set_degree(1); // 线性 + profile.set_knots({0, 0, 0.25, 0.5, 0.75, 1, 1}); + profile.set_control_points({ + Point3D(-10, 0, -10), + Point3D( 10, 0, -10), + Point3D( 10, 0, 10), + Point3D(-10, 0, 10), + Point3D(-10, 0, -10) // 闭合 + }); + + // 线性挤出:沿 Z 轴挤出 50 单位 + auto extruded = extrude(profile, Vector3D(0, 0, 50)); + std::cout << "Extruded faces: " << extruded.num_faces() << "\n"; + + // 旋转扫掠(Revolve):半圆绕 Y 轴旋转 + NurbsCurve semicircle; + semicircle.set_degree(2); + // ... 控制点定义上半圆 ... + auto revolved = revolve(semicircle, + Point3D(0, 0, 0), // 旋转轴起点 + Vector3D(0, 1, 0), // 旋转轴方向(Y 轴) + 360.0 // 角度(度) + ); + std::cout << "Revolved faces: " << revolved.num_faces() << "\n"; + + // 沿路径扫掠(Sweep):圆形截面沿螺旋路径 + NurbsCurve circle_profile; // 小圆 + NurbsCurve helix_path; // 螺旋线 + auto swept = sweep(circle_profile, helix_path); + + // 放样(Loft):多个截面 → 实体 + std::vector profiles = {profile_a, profile_b, profile_c}; + auto lofted = loft(profiles); + + return 0; +} +``` + +## 特征操作(倒圆、倒角、抽壳) + +编辑操作通过链式调用逐步细化模型: + +```cpp +#include +#include +#include + +using namespace vde::brep; +using namespace vde::foundation; + +int main() { + // 基础块:100 × 60 × 40 mm + auto part = make_box(100.0, 60.0, 40.0); + + // 对所有边倒圆角(face_id = 0 代表指定面,或对所有边操作) + part = fillet(part, 0, 5.0); // 半径 5mm 圆角 + std::cout << "After fillet: " << part.num_faces() << " faces\n"; + + // 对顶面边缘倒角(典型机加工倒角) + // chamfer(face_id, distance) + // part = chamfer(part, 5, 2.0); + + // 抽壳:创建均匀壁厚的空心壳体 + // face_id = -1 表示封闭壳体(无开口) + // thickness > 0 表示向外增厚,< 0 表示保留内腔 + part = shell(part, -1, 2.0); + std::cout << "After shell (2mm wall): " + << part.num_faces() << " faces\n"; + + return 0; +} +``` + +## 布尔运算 + +B-Rep 层面支持精确布尔运算: + +```cpp +#include +#include + +using namespace vde::brep; + +int main() { + auto block = make_box(50, 50, 50); + auto cylinder = make_cylinder(15, 80, 64); + + // 并集(Union):块 + 柱 + auto union_result = brep_union(block, cylinder); + + // 交集(Intersection):块 ∩ 柱 + auto isect_result = brep_intersection(block, cylinder); + + // 差集(Difference):块 - 柱(在块上打孔) + auto diff_result = brep_difference(block, cylinder); + + std::cout << "Union: " << union_result.num_faces() << " faces\n"; + std::cout << "Intersection: " << isect_result.num_faces() << " faces\n"; + std::cout << "Difference: " << diff_result.num_faces() << " faces\n"; + + return 0; +} +``` + +## STEP 导出 → CAM + +STEP(AP214)是工业制造的标准交换格式,可直接导入 CAM 软件生成刀具路径: + +```cpp +#include +#include + +using namespace vde::brep; + +int main() { + auto part = make_box(80, 40, 20); + part = fillet(part, 0, 3.0); + part = shell(part, -1, 2.0); + + // 导出为 STEP AP214 文件 + export_step_file("flange.stp", {part}); + + // 或以字符串形式获取 STEP 内容 + std::string step_str = export_step({part}); + std::cout << "STEP size: " << step_str.size() << " chars\n"; + + std::cout << "STEP exported. Ready for:\n" + << " - Fusion 360 / SolidWorks / FreeCAD\n" + << " - CAM toolpath generation\n" + << " - CMM inspection\n"; + return 0; +} +``` + +## 完整示例:法兰零件 + +```cpp +/// 法兰零件:盒基 + 挤出环 → 倒角 → 钻孔阵列 → STEP/GLB 导出 +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace vde::brep; +using namespace vde::foundation; +using namespace vde::core; + +int main() { + std::cout << std::fixed << std::setprecision(3); + + // ── 法兰本体 ────────────────────────────────── + // 80×80×15 mm 的底座 + double W = 80.0, D = 80.0, H = 15.0; + auto flange = make_box(W, D, H); + std::cout << "Base block: " << W << "×" << D << "×" << H << " mm\n"; + + // ── 中心凸台(挤出圆柱) ────────────────────── + double boss_d = 40.0, boss_h = 25.0; + auto boss = make_cylinder(boss_d / 2.0, boss_h, 64); + // 定位:凸台在法兰顶面中心 + // (如需要偏移,可通过 brep_boolean 的变换参数定位) + flange = brep_union(flange, boss); + std::cout << "Added boss: Ø" << boss_d << "×" << boss_h << " mm\n"; + + // ── 边缘圆角 ────────────────────────────────── + flange = fillet(flange, 0, 3.0); + std::cout << "Edge fillet: R3.0 applied\n"; + + // ── 螺栓孔阵列(4 个 M8 通孔) ──────────────── + double hole_d = 8.5; // M8 通孔直径 + double bolt_circle_r = 30.0; // 螺栓孔分布圆半径 + + for (int i = 0; i < 4; i++) { + double angle = i * M_PI / 2.0; // 0°, 90°, 180°, 270° + double cx = bolt_circle_r * std::cos(angle); + double cy = bolt_circle_r * std::sin(angle); + + auto hole = make_cylinder(hole_d / 2.0, H + 5.0, 32); + // 差集减孔(如需定位偏移,使用 brep_difference 的变换版本) + flange = brep_difference(flange, hole); + } + std::cout << "Bolt holes: 4ר" << hole_d << " on Ø" << bolt_circle_r * 2 << " BCD\n"; + + // ── 质量信息 ────────────────────────────────── + std::cout << "\nFinal part:\n" + << " faces: " << flange.num_faces() << "\n" + << " edges: " << flange.num_edges() << "\n"; + + // ── 导出 ────────────────────────────────────── + // STEP(CAM 用) + export_step_file("flange.stp", {flange}); + std::cout << " → flange.stp (STEP AP214)\n"; + + // GLB(预览用) + if (write_brep_gltf("flange.glb", flange, 32)) { + std::cout << " → flange.glb (GLB visualization)\n"; + } + + // ── 交互式预览 ──────────────────────────────── + std::cout << "\nOpen viewer/index.html and drop flange.glb to inspect.\n"; + return 0; +} +``` + +编译运行: + +```bash +g++ -std=c++17 -I../include flange_demo.cpp -L. -lvde -o flange_demo +./flange_demo +``` + +生成的 `flange.stp` 可导入 FreeCAD / Fusion 360 进行 CAM 刀具路径生成, +`flange.glb` 可在项目自带的 `viewer/index.html` 中预览。 diff --git a/viewer/index.html b/viewer/index.html new file mode 100644 index 0000000..518f8b1 --- /dev/null +++ b/viewer/index.html @@ -0,0 +1,132 @@ + + + + +ViewDesignEngine 3D Viewer + + + +
ViewDesignEngine 3D Viewer
+
拖放 .glb / .stl 文件到这里
+
+ + + +
+ + + + + +