/** * @file test_gpu_acceleration.cpp * @brief GPU 加速模块测试(8 项) * * 测试覆盖: * 1. gpu_available — 检测 CUDA 可用性 * 2. gpu_marching_cubes — SDF → 网格(球体) * 3. gpu_marching_cubes — 低分辨率 * 4. gpu_marching_cubes — 立方体 SDF * 5. gpu_mesh_simplify — 基本简化 * 6. gpu_mesh_simplify — 微小 target_ratio * 7. gpu_boolean_intersect — 两个相交球体 * 8. gpu_boolean_intersect — 不相交网格 */ #include "vde/gpu/gpu_acceleration.h" #include "vde/mesh/marching_cubes.h" #include "vde/mesh/halfedge_mesh.h" #include #include #include using namespace vde::gpu; using vde::core::AABB3D; using vde::core::Point3D; using vde::core::Vector3D; using vde::mesh::HalfedgeMesh; // ── 帮助函数 ───────────────────────────────────────────────────── /// 创建一个简单球体 SDF 函数 static auto make_sphere_sdf(double cx, double cy, double cz, double r) { return [=](double x, double y, double z) -> double { return std::sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy) + (z - cz) * (z - cz)) - r; }; } /// 创建立方体 SDF 函数 static auto make_box_sdf(double hx, double hy, double hz) { return [=](double x, double y, double z) -> double { double dx = std::abs(x) - hx, dy = std::abs(y) - hy, dz = std::abs(z) - hz; return std::sqrt(std::max(dx, 0.0) * std::max(dx, 0.0) + std::max(dy, 0.0) * std::max(dy, 0.0) + std::max(dz, 0.0) * std::max(dz, 0.0)) + std::min(std::max({dx, dy, dz}), 0.0); }; } /// 快速创建简单三角网格(正四面体) static HalfedgeMesh make_tetrahedron(double ox, double oy, double oz, double s) { HalfedgeMesh mesh; std::vector verts = { {ox, oy + s, oz}, {ox - s * 0.866, oy, oz - s * 0.5}, {ox + s * 0.866, oy, oz - s * 0.5}, {ox, oy, oz + s} }; std::vector> tris = { {0, 1, 2}, {0, 2, 3}, {0, 3, 1}, {1, 3, 2} }; for (auto& v : verts) mesh.add_vertex(v); for (auto& t : tris) mesh.add_face({t[0], t[1], t[2]}); return mesh; } /// 验证网格基本有效性 static void expect_valid_mesh(const HalfedgeMesh& mesh) { EXPECT_GT(mesh.num_vertices(), 0u); EXPECT_GT(mesh.num_faces(), 0u); EXPECT_GT(mesh.num_edges(), 0u); // 欧拉公式:V - E + F ≈ 2(对于闭曲面) // 不作严格检查,但确保拓扑合理 int euler = static_cast(mesh.num_vertices()) - static_cast(mesh.num_edges()) + static_cast(mesh.num_faces()); EXPECT_GE(euler, 0) << "Euler characteristic should be non-negative"; } // ====================================================================== // 测试 1:gpu_available // ====================================================================== TEST(GpuAcceleration, GpuAvailable) { // 无论 CUDA 是否可用,函数必须不崩溃并返回布尔值 bool avail = gpu_available(); EXPECT_TRUE(avail == true || avail == false); #if VDE_USE_CUDA // CUDA 编译时开启 → 运行时至少应无错误 // (CUDA 设备可能为 0,但调用不崩溃) SUCCEED() << "CUDA compiled in, runtime detection: " << (avail ? "YES" : "NO"); #else EXPECT_FALSE(avail) << "Without CUDA build, gpu_available must return false"; #endif } // ====================================================================== // 测试 2:gpu_marching_cubes — 球体 SDF // ====================================================================== TEST(GpuAcceleration, MarchingCubesSphere) { auto sdf = make_sphere_sdf(0, 0, 0, 1.0); AABB3D bounds({-1.5, -1.5, -1.5}, {1.5, 1.5, 1.5}); HalfedgeMesh mesh = gpu_marching_cubes(sdf, bounds, 32); expect_valid_mesh(mesh); // 球体应当构成闭曲面 EXPECT_GE(mesh.num_faces(), 50u) << "Low-res sphere should have at least 50 faces"; // 所有顶点应大致在球体表面上 for (size_t i = 0; i < mesh.num_vertices(); ++i) { const auto& p = mesh.vertex(i); double dist = std::sqrt(p.x() * p.x() + p.y() * p.y() + p.z() * p.z()); EXPECT_NEAR(dist, 1.0, 0.15) << "Vertex " << i << " distance from origin"; } } // ====================================================================== // 测试 3:gpu_marching_cubes — 低分辨率 // ====================================================================== TEST(GpuAcceleration, MarchingCubesLowRes) { auto sdf = make_sphere_sdf(0, 0, 0, 1.0); AABB3D bounds({-1.2, -1.2, -1.2}, {1.2, 1.2, 1.2}); HalfedgeMesh mesh = gpu_marching_cubes(sdf, bounds, 8); // 极低分辨率仍有输出 EXPECT_GT(mesh.num_vertices(), 0u); EXPECT_GT(mesh.num_faces(), 0u); } // ====================================================================== // 测试 4:gpu_marching_cubes — 立方体 SDF // ====================================================================== TEST(GpuAcceleration, MarchingCubesBox) { auto sdf = make_box_sdf(1.0, 0.5, 0.3); AABB3D bounds({-1.5, -1.0, -0.8}, {1.5, 1.0, 0.8}); HalfedgeMesh mesh = gpu_marching_cubes(sdf, bounds, 24); expect_valid_mesh(mesh); // 检查包围盒尺寸与预期一致 auto bb = mesh.bounds(); double max_dim = std::max({bb.extent().x(), bb.extent().y(), bb.extent().z()}); EXPECT_GT(max_dim, 0.5); EXPECT_LT(max_dim, 4.0); } // ====================================================================== // 测试 5:gpu_mesh_simplify — 基本简化 // ====================================================================== TEST(GpuAcceleration, MeshSimplifyBasic) { auto sdf = make_sphere_sdf(0, 0, 0, 1.0); AABB3D bounds({-1.5, -1.5, -1.5}, {1.5, 1.5, 1.5}); HalfedgeMesh original = gpu_marching_cubes(sdf, bounds, 32); size_t orig_faces = original.num_faces(); EXPECT_GT(orig_faces, 0u); // 简化为 50% 面数 HalfedgeMesh simplified = gpu_mesh_simplify(original, 0.5f); expect_valid_mesh(simplified); // 简化后面数应减少 EXPECT_LE(simplified.num_faces(), orig_faces) << "Simplified mesh should have ≤ original face count"; } // ====================================================================== // 测试 6:gpu_mesh_simplify — 极低 target_ratio // ====================================================================== TEST(GpuAcceleration, MeshSimplifyTinyRatio) { auto sdf = make_sphere_sdf(0, 0, 0, 1.0); AABB3D bounds({-1.5, -1.5, -1.5}, {1.5, 1.5, 1.5}); HalfedgeMesh original = gpu_marching_cubes(sdf, bounds, 20); ASSERT_GT(original.num_faces(), 10u); // target_ratio = 0.1 (极低比例) HalfedgeMesh simplified = gpu_mesh_simplify(original, 0.1f); // 不应崩溃且至少保留一些面 EXPECT_GT(simplified.num_vertices(), 3u); EXPECT_GT(simplified.num_faces(), 1u); EXPECT_LT(simplified.num_faces(), original.num_faces()); } // ====================================================================== // 测试 7:gpu_boolean_intersect — 两个相交球体 // ====================================================================== TEST(GpuAcceleration, BooleanIntersectIntersecting) { // 两个相交球体(中心相距 1.0,半径各 1.2) auto sdf_a = make_sphere_sdf(-0.5, 0, 0, 1.2); auto sdf_b = make_sphere_sdf(0.5, 0, 0, 1.2); AABB3D bounds({-2.0, -1.5, -1.5}, {2.0, 1.5, 1.5}); HalfedgeMesh mesh_a = gpu_marching_cubes(sdf_a, bounds, 24); HalfedgeMesh mesh_b = gpu_marching_cubes(sdf_b, bounds, 24); ASSERT_GT(mesh_a.num_faces(), 0u); ASSERT_GT(mesh_b.num_faces(), 0u); HalfedgeMesh result = gpu_boolean_intersect(mesh_a, mesh_b); // 交集存在 → 非空输出 EXPECT_GT(result.num_vertices(), 0u) << "Intersection of overlapping spheres should not be empty"; } // ====================================================================== // 测试 8:gpu_boolean_intersect — 不相交网格 // ====================================================================== TEST(GpuAcceleration, BooleanIntersectDisjoint) { // 两个远距离球体(中心相距 10,半径各 1) auto sdf_a = make_sphere_sdf(-5, 0, 0, 1.0); auto sdf_b = make_sphere_sdf(5, 0, 0, 1.0); AABB3D bounds({-6.5, -1.5, -1.5}, {6.5, 1.5, 1.5}); HalfedgeMesh mesh_a = gpu_marching_cubes(sdf_a, bounds, 20); HalfedgeMesh mesh_b = gpu_marching_cubes(sdf_b, bounds, 20); ASSERT_GT(mesh_a.num_faces(), 0u); ASSERT_GT(mesh_b.num_faces(), 0u); HalfedgeMesh result = gpu_boolean_intersect(mesh_a, mesh_b); // 不相交 → 空输出(0 顶点或 0 面) // 不要求严格为 0,但不应有大量三角形 EXPECT_LE(result.num_faces(), mesh_a.num_faces() / 2u) << "Disjoint meshes should have few or no intersection faces"; } // ====================================================================== // 额外测试:空网格安全 // ====================================================================== TEST(GpuAcceleration, EmptyMeshSafety) { HalfedgeMesh empty; // simplify 空网格不崩溃 HalfedgeMesh r1 = gpu_mesh_simplify(empty, 0.5f); EXPECT_EQ(r1.num_vertices(), 0u); EXPECT_EQ(r1.num_faces(), 0u); // boolean intersect 空网格不崩溃 auto sdf = make_sphere_sdf(0, 0, 0, 1.0); AABB3D bounds({-1.5, -1.5, -1.5}, {1.5, 1.5, 1.5}); HalfedgeMesh sphere = gpu_marching_cubes(sdf, bounds, 16); HalfedgeMesh r2 = gpu_boolean_intersect(empty, sphere); EXPECT_EQ(r2.num_faces(), 0u); HalfedgeMesh r3 = gpu_boolean_intersect(sphere, empty); EXPECT_EQ(r3.num_faces(), 0u); }