#include #include "vde/brep/brep.h" #include "vde/brep/modeling.h" #include using namespace vde::brep; using namespace vde::core; // ═══════════════════════════════════════════════════════════ // Fillet Tests // ═══════════════════════════════════════════════════════════ TEST(FilletTest, FilletOnBoxEdge) { auto box = make_box(4.0, 4.0, 4.0); ASSERT_TRUE(box.is_valid()); ASSERT_GE(box.num_edges(), 12u); // 4 edges × 6 faces / shared? Actually 4*6=24 in current impl // Fillet edge 0 (first edge of first face) auto result = fillet(box, 0, 0.5); EXPECT_TRUE(result.is_valid()); EXPECT_EQ(result.num_bodies(), 1u); // Should have more faces than original (fillet adds new faces, removes sharper ones) // The fillet replaces 2 faces with their trimmed versions + N fillet faces EXPECT_GT(result.num_faces(), 4u); EXPECT_GT(result.num_vertices(), 8u); } TEST(FilletTest, FilletOnBoxEdge_ValidBody) { auto box = make_box(2.0, 2.0, 2.0); auto result = fillet(box, 0, 0.3); EXPECT_TRUE(result.is_valid()); EXPECT_EQ(result.num_bodies(), 1u); EXPECT_GE(result.num_faces(), 3u); EXPECT_GE(result.num_vertices(), 8u); } TEST(FilletTest, FilletOnBoxEdge_ToMesh) { auto box = make_box(3.0, 3.0, 3.0); auto result = fillet(box, 0, 0.3); auto mesh = result.to_mesh(); EXPECT_GT(mesh.num_faces(), 0u); EXPECT_GT(mesh.num_vertices(), 0u); } TEST(FilletTest, Fillet_ZeroRadius_ReturnsOriginal) { auto box = make_box(2.0, 2.0, 2.0); auto result = fillet(box, 0, 0.0); // Zero radius should return original unchanged EXPECT_TRUE(result.is_valid()); EXPECT_EQ(result.num_faces(), box.num_faces()); } TEST(FilletTest, Fillet_InvalidEdge_ReturnsOriginal) { auto box = make_box(2.0, 2.0, 2.0); auto result = fillet(box, -1, 0.5); EXPECT_TRUE(result.is_valid()); EXPECT_EQ(result.num_faces(), box.num_faces()); } TEST(FilletTest, Fillet_NonexistentEdge_ReturnsOriginal) { auto box = make_box(2.0, 2.0, 2.0); int fake_edge = static_cast(box.num_edges() + 10); auto result = fillet(box, fake_edge, 0.5); EXPECT_TRUE(result.is_valid()); EXPECT_EQ(result.num_faces(), box.num_faces()); } TEST(FilletTest, Fillet_NonManifoldEdge_ReturnsOriginal) { // A box has 24 edges (4 per face) — test with an edge shared by ≥3 faces // But in the current implementation each face has its own edges, // so every edge is shared by exactly 1 face. edge_faces returns 1. // Let's just verify a specific edge returns faces auto box = make_box(2.0, 2.0, 2.0); auto faces = box.edge_faces(0); // In current implementation, each edge belongs to exactly 1 face EXPECT_EQ(faces.size(), 1u); } TEST(FilletTest, Fillet_MultipleEdges) { auto box = make_box(4.0, 4.0, 4.0); // First verify our model has enough edges ASSERT_GE(box.num_edges(), 4u); // Fillet first edge auto result1 = fillet(box, 0, 0.3); EXPECT_TRUE(result1.is_valid()); EXPECT_GT(result1.num_faces(), 4u); // Fillet a different edge if (box.num_edges() >= 5) { auto result2 = fillet(box, 4, 0.3); EXPECT_TRUE(result2.is_valid()); } } // ═══════════════════════════════════════════════════════════ // Chamfer Tests // ═══════════════════════════════════════════════════════════ TEST(ChamferTest, ChamferOnBoxEdge) { auto box = make_box(4.0, 4.0, 4.0); ASSERT_TRUE(box.is_valid()); auto result = chamfer(box, 0, 0.5); EXPECT_TRUE(result.is_valid()); EXPECT_EQ(result.num_bodies(), 1u); // Chamfer adds faces (bevel face replaces sharp corner) EXPECT_GT(result.num_faces(), 4u); EXPECT_GT(result.num_vertices(), 8u); } TEST(ChamferTest, Chamfer_ToMesh) { auto box = make_box(3.0, 3.0, 3.0); auto result = chamfer(box, 0, 0.3); auto mesh = result.to_mesh(); EXPECT_GT(mesh.num_faces(), 0u); EXPECT_GT(mesh.num_vertices(), 0u); } TEST(ChamferTest, Chamfer_ZeroDistance_ReturnsOriginal) { auto box = make_box(2.0, 2.0, 2.0); auto result = chamfer(box, 0, 0.0); EXPECT_TRUE(result.is_valid()); EXPECT_EQ(result.num_faces(), box.num_faces()); } TEST(ChamferTest, Chamfer_InvalidEdge_ReturnsOriginal) { auto box = make_box(2.0, 2.0, 2.0); auto result = chamfer(box, -1, 0.5); EXPECT_TRUE(result.is_valid()); EXPECT_EQ(result.num_faces(), box.num_faces()); } TEST(ChamferTest, Chamfer_NonexistentEdge_ReturnsOriginal) { auto box = make_box(2.0, 2.0, 2.0); int fake_edge = static_cast(box.num_edges() + 5); auto result = chamfer(box, fake_edge, 0.5); EXPECT_TRUE(result.is_valid()); EXPECT_EQ(result.num_faces(), box.num_faces()); } // ═══════════════════════════════════════════════════════════ // Shell Tests // ═══════════════════════════════════════════════════════════ TEST(ShellTest, Shell_OpenBox_CreatesThinWall) { auto box = make_box(2.0, 2.0, 2.0); // Shell with open face (remove first face, thickness = 0.2) auto result = shell(box, 0, 0.2); EXPECT_TRUE(result.is_valid()); EXPECT_EQ(result.num_bodies(), 1u); // Shell creates both outer and inner faces + side walls for each edge of open face // Open face has 4 edges → 4 side wall quads // Remaining 5 faces → 5 outer + 5 inner = 10 faces // Total: 4 side walls + 10 offset faces = 14 faces EXPECT_GE(result.num_faces(), 10u); EXPECT_GE(result.num_vertices(), 16u); // Bounds should expand outward slightly (both inner and outer vertices exist) auto b = result.bounds(); EXPECT_NEAR(b.extent().x(), 2.0, 0.1); EXPECT_NEAR(b.extent().y(), 2.0, 0.1); EXPECT_NEAR(b.extent().z(), 2.0, 0.1); } TEST(ShellTest, Shell_OpenBox_ToMesh) { auto box = make_box(2.0, 2.0, 2.0); auto result = shell(box, 0, 0.2); auto mesh = result.to_mesh(); EXPECT_GT(mesh.num_faces(), 0u); EXPECT_GT(mesh.num_vertices(), 0u); } TEST(ShellTest, Shell_ClosedBody_HollowSphere) { auto sphere = make_sphere(1.5, 16, 8); // Closed shell: no opening, just hollow auto result = shell(sphere, -1, 0.1); EXPECT_TRUE(result.is_valid()); EXPECT_EQ(result.num_bodies(), 1u); // With closed shell, we get inner + outer faces for each original face EXPECT_GE(result.num_faces(), sphere.num_faces() * 2); auto mesh = result.to_mesh(); EXPECT_GT(mesh.num_faces(), 0u); } TEST(ShellTest, Shell_ClosedBody_ToMesh) { auto sphere = make_sphere(1.0, 12, 6); auto result = shell(sphere, -1, 0.15); auto mesh = result.to_mesh(); EXPECT_GT(mesh.num_faces(), 0u); EXPECT_GT(mesh.num_vertices(), 0u); } TEST(ShellTest, Shell_PositiveThickness_OutwardOffset) { auto box = make_box(2.0, 2.0, 2.0); // Positive thickness → outward offset auto result = shell(box, 0, 0.3); EXPECT_TRUE(result.is_valid()); auto b = result.bounds(); // Outer bounds should expand EXPECT_GT(b.extent().x(), 2.0 - 1e-6); } TEST(ShellTest, Shell_NegativeThickness_InwardOffset) { auto box = make_box(2.0, 2.0, 2.0); // Negative thickness → inward offset // Note: current implementation always uses -abs(thickness) for inward auto result = shell(box, 0, -0.2); EXPECT_TRUE(result.is_valid()); // Should still produce valid geometry EXPECT_GT(result.num_faces(), 4u); } TEST(ShellTest, Shell_InvalidOpenFace) { auto box = make_box(2.0, 2.0, 2.0); int fake_face = static_cast(box.num_faces() + 10); auto result = shell(box, fake_face, 0.2); // Should still work (just no side walls) EXPECT_TRUE(result.is_valid()); EXPECT_GE(result.num_faces(), 4u); } TEST(ShellTest, Shell_SideWallsExist) { auto box = make_box(2.0, 2.0, 2.0); auto result = shell(box, 0, 0.2); // With an open face, the face count should be: // 5 outer faces + 5 inner faces + (4 edges * 1 side wall each) = 14 // But our box has 6 faces, each with 4 edges, 24 total edges // Face 0 has 4 edges → 4 side walls auto open_edges = box.face_edges(0); size_t expected_walls = open_edges.size(); // Total faces = (num_faces-1)*2 + expected_walls size_t expected_total = (box.num_faces() - 1) * 2 + expected_walls; EXPECT_GE(result.num_faces(), expected_total); } TEST(ShellTest, Shell_ThinWall_VeryThin) { auto box = make_box(2.0, 2.0, 2.0); // Very thin wall should still work auto result = shell(box, 0, 0.01); EXPECT_TRUE(result.is_valid()); EXPECT_GT(result.num_faces(), 4u); } TEST(ShellTest, Shell_ClosedBox) { auto box = make_box(2.0, 2.0, 2.0); // Closed shell (open_face = -1) — no opening, fully hollow box auto result = shell(box, -1, 0.1); EXPECT_TRUE(result.is_valid()); // All 6 faces → 6 outer + 6 inner = 12 faces (no side walls) EXPECT_GE(result.num_faces(), 12u); } TEST(ShellTest, Shell_ZeroSized) { auto box = make_box(1.0, 1.0, 1.0); // Shell with thickness larger than box — should still produce valid geometry auto result = shell(box, 0, 0.6); EXPECT_TRUE(result.is_valid()); // May produce fewer faces if some faces collapse, but shouldn't crash EXPECT_GE(result.num_faces(), 0u); }