Skip to main content

manifold_rust/
collider.rs

1// Copyright 2026 Lars Brubaker
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Collider — BVH-based broadphase collision detection.
16// Uses a radix tree built from Morton codes for O(n log n) queries,
17// matching the C++ Manifold implementation.
18
19use crate::impl_mesh::ManifoldImpl;
20use crate::linalg::{cross, distance2, dot, Mat3x4, Vec3};
21use crate::sort::{get_face_box_morton, morton_code};
22use crate::types::Box as BBox;
23
24// Node encoding (matches C++):
25// - Even indices are leaf nodes: leaf i -> node 2*i
26// - Odd indices are internal nodes: internal i -> node 2*i + 1
27// - Root is always node index 1 (internal node 0)
28const K_ROOT: i32 = 1;
29
30fn leaf_to_node(leaf: i32) -> i32 {
31    leaf * 2
32}
33fn node_to_leaf(node: i32) -> i32 {
34    node / 2
35}
36fn internal_to_node(internal: i32) -> i32 {
37    internal * 2 + 1
38}
39fn node_to_internal(node: i32) -> i32 {
40    node / 2
41}
42fn is_leaf(node: i32) -> bool {
43    node % 2 == 0
44}
45fn is_internal(node: i32) -> bool {
46    node % 2 == 1
47}
48
49#[derive(Clone, Debug, Default)]
50pub struct Collider {
51    leaf_bbox: Vec<BBox>,
52    leaf_morton: Vec<u32>,
53    sorted_to_original: Vec<usize>, // Maps sorted leaf index → original index
54    // BVH tree data (built on demand)
55    node_bbox: Vec<BBox>,          // AABBs for all nodes (2*num_leaves - 1)
56    internal_children: Vec<[i32; 2]>, // Child pairs for internal nodes (num_leaves - 1)
57}
58
59impl Collider {
60    /// Create a new Collider from leaf bounding boxes and morton codes.
61    /// Sorts leaves by morton code internally (required by radix tree algorithm).
62    pub fn new(leaf_bbox: Vec<BBox>, leaf_morton: Vec<u32>) -> Self {
63        debug_assert_eq!(leaf_bbox.len(), leaf_morton.len());
64        let n = leaf_bbox.len();
65        // Sort leaves by morton code (matching C++ SortGeometry ordering)
66        let mut order: Vec<usize> = (0..n).collect();
67        order.sort_by_key(|&i| leaf_morton[i]);
68        let sorted_bbox: Vec<BBox> = order.iter().map(|&i| leaf_bbox[i]).collect();
69        let sorted_morton: Vec<u32> = order.iter().map(|&i| leaf_morton[i]).collect();
70        let sorted_to_original = order;
71
72        let mut collider = Self {
73            leaf_bbox: sorted_bbox,
74            leaf_morton: sorted_morton,
75            sorted_to_original,
76            node_bbox: Vec::new(),
77            internal_children: Vec::new(),
78        };
79        collider.build_bvh();
80        collider
81    }
82
83
84    fn build_bvh(&mut self) {
85        let num_leaves = self.leaf_bbox.len();
86        if num_leaves == 0 {
87            return;
88        }
89        if num_leaves == 1 {
90            // Single leaf: root bbox = leaf bbox
91            self.node_bbox = vec![BBox::default(); 2];
92            self.node_bbox[0] = self.leaf_bbox[0]; // leaf 0 -> node 0
93            self.node_bbox[1] = self.leaf_bbox[0]; // root node 1
94            self.internal_children = vec![[0, 0]; 1]; // root points to leaf 0
95            return;
96        }
97
98        let num_internal = num_leaves - 1;
99        let num_nodes = 2 * num_leaves - 1;
100        self.node_bbox = vec![BBox::default(); num_nodes];
101        self.internal_children = vec![[-1, -1]; num_internal];
102
103        // Copy leaf bboxes into node array
104        for i in 0..num_leaves {
105            self.node_bbox[leaf_to_node(i as i32) as usize] = self.leaf_bbox[i];
106        }
107
108        // Build radix tree structure
109        self.create_radix_tree();
110
111        // Build internal bounding boxes bottom-up
112        self.build_internal_boxes();
113    }
114
115    /// Build radix tree from sorted Morton codes (matches C++ CreateRadixTree).
116    fn create_radix_tree(&mut self) {
117        let num_leaves = self.leaf_bbox.len();
118        if num_leaves <= 1 {
119            return;
120        }
121        let num_internal = num_leaves - 1;
122
123        const K_INITIAL_LENGTH: i32 = 128;
124        const K_LENGTH_MULTIPLE: i32 = 4;
125
126        // Helper: count leading zeros of XOR of two morton codes
127        let prefix_length = |i: i32, j: i32| -> i32 {
128            if j < 0 || j >= num_leaves as i32 {
129                return -1;
130            }
131            let mi = self.leaf_morton[i as usize];
132            let mj = self.leaf_morton[j as usize];
133            let xor = mi ^ mj;
134            if xor == 0 {
135                // Same morton code, use index as tiebreaker (matches C++ clz)
136                32 + ((i as u32 ^ j as u32).leading_zeros() as i32)
137            } else {
138                xor.leading_zeros() as i32
139            }
140        };
141
142        // RangeEnd: find the other end of the range for internal node i
143        let range_end = |i: i32| -> i32 {
144            let dir_val = prefix_length(i, i + 1) - prefix_length(i, i - 1);
145            let dir = if dir_val > 0 { 1i32 } else if dir_val < 0 { -1i32 } else { -1i32 };
146            let common_prefix = prefix_length(i, i - dir);
147            let mut max_length = K_INITIAL_LENGTH;
148            while prefix_length(i, i + dir * max_length) > common_prefix {
149                max_length *= K_LENGTH_MULTIPLE;
150            }
151            let mut length = 0i32;
152            let mut step = max_length / 2;
153            while step > 0 {
154                if prefix_length(i, i + dir * (length + step)) > common_prefix {
155                    length += step;
156                }
157                step /= 2;
158            }
159            i + dir * length
160        };
161
162        // FindSplit: find where the split occurs within [first, last]
163        let find_split = |first: i32, last: i32| -> i32 {
164            let common_prefix = prefix_length(first, last);
165            let mut split = first;
166            let mut step = last - first;
167            loop {
168                step = (step + 1) >> 1; // divide by 2, rounding up
169                let new_split = split + step;
170                if new_split < last {
171                    let split_prefix = prefix_length(first, new_split);
172                    if split_prefix > common_prefix {
173                        split = new_split;
174                    }
175                }
176                if step <= 1 { break; }
177            }
178            split
179        };
180
181        // For each internal node, find its range and split point
182        let mut node_parent = vec![-1i32; 2 * num_leaves - 1];
183
184        for internal in 0..num_internal {
185            let i = internal as i32;
186            let mut first = i;
187            let mut last = range_end(i);
188            if first > last {
189                std::mem::swap(&mut first, &mut last);
190            }
191
192            let split = find_split(first, last);
193
194            // Assign children (matches C++ exactly)
195            let child1 = if split == first {
196                leaf_to_node(split)
197            } else {
198                internal_to_node(split)
199            };
200            // C++ increments split before computing child2
201            let split2 = split + 1;
202            let child2 = if split2 == last {
203                leaf_to_node(split2)
204            } else if (split2 as usize) < num_internal {
205                internal_to_node(split2)
206            } else {
207                // Degenerate case: split2 exceeds internal node range.
208                // This mirrors C++ UB when dir=0 in RangeEnd (clz(0) is UB).
209                // Make child2 = child1 so traversal still works.
210                child1
211            };
212
213            self.internal_children[internal] = [child1, child2];
214            let node = internal_to_node(i);
215            node_parent[child1 as usize] = node;
216            node_parent[child2 as usize] = node;
217        }
218
219        // Build bboxes bottom-up using a counter-based approach
220        // Process leaves and walk up to root
221        let mut counter = vec![0u32; num_internal];
222
223        for leaf in 0..num_leaves {
224            let mut node = leaf_to_node(leaf as i32);
225            loop {
226                let parent = node_parent[node as usize];
227                if parent < 0 {
228                    break; // at root
229                }
230                let internal = node_to_internal(parent);
231                if internal < 0 || internal >= num_internal as i32 {
232                    break;
233                }
234                let idx = internal as usize;
235                counter[idx] += 1;
236                if counter[idx] < 2 {
237                    break; // wait for second child
238                }
239                // Both children ready, compute union
240                let [c1, c2] = self.internal_children[idx];
241                let b1 = self.node_bbox[c1 as usize];
242                let b2 = self.node_bbox[c2 as usize];
243                self.node_bbox[parent as usize] = b1.union_box(&b2);
244                node = parent;
245            }
246        }
247    }
248
249    /// Build internal bounding boxes (already done in create_radix_tree for sequential).
250    fn build_internal_boxes(&mut self) {
251        // Already built in create_radix_tree above.
252        // The C++ separates these for GPU parallelism; we combine them.
253    }
254
255    /// Run a single query box against the BVH, invoking `record(query_idx,
256    /// leaf_idx)` per overlap. Same traversal as `collisions_fn` for one
257    /// index — the per-query entry point for parallel callers (`&self` only,
258    /// so queries can run concurrently with thread-local recording).
259    pub fn collisions_one<R: FnMut(usize, usize)>(
260        &self,
261        query: &BBox,
262        query_idx: usize,
263        mut record: R,
264    ) {
265        if query.is_empty() {
266            return;
267        }
268        if self.internal_children.is_empty() {
269            if self.leaf_bbox.len() == 1 {
270                let original_idx = self.sorted_to_original[0];
271                if query.does_overlap_box(&self.leaf_bbox[0]) {
272                    record(query_idx, original_idx);
273                }
274            }
275            return;
276        }
277        self.traverse_bvh(query, query_idx, false, &mut record);
278    }
279
280    /// BVH-accelerated collision query with function-generated query boxes.
281    /// For each query index 0..n, calls query_box_fn(i) to get the query AABB,
282    /// then traverses the BVH to find overlapping leaves.
283    pub fn collisions_fn<F, R>(
284        &self,
285        query_box_fn: F,
286        n: usize,
287        mut record: R,
288    ) where
289        F: Fn(usize) -> BBox,
290        R: FnMut(usize, usize),
291    {
292        if self.internal_children.is_empty() {
293            // Fallback for 0-1 leaves
294            if self.leaf_bbox.len() == 1 {
295                let original_idx = self.sorted_to_original[0];
296                for query_idx in 0..n {
297                    let query = query_box_fn(query_idx);
298                    if !query.is_empty() && query.does_overlap_box(&self.leaf_bbox[0]) {
299                        record(query_idx, original_idx);
300                    }
301                }
302            }
303            return;
304        }
305
306        for query_idx in 0..n {
307            let query = query_box_fn(query_idx);
308            if query.is_empty() {
309                continue;
310            }
311            self.traverse_bvh(&query, query_idx, false, &mut record);
312        }
313    }
314
315    /// BVH-accelerated collision query with pre-computed query boxes.
316    pub fn collisions_with_boxes<F: FnMut(usize, usize)>(
317        &self,
318        queries: &[BBox],
319        self_collision: bool,
320        mut record: F,
321    ) {
322        if self.internal_children.is_empty() {
323            // Fallback for 0-1 leaves
324            if self.leaf_bbox.len() == 1 {
325                let original_idx = self.sorted_to_original[0];
326                for (qi, q) in queries.iter().enumerate() {
327                    if !(self_collision && qi == original_idx) && q.does_overlap_box(&self.leaf_bbox[0]) {
328                        record(qi, original_idx);
329                    }
330                }
331            }
332            return;
333        }
334
335        for (query_idx, query) in queries.iter().enumerate() {
336            if query.is_empty() {
337                continue;
338            }
339            self.traverse_bvh(query, query_idx, self_collision, &mut record);
340        }
341    }
342
343    /// Point-based collision query using BVH.
344    pub fn collisions_point<F, R>(
345        &self,
346        point_fn: F,
347        n: usize,
348        mut record: R,
349    ) where
350        F: Fn(usize) -> Vec3,
351        R: FnMut(usize, usize),
352    {
353        if self.internal_children.is_empty() {
354            if self.leaf_bbox.len() == 1 {
355                let original_idx = self.sorted_to_original[0];
356                for query_idx in 0..n {
357                    let pt = point_fn(query_idx);
358                    let query = BBox::from_point(pt);
359                    if query.does_overlap_box(&self.leaf_bbox[0]) {
360                        record(query_idx, original_idx);
361                    }
362                }
363            }
364            return;
365        }
366
367        for query_idx in 0..n {
368            let pt = point_fn(query_idx);
369            let query = BBox::from_point(pt);
370            self.traverse_bvh(&query, query_idx, false, &mut record);
371        }
372    }
373
374    /// Stack-based depth-first BVH traversal (matches C++ FindCollision).
375    fn traverse_bvh<F: FnMut(usize, usize)>(
376        &self,
377        query: &BBox,
378        query_idx: usize,
379        self_collision: bool,
380        record: &mut F,
381    ) {
382        let mut stack = [0i32; 64];
383        let mut top: i32 = -1;
384        let mut node = K_ROOT;
385
386        loop {
387            let internal = node_to_internal(node);
388            if internal < 0 || internal as usize >= self.internal_children.len() {
389                if top < 0 { break; }
390                node = stack[top as usize];
391                top -= 1;
392                continue;
393            }
394            let [child1, child2] = self.internal_children[internal as usize];
395
396            let traverse1 = self.check_node(query, child1, query_idx, self_collision, record);
397            let traverse2 = self.check_node(query, child2, query_idx, self_collision, record);
398
399            if !traverse1 && !traverse2 {
400                if top < 0 {
401                    break;
402                }
403                node = stack[top as usize];
404                top -= 1;
405            } else {
406                node = if traverse1 { child1 } else { child2 };
407                if traverse1 && traverse2 {
408                    top += 1;
409                    debug_assert!((top as usize) < 64, "BVH stack overflow");
410                    stack[top as usize] = child2;
411                }
412            }
413        }
414    }
415
416    /// Check if a node's AABB overlaps the query. If it's a leaf, record the hit.
417    /// Returns true if the node is internal and overlaps (should traverse deeper).
418    #[inline]
419    fn check_node<F: FnMut(usize, usize)>(
420        &self,
421        query: &BBox,
422        node: i32,
423        query_idx: usize,
424        self_collision: bool,
425        record: &mut F,
426    ) -> bool {
427        if node < 0 || node as usize >= self.node_bbox.len() {
428            return false;
429        }
430        let node_box = &self.node_bbox[node as usize];
431        let overlaps = query.does_overlap_box(node_box);
432        if overlaps && is_leaf(node) {
433            let sorted_idx = node_to_leaf(node) as usize;
434            let original_idx = self.sorted_to_original[sorted_idx];
435            if !self_collision || original_idx != query_idx {
436                record(query_idx, original_idx);
437            }
438        }
439        overlaps && is_internal(node)
440    }
441
442    pub fn update_boxes(&mut self, leaf_bbox: Vec<BBox>) {
443        debug_assert_eq!(leaf_bbox.len(), self.leaf_bbox.len());
444        // Reorder to sorted order (sorted_to_original maps sorted→original)
445        // We need original→sorted, which is the inverse
446        for (sorted_idx, &orig_idx) in self.sorted_to_original.iter().enumerate() {
447            self.leaf_bbox[sorted_idx] = leaf_bbox[orig_idx];
448        }
449        // Rebuild BVH with new boxes (morton codes and sort order unchanged)
450        self.build_bvh();
451    }
452
453    pub fn transform(&mut self, transform: &Mat3x4) {
454        debug_assert!(Self::is_axis_aligned(transform));
455        for bbox in &mut self.leaf_bbox {
456            *bbox = bbox.transform(transform);
457        }
458        // Rebuild BVH after transform
459        self.build_bvh();
460    }
461
462    pub fn leaf_count(&self) -> usize {
463        self.leaf_bbox.len()
464    }
465
466    pub fn leaf_bbox(&self) -> &[BBox] {
467        &self.leaf_bbox
468    }
469
470    pub fn morton_code(position: Vec3, bbox: &BBox) -> u32 {
471        morton_code(position, bbox)
472    }
473
474    pub fn is_axis_aligned(transform: &Mat3x4) -> bool {
475        for row in 0..3 {
476            let mut zero_count = 0;
477            for col in 0..3 {
478                if transform[col][row] == 0.0 {
479                    zero_count += 1;
480                }
481            }
482            if zero_count != 2 {
483                return false;
484            }
485        }
486        true
487    }
488
489    pub fn leaf_morton(&self) -> &[u32] {
490        &self.leaf_morton
491    }
492}
493
494pub fn edge_edge_dist(p: Vec3, a: Vec3, q: Vec3, b: Vec3) -> (Vec3, Vec3) {
495    let t_vec = q - p;
496    let a_dot_a = dot(a, a);
497    let b_dot_b = dot(b, b);
498    let a_dot_b = dot(a, b);
499    let a_dot_t = dot(a, t_vec);
500    let b_dot_t = dot(b, t_vec);
501
502    let denom = a_dot_a * b_dot_b - a_dot_b * a_dot_b;
503    let mut t = if denom != 0.0 {
504        ((a_dot_t * b_dot_b - b_dot_t * a_dot_b) / denom).clamp(0.0, 1.0)
505    } else {
506        0.0
507    };
508
509    let u = if b_dot_b != 0.0 {
510        let u = (t * a_dot_b - b_dot_t) / b_dot_b;
511        if u < 0.0 {
512            t = if a_dot_a != 0.0 { (a_dot_t / a_dot_a).clamp(0.0, 1.0) } else { 0.0 };
513            0.0
514        } else if u > 1.0 {
515            t = if a_dot_a != 0.0 {
516                ((a_dot_b + a_dot_t) / a_dot_a).clamp(0.0, 1.0)
517            } else {
518                0.0
519            };
520            1.0
521        } else {
522            u
523        }
524    } else {
525        t = if a_dot_a != 0.0 { (a_dot_t / a_dot_a).clamp(0.0, 1.0) } else { 0.0 };
526        0.0
527    };
528
529    (p + a * t, q + b * u)
530}
531
532pub fn distance_triangle_triangle_squared(p: [Vec3; 3], q: [Vec3; 3]) -> f64 {
533    let sv = [p[1] - p[0], p[2] - p[1], p[0] - p[2]];
534    let tv = [q[1] - q[0], q[2] - q[1], q[0] - q[2]];
535
536    let mut shown_disjoint = false;
537    let mut mindd = f64::MAX;
538
539    for i in 0..3 {
540        for j in 0..3 {
541            let (cp, cq) = edge_edge_dist(p[i], sv[i], q[j], tv[j]);
542            let v = cq - cp;
543            let dd = dot(v, v);
544
545            if dd <= mindd {
546                mindd = dd;
547
548                let mut id = i + 2;
549                if id >= 3 { id -= 3; }
550                let z = p[id] - cp;
551                let mut a = dot(z, v);
552
553                id = j + 2;
554                if id >= 3 { id -= 3; }
555                let z = q[id] - cq;
556                let mut b = dot(z, v);
557
558                if a <= 0.0 && b >= 0.0 {
559                    return dot(v, v);
560                }
561
562                if a <= 0.0 { a = 0.0; } else if b > 0.0 { b = 0.0; }
563
564                if mindd - a + b > 0.0 {
565                    shown_disjoint = true;
566                }
567            }
568        }
569    }
570
571    let sn = cross(sv[0], sv[1]);
572    let snl = dot(sn, sn);
573    if snl > 1e-15 {
574        let tp = Vec3::new(dot(p[0] - q[0], sn), dot(p[0] - q[1], sn), dot(p[0] - q[2], sn));
575        let mut index = None;
576        if tp.x > 0.0 && tp.y > 0.0 && tp.z > 0.0 {
577            let mut idx = if tp.x < tp.y { 0 } else { 1 };
578            if tp.z < tp[idx] { idx = 2; }
579            index = Some(idx);
580        } else if tp.x < 0.0 && tp.y < 0.0 && tp.z < 0.0 {
581            let mut idx = if tp.x > tp.y { 0 } else { 1 };
582            if tp.z > tp[idx] { idx = 2; }
583            index = Some(idx);
584        }
585
586        if let Some(index) = index {
587            shown_disjoint = true;
588            let q_index = q[index];
589            let v = q_index - p[0];
590            let z = cross(sn, sv[0]);
591            if dot(v, z) > 0.0 {
592                let v = q_index - p[1];
593                let z = cross(sn, sv[1]);
594                if dot(v, z) > 0.0 {
595                    let v = q_index - p[2];
596                    let z = cross(sn, sv[2]);
597                    if dot(v, z) > 0.0 {
598                        let cp = q_index + sn * (tp[index] / snl);
599                        let cq = q_index;
600                        return dot(cp - cq, cp - cq);
601                    }
602                }
603            }
604        }
605    }
606
607    let tn = cross(tv[0], tv[1]);
608    let tnl = dot(tn, tn);
609    if tnl > 1e-15 {
610        let sp = Vec3::new(dot(q[0] - p[0], tn), dot(q[0] - p[1], tn), dot(q[0] - p[2], tn));
611        let mut index = None;
612        if sp.x > 0.0 && sp.y > 0.0 && sp.z > 0.0 {
613            let mut idx = if sp.x < sp.y { 0 } else { 1 };
614            if sp.z < sp[idx] { idx = 2; }
615            index = Some(idx);
616        } else if sp.x < 0.0 && sp.y < 0.0 && sp.z < 0.0 {
617            let mut idx = if sp.x > sp.y { 0 } else { 1 };
618            if sp.z > sp[idx] { idx = 2; }
619            index = Some(idx);
620        }
621
622        if let Some(index) = index {
623            shown_disjoint = true;
624            let p_index = p[index];
625            let v = p_index - q[0];
626            let z = cross(tn, tv[0]);
627            if dot(v, z) > 0.0 {
628                let v = p_index - q[1];
629                let z = cross(tn, tv[1]);
630                if dot(v, z) > 0.0 {
631                    let v = p_index - q[2];
632                    let z = cross(tn, tv[2]);
633                    if dot(v, z) > 0.0 {
634                        let cp = p_index;
635                        let cq = p_index + tn * (sp[index] / tnl);
636                        return dot(cp - cq, cp - cq);
637                    }
638                }
639            }
640        }
641    }
642
643    if shown_disjoint { mindd } else { 0.0 }
644}
645
646pub fn ray_triangle_intersection(
647    origin: Vec3,
648    direction: Vec3,
649    tri: [Vec3; 3],
650) -> Option<f64> {
651    let eps = 1e-9;
652    let edge1 = tri[1] - tri[0];
653    let edge2 = tri[2] - tri[0];
654    let h = cross(direction, edge2);
655    let a = dot(edge1, h);
656    if a.abs() < eps {
657        return None;
658    }
659    let f = 1.0 / a;
660    let s = origin - tri[0];
661    let u = f * dot(s, h);
662    if !(0.0..=1.0).contains(&u) {
663        return None;
664    }
665    let q = cross(s, edge1);
666    let v = f * dot(direction, q);
667    if v < 0.0 || u + v > 1.0 {
668        return None;
669    }
670    let t = f * dot(edge2, q);
671    if t > eps { Some(t) } else { None }
672}
673
674impl ManifoldImpl {
675    pub fn is_self_intersecting(&self) -> bool {
676        let ep = 2.0 * self.epsilon;
677        let epsilon_sq = ep * ep;
678        let (face_box, face_morton) = get_face_box_morton(self);
679        let collider = Collider::new(face_box.clone(), face_morton);
680        let mut intersecting = false;
681
682        collider.collisions_with_boxes(&face_box, true, |tri0, tri1| {
683            if intersecting {
684                return;
685            }
686            let tri_verts0 = self.face_triangle_vertices(tri0);
687            let tri_verts1 = self.face_triangle_vertices(tri1);
688
689            for a in &tri_verts0 {
690                for b in &tri_verts1 {
691                    if distance2(*a, *b) <= epsilon_sq {
692                        return;
693                    }
694                }
695            }
696
697            if distance_triangle_triangle_squared(tri_verts0, tri_verts1) == 0.0 {
698                let mut tmp0 = tri_verts0;
699                let mut tmp1 = tri_verts1;
700                for i in 0..3 {
701                    tmp0[i] = tri_verts0[i] + self.face_normal[tri1] * ep;
702                }
703                if distance_triangle_triangle_squared(tmp0, tri_verts1) > 0.0 {
704                    return;
705                }
706                for i in 0..3 {
707                    tmp0[i] = tri_verts0[i] - self.face_normal[tri1] * ep;
708                }
709                if distance_triangle_triangle_squared(tmp0, tri_verts1) > 0.0 {
710                    return;
711                }
712                for i in 0..3 {
713                    tmp1[i] = tri_verts1[i] + self.face_normal[tri0] * ep;
714                }
715                if distance_triangle_triangle_squared(tri_verts0, tmp1) > 0.0 {
716                    return;
717                }
718                for i in 0..3 {
719                    tmp1[i] = tri_verts1[i] - self.face_normal[tri0] * ep;
720                }
721                if distance_triangle_triangle_squared(tri_verts0, tmp1) > 0.0 {
722                    return;
723                }
724                intersecting = true;
725            }
726        });
727
728        intersecting
729    }
730
731    pub fn min_gap(&self, other: &ManifoldImpl, search_length: f64) -> f64 {
732        let (self_box, self_morton) = get_face_box_morton(self);
733        let (mut other_box, _) = get_face_box_morton(other);
734        for bbox in &mut other_box {
735            bbox.min = bbox.min - Vec3::splat(search_length);
736            bbox.max = bbox.max + Vec3::splat(search_length);
737        }
738
739        let collider = Collider::new(self_box, self_morton);
740        let mut min_distance = f64::INFINITY;
741        collider.collisions_with_boxes(&other_box, false, |tri_other, tri| {
742            let p = self.face_triangle_vertices(tri);
743            let q = other.face_triangle_vertices(tri_other);
744            min_distance = min_distance.min(distance_triangle_triangle_squared(p, q));
745        });
746
747        min_distance.min(search_length * search_length).sqrt()
748    }
749
750    fn face_triangle_vertices(&self, tri: usize) -> [Vec3; 3] {
751        [
752            self.vert_pos[self.halfedge[3 * tri].start_vert as usize],
753            self.vert_pos[self.halfedge[3 * tri + 1].start_vert as usize],
754            self.vert_pos[self.halfedge[3 * tri + 2].start_vert as usize],
755        ]
756    }
757}
758
759#[cfg(test)]
760mod tests {
761    use super::*;
762    use crate::linalg::{mat4_to_mat3x4, translation_matrix};
763
764    #[test]
765    fn test_collider_box_overlap() {
766        let boxes = vec![
767            BBox::from_points(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 1.0)),
768            BBox::from_points(Vec3::new(2.0, 2.0, 2.0), Vec3::new(3.0, 3.0, 3.0)),
769        ];
770        let collider = Collider::new(boxes.clone(), vec![0, 1]);
771        let mut hits = Vec::new();
772        collider.collisions_with_boxes(&boxes, true, |a, b| hits.push((a, b)));
773        assert!(hits.is_empty());
774
775        let queries = vec![BBox::from_points(Vec3::new(0.5, 0.5, 0.5), Vec3::new(2.5, 2.5, 2.5))];
776        collider.collisions_with_boxes(&queries, false, |a, b| hits.push((a, b)));
777        assert_eq!(hits, vec![(0, 0), (0, 1)]);
778    }
779
780    #[test]
781    fn test_ray_triangle_intersection() {
782        let tri = [
783            Vec3::new(0.0, 0.0, 0.0),
784            Vec3::new(1.0, 0.0, 0.0),
785            Vec3::new(0.0, 1.0, 0.0),
786        ];
787        let hit = ray_triangle_intersection(Vec3::new(0.25, 0.25, -1.0), Vec3::new(0.0, 0.0, 1.0), tri);
788        assert!(hit.is_some());
789    }
790
791    #[test]
792    fn test_triangle_triangle_distance_zero_for_intersection() {
793        let a = [
794            Vec3::new(0.0, 0.0, 0.0),
795            Vec3::new(1.0, 0.0, 0.0),
796            Vec3::new(0.0, 1.0, 0.0),
797        ];
798        let b = [
799            Vec3::new(0.25, 0.25, -1.0),
800            Vec3::new(0.25, 0.25, 1.0),
801            Vec3::new(0.75, 0.25, 0.0),
802        ];
803        assert_eq!(distance_triangle_triangle_squared(a, b), 0.0);
804    }
805
806    #[test]
807    fn test_cube_not_self_intersecting() {
808        let m = ManifoldImpl::cube(&Mat3x4::identity());
809        assert!(!m.is_self_intersecting());
810    }
811
812    #[test]
813    fn test_min_gap_between_cubes() {
814        let a = ManifoldImpl::cube(&Mat3x4::identity());
815        let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(2.0, 0.0, 0.0))));
816        let gap = a.min_gap(&b, 5.0);
817        assert!((gap - 1.0).abs() < 1e-8, "gap = {}", gap);
818    }
819
820    #[test]
821    fn test_bvh_many_boxes() {
822        // Create many non-overlapping boxes and verify BVH finds the right pairs
823        let n = 100;
824        let mut boxes = Vec::new();
825        let mut mortons = Vec::new();
826        for i in 0..n {
827            let x = i as f64 * 3.0;
828            boxes.push(BBox::from_points(
829                Vec3::new(x, 0.0, 0.0),
830                Vec3::new(x + 1.0, 1.0, 1.0),
831            ));
832            mortons.push(i as u32);
833        }
834        let collider = Collider::new(boxes.clone(), mortons);
835
836        // Query that overlaps box 50
837        let query = vec![BBox::from_points(
838            Vec3::new(150.5, 0.5, 0.5),
839            Vec3::new(150.6, 0.6, 0.6),
840        )];
841        let mut hits = Vec::new();
842        collider.collisions_with_boxes(&query, false, |a, b| hits.push((a, b)));
843        assert_eq!(hits, vec![(0, 50)]);
844    }
845}