feat(v4.6): memory pool + parallel boolean/MC/intersection + GMP exact predicates
M1 — Memory Pool (内存池): - ObjectPool<T>: O(1) acquire/release with free-list, chunk-based expansion - ThreadSafeObjectPool<T>: per-thread local pools with global fallback - CowPtr<T>: copy-on-write smart pointer with shallow/deep copy - 10 tests: single/multi-thread, CoW detach, reuse rate M2 — Parallel Boolean (并行布尔): - parallel_face_face_intersection(): OpenMP face-pair dispatch with AABB culling - parallel_classify_faces(): thread-safe ray casting classification - parallel_mesh_union/intersection/difference + B-Rep variants - Thread count management API - 12 tests: coverage, empty cases, identity operations M3 — Parallel Marching Cubes (并行MC): - parallel_marching_cubes(): Z-slice parallel voxel processing - parallel_adaptive_mc(): octree-based adaptive resolution - Complete 256-entry edge table + Möller-Trumbore interpolation - 5 tests: sphere basic/high-res/empty/adaptive/threaded M4 — Parallel Surface Intersection (并行求交): - parallel_curve_intersections(): per-pair parallel dispatch - parallel_surface_intersection(): recursive subdivision + Newton refinement - AABB broad-phase culling before narrow-phase - 4 tests: curve pairs, surface batch M5 — GMP Exact Predicates (精确谓词): - exact_orient2d/3d: adaptive precision (double → GMP fallback) - exact_in_sphere: circumsphere test with degenerate handling - exact_point_in_polygon/polyhedron: robust ray casting - PrecisionMode: Fast/Adaptive/Exact with global toggle - 12 tests: all orientations, boundary cases, mode switching 21 files, +2263 lines
This commit is contained in:
@@ -26,3 +26,4 @@ add_vde_test(test_euler_op)
|
||||
add_vde_test(test_draft_analysis)
|
||||
add_vde_test(test_incremental_mesh)
|
||||
add_vde_test(test_kinematic_chain)
|
||||
add_vde_test(test_parallel_boolean)
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/brep/parallel_boolean.h"
|
||||
#include "vde/brep/modeling.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace vde::brep;
|
||||
using namespace vde::core;
|
||||
|
||||
TEST(ParallelBooleanTest, ThreadCount) {
|
||||
int n = parallel_thread_count();
|
||||
EXPECT_GT(n, 0);
|
||||
}
|
||||
|
||||
TEST(ParallelBooleanTest, SetThreadCount) {
|
||||
set_parallel_threads(2);
|
||||
EXPECT_EQ(parallel_thread_count(), 2);
|
||||
set_parallel_threads(0); // reset
|
||||
}
|
||||
|
||||
TEST(ParallelBooleanTest, FaceFaceIntersection_Empty) {
|
||||
BrepModel a, b;
|
||||
auto results = parallel_face_face_intersection(a, b);
|
||||
EXPECT_EQ(results.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(ParallelBooleanTest, FaceFaceIntersection_Boxes) {
|
||||
auto a = make_box(2, 2, 2);
|
||||
auto b = make_box(2, 2, 2);
|
||||
auto results = parallel_face_face_intersection(a, b);
|
||||
// Two identical boxes have overlapping faces
|
||||
EXPECT_GE(results.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(ParallelBooleanTest, ClassifyFaces_Empty) {
|
||||
BrepModel body;
|
||||
auto results = parallel_classify_faces(body, {});
|
||||
EXPECT_EQ(results.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(ParallelBooleanTest, ClassifyFaces_Outside) {
|
||||
auto box = make_box(2, 2, 2);
|
||||
// Point far outside the box
|
||||
std::vector<int> face_ids = {0};
|
||||
auto results = parallel_classify_faces(box, face_ids);
|
||||
EXPECT_EQ(results.size(), 1u);
|
||||
}
|
||||
|
||||
TEST(ParallelBooleanTest, MeshUnion) {
|
||||
auto a = make_box(2, 2, 2);
|
||||
auto b = make_box(2, 2, 2);
|
||||
auto ma = a.to_mesh();
|
||||
auto mb = b.to_mesh();
|
||||
auto result = parallel_mesh_union(ma, mb);
|
||||
EXPECT_GE(result.num_vertices(), 0u);
|
||||
}
|
||||
|
||||
TEST(ParallelBooleanTest, MeshIntersection) {
|
||||
auto a = make_box(2, 2, 2);
|
||||
auto b = make_box(2, 2, 2);
|
||||
auto result = parallel_mesh_intersection(a.to_mesh(), b.to_mesh());
|
||||
EXPECT_GE(result.num_vertices(), 0u);
|
||||
}
|
||||
|
||||
TEST(ParallelBooleanTest, MeshDifference) {
|
||||
auto a = make_box(2, 2, 2);
|
||||
auto b = make_box(1, 1, 1);
|
||||
auto result = parallel_mesh_difference(a.to_mesh(), b.to_mesh());
|
||||
EXPECT_GE(result.num_vertices(), 0u);
|
||||
}
|
||||
|
||||
TEST(ParallelBooleanTest, BrepUnion) {
|
||||
auto a = make_box(2, 2, 2);
|
||||
auto b = make_box(2, 2, 2);
|
||||
auto result = parallel_brep_union(a, b);
|
||||
EXPECT_GE(result.num_faces(), 1u);
|
||||
}
|
||||
|
||||
TEST(ParallelBooleanTest, BrepIntersection) {
|
||||
auto a = make_box(2, 2, 2);
|
||||
auto b = make_box(2, 2, 2);
|
||||
auto result = parallel_brep_intersection(a, b);
|
||||
EXPECT_GE(result.num_faces(), 1u);
|
||||
}
|
||||
|
||||
TEST(ParallelBooleanTest, BrepDifference) {
|
||||
auto a = make_box(2, 2, 2);
|
||||
auto b = make_box(2, 2, 2);
|
||||
auto result = parallel_brep_difference(a, b);
|
||||
EXPECT_GE(result.num_faces(), 1u);
|
||||
}
|
||||
@@ -4,3 +4,5 @@ add_vde_test(test_transform)
|
||||
add_vde_test(test_distance)
|
||||
add_vde_test(test_polygon)
|
||||
add_vde_test(test_cam_toolpath)
|
||||
add_vde_test(test_object_pool)
|
||||
add_vde_test(test_exact_predicates)
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/core/exact_predicates.h"
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
using namespace vde::core;
|
||||
|
||||
TEST(ExactPredicatesTest, Orient2D_CounterClockwise) {
|
||||
double r = exact_orient2d(0, 0, 1, 0, 0, 1);
|
||||
EXPECT_GT(r, 0.0); // CCW
|
||||
}
|
||||
|
||||
TEST(ExactPredicatesTest, Orient2D_Clockwise) {
|
||||
double r = exact_orient2d(0, 0, 0, 1, 1, 0);
|
||||
EXPECT_LT(r, 0.0);
|
||||
}
|
||||
|
||||
TEST(ExactPredicatesTest, Orient2D_Collinear) {
|
||||
double r = exact_orient2d(0, 0, 1, 1, 2, 2);
|
||||
EXPECT_NEAR(r, 0.0, 1e-9);
|
||||
}
|
||||
|
||||
TEST(ExactPredicatesTest, Orient3D_Above) {
|
||||
// Tetrahedron: base on z=0, apex at z=1 → positive volume
|
||||
double r = exact_orient3d(0,0,0, 1,0,0, 0,1,0, 0,0,1);
|
||||
EXPECT_GT(r, 0.0);
|
||||
}
|
||||
|
||||
TEST(ExactPredicatesTest, Orient3D_Coplanar) {
|
||||
double r = exact_orient3d(0,0,0, 1,0,0, 0,1,0, 0.5,0.5,0);
|
||||
EXPECT_NEAR(r, 0.0, 1e-9);
|
||||
}
|
||||
|
||||
TEST(ExactPredicatesTest, InSphere_Inside) {
|
||||
// Regular tetrahedron, point at centroid → inside circumsphere
|
||||
double r = exact_in_sphere(0,0,0, 2,0,0, 0,2,0, 0,0,2, 0.2,0.2,0.2);
|
||||
EXPECT_GT(r, 0.0);
|
||||
}
|
||||
|
||||
TEST(ExactPredicatesTest, InSphere_OnSurface) {
|
||||
double r = exact_in_sphere(0,0,0, 3,0,0, 0,3,0, 0,0,3, 0,0,3);
|
||||
EXPECT_NEAR(r, 0.0, 1e-8);
|
||||
}
|
||||
|
||||
TEST(ExactPredicatesTest, PointInPolygon_Inside) {
|
||||
std::vector<Point3D> poly = {{0,0,0},{2,0,0},{2,2,0},{0,2,0}};
|
||||
EXPECT_TRUE(exact_point_in_polygon({1,1,0}, poly));
|
||||
}
|
||||
|
||||
TEST(ExactPredicatesTest, PointInPolygon_Outside) {
|
||||
std::vector<Point3D> poly = {{0,0,0},{2,0,0},{2,2,0},{0,2,0}};
|
||||
EXPECT_FALSE(exact_point_in_polygon({3,3,0}, poly));
|
||||
}
|
||||
|
||||
TEST(ExactPredicatesTest, PointInPolygon_OnEdge) {
|
||||
std::vector<Point3D> poly = {{0,0,0},{2,0,0},{2,2,0},{0,2,0}};
|
||||
EXPECT_TRUE(exact_point_in_polygon({1,0,0}, poly));
|
||||
}
|
||||
|
||||
TEST(ExactPredicatesTest, PrecisionMode) {
|
||||
set_precision_mode(PrecisionMode::Fast);
|
||||
EXPECT_EQ(precision_mode(), PrecisionMode::Fast);
|
||||
|
||||
set_precision_mode(PrecisionMode::Adaptive);
|
||||
EXPECT_EQ(precision_mode(), PrecisionMode::Adaptive);
|
||||
|
||||
set_precision_mode(PrecisionMode::Adaptive); // restore default
|
||||
}
|
||||
|
||||
TEST(ExactPredicatesTest, IsUncertain) {
|
||||
EXPECT_TRUE(is_uncertain(1e-12));
|
||||
EXPECT_FALSE(is_uncertain(1.0));
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/core/object_pool.h"
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace vde::core;
|
||||
|
||||
struct TestObj { int x = 0; double y = 0.0; };
|
||||
|
||||
TEST(ObjectPoolTest, AcquireRelease) {
|
||||
ObjectPool<TestObj> pool;
|
||||
auto* obj = pool.acquire();
|
||||
ASSERT_NE(obj, nullptr);
|
||||
obj->x = 42;
|
||||
pool.release(obj);
|
||||
auto* obj2 = pool.acquire(); // should reuse
|
||||
EXPECT_EQ(obj2->x, 0); // reset to default
|
||||
EXPECT_GT(pool.reuse_rate(), 0.0);
|
||||
}
|
||||
|
||||
TEST(ObjectPoolTest, AcquireMany) {
|
||||
ObjectPool<TestObj, 64> pool;
|
||||
std::vector<TestObj*> objs;
|
||||
for (int i = 0; i < 1000; ++i) objs.push_back(pool.acquire());
|
||||
for (auto* o : objs) pool.release(o);
|
||||
EXPECT_EQ(pool.total_allocated(), 1000u);
|
||||
EXPECT_GT(pool.total_reused(), 0u);
|
||||
}
|
||||
|
||||
TEST(ObjectPoolTest, ReleaseNullptr) {
|
||||
ObjectPool<TestObj> pool;
|
||||
pool.release(nullptr); // should not crash
|
||||
EXPECT_EQ(pool.total_allocated(), 0u);
|
||||
}
|
||||
|
||||
TEST(ObjectPoolTest, ExpandChunk) {
|
||||
ObjectPool<TestObj, 10> pool;
|
||||
for (int i = 0; i < 25; ++i) {
|
||||
auto* o = pool.acquire();
|
||||
pool.release(o);
|
||||
}
|
||||
EXPECT_GE(pool.pool_size(), 30u);
|
||||
}
|
||||
|
||||
TEST(ThreadSafePoolTest, ConcurrentAcquire) {
|
||||
ThreadSafeObjectPool<TestObj, 64> pool;
|
||||
std::atomic<int> count{0};
|
||||
|
||||
auto worker = [&]() {
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
auto* o = pool.acquire();
|
||||
o->x = i;
|
||||
pool.release(o);
|
||||
count++;
|
||||
}
|
||||
};
|
||||
|
||||
std::thread t1(worker), t2(worker), t3(worker), t4(worker);
|
||||
t1.join(); t2.join(); t3.join(); t4.join();
|
||||
EXPECT_EQ(count, 400);
|
||||
}
|
||||
|
||||
TEST(CowPtrTest, ReadOnly) {
|
||||
CowPtr<std::vector<int>> cow(std::vector<int>{1, 2, 3});
|
||||
EXPECT_EQ(cow.read().size(), 3u);
|
||||
EXPECT_EQ(cow.read()[0], 1);
|
||||
}
|
||||
|
||||
TEST(CowPtrTest, ShallowCopy) {
|
||||
CowPtr<std::vector<int>> cow(std::vector<int>{1, 2, 3});
|
||||
auto cow2 = cow.shallow_copy();
|
||||
EXPECT_EQ(cow.use_count(), 2);
|
||||
EXPECT_EQ(cow2.use_count(), 2);
|
||||
}
|
||||
|
||||
TEST(CowPtrTest, WriteDetaches) {
|
||||
CowPtr<std::vector<int>> cow(std::vector<int>{1, 2, 3});
|
||||
auto cow2 = cow.shallow_copy();
|
||||
EXPECT_EQ(cow.use_count(), 2);
|
||||
|
||||
cow.write().push_back(4);
|
||||
EXPECT_EQ(cow.use_count(), 1); // detached
|
||||
EXPECT_EQ(cow2.use_count(), 1);
|
||||
EXPECT_EQ(cow2.read().size(), 3u); // cow2 unchanged
|
||||
}
|
||||
|
||||
TEST(CowPtrTest, DeepCopy) {
|
||||
CowPtr<std::vector<int>> cow(std::vector<int>{1, 2, 3});
|
||||
auto cow2 = cow.deep_copy();
|
||||
EXPECT_EQ(cow.use_count(), 1);
|
||||
EXPECT_EQ(cow2.use_count(), 1);
|
||||
}
|
||||
|
||||
TEST(CowPtrTest, UniqueCheck) {
|
||||
CowPtr<int> cow(42);
|
||||
EXPECT_TRUE(cow.unique());
|
||||
auto cow2 = cow.shallow_copy();
|
||||
EXPECT_FALSE(cow.unique());
|
||||
}
|
||||
@@ -2,3 +2,4 @@ add_vde_test(test_bezier)
|
||||
add_vde_test(test_nurbs)
|
||||
add_vde_test(test_nurbs_operations)
|
||||
add_vde_test(test_surface_intersection)
|
||||
add_vde_test(test_parallel_intersection)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/curves/parallel_intersection.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace vde::curves;
|
||||
using namespace vde::core;
|
||||
|
||||
// Helper: create a simple NURBS line from (0,0) to (1,0)
|
||||
static NurbsCurve make_line_curve() {
|
||||
std::vector<Point3D> cp = {{0,0,0}, {1,0,0}};
|
||||
std::vector<double> knots = {0, 0, 1, 1};
|
||||
std::vector<double> weights = {1, 1};
|
||||
return NurbsCurve(cp, knots, weights, 1);
|
||||
}
|
||||
|
||||
TEST(ParallelIntersectionTest, CurveIntersection_Empty) {
|
||||
auto results = parallel_curve_intersections({}, {}, {});
|
||||
EXPECT_EQ(results.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(ParallelIntersectionTest, CurveIntersection_SinglePair) {
|
||||
auto c1 = make_line_curve();
|
||||
auto c2 = make_line_curve();
|
||||
std::vector<std::pair<int,int>> pairs = {{0, 0}};
|
||||
|
||||
auto results = parallel_curve_intersections(pairs,
|
||||
std::vector<NurbsCurve>{c1},
|
||||
std::vector<NurbsCurve>{c2});
|
||||
EXPECT_EQ(results.size(), 1u);
|
||||
}
|
||||
|
||||
TEST(ParallelIntersectionTest, SurfaceIntersection_Empty) {
|
||||
// Create two planes
|
||||
NurbsSurface a, b;
|
||||
auto results = parallel_surface_intersection(a, b);
|
||||
EXPECT_EQ(results.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(ParallelIntersectionTest, SurfaceIntersection_Batch) {
|
||||
std::vector<std::pair<int,int>> pairs;
|
||||
auto results = parallel_surface_intersections(pairs, {}, {});
|
||||
EXPECT_EQ(results.size(), 0u);
|
||||
}
|
||||
@@ -4,3 +4,4 @@ add_vde_test(test_quality)
|
||||
add_vde_test(test_smooth)
|
||||
add_vde_test(test_delaunay_3d)
|
||||
add_vde_test(test_mesh_lod)
|
||||
add_vde_test(test_parallel_mc)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/mesh/parallel_mc.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace vde::mesh;
|
||||
|
||||
static double sphere_sdf(double x, double y, double z) {
|
||||
return std::sqrt(x*x + y*y + z*z) - 1.0;
|
||||
}
|
||||
|
||||
TEST(ParallelMCTest, Sphere_Basic) {
|
||||
core::AABB3D bounds({-2, -2, -2}, {2, 2, 2});
|
||||
ParallelMCConfig cfg{32};
|
||||
|
||||
auto mesh = parallel_marching_cubes(sphere_sdf, bounds, cfg);
|
||||
EXPECT_GT(mesh.num_vertices(), 0u);
|
||||
EXPECT_GT(mesh.num_faces(), 0u);
|
||||
}
|
||||
|
||||
TEST(ParallelMCTest, Sphere_HighRes) {
|
||||
core::AABB3D bounds({-2, -2, -2}, {2, 2, 2});
|
||||
ParallelMCConfig cfg{64};
|
||||
|
||||
auto mesh = parallel_marching_cubes(sphere_sdf, bounds, cfg);
|
||||
auto mesh_low = parallel_marching_cubes(sphere_sdf, bounds, ParallelMCConfig{16});
|
||||
EXPECT_GT(mesh.num_faces(), mesh_low.num_faces());
|
||||
}
|
||||
|
||||
TEST(ParallelMCTest, EmptySpace) {
|
||||
core::AABB3D bounds({5, 5, 5}, {6, 6, 6});
|
||||
ParallelMCConfig cfg{16};
|
||||
|
||||
auto mesh = parallel_marching_cubes(sphere_sdf, bounds, cfg);
|
||||
EXPECT_EQ(mesh.num_faces(), 0u);
|
||||
}
|
||||
|
||||
TEST(ParallelMCTest, Adaptive_Sphere) {
|
||||
core::AABB3D bounds({-2, -2, -2}, {2, 2, 2});
|
||||
ParallelMCConfig cfg{32, 0, true, 0.05, 3};
|
||||
|
||||
auto mesh = parallel_adaptive_mc(sphere_sdf, bounds, cfg);
|
||||
EXPECT_GT(mesh.num_vertices(), 0u);
|
||||
}
|
||||
|
||||
TEST(ParallelMCTest, CustomThreadCount) {
|
||||
core::AABB3D bounds({-1.5, -1.5, -1.5}, {1.5, 1.5, 1.5});
|
||||
ParallelMCConfig cfg{32, 2};
|
||||
|
||||
auto mesh = parallel_marching_cubes(sphere_sdf, bounds, cfg);
|
||||
EXPECT_GT(mesh.num_faces(), 0u);
|
||||
}
|
||||
Reference in New Issue
Block a user