1use super::types::{AabbGpu, SupportPoint};
6
7pub(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}
17pub 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}
25pub(super) fn dot3f(a: [f32; 3], b: [f32; 3]) -> f32 {
27 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
28}
29pub(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}
33pub(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}
37pub(super) fn scale3f(a: [f32; 3], s: f32) -> [f32; 3] {
39 [a[0] * s, a[1] * s, a[2] * s]
40}
41pub(super) fn len_sq3f(a: [f32; 3]) -> f32 {
43 dot3f(a, a)
44}
45pub(super) fn len3f(a: [f32; 3]) -> f32 {
47 len_sq3f(a).sqrt()
48}
49pub(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}
58pub(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}
78pub(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}
85pub(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}
123pub(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}