e469ef75ef
M4.1 — 工业格式 (Agent #0): - JT parser: ISO 14306 Segment→Part→Mesh/LOD, XT B-Rep decode - Parasolid XT: text/binary parser, Body→BrepModel mapping - ACIS SAT: text format parser with version handling - IFC: SPF parser, IfcWall/Slab/Beam/Column entities - STEP AP242: PMI annotation + tolerance export - 47 tests covering all formats + edge cases M4.2 — GPU 加速 (Agent #1): - CUDA kernels: mc_kernel, qem_cost_kernel, tri_intersect_kernel - __constant__ memory for edge tables, atomic triangle collection - CPU fallback when CUDA unavailable (seamless degradation) - VDE_USE_CUDA CMake option, .cu compilation support - 9 tests with CPU path validation M4.3 — Python 绑定 (Agent #2): - vde_brep: BrepModel, make_*, boolean, STEP/IGES, heal, validate - vde_sdf: primitives, operations, to_mesh, gradient descent - vde_cam: roughing/finishing/drilling, Tool, ToolLibrary - vde_assembly: Assembly, AssemblyNode, interference, explode - Version: 3.3.0 synced across pyproject/setup/__init__ 13 files, ~5200 lines, 56+ tests
255 lines
9.6 KiB
C++
255 lines
9.6 KiB
C++
/**
|
||
* @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 <gtest/gtest.h>
|
||
#include <cmath>
|
||
#include <memory>
|
||
|
||
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<Point3D> 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<std::array<int, 3>> 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<int>(mesh.num_vertices()) -
|
||
static_cast<int>(mesh.num_edges()) +
|
||
static_cast<int>(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);
|
||
}
|