1use nalgebra::SVector;
15use crate::body::{BodyHandle, BodyType, RigidBody};
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19pub struct BroadphasePair(pub BodyHandle, pub BodyHandle);
20
21#[derive(Clone, Debug)]
23pub struct Aabb<const D: usize> {
24 pub min: SVector<f64, D>,
25 pub max: SVector<f64, D>,
26}
27
28impl<const D: usize> Aabb<D> {
29 pub fn empty() -> Self {
30 Self { min: SVector::from_element(f64::MAX), max: SVector::from_element(f64::MIN) }
31 }
32 pub fn union(&self, other: &Self) -> Self {
33 let mut min = self.min; let mut max = self.max;
34 for i in 0..D {
35 if other.min[i] < min[i] { min[i] = other.min[i]; }
36 if other.max[i] > max[i] { max[i] = other.max[i]; }
37 }
38 Self { min, max }
39 }
40 pub fn overlaps(&self, other: &Self) -> bool {
41 for i in 0..D {
42 if self.max[i] < other.min[i] || self.min[i] > other.max[i] { return false; }
43 }
44 true
45 }
46 pub fn from_sphere(center: &SVector<f64, D>, radius: f64) -> Self {
47 Self { min: center - SVector::from_element(radius), max: center + SVector::from_element(radius) }
48 }
49}
50
51fn quantize(value: f64, world_min: f64, world_max: f64, bits: usize) -> u32 {
53 let range = world_max - world_min;
54 if range <= 0.0 { return 0; }
55 let max_val = ((1u64 << bits) - 1) as f64;
56 let normalized = ((value - world_min) / range).clamp(0.0, 1.0);
57 (normalized * max_val) as u32
58}
59
60pub fn morton_encode<const D: usize>(
62 position: &SVector<f64, D>, world_min: &SVector<f64, D>, world_max: &SVector<f64, D>,
63) -> u64 {
64 let bits = 64 / D;
65 let mut code: u64 = 0;
66 for bit in 0..bits {
67 for axis in 0..D {
68 let q = quantize(position[axis], world_min[axis], world_max[axis], bits);
69 code |= (((q >> bit) & 1) as u64) << (bit * D + axis);
70 }
71 }
72 code
73}
74
75pub fn morton_prefix(code: u64, prefix_bits: usize) -> u64 {
77 if prefix_bits >= 64 { code } else { code >> (64 - prefix_bits) }
78}
79
80const BRUTE_FORCE_THRESHOLD: usize = 50;
82const LEAF_BIT: u32 = 0x8000_0000;
83fn is_leaf(idx: u32) -> bool { idx & LEAF_BIT != 0 }
84fn leaf_index(idx: u32) -> usize { (idx & !LEAF_BIT) as usize }
85
86#[derive(Clone, Debug)]
87struct BvhNode<const D: usize> { aabb: Aabb<D>, left: u32, right: u32 }
88
89pub struct Lbvh<const D: usize> {
91 sorted_indices: Vec<usize>,
92 leaf_aabbs: Vec<Aabb<D>>,
93 nodes: Vec<BvhNode<D>>,
94}
95
96impl<const D: usize> Lbvh<D> {
97 pub fn build(bodies: &[RigidBody<D>]) -> Self {
98 let n = bodies.len();
99 let mut aabbs = Vec::with_capacity(n);
100 let mut world_min = SVector::<f64, D>::from_element(f64::MAX);
101 let mut world_max = SVector::<f64, D>::from_element(f64::MIN);
102 for body in bodies {
103 let (c_local, r) = body.collider.bounding_sphere();
104 let c_world = body.transform.transform_point(&c_local).0;
105 let aabb = Aabb::from_sphere(&c_world, r);
106 for i in 0..D {
107 if aabb.min[i] < world_min[i] { world_min[i] = aabb.min[i]; }
108 if aabb.max[i] > world_max[i] { world_max[i] = aabb.max[i]; }
109 }
110 aabbs.push(aabb);
111 }
112 for i in 0..D {
113 if world_max[i] - world_min[i] < 1e-6 { world_min[i] -= 1.0; world_max[i] += 1.0; }
114 }
115 let mut morton_codes: Vec<u64> = aabbs.iter()
116 .map(|a| { let c = (a.min + a.max) * 0.5; morton_encode::<D>(&c, &world_min, &world_max) })
117 .collect();
118 let mut sorted_indices: Vec<usize> = (0..n).collect();
119 sorted_indices.sort_by_key(|&i| morton_codes[i]);
120 let sorted_aabbs: Vec<Aabb<D>> = sorted_indices.iter().map(|&i| aabbs[i].clone()).collect();
121 let nodes = if n <= 1 { Vec::new() } else { Self::build_nodes(&sorted_indices.iter().map(|&i| morton_codes[i]).collect::<Vec<_>>(), &sorted_aabbs) };
122 Self { sorted_indices, leaf_aabbs: sorted_aabbs, nodes }
123 }
124
125 fn find_split(codes: &[u64], first: usize, last: usize) -> usize {
126 if codes[first] == codes[last] { return (first + last) / 2; }
127 let cp = (codes[first] ^ codes[last]).leading_zeros();
128 let mut split = first; let mut step = last - first;
129 loop {
130 step = (step + 1) / 2;
131 let ns = split + step;
132 if ns < last && (codes[first] ^ codes[ns]).leading_zeros() > cp { split = ns; }
133 if step <= 1 { break; }
134 }
135 split
136 }
137
138 fn build_nodes(codes: &[u64], aabbs: &[Aabb<D>]) -> Vec<BvhNode<D>> {
139 let mut nodes = Vec::with_capacity(codes.len() - 1);
140 Self::build_rec(codes, aabbs, 0, codes.len() - 1, &mut nodes);
141 nodes
142 }
143
144 fn build_rec(codes: &[u64], aabbs: &[Aabb<D>], first: usize, last: usize, nodes: &mut Vec<BvhNode<D>>) -> u32 {
145 if first == last { return LEAF_BIT | (first as u32); }
146 let split = Self::find_split(codes, first, last);
147 let left = Self::build_rec(codes, aabbs, first, split, nodes);
148 let right = Self::build_rec(codes, aabbs, split + 1, last, nodes);
149 let la = if is_leaf(left) { aabbs[leaf_index(left)].clone() } else { nodes[left as usize].aabb.clone() };
150 let ra = if is_leaf(right) { aabbs[leaf_index(right)].clone() } else { nodes[right as usize].aabb.clone() };
151 let idx = nodes.len() as u32;
152 nodes.push(BvhNode { aabb: la.union(&ra), left, right });
153 idx
154 }
155
156 pub fn query_pairs(&self, bodies: &[RigidBody<D>]) -> Vec<BroadphasePair> {
157 let n = self.sorted_indices.len();
158 if n <= 1 { return Vec::new(); }
159 let mut pairs = Vec::new();
160 let root = (self.nodes.len() - 1) as u32;
161 for li in 0..n {
162 let la = &self.leaf_aabbs[li];
163 let lt = bodies[self.sorted_indices[li]].body_type;
164 let mut stack = arrayvec::ArrayVec::<u32, 64>::new();
165 stack.push(root);
166 while let Some(ni) = stack.pop() {
167 if is_leaf(ni) {
168 let oi = leaf_index(ni);
169 if oi > li {
170 let ot = bodies[self.sorted_indices[oi]].body_type;
171 if lt == BodyType::Static && ot == BodyType::Static { continue; }
172 if !should_collide(&bodies[self.sorted_indices[li]], &bodies[self.sorted_indices[oi]]) { continue; }
173 if la.overlaps(&self.leaf_aabbs[oi]) {
174 let (ha, hb) = (bodies[self.sorted_indices[li]].handle, bodies[self.sorted_indices[oi]].handle);
175 if ha < hb { pairs.push(BroadphasePair(ha, hb)); }
176 else { pairs.push(BroadphasePair(hb, ha)); }
177 }
178 }
179 continue;
180 }
181 let node = &self.nodes[ni as usize];
182 if la.overlaps(&node.aabb) { stack.push(node.left); stack.push(node.right); }
183 }
184 }
185 pairs.sort_unstable_by_key(|p| (p.0, p.1));
186 pairs.dedup();
187 pairs
188 }
189}
190
191#[inline]
194fn should_collide<const D: usize>(a: &RigidBody<D>, b: &RigidBody<D>) -> bool {
195 (a.collision_group & b.collision_mask != 0) && (b.collision_group & a.collision_mask != 0)
196}
197
198pub fn find_pairs<const D: usize>(bodies: &[RigidBody<D>]) -> Vec<BroadphasePair> {
200 if bodies.len() < BRUTE_FORCE_THRESHOLD { find_pairs_brute(bodies) }
201 else { Lbvh::build(bodies).query_pairs(bodies) }
202}
203
204fn find_pairs_brute<const D: usize>(bodies: &[RigidBody<D>]) -> Vec<BroadphasePair> {
205 let mut pairs = Vec::new();
206 for i in 0..bodies.len() {
207 let ti = bodies[i].body_type;
208 for j in (i+1)..bodies.len() {
209 if ti == BodyType::Static && bodies[j].body_type == BodyType::Static { continue; }
210 if !should_collide(&bodies[i], &bodies[j]) { continue; }
211 let (ci, ri) = bodies[i].collider.bounding_sphere();
212 let (cj, rj) = bodies[j].collider.bounding_sphere();
213 let wi = bodies[i].transform.transform_point(&ci);
214 let wj = bodies[j].transform.transform_point(&cj);
215 if wi.distance(&wj) <= ri + rj {
216 pairs.push(BroadphasePair(bodies[i].handle, bodies[j].handle));
217 }
218 }
219 }
220 pairs
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::body::RigidBody;
227 use symtropy_math::{Point, Sphere};
228
229 #[test]
230 fn overlapping_pair_detected() {
231 let bodies = vec![
232 RigidBody::<3>::dynamic_sphere(BodyHandle(0), Point::new([0.0, 0.0, 0.0]), 1.0, 1.0),
233 RigidBody::<3>::dynamic_sphere(BodyHandle(1), Point::new([1.5, 0.0, 0.0]), 1.0, 1.0),
234 ];
235 assert_eq!(find_pairs(&bodies).len(), 1);
236 }
237
238 #[test]
239 fn separated_pair_not_detected() {
240 let bodies = vec![
241 RigidBody::<3>::dynamic_sphere(BodyHandle(0), Point::new([0.0, 0.0, 0.0]), 1.0, 1.0),
242 RigidBody::<3>::dynamic_sphere(BodyHandle(1), Point::new([5.0, 0.0, 0.0]), 1.0, 1.0),
243 ];
244 assert!(find_pairs(&bodies).is_empty());
245 }
246
247 #[test]
248 fn static_static_ignored() {
249 let bodies = vec![
250 RigidBody::<3>::static_body(BodyHandle(0), Point::origin(), Box::new(Sphere::unit())),
251 RigidBody::<3>::static_body(BodyHandle(1), Point::new([0.5, 0.0, 0.0]), Box::new(Sphere::unit())),
252 ];
253 assert!(find_pairs(&bodies).is_empty());
254 }
255
256 #[test]
257 fn static_dynamic_detected() {
258 let bodies = vec![
259 RigidBody::<3>::static_body(BodyHandle(0), Point::origin(), Box::new(Sphere::unit())),
260 RigidBody::<3>::dynamic_sphere(BodyHandle(1), Point::new([1.5, 0.0, 0.0]), 1.0, 1.0),
261 ];
262 assert_eq!(find_pairs(&bodies).len(), 1);
263 }
264
265 #[test]
266 fn multiple_pairs() {
267 let bodies = vec![
268 RigidBody::<3>::dynamic_sphere(BodyHandle(0), Point::new([0.0, 0.0, 0.0]), 1.0, 1.0),
269 RigidBody::<3>::dynamic_sphere(BodyHandle(1), Point::new([1.5, 0.0, 0.0]), 1.0, 1.0),
270 RigidBody::<3>::dynamic_sphere(BodyHandle(2), Point::new([10.0, 0.0, 0.0]), 1.0, 1.0),
271 RigidBody::<3>::dynamic_sphere(BodyHandle(3), Point::new([1.0, 1.0, 0.0]), 1.0, 1.0),
272 ];
273 assert!(find_pairs(&bodies).len() >= 2);
274 }
275
276 #[test]
277 fn morton_encode_2d_origin() {
278 let pos = SVector::<f64, 2>::from([0.0, 0.0]);
279 let min = SVector::<f64, 2>::from([0.0, 0.0]);
280 let max = SVector::<f64, 2>::from([100.0, 100.0]);
281 assert_eq!(morton_encode::<2>(&pos, &min, &max), 0);
282 }
283
284 #[test]
285 fn morton_locality() {
286 let min = SVector::<f64, 3>::from([0.0; 3]);
287 let max = SVector::<f64, 3>::from([100.0; 3]);
288 let a = morton_encode::<3>(&SVector::from([10.0, 10.0, 10.0]), &min, &max);
289 let b = morton_encode::<3>(&SVector::from([11.0, 10.0, 10.0]), &min, &max);
290 let far = morton_encode::<3>(&SVector::from([90.0, 90.0, 90.0]), &min, &max);
291 assert!((a as i64 - b as i64).unsigned_abs() < (a as i64 - far as i64).unsigned_abs());
292 }
293
294 #[test]
295 fn lbvh_matches_brute_force() {
296 let mut bodies = Vec::new();
297 for i in 0..(BRUTE_FORCE_THRESHOLD + 10) {
298 let x = (i as f64) * 0.5;
299 bodies.push(RigidBody::<2>::dynamic_sphere(BodyHandle(i), Point::new([x, 0.0]), 1.0, 1.0));
300 }
301 let mut brute = find_pairs_brute(&bodies);
302 brute.sort_by_key(|p| (p.0, p.1));
303 let lbvh = find_pairs(&bodies);
304 for pair in &brute {
305 assert!(lbvh.contains(pair), "LBVH missing pair {:?}", pair);
306 }
307 }
308
309 #[test]
310 fn lbvh_deterministic() {
311 let bodies = vec![
312 RigidBody::<3>::dynamic_sphere(BodyHandle(0), Point::new([0.0, 0.0, 0.0]), 1.0, 1.0),
313 RigidBody::<3>::dynamic_sphere(BodyHandle(1), Point::new([1.5, 0.0, 0.0]), 1.0, 1.0),
314 RigidBody::<3>::dynamic_sphere(BodyHandle(2), Point::new([0.5, 0.5, 0.5]), 0.8, 1.0),
315 ];
316 assert_eq!(find_pairs(&bodies), find_pairs(&bodies));
317 }
318
319 #[test]
320 fn aabb_overlap() {
321 let a = Aabb::<2> { min: SVector::from([0.0, 0.0]), max: SVector::from([2.0, 2.0]) };
322 let b = Aabb::<2> { min: SVector::from([1.0, 1.0]), max: SVector::from([3.0, 3.0]) };
323 let c = Aabb::<2> { min: SVector::from([5.0, 5.0]), max: SVector::from([6.0, 6.0]) };
324 assert!(a.overlaps(&b));
325 assert!(!a.overlaps(&c));
326 }
327}