Skip to content

Commit e67f802

Browse files
authored
Add Rightmost-Leaf Pruning to LBVH Traversal (#222)
* Add rightmost-leaf tracking to LBVH Use RightmostLeaves to skip subtrees fully left of a query during triangular (self-collision) traversal. Propagate the structure through init_bvh, traversal routines (including SIMD), detect_candidates, and clear(). Also adjust attempt_add_candidate and add a benchmark test for edge-edge candidate detection. * Fix index errors in edge-edge feasibility Correct vertex indices passed to point_edge_closest_point and check_vertex_feasible_region in is_edge_edge_feasible. Previously the EA/EB vertex arguments were swapped, causing incorrect closest-point computations and feasibility checks
1 parent 746916e commit e67f802

4 files changed

Lines changed: 195 additions & 44 deletions

File tree

src/ipc/broad_phase/lbvh.cpp

Lines changed: 96 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ void LBVH::build(
6565

6666
compute_mesh_aabb(mesh_aabb.min, mesh_aabb.max);
6767

68-
init_bvh(vertex_boxes, vertex_bvh);
69-
init_bvh(edge_boxes, edge_bvh);
70-
init_bvh(face_boxes, face_bvh);
68+
init_bvh(vertex_boxes, vertex_bvh, vertex_rightmost_leaves);
69+
init_bvh(edge_boxes, edge_bvh, edge_rightmost_leaves);
70+
init_bvh(face_boxes, face_bvh, face_rightmost_leaves);
7171

7272
// Copy edge and face vertex ids for access during traversal
7373
{
@@ -199,7 +199,8 @@ namespace {
199199
}
200200
} // namespace
201201

202-
void LBVH::init_bvh(const AABBs& boxes, Nodes& lbvh) const
202+
void LBVH::init_bvh(
203+
const AABBs& boxes, Nodes& lbvh, RightmostLeaves& rightmost_leaves) const
203204
{
204205
if (boxes.empty()) {
205206
return;
@@ -252,6 +253,10 @@ void LBVH::init_bvh(const AABBs& boxes, Nodes& lbvh) const
252253
assert(boxes.size() <= std::numeric_limits<int>::max());
253254
const int LEAF_OFFSET = int(boxes.size()) - 1;
254255

256+
if (rightmost_leaves.size() != lbvh.size()) {
257+
rightmost_leaves.resize(lbvh.size());
258+
}
259+
255260
LBVH::ConstructionInfos construction_infos(lbvh.size());
256261
{
257262
IPC_TOOLKIT_PROFILE_BLOCK("build_hierarchy");
@@ -269,6 +274,8 @@ void LBVH::init_bvh(const AABBs& boxes, Nodes& lbvh) const
269274
leaf_node.primitive_id = morton_codes[i].box_id;
270275
leaf_node.is_inner_marker = 0;
271276
lbvh[LEAF_OFFSET + i] = leaf_node; // Store leaf
277+
// A leaf's rightmost leaf is itself
278+
rightmost_leaves[LEAF_OFFSET + i] = i;
272279
}
273280

274281
if (i < LEAF_OFFSET) {
@@ -345,6 +352,11 @@ void LBVH::init_bvh(const AABBs& boxes, Nodes& lbvh) const
345352
lbvh[node_idx].aabb_max =
346353
child_a.aabb_max.max(child_b.aabb_max);
347354

355+
// Compute rightmost leaf: max of children's rightmost
356+
rightmost_leaves[node_idx] = std::max(
357+
rightmost_leaves[lbvh[node_idx].left],
358+
rightmost_leaves[lbvh[node_idx].right]);
359+
348360
if (node_idx == 0) {
349361
break; // root node
350362
}
@@ -360,16 +372,19 @@ void LBVH::clear()
360372
BroadPhase::clear();
361373
// Clear BVH nodes
362374
vertex_bvh.clear();
375+
vertex_rightmost_leaves.clear();
363376
edge_bvh.clear();
377+
edge_rightmost_leaves.clear();
364378
face_bvh.clear();
379+
face_rightmost_leaves.clear();
365380

366381
// Clear vertex IDs
367382
edge_vertex_ids.clear();
368383
face_vertex_ids.clear();
369384
}
370385

371386
namespace {
372-
template <typename Candidate, bool swap_order, bool triangular>
387+
template <typename Candidate, bool swap_order>
373388
inline void attempt_add_candidate(
374389
const LBVH::Node& query,
375390
const LBVH::Node& node,
@@ -381,12 +396,6 @@ namespace {
381396
std::swap(i, j);
382397
}
383398

384-
if constexpr (triangular) {
385-
if (i >= j) {
386-
return;
387-
}
388-
}
389-
390399
if (!can_collide(i, j)) {
391400
return;
392401
}
@@ -397,7 +406,9 @@ namespace {
397406
template <typename Candidate, bool swap_order, bool triangular>
398407
void traverse_lbvh(
399408
const LBVH::Node& query,
409+
const size_t query_leaf_idx,
400410
const LBVH::Nodes& lbvh,
411+
const LBVH::RightmostLeaves& rightmost_leaves,
401412
const std::function<bool(size_t, size_t)>& can_collide,
402413
std::vector<Candidate>& candidates)
403414
{
@@ -413,8 +424,11 @@ namespace {
413424

414425
if (lbvh.size() == 1) { // Single node case (only root)
415426
assert(node.is_leaf()); // Only one node, so it must be a leaf
427+
if constexpr (triangular) {
428+
break; // No self-collision if only one node
429+
}
416430
if (node.intersects(query)) {
417-
attempt_add_candidate<Candidate, swap_order, triangular>(
431+
attempt_add_candidate<Candidate, swap_order>(
418432
query, node, can_collide, candidates);
419433
}
420434
break;
@@ -431,16 +445,29 @@ namespace {
431445

432446
const LBVH::Node& child_l = lbvh[node.left];
433447
const LBVH::Node& child_r = lbvh[node.right];
434-
const bool intersects_l = child_l.intersects(query);
435-
const bool intersects_r = child_r.intersects(query);
448+
bool intersects_l = child_l.intersects(query);
449+
bool intersects_r = child_r.intersects(query);
450+
451+
// Ignore overlap if the subtree is fully on the
452+
// left-hand side of the query (triangular traversal only).
453+
if constexpr (triangular) {
454+
if (intersects_l
455+
&& rightmost_leaves[node.left] <= query_leaf_idx) {
456+
intersects_l = false;
457+
}
458+
if (intersects_r
459+
&& rightmost_leaves[node.right] <= query_leaf_idx) {
460+
intersects_r = false;
461+
}
462+
}
436463

437464
// Query overlaps a leaf node => report collision.
438465
if (intersects_l && child_l.is_leaf()) {
439-
attempt_add_candidate<Candidate, swap_order, triangular>(
466+
attempt_add_candidate<Candidate, swap_order>(
440467
query, child_l, can_collide, candidates);
441468
}
442469
if (intersects_r && child_r.is_leaf()) {
443-
attempt_add_candidate<Candidate, swap_order, triangular>(
470+
attempt_add_candidate<Candidate, swap_order>(
444471
query, child_r, can_collide, candidates);
445472
}
446473

@@ -468,8 +495,10 @@ namespace {
468495
template <typename Candidate, bool swap_order, bool triangular>
469496
void traverse_lbvh_simd(
470497
const LBVH::Node* queries,
498+
const size_t first_query_leaf_idx,
471499
const size_t n_queries,
472500
const LBVH::Nodes& lbvh,
501+
const LBVH::RightmostLeaves& rightmost_leaves,
473502
const std::function<bool(size_t, size_t)>& can_collide,
474503
std::vector<Candidate>& candidates)
475504
{
@@ -518,6 +547,9 @@ namespace {
518547

519548
if (lbvh.size() == 1) { // Single node case (only root)
520549
assert(node.is_leaf()); // Only one node, so it must be a leaf
550+
if constexpr (triangular) {
551+
break; // No self-collision if only one node
552+
}
521553
// Check intersection with all queries simultaneously
522554
const xs::batch_bool<float> intersects =
523555
(node.aabb_min.x() <= q_max_x)
@@ -529,8 +561,7 @@ namespace {
529561
if (xs::any(intersects)) {
530562
for (int k = 0; k < n_queries; ++k) {
531563
if (intersects.get(k)) {
532-
attempt_add_candidate<
533-
Candidate, swap_order, triangular>(
564+
attempt_add_candidate<Candidate, swap_order>(
534565
queries[k], node, can_collide, candidates);
535566
}
536567
}
@@ -552,7 +583,7 @@ namespace {
552583

553584
// 1. Intersect multiple queries at once
554585
// (child_l.min <= query.max) && (query.min <= child_l.max)
555-
const xs::batch_bool<float> intersects_l =
586+
xs::batch_bool<float> intersects_l =
556587
(child_l.aabb_min.x() <= q_max_x)
557588
& (child_l.aabb_min.y() <= q_max_y)
558589
& (child_l.aabb_min.z() <= q_max_z)
@@ -562,32 +593,57 @@ namespace {
562593

563594
// 2. Intersect multiple queries at once
564595
// (child_r.min <= query.max) && (query.min <= child_r.max)
565-
const xs::batch_bool<float> intersects_r =
596+
xs::batch_bool<float> intersects_r =
566597
(child_r.aabb_min.x() <= q_max_x)
567598
& (child_r.aabb_min.y() <= q_max_y)
568599
& (child_r.aabb_min.z() <= q_max_z)
569600
& (q_min_x <= child_r.aabb_max.x())
570601
& (q_min_y <= child_r.aabb_max.y())
571602
& (q_min_z <= child_r.aabb_max.z());
572603

604+
// Ignore overlap if the subtree is fully on the left-hand side
605+
// of all queries (triangular traversal only).
606+
// We use first_query_leaf_idx (the smallest query leaf index
607+
// in the SIMD batch) for a conservative check: if all leaves
608+
// in the subtree are <= the smallest query, they are also <=
609+
// every other query in the batch.
610+
if constexpr (triangular) {
611+
if (rightmost_leaves[node.left] <= first_query_leaf_idx) {
612+
intersects_l = xs::batch_bool<float>(false);
613+
}
614+
if (rightmost_leaves[node.right] <= first_query_leaf_idx) {
615+
intersects_r = xs::batch_bool<float>(false);
616+
}
617+
}
618+
573619
const bool any_intersects_l = xs::any(intersects_l);
574620
const bool any_intersects_r = xs::any(intersects_r);
575621

576622
// Query overlaps a leaf node => report collision
577623
if (any_intersects_l && child_l.is_leaf()) {
578624
for (int k = 0; k < n_queries; ++k) {
625+
if constexpr (triangular) {
626+
if (rightmost_leaves[node.left]
627+
<= first_query_leaf_idx + k) {
628+
continue;
629+
}
630+
}
579631
if (intersects_l.get(k)) {
580-
attempt_add_candidate<
581-
Candidate, swap_order, triangular>(
632+
attempt_add_candidate<Candidate, swap_order>(
582633
queries[k], child_l, can_collide, candidates);
583634
}
584635
}
585636
}
586637
if (any_intersects_r && child_r.is_leaf()) {
587638
for (int k = 0; k < n_queries; ++k) {
639+
if constexpr (triangular) {
640+
if (rightmost_leaves[node.right]
641+
<= first_query_leaf_idx + k) {
642+
continue;
643+
}
644+
}
588645
if (intersects_r.get(k)) {
589-
attempt_add_candidate<
590-
Candidate, swap_order, triangular>(
646+
attempt_add_candidate<Candidate, swap_order>(
591647
queries[k], child_r, can_collide, candidates);
592648
}
593649
}
@@ -620,6 +676,7 @@ namespace {
620676
void independent_traversal(
621677
const LBVH::Nodes& source,
622678
const LBVH::Nodes& target,
679+
const LBVH::RightmostLeaves& rightmost_leaves,
623680
const std::function<bool(size_t, size_t)>& can_collide,
624681
tbb::enumerable_thread_specific<std::vector<Candidate>>& storage)
625682
{
@@ -655,14 +712,14 @@ namespace {
655712
if constexpr (use_simd) {
656713
assert(actual_end - idx >= 1);
657714
traverse_lbvh_simd<Candidate, swap_order, triangular>(
658-
&source[source_leaf_offset + idx],
715+
&source[source_leaf_offset + idx], idx,
659716
std::min(SIMD_SIZE, actual_end - idx), target,
660-
can_collide, local_candidates);
717+
rightmost_leaves, can_collide, local_candidates);
661718
} else {
662719
#endif
663720
traverse_lbvh<Candidate, swap_order, triangular>(
664-
source[source_leaf_offset + idx], target,
665-
can_collide, local_candidates);
721+
source[source_leaf_offset + idx], idx, target,
722+
rightmost_leaves, can_collide, local_candidates);
666723
#ifdef IPC_TOOLKIT_WITH_SIMD
667724
}
668725
#endif
@@ -675,6 +732,7 @@ template <typename Candidate, bool swap_order, bool triangular>
675732
void LBVH::detect_candidates(
676733
const Nodes& source,
677734
const Nodes& target,
735+
const RightmostLeaves& rightmost_leaves,
678736
const std::function<bool(size_t, size_t)>& can_collide,
679737
std::vector<Candidate>& candidates)
680738
{
@@ -685,7 +743,7 @@ void LBVH::detect_candidates(
685743
tbb::enumerable_thread_specific<std::vector<Candidate>> storage;
686744

687745
independent_traversal<Candidate, swap_order, triangular>(
688-
source, target, can_collide, storage);
746+
source, target, rightmost_leaves, can_collide, storage);
689747

690748
merge_thread_local_vectors(storage, candidates);
691749
}
@@ -699,7 +757,8 @@ void LBVH::detect_vertex_vertex_candidates(
699757

700758
IPC_TOOLKIT_PROFILE_BLOCK("LBVH::detect_vertex_vertex_candidates");
701759

702-
detect_candidates(vertex_bvh, can_vertices_collide, candidates);
760+
detect_candidates(
761+
vertex_bvh, vertex_rightmost_leaves, can_vertices_collide, candidates);
703762
}
704763

705764
void LBVH::detect_edge_vertex_candidates(
@@ -714,7 +773,7 @@ void LBVH::detect_edge_vertex_candidates(
714773
// In 2D and for codimensional edge-vertex collisions, there are more
715774
// vertices than edges, so we want to iterate over the edges.
716775
detect_candidates(
717-
edge_bvh, vertex_bvh,
776+
edge_bvh, vertex_bvh, vertex_rightmost_leaves,
718777
std::bind(&LBVH::can_edge_vertex_collide, this, _1, _2), candidates);
719778
}
720779

@@ -728,8 +787,8 @@ void LBVH::detect_edge_edge_candidates(
728787
IPC_TOOLKIT_PROFILE_BLOCK("LBVH::detect_edge_edge_candidates");
729788

730789
detect_candidates(
731-
edge_bvh, std::bind(&LBVH::can_edges_collide, this, _1, _2),
732-
candidates);
790+
edge_bvh, edge_rightmost_leaves,
791+
std::bind(&LBVH::can_edges_collide, this, _1, _2), candidates);
733792
}
734793

735794
void LBVH::detect_face_vertex_candidates(
@@ -743,7 +802,7 @@ void LBVH::detect_face_vertex_candidates(
743802

744803
// The ratio vertices:faces is 1:2, so we want to iterate over the vertices.
745804
detect_candidates<FaceVertexCandidate, /*swap_order=*/true>(
746-
vertex_bvh, face_bvh,
805+
vertex_bvh, face_bvh, face_rightmost_leaves,
747806
std::bind(&LBVH::can_face_vertex_collide, this, _1, _2), candidates);
748807
}
749808

@@ -758,7 +817,7 @@ void LBVH::detect_edge_face_candidates(
758817

759818
// The ratio edges:faces is 3:2, so we want to iterate over the faces.
760819
detect_candidates<EdgeFaceCandidate, /*swap_order=*/true>(
761-
face_bvh, edge_bvh,
820+
face_bvh, edge_bvh, edge_rightmost_leaves,
762821
std::bind(&LBVH::can_edge_face_collide, this, _1, _2), candidates);
763822
}
764823

@@ -771,8 +830,8 @@ void LBVH::detect_face_face_candidates(
771830

772831
IPC_TOOLKIT_PROFILE_BLOCK("LBVH::detect_face_face_candidates");
773832
detect_candidates(
774-
face_bvh, std::bind(&LBVH::can_faces_collide, this, _1, _2),
775-
candidates);
833+
face_bvh, face_rightmost_leaves,
834+
std::bind(&LBVH::can_faces_collide, this, _1, _2), candidates);
776835
}
777836

778837
// ============================================================================

0 commit comments

Comments
 (0)