Files
ViewDesignEngine/tests/brep/test_trimmed_surface.cpp
T
茂之钳 ebce6abdd6
CI / Build & Test (push) Failing after 41s
CI / Release Build (push) Failing after 32s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
feat(v3.6): TrimmedSurface — B-Rep fundamental primitive
2026-07-24 14:25:51 +00:00

248 lines
9.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <gtest/gtest.h>
#include "vde/brep/trimmed_surface.h"
#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
/// Control grid: 2×2, degree 1×1, knots {0,0,1,1}
/// Domain: u∈[0,1], v∈[0,1] → maps to XY square [0,10]×[0,10] at Z=0
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
// ═══════════════════════════════════════════════════════════
// Untrimmed surface
// ═══════════════════════════════════════════════════════════
TEST(TrimmedSurfaceTest, Untrimmed_EvaluatesAllCorners) {
auto surf = make_plane();
auto ts = TrimmedSurface::from_surface(surf);
// All points in the parameter domain should evaluate successfully
auto p00 = ts.evaluate(0.0, 0.0);
ASSERT_TRUE(p00.has_value());
EXPECT_NEAR(p00->x(), 0.0, 1e-6);
EXPECT_NEAR(p00->y(), 0.0, 1e-6);
EXPECT_NEAR(p00->z(), 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);
EXPECT_NEAR(p11->z(), 0.0, 1e-6);
auto p10 = ts.evaluate(1.0, 0.0);
ASSERT_TRUE(p10.has_value());
EXPECT_NEAR(p10->x(), 10.0, 1e-6);
EXPECT_NEAR(p10->y(), 0.0, 1e-6);
auto p01 = ts.evaluate(0.0, 1.0);
ASSERT_TRUE(p01.has_value());
EXPECT_NEAR(p01->x(), 0.0, 1e-6);
EXPECT_NEAR(p01->y(), 10.0, 1e-6);
// Center
auto pc = ts.evaluate(0.5, 0.5);
ASSERT_TRUE(pc.has_value());
EXPECT_NEAR(pc->x(), 5.0, 1e-6);
EXPECT_NEAR(pc->y(), 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));
EXPECT_TRUE(ts.is_inside(0.123, 0.789));
}
// ═══════════════════════════════════════════════════════════
// Rectangular trim
// ═══════════════════════════════════════════════════════════
TEST(TrimmedSurfaceTest, RectTrim_InsideRegion) {
auto surf = make_plane();
auto ts = TrimmedSurface::from_rect(surf, 0.0, 0.5, 0.0, 0.5);
// Point at (0.25, 0.25) → 3D: (2.5, 2.5, 0) — inside trim
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_NEAR(p->z(), 0.0, 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);
// Point at (0.75, 0.25) — outside trim (u > 0.5)
auto p = ts.evaluate(0.75, 0.25);
EXPECT_FALSE(p.has_value());
EXPECT_FALSE(ts.is_inside(0.75, 0.25));
// Point at (0.25, 0.75) — outside trim (v > 0.5)
EXPECT_FALSE(ts.is_inside(0.25, 0.75));
// Point at (0.75, 0.75) — outside both
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);
// Points on the boundary should be considered inside (convention)
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));
EXPECT_TRUE(ts.is_inside(0.5, 0.0));
}
// ═══════════════════════════════════════════════════════════
// Hole (inner loop)
// ═══════════════════════════════════════════════════════════
TEST(TrimmedSurfaceTest, Hole_CenterIsOutside) {
auto surf = make_plane();
TrimmedSurface ts;
ts.base_surface = surf;
// Outer loop: full [0,1]×[0,1] rectangle
ts.loops.push_back(make_rect_loop(0.0, 1.0, 0.0, 1.0));
// Inner loop (hole): [0.25,0.75]×[0.25,0.75] square
auto hole = make_rect_loop(0.25, 0.75, 0.25, 0.75);
hole.is_outer = false;
ts.loops.push_back(hole);
// Center (0.5, 0.5) is inside the hole → outside trimmed surface
EXPECT_FALSE(ts.is_inside(0.5, 0.5));
auto p_center = ts.evaluate(0.5, 0.5);
EXPECT_FALSE(p_center.has_value());
// Corner (0.1, 0.1) is inside outer, outside hole → inside trimmed surface
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);
EXPECT_NEAR(p_corner->y(), 1.0, 1e-6);
// Point inside hole boundary
EXPECT_FALSE(ts.is_inside(0.3, 0.3));
}
// ═══════════════════════════════════════════════════════════
// Bounds
// ═══════════════════════════════════════════════════════════
TEST(TrimmedSurfaceTest, Bounds_Untrimmed) {
auto surf = make_plane();
auto ts = TrimmedSurface::from_surface(surf);
auto b = ts.bounds();
// The plane spans [0,10]×[0,10] at Z=0
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);
EXPECT_NEAR(b.min().z(), 0.0, 1e-6);
EXPECT_NEAR(b.max().z(), 0.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();
// Trimmed to [2.5, 7.5]×[2.5, 7.5] at Z=0
EXPECT_NEAR(b.min().x(), 2.5, 1e-6);
EXPECT_NEAR(b.max().x(), 7.5, 1e-6);
EXPECT_NEAR(b.min().y(), 2.5, 1e-6);
EXPECT_NEAR(b.max().y(), 7.5, 1e-6);
EXPECT_NEAR(b.min().z(), 0.0, 1e-6);
EXPECT_NEAR(b.max().z(), 0.0, 1e-6);
}
TEST(TrimmedSurfaceTest, Bounds_WithHole) {
auto surf = make_plane();
TrimmedSurface ts;
ts.base_surface = surf;
ts.loops.push_back(make_rect_loop(0.2, 0.8, 0.2, 0.8));
auto hole = make_rect_loop(0.3, 0.7, 0.3, 0.7);
hole.is_outer = false;
ts.loops.push_back(hole);
auto b = ts.bounds();
// Bounds are computed from outer loop only, spans [2,8]×[2,8] at Z=0
EXPECT_NEAR(b.min().x(), 2.0, 1e-6);
EXPECT_NEAR(b.max().x(), 8.0, 1e-6);
EXPECT_NEAR(b.min().y(), 2.0, 1e-6);
EXPECT_NEAR(b.max().y(), 8.0, 1e-6);
}
// ═══════════════════════════════════════════════════════════
// from_surface constructor
// ═══════════════════════════════════════════════════════════
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));
}
// ═══════════════════════════════════════════════════════════
// from_rect constructor
// ═══════════════════════════════════════════════════════════
TEST(TrimmedSurfaceTest, FromRect_CreatesOneOuterLoop) {
auto surf = make_plane();
auto ts = TrimmedSurface::from_rect(surf, 0.0, 1.0, 0.0, 1.0);
EXPECT_EQ(ts.loops.size(), 1u);
EXPECT_TRUE(ts.loops[0].is_outer);
// Rectangular loop has 4 p-curves (one per side)
EXPECT_EQ(ts.loops[0].p_curves.size(), 4u);
}