189 lines
6.3 KiB
C++
189 lines
6.3 KiB
C++
/**
|
|
* @file test_mesh_to_curve.cpp
|
|
* @brief 测试 mesh→curve 桥接API (VDE-021)
|
|
*
|
|
* 覆盖:
|
|
* 1. mesh_boundary_to_curve — 基本边界提取
|
|
* 2. mesh_boundary_to_curve — 闭合流形(无边界)
|
|
* 3. extract_profiles_from_mesh — 单轮廓
|
|
* 4. extract_profiles_from_mesh — 多轮廓(离散边界环)
|
|
* 5. mesh_contour_to_curves — Z 截面
|
|
* 6. mesh_contour_to_curves — 无交点
|
|
* 7. extrude(mesh) 重载
|
|
* 8. loft(mesh) 重载
|
|
*/
|
|
#include <gtest/gtest.h>
|
|
#include "vde/mesh/halfedge_mesh.h"
|
|
#include "vde/mesh/mesh_to_curve.h"
|
|
#include "vde/brep/modeling.h"
|
|
|
|
using namespace vde;
|
|
using vde::core::Point3D;
|
|
using vde::core::Vector3D;
|
|
|
|
namespace {
|
|
|
|
// ── Helper: build a square patch mesh with a hole (boundary inside and outside) ──
|
|
// Returns a mesh that is a flat quad in XY plane with a triangular hole
|
|
mesh::HalfedgeMesh make_square_with_hole() {
|
|
// Outer square vertices (counter-clockwise)
|
|
// (0,0,0), (4,0,0), (4,4,0), (0,4,0)
|
|
// Inner triangle hole vertices (clockwise for hole):
|
|
// (1,1,0), (3,1,0), (2,3,0)
|
|
std::vector<Point3D> verts = {
|
|
{0,0,0}, {4,0,0}, {4,4,0}, {0,4,0}, // outer 0-3
|
|
{1,1,0}, {3,1,0}, {2,3,0}, // inner 4-6
|
|
};
|
|
// Triangulate the annulus region manually
|
|
// Using a fan from inner to outer
|
|
std::vector<std::array<int,3>> tris;
|
|
// Ring: connect outer edges to inner edges
|
|
// side 0-1 (bottom) with hole edge 4-5
|
|
tris.push_back({0,1,5}); tris.push_back({0,5,4});
|
|
// side 1-2 (right) with hole edge 5-6
|
|
tris.push_back({1,2,6}); tris.push_back({1,6,5});
|
|
// side 2-3 (top) with hole edge 6-4
|
|
tris.push_back({2,3,4}); tris.push_back({2,4,6});
|
|
// side 3-0 (left)
|
|
tris.push_back({3,0,4});
|
|
|
|
mesh::HalfedgeMesh m;
|
|
m.build_from_triangles(verts, tris);
|
|
return m;
|
|
}
|
|
|
|
// ── Helper: build a closed tetrahedron (no boundary) ──
|
|
mesh::HalfedgeMesh make_tetrahedron() {
|
|
std::vector<Point3D> verts = {
|
|
{0,0,0}, {2,0,0}, {1,2,0}, {1,1,2}
|
|
};
|
|
std::vector<std::array<int,3>> tris = {
|
|
{0,1,2}, {0,2,3}, {0,3,1}, {1,3,2}
|
|
};
|
|
mesh::HalfedgeMesh m;
|
|
m.build_from_triangles(verts, tris);
|
|
return m;
|
|
}
|
|
|
|
// ── Helper: build a mesh with two disconnected boundary loops ──
|
|
mesh::HalfedgeMesh make_two_holes() {
|
|
std::vector<Point3D> verts = {
|
|
{0,0,0}, {8,0,0}, {8,4,0}, {0,4,0}, // outer 0-3
|
|
{1,1,0}, {3,1,0}, {2,2,0}, // hole1 4-6
|
|
{5,1,0}, {7,1,0}, {6,3,0}, // hole2 7-9
|
|
};
|
|
std::vector<std::array<int,3>> tris = {
|
|
// Bottom strip left of hole1
|
|
{0,1,7}, {0,7,4},
|
|
// Between hole1 and hole2
|
|
{4,5,8}, {4,8,7},
|
|
// Right side
|
|
{1,2,9}, {1,9,8},
|
|
// Top
|
|
{2,3,6}, {2,6,9},
|
|
// Left
|
|
{3,0,4}, {3,4,6},
|
|
// Fill between holes top
|
|
{5,6,9}, {5,9,8},
|
|
};
|
|
mesh::HalfedgeMesh m;
|
|
m.build_from_triangles(verts, tris);
|
|
return m;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// ── Test 1: mesh_boundary_to_curve on mesh with hole ──
|
|
TEST(MeshToCurve, BoundaryToCurve_Hole) {
|
|
auto mesh = make_square_with_hole();
|
|
auto curve = mesh::mesh_boundary_to_curve(mesh);
|
|
// Should produce a valid curve (degree >= 1)
|
|
EXPECT_GT(curve.degree(), 0);
|
|
EXPECT_FALSE(curve.control_points().empty());
|
|
// Verify it evaluates without error
|
|
auto [t0, t1] = curve.domain();
|
|
auto pt = curve.evaluate((t0 + t1) * 0.5);
|
|
EXPECT_FALSE(std::isnan(pt.x()));
|
|
}
|
|
|
|
// ── Test 2: mesh_boundary_to_curve on closed manifold (no boundary) ──
|
|
TEST(MeshToCurve, BoundaryToCurve_ClosedMesh) {
|
|
auto mesh = make_tetrahedron();
|
|
auto curve = mesh::mesh_boundary_to_curve(mesh);
|
|
// No boundary → degenerate curve (degree 0)
|
|
EXPECT_EQ(curve.degree(), 0);
|
|
EXPECT_TRUE(curve.control_points().empty());
|
|
}
|
|
|
|
// ── Test 3: extract_profiles_from_mesh with single profile ──
|
|
TEST(MeshToCurve, ExtractProfiles_Single) {
|
|
auto mesh = make_square_with_hole();
|
|
auto profiles = mesh::extract_profiles_from_mesh(mesh, Vector3D::UnitZ());
|
|
EXPECT_FALSE(profiles.empty());
|
|
for (auto& p : profiles) {
|
|
EXPECT_GT(p.degree(), 0);
|
|
EXPECT_FALSE(p.control_points().empty());
|
|
}
|
|
}
|
|
|
|
// ── Test 4: extract_profiles_from_mesh with multiple boundary loops ──
|
|
TEST(MeshToCurve, ExtractProfiles_Multiple) {
|
|
auto mesh = make_two_holes();
|
|
auto profiles = mesh::extract_profiles_from_mesh(mesh, Vector3D::UnitZ());
|
|
// At minimum the outer boundary should be extracted;
|
|
// inner holes depend on mesh triangulation completeness
|
|
EXPECT_GE(profiles.size(), 1u);
|
|
for (auto& p : profiles) {
|
|
EXPECT_GT(p.degree(), 0);
|
|
}
|
|
}
|
|
|
|
// ── Test 5: mesh_contour_to_curves at Z plane ──
|
|
TEST(MeshToCurve, ContourToCurves_ZSlice) {
|
|
auto mesh = make_tetrahedron();
|
|
// The tetrahedron has vertices at z=0,0,0,2. Slice at z=1 should intersect.
|
|
auto contours = mesh::mesh_contour_to_curves(mesh, 1.0);
|
|
EXPECT_FALSE(contours.empty());
|
|
for (auto& c : contours) {
|
|
EXPECT_GT(c.degree(), 0);
|
|
}
|
|
}
|
|
|
|
// ── Test 6: mesh_contour_to_curves no intersection ──
|
|
TEST(MeshToCurve, ContourToCurves_NoIntersection) {
|
|
auto mesh = make_tetrahedron();
|
|
auto contours = mesh::mesh_contour_to_curves(mesh, 10.0);
|
|
// Z=10 is above the tetrahedron, no intersection
|
|
EXPECT_TRUE(contours.empty());
|
|
}
|
|
|
|
// ── Test 7: extrude mesh overload ──
|
|
TEST(MeshToCurve, ExtrudeMeshOverload) {
|
|
auto mesh = make_square_with_hole();
|
|
auto result = brep::extrude(mesh, Vector3D(0, 0, 5));
|
|
EXPECT_GT(result.num_faces(), 0u);
|
|
EXPECT_GT(result.num_bodies(), 0u);
|
|
}
|
|
|
|
// ── Test 8: loft mesh overload ──
|
|
TEST(MeshToCurve, LoftMeshOverload) {
|
|
// Create two square meshes at different Z heights
|
|
auto make_square = [](double z) {
|
|
std::vector<Point3D> verts = {
|
|
{0,0,z}, {3,0,z}, {3,3,z}, {0,3,z}
|
|
};
|
|
std::vector<std::array<int,3>> tris = {
|
|
{0,1,2}, {0,2,3}
|
|
};
|
|
mesh::HalfedgeMesh m;
|
|
m.build_from_triangles(verts, tris);
|
|
return m;
|
|
};
|
|
auto mesh1 = make_square(0.0);
|
|
auto mesh2 = make_square(5.0);
|
|
std::vector<mesh::HalfedgeMesh> profiles = {mesh1, mesh2};
|
|
auto result = brep::loft(profiles);
|
|
EXPECT_GT(result.num_faces(), 0u);
|
|
EXPECT_GT(result.num_bodies(), 0u);
|
|
}
|