Files
ViewDesignEngine/tests/boolean/test_boolean_mesh.cpp
T
茂之钳 5a512674c4
CI / Build & Test (push) Failing after 31s
CI / Release Build (push) Failing after 34s
fix: 统一使用 using namespace vde::core 引入 Point3D/Vector3D
2026-07-23 15:29:04 +00:00

256 lines
10 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/boolean/boolean_mesh.h"
#include "vde/mesh/halfedge_mesh.h"
#include <cmath>
using namespace vde::boolean;
using namespace vde::mesh;
using namespace vde::core;
// ── Helper: build a box mesh (12 triangles, 8 vertices) ──
// Face normals point outward (CCW winding from outside)
static HalfedgeMesh make_box(double w, double h, double d) {
double hw = w * 0.5, hh = h * 0.5, hd = d * 0.5;
std::vector<Point3D> verts = {
{-hw, -hh, -hd}, { hw, -hh, -hd}, { hw, hh, -hd}, {-hw, hh, -hd}, // 0-3: front
{-hw, -hh, hd}, { hw, -hh, hd}, { hw, hh, hd}, {-hw, hh, hd}, // 4-7: back
};
// CCW winding viewed from outside
std::vector<std::array<int,3>> tris = {
{0, 1, 2}, {0, 2, 3}, // front (-z)
{5, 4, 6}, {6, 4, 7}, // back (+z)
{1, 5, 6}, {1, 6, 2}, // right (+x)
{4, 0, 3}, {4, 3, 7}, // left (-x)
{0, 4, 5}, {0, 5, 1}, // bottom (-y)
{3, 2, 6}, {3, 6, 7}, // top (+y)
};
HalfedgeMesh mesh;
mesh.build_from_triangles(verts, tris);
return mesh;
}
// ── Helper: build a translated box ──
static HalfedgeMesh make_box_at(double cx, double cy, double cz,
double w, double h, double d) {
double hw = w * 0.5, hh = h * 0.5, hd = d * 0.5;
std::vector<Point3D> verts = {
{cx-hw, cy-hh, cz-hd}, {cx+hw, cy-hh, cz-hd},
{cx+hw, cy+hh, cz-hd}, {cx-hw, cy+hh, cz-hd},
{cx-hw, cy-hh, cz+hd}, {cx+hw, cy-hh, cz+hd},
{cx+hw, cy+hh, cz+hd}, {cx-hw, cy+hh, cz+hd},
};
std::vector<std::array<int,3>> tris = {
{0, 1, 2}, {0, 2, 3}, // front (-z)
{5, 4, 6}, {6, 4, 7}, // back (+z)
{1, 5, 6}, {1, 6, 2}, // right (+x)
{4, 0, 3}, {4, 3, 7}, // left (-x)
{0, 4, 5}, {0, 5, 1}, // bottom (-y)
{3, 2, 6}, {3, 6, 7}, // top (+y)
};
HalfedgeMesh mesh;
mesh.build_from_triangles(verts, tris);
return mesh;
}
// ═══════════════════════════════════════════════════════════
// Union tests
// ═══════════════════════════════════════════════════════════
TEST(BooleanMeshTest, Union_IntersectingBoxes_HasResult) {
auto a = make_box(2, 2, 2); // centered at origin
auto b = make_box_at(1, 0, 0, 2, 2, 2); // shifted +x by 1
auto result = mesh_union(a, b);
EXPECT_GT(result.num_faces(), 0u);
// Union should have at least as many faces as the individual boxes
// (since they partially overlap, the exterior faces are kept)
EXPECT_GE(result.num_faces(), a.num_faces() / 2);
}
TEST(BooleanMeshTest, Union_DisjointBoxes_HasAllFaces) {
auto a = make_box(1, 1, 1); // centered at origin
auto b = make_box_at(3, 0, 0, 1, 1, 1); // far away
auto result = mesh_union(a, b);
// Disjoint: both meshes should be fully retained
EXPECT_EQ(result.num_faces(), a.num_faces() + b.num_faces());
}
// ═══════════════════════════════════════════════════════════
// Intersection tests
// ═══════════════════════════════════════════════════════════
TEST(BooleanMeshTest, Intersection_OverlappingBoxes_HasResult) {
auto a = make_box(2, 2, 2); // [-1,1]³
auto b = make_box_at(1, 0, 0, 2, 2, 2); // [0,2]×[-1,1]×[-1,1]
auto result = mesh_intersection(a, b);
// Overlap region = [0,1]×[-1,1]×[-1,1] — should have faces
EXPECT_GT(result.num_faces(), 0u);
}
TEST(BooleanMeshTest, Intersection_Disjoint_ReturnsEmpty) {
auto a = make_box(1, 1, 1);
auto b = make_box_at(3, 0, 0, 1, 1, 1);
auto result = mesh_intersection(a, b);
EXPECT_EQ(result.num_faces(), 0u);
}
TEST(BooleanMeshTest, Intersection_BContainsA_ReturnsA) {
auto a = make_box(1, 1, 1); // small cube at origin
auto b = make_box(4, 4, 4); // large cube containing a
auto result = mesh_intersection(a, b);
// All faces of A are inside B → intersection should have 12 faces
EXPECT_EQ(result.num_faces(), a.num_faces());
}
// ═══════════════════════════════════════════════════════════
// Difference tests
// ═══════════════════════════════════════════════════════════
TEST(BooleanMeshTest, Difference_Overlapping_HasResult) {
auto a = make_box(2, 2, 2);
auto b = make_box_at(1, 0, 0, 2, 2, 2);
auto result = mesh_difference(a, b);
// A-b: faces of A whose centroids are outside B
EXPECT_GT(result.num_faces(), 0u);
// Should be fewer faces than original A (some faces are clipped)
EXPECT_LT(result.num_faces(), a.num_faces());
}
TEST(BooleanMeshTest, Difference_Disjoint_ReturnsAllA) {
auto a = make_box(1, 1, 1);
auto b = make_box_at(3, 0, 0, 1, 1, 1);
auto result = mesh_difference(a, b);
EXPECT_EQ(result.num_faces(), a.num_faces());
}
TEST(BooleanMeshTest, Difference_BContainsA_ReturnsEmpty) {
auto a = make_box(1, 1, 1); // small inside
auto b = make_box(4, 4, 4); // large container
auto result = mesh_difference(a, b);
// A is fully inside B → all faces are clipped
EXPECT_EQ(result.num_faces(), 0u);
}
// ═══════════════════════════════════════════════════════════
// Symmetric difference tests
// ═══════════════════════════════════════════════════════════
TEST(BooleanMeshTest, SymDiff_Overlapping_HasResult) {
auto a = make_box(2, 2, 2);
auto b = make_box_at(1, 0, 0, 2, 2, 2);
auto result = mesh_sym_diff(a, b);
EXPECT_GT(result.num_faces(), 0u);
}
// ═══════════════════════════════════════════════════════════
// Empty mesh input tests
// ═══════════════════════════════════════════════════════════
TEST(BooleanMeshTest, Union_EmptyA_ReturnsB) {
HalfedgeMesh empty;
auto b = make_box(2, 2, 2);
auto result = mesh_union(empty, b);
EXPECT_EQ(result.num_faces(), b.num_faces());
}
TEST(BooleanMeshTest, Union_EmptyB_ReturnsA) {
auto a = make_box(2, 2, 2);
HalfedgeMesh empty;
auto result = mesh_union(a, empty);
EXPECT_EQ(result.num_faces(), a.num_faces());
}
TEST(BooleanMeshTest, Intersection_EmptyA_ReturnsEmpty) {
HalfedgeMesh empty;
auto b = make_box(2, 2, 2);
auto result = mesh_intersection(empty, b);
EXPECT_EQ(result.num_faces(), 0u);
}
TEST(BooleanMeshTest, Difference_EmptyA_ReturnsEmpty) {
HalfedgeMesh empty;
auto b = make_box(2, 2, 2);
auto result = mesh_difference(empty, b);
EXPECT_EQ(result.num_faces(), 0u);
}
TEST(BooleanMeshTest, Difference_EmptyB_ReturnsA) {
auto a = make_box(2, 2, 2);
HalfedgeMesh empty;
auto result = mesh_difference(a, empty);
EXPECT_EQ(result.num_faces(), a.num_faces());
}
// ═══════════════════════════════════════════════════════════
// BooleanOp dispatcher
// ═══════════════════════════════════════════════════════════
TEST(BooleanMeshTest, Dispatch_Union) {
auto a = make_box(2, 2, 2);
auto b = make_box_at(3, 0, 0, 2, 2, 2);
auto result = mesh_boolean(a, b, BooleanOp::Union);
EXPECT_EQ(result.num_faces(), a.num_faces() + b.num_faces());
}
TEST(BooleanMeshTest, Dispatch_Intersection_Disjoint) {
auto a = make_box(1, 1, 1);
auto b = make_box_at(3, 0, 0, 1, 1, 1);
auto result = mesh_boolean(a, b, BooleanOp::Intersection);
EXPECT_EQ(result.num_faces(), 0u);
}
// ═══════════════════════════════════════════════════════════
// Point-in-mesh classification
// ═══════════════════════════════════════════════════════════
TEST(BooleanMeshTest, PointInside_OriginInCenteredBox) {
auto box = make_box(2, 2, 2);
Point3D origin(0, 0, 0);
bool inside = is_point_inside_mesh(origin, box);
EXPECT_TRUE(inside);
}
TEST(BooleanMeshTest, PointInside_FarPointOutsideBox) {
auto box = make_box(2, 2, 2);
Point3D far(10, 0, 0);
bool inside = is_point_inside_mesh(far, box);
EXPECT_FALSE(inside);
}
// ═══════════════════════════════════════════════════════════
// Invert / merge helpers
// ═══════════════════════════════════════════════════════════
TEST(BooleanMeshTest, Invert_PreservesFaceCount) {
auto box = make_box(2, 2, 2);
auto inverted = invert_mesh(box);
EXPECT_EQ(inverted.num_faces(), box.num_faces());
EXPECT_EQ(inverted.num_vertices(), box.num_vertices());
}
TEST(BooleanMeshTest, Merge_TwoBoxes) {
auto a = make_box(1, 1, 1);
auto b = make_box_at(3, 0, 0, 1, 1, 1);
auto merged = merge_meshes(a, b);
EXPECT_EQ(merged.num_faces(), a.num_faces() + b.num_faces());
EXPECT_EQ(merged.num_vertices(), a.num_vertices() + b.num_vertices());
}