1#![allow(dead_code)]
14
15#[derive(Debug, Clone)]
22pub struct GpuBroadphaseGrid {
23 pub cell_size: f64,
25 pub nx: usize,
27 pub ny: usize,
29 pub nz: usize,
31 pub cells: Vec<Vec<usize>>,
33}
34
35impl GpuBroadphaseGrid {
36 pub fn new(cell_size: f64, nx: usize, ny: usize, nz: usize) -> Self {
42 let total = nx * ny * nz;
43 Self {
44 cell_size,
45 nx,
46 ny,
47 nz,
48 cells: vec![Vec::new(); total],
49 }
50 }
51
52 pub fn insert(&mut self, particle_id: usize, pos: [f64; 3]) {
56 let (ix, iy, iz) = self.cell_index(pos);
57 let flat = iz * self.nx * self.ny + iy * self.nx + ix;
58 if flat < self.cells.len() {
59 self.cells[flat].push(particle_id);
60 }
61 }
62
63 pub fn cell_index(&self, pos: [f64; 3]) -> (usize, usize, usize) {
67 let clamp = |v: f64, n: usize| {
68 let i = (v / self.cell_size).floor() as isize;
69 i.max(0).min(n as isize - 1) as usize
70 };
71 let ix = clamp(pos[0], self.nx.max(1));
72 let iy = clamp(pos[1], self.ny.max(1));
73 let iz = clamp(pos[2], self.nz.max(1));
74 (ix, iy, iz)
75 }
76
77 pub fn query_neighbors(&self, pos: [f64; 3]) -> Vec<usize> {
79 let (cx, cy, cz) = self.cell_index(pos);
80 let mut result = Vec::new();
81 for dz in -1isize..=1 {
82 for dy in -1isize..=1 {
83 for dx in -1isize..=1 {
84 let ix = cx as isize + dx;
85 let iy = cy as isize + dy;
86 let iz = cz as isize + dz;
87 if ix < 0
88 || iy < 0
89 || iz < 0
90 || ix >= self.nx as isize
91 || iy >= self.ny as isize
92 || iz >= self.nz as isize
93 {
94 continue;
95 }
96 let flat =
97 iz as usize * self.nx * self.ny + iy as usize * self.nx + ix as usize;
98 result.extend_from_slice(&self.cells[flat]);
99 }
100 }
101 }
102 result
103 }
104
105 pub fn cell_count(&self) -> usize {
107 self.nx * self.ny * self.nz
108 }
109
110 pub fn clear(&mut self) {
112 for cell in &mut self.cells {
113 cell.clear();
114 }
115 }
116}
117
118pub fn gpu_aabb_overlap(
124 a_min: [f32; 3],
125 a_max: [f32; 3],
126 b_min: [f32; 3],
127 b_max: [f32; 3],
128) -> bool {
129 a_min[0] <= b_max[0]
130 && a_max[0] >= b_min[0]
131 && a_min[1] <= b_max[1]
132 && a_max[1] >= b_min[1]
133 && a_min[2] <= b_max[2]
134 && a_max[2] >= b_min[2]
135}
136
137pub fn gpu_sphere_sphere_overlap(c1: [f32; 3], r1: f32, c2: [f32; 3], r2: f32) -> bool {
141 let dx = c1[0] - c2[0];
142 let dy = c1[1] - c2[1];
143 let dz = c1[2] - c2[2];
144 let dist2 = dx * dx + dy * dy + dz * dz;
145 let rsum = r1 + r2;
146 dist2 <= rsum * rsum
147}
148
149pub fn gpu_point_in_aabb(p: [f32; 3], min: [f32; 3], max: [f32; 3]) -> bool {
151 p[0] >= min[0]
152 && p[0] <= max[0]
153 && p[1] >= min[1]
154 && p[1] <= max[1]
155 && p[2] >= min[2]
156 && p[2] <= max[2]
157}
158
159pub fn gpu_ray_aabb_intersect(
166 ray_origin: [f32; 3],
167 ray_dir: [f32; 3],
168 min: [f32; 3],
169 max: [f32; 3],
170) -> Option<f32> {
171 let mut t_min = f32::NEG_INFINITY;
172 let mut t_max = f32::INFINITY;
173 for i in 0..3 {
174 let inv_d = if ray_dir[i].abs() > 1e-12 {
175 1.0 / ray_dir[i]
176 } else {
177 if ray_origin[i] < min[i] || ray_origin[i] > max[i] {
179 return None;
180 }
181 continue;
182 };
183 let t1 = (min[i] - ray_origin[i]) * inv_d;
184 let t2 = (max[i] - ray_origin[i]) * inv_d;
185 let (ta, tb) = if t1 < t2 { (t1, t2) } else { (t2, t1) };
186 t_min = t_min.max(ta);
187 t_max = t_max.min(tb);
188 if t_min > t_max {
189 return None;
190 }
191 }
192 if t_max < 0.0 {
193 return None;
194 }
195 Some(if t_min >= 0.0 { t_min } else { t_max })
196}
197
198pub fn gpu_ray_sphere_intersect(
202 ray_origin: [f32; 3],
203 ray_dir: [f32; 3],
204 center: [f32; 3],
205 radius: f32,
206) -> Option<f32> {
207 let ocx = ray_origin[0] - center[0];
208 let ocy = ray_origin[1] - center[1];
209 let ocz = ray_origin[2] - center[2];
210 let a = ray_dir[0] * ray_dir[0] + ray_dir[1] * ray_dir[1] + ray_dir[2] * ray_dir[2];
211 let b = 2.0 * (ray_dir[0] * ocx + ray_dir[1] * ocy + ray_dir[2] * ocz);
212 let c = ocx * ocx + ocy * ocy + ocz * ocz - radius * radius;
213 let discriminant = b * b - 4.0 * a * c;
214 if discriminant < 0.0 {
215 return None;
216 }
217 let sqrt_d = discriminant.sqrt();
218 let t1 = (-b - sqrt_d) / (2.0 * a);
219 let t2 = (-b + sqrt_d) / (2.0 * a);
220 if t1 >= 0.0 {
221 Some(t1)
222 } else if t2 >= 0.0 {
223 Some(t2)
224 } else {
225 None
226 }
227}
228
229#[derive(Debug, Clone)]
236pub struct GpuContactManifold {
237 pub body_a: usize,
239 pub body_b: usize,
241 pub contact_points: Vec<[f32; 3]>,
243 pub normals: Vec<[f32; 3]>,
245 pub depths: Vec<f32>,
247}
248
249impl GpuContactManifold {
250 pub fn new(body_a: usize, body_b: usize) -> Self {
252 Self {
253 body_a,
254 body_b,
255 contact_points: Vec::new(),
256 normals: Vec::new(),
257 depths: Vec::new(),
258 }
259 }
260
261 pub fn add_contact(&mut self, point: [f32; 3], normal: [f32; 3], depth: f32) {
267 self.contact_points.push(point);
268 self.normals.push(normal);
269 self.depths.push(depth);
270 }
271
272 pub fn contact_count(&self) -> usize {
274 self.contact_points.len()
275 }
276
277 pub fn max_penetration(&self) -> f32 {
279 self.depths.iter().copied().fold(0.0_f32, f32::max)
280 }
281}
282
283pub fn gpu_gjk_distance(vertices_a: &[[f32; 3]], vertices_b: &[[f32; 3]]) -> f32 {
290 if vertices_a.is_empty() || vertices_b.is_empty() {
291 return 0.0;
292 }
293 let mut min_dist2 = f32::MAX;
294 for &va in vertices_a {
295 for &vb in vertices_b {
296 let dx = va[0] - vb[0];
297 let dy = va[1] - vb[1];
298 let dz = va[2] - vb[2];
299 let d2 = dx * dx + dy * dy + dz * dz;
300 if d2 < min_dist2 {
301 min_dist2 = d2;
302 }
303 }
304 }
305 min_dist2.sqrt()
306}
307
308#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
317 fn broadphase_cell_count_basic() {
318 let g = GpuBroadphaseGrid::new(1.0, 4, 4, 4);
319 assert_eq!(g.cell_count(), 64);
320 }
321
322 #[test]
323 fn broadphase_cell_count_non_uniform() {
324 let g = GpuBroadphaseGrid::new(0.5, 2, 3, 5);
325 assert_eq!(g.cell_count(), 30);
326 }
327
328 #[test]
329 fn broadphase_insert_and_query() {
330 let mut g = GpuBroadphaseGrid::new(1.0, 4, 4, 4);
331 g.insert(0, [0.5, 0.5, 0.5]);
332 let neighbors = g.query_neighbors([0.5, 0.5, 0.5]);
333 assert!(!neighbors.is_empty());
334 assert!(neighbors.contains(&0));
335 }
336
337 #[test]
338 fn broadphase_query_nearby_cell() {
339 let mut g = GpuBroadphaseGrid::new(1.0, 4, 4, 4);
340 g.insert(42, [1.5, 1.5, 1.5]);
341 let neighbors = g.query_neighbors([0.5, 0.5, 0.5]);
343 assert!(neighbors.contains(&42));
344 }
345
346 #[test]
347 fn broadphase_clear_removes_particles() {
348 let mut g = GpuBroadphaseGrid::new(1.0, 4, 4, 4);
349 g.insert(0, [0.5, 0.5, 0.5]);
350 g.clear();
351 let neighbors = g.query_neighbors([0.5, 0.5, 0.5]);
352 assert!(neighbors.is_empty());
353 }
354
355 #[test]
356 fn broadphase_multiple_particles_same_cell() {
357 let mut g = GpuBroadphaseGrid::new(1.0, 4, 4, 4);
358 g.insert(1, [0.1, 0.1, 0.1]);
359 g.insert(2, [0.9, 0.9, 0.9]);
360 let neighbors = g.query_neighbors([0.5, 0.5, 0.5]);
361 assert!(neighbors.contains(&1));
362 assert!(neighbors.contains(&2));
363 }
364
365 #[test]
366 fn broadphase_cell_index_origin() {
367 let g = GpuBroadphaseGrid::new(1.0, 4, 4, 4);
368 let (ix, iy, iz) = g.cell_index([0.0, 0.0, 0.0]);
369 assert_eq!((ix, iy, iz), (0, 0, 0));
370 }
371
372 #[test]
373 fn broadphase_cell_index_clamped() {
374 let g = GpuBroadphaseGrid::new(1.0, 4, 4, 4);
375 let (ix, _iy, _iz) = g.cell_index([100.0, 0.0, 0.0]);
377 assert_eq!(ix, 3);
378 }
379
380 #[test]
381 fn broadphase_query_empty_grid() {
382 let g = GpuBroadphaseGrid::new(1.0, 4, 4, 4);
383 let neighbors = g.query_neighbors([0.5, 0.5, 0.5]);
384 assert!(neighbors.is_empty());
385 }
386
387 #[test]
388 fn broadphase_insert_many() {
389 let mut g = GpuBroadphaseGrid::new(1.0, 8, 8, 8);
390 for i in 0..20 {
391 g.insert(i, [(i % 8) as f64 * 0.9, 0.5, 0.5]);
392 }
393 assert_eq!(g.cell_count(), 512);
395 }
396
397 #[test]
400 fn aabb_overlap_basic() {
401 let a_min = [0.0f32; 3];
402 let a_max = [1.0f32; 3];
403 let b_min = [0.5f32; 3];
404 let b_max = [1.5f32; 3];
405 assert!(gpu_aabb_overlap(a_min, a_max, b_min, b_max));
406 }
407
408 #[test]
409 fn aabb_overlap_no_overlap() {
410 let a_min = [0.0f32; 3];
411 let a_max = [1.0f32; 3];
412 let b_min = [2.0f32; 3];
413 let b_max = [3.0f32; 3];
414 assert!(!gpu_aabb_overlap(a_min, a_max, b_min, b_max));
415 }
416
417 #[test]
418 fn aabb_overlap_touching_face() {
419 let a_min = [0.0f32, 0.0, 0.0];
421 let a_max = [1.0f32, 1.0, 1.0];
422 let b_min = [1.0f32, 0.0, 0.0];
423 let b_max = [2.0f32, 1.0, 1.0];
424 assert!(gpu_aabb_overlap(a_min, a_max, b_min, b_max));
425 }
426
427 #[test]
428 fn aabb_overlap_separated_in_y() {
429 let a_min = [0.0f32, 0.0, 0.0];
430 let a_max = [1.0f32, 1.0, 1.0];
431 let b_min = [0.0f32, 2.0, 0.0];
432 let b_max = [1.0f32, 3.0, 1.0];
433 assert!(!gpu_aabb_overlap(a_min, a_max, b_min, b_max));
434 }
435
436 #[test]
437 fn aabb_overlap_contained() {
438 let outer_min = [0.0f32; 3];
439 let outer_max = [10.0f32; 3];
440 let inner_min = [2.0f32; 3];
441 let inner_max = [3.0f32; 3];
442 assert!(gpu_aabb_overlap(outer_min, outer_max, inner_min, inner_max));
443 }
444
445 #[test]
448 fn sphere_overlap_clearly_overlapping() {
449 let c1 = [0.0f32; 3];
450 let c2 = [1.0f32, 0.0, 0.0];
451 assert!(gpu_sphere_sphere_overlap(c1, 1.0, c2, 1.0));
452 }
453
454 #[test]
455 fn sphere_overlap_just_touching() {
456 let c1 = [0.0f32; 3];
458 let c2 = [2.0f32, 0.0, 0.0];
459 assert!(gpu_sphere_sphere_overlap(c1, 1.0, c2, 1.0));
460 }
461
462 #[test]
463 fn sphere_overlap_separated() {
464 let c1 = [0.0f32; 3];
465 let c2 = [3.0f32, 0.0, 0.0];
466 assert!(!gpu_sphere_sphere_overlap(c1, 1.0, c2, 1.0));
467 }
468
469 #[test]
470 fn sphere_overlap_same_center() {
471 let c = [0.0f32; 3];
472 assert!(gpu_sphere_sphere_overlap(c, 1.0, c, 1.0));
473 }
474
475 #[test]
478 fn point_in_aabb_inside() {
479 let min = [0.0f32; 3];
480 let max = [1.0f32; 3];
481 assert!(gpu_point_in_aabb([0.5, 0.5, 0.5], min, max));
482 }
483
484 #[test]
485 fn point_in_aabb_outside() {
486 let min = [0.0f32; 3];
487 let max = [1.0f32; 3];
488 assert!(!gpu_point_in_aabb([2.0, 0.5, 0.5], min, max));
489 }
490
491 #[test]
492 fn point_in_aabb_corner() {
493 let min = [0.0f32; 3];
494 let max = [1.0f32; 3];
495 assert!(gpu_point_in_aabb([0.0, 0.0, 0.0], min, max));
496 assert!(gpu_point_in_aabb([1.0, 1.0, 1.0], min, max));
497 }
498
499 #[test]
500 fn point_in_aabb_just_outside() {
501 let min = [0.0f32; 3];
502 let max = [1.0f32; 3];
503 assert!(!gpu_point_in_aabb([1.0001, 0.5, 0.5], min, max));
504 }
505
506 #[test]
509 fn ray_aabb_hit() {
510 let origin = [-2.0f32, 0.5, 0.5];
511 let dir = [1.0f32, 0.0, 0.0];
512 let min = [0.0f32; 3];
513 let max = [1.0f32; 3];
514 let t = gpu_ray_aabb_intersect(origin, dir, min, max);
515 assert!(t.is_some());
516 assert!((t.unwrap() - 2.0).abs() < 1e-5);
517 }
518
519 #[test]
520 fn ray_aabb_miss() {
521 let origin = [-2.0f32, 5.0, 0.5];
522 let dir = [1.0f32, 0.0, 0.0];
523 let min = [0.0f32; 3];
524 let max = [1.0f32; 3];
525 assert!(gpu_ray_aabb_intersect(origin, dir, min, max).is_none());
526 }
527
528 #[test]
529 fn ray_aabb_origin_inside() {
530 let origin = [0.5f32; 3];
531 let dir = [1.0f32, 0.0, 0.0];
532 let min = [0.0f32; 3];
533 let max = [1.0f32; 3];
534 let t = gpu_ray_aabb_intersect(origin, dir, min, max);
536 assert!(t.is_some());
537 assert!(t.unwrap() >= 0.0);
538 }
539
540 #[test]
541 fn ray_aabb_opposite_direction() {
542 let origin = [5.0f32, 0.5, 0.5];
544 let dir = [1.0f32, 0.0, 0.0];
545 let min = [0.0f32; 3];
546 let max = [1.0f32; 3];
547 assert!(gpu_ray_aabb_intersect(origin, dir, min, max).is_none());
548 }
549
550 #[test]
553 fn ray_sphere_hit() {
554 let origin = [-5.0f32, 0.0, 0.0];
555 let dir = [1.0f32, 0.0, 0.0];
556 let center = [0.0f32; 3];
557 let t = gpu_ray_sphere_intersect(origin, dir, center, 1.0);
558 assert!(t.is_some());
559 assert!((t.unwrap() - 4.0).abs() < 1e-4);
561 }
562
563 #[test]
564 fn ray_sphere_miss() {
565 let origin = [0.0f32, 5.0, 0.0];
566 let dir = [1.0f32, 0.0, 0.0];
567 let center = [0.0f32; 3];
568 assert!(gpu_ray_sphere_intersect(origin, dir, center, 1.0).is_none());
569 }
570
571 #[test]
572 fn ray_sphere_origin_inside() {
573 let origin = [0.0f32; 3];
574 let dir = [1.0f32, 0.0, 0.0];
575 let center = [0.0f32; 3];
576 let t = gpu_ray_sphere_intersect(origin, dir, center, 2.0);
577 assert!(t.is_some());
579 assert!(t.unwrap() > 0.0);
580 }
581
582 #[test]
585 fn manifold_new_empty() {
586 let m = GpuContactManifold::new(0, 1);
587 assert_eq!(m.body_a, 0);
588 assert_eq!(m.body_b, 1);
589 assert_eq!(m.contact_count(), 0);
590 }
591
592 #[test]
593 fn manifold_add_contact_increases_count() {
594 let mut m = GpuContactManifold::new(0, 1);
595 m.add_contact([0.0; 3], [0.0, 1.0, 0.0], 0.1);
596 assert_eq!(m.contact_count(), 1);
597 m.add_contact([1.0, 0.0, 0.0], [0.0, 1.0, 0.0], 0.2);
598 assert_eq!(m.contact_count(), 2);
599 }
600
601 #[test]
602 fn manifold_max_penetration_empty() {
603 let m = GpuContactManifold::new(0, 1);
604 assert!((m.max_penetration() - 0.0).abs() < 1e-6);
605 }
606
607 #[test]
608 fn manifold_max_penetration_multiple() {
609 let mut m = GpuContactManifold::new(0, 1);
610 m.add_contact([0.0; 3], [0.0, 1.0, 0.0], 0.1);
611 m.add_contact([0.0; 3], [0.0, 1.0, 0.0], 0.5);
612 m.add_contact([0.0; 3], [0.0, 1.0, 0.0], 0.3);
613 assert!((m.max_penetration() - 0.5).abs() < 1e-6);
614 }
615
616 #[test]
617 fn manifold_contact_points_stored() {
618 let mut m = GpuContactManifold::new(2, 3);
619 let pt = [1.0, 2.0, 3.0];
620 m.add_contact(pt, [0.0, 1.0, 0.0], 0.05);
621 assert!((m.contact_points[0][0] - 1.0).abs() < 1e-6);
622 }
623
624 #[test]
627 fn gjk_distance_touching() {
628 let verts_a: Vec<[f32; 3]> = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]];
629 let verts_b: Vec<[f32; 3]> = vec![[1.0, 0.0, 0.0], [2.0, 0.0, 0.0]];
630 let d = gpu_gjk_distance(&verts_a, &verts_b);
631 assert!(d < 1e-5, "expected ~0, got {d}");
632 }
633
634 #[test]
635 fn gjk_distance_separated() {
636 let verts_a: Vec<[f32; 3]> = vec![[0.0; 3]];
637 let verts_b: Vec<[f32; 3]> = vec![[3.0, 0.0, 0.0]];
638 let d = gpu_gjk_distance(&verts_a, &verts_b);
639 assert!((d - 3.0).abs() < 1e-5);
640 }
641
642 #[test]
643 fn gjk_distance_empty_a() {
644 let d = gpu_gjk_distance(&[], &[[1.0, 0.0, 0.0]]);
645 assert!((d - 0.0).abs() < 1e-6);
646 }
647
648 #[test]
649 fn gjk_distance_empty_b() {
650 let d = gpu_gjk_distance(&[[1.0, 0.0, 0.0]], &[]);
651 assert!((d - 0.0).abs() < 1e-6);
652 }
653
654 #[test]
655 fn gjk_distance_same_point() {
656 let v = [[0.0f32; 3]];
657 let d = gpu_gjk_distance(&v, &v);
658 assert!(d < 1e-6);
659 }
660}