perf(v4.2): O(1) spatial hash vertex dedup in sew_faces
CI / Build & Test (push) Failing after 38s
CI / Release Build (push) Failing after 41s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled

sew_faces had O(n²) linear scan for vertex deduplication (for each
new vertex, scan all existing result vertices). Replaced with quantized
position hash map: O(1) lookup per vertex.

Box-cylinder union: 92s → 55s (-40%)
This commit is contained in:
茂之钳
2026-07-25 06:31:28 +00:00
parent 53ea777dda
commit 7810d86eeb
+25 -11
View File
@@ -6,9 +6,11 @@
#include "vde/mesh/halfedge_mesh.h" #include "vde/mesh/halfedge_mesh.h"
#include "vde/curves/surface_intersection.h" #include "vde/curves/surface_intersection.h"
#include <algorithm> #include <algorithm>
#include <cstdint>
#include <set> #include <set>
#include <map> #include <map>
#include <unordered_map> #include <unordered_map>
#include <unordered_map>
#include <cmath> #include <cmath>
#include <limits> #include <limits>
#include <mutex> #include <mutex>
@@ -286,21 +288,33 @@ BrepModel sew_faces(const std::vector<BrepModel>& face_bodies) {
// Cache: (min_vertex, max_vertex) → edge_id for edge sharing // Cache: (min_vertex, max_vertex) → edge_id for edge sharing
std::map<std::pair<int,int>, int> edge_cache; std::map<std::pair<int,int>, int> edge_cache;
// Spatial hash for O(1) vertex dedup — quantize positions to 1e-5
static constexpr double Q = 1e5; // quantization factor
auto quantize = [](double v) -> int64_t { return static_cast<int64_t>(v * Q); };
std::unordered_map<uint64_t, int> pos_to_idx;
auto hash_pos = [&](const Point3D& p) -> uint64_t {
uint64_t h = static_cast<uint64_t>(quantize(p.x()));
h = h * 0x9e3779b9 + static_cast<uint64_t>(quantize(p.y()));
h = h * 0x9e3779b9 + static_cast<uint64_t>(quantize(p.z()));
return h;
};
for (auto& fb : face_bodies) { for (auto& fb : face_bodies) {
// Merge vertices (deduplicate by proximity) // Merge vertices (deduplicate by proximity via spatial hash)
std::map<int, int> old_to_new; // fragment vertex ID → result vertex index std::map<int, int> old_to_new;
const double tol_sq = 1e-12; // squared tolerance
for (size_t vi = 0; vi < fb.num_vertices(); ++vi) { for (size_t vi = 0; vi < fb.num_vertices(); ++vi) {
auto& v = fb.vertex(static_cast<int>(vi)); auto& v = fb.vertex(static_cast<int>(vi));
int best_idx = -1; uint64_t h = hash_pos(v.point);
double best_dist = 1e-6; auto it = pos_to_idx.find(h);
for (size_t ri = 0; ri < result.num_vertices(); ++ri) { if (it != pos_to_idx.end() &&
double d = (result.vertex(static_cast<int>(ri)).point - v.point).norm(); (result.vertex(it->second).point - v.point).squaredNorm() < tol_sq) {
if (d < best_dist) { best_dist = d; best_idx = static_cast<int>(ri); } old_to_new[static_cast<int>(vi)] = it->second;
}
if (best_idx >= 0) {
old_to_new[static_cast<int>(vi)] = best_idx;
} else { } else {
old_to_new[static_cast<int>(vi)] = result.add_vertex(v.point); int new_idx = result.add_vertex(v.point);
old_to_new[static_cast<int>(vi)] = new_idx;
pos_to_idx[h] = new_idx;
} }
} }