2026-07-24 14:25:51 +00:00
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
#include "vde/brep/trimmed_surface.h"
|
2026-07-26 20:35:24 +08:00
|
|
|
|
#include "vde/brep/brep.h"
|
|
|
|
|
|
#include "vde/brep/modeling.h"
|
2026-07-24 14:25:51 +00:00
|
|
|
|
#include "vde/curves/nurbs_surface.h"
|
|
|
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
|
|
|
|
using namespace vde::brep;
|
|
|
|
|
|
using namespace vde::curves;
|
|
|
|
|
|
using vde::core::Point3D;
|
|
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
|
|
/// Helper: create a simple 10×10 planar NURBS surface in the XY plane
|
|
|
|
|
|
NurbsSurface make_plane() {
|
|
|
|
|
|
std::vector<std::vector<Point3D>> cp = {
|
|
|
|
|
|
{Point3D(0, 0, 0), Point3D(0, 10, 0)},
|
|
|
|
|
|
{Point3D(10, 0, 0), Point3D(10, 10, 0)}
|
|
|
|
|
|
};
|
|
|
|
|
|
std::vector<double> ku = {0, 0, 1, 1};
|
|
|
|
|
|
std::vector<double> kv = {0, 0, 1, 1};
|
|
|
|
|
|
std::vector<std::vector<double>> w = {{1, 1}, {1, 1}};
|
|
|
|
|
|
return NurbsSurface(cp, ku, kv, w, 1, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Helper: create a linear p-curve line segment in parameter space
|
|
|
|
|
|
PCurve make_pcurve_line(double ua, double va, double ub, double vb) {
|
|
|
|
|
|
std::vector<Point3D> cps = { Point3D(ua, va, 0.0), Point3D(ub, vb, 0.0) };
|
|
|
|
|
|
std::vector<double> knots = { 0.0, 0.0, 1.0, 1.0 };
|
|
|
|
|
|
std::vector<double> weights = { 1.0, 1.0 };
|
|
|
|
|
|
return PCurve(cps, knots, weights, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Helper: create a rectangular trim loop
|
|
|
|
|
|
TrimLoop make_rect_loop(double u0, double u1, double v0, double v1) {
|
|
|
|
|
|
TrimLoop loop;
|
|
|
|
|
|
loop.is_outer = true;
|
|
|
|
|
|
loop.p_curves.push_back(make_pcurve_line(u0, v0, u1, v0)); // bottom
|
|
|
|
|
|
loop.p_curves.push_back(make_pcurve_line(u1, v0, u1, v1)); // right
|
|
|
|
|
|
loop.p_curves.push_back(make_pcurve_line(u1, v1, u0, v1)); // top
|
|
|
|
|
|
loop.p_curves.push_back(make_pcurve_line(u0, v1, u0, v0)); // left
|
|
|
|
|
|
return loop;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// 1-3: Untrimmed surface
|
2026-07-24 14:25:51 +00:00
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, Untrimmed_EvaluatesAllCorners) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_surface(surf);
|
|
|
|
|
|
|
|
|
|
|
|
auto p00 = ts.evaluate(0.0, 0.0);
|
|
|
|
|
|
ASSERT_TRUE(p00.has_value());
|
|
|
|
|
|
EXPECT_NEAR(p00->x(), 0.0, 1e-6);
|
|
|
|
|
|
|
|
|
|
|
|
auto p11 = ts.evaluate(1.0, 1.0);
|
|
|
|
|
|
ASSERT_TRUE(p11.has_value());
|
|
|
|
|
|
EXPECT_NEAR(p11->x(), 10.0, 1e-6);
|
|
|
|
|
|
EXPECT_NEAR(p11->y(), 10.0, 1e-6);
|
|
|
|
|
|
|
|
|
|
|
|
auto pc = ts.evaluate(0.5, 0.5);
|
|
|
|
|
|
ASSERT_TRUE(pc.has_value());
|
|
|
|
|
|
EXPECT_NEAR(pc->x(), 5.0, 1e-6);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, Untrimmed_IsInsideAlwaysTrue) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_surface(surf);
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(0.0, 0.0));
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(0.5, 0.5));
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(1.0, 1.0));
|
2026-07-26 20:35:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, Untrimmed_WindingNumber) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_surface(surf);
|
|
|
|
|
|
EXPECT_TRUE(ts.loops.empty());
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(0.3, 0.7));
|
2026-07-24 14:25:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// 4-6: Rectangular trim
|
2026-07-24 14:25:51 +00:00
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, RectTrim_InsideRegion) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect(surf, 0.0, 0.5, 0.0, 0.5);
|
|
|
|
|
|
|
|
|
|
|
|
auto p = ts.evaluate(0.25, 0.25);
|
|
|
|
|
|
ASSERT_TRUE(p.has_value());
|
|
|
|
|
|
EXPECT_NEAR(p->x(), 2.5, 1e-6);
|
|
|
|
|
|
EXPECT_NEAR(p->y(), 2.5, 1e-6);
|
|
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(0.25, 0.25));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, RectTrim_OutsideRegion) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect(surf, 0.0, 0.5, 0.0, 0.5);
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
EXPECT_FALSE(ts.evaluate(0.75, 0.25).has_value());
|
2026-07-24 14:25:51 +00:00
|
|
|
|
EXPECT_FALSE(ts.is_inside(0.75, 0.25));
|
|
|
|
|
|
EXPECT_FALSE(ts.is_inside(0.25, 0.75));
|
|
|
|
|
|
EXPECT_FALSE(ts.is_inside(0.75, 0.75));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, RectTrim_BoundaryIsInside) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect(surf, 0.0, 0.5, 0.0, 0.5);
|
|
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(0.0, 0.0));
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(0.5, 0.5));
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(0.0, 0.5));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// 7-9: Hole (inner loop)
|
2026-07-24 14:25:51 +00:00
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, Hole_CenterIsOutside) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
TrimmedSurface ts;
|
|
|
|
|
|
ts.base_surface = surf;
|
|
|
|
|
|
ts.loops.push_back(make_rect_loop(0.0, 1.0, 0.0, 1.0));
|
|
|
|
|
|
auto hole = make_rect_loop(0.25, 0.75, 0.25, 0.75);
|
|
|
|
|
|
hole.is_outer = false;
|
|
|
|
|
|
ts.loops.push_back(hole);
|
|
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(ts.is_inside(0.5, 0.5));
|
|
|
|
|
|
auto p_center = ts.evaluate(0.5, 0.5);
|
|
|
|
|
|
EXPECT_FALSE(p_center.has_value());
|
2026-07-26 20:35:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, Hole_CornerIsInside) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
TrimmedSurface ts;
|
|
|
|
|
|
ts.base_surface = surf;
|
|
|
|
|
|
ts.loops.push_back(make_rect_loop(0.0, 1.0, 0.0, 1.0));
|
|
|
|
|
|
auto hole = make_rect_loop(0.25, 0.75, 0.25, 0.75);
|
|
|
|
|
|
hole.is_outer = false;
|
|
|
|
|
|
ts.loops.push_back(hole);
|
2026-07-24 14:25:51 +00:00
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(0.1, 0.1));
|
|
|
|
|
|
auto p_corner = ts.evaluate(0.1, 0.1);
|
|
|
|
|
|
ASSERT_TRUE(p_corner.has_value());
|
|
|
|
|
|
EXPECT_NEAR(p_corner->x(), 1.0, 1e-6);
|
2026-07-26 20:35:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, Hole_BoundaryIsOutside) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
TrimmedSurface ts;
|
|
|
|
|
|
ts.base_surface = surf;
|
|
|
|
|
|
ts.loops.push_back(make_rect_loop(0.0, 1.0, 0.0, 1.0));
|
|
|
|
|
|
auto hole = make_rect_loop(0.25, 0.75, 0.25, 0.75);
|
|
|
|
|
|
hole.is_outer = false;
|
|
|
|
|
|
ts.loops.push_back(hole);
|
2026-07-24 14:25:51 +00:00
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// Point on hole boundary → outside
|
|
|
|
|
|
EXPECT_FALSE(ts.is_inside(0.25, 0.25));
|
2026-07-24 14:25:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// 10-11: Circular hole (from_rect_with_hole)
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, CircularHole_CenterOutside) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect_with_hole(surf, 0.0, 1.0, 0.0, 1.0,
|
|
|
|
|
|
0.5, 0.5, 0.2, 32);
|
|
|
|
|
|
|
|
|
|
|
|
// Center of hole → outside
|
|
|
|
|
|
EXPECT_FALSE(ts.is_inside(0.5, 0.5));
|
|
|
|
|
|
auto p = ts.evaluate(0.5, 0.5);
|
|
|
|
|
|
EXPECT_FALSE(p.has_value());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, CircularHole_CornerInside) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect_with_hole(surf, 0.0, 1.0, 0.0, 1.0,
|
|
|
|
|
|
0.5, 0.5, 0.2, 32);
|
|
|
|
|
|
|
|
|
|
|
|
// Corner → inside outer, outside hole
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(0.1, 0.1));
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(0.9, 0.9));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
// 12-13: Bounds
|
2026-07-24 14:25:51 +00:00
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, Bounds_Untrimmed) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_surface(surf);
|
|
|
|
|
|
auto b = ts.bounds();
|
|
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(b.min().x(), 0.0, 1e-6);
|
|
|
|
|
|
EXPECT_NEAR(b.max().x(), 10.0, 1e-6);
|
|
|
|
|
|
EXPECT_NEAR(b.min().y(), 0.0, 1e-6);
|
|
|
|
|
|
EXPECT_NEAR(b.max().y(), 10.0, 1e-6);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, Bounds_RectangularTrim) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect(surf, 0.25, 0.75, 0.25, 0.75);
|
|
|
|
|
|
auto b = ts.bounds();
|
|
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(b.min().x(), 2.5, 1e-6);
|
|
|
|
|
|
EXPECT_NEAR(b.max().x(), 7.5, 1e-6);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
// 14-15: Winding number
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, WindingNumber_RectOuter) {
|
2026-07-24 14:25:51 +00:00
|
|
|
|
auto surf = make_plane();
|
2026-07-26 20:35:24 +08:00
|
|
|
|
auto ts = TrimmedSurface::from_rect(surf, 0.2, 0.8, 0.2, 0.8);
|
2026-07-24 14:25:51 +00:00
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
int w = ts.winding_number(0.5, 0.5, ts.loops[0]);
|
|
|
|
|
|
EXPECT_GT(std::abs(w), 0); // Inside outer loop
|
|
|
|
|
|
|
|
|
|
|
|
int w_out = ts.winding_number(0.0, 0.0, ts.loops[0]);
|
|
|
|
|
|
EXPECT_EQ(w_out, 0); // Outside
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, WindingNumber_Hole) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect_with_hole(surf, 0.0, 1.0, 0.0, 1.0,
|
|
|
|
|
|
0.4, 0.4, 0.15, 32);
|
|
|
|
|
|
ASSERT_EQ(ts.loops.size(), 2u);
|
|
|
|
|
|
|
|
|
|
|
|
// Hole loop at center
|
|
|
|
|
|
int w_hole_in = ts.winding_number(0.4, 0.4, ts.loops[1]);
|
|
|
|
|
|
EXPECT_GT(std::abs(w_hole_in), 0);
|
2026-07-24 14:25:51 +00:00
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
int w_hole_out = ts.winding_number(0.1, 0.1, ts.loops[1]);
|
|
|
|
|
|
EXPECT_EQ(w_hole_out, 0);
|
2026-07-24 14:25:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// 16: Closest boundary point
|
2026-07-24 14:25:51 +00:00
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
TEST(TrimmedSurfaceTest, ClosestBoundary) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect(surf, 0.25, 0.75, 0.25, 0.75);
|
|
|
|
|
|
|
|
|
|
|
|
// Point outside → should project to boundary
|
|
|
|
|
|
auto bp = ts.closest_boundary_point(1.0, 0.5);
|
|
|
|
|
|
// Should be near the right edge of trimmed region in 3D
|
|
|
|
|
|
EXPECT_NEAR(bp.x(), 7.5, 1e-5); // u=0.75 → x=7.5
|
|
|
|
|
|
EXPECT_NEAR(bp.y(), 5.0, 1e-5); // v=0.5 → y=5.0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
// 17-18: to_mesh
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, ToMesh_Untrimmed) {
|
2026-07-24 14:25:51 +00:00
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_surface(surf);
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
auto mesh = ts.to_mesh(0.1);
|
|
|
|
|
|
EXPECT_GT(mesh.num_vertices(), 0u);
|
|
|
|
|
|
EXPECT_GT(mesh.num_faces(), 0u);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, ToMesh_RectTrimmed) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect(surf, 0.25, 0.75, 0.25, 0.75);
|
|
|
|
|
|
|
|
|
|
|
|
auto mesh = ts.to_mesh(0.1);
|
|
|
|
|
|
EXPECT_GT(mesh.num_vertices(), 0u);
|
|
|
|
|
|
EXPECT_GT(mesh.num_faces(), 0u);
|
|
|
|
|
|
|
|
|
|
|
|
// All vertices should be inside the trimmed region bounds
|
|
|
|
|
|
auto b = mesh.bounds();
|
|
|
|
|
|
EXPECT_GE(b.min().x(), 2.0);
|
|
|
|
|
|
EXPECT_LE(b.max().x(), 8.0);
|
2026-07-24 14:25:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
2026-07-26 20:35:24 +08:00
|
|
|
|
// 19-20: BrepModel integration
|
2026-07-24 14:25:51 +00:00
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
TEST(TrimmedSurfaceTest, BrepModel_AddTrimmedSurface) {
|
|
|
|
|
|
BrepModel model;
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect(surf, 0.0, 1.0, 0.0, 1.0);
|
|
|
|
|
|
|
|
|
|
|
|
int tid = model.add_trimmed_surface(ts);
|
|
|
|
|
|
EXPECT_EQ(tid, 0);
|
|
|
|
|
|
EXPECT_EQ(model.num_trimmed_surfaces(), 1u);
|
|
|
|
|
|
|
|
|
|
|
|
const auto& stored = model.trimmed_surface(tid);
|
|
|
|
|
|
EXPECT_EQ(stored.loops.size(), 1u);
|
|
|
|
|
|
EXPECT_TRUE(stored.loops[0].is_outer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, BrepModel_SetFaceTrimmedSurface) {
|
|
|
|
|
|
BrepModel model;
|
2026-07-24 14:25:51 +00:00
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect(surf, 0.0, 1.0, 0.0, 1.0);
|
2026-07-26 20:35:24 +08:00
|
|
|
|
int tid = model.add_trimmed_surface(ts);
|
|
|
|
|
|
|
|
|
|
|
|
int v0 = model.add_vertex({0,0,0});
|
|
|
|
|
|
int v1 = model.add_vertex({1,0,0});
|
|
|
|
|
|
int v2 = model.add_vertex({1,1,0});
|
|
|
|
|
|
int v3 = model.add_vertex({0,1,0});
|
|
|
|
|
|
int e1 = model.add_edge(v0, v1);
|
|
|
|
|
|
int e2 = model.add_edge(v1, v2);
|
|
|
|
|
|
int e3 = model.add_edge(v2, v3);
|
|
|
|
|
|
int e4 = model.add_edge(v3, v0);
|
|
|
|
|
|
int lp = model.add_loop({e1,e2,e3,e4}, true);
|
|
|
|
|
|
int sid = model.add_surface(surf);
|
|
|
|
|
|
int fid = model.add_face(sid, {lp});
|
|
|
|
|
|
model.set_face_trimmed_surface(fid, tid);
|
|
|
|
|
|
|
|
|
|
|
|
const auto& face = model.face(fid);
|
|
|
|
|
|
EXPECT_EQ(face.trimmed_surface_id, tid);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, BrepModel_ToMeshWithTrimmedSurface) {
|
|
|
|
|
|
auto box = make_box(10, 10, 10);
|
|
|
|
|
|
// make_box now creates TrimmedSurface-wrapped faces
|
|
|
|
|
|
auto mesh = box.to_mesh(0.1);
|
|
|
|
|
|
EXPECT_GT(mesh.num_vertices(), 0u);
|
|
|
|
|
|
EXPECT_GT(mesh.num_faces(), 0u);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify each face has a trimmed_surface_id set
|
|
|
|
|
|
for (size_t fi = 0; fi < box.num_faces(); ++fi) {
|
|
|
|
|
|
const auto& face = box.face(static_cast<int>(fi));
|
|
|
|
|
|
EXPECT_GE(face.trimmed_surface_id, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, BrepModel_SphereTrimmedSurface) {
|
|
|
|
|
|
auto sphere = make_sphere(5.0, 8, 4);
|
|
|
|
|
|
auto mesh = sphere.to_mesh(0.1);
|
|
|
|
|
|
EXPECT_GT(mesh.num_vertices(), 0u);
|
|
|
|
|
|
EXPECT_GT(mesh.num_faces(), 0u);
|
|
|
|
|
|
|
|
|
|
|
|
// All vertices should be approximately on the sphere surface (radius ~5)
|
|
|
|
|
|
auto b = mesh.bounds();
|
|
|
|
|
|
for (size_t vi = 0; vi < mesh.num_vertices(); ++vi) {
|
|
|
|
|
|
auto p = mesh.vertex(vi);
|
|
|
|
|
|
double dist = std::sqrt(p.x()*p.x() + p.y()*p.y() + p.z()*p.z());
|
|
|
|
|
|
EXPECT_NEAR(dist, 5.0, 1.0); // tolerance for approximation
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, BrepModel_CylinderTrimmedSurface) {
|
|
|
|
|
|
auto cyl = make_cylinder(3.0, 10.0, 16);
|
|
|
|
|
|
auto mesh = cyl.to_mesh(0.1);
|
|
|
|
|
|
EXPECT_GT(mesh.num_vertices(), 0u);
|
|
|
|
|
|
EXPECT_GT(mesh.num_faces(), 0u);
|
2026-07-24 14:25:51 +00:00
|
|
|
|
|
2026-07-26 20:35:24 +08:00
|
|
|
|
for (size_t fi = 0; fi < cyl.num_faces(); ++fi) {
|
|
|
|
|
|
const auto& face = cyl.face(static_cast<int>(fi));
|
|
|
|
|
|
EXPECT_GE(face.trimmed_surface_id, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
// 21-22: from_surface / from_rect constructors
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, FromSurface_HasNoLoops) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_surface(surf);
|
|
|
|
|
|
EXPECT_TRUE(ts.loops.empty());
|
|
|
|
|
|
EXPECT_TRUE(ts.is_inside(0.5, 0.5));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, FromRect_CreatesOneOuterLoop) {
|
|
|
|
|
|
auto surf = make_plane();
|
|
|
|
|
|
auto ts = TrimmedSurface::from_rect(surf, 0.0, 1.0, 0.0, 1.0);
|
2026-07-24 14:25:51 +00:00
|
|
|
|
EXPECT_EQ(ts.loops.size(), 1u);
|
|
|
|
|
|
EXPECT_TRUE(ts.loops[0].is_outer);
|
|
|
|
|
|
EXPECT_EQ(ts.loops[0].p_curves.size(), 4u);
|
|
|
|
|
|
}
|
2026-07-26 20:35:24 +08:00
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
// 23: is_valid with trimmed surfaces
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
TEST(TrimmedSurfaceTest, BrepModel_IsValidWithTrimmedFaces) {
|
|
|
|
|
|
auto box = make_box(5, 5, 5);
|
|
|
|
|
|
EXPECT_TRUE(box.is_valid());
|
|
|
|
|
|
}
|