feat: P2 B-Rep 内核 + 建模操作
- 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 模块,更新聚合库
This commit is contained in:
@@ -30,6 +30,7 @@ endif()
|
||||
|
||||
# ── 子目录 ────────────────────────────────────────
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(src)
|
||||
|
||||
# ── 测试 ──────────────────────────────────────────
|
||||
if(BUILD_TESTS)
|
||||
|
||||
@@ -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 <vector>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
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<curves::NurbsCurve> curve;
|
||||
bool reversed = false;
|
||||
};
|
||||
struct TopoLoop { int id; std::vector<int> edges; bool is_outer = true; };
|
||||
struct TopoFace { int id, surface_id; std::vector<int> loops; bool reversed = false; };
|
||||
struct TopoShell { int id; std::vector<int> faces; bool closed = true; };
|
||||
struct TopoBody { int id; std::vector<int> 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<int>& edges, bool outer = true);
|
||||
int add_face(int surface_id, const std::vector<int>& loops);
|
||||
int add_shell(const std::vector<int>& faces, bool closed = true);
|
||||
int add_body(const std::vector<int>& 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<int> face_edges(int face_id) const;
|
||||
[[nodiscard]] std::vector<int> edge_faces(int edge_id) const;
|
||||
[[nodiscard]] std::vector<int> vertex_edges(int vertex_id) const;
|
||||
|
||||
private:
|
||||
std::vector<TopoVertex> vertices_;
|
||||
std::vector<TopoEdge> edges_;
|
||||
std::vector<TopoLoop> loops_;
|
||||
std::vector<TopoFace> faces_;
|
||||
std::vector<TopoShell> shells_;
|
||||
std::vector<TopoBody> bodies_;
|
||||
std::vector<curves::NurbsSurface> surfaces_;
|
||||
int next_id_ = 0;
|
||||
};
|
||||
|
||||
} // namespace vde::brep
|
||||
@@ -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<curves::NurbsCurve>& 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
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "vde/brep/brep.h"
|
||||
#include <algorithm>
|
||||
|
||||
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<Point3D> ctrl = {vertices_[v0].point, vertices_[v1].point};
|
||||
std::vector<double> 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<curves::NurbsCurve>(curve), false});
|
||||
return id;
|
||||
}
|
||||
|
||||
int BrepModel::add_loop(const std::vector<int>& 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<int>& loop_ids) {
|
||||
int id = next_id_++;
|
||||
faces_.push_back({id, surface_id, loop_ids, false});
|
||||
return id;
|
||||
}
|
||||
|
||||
int BrepModel::add_shell(const std::vector<int>& face_ids, bool closed) {
|
||||
int id = next_id_++;
|
||||
shells_.push_back({id, face_ids, closed});
|
||||
return id;
|
||||
}
|
||||
|
||||
int BrepModel::add_body(const std::vector<int>& 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<int>(vertices_.size()) ||
|
||||
e.v_end >= static_cast<int>(vertices_.size()))
|
||||
return false;
|
||||
}
|
||||
for (const auto& f : faces_) {
|
||||
if (f.surface_id >= static_cast<int>(surfaces_.size())) return false;
|
||||
for (int li : f.loops)
|
||||
if (li >= static_cast<int>(loops_.size())) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
mesh::HalfedgeMesh BrepModel::to_mesh(double deflection) const {
|
||||
mesh::HalfedgeMesh result;
|
||||
std::vector<Point3D> all_verts;
|
||||
std::vector<std::array<int,3>> all_tris;
|
||||
|
||||
for (const auto& face : faces_) {
|
||||
if (face.surface_id < 0 || face.surface_id >= static_cast<int>(surfaces_.size()))
|
||||
continue;
|
||||
const auto& surf = surfaces_[face.surface_id];
|
||||
int res = std::max(4, static_cast<int>(1.0 / deflection));
|
||||
auto [verts, tris] = surf.tessellate(res, res);
|
||||
|
||||
int offset = static_cast<int>(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<int> BrepModel::face_edges(int face_id) const {
|
||||
std::vector<int> result;
|
||||
if (face_id < 0 || face_id >= static_cast<int>(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<int> BrepModel::edge_faces(int edge_id) const {
|
||||
std::vector<int> 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<int>(fi));
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int> BrepModel::vertex_edges(int vertex_id) const {
|
||||
std::vector<int> 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
|
||||
@@ -0,0 +1,444 @@
|
||||
#include "vde/brep/modeling.h"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
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<std::vector<Point3D>> 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<Point3D> 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<double> knots = {0,0,0,0.25,0.25,0.5,0.5,0.75,0.75,1,1,1};
|
||||
std::vector<double> 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<std::vector<int>> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<std::vector<int>> 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<int> 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<std::vector<Point3D>>{{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<std::vector<Point3D>>{
|
||||
{{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<int>(cp.size());
|
||||
|
||||
// Bottom face vertices
|
||||
std::vector<int> 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<int> 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<int> 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<int> 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<int>(profile.control_points().size());
|
||||
Vector3D ax = axis_dir.normalized();
|
||||
|
||||
// Build vertices in rings
|
||||
std::vector<std::vector<int>> rings(samples+1);
|
||||
for (int i = 0; i <= samples; ++i) {
|
||||
double theta = angle * i / samples;
|
||||
Eigen::AngleAxis<double> 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<int> 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<int>(std::max(16.0, path.control_points().size() * 2.0));
|
||||
const auto& pcp = profile.control_points();
|
||||
int pn = static_cast<int>(pcp.size());
|
||||
|
||||
// Sample path positions and frames
|
||||
std::vector<Point3D> positions;
|
||||
std::vector<Vector3D> 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<std::vector<int>> 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<int> 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<curves::NurbsCurve>& profiles) {
|
||||
if (profiles.size() < 2) return BrepModel();
|
||||
BrepModel result;
|
||||
|
||||
int pn = static_cast<int>(profiles[0].control_points().size());
|
||||
std::vector<std::vector<int>> rings;
|
||||
|
||||
for (const auto& profile : profiles) {
|
||||
std::vector<int> ring;
|
||||
for (const auto& cp : profile.control_points())
|
||||
ring.push_back(result.add_vertex(cp));
|
||||
rings.push_back(ring);
|
||||
}
|
||||
|
||||
std::vector<int> 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
|
||||
Reference in New Issue
Block a user