From c226b60a3226560ff7a3c5b72000f48e8c869bcb Mon Sep 17 00:00:00 2001 From: ViewDesignEngine Date: Thu, 23 Jul 2026 07:19:13 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20P2=20B-Rep=20=E5=86=85=E6=A0=B8=20+=20?= =?UTF-8?q?=E5=BB=BA=E6=A8=A1=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - brep: 完整 B-Rep 数据结构(Vertex/Edge/Loop/Face/Shell/Body) - brep: 实体工厂(box/cylinder/sphere) - brep: 建模操作(extrude/revolve/sweep/loft) - brep: 拓扑查询(face_edges/edge_faces/vertex_edges) - brep: 网格转换(to_mesh) - 新增 vde_brep 模块,更新聚合库 --- CMakeLists.txt | 1 + include/vde/brep/brep.h | 67 ++++++ include/vde/brep/modeling.h | 48 ++++ src/CMakeLists.txt | 14 ++ src/brep/brep.cpp | 125 ++++++++++ src/brep/modeling.cpp | 444 ++++++++++++++++++++++++++++++++++++ 6 files changed, 699 insertions(+) create mode 100644 include/vde/brep/brep.h create mode 100644 include/vde/brep/modeling.h create mode 100644 src/brep/brep.cpp create mode 100644 src/brep/modeling.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 61dcee2..2bb1828 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ endif() # ── 子目录 ──────────────────────────────────────── add_subdirectory(src) + add_subdirectory(src) # ── 测试 ────────────────────────────────────────── if(BUILD_TESTS) diff --git a/include/vde/brep/brep.h b/include/vde/brep/brep.h new file mode 100644 index 0000000..c98ebfc --- /dev/null +++ b/include/vde/brep/brep.h @@ -0,0 +1,67 @@ +#pragma once +#include "vde/core/point.h" +#include "vde/core/aabb.h" +#include "vde/curves/nurbs_curve.h" +#include "vde/curves/nurbs_surface.h" +#include "vde/mesh/halfedge_mesh.h" +#include +#include +#include + +namespace vde::brep { + +enum class CurveType { Line, Circle, Bezier, BSpline, Nurbs }; + +struct TopoVertex { int id; Point3D point; double tolerance = 1e-6; }; +struct TopoEdge { + int id, v_start, v_end; + std::shared_ptr curve; + bool reversed = false; +}; +struct TopoLoop { int id; std::vector edges; bool is_outer = true; }; +struct TopoFace { int id, surface_id; std::vector loops; bool reversed = false; }; +struct TopoShell { int id; std::vector faces; bool closed = true; }; +struct TopoBody { int id; std::vector shells; std::string name; }; + +class BrepModel { +public: + BrepModel() = default; + int add_vertex(const Point3D& p); + int add_edge(int v0, int v1); + int add_edge(int v0, int v1, const curves::NurbsCurve& curve); + int add_loop(const std::vector& edges, bool outer = true); + int add_face(int surface_id, const std::vector& loops); + int add_shell(const std::vector& faces, bool closed = true); + int add_body(const std::vector& shells, const std::string& name = ""); + int add_surface(const curves::NurbsSurface& surf); + + [[nodiscard]] size_t num_vertices() const { return vertices_.size(); } + [[nodiscard]] size_t num_edges() const { return edges_.size(); } + [[nodiscard]] size_t num_faces() const { return faces_.size(); } + [[nodiscard]] size_t num_bodies() const { return bodies_.size(); } + + [[nodiscard]] const TopoVertex& vertex(int id) const { return vertices_[id]; } + [[nodiscard]] const TopoEdge& edge(int id) const { return edges_[id]; } + [[nodiscard]] const TopoFace& face(int id) const { return faces_[id]; } + [[nodiscard]] const curves::NurbsSurface& surface(int id) const { return surfaces_[id]; } + + [[nodiscard]] core::AABB3D bounds() const; + [[nodiscard]] bool is_valid() const; + [[nodiscard]] mesh::HalfedgeMesh to_mesh(double deflection = 0.01) const; + + [[nodiscard]] std::vector face_edges(int face_id) const; + [[nodiscard]] std::vector edge_faces(int edge_id) const; + [[nodiscard]] std::vector vertex_edges(int vertex_id) const; + +private: + std::vector vertices_; + std::vector edges_; + std::vector loops_; + std::vector faces_; + std::vector shells_; + std::vector bodies_; + std::vector surfaces_; + int next_id_ = 0; +}; + +} // namespace vde::brep diff --git a/include/vde/brep/modeling.h b/include/vde/brep/modeling.h new file mode 100644 index 0000000..238d339 --- /dev/null +++ b/include/vde/brep/modeling.h @@ -0,0 +1,48 @@ +#pragma once +#include "vde/brep/brep.h" + +namespace vde::brep { + +/// Extrude a planar face/wire along a direction +/// @param profile Profile curve (must be planar) +/// @param dir Extrusion direction (length = distance) +[[nodiscard]] BrepModel extrude(const curves::NurbsCurve& profile, const Vector3D& dir); + +/// Revolve a profile around an axis +[[nodiscard]] BrepModel revolve(const curves::NurbsCurve& profile, + const Point3D& axis_origin, + const Vector3D& axis_dir, + double angle_rad = 2.0 * M_PI); + +/// Sweep a profile along a path curve +[[nodiscard]] BrepModel sweep(const curves::NurbsCurve& profile, + const curves::NurbsCurve& path); + +/// Loft between two or more profile curves +[[nodiscard]] BrepModel loft(const std::vector& profiles); + +/// Create a solid box +[[nodiscard]] BrepModel make_box(double w, double h, double d); + +/// Create a solid cylinder +[[nodiscard]] BrepModel make_cylinder(double radius, double height, int segments = 32); + +/// Create a solid sphere +[[nodiscard]] BrepModel make_sphere(double radius, int segments_u = 32, int segments_v = 16); + +/// Fillet an edge (constant radius) +/// @param body Input body +/// @param edge_id Edge to fillet +/// @param radius Fillet radius +[[nodiscard]] BrepModel fillet(const BrepModel& body, int edge_id, double radius); + +/// Chamfer an edge +[[nodiscard]] BrepModel chamfer(const BrepModel& body, int edge_id, double distance); + +/// Shell a solid body (hollow it out) +/// @param body Input solid body +/// @param face_id Face to remove (opening), -1 for closed shell +/// @param thickness Wall thickness (positive = outward offset) +[[nodiscard]] BrepModel shell(const BrepModel& body, int face_id, double thickness); + +} // namespace vde::brep diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e90c94d..411f30d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -129,5 +129,19 @@ target_link_libraries(vde PUBLIC vde_spatial PUBLIC vde_boolean PUBLIC vde_collision + PUBLIC vde_brep ) add_library(vde::engine ALIAS vde) + +# ── brep ─────────────────────────────────────────── +add_library(vde_brep STATIC + brep/brep.cpp + brep/modeling.cpp +) +target_include_directories(vde_brep + PUBLIC ${CMAKE_SOURCE_DIR}/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} +) +target_link_libraries(vde_brep + PUBLIC vde_curves vde_mesh vde_compile_options +) diff --git a/src/brep/brep.cpp b/src/brep/brep.cpp new file mode 100644 index 0000000..2fe969d --- /dev/null +++ b/src/brep/brep.cpp @@ -0,0 +1,125 @@ +#include "vde/brep/brep.h" +#include + +namespace vde::brep { + +int BrepModel::add_vertex(const Point3D& p) { + int id = next_id_++; + vertices_.push_back({id, p}); + return id; +} + +int BrepModel::add_edge(int v0, int v1) { + std::vector ctrl = {vertices_[v0].point, vertices_[v1].point}; + std::vector knots = {0,0,1,1}; + curves::NurbsCurve line(ctrl, knots, {1,1}, 1); + return add_edge(v0, v1, line); +} + +int BrepModel::add_edge(int v0, int v1, const curves::NurbsCurve& curve) { + int id = next_id_++; + edges_.push_back({id, v0, v1, std::make_shared(curve), false}); + return id; +} + +int BrepModel::add_loop(const std::vector& edge_ids, bool outer) { + int id = next_id_++; + loops_.push_back({id, edge_ids, outer}); + return id; +} + +int BrepModel::add_face(int surface_id, const std::vector& loop_ids) { + int id = next_id_++; + faces_.push_back({id, surface_id, loop_ids, false}); + return id; +} + +int BrepModel::add_shell(const std::vector& face_ids, bool closed) { + int id = next_id_++; + shells_.push_back({id, face_ids, closed}); + return id; +} + +int BrepModel::add_body(const std::vector& shell_ids, const std::string& name) { + int id = next_id_++; + bodies_.push_back({id, shell_ids, name}); + return id; +} + +int BrepModel::add_surface(const curves::NurbsSurface& surf) { + int id = next_id_++; + surfaces_.push_back(surf); + return id; +} + +core::AABB3D BrepModel::bounds() const { + core::AABB3D box; + for (const auto& v : vertices_) box.expand(v.point); + return box; +} + +bool BrepModel::is_valid() const { + // Basic validity: all referenced IDs must exist + for (const auto& e : edges_) { + if (e.v_start >= static_cast(vertices_.size()) || + e.v_end >= static_cast(vertices_.size())) + return false; + } + for (const auto& f : faces_) { + if (f.surface_id >= static_cast(surfaces_.size())) return false; + for (int li : f.loops) + if (li >= static_cast(loops_.size())) return false; + } + return true; +} + +mesh::HalfedgeMesh BrepModel::to_mesh(double deflection) const { + mesh::HalfedgeMesh result; + std::vector all_verts; + std::vector> all_tris; + + for (const auto& face : faces_) { + if (face.surface_id < 0 || face.surface_id >= static_cast(surfaces_.size())) + continue; + const auto& surf = surfaces_[face.surface_id]; + int res = std::max(4, static_cast(1.0 / deflection)); + auto [verts, tris] = surf.tessellate(res, res); + + int offset = static_cast(all_verts.size()); + for (const auto& v : verts) all_verts.push_back(v); + for (const auto& t : tris) + all_tris.push_back({t[0]+offset, t[1]+offset, t[2]+offset}); + } + + if (!all_verts.empty()) + result.build_from_triangles(all_verts, all_tris); + return result; +} + +std::vector BrepModel::face_edges(int face_id) const { + std::vector result; + if (face_id < 0 || face_id >= static_cast(faces_.size())) return result; + for (int li : faces_[face_id].loops) + for (int ei : loops_[li].edges) + result.push_back(ei); + return result; +} + +std::vector BrepModel::edge_faces(int edge_id) const { + std::vector result; + for (size_t fi = 0; fi < faces_.size(); ++fi) + for (int li : faces_[fi].loops) + for (int ei : loops_[li].edges) + if (ei == edge_id) result.push_back(static_cast(fi)); + return result; +} + +std::vector BrepModel::vertex_edges(int vertex_id) const { + std::vector result; + for (const auto& e : edges_) + if (e.v_start == vertex_id || e.v_end == vertex_id) + result.push_back(e.id); + return result; +} + +} // namespace vde::brep diff --git a/src/brep/modeling.cpp b/src/brep/modeling.cpp new file mode 100644 index 0000000..85c5c0b --- /dev/null +++ b/src/brep/modeling.cpp @@ -0,0 +1,444 @@ +#include "vde/brep/modeling.h" +#include +#include + +namespace vde::brep { + +namespace { + +// Helper: create planar surface from 4 corner points +curves::NurbsSurface make_plane_surface(const Point3D& p0, const Point3D& p1, + const Point3D& p2, const Point3D& p3) { + std::vector> grid = {{p0, p3}, {p1, p2}}; + return curves::NurbsSurface(grid, {0,0,1,1}, {0,0,1,1}, {}, 1, 1); +} + +// Helper: approximate a circle as NURBS +curves::NurbsCurve make_circle_nurbs(const Point3D& center, const Vector3D& normal, double r) { + double w = std::sqrt(2.0) / 2.0; + Vector3D u = std::abs(normal.x()) < 0.9 ? Vector3D::UnitX().cross(normal).normalized() + : Vector3D::UnitY().cross(normal).normalized(); + Vector3D v = normal.cross(u).normalized(); + + std::vector cp = { + center + u * r, + center + (u+v) * r * w, + center + v * r, + center + (-u+v) * r * w, + center - u * r, + center + (-u-v) * r * w, + center - v * r, + center + (u-v) * r * w, + center + u * r + }; + std::vector knots = {0,0,0,0.25,0.25,0.5,0.5,0.75,0.75,1,1,1}; + std::vector weights = {1,w,1,w,1,w,1,w,1}; + return curves::NurbsCurve(cp, knots, weights, 2); +} + +} // namespace + +BrepModel make_box(double w, double h, double d) { + double hw=w/2, hh=h/2, hd=d/2; + BrepModel model; + + // 8 vertices + int v[8]; + v[0]=model.add_vertex({-hw,-hh,-hd}); v[1]=model.add_vertex({ hw,-hh,-hd}); + v[2]=model.add_vertex({ hw, hh,-hd}); v[3]=model.add_vertex({-hw, hh,-hd}); + v[4]=model.add_vertex({-hw,-hh, hd}); v[5]=model.add_vertex({ hw,-hh, hd}); + v[6]=model.add_vertex({ hw, hh, hd}); v[7]=model.add_vertex({-hw, hh, hd}); + + // 12 edges (simplified with 6 quad faces) + std::vector> face_quads = { + {0,1,2,3}, {4,5,6,7}, {0,1,5,4}, {2,3,7,6}, {1,2,6,5}, {0,3,7,4} + }; + Point3D corners[6][4] = { + {{-hw,-hh,-hd},{hw,-hh,-hd},{hw,hh,-hd},{-hw,hh,-hd}}, // front + {{-hw,-hh,hd},{hw,-hh,hd},{hw,hh,hd},{-hw,hh,hd}}, // back + {{-hw,-hh,-hd},{hw,-hh,-hd},{hw,-hh,hd},{-hw,-hh,hd}}, // bottom + {{-hw,hh,-hd},{hw,hh,-hd},{hw,hh,hd},{-hw,hh,hd}}, // top + {{hw,-hh,-hd},{hw,hh,-hd},{hw,hh,hd},{hw,-hh,hd}}, // right + {{-hw,-hh,-hd},{-hw,hh,-hd},{-hw,hh,hd},{-hw,-hh,hd}}, // left + }; + + std::vector face_ids; + for (int i = 0; i < 6; ++i) { + int s = model.add_surface(make_plane_surface(corners[i][0], corners[i][1], + corners[i][2], corners[i][3])); + std::vector edges; + for (int j = 0; j < 4; ++j) { + int va = face_quads[i][j], vb = face_quads[i][(j+1)%4]; + int e = model.add_edge(va, vb); + edges.push_back(e); + } + int loop = model.add_loop(edges, true); + int f = model.add_face(s, {loop}); + face_ids.push_back(f); + } + + int shell = model.add_shell(face_ids, true); + model.add_body({shell}, "box"); + return model; +} + +BrepModel make_cylinder(double radius, double height, int segments) { + BrepModel model; + + // Bottom and top center + int v_bot = model.add_vertex({0,0,-height/2}); + int v_top = model.add_vertex({0,0,height/2}); + + // Circular edge vertices + std::vector v_bot_ring(segments), v_top_ring(segments); + for (int i = 0; i < segments; ++i) { + double a = 2*M_PI*i/segments; + double x = radius*std::cos(a), y = radius*std::sin(a); + v_bot_ring[i] = model.add_vertex({x,y,-height/2}); + v_top_ring[i] = model.add_vertex({x,y,height/2}); + } + + // Bottom face + std::vector bot_edges; + for (int i = 0; i < segments; ++i) { + int e = model.add_edge(v_bot_ring[i], v_bot_ring[(i+1)%segments]); + bot_edges.push_back(e); + } + int bot_loop = model.add_loop(bot_edges, true); + int bot_surf = model.add_surface(curves::NurbsSurface( + {{{-radius,-radius,-height/2},{radius,-radius,-height/2}}, + {{-radius,radius,-height/2},{radius,radius,-height/2}}}, + {0,0,1,1},{0,0,1,1},{},1,1)); + int bot_face = model.add_face(bot_surf, {bot_loop}); + + // Top face + std::vector top_edges; + for (int i = 0; i < segments; ++i) { + int e = model.add_edge(v_top_ring[(i+1)%segments], v_top_ring[i]); + top_edges.push_back(e); + } + int top_loop = model.add_loop(top_edges, true); + int top_surf_id = model.add_surface(curves::NurbsSurface( + {{{-radius,-radius,height/2},{radius,-radius,height/2}}, + {{-radius,radius,height/2},{radius,radius,height/2}}}, + {0,0,1,1},{0,0,1,1},{},1,1)); + int top_face = model.add_face(top_surf_id, {top_loop}); + + // Side faces (quad per segment) + std::vector side_faces; + for (int i = 0; i < segments; ++i) { + int j = (i+1)%segments; + int e1 = model.add_edge(v_bot_ring[i], v_top_ring[i]); + int e2 = model.add_edge(v_top_ring[i], v_top_ring[j]); + int e3 = model.add_edge(v_top_ring[j], v_bot_ring[j]); + int e4 = model.add_edge(v_bot_ring[j], v_bot_ring[i]); + + std::vector side_edges = {e1, e2, e3, e4}; + int loop = model.add_loop(side_edges, true); + + double a0 = 2*M_PI*i/segments, a1 = 2*M_PI*j/segments; + Point3D p0(radius*std::cos(a0), radius*std::sin(a0), -height/2); + Point3D p1(radius*std::cos(a1), radius*std::sin(a1), -height/2); + Point3D p2(radius*std::cos(a1), radius*std::sin(a1), height/2); + Point3D p3(radius*std::cos(a0), radius*std::sin(a0), height/2); + int surf = model.add_surface(make_plane_surface(p0,p1,p2,p3)); + side_faces.push_back(model.add_face(surf, {loop})); + } + + std::vector all_faces; + all_faces.push_back(bot_face); + all_faces.push_back(top_face); + all_faces.insert(all_faces.end(), side_faces.begin(), side_faces.end()); + + int shell = model.add_shell(all_faces, true); + model.add_body({shell}, "cylinder"); + return model; +} + +BrepModel make_sphere(double radius, int seg_u, int seg_v) { + BrepModel model; + int v_top = model.add_vertex({0,0,radius}); + int v_bot = model.add_vertex({0,0,-radius}); + + // Lat/lon vertices + std::vector> ring_verts(seg_v+1); + for (int j = 1; j < seg_v; ++j) { + double phi = M_PI * j / seg_v; + double z = radius * std::cos(phi); + double r = radius * std::sin(phi); + for (int i = 0; i < seg_u; ++i) { + double theta = 2*M_PI*i/seg_u; + ring_verts[j].push_back(model.add_vertex({r*std::cos(theta), r*std::sin(theta), z})); + } + } + + // Equatorial ring for the first row + for (int i = 0; i < seg_u; ++i) { + double theta = 2*M_PI*i/seg_u; + ring_verts[0].push_back(model.add_vertex( + {radius*std::cos(theta), radius*std::sin(theta), 0})); + } + + std::vector all_faces; + + // Top cap (triangles to v_top) + for (int i = 0; i < seg_u; ++i) { + int j = (i+1)%seg_u; + int e1 = model.add_edge(v_top, ring_verts[1][i]); + int e2 = model.add_edge(ring_verts[1][i], ring_verts[1][j]); + int e3 = model.add_edge(ring_verts[1][j], v_top); + int loop = model.add_loop({e1,e2,e3}, true); + // Spherical patch surface (simplified as plane) + Point3D cp[3] = {{0,0,radius}, model.vertex(ring_verts[1][i]).point, + model.vertex(ring_verts[1][j]).point}; + auto grid = std::vector>{{cp[0], cp[2]}, {cp[0], cp[1]}}; + int surf = model.add_surface(curves::NurbsSurface(grid,{0,0,1,1},{0,0,1,1},{},1,1)); + all_faces.push_back(model.add_face(surf, {loop})); + } + + // Middle quads + for (int row = 1; row < seg_v-1; ++row) { + for (int i = 0; i < seg_u; ++i) { + int j = (i+1)%seg_u; + int v0=ring_verts[row][i], v1=ring_verts[row][j]; + int v2=ring_verts[row+1][j], v3=ring_verts[row+1][i]; + int e1=model.add_edge(v0,v1), e2=model.add_edge(v1,v2); + int e3=model.add_edge(v2,v3), e4=model.add_edge(v3,v0); + int loop=model.add_loop({e1,e2,e3,e4}, true); + Point3D p0=model.vertex(v0).point, p1=model.vertex(v1).point; + Point3D p2=model.vertex(v2).point, p3=model.vertex(v3).point; + int surf=model.add_surface(make_plane_surface(p0,p1,p2,p3)); + all_faces.push_back(model.add_face(surf, {loop})); + } + } + + // Bottom cap + int last_row = seg_v-1; + for (int i = 0; i < seg_u; ++i) { + int j = (i+1)%seg_u; + int e1=model.add_edge(ring_verts[last_row][i], v_bot); + int e2=model.add_edge(v_bot, ring_verts[last_row][j]); + int e3=model.add_edge(ring_verts[last_row][j], ring_verts[last_row][i]); + int loop=model.add_loop({e1,e2,e3}, true); + auto grid = std::vector>{ + {{0,0,-radius}, model.vertex(ring_verts[last_row][j]).point}, + {{0,0,-radius}, model.vertex(ring_verts[last_row][i]).point}}; + int surf=model.add_surface(curves::NurbsSurface(grid,{0,0,1,1},{0,0,1,1},{},1,1)); + all_faces.push_back(model.add_face(surf, {loop})); + } + + int shell=model.add_shell(all_faces, true); + model.add_body({shell}, "sphere"); + return model; +} + +BrepModel extrude(const curves::NurbsCurve& profile, const Vector3D& dir) { + BrepModel result; + const auto& cp = profile.control_points(); + int n = static_cast(cp.size()); + + // Bottom face vertices + std::vector bot_verts, top_verts; + for (int i = 0; i < n; ++i) { + bot_verts.push_back(result.add_vertex(cp[i])); + top_verts.push_back(result.add_vertex(cp[i] + dir)); + } + + // Bottom face edges + std::vector bot_edges; + for (int i = 0; i < n; ++i) { + int j = (i+1)%n; + bot_edges.push_back(result.add_edge(bot_verts[i], bot_verts[j])); + } + int bot_loop = result.add_loop(bot_edges, true); + + // Bottom surface (planar) + // ... simplified: use bounding box plane + Point3D bmin=cp[0], bmax=cp[0]; + for (const auto& p : cp) { bmin=bmin.cwiseMin(p); bmax=bmax.cwiseMax(p); } + curves::NurbsSurface bot_surf({{{bmin.x(),bmin.y(),bmin.z()},{bmax.x(),bmin.y(),bmin.z()}}, + {{bmin.x(),bmax.y(),bmin.z()},{bmax.x(),bmax.y(),bmin.z()}}}, + {0,0,1,1},{0,0,1,1},{},1,1); + int bot_surf_id = result.add_surface(bot_surf); + result.add_face(bot_surf_id, {bot_loop}); + + // Top face (same edges, reversed) + std::vector top_edges; + for (int i = 0; i < n; ++i) { + int j = (i+1)%n; + top_edges.push_back(result.add_edge(top_verts[j], top_verts[i])); + } + int top_loop = result.add_loop(top_edges, true); + curves::NurbsSurface top_surf({{{bmin.x()+dir.x(),bmin.y()+dir.y(),bmin.z()+dir.z()}, + {bmax.x()+dir.x(),bmin.y()+dir.y(),bmin.z()+dir.z()}}, + {{bmin.x()+dir.x(),bmax.y()+dir.y(),bmin.z()+dir.z()}, + {bmax.x()+dir.x(),bmax.y()+dir.y(),bmin.z()+dir.z()}}}, + {0,0,1,1},{0,0,1,1},{},1,1); + int top_surf_id = result.add_surface(top_surf); + result.add_face(top_surf_id, {top_loop}); + + // Side faces + std::vector all_faces = {0, 1}; + for (int i = 0; i < n; ++i) { + int j = (i+1)%n; + int e1 = result.add_edge(bot_verts[i], top_verts[i]); + int e2 = result.add_edge(top_verts[i], top_verts[j]); + int e3 = result.add_edge(top_verts[j], bot_verts[j]); + int e4 = result.add_edge(bot_verts[j], bot_verts[i]); + int loop = result.add_loop({e1, e2, e3, e4}, true); + Point3D p0=cp[i], p1=cp[j], p2=cp[j]+dir, p3=cp[i]+dir; + int surf = result.add_surface(make_plane_surface(p0,p1,p2,p3)); + all_faces.push_back(result.add_face(surf, {loop})); + } + + int shell = result.add_shell(all_faces, true); + result.add_body({shell}, "extrude"); + return result; +} + +BrepModel revolve(const curves::NurbsCurve& profile, + const Point3D& axis_origin, const Vector3D& axis_dir, double angle) { + // Simplified: create surface of revolution and wrap in B-Rep + auto surf = curves::NurbsSurface::revolve(profile, axis_origin, axis_dir, angle); (void)surf; + BrepModel result; + // Discrete approximation + const int samples = 32; + int n = static_cast(profile.control_points().size()); + Vector3D ax = axis_dir.normalized(); + + // Build vertices in rings + std::vector> rings(samples+1); + for (int i = 0; i <= samples; ++i) { + double theta = angle * i / samples; + Eigen::AngleAxis rot(theta, ax); + for (int j = 0; j < n; ++j) { + Vector3D rel = profile.control_points()[j] - axis_origin; + rings[i].push_back(result.add_vertex(axis_origin + rot * rel)); + } + } + + std::vector all_faces; + for (int i = 0; i < samples; ++i) { + for (int j = 0; j < n-1; ++j) { + int v00=rings[i][j], v10=rings[i+1][j]; + int v11=rings[i+1][j+1], v01=rings[i][j+1]; + int e1=result.add_edge(v00,v10), e2=result.add_edge(v10,v11); + int e3=result.add_edge(v11,v01), e4=result.add_edge(v01,v00); + int loop=result.add_loop({e1,e2,e3,e4}, true); + Point3D p0=result.vertex(v00).point, p1=result.vertex(v10).point; + Point3D p2=result.vertex(v11).point, p3=result.vertex(v01).point; + int s=result.add_surface(make_plane_surface(p0,p1,p2,p3)); + all_faces.push_back(result.add_face(s, {loop})); + } + } + + int shell=result.add_shell(all_faces, true); + result.add_body({shell}, "revolve"); + return result; +} + +BrepModel sweep(const curves::NurbsCurve& profile, const curves::NurbsCurve& path) { + BrepModel result; + int samples = static_cast(std::max(16.0, path.control_points().size() * 2.0)); + const auto& pcp = profile.control_points(); + int pn = static_cast(pcp.size()); + + // Sample path positions and frames + std::vector positions; + std::vector tangents; + auto [t0, t1] = path.domain(); + for (int i = 0; i <= samples; ++i) { + double t = t0 + (t1-t0)*i/samples; + positions.push_back(path.evaluate(t)); + auto dt = path.derivative(t, 1); + tangents.push_back(dt.norm() > 1e-10 ? dt.normalized() : Vector3D::UnitZ()); + } + + // Build profile at each sample position + std::vector> profile_rings(samples+1); + for (int i = 0; i <= samples; ++i) { + // Build local frame + Vector3D T = tangents[i]; + Vector3D N = std::abs(T.x()) < 0.9 ? Vector3D::UnitX().cross(T).normalized() + : Vector3D::UnitY().cross(T).normalized(); + Vector3D B = T.cross(N); + + for (int j = 0; j < pn; ++j) { + Point3D p = positions[i] + pcp[j].x()*N + pcp[j].y()*B + pcp[j].z()*T; + profile_rings[i].push_back(result.add_vertex(p)); + } + } + + std::vector all_faces; + for (int i = 0; i < samples; ++i) { + for (int j = 0; j < pn-1; ++j) { + int v00=profile_rings[i][j], v10=profile_rings[i+1][j]; + int v11=profile_rings[i+1][j+1], v01=profile_rings[i][j+1]; + int e1=result.add_edge(v00,v10), e2=result.add_edge(v10,v11); + int e3=result.add_edge(v11,v01), e4=result.add_edge(v01,v00); + int loop=result.add_loop({e1,e2,e3,e4}, true); + Point3D p0=result.vertex(v00).point, p1=result.vertex(v10).point; + Point3D p2=result.vertex(v11).point, p3=result.vertex(v01).point; + int s=result.add_surface(make_plane_surface(p0,p1,p2,p3)); + all_faces.push_back(result.add_face(s, {loop})); + } + } + + int shell=result.add_shell(all_faces, true); + result.add_body({shell}, "sweep"); + return result; +} + +BrepModel loft(const std::vector& profiles) { + if (profiles.size() < 2) return BrepModel(); + BrepModel result; + + int pn = static_cast(profiles[0].control_points().size()); + std::vector> rings; + + for (const auto& profile : profiles) { + std::vector ring; + for (const auto& cp : profile.control_points()) + ring.push_back(result.add_vertex(cp)); + rings.push_back(ring); + } + + std::vector all_faces; + for (size_t i = 0; i+1 < rings.size(); ++i) { + for (int j = 0; j < pn; ++j) { + int k = (j+1)%pn; + int v00=rings[i][j], v10=rings[i+1][j]; + int v11=rings[i+1][k], v01=rings[i][k]; + int e1=result.add_edge(v00,v10), e2=result.add_edge(v10,v11); + int e3=result.add_edge(v11,v01), e4=result.add_edge(v01,v00); + int loop=result.add_loop({e1,e2,e3,e4}, true); + Point3D p0=result.vertex(v00).point, p1=result.vertex(v10).point; + Point3D p2=result.vertex(v11).point, p3=result.vertex(v01).point; + int s=result.add_surface(make_plane_surface(p0,p1,p2,p3)); + all_faces.push_back(result.add_face(s, {loop})); + } + } + + int shell=result.add_shell(all_faces, true); + result.add_body({shell}, "loft"); + return result; +} + +BrepModel fillet(const BrepModel& body, int edge_id, double radius) { + (void)edge_id; (void)radius; + return body; // TODO: full fillet with rolling ball +} + +BrepModel chamfer(const BrepModel& body, int edge_id, double distance) { + (void)edge_id; (void)distance; + return body; // TODO: full chamfer +} + +BrepModel shell(const BrepModel& body, int face_id, double thickness) { + BrepModel result = body; + // TODO: offset all faces except face_id by thickness + (void)face_id; (void)thickness; + return result; +} + +} // namespace vde::brep