1#![allow(clippy::needless_range_loop)]
2use super::functions::*;
3#[derive(Debug, Default)]
11pub struct BroadphaseGpuKernel {
12 pub margin: f32,
14}
15impl BroadphaseGpuKernel {
16 pub fn new(margin: f32) -> Self {
18 Self { margin }
19 }
20 pub fn dispatch(&self, aabbs: &[AabbGpu]) -> Vec<CollisionPair> {
24 let mut pairs = Vec::new();
25 for i in 0..aabbs.len() {
26 let expanded_i = aabbs[i].expanded(self.margin);
27 for j in (i + 1)..aabbs.len() {
28 if expanded_i.overlaps(&aabbs[j]) {
29 pairs.push(CollisionPair::new(i as u32, j as u32));
30 }
31 }
32 }
33 pairs
34 }
35 pub fn dispatch_bvh(&self, aabbs: &[AabbGpu]) -> Vec<CollisionPair> {
37 let mut builder = GpuBvhBuilder::new();
38 let root = builder.build(aabbs);
39 let mut pairs = Vec::new();
40 for (i, aabb) in aabbs.iter().enumerate() {
41 let query = aabb.expanded(self.margin);
42 let hits = builder.query_overlaps(root, &query);
43 for j in hits {
44 if j as usize > i {
45 pairs.push(CollisionPair::new(i as u32, j));
46 }
47 }
48 }
49 pairs
50 }
51}
52#[derive(Debug, Clone, PartialEq)]
56pub struct AabbGpu {
57 pub min: [f32; 3],
59 pub max: [f32; 3],
61}
62impl AabbGpu {
63 pub fn new(min: [f32; 3], max: [f32; 3]) -> Self {
65 Self { min, max }
66 }
67 pub fn from_point(p: [f32; 3]) -> Self {
69 Self { min: p, max: p }
70 }
71 pub fn merge(&self, other: &AabbGpu) -> AabbGpu {
73 AabbGpu {
74 min: [
75 self.min[0].min(other.min[0]),
76 self.min[1].min(other.min[1]),
77 self.min[2].min(other.min[2]),
78 ],
79 max: [
80 self.max[0].max(other.max[0]),
81 self.max[1].max(other.max[1]),
82 self.max[2].max(other.max[2]),
83 ],
84 }
85 }
86 pub fn overlaps(&self, other: &AabbGpu) -> bool {
88 self.min[0] <= other.max[0]
89 && self.max[0] >= other.min[0]
90 && self.min[1] <= other.max[1]
91 && self.max[1] >= other.min[1]
92 && self.min[2] <= other.max[2]
93 && self.max[2] >= other.min[2]
94 }
95 pub fn contains_point(&self, p: [f32; 3]) -> bool {
97 p[0] >= self.min[0]
98 && p[0] <= self.max[0]
99 && p[1] >= self.min[1]
100 && p[1] <= self.max[1]
101 && p[2] >= self.min[2]
102 && p[2] <= self.max[2]
103 }
104 pub fn surface_area(&self) -> f32 {
106 let dx = (self.max[0] - self.min[0]).max(0.0);
107 let dy = (self.max[1] - self.min[1]).max(0.0);
108 let dz = (self.max[2] - self.min[2]).max(0.0);
109 2.0 * (dx * dy + dy * dz + dz * dx)
110 }
111 pub fn volume(&self) -> f32 {
113 let dx = (self.max[0] - self.min[0]).max(0.0);
114 let dy = (self.max[1] - self.min[1]).max(0.0);
115 let dz = (self.max[2] - self.min[2]).max(0.0);
116 dx * dy * dz
117 }
118 pub fn centre(&self) -> [f32; 3] {
120 [
121 (self.min[0] + self.max[0]) * 0.5,
122 (self.min[1] + self.max[1]) * 0.5,
123 (self.min[2] + self.max[2]) * 0.5,
124 ]
125 }
126 pub fn half_extents(&self) -> [f32; 3] {
128 [
129 (self.max[0] - self.min[0]) * 0.5,
130 (self.max[1] - self.min[1]) * 0.5,
131 (self.max[2] - self.min[2]) * 0.5,
132 ]
133 }
134 pub fn expanded(&self, margin: f32) -> AabbGpu {
136 AabbGpu {
137 min: [
138 self.min[0] - margin,
139 self.min[1] - margin,
140 self.min[2] - margin,
141 ],
142 max: [
143 self.max[0] + margin,
144 self.max[1] + margin,
145 self.max[2] + margin,
146 ],
147 }
148 }
149}
150#[derive(Debug, Clone)]
155pub struct PersistentManifoldGpu {
156 pub body_a: u32,
158 pub body_b: u32,
160 pub points: [Option<ManifoldPoint>; 4],
162 pub num_points: usize,
164}
165impl PersistentManifoldGpu {
166 pub fn new(body_a: u32, body_b: u32) -> Self {
168 Self {
169 body_a,
170 body_b,
171 points: [None, None, None, None],
172 num_points: 0,
173 }
174 }
175 pub fn add_contact(&mut self, new_pt: ManifoldPoint) {
181 if self.num_points < 4 {
182 self.points[self.num_points] = Some(new_pt);
183 self.num_points += 1;
184 return;
185 }
186 let mut shallowest_idx = 0;
187 let mut shallowest_depth = f32::INFINITY;
188 for (i, opt) in self.points.iter().enumerate() {
189 if let Some(p) = opt
190 && p.depth < shallowest_depth
191 {
192 shallowest_depth = p.depth;
193 shallowest_idx = i;
194 }
195 }
196 if new_pt.depth > shallowest_depth {
197 self.points[shallowest_idx] = Some(new_pt);
198 }
199 }
200 pub fn remove_stale(&mut self, threshold: f32) {
202 for slot in self.points.iter_mut() {
203 if let Some(p) = slot
204 && p.depth < threshold
205 {
206 *slot = None;
207 }
208 }
209 self.num_points = self.points.iter().filter(|s| s.is_some()).count();
210 }
211 pub fn reset_warm_start(&mut self) {
213 for slot in self.points.iter_mut().flatten() {
214 slot.warm_impulse_normal = 0.0;
215 slot.warm_impulse_t1 = 0.0;
216 slot.warm_impulse_t2 = 0.0;
217 }
218 }
219 pub fn active_points(&self) -> impl Iterator<Item = &ManifoldPoint> {
221 self.points.iter().flatten()
222 }
223}
224#[derive(Debug, Default)]
229pub struct GpuBvhBuilder {
230 pub(super) nodes: Vec<BvhNodeGpu>,
231}
232impl GpuBvhBuilder {
233 pub fn new() -> Self {
235 Self { nodes: Vec::new() }
236 }
237 pub fn build(&mut self, aabbs: &[AabbGpu]) -> usize {
241 self.nodes.clear();
242 if aabbs.is_empty() {
243 return 0;
244 }
245 let scene_min = [
246 aabbs.iter().map(|a| a.min[0]).fold(f32::INFINITY, f32::min),
247 aabbs.iter().map(|a| a.min[1]).fold(f32::INFINITY, f32::min),
248 aabbs.iter().map(|a| a.min[2]).fold(f32::INFINITY, f32::min),
249 ];
250 let scene_max = [
251 aabbs
252 .iter()
253 .map(|a| a.max[0])
254 .fold(f32::NEG_INFINITY, f32::max),
255 aabbs
256 .iter()
257 .map(|a| a.max[1])
258 .fold(f32::NEG_INFINITY, f32::max),
259 aabbs
260 .iter()
261 .map(|a| a.max[2])
262 .fold(f32::NEG_INFINITY, f32::max),
263 ];
264 let extent = [
265 (scene_max[0] - scene_min[0]).max(1e-6),
266 (scene_max[1] - scene_min[1]).max(1e-6),
267 (scene_max[2] - scene_min[2]).max(1e-6),
268 ];
269 let mut indexed: Vec<(u32, usize)> = aabbs
270 .iter()
271 .enumerate()
272 .map(|(i, a)| {
273 let c = a.centre();
274 let nx = (c[0] - scene_min[0]) / extent[0];
275 let ny = (c[1] - scene_min[1]) / extent[1];
276 let nz = (c[2] - scene_min[2]) / extent[2];
277 (morton_code(nx, ny, nz), i)
278 })
279 .collect();
280 indexed.sort_by_key(|&(code, _)| code);
281 let leaf_base = 0usize;
282 for &(_, prim_idx) in &indexed {
283 self.nodes
284 .push(BvhNodeGpu::leaf(aabbs[prim_idx].clone(), prim_idx as u32));
285 }
286 let mut current_level: Vec<usize> = (leaf_base..leaf_base + indexed.len()).collect();
287 while current_level.len() > 1 {
288 let mut next_level = Vec::new();
289 let mut i = 0;
290 while i < current_level.len() {
291 if i + 1 < current_level.len() {
292 let l = current_level[i];
293 let r = current_level[i + 1];
294 let merged = self.nodes[l].aabb.merge(&self.nodes[r].aabb);
295 let internal = BvhNodeGpu::internal(merged, l as u32, r as u32);
296 let idx = self.nodes.len();
297 self.nodes.push(internal);
298 next_level.push(idx);
299 i += 2;
300 } else {
301 next_level.push(current_level[i]);
302 i += 1;
303 }
304 }
305 current_level = next_level;
306 }
307 current_level[0]
308 }
309 pub fn nodes(&self) -> &[BvhNodeGpu] {
311 &self.nodes
312 }
313 pub fn query_overlaps(&self, root: usize, query: &AabbGpu) -> Vec<u32> {
315 if self.nodes.is_empty() {
316 return Vec::new();
317 }
318 let mut result = Vec::new();
319 let mut stack = vec![root];
320 while let Some(idx) = stack.pop() {
321 let node = &self.nodes[idx];
322 if !node.aabb.overlaps(query) {
323 continue;
324 }
325 if node.is_leaf {
326 result.push(node.primitive_index);
327 } else {
328 stack.push(node.left_child as usize);
329 stack.push(node.right_child as usize);
330 }
331 }
332 result
333 }
334}
335#[derive(Debug, Clone)]
337pub struct ManifoldPoint {
338 pub position: [f32; 3],
340 pub normal: [f32; 3],
342 pub depth: f32,
344 pub warm_impulse_normal: f32,
346 pub warm_impulse_t1: f32,
348 pub warm_impulse_t2: f32,
350}
351impl ManifoldPoint {
352 pub fn from_contact(c: &ContactResult) -> Self {
354 Self {
355 position: c.contact_point,
356 normal: c.normal,
357 depth: c.depth,
358 warm_impulse_normal: 0.0,
359 warm_impulse_t1: 0.0,
360 warm_impulse_t2: 0.0,
361 }
362 }
363}
364#[derive(Debug, Default)]
369pub struct NarrowphaseGpuKernel;
370impl NarrowphaseGpuKernel {
371 pub fn new() -> Self {
373 Self
374 }
375 pub fn test_aabb_pair(
378 &self,
379 prim_a: u32,
380 aabb_a: &AabbGpu,
381 prim_b: u32,
382 aabb_b: &AabbGpu,
383 ) -> Option<ContactResult> {
384 let axes: [[f32; 3]; 3] = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
385 let mut min_depth = f32::INFINITY;
386 let mut min_axis = [1.0f32, 0.0, 0.0];
387 for axis in &axes {
388 let a_min = dot3f(aabb_a.min, *axis);
389 let a_max = dot3f(aabb_a.max, *axis);
390 let b_min = dot3f(aabb_b.min, *axis);
391 let b_max = dot3f(aabb_b.max, *axis);
392 let overlap = a_max.min(b_max) - a_min.max(b_min);
393 if overlap <= 0.0 {
394 return None;
395 }
396 if overlap < min_depth {
397 min_depth = overlap;
398 let ca = dot3f(aabb_a.centre(), *axis);
399 let cb = dot3f(aabb_b.centre(), *axis);
400 let sign = if ca > cb { 1.0 } else { -1.0 };
401 min_axis = scale3f(*axis, sign);
402 }
403 }
404 let ca = aabb_a.centre();
405 let cb = aabb_b.centre();
406 let contact_point = [
407 (ca[0] + cb[0]) * 0.5,
408 (ca[1] + cb[1]) * 0.5,
409 (ca[2] + cb[2]) * 0.5,
410 ];
411 Some(ContactResult {
412 prim_a,
413 prim_b,
414 contact_point,
415 normal: min_axis,
416 depth: min_depth,
417 })
418 }
419 pub fn gjk_intersect(&self, a: &AabbGpu, b: &AabbGpu) -> (bool, f32) {
424 let ca = a.centre();
425 let cb = b.centre();
426 let mut d = sub3f(ca, cb);
427 if len_sq3f(d) < 1e-9 {
428 d = [1.0, 0.0, 0.0];
429 }
430 let s0 = minkowski_support(a, b, d);
431 let mut simplex = vec![s0];
432 d = [-s0.v[0], -s0.v[1], -s0.v[2]];
433 for _ in 0..32 {
434 if len_sq3f(d) < 1e-12 {
435 return (true, 0.0);
436 }
437 let a_sup = minkowski_support(a, b, d);
438 if dot3f(a_sup.v, d) < 0.0 {
439 return (false, len3f(d));
440 }
441 simplex.push(a_sup);
442 let (intersecting, new_d) = update_simplex(&mut simplex);
443 if intersecting {
444 return (true, 0.0);
445 }
446 d = new_d;
447 }
448 (true, 0.0)
449 }
450 pub fn dispatch(&self, pairs: &[CollisionPair], aabbs: &[AabbGpu]) -> Vec<ContactResult> {
452 let mut contacts = Vec::new();
453 for pair in pairs {
454 let a = pair.a as usize;
455 let b = pair.b as usize;
456 if a < aabbs.len()
457 && b < aabbs.len()
458 && let Some(c) = self.test_aabb_pair(pair.a, &aabbs[a], pair.b, &aabbs[b])
459 {
460 contacts.push(c);
461 }
462 }
463 contacts
464 }
465}
466#[derive(Debug, Clone)]
472pub struct BvhNodeGpu {
473 pub aabb: AabbGpu,
475 pub left_child: u32,
477 pub right_child: u32,
479 pub is_leaf: bool,
481 pub primitive_index: u32,
483}
484impl BvhNodeGpu {
485 pub fn internal(aabb: AabbGpu, left_child: u32, right_child: u32) -> Self {
487 Self {
488 aabb,
489 left_child,
490 right_child,
491 is_leaf: false,
492 primitive_index: u32::MAX,
493 }
494 }
495 pub fn leaf(aabb: AabbGpu, primitive_index: u32) -> Self {
497 Self {
498 aabb,
499 left_child: u32::MAX,
500 right_child: u32::MAX,
501 is_leaf: true,
502 primitive_index,
503 }
504 }
505}
506#[derive(Debug)]
511pub struct CollisionGpuPipeline {
512 pub broadphase: BroadphaseGpuKernel,
514 pub narrowphase: NarrowphaseGpuKernel,
516 pub manifolds: Vec<PersistentManifoldGpu>,
518 pub contacts: Vec<ContactResult>,
520}
521impl CollisionGpuPipeline {
522 pub fn new(margin: f32) -> Self {
524 Self {
525 broadphase: BroadphaseGpuKernel::new(margin),
526 narrowphase: NarrowphaseGpuKernel::new(),
527 manifolds: Vec::new(),
528 contacts: Vec::new(),
529 }
530 }
531 pub fn dispatch(&mut self, aabbs: &[AabbGpu]) {
537 let pairs = self.broadphase.dispatch(aabbs);
538 self.contacts = self.narrowphase.dispatch(&pairs, aabbs);
539 self.update_manifolds();
540 }
541 pub fn dispatch_bvh(&mut self, aabbs: &[AabbGpu]) {
543 let pairs = self.broadphase.dispatch_bvh(aabbs);
544 self.contacts = self.narrowphase.dispatch(&pairs, aabbs);
545 self.update_manifolds();
546 }
547 fn update_manifolds(&mut self) {
549 self.manifolds.retain(|m| m.num_points > 0);
550 for contact in &self.contacts {
551 let existing = self.manifolds.iter_mut().find(|m| {
552 (m.body_a == contact.prim_a && m.body_b == contact.prim_b)
553 || (m.body_a == contact.prim_b && m.body_b == contact.prim_a)
554 });
555 let pt = ManifoldPoint::from_contact(contact);
556 if let Some(manifold) = existing {
557 manifold.add_contact(pt);
558 } else {
559 let mut new_m = PersistentManifoldGpu::new(contact.prim_a, contact.prim_b);
560 new_m.add_contact(pt);
561 self.manifolds.push(new_m);
562 }
563 }
564 }
565 pub fn reset(&mut self) {
567 self.manifolds.clear();
568 self.contacts.clear();
569 }
570 pub fn total_contact_points(&self) -> usize {
572 self.manifolds.iter().map(|m| m.num_points).sum()
573 }
574}
575#[derive(Debug, Clone)]
577pub struct GjkResult {
578 pub intersecting: bool,
580 pub distance: f32,
582 pub closest_a: [f32; 3],
584 pub closest_b: [f32; 3],
586 pub axis: [f32; 3],
588}
589#[derive(Debug)]
595pub struct GpuCollisionPipeline {
596 pub broadphase: GpuBroadphase,
598 pub narrowphase: GpuNarrowphase,
600 pub contact_cache: GpuContactCache,
602 pub aabb_tree: GpuAabbTree,
604 pub contacts: Vec<ContactResult>,
606 pub manifolds: Vec<PersistentManifoldGpu>,
608 pub cumulative_stats: CollisionKernelStats,
610}
611impl GpuCollisionPipeline {
612 pub fn new(margin: f32, max_cache_age: u32) -> Self {
614 Self {
615 broadphase: GpuBroadphase::new(0, margin),
616 narrowphase: GpuNarrowphase::new(32),
617 contact_cache: GpuContactCache::new(max_cache_age),
618 aabb_tree: GpuAabbTree::new(),
619 contacts: Vec::new(),
620 manifolds: Vec::new(),
621 cumulative_stats: CollisionKernelStats::new(),
622 }
623 }
624 pub fn step(&mut self, aabbs: &[AabbGpu]) {
634 self.aabb_tree.build(aabbs);
635 self.broadphase.sweep_axis = GpuBroadphase::choose_best_axis(aabbs);
636 let pairs = self.broadphase.dispatch(aabbs);
637 self.contacts = self.narrowphase.dispatch(&pairs, aabbs);
638 for (pair, contact) in pairs.iter().zip(self.contacts.iter()) {
639 self.contact_cache.insert(pair, contact);
640 }
641 self.update_manifolds();
642 self.contact_cache.advance_frame();
643 let mut step_stats = self.broadphase.stats.clone();
644 step_stats.accumulate(&self.narrowphase.stats);
645 self.cumulative_stats.accumulate(&step_stats);
646 }
647 fn update_manifolds(&mut self) {
648 self.manifolds.retain(|m| m.num_points > 0);
649 for contact in &self.contacts {
650 let existing = self.manifolds.iter_mut().find(|m| {
651 (m.body_a == contact.prim_a && m.body_b == contact.prim_b)
652 || (m.body_a == contact.prim_b && m.body_b == contact.prim_a)
653 });
654 let mut pt = ManifoldPoint::from_contact(contact);
655 if let Some(entry) = self.contact_cache.find(contact.prim_a, contact.prim_b) {
656 entry.apply_warm_start(&mut pt);
657 }
658 if let Some(manifold) = existing {
659 manifold.add_contact(pt);
660 } else {
661 let mut new_m = PersistentManifoldGpu::new(contact.prim_a, contact.prim_b);
662 new_m.add_contact(pt);
663 self.manifolds.push(new_m);
664 }
665 }
666 }
667 pub fn reset(&mut self) {
669 self.contacts.clear();
670 self.manifolds.clear();
671 self.contact_cache.clear();
672 }
673 pub fn total_contact_points(&self) -> usize {
675 self.manifolds.iter().map(|m| m.num_points).sum()
676 }
677 pub fn frame_stats(&self) -> CollisionKernelStats {
679 let mut s = self.broadphase.stats.clone();
680 s.accumulate(&self.narrowphase.stats);
681 s
682 }
683}
684#[derive(Debug, Clone)]
686pub(super) struct SapEntry {
687 pub(super) lo: f32,
689 pub(super) hi: f32,
691 pub(super) idx: u32,
693}
694#[derive(Debug, Clone, PartialEq, Eq, Hash)]
696pub struct CollisionPair {
697 pub a: u32,
699 pub b: u32,
701}
702impl CollisionPair {
703 pub fn new(a: u32, b: u32) -> Self {
705 if a <= b {
706 Self { a, b }
707 } else {
708 Self { a: b, b: a }
709 }
710 }
711}
712#[derive(Debug, Clone)]
714pub struct ContactResult {
715 pub prim_a: u32,
717 pub prim_b: u32,
719 pub contact_point: [f32; 3],
721 pub normal: [f32; 3],
723 pub depth: f32,
725}
726#[derive(Debug, Clone, Copy)]
728pub(super) struct SupportPoint {
729 pub(super) v: [f32; 3],
731}
732impl SupportPoint {
733 pub(super) fn new(v: [f32; 3]) -> Self {
734 Self { v }
735 }
736}
737#[derive(Debug, Clone, Default)]
739pub struct CollisionKernelStats {
740 pub bytes_transferred: u64,
742 pub broadphase_pairs_tested: u64,
744 pub broadphase_hits: u64,
746 pub narrowphase_queries: u64,
748 pub narrowphase_contacts: u64,
750 pub elapsed_secs: f64,
752}
753impl CollisionKernelStats {
754 pub fn new() -> Self {
756 Self::default()
757 }
758 pub fn broadphase_hit_rate(&self) -> f64 {
762 if self.broadphase_pairs_tested == 0 {
763 return 0.0_f64;
764 }
765 self.broadphase_hits as f64 / self.broadphase_pairs_tested as f64
766 }
767 pub fn narrowphase_contact_rate(&self) -> f64 {
771 if self.narrowphase_queries == 0 {
772 return 0.0_f64;
773 }
774 self.narrowphase_contacts as f64 / self.narrowphase_queries as f64
775 }
776 pub fn bandwidth_gb_s(&self) -> f64 {
780 if self.elapsed_secs <= 0.0_f64 {
781 return 0.0_f64;
782 }
783 self.bytes_transferred as f64 / self.elapsed_secs / 1.0e9_f64
784 }
785 pub fn pair_throughput(&self) -> f64 {
789 if self.elapsed_secs <= 0.0_f64 {
790 return 0.0_f64;
791 }
792 self.broadphase_pairs_tested as f64 / self.elapsed_secs
793 }
794 pub fn accumulate(&mut self, other: &CollisionKernelStats) {
796 self.bytes_transferred += other.bytes_transferred;
797 self.broadphase_pairs_tested += other.broadphase_pairs_tested;
798 self.broadphase_hits += other.broadphase_hits;
799 self.narrowphase_queries += other.narrowphase_queries;
800 self.narrowphase_contacts += other.narrowphase_contacts;
801 self.elapsed_secs += other.elapsed_secs;
802 }
803}
804#[derive(Debug, Clone)]
806pub struct ContactCacheEntry {
807 pub key: (u32, u32),
809 pub accumulated_normal: f32,
811 pub accumulated_tangent_1: f32,
813 pub accumulated_tangent_2: f32,
815 pub contact_point: [f32; 3],
817 pub age_frames: u32,
819}
820impl ContactCacheEntry {
821 pub fn new(pair: &CollisionPair, contact: &ContactResult) -> Self {
823 let (a, b) = if pair.a <= pair.b {
824 (pair.a, pair.b)
825 } else {
826 (pair.b, pair.a)
827 };
828 Self {
829 key: (a, b),
830 accumulated_normal: 0.0_f32,
831 accumulated_tangent_1: 0.0_f32,
832 accumulated_tangent_2: 0.0_f32,
833 contact_point: contact.contact_point,
834 age_frames: 0,
835 }
836 }
837 pub fn apply_warm_start(&self, pt: &mut ManifoldPoint) {
839 pt.warm_impulse_normal = self.accumulated_normal;
840 pt.warm_impulse_t1 = self.accumulated_tangent_1;
841 pt.warm_impulse_t2 = self.accumulated_tangent_2;
842 }
843 pub fn update(&mut self, normal_impulse: f32, t1: f32, t2: f32) {
845 self.accumulated_normal = normal_impulse;
846 self.accumulated_tangent_1 = t1;
847 self.accumulated_tangent_2 = t2;
848 self.age_frames = 0;
849 }
850}
851#[derive(Debug, Default)]
857pub struct GpuAabbTree {
858 pub nodes: Vec<BvhNodeGpu>,
860 pub root: usize,
862 pub num_primitives: usize,
864 pub morton_codes: Vec<u32>,
866}
867impl GpuAabbTree {
868 pub fn new() -> Self {
870 Self::default()
871 }
872 pub fn build(&mut self, aabbs: &[AabbGpu]) {
877 self.nodes.clear();
878 self.morton_codes.clear();
879 self.num_primitives = aabbs.len();
880 if aabbs.is_empty() {
881 self.root = 0;
882 return;
883 }
884 let mut scene_min = [f32::INFINITY; 3];
885 let mut scene_max = [f32::NEG_INFINITY; 3];
886 for a in aabbs {
887 for d in 0..3 {
888 scene_min[d] = scene_min[d].min(a.min[d]);
889 scene_max[d] = scene_max[d].max(a.max[d]);
890 }
891 }
892 let extent = [
893 (scene_max[0] - scene_min[0]).max(1.0e-6_f32),
894 (scene_max[1] - scene_min[1]).max(1.0e-6_f32),
895 (scene_max[2] - scene_min[2]).max(1.0e-6_f32),
896 ];
897 let mut order: Vec<(u32, usize)> = aabbs
898 .iter()
899 .enumerate()
900 .map(|(i, a)| {
901 let c = a.centre();
902 let nx = (c[0] - scene_min[0]) / extent[0];
903 let ny = (c[1] - scene_min[1]) / extent[1];
904 let nz = (c[2] - scene_min[2]) / extent[2];
905 let code = morton_code(nx, ny, nz);
906 self.morton_codes.push(code);
907 (code, i)
908 })
909 .collect();
910 order.sort_by_key(|&(code, _)| code);
911 for &(_, prim_idx) in &order {
912 self.nodes
913 .push(BvhNodeGpu::leaf(aabbs[prim_idx].clone(), prim_idx as u32));
914 }
915 let mut level: Vec<usize> = (0..aabbs.len()).collect();
916 while level.len() > 1 {
917 let mut next = Vec::with_capacity(level.len().div_ceil(2));
918 let mut i = 0;
919 while i < level.len() {
920 if i + 1 < level.len() {
921 let l = level[i];
922 let r = level[i + 1];
923 let merged = self.nodes[l].aabb.merge(&self.nodes[r].aabb);
924 let parent = BvhNodeGpu::internal(merged, l as u32, r as u32);
925 let idx = self.nodes.len();
926 self.nodes.push(parent);
927 next.push(idx);
928 i += 2;
929 } else {
930 next.push(level[i]);
931 i += 1;
932 }
933 }
934 level = next;
935 }
936 self.root = level[0];
937 }
938 pub fn query(&self, query: &AabbGpu) -> Vec<u32> {
940 if self.nodes.is_empty() {
941 return Vec::new();
942 }
943 let mut result = Vec::new();
944 let mut stack = vec![self.root];
945 while let Some(idx) = stack.pop() {
946 let node = &self.nodes[idx];
947 if !node.aabb.overlaps(query) {
948 continue;
949 }
950 if node.is_leaf {
951 result.push(node.primitive_index);
952 } else {
953 stack.push(node.left_child as usize);
954 stack.push(node.right_child as usize);
955 }
956 }
957 result
958 }
959 pub fn depth(&self) -> usize {
961 if self.nodes.is_empty() {
962 return 0;
963 }
964 self.depth_from(self.root)
965 }
966 fn depth_from(&self, idx: usize) -> usize {
967 let node = &self.nodes[idx];
968 if node.is_leaf {
969 1
970 } else {
971 let ld = self.depth_from(node.left_child as usize);
972 let rd = self.depth_from(node.right_child as usize);
973 1 + ld.max(rd)
974 }
975 }
976 pub fn leaf_count(&self) -> usize {
978 self.nodes.iter().filter(|n| n.is_leaf).count()
979 }
980 pub fn sah_cost(&self) -> f32 {
984 self.nodes
985 .iter()
986 .filter(|n| !n.is_leaf)
987 .map(|n| n.aabb.surface_area())
988 .sum()
989 }
990}
991#[derive(Debug, Default)]
996pub struct GpuNarrowphase {
997 pub max_iterations: usize,
999 pub stats: CollisionKernelStats,
1001}
1002impl GpuNarrowphase {
1003 pub fn new(max_iterations: usize) -> Self {
1005 Self {
1006 max_iterations: max_iterations.max(4),
1007 stats: CollisionKernelStats::new(),
1008 }
1009 }
1010 pub fn gjk_distance(&self, a: &AabbGpu, b: &AabbGpu) -> GjkResult {
1012 let ca = a.centre();
1013 let cb = b.centre();
1014 let mut d = sub3f(ca, cb);
1015 if len_sq3f(d) < 1.0e-9_f32 {
1016 d = [1.0_f32, 0.0_f32, 0.0_f32];
1017 }
1018 let s0 = minkowski_support(a, b, d);
1019 let mut simplex: Vec<SupportPoint> = vec![s0];
1020 d = [-s0.v[0], -s0.v[1], -s0.v[2]];
1021 for _ in 0..self.max_iterations {
1022 if len_sq3f(d) < 1.0e-12_f32 {
1023 return GjkResult {
1024 intersecting: true,
1025 distance: 0.0_f32,
1026 closest_a: ca,
1027 closest_b: cb,
1028 axis: norm3f(sub3f(ca, cb)),
1029 };
1030 }
1031 let sup = minkowski_support(a, b, d);
1032 if dot3f(sup.v, d) < 0.0_f32 {
1033 let dist = len3f(d);
1034 let axis = norm3f(d);
1035 return GjkResult {
1036 intersecting: false,
1037 distance: dist,
1038 closest_a: aabb_support(a, axis),
1039 closest_b: aabb_support(b, [-axis[0], -axis[1], -axis[2]]),
1040 axis,
1041 };
1042 }
1043 simplex.push(sup);
1044 let (inter, new_d) = update_simplex(&mut simplex);
1045 if inter {
1046 return GjkResult {
1047 intersecting: true,
1048 distance: 0.0_f32,
1049 closest_a: ca,
1050 closest_b: cb,
1051 axis: norm3f(sub3f(ca, cb)),
1052 };
1053 }
1054 d = new_d;
1055 }
1056 GjkResult {
1057 intersecting: true,
1058 distance: 0.0_f32,
1059 closest_a: ca,
1060 closest_b: cb,
1061 axis: norm3f(sub3f(ca, cb)),
1062 }
1063 }
1064 pub fn sat_depth(&self, a: &AabbGpu, b: &AabbGpu) -> Option<(f32, [f32; 3])> {
1068 let mut min_depth = f32::INFINITY;
1069 let mut min_axis = [1.0_f32, 0.0_f32, 0.0_f32];
1070 for d in 0..3usize {
1071 let mut axis = [0.0_f32; 3];
1072 axis[d] = 1.0_f32;
1073 let a_min = a.min[d];
1074 let a_max = a.max[d];
1075 let b_min = b.min[d];
1076 let b_max = b.max[d];
1077 let overlap = a_max.min(b_max) - a_min.max(b_min);
1078 if overlap <= 0.0_f32 {
1079 return None;
1080 }
1081 if overlap < min_depth {
1082 min_depth = overlap;
1083 let sign = if (a.min[d] + a.max[d]) > (b.min[d] + b.max[d]) {
1084 1.0_f32
1085 } else {
1086 -1.0_f32
1087 };
1088 axis[d] = sign;
1089 min_axis = axis;
1090 }
1091 }
1092 Some((min_depth, min_axis))
1093 }
1094 pub fn dispatch(&mut self, pairs: &[CollisionPair], aabbs: &[AabbGpu]) -> Vec<ContactResult> {
1098 let mut contacts = Vec::new();
1099 self.stats.narrowphase_queries += pairs.len() as u64;
1100 for pair in pairs {
1101 let ai = pair.a as usize;
1102 let bi = pair.b as usize;
1103 if ai >= aabbs.len() || bi >= aabbs.len() {
1104 continue;
1105 }
1106 let gjk = self.gjk_distance(&aabbs[ai], &aabbs[bi]);
1107 if gjk.intersecting {
1108 let (depth, normal) = self
1109 .sat_depth(&aabbs[ai], &aabbs[bi])
1110 .unwrap_or((0.0_f32, gjk.axis));
1111 let ca = aabbs[ai].centre();
1112 let cb = aabbs[bi].centre();
1113 let contact_point = [
1114 (ca[0] + cb[0]) * 0.5_f32,
1115 (ca[1] + cb[1]) * 0.5_f32,
1116 (ca[2] + cb[2]) * 0.5_f32,
1117 ];
1118 contacts.push(ContactResult {
1119 prim_a: pair.a,
1120 prim_b: pair.b,
1121 contact_point,
1122 normal,
1123 depth,
1124 });
1125 self.stats.narrowphase_contacts += 1;
1126 }
1127 }
1128 contacts
1129 }
1130}
1131#[derive(Debug, Default)]
1136pub struct GpuContactCache {
1137 pub entries: Vec<ContactCacheEntry>,
1139 pub max_age_frames: u32,
1141}
1142impl GpuContactCache {
1143 pub fn new(max_age_frames: u32) -> Self {
1145 Self {
1146 entries: Vec::new(),
1147 max_age_frames: max_age_frames.max(1),
1148 }
1149 }
1150 pub fn find(&self, a: u32, b: u32) -> Option<&ContactCacheEntry> {
1152 let key = if a <= b { (a, b) } else { (b, a) };
1153 self.entries.iter().find(|e| e.key == key)
1154 }
1155 pub fn find_mut(&mut self, a: u32, b: u32) -> Option<&mut ContactCacheEntry> {
1157 let key = if a <= b { (a, b) } else { (b, a) };
1158 self.entries.iter_mut().find(|e| e.key == key)
1159 }
1160 pub fn insert(&mut self, pair: &CollisionPair, contact: &ContactResult) {
1162 let a = pair.a;
1163 let b = pair.b;
1164 let key = if a <= b { (a, b) } else { (b, a) };
1165 if let Some(e) = self.entries.iter_mut().find(|e| e.key == key) {
1166 e.contact_point = contact.contact_point;
1167 e.age_frames = 0;
1168 } else {
1169 self.entries.push(ContactCacheEntry::new(pair, contact));
1170 }
1171 }
1172 pub fn advance_frame(&mut self) {
1174 for e in self.entries.iter_mut() {
1175 e.age_frames += 1;
1176 }
1177 let max_age = self.max_age_frames;
1178 self.entries.retain(|e| e.age_frames <= max_age);
1179 }
1180 pub fn len(&self) -> usize {
1182 self.entries.len()
1183 }
1184 pub fn is_empty(&self) -> bool {
1186 self.entries.is_empty()
1187 }
1188 pub fn clear(&mut self) {
1190 self.entries.clear();
1191 }
1192}
1193#[derive(Debug, Default)]
1199pub struct GpuBroadphase {
1200 pub sweep_axis: usize,
1202 pub margin: f32,
1204 pub stats: CollisionKernelStats,
1206}
1207impl GpuBroadphase {
1208 pub fn new(sweep_axis: usize, margin: f32) -> Self {
1210 Self {
1211 sweep_axis: sweep_axis % 3,
1212 margin,
1213 stats: CollisionKernelStats::new(),
1214 }
1215 }
1216 pub fn choose_best_axis(aabbs: &[AabbGpu]) -> usize {
1220 if aabbs.is_empty() {
1221 return 0;
1222 }
1223 let mut spread = [0.0_f32; 3];
1224 for d in 0..3usize {
1225 let lo = aabbs.iter().map(|a| a.min[d]).fold(f32::INFINITY, f32::min);
1226 let hi = aabbs
1227 .iter()
1228 .map(|a| a.max[d])
1229 .fold(f32::NEG_INFINITY, f32::max);
1230 spread[d] = hi - lo;
1231 }
1232 if spread[0] >= spread[1] && spread[0] >= spread[2] {
1233 0
1234 } else if spread[1] >= spread[2] {
1235 1
1236 } else {
1237 2
1238 }
1239 }
1240 pub fn dispatch(&mut self, aabbs: &[AabbGpu]) -> Vec<CollisionPair> {
1244 let axis = self.sweep_axis;
1245 let _n = aabbs.len();
1246 let mut pairs = Vec::new();
1247 let mut entries: Vec<SapEntry> = aabbs
1248 .iter()
1249 .enumerate()
1250 .map(|(i, a)| SapEntry {
1251 lo: a.min[axis] - self.margin,
1252 hi: a.max[axis] + self.margin,
1253 idx: i as u32,
1254 })
1255 .collect();
1256 entries.sort_by(|a, b| a.lo.partial_cmp(&b.lo).unwrap_or(std::cmp::Ordering::Equal));
1257 let mut tests = 0u64;
1258 let mut hits = 0u64;
1259 for i in 0..entries.len() {
1260 let ei = &entries[i];
1261 for j in (i + 1)..entries.len() {
1262 let ej = &entries[j];
1263 if ej.lo > ei.hi {
1264 break;
1265 }
1266 tests += 1;
1267 let pi = ei.idx as usize;
1268 let pj = ej.idx as usize;
1269 let expanded_i = aabbs[pi].expanded(self.margin);
1270 if expanded_i.overlaps(&aabbs[pj]) {
1271 hits += 1;
1272 pairs.push(CollisionPair::new(ei.idx, ej.idx));
1273 }
1274 }
1275 }
1276 self.stats.broadphase_pairs_tested += tests;
1277 self.stats.broadphase_hits += hits;
1278 self.stats.bytes_transferred += std::mem::size_of_val(aabbs) as u64;
1279 pairs
1280 }
1281}