Skip to main content

oxiphysics_gpu/collision_gpu/
functions.rs

1// Auto-generated module
2//
3// 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::types::{AabbGpu, SupportPoint};
6
7/// Expand a 10-bit integer into 30 bits by inserting two zero bits between
8/// each original bit.  Used for 3-D Morton code generation.
9pub(super) fn expand_bits(mut v: u32) -> u32 {
10    v &= 0x0000_03FF;
11    v = (v | (v << 16)) & 0x030000FF;
12    v = (v | (v << 8)) & 0x0300F00F;
13    v = (v | (v << 4)) & 0x030C30C3;
14    v = (v | (v << 2)) & 0x09249249;
15    v
16}
17/// Compute a 30-bit Morton code for a normalised 3-D position `(x, y, z)`,
18/// where each component is in `[0, 1]`.
19pub fn morton_code(x: f32, y: f32, z: f32) -> u32 {
20    let xi = (x.clamp(0.0, 1.0) * 1023.0) as u32;
21    let yi = (y.clamp(0.0, 1.0) * 1023.0) as u32;
22    let zi = (z.clamp(0.0, 1.0) * 1023.0) as u32;
23    (expand_bits(xi) << 2) | (expand_bits(yi) << 1) | expand_bits(zi)
24}
25/// Dot product of two f32 3-vectors.
26pub(super) fn dot3f(a: [f32; 3], b: [f32; 3]) -> f32 {
27    a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
28}
29/// Subtract two f32 3-vectors.
30pub(super) fn sub3f(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
31    [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
32}
33/// Add two f32 3-vectors.
34pub(super) fn add3f(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
35    [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
36}
37/// Scale an f32 3-vector.
38pub(super) fn scale3f(a: [f32; 3], s: f32) -> [f32; 3] {
39    [a[0] * s, a[1] * s, a[2] * s]
40}
41/// Squared length of an f32 3-vector.
42pub(super) fn len_sq3f(a: [f32; 3]) -> f32 {
43    dot3f(a, a)
44}
45/// Length of an f32 3-vector.
46pub(super) fn len3f(a: [f32; 3]) -> f32 {
47    len_sq3f(a).sqrt()
48}
49/// Normalise an f32 3-vector; returns zero vector if length < eps.
50pub(super) fn norm3f(a: [f32; 3]) -> [f32; 3] {
51    let l = len3f(a);
52    if l < 1e-9 {
53        [0.0; 3]
54    } else {
55        scale3f(a, 1.0 / l)
56    }
57}
58/// Support function for an AABB along direction `d`.
59pub(super) fn aabb_support(aabb: &AabbGpu, d: [f32; 3]) -> [f32; 3] {
60    [
61        if d[0] >= 0.0 {
62            aabb.max[0]
63        } else {
64            aabb.min[0]
65        },
66        if d[1] >= 0.0 {
67            aabb.max[1]
68        } else {
69            aabb.min[1]
70        },
71        if d[2] >= 0.0 {
72            aabb.max[2]
73        } else {
74            aabb.min[2]
75        },
76    ]
77}
78/// Support in Minkowski difference A - B along `d`.
79pub(super) fn minkowski_support(a: &AabbGpu, b: &AabbGpu, d: [f32; 3]) -> SupportPoint {
80    let neg_d = [-d[0], -d[1], -d[2]];
81    let pa = aabb_support(a, d);
82    let pb = aabb_support(b, neg_d);
83    SupportPoint::new(sub3f(pa, pb))
84}
85/// Update the GJK simplex and return `(intersecting, new_direction)`.
86pub(super) fn update_simplex(simplex: &mut Vec<SupportPoint>) -> (bool, [f32; 3]) {
87    match simplex.len() {
88        2 => {
89            let b = simplex[0].v;
90            let a = simplex[1].v;
91            let ab = sub3f(b, a);
92            let ao = [-a[0], -a[1], -a[2]];
93            if dot3f(ab, ao) > 0.0 {
94                let t = dot3f(ao, ab) / dot3f(ab, ab);
95                let closest = add3f(a, scale3f(ab, t));
96                (false, [-closest[0], -closest[1], -closest[2]])
97            } else {
98                simplex.drain(0..1);
99                (false, ao)
100            }
101        }
102        3 => {
103            let c = simplex[0].v;
104            let b_pt = simplex[1].v;
105            let a = simplex[2].v;
106            let ab = sub3f(b_pt, a);
107            let ac = sub3f(c, a);
108            let ao = [-a[0], -a[1], -a[2]];
109            let n = cross3f(ab, ac);
110            if dot3f(n, ao) > 0.0 {
111                (false, n)
112            } else {
113                (false, [-n[0], -n[1], -n[2]])
114            }
115        }
116        4 => (true, [0.0; 3]),
117        _ => {
118            let a = simplex.last().expect("collection should not be empty").v;
119            (false, [-a[0], -a[1], -a[2]])
120        }
121    }
122}
123/// Cross product for f32 3-vectors.
124pub(super) fn cross3f(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
125    [
126        a[1] * b[2] - a[2] * b[1],
127        a[2] * b[0] - a[0] * b[2],
128        a[0] * b[1] - a[1] * b[0],
129    ]
130}
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use crate::collision_gpu::BroadphaseGpuKernel;
135    use crate::collision_gpu::BvhNodeGpu;
136    use crate::collision_gpu::CollisionGpuPipeline;
137    use crate::collision_gpu::CollisionKernelStats;
138    use crate::collision_gpu::CollisionPair;
139    use crate::collision_gpu::ContactResult;
140    use crate::collision_gpu::GpuAabbTree;
141    use crate::collision_gpu::GpuBroadphase;
142    use crate::collision_gpu::GpuBvhBuilder;
143    use crate::collision_gpu::GpuCollisionPipeline;
144    use crate::collision_gpu::GpuContactCache;
145    use crate::collision_gpu::GpuNarrowphase;
146    use crate::collision_gpu::ManifoldPoint;
147    use crate::collision_gpu::NarrowphaseGpuKernel;
148    use crate::collision_gpu::PersistentManifoldGpu;
149    fn unit_box() -> AabbGpu {
150        AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0])
151    }
152    #[test]
153    fn test_aabb_new() {
154        let a = unit_box();
155        assert_eq!(a.min, [0.0, 0.0, 0.0]);
156        assert_eq!(a.max, [1.0, 1.0, 1.0]);
157    }
158    #[test]
159    fn test_aabb_from_point() {
160        let p = [3.0, 4.0, 5.0];
161        let a = AabbGpu::from_point(p);
162        assert_eq!(a.min, p);
163        assert_eq!(a.max, p);
164    }
165    #[test]
166    fn test_aabb_surface_area() {
167        let a = unit_box();
168        assert!((a.surface_area() - 6.0).abs() < 1e-5);
169    }
170    #[test]
171    fn test_aabb_volume() {
172        let a = unit_box();
173        assert!((a.volume() - 1.0).abs() < 1e-5);
174    }
175    #[test]
176    fn test_aabb_centre() {
177        let a = unit_box();
178        let c = a.centre();
179        assert!((c[0] - 0.5).abs() < 1e-5);
180        assert!((c[1] - 0.5).abs() < 1e-5);
181        assert!((c[2] - 0.5).abs() < 1e-5);
182    }
183    #[test]
184    fn test_aabb_half_extents() {
185        let a = unit_box();
186        let h = a.half_extents();
187        assert!((h[0] - 0.5).abs() < 1e-5);
188    }
189    #[test]
190    fn test_aabb_overlaps_true() {
191        let a = unit_box();
192        let b = AabbGpu::new([0.5, 0.5, 0.5], [1.5, 1.5, 1.5]);
193        assert!(a.overlaps(&b));
194    }
195    #[test]
196    fn test_aabb_overlaps_touching() {
197        let a = unit_box();
198        let b = AabbGpu::new([1.0, 0.0, 0.0], [2.0, 1.0, 1.0]);
199        assert!(a.overlaps(&b));
200    }
201    #[test]
202    fn test_aabb_overlaps_false() {
203        let a = unit_box();
204        let b = AabbGpu::new([2.0, 0.0, 0.0], [3.0, 1.0, 1.0]);
205        assert!(!a.overlaps(&b));
206    }
207    #[test]
208    fn test_aabb_contains_point_inside() {
209        let a = unit_box();
210        assert!(a.contains_point([0.5, 0.5, 0.5]));
211    }
212    #[test]
213    fn test_aabb_contains_point_outside() {
214        let a = unit_box();
215        assert!(!a.contains_point([1.5, 0.5, 0.5]));
216    }
217    #[test]
218    fn test_aabb_expanded() {
219        let a = unit_box();
220        let e = a.expanded(0.1);
221        assert!((e.min[0] - (-0.1)).abs() < 1e-5);
222        assert!((e.max[0] - 1.1).abs() < 1e-5);
223    }
224    #[test]
225    fn test_aabb_merge() {
226        let a = unit_box();
227        let b = AabbGpu::new([2.0, 2.0, 2.0], [3.0, 3.0, 3.0]);
228        let m = a.merge(&b);
229        assert_eq!(m.min, [0.0, 0.0, 0.0]);
230        assert_eq!(m.max, [3.0, 3.0, 3.0]);
231    }
232    #[test]
233    fn test_morton_code_origin() {
234        let code = morton_code(0.0, 0.0, 0.0);
235        assert_eq!(code, 0);
236    }
237    #[test]
238    fn test_morton_code_max() {
239        let _code = morton_code(1.0, 1.0, 1.0);
240    }
241    #[test]
242    fn test_morton_code_sorted_along_x() {
243        let c1 = morton_code(0.1, 0.5, 0.5);
244        let c2 = morton_code(0.9, 0.5, 0.5);
245        assert_ne!(c1, c2);
246    }
247    #[test]
248    fn test_bvh_node_leaf() {
249        let node = BvhNodeGpu::leaf(unit_box(), 42);
250        assert!(node.is_leaf);
251        assert_eq!(node.primitive_index, 42);
252    }
253    #[test]
254    fn test_bvh_node_internal() {
255        let node = BvhNodeGpu::internal(unit_box(), 1, 2);
256        assert!(!node.is_leaf);
257        assert_eq!(node.left_child, 1);
258        assert_eq!(node.right_child, 2);
259    }
260    #[test]
261    fn test_bvh_builder_empty() {
262        let mut builder = GpuBvhBuilder::new();
263        let root = builder.build(&[]);
264        assert_eq!(root, 0);
265        assert!(builder.nodes().is_empty());
266    }
267    #[test]
268    fn test_bvh_builder_single() {
269        let mut builder = GpuBvhBuilder::new();
270        let aabbs = vec![unit_box()];
271        let root = builder.build(&aabbs);
272        assert_eq!(builder.nodes()[root].primitive_index, 0);
273        assert!(builder.nodes()[root].is_leaf);
274    }
275    #[test]
276    fn test_bvh_builder_two() {
277        let aabbs = vec![
278            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
279            AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]),
280        ];
281        let mut builder = GpuBvhBuilder::new();
282        let root = builder.build(&aabbs);
283        assert!(!builder.nodes()[root].is_leaf);
284        assert_eq!(builder.nodes().len(), 3);
285    }
286    #[test]
287    fn test_bvh_query_overlaps() {
288        let aabbs = vec![
289            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
290            AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]),
291            AabbGpu::new([0.5, 0.5, 0.5], [1.5, 1.5, 1.5]),
292        ];
293        let mut builder = GpuBvhBuilder::new();
294        let root = builder.build(&aabbs);
295        let query = AabbGpu::new([0.2, 0.2, 0.2], [0.8, 0.8, 0.8]);
296        let hits = builder.query_overlaps(root, &query);
297        assert!(hits.contains(&0) || hits.contains(&2));
298    }
299    #[test]
300    fn test_broadphase_no_overlap() {
301        let kernel = BroadphaseGpuKernel::new(0.0);
302        let aabbs = vec![
303            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
304            AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]),
305        ];
306        let pairs = kernel.dispatch(&aabbs);
307        assert!(pairs.is_empty());
308    }
309    #[test]
310    fn test_broadphase_overlap() {
311        let kernel = BroadphaseGpuKernel::new(0.0);
312        let aabbs = vec![
313            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
314            AabbGpu::new([0.5, 0.5, 0.5], [1.5, 1.5, 1.5]),
315        ];
316        let pairs = kernel.dispatch(&aabbs);
317        assert_eq!(pairs.len(), 1);
318        assert_eq!(pairs[0].a, 0);
319        assert_eq!(pairs[0].b, 1);
320    }
321    #[test]
322    fn test_broadphase_margin_activates_pair() {
323        let kernel = BroadphaseGpuKernel::new(1.0);
324        let aabbs = vec![
325            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
326            AabbGpu::new([1.5, 0.0, 0.0], [2.5, 1.0, 1.0]),
327        ];
328        let pairs = kernel.dispatch(&aabbs);
329        assert_eq!(pairs.len(), 1);
330    }
331    #[test]
332    fn test_broadphase_bvh_matches_brute() {
333        let kernel = BroadphaseGpuKernel::new(0.0);
334        let aabbs = vec![
335            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
336            AabbGpu::new([0.5, 0.5, 0.5], [1.5, 1.5, 1.5]),
337            AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]),
338        ];
339        let brute = kernel.dispatch(&aabbs);
340        let bvh = kernel.dispatch_bvh(&aabbs);
341        assert_eq!(brute.len(), bvh.len());
342    }
343    #[test]
344    fn test_narrowphase_no_contact() {
345        let kernel = NarrowphaseGpuKernel::new();
346        let a = unit_box();
347        let b = AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]);
348        let result = kernel.test_aabb_pair(0, &a, 1, &b);
349        assert!(result.is_none());
350    }
351    #[test]
352    fn test_narrowphase_contact() {
353        let kernel = NarrowphaseGpuKernel::new();
354        let a = unit_box();
355        let b = AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]);
356        let result = kernel.test_aabb_pair(0, &a, 1, &b);
357        assert!(result.is_some());
358        let c = result.unwrap();
359        assert!(c.depth > 0.0);
360    }
361    #[test]
362    fn test_narrowphase_dispatch_pairs() {
363        let kernel = NarrowphaseGpuKernel::new();
364        let aabbs = vec![
365            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
366            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
367        ];
368        let pairs = vec![CollisionPair::new(0, 1)];
369        let contacts = kernel.dispatch(&pairs, &aabbs);
370        assert_eq!(contacts.len(), 1);
371    }
372    #[test]
373    fn test_gjk_intersect_overlapping() {
374        let kernel = NarrowphaseGpuKernel::new();
375        let a = unit_box();
376        let b = AabbGpu::new([0.5, 0.5, 0.5], [1.5, 1.5, 1.5]);
377        let (intersect, _dist) = kernel.gjk_intersect(&a, &b);
378        assert!(intersect);
379    }
380    #[test]
381    fn test_gjk_separated() {
382        let kernel = NarrowphaseGpuKernel::new();
383        let a = unit_box();
384        let b = AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]);
385        let (intersect, dist) = kernel.gjk_intersect(&a, &b);
386        assert!(!intersect);
387        assert!(dist > 0.0);
388    }
389    #[test]
390    fn test_manifold_add_points() {
391        let mut m = PersistentManifoldGpu::new(0, 1);
392        let c = ContactResult {
393            prim_a: 0,
394            prim_b: 1,
395            contact_point: [0.5, 0.0, 0.5],
396            normal: [0.0, 1.0, 0.0],
397            depth: 0.1,
398        };
399        m.add_contact(ManifoldPoint::from_contact(&c));
400        assert_eq!(m.num_points, 1);
401    }
402    #[test]
403    fn test_manifold_max_four_points() {
404        let mut m = PersistentManifoldGpu::new(0, 1);
405        for i in 0..6 {
406            let c = ContactResult {
407                prim_a: 0,
408                prim_b: 1,
409                contact_point: [i as f32, 0.0, 0.0],
410                normal: [0.0, 1.0, 0.0],
411                depth: 0.1 * (i as f32 + 1.0),
412            };
413            m.add_contact(ManifoldPoint::from_contact(&c));
414        }
415        assert!(m.num_points <= 4);
416    }
417    #[test]
418    fn test_manifold_remove_stale() {
419        let mut m = PersistentManifoldGpu::new(0, 1);
420        let c = ContactResult {
421            prim_a: 0,
422            prim_b: 1,
423            contact_point: [0.0; 3],
424            normal: [0.0, 1.0, 0.0],
425            depth: 0.01,
426        };
427        m.add_contact(ManifoldPoint::from_contact(&c));
428        m.remove_stale(0.05);
429        assert_eq!(m.num_points, 0);
430    }
431    #[test]
432    fn test_manifold_reset_warm_start() {
433        let mut m = PersistentManifoldGpu::new(0, 1);
434        let c = ContactResult {
435            prim_a: 0,
436            prim_b: 1,
437            contact_point: [0.0; 3],
438            normal: [0.0, 1.0, 0.0],
439            depth: 0.1,
440        };
441        let mut pt = ManifoldPoint::from_contact(&c);
442        pt.warm_impulse_normal = 5.0;
443        m.add_contact(pt);
444        m.reset_warm_start();
445        for pt in m.active_points() {
446            assert!((pt.warm_impulse_normal).abs() < 1e-6);
447        }
448    }
449    #[test]
450    fn test_pipeline_dispatch_no_contacts() {
451        let mut pipeline = CollisionGpuPipeline::new(0.0);
452        let aabbs = vec![
453            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
454            AabbGpu::new([10.0, 0.0, 0.0], [11.0, 1.0, 1.0]),
455        ];
456        pipeline.dispatch(&aabbs);
457        assert!(pipeline.contacts.is_empty());
458    }
459    #[test]
460    fn test_pipeline_dispatch_with_contact() {
461        let mut pipeline = CollisionGpuPipeline::new(0.0);
462        let aabbs = vec![
463            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
464            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
465        ];
466        pipeline.dispatch(&aabbs);
467        assert!(!pipeline.contacts.is_empty());
468        assert!(pipeline.total_contact_points() > 0);
469    }
470    #[test]
471    fn test_pipeline_reset() {
472        let mut pipeline = CollisionGpuPipeline::new(0.0);
473        let aabbs = vec![
474            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
475            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
476        ];
477        pipeline.dispatch(&aabbs);
478        pipeline.reset();
479        assert!(pipeline.contacts.is_empty());
480        assert!(pipeline.manifolds.is_empty());
481    }
482    #[test]
483    fn test_pipeline_bvh_dispatch() {
484        let mut pipeline = CollisionGpuPipeline::new(0.0);
485        let aabbs = vec![
486            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
487            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
488            AabbGpu::new([10.0, 0.0, 0.0], [11.0, 1.0, 1.0]),
489        ];
490        pipeline.dispatch_bvh(&aabbs);
491        assert!(!pipeline.contacts.is_empty());
492    }
493    #[test]
494    fn test_pipeline_persistent_manifolds_accumulate() {
495        let mut pipeline = CollisionGpuPipeline::new(0.0);
496        let aabbs = vec![
497            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
498            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
499        ];
500        pipeline.dispatch(&aabbs);
501        let first_count = pipeline.manifolds.len();
502        pipeline.dispatch(&aabbs);
503        assert!(pipeline.manifolds.len() >= first_count);
504    }
505    #[test]
506    fn test_collision_pair_canonical() {
507        let p1 = CollisionPair::new(3, 1);
508        let p2 = CollisionPair::new(1, 3);
509        assert_eq!(p1, p2);
510        assert_eq!(p1.a, 1);
511        assert_eq!(p1.b, 3);
512    }
513    #[test]
514    fn test_stats_default_zero() {
515        let s = CollisionKernelStats::new();
516        assert_eq!(s.broadphase_pairs_tested, 0);
517        assert_eq!(s.narrowphase_contacts, 0);
518    }
519    #[test]
520    fn test_stats_broadphase_hit_rate() {
521        let mut s = CollisionKernelStats::new();
522        s.broadphase_pairs_tested = 10;
523        s.broadphase_hits = 3;
524        let rate = s.broadphase_hit_rate();
525        assert!((rate - 0.3_f64).abs() < 1.0e-10_f64);
526    }
527    #[test]
528    fn test_stats_broadphase_hit_rate_zero_tested() {
529        let s = CollisionKernelStats::new();
530        assert!((s.broadphase_hit_rate()).abs() < 1.0e-10_f64);
531    }
532    #[test]
533    fn test_stats_narrowphase_contact_rate() {
534        let mut s = CollisionKernelStats::new();
535        s.narrowphase_queries = 8;
536        s.narrowphase_contacts = 4;
537        assert!((s.narrowphase_contact_rate() - 0.5_f64).abs() < 1.0e-10_f64);
538    }
539    #[test]
540    fn test_stats_bandwidth() {
541        let mut s = CollisionKernelStats::new();
542        s.bytes_transferred = 1_000_000_000;
543        s.elapsed_secs = 1.0_f64;
544        assert!((s.bandwidth_gb_s() - 1.0_f64).abs() < 1.0e-9_f64);
545    }
546    #[test]
547    fn test_stats_bandwidth_zero_time() {
548        let s = CollisionKernelStats::new();
549        assert!((s.bandwidth_gb_s()).abs() < 1.0e-10_f64);
550    }
551    #[test]
552    fn test_stats_pair_throughput() {
553        let mut s = CollisionKernelStats::new();
554        s.broadphase_pairs_tested = 1000;
555        s.elapsed_secs = 0.001_f64;
556        assert!((s.pair_throughput() - 1_000_000.0_f64).abs() < 1.0_f64);
557    }
558    #[test]
559    fn test_stats_accumulate() {
560        let mut a = CollisionKernelStats::new();
561        a.broadphase_pairs_tested = 5;
562        let mut b = CollisionKernelStats::new();
563        b.broadphase_pairs_tested = 3;
564        a.accumulate(&b);
565        assert_eq!(a.broadphase_pairs_tested, 8);
566    }
567    #[test]
568    fn test_gpu_aabb_tree_empty() {
569        let mut tree = GpuAabbTree::new();
570        tree.build(&[]);
571        assert_eq!(tree.leaf_count(), 0);
572        assert_eq!(tree.depth(), 0);
573    }
574    #[test]
575    fn test_gpu_aabb_tree_single() {
576        let mut tree = GpuAabbTree::new();
577        tree.build(&[unit_box()]);
578        assert_eq!(tree.leaf_count(), 1);
579        assert_eq!(tree.num_primitives, 1);
580    }
581    #[test]
582    fn test_gpu_aabb_tree_two_nodes() {
583        let aabbs = vec![
584            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
585            AabbGpu::new([5.0, 0.0, 0.0], [6.0, 1.0, 1.0]),
586        ];
587        let mut tree = GpuAabbTree::new();
588        tree.build(&aabbs);
589        assert_eq!(tree.leaf_count(), 2);
590        assert_eq!(tree.nodes.len(), 3);
591    }
592    #[test]
593    fn test_gpu_aabb_tree_query_hit() {
594        let aabbs = vec![
595            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
596            AabbGpu::new([5.0, 0.0, 0.0], [6.0, 1.0, 1.0]),
597        ];
598        let mut tree = GpuAabbTree::new();
599        tree.build(&aabbs);
600        let query = AabbGpu::new([0.2, 0.2, 0.2], [0.8, 0.8, 0.8]);
601        let hits = tree.query(&query);
602        assert!(!hits.is_empty());
603    }
604    #[test]
605    fn test_gpu_aabb_tree_query_miss() {
606        let aabbs = vec![AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0])];
607        let mut tree = GpuAabbTree::new();
608        tree.build(&aabbs);
609        let query = AabbGpu::new([10.0, 10.0, 10.0], [11.0, 11.0, 11.0]);
610        let hits = tree.query(&query);
611        assert!(hits.is_empty());
612    }
613    #[test]
614    fn test_gpu_aabb_tree_sah_cost_nonneg() {
615        let aabbs = vec![
616            unit_box(),
617            AabbGpu::new([2.0, 0.0, 0.0], [3.0, 1.0, 1.0]),
618            AabbGpu::new([4.0, 0.0, 0.0], [5.0, 1.0, 1.0]),
619        ];
620        let mut tree = GpuAabbTree::new();
621        tree.build(&aabbs);
622        assert!(tree.sah_cost() >= 0.0_f32);
623    }
624    #[test]
625    fn test_gpu_aabb_tree_depth_single() {
626        let mut tree = GpuAabbTree::new();
627        tree.build(&[unit_box()]);
628        assert_eq!(tree.depth(), 1);
629    }
630    #[test]
631    fn test_gpu_aabb_tree_morton_codes_computed() {
632        let aabbs = vec![unit_box(), AabbGpu::new([2.0, 0.0, 0.0], [3.0, 1.0, 1.0])];
633        let mut tree = GpuAabbTree::new();
634        tree.build(&aabbs);
635        assert_eq!(tree.morton_codes.len(), 2);
636    }
637    #[test]
638    fn test_sap_broadphase_no_overlap() {
639        let mut bp = GpuBroadphase::new(0, 0.0_f32);
640        let aabbs = vec![
641            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
642            AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]),
643        ];
644        let pairs = bp.dispatch(&aabbs);
645        assert!(pairs.is_empty());
646    }
647    #[test]
648    fn test_sap_broadphase_overlap() {
649        let mut bp = GpuBroadphase::new(0, 0.0_f32);
650        let aabbs = vec![
651            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
652            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
653        ];
654        let pairs = bp.dispatch(&aabbs);
655        assert_eq!(pairs.len(), 1);
656    }
657    #[test]
658    fn test_sap_broadphase_margin() {
659        let mut bp = GpuBroadphase::new(0, 1.0_f32);
660        let aabbs = vec![
661            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
662            AabbGpu::new([1.5, 0.0, 0.0], [2.5, 1.0, 1.0]),
663        ];
664        let pairs = bp.dispatch(&aabbs);
665        assert_eq!(pairs.len(), 1);
666    }
667    #[test]
668    fn test_sap_broadphase_stats_updated() {
669        let mut bp = GpuBroadphase::new(0, 0.0_f32);
670        let aabbs = vec![
671            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
672            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
673            AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]),
674        ];
675        bp.dispatch(&aabbs);
676        assert!(bp.stats.broadphase_pairs_tested > 0);
677        assert!(bp.stats.bytes_transferred > 0);
678    }
679    #[test]
680    fn test_sap_choose_best_axis_x() {
681        let aabbs = vec![
682            AabbGpu::new([0.0, 0.0, 0.0], [100.0, 1.0, 1.0]),
683            AabbGpu::new([200.0, 0.0, 0.0], [300.0, 1.0, 1.0]),
684        ];
685        let axis = GpuBroadphase::choose_best_axis(&aabbs);
686        assert_eq!(axis, 0);
687    }
688    #[test]
689    fn test_sap_choose_best_axis_empty() {
690        let axis = GpuBroadphase::choose_best_axis(&[]);
691        assert_eq!(axis, 0);
692    }
693    #[test]
694    fn test_gjk_distance_intersecting() {
695        let np = GpuNarrowphase::new(32);
696        let a = unit_box();
697        let b = AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]);
698        let res = np.gjk_distance(&a, &b);
699        assert!(res.intersecting);
700        assert!((res.distance).abs() < 1.0e-5_f32);
701    }
702    #[test]
703    fn test_gjk_distance_separated() {
704        let np = GpuNarrowphase::new(32);
705        let a = unit_box();
706        let b = AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]);
707        let res = np.gjk_distance(&a, &b);
708        assert!(!res.intersecting);
709        assert!(res.distance > 0.0_f32);
710    }
711    #[test]
712    fn test_gjk_axis_is_unit() {
713        let np = GpuNarrowphase::new(32);
714        let a = unit_box();
715        let b = AabbGpu::new([5.0, 0.0, 0.0], [6.0, 1.0, 1.0]);
716        let res = np.gjk_distance(&a, &b);
717        let len = len3f(res.axis);
718        assert!((len - 1.0_f32).abs() < 1.0e-5_f32);
719    }
720    #[test]
721    fn test_sat_depth_overlap() {
722        let np = GpuNarrowphase::new(16);
723        let a = unit_box();
724        let b = AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]);
725        let result = np.sat_depth(&a, &b);
726        assert!(result.is_some());
727        let (depth, _axis) = result.unwrap();
728        assert!(depth > 0.0_f32);
729    }
730    #[test]
731    fn test_sat_depth_no_overlap() {
732        let np = GpuNarrowphase::new(16);
733        let a = unit_box();
734        let b = AabbGpu::new([5.0, 0.0, 0.0], [6.0, 1.0, 1.0]);
735        assert!(np.sat_depth(&a, &b).is_none());
736    }
737    #[test]
738    fn test_gpu_narrowphase_dispatch() {
739        let mut np = GpuNarrowphase::new(32);
740        let aabbs = vec![
741            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
742            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
743        ];
744        let pairs = vec![CollisionPair::new(0, 1)];
745        let contacts = np.dispatch(&pairs, &aabbs);
746        assert_eq!(contacts.len(), 1);
747        assert_eq!(np.stats.narrowphase_contacts, 1);
748    }
749    #[test]
750    fn test_gpu_narrowphase_dispatch_no_contact() {
751        let mut np = GpuNarrowphase::new(32);
752        let aabbs = vec![
753            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
754            AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]),
755        ];
756        let pairs = vec![CollisionPair::new(0, 1)];
757        let contacts = np.dispatch(&pairs, &aabbs);
758        assert!(contacts.is_empty());
759    }
760    #[test]
761    fn test_contact_cache_empty() {
762        let cache = GpuContactCache::new(3);
763        assert!(cache.is_empty());
764        assert_eq!(cache.len(), 0);
765    }
766    #[test]
767    fn test_contact_cache_insert_and_find() {
768        let mut cache = GpuContactCache::new(3);
769        let pair = CollisionPair::new(0, 1);
770        let contact = ContactResult {
771            prim_a: 0,
772            prim_b: 1,
773            contact_point: [0.5_f32, 0.0_f32, 0.5_f32],
774            normal: [0.0_f32, 1.0_f32, 0.0_f32],
775            depth: 0.1_f32,
776        };
777        cache.insert(&pair, &contact);
778        assert_eq!(cache.len(), 1);
779        assert!(cache.find(0, 1).is_some());
780    }
781    #[test]
782    fn test_contact_cache_canonical_key() {
783        let mut cache = GpuContactCache::new(3);
784        let pair = CollisionPair::new(1, 0);
785        let contact = ContactResult {
786            prim_a: 1,
787            prim_b: 0,
788            contact_point: [0.0_f32; 3],
789            normal: [0.0_f32, 1.0_f32, 0.0_f32],
790            depth: 0.1_f32,
791        };
792        cache.insert(&pair, &contact);
793        assert!(cache.find(0, 1).is_some());
794        assert!(cache.find(1, 0).is_some());
795    }
796    #[test]
797    fn test_contact_cache_advance_frame_evicts() {
798        let mut cache = GpuContactCache::new(2);
799        let pair = CollisionPair::new(0, 1);
800        let contact = ContactResult {
801            prim_a: 0,
802            prim_b: 1,
803            contact_point: [0.0_f32; 3],
804            normal: [1.0_f32, 0.0_f32, 0.0_f32],
805            depth: 0.1_f32,
806        };
807        cache.insert(&pair, &contact);
808        cache.advance_frame();
809        cache.advance_frame();
810        cache.advance_frame();
811        assert!(cache.is_empty());
812    }
813    #[test]
814    fn test_contact_cache_clear() {
815        let mut cache = GpuContactCache::new(3);
816        let pair = CollisionPair::new(0, 1);
817        let contact = ContactResult {
818            prim_a: 0,
819            prim_b: 1,
820            contact_point: [0.0_f32; 3],
821            normal: [1.0_f32, 0.0_f32, 0.0_f32],
822            depth: 0.1_f32,
823        };
824        cache.insert(&pair, &contact);
825        cache.clear();
826        assert!(cache.is_empty());
827    }
828    #[test]
829    fn test_contact_cache_warmstart() {
830        let mut cache = GpuContactCache::new(5);
831        let pair = CollisionPair::new(0, 1);
832        let contact = ContactResult {
833            prim_a: 0,
834            prim_b: 1,
835            contact_point: [0.0_f32; 3],
836            normal: [1.0_f32, 0.0_f32, 0.0_f32],
837            depth: 0.1_f32,
838        };
839        cache.insert(&pair, &contact);
840        if let Some(entry) = cache.find_mut(0, 1) {
841            entry.update(5.0_f32, 1.0_f32, 2.0_f32);
842        }
843        let raw_contact = ContactResult {
844            prim_a: 0,
845            prim_b: 1,
846            contact_point: [0.0_f32; 3],
847            normal: [1.0_f32, 0.0_f32, 0.0_f32],
848            depth: 0.1_f32,
849        };
850        let mut pt = ManifoldPoint::from_contact(&raw_contact);
851        if let Some(entry) = cache.find(0, 1) {
852            entry.apply_warm_start(&mut pt);
853        }
854        assert!((pt.warm_impulse_normal - 5.0_f32).abs() < 1.0e-6_f32);
855    }
856    #[test]
857    fn test_gpu_pipeline_no_contacts() {
858        let mut pipeline = GpuCollisionPipeline::new(0.0_f32, 4);
859        let aabbs = vec![
860            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
861            AabbGpu::new([10.0, 10.0, 10.0], [11.0, 11.0, 11.0]),
862        ];
863        pipeline.step(&aabbs);
864        assert!(pipeline.contacts.is_empty());
865    }
866    #[test]
867    fn test_gpu_pipeline_contact_detected() {
868        let mut pipeline = GpuCollisionPipeline::new(0.0_f32, 4);
869        let aabbs = vec![
870            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
871            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
872        ];
873        pipeline.step(&aabbs);
874        assert!(!pipeline.contacts.is_empty());
875    }
876    #[test]
877    fn test_gpu_pipeline_manifold_created() {
878        let mut pipeline = GpuCollisionPipeline::new(0.0_f32, 4);
879        let aabbs = vec![
880            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
881            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
882        ];
883        pipeline.step(&aabbs);
884        assert!(!pipeline.manifolds.is_empty());
885    }
886    #[test]
887    fn test_gpu_pipeline_cache_populated() {
888        let mut pipeline = GpuCollisionPipeline::new(0.0_f32, 4);
889        let aabbs = vec![
890            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
891            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
892        ];
893        pipeline.step(&aabbs);
894        assert!(!pipeline.contact_cache.is_empty());
895    }
896    #[test]
897    fn test_gpu_pipeline_reset() {
898        let mut pipeline = GpuCollisionPipeline::new(0.0_f32, 4);
899        let aabbs = vec![
900            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
901            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
902        ];
903        pipeline.step(&aabbs);
904        pipeline.reset();
905        assert!(pipeline.contacts.is_empty());
906        assert!(pipeline.manifolds.is_empty());
907        assert!(pipeline.contact_cache.is_empty());
908    }
909    #[test]
910    fn test_gpu_pipeline_cumulative_stats() {
911        let mut pipeline = GpuCollisionPipeline::new(0.0_f32, 4);
912        let aabbs = vec![
913            unit_box(),
914            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
915            AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]),
916        ];
917        pipeline.step(&aabbs);
918        pipeline.step(&aabbs);
919        assert!(pipeline.cumulative_stats.bytes_transferred > 0);
920    }
921    #[test]
922    fn test_gpu_pipeline_total_contact_points() {
923        let mut pipeline = GpuCollisionPipeline::new(0.0_f32, 4);
924        let aabbs = vec![
925            AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]),
926            AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]),
927        ];
928        pipeline.step(&aabbs);
929        assert!(pipeline.total_contact_points() > 0);
930    }
931    #[test]
932    fn test_gpu_pipeline_aabb_tree_rebuilt() {
933        let mut pipeline = GpuCollisionPipeline::new(0.0_f32, 4);
934        let aabbs = vec![unit_box()];
935        pipeline.step(&aabbs);
936        assert_eq!(pipeline.aabb_tree.num_primitives, 1);
937    }
938    #[test]
939    fn test_gpu_pipeline_frame_stats_nonzero() {
940        let mut pipeline = GpuCollisionPipeline::new(0.0_f32, 4);
941        let aabbs = vec![unit_box(), AabbGpu::new([0.5, 0.0, 0.0], [1.5, 1.0, 1.0])];
942        pipeline.step(&aabbs);
943        let stats = pipeline.frame_stats();
944        assert!(stats.narrowphase_queries > 0);
945    }
946}