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