1use super::functions::*;
2#[derive(Debug, Default)]
10pub struct BroadphaseGpuKernel {
11 pub margin: f32,
13}
14impl BroadphaseGpuKernel {
15 pub fn new(margin: f32) -> Self {
17 Self { margin }
18 }
19 pub fn dispatch(&self, aabbs: &[AabbGpu]) -> Vec<CollisionPair> {
23 let mut pairs = Vec::new();
24 for (i, aabb_i) in aabbs.iter().enumerate() {
25 let expanded_i = aabb_i.expanded(self.margin);
26 for (j, aabb_j) in aabbs.iter().enumerate().skip(i + 1) {
27 if expanded_i.overlaps(aabb_j) {
28 pairs.push(CollisionPair::new(i as u32, j as u32));
29 }
30 }
31 }
32 pairs
33 }
34 pub fn dispatch_bvh(&self, aabbs: &[AabbGpu]) -> Vec<CollisionPair> {
36 let mut builder = GpuBvhBuilder::new();
37 let root = builder.build(aabbs);
38 let mut pairs = Vec::new();
39 for (i, aabb) in aabbs.iter().enumerate() {
40 let query = aabb.expanded(self.margin);
41 let hits = builder.query_overlaps(root, &query);
42 for j in hits {
43 if j as usize > i {
44 pairs.push(CollisionPair::new(i as u32, j));
45 }
46 }
47 }
48 pairs
49 }
50}
51#[derive(Debug, Clone, PartialEq)]
55pub struct AabbGpu {
56 pub min: [f32; 3],
58 pub max: [f32; 3],
60}
61impl AabbGpu {
62 pub fn new(min: [f32; 3], max: [f32; 3]) -> Self {
64 Self { min, max }
65 }
66 pub fn from_point(p: [f32; 3]) -> Self {
68 Self { min: p, max: p }
69 }
70 pub fn merge(&self, other: &AabbGpu) -> AabbGpu {
72 AabbGpu {
73 min: [
74 self.min[0].min(other.min[0]),
75 self.min[1].min(other.min[1]),
76 self.min[2].min(other.min[2]),
77 ],
78 max: [
79 self.max[0].max(other.max[0]),
80 self.max[1].max(other.max[1]),
81 self.max[2].max(other.max[2]),
82 ],
83 }
84 }
85 pub fn overlaps(&self, other: &AabbGpu) -> bool {
87 self.min[0] <= other.max[0]
88 && self.max[0] >= other.min[0]
89 && self.min[1] <= other.max[1]
90 && self.max[1] >= other.min[1]
91 && self.min[2] <= other.max[2]
92 && self.max[2] >= other.min[2]
93 }
94 pub fn contains_point(&self, p: [f32; 3]) -> bool {
96 p[0] >= self.min[0]
97 && p[0] <= self.max[0]
98 && p[1] >= self.min[1]
99 && p[1] <= self.max[1]
100 && p[2] >= self.min[2]
101 && p[2] <= self.max[2]
102 }
103 pub fn surface_area(&self) -> f32 {
105 let dx = (self.max[0] - self.min[0]).max(0.0);
106 let dy = (self.max[1] - self.min[1]).max(0.0);
107 let dz = (self.max[2] - self.min[2]).max(0.0);
108 2.0 * (dx * dy + dy * dz + dz * dx)
109 }
110 pub fn volume(&self) -> f32 {
112 let dx = (self.max[0] - self.min[0]).max(0.0);
113 let dy = (self.max[1] - self.min[1]).max(0.0);
114 let dz = (self.max[2] - self.min[2]).max(0.0);
115 dx * dy * dz
116 }
117 pub fn centre(&self) -> [f32; 3] {
119 [
120 (self.min[0] + self.max[0]) * 0.5,
121 (self.min[1] + self.max[1]) * 0.5,
122 (self.min[2] + self.max[2]) * 0.5,
123 ]
124 }
125 pub fn half_extents(&self) -> [f32; 3] {
127 [
128 (self.max[0] - self.min[0]) * 0.5,
129 (self.max[1] - self.min[1]) * 0.5,
130 (self.max[2] - self.min[2]) * 0.5,
131 ]
132 }
133 pub fn expanded(&self, margin: f32) -> AabbGpu {
135 AabbGpu {
136 min: [
137 self.min[0] - margin,
138 self.min[1] - margin,
139 self.min[2] - margin,
140 ],
141 max: [
142 self.max[0] + margin,
143 self.max[1] + margin,
144 self.max[2] + margin,
145 ],
146 }
147 }
148}
149#[derive(Debug, Clone)]
154pub struct PersistentManifoldGpu {
155 pub body_a: u32,
157 pub body_b: u32,
159 pub points: [Option<ManifoldPoint>; 4],
161 pub num_points: usize,
163}
164impl PersistentManifoldGpu {
165 pub fn new(body_a: u32, body_b: u32) -> Self {
167 Self {
168 body_a,
169 body_b,
170 points: [None, None, None, None],
171 num_points: 0,
172 }
173 }
174 pub fn add_contact(&mut self, new_pt: ManifoldPoint) {
180 if self.num_points < 4 {
181 self.points[self.num_points] = Some(new_pt);
182 self.num_points += 1;
183 return;
184 }
185 let mut shallowest_idx = 0;
186 let mut shallowest_depth = f32::INFINITY;
187 for (i, opt) in self.points.iter().enumerate() {
188 if let Some(p) = opt
189 && p.depth < shallowest_depth
190 {
191 shallowest_depth = p.depth;
192 shallowest_idx = i;
193 }
194 }
195 if new_pt.depth > shallowest_depth {
196 self.points[shallowest_idx] = Some(new_pt);
197 }
198 }
199 pub fn remove_stale(&mut self, threshold: f32) {
201 for slot in self.points.iter_mut() {
202 if let Some(p) = slot
203 && p.depth < threshold
204 {
205 *slot = None;
206 }
207 }
208 self.num_points = self.points.iter().filter(|s| s.is_some()).count();
209 }
210 pub fn reset_warm_start(&mut self) {
212 for slot in self.points.iter_mut().flatten() {
213 slot.warm_impulse_normal = 0.0;
214 slot.warm_impulse_t1 = 0.0;
215 slot.warm_impulse_t2 = 0.0;
216 }
217 }
218 pub fn active_points(&self) -> impl Iterator<Item = &ManifoldPoint> {
220 self.points.iter().flatten()
221 }
222}
223#[derive(Debug, Default)]
228pub struct GpuBvhBuilder {
229 pub(super) nodes: Vec<BvhNodeGpu>,
230}
231impl GpuBvhBuilder {
232 pub fn new() -> Self {
234 Self { nodes: Vec::new() }
235 }
236 pub fn build(&mut self, aabbs: &[AabbGpu]) -> usize {
240 self.nodes.clear();
241 if aabbs.is_empty() {
242 return 0;
243 }
244 let scene_min = [
245 aabbs.iter().map(|a| a.min[0]).fold(f32::INFINITY, f32::min),
246 aabbs.iter().map(|a| a.min[1]).fold(f32::INFINITY, f32::min),
247 aabbs.iter().map(|a| a.min[2]).fold(f32::INFINITY, f32::min),
248 ];
249 let scene_max = [
250 aabbs
251 .iter()
252 .map(|a| a.max[0])
253 .fold(f32::NEG_INFINITY, f32::max),
254 aabbs
255 .iter()
256 .map(|a| a.max[1])
257 .fold(f32::NEG_INFINITY, f32::max),
258 aabbs
259 .iter()
260 .map(|a| a.max[2])
261 .fold(f32::NEG_INFINITY, f32::max),
262 ];
263 let extent = [
264 (scene_max[0] - scene_min[0]).max(1e-6),
265 (scene_max[1] - scene_min[1]).max(1e-6),
266 (scene_max[2] - scene_min[2]).max(1e-6),
267 ];
268 let mut indexed: Vec<(u32, usize)> = aabbs
269 .iter()
270 .enumerate()
271 .map(|(i, a)| {
272 let c = a.centre();
273 let nx = (c[0] - scene_min[0]) / extent[0];
274 let ny = (c[1] - scene_min[1]) / extent[1];
275 let nz = (c[2] - scene_min[2]) / extent[2];
276 (morton_code(nx, ny, nz), i)
277 })
278 .collect();
279 indexed.sort_by_key(|&(code, _)| code);
280 let leaf_base = 0usize;
281 for &(_, prim_idx) in &indexed {
282 self.nodes
283 .push(BvhNodeGpu::leaf(aabbs[prim_idx].clone(), prim_idx as u32));
284 }
285 let mut current_level: Vec<usize> = (leaf_base..leaf_base + indexed.len()).collect();
286 while current_level.len() > 1 {
287 let mut next_level = Vec::new();
288 let mut i = 0;
289 while i < current_level.len() {
290 if i + 1 < current_level.len() {
291 let l = current_level[i];
292 let r = current_level[i + 1];
293 let merged = self.nodes[l].aabb.merge(&self.nodes[r].aabb);
294 let internal = BvhNodeGpu::internal(merged, l as u32, r as u32);
295 let idx = self.nodes.len();
296 self.nodes.push(internal);
297 next_level.push(idx);
298 i += 2;
299 } else {
300 next_level.push(current_level[i]);
301 i += 1;
302 }
303 }
304 current_level = next_level;
305 }
306 current_level[0]
307 }
308 pub fn nodes(&self) -> &[BvhNodeGpu] {
310 &self.nodes
311 }
312 pub fn query_overlaps(&self, root: usize, query: &AabbGpu) -> Vec<u32> {
314 if self.nodes.is_empty() {
315 return Vec::new();
316 }
317 let mut result = Vec::new();
318 let mut stack = vec![root];
319 while let Some(idx) = stack.pop() {
320 let node = &self.nodes[idx];
321 if !node.aabb.overlaps(query) {
322 continue;
323 }
324 if node.is_leaf {
325 result.push(node.primitive_index);
326 } else {
327 stack.push(node.left_child as usize);
328 stack.push(node.right_child as usize);
329 }
330 }
331 result
332 }
333}
334#[derive(Debug, Clone)]
336pub struct ManifoldPoint {
337 pub position: [f32; 3],
339 pub normal: [f32; 3],
341 pub depth: f32,
343 pub warm_impulse_normal: f32,
345 pub warm_impulse_t1: f32,
347 pub warm_impulse_t2: f32,
349}
350impl ManifoldPoint {
351 pub fn from_contact(c: &ContactResult) -> Self {
353 Self {
354 position: c.contact_point,
355 normal: c.normal,
356 depth: c.depth,
357 warm_impulse_normal: 0.0,
358 warm_impulse_t1: 0.0,
359 warm_impulse_t2: 0.0,
360 }
361 }
362}
363#[derive(Debug, Default)]
368pub struct NarrowphaseGpuKernel;
369impl NarrowphaseGpuKernel {
370 pub fn new() -> Self {
372 Self
373 }
374 pub fn test_aabb_pair(
377 &self,
378 prim_a: u32,
379 aabb_a: &AabbGpu,
380 prim_b: u32,
381 aabb_b: &AabbGpu,
382 ) -> Option<ContactResult> {
383 let axes: [[f32; 3]; 3] = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
384 let mut min_depth = f32::INFINITY;
385 let mut min_axis = [1.0f32, 0.0, 0.0];
386 for axis in &axes {
387 let a_min = dot3f(aabb_a.min, *axis);
388 let a_max = dot3f(aabb_a.max, *axis);
389 let b_min = dot3f(aabb_b.min, *axis);
390 let b_max = dot3f(aabb_b.max, *axis);
391 let overlap = a_max.min(b_max) - a_min.max(b_min);
392 if overlap <= 0.0 {
393 return None;
394 }
395 if overlap < min_depth {
396 min_depth = overlap;
397 let ca = dot3f(aabb_a.centre(), *axis);
398 let cb = dot3f(aabb_b.centre(), *axis);
399 let sign = if ca > cb { 1.0 } else { -1.0 };
400 min_axis = scale3f(*axis, sign);
401 }
402 }
403 let ca = aabb_a.centre();
404 let cb = aabb_b.centre();
405 let contact_point = [
406 (ca[0] + cb[0]) * 0.5,
407 (ca[1] + cb[1]) * 0.5,
408 (ca[2] + cb[2]) * 0.5,
409 ];
410 Some(ContactResult {
411 prim_a,
412 prim_b,
413 contact_point,
414 normal: min_axis,
415 depth: min_depth,
416 })
417 }
418 pub fn gjk_intersect(&self, a: &AabbGpu, b: &AabbGpu) -> (bool, f32) {
423 let ca = a.centre();
424 let cb = b.centre();
425 let mut d = sub3f(ca, cb);
426 if len_sq3f(d) < 1e-9 {
427 d = [1.0, 0.0, 0.0];
428 }
429 let s0 = minkowski_support(a, b, d);
430 let mut simplex = vec![s0];
431 d = [-s0.v[0], -s0.v[1], -s0.v[2]];
432 for _ in 0..32 {
433 if len_sq3f(d) < 1e-12 {
434 return (true, 0.0);
435 }
436 let a_sup = minkowski_support(a, b, d);
437 if dot3f(a_sup.v, d) < 0.0 {
438 return (false, len3f(d));
439 }
440 simplex.push(a_sup);
441 let (intersecting, new_d) = update_simplex(&mut simplex);
442 if intersecting {
443 return (true, 0.0);
444 }
445 d = new_d;
446 }
447 (true, 0.0)
448 }
449 pub fn dispatch(&self, pairs: &[CollisionPair], aabbs: &[AabbGpu]) -> Vec<ContactResult> {
451 let mut contacts = Vec::new();
452 for pair in pairs {
453 let a = pair.a as usize;
454 let b = pair.b as usize;
455 if a < aabbs.len()
456 && b < aabbs.len()
457 && let Some(c) = self.test_aabb_pair(pair.a, &aabbs[a], pair.b, &aabbs[b])
458 {
459 contacts.push(c);
460 }
461 }
462 contacts
463 }
464}
465#[derive(Debug, Clone)]
471pub struct BvhNodeGpu {
472 pub aabb: AabbGpu,
474 pub left_child: u32,
476 pub right_child: u32,
478 pub is_leaf: bool,
480 pub primitive_index: u32,
482}
483impl BvhNodeGpu {
484 pub fn internal(aabb: AabbGpu, left_child: u32, right_child: u32) -> Self {
486 Self {
487 aabb,
488 left_child,
489 right_child,
490 is_leaf: false,
491 primitive_index: u32::MAX,
492 }
493 }
494 pub fn leaf(aabb: AabbGpu, primitive_index: u32) -> Self {
496 Self {
497 aabb,
498 left_child: u32::MAX,
499 right_child: u32::MAX,
500 is_leaf: true,
501 primitive_index,
502 }
503 }
504}
505#[derive(Debug)]
510pub struct CollisionGpuPipeline {
511 pub broadphase: BroadphaseGpuKernel,
513 pub narrowphase: NarrowphaseGpuKernel,
515 pub manifolds: Vec<PersistentManifoldGpu>,
517 pub contacts: Vec<ContactResult>,
519}
520impl CollisionGpuPipeline {
521 pub fn new(margin: f32) -> Self {
523 Self {
524 broadphase: BroadphaseGpuKernel::new(margin),
525 narrowphase: NarrowphaseGpuKernel::new(),
526 manifolds: Vec::new(),
527 contacts: Vec::new(),
528 }
529 }
530 pub fn dispatch(&mut self, aabbs: &[AabbGpu]) {
536 let pairs = self.broadphase.dispatch(aabbs);
537 self.contacts = self.narrowphase.dispatch(&pairs, aabbs);
538 self.update_manifolds();
539 }
540 pub fn dispatch_bvh(&mut self, aabbs: &[AabbGpu]) {
542 let pairs = self.broadphase.dispatch_bvh(aabbs);
543 self.contacts = self.narrowphase.dispatch(&pairs, aabbs);
544 self.update_manifolds();
545 }
546 fn update_manifolds(&mut self) {
548 self.manifolds.retain(|m| m.num_points > 0);
549 for contact in &self.contacts {
550 let existing = self.manifolds.iter_mut().find(|m| {
551 (m.body_a == contact.prim_a && m.body_b == contact.prim_b)
552 || (m.body_a == contact.prim_b && m.body_b == contact.prim_a)
553 });
554 let pt = ManifoldPoint::from_contact(contact);
555 if let Some(manifold) = existing {
556 manifold.add_contact(pt);
557 } else {
558 let mut new_m = PersistentManifoldGpu::new(contact.prim_a, contact.prim_b);
559 new_m.add_contact(pt);
560 self.manifolds.push(new_m);
561 }
562 }
563 }
564 pub fn reset(&mut self) {
566 self.manifolds.clear();
567 self.contacts.clear();
568 }
569 pub fn total_contact_points(&self) -> usize {
571 self.manifolds.iter().map(|m| m.num_points).sum()
572 }
573}
574#[derive(Debug, Clone)]
576pub struct GjkResult {
577 pub intersecting: bool,
579 pub distance: f32,
581 pub closest_a: [f32; 3],
583 pub closest_b: [f32; 3],
585 pub axis: [f32; 3],
587}
588#[derive(Debug)]
594pub struct GpuCollisionPipeline {
595 pub broadphase: GpuBroadphase,
597 pub narrowphase: GpuNarrowphase,
599 pub contact_cache: GpuContactCache,
601 pub aabb_tree: GpuAabbTree,
603 pub contacts: Vec<ContactResult>,
605 pub manifolds: Vec<PersistentManifoldGpu>,
607 pub cumulative_stats: CollisionKernelStats,
609}
610impl GpuCollisionPipeline {
611 pub fn new(margin: f32, max_cache_age: u32) -> Self {
613 Self {
614 broadphase: GpuBroadphase::new(0, margin),
615 narrowphase: GpuNarrowphase::new(32),
616 contact_cache: GpuContactCache::new(max_cache_age),
617 aabb_tree: GpuAabbTree::new(),
618 contacts: Vec::new(),
619 manifolds: Vec::new(),
620 cumulative_stats: CollisionKernelStats::new(),
621 }
622 }
623 pub fn step(&mut self, aabbs: &[AabbGpu]) {
633 self.aabb_tree.build(aabbs);
634 self.broadphase.sweep_axis = GpuBroadphase::choose_best_axis(aabbs);
635 let pairs = self.broadphase.dispatch(aabbs);
636 self.contacts = self.narrowphase.dispatch(&pairs, aabbs);
637 for (pair, contact) in pairs.iter().zip(self.contacts.iter()) {
638 self.contact_cache.insert(pair, contact);
639 }
640 self.update_manifolds();
641 self.contact_cache.advance_frame();
642 let mut step_stats = self.broadphase.stats.clone();
643 step_stats.accumulate(&self.narrowphase.stats);
644 self.cumulative_stats.accumulate(&step_stats);
645 }
646 fn update_manifolds(&mut self) {
647 self.manifolds.retain(|m| m.num_points > 0);
648 for contact in &self.contacts {
649 let existing = self.manifolds.iter_mut().find(|m| {
650 (m.body_a == contact.prim_a && m.body_b == contact.prim_b)
651 || (m.body_a == contact.prim_b && m.body_b == contact.prim_a)
652 });
653 let mut pt = ManifoldPoint::from_contact(contact);
654 if let Some(entry) = self.contact_cache.find(contact.prim_a, contact.prim_b) {
655 entry.apply_warm_start(&mut pt);
656 }
657 if let Some(manifold) = existing {
658 manifold.add_contact(pt);
659 } else {
660 let mut new_m = PersistentManifoldGpu::new(contact.prim_a, contact.prim_b);
661 new_m.add_contact(pt);
662 self.manifolds.push(new_m);
663 }
664 }
665 }
666 pub fn reset(&mut self) {
668 self.contacts.clear();
669 self.manifolds.clear();
670 self.contact_cache.clear();
671 }
672 pub fn total_contact_points(&self) -> usize {
674 self.manifolds.iter().map(|m| m.num_points).sum()
675 }
676 pub fn frame_stats(&self) -> CollisionKernelStats {
678 let mut s = self.broadphase.stats.clone();
679 s.accumulate(&self.narrowphase.stats);
680 s
681 }
682}
683#[derive(Debug, Clone)]
685pub(super) struct SapEntry {
686 pub(super) lo: f32,
688 pub(super) hi: f32,
690 pub(super) idx: u32,
692}
693#[derive(Debug, Clone, PartialEq, Eq, Hash)]
695pub struct CollisionPair {
696 pub a: u32,
698 pub b: u32,
700}
701impl CollisionPair {
702 pub fn new(a: u32, b: u32) -> Self {
704 if a <= b {
705 Self { a, b }
706 } else {
707 Self { a: b, b: a }
708 }
709 }
710}
711#[derive(Debug, Clone)]
713pub struct ContactResult {
714 pub prim_a: u32,
716 pub prim_b: u32,
718 pub contact_point: [f32; 3],
720 pub normal: [f32; 3],
722 pub depth: f32,
724}
725#[derive(Debug, Clone, Copy)]
727pub(super) struct SupportPoint {
728 pub(super) v: [f32; 3],
730}
731impl SupportPoint {
732 pub(super) fn new(v: [f32; 3]) -> Self {
733 Self { v }
734 }
735}
736#[derive(Debug, Clone, Default)]
738pub struct CollisionKernelStats {
739 pub bytes_transferred: u64,
741 pub broadphase_pairs_tested: u64,
743 pub broadphase_hits: u64,
745 pub narrowphase_queries: u64,
747 pub narrowphase_contacts: u64,
749 pub elapsed_secs: f64,
751}
752impl CollisionKernelStats {
753 pub fn new() -> Self {
755 Self::default()
756 }
757 pub fn broadphase_hit_rate(&self) -> f64 {
761 if self.broadphase_pairs_tested == 0 {
762 return 0.0_f64;
763 }
764 self.broadphase_hits as f64 / self.broadphase_pairs_tested as f64
765 }
766 pub fn narrowphase_contact_rate(&self) -> f64 {
770 if self.narrowphase_queries == 0 {
771 return 0.0_f64;
772 }
773 self.narrowphase_contacts as f64 / self.narrowphase_queries as f64
774 }
775 pub fn bandwidth_gb_s(&self) -> f64 {
779 if self.elapsed_secs <= 0.0_f64 {
780 return 0.0_f64;
781 }
782 self.bytes_transferred as f64 / self.elapsed_secs / 1.0e9_f64
783 }
784 pub fn pair_throughput(&self) -> f64 {
788 if self.elapsed_secs <= 0.0_f64 {
789 return 0.0_f64;
790 }
791 self.broadphase_pairs_tested as f64 / self.elapsed_secs
792 }
793 pub fn accumulate(&mut self, other: &CollisionKernelStats) {
795 self.bytes_transferred += other.bytes_transferred;
796 self.broadphase_pairs_tested += other.broadphase_pairs_tested;
797 self.broadphase_hits += other.broadphase_hits;
798 self.narrowphase_queries += other.narrowphase_queries;
799 self.narrowphase_contacts += other.narrowphase_contacts;
800 self.elapsed_secs += other.elapsed_secs;
801 }
802}
803#[derive(Debug, Clone)]
805pub struct ContactCacheEntry {
806 pub key: (u32, u32),
808 pub accumulated_normal: f32,
810 pub accumulated_tangent_1: f32,
812 pub accumulated_tangent_2: f32,
814 pub contact_point: [f32; 3],
816 pub age_frames: u32,
818}
819impl ContactCacheEntry {
820 pub fn new(pair: &CollisionPair, contact: &ContactResult) -> Self {
822 let (a, b) = if pair.a <= pair.b {
823 (pair.a, pair.b)
824 } else {
825 (pair.b, pair.a)
826 };
827 Self {
828 key: (a, b),
829 accumulated_normal: 0.0_f32,
830 accumulated_tangent_1: 0.0_f32,
831 accumulated_tangent_2: 0.0_f32,
832 contact_point: contact.contact_point,
833 age_frames: 0,
834 }
835 }
836 pub fn apply_warm_start(&self, pt: &mut ManifoldPoint) {
838 pt.warm_impulse_normal = self.accumulated_normal;
839 pt.warm_impulse_t1 = self.accumulated_tangent_1;
840 pt.warm_impulse_t2 = self.accumulated_tangent_2;
841 }
842 pub fn update(&mut self, normal_impulse: f32, t1: f32, t2: f32) {
844 self.accumulated_normal = normal_impulse;
845 self.accumulated_tangent_1 = t1;
846 self.accumulated_tangent_2 = t2;
847 self.age_frames = 0;
848 }
849}
850#[derive(Debug, Default)]
856pub struct GpuAabbTree {
857 pub nodes: Vec<BvhNodeGpu>,
859 pub root: usize,
861 pub num_primitives: usize,
863 pub morton_codes: Vec<u32>,
865}
866impl GpuAabbTree {
867 pub fn new() -> Self {
869 Self::default()
870 }
871 pub fn build(&mut self, aabbs: &[AabbGpu]) {
876 self.nodes.clear();
877 self.morton_codes.clear();
878 self.num_primitives = aabbs.len();
879 if aabbs.is_empty() {
880 self.root = 0;
881 return;
882 }
883 let mut scene_min = [f32::INFINITY; 3];
884 let mut scene_max = [f32::NEG_INFINITY; 3];
885 for a in aabbs {
886 for d in 0..3 {
887 scene_min[d] = scene_min[d].min(a.min[d]);
888 scene_max[d] = scene_max[d].max(a.max[d]);
889 }
890 }
891 let extent = [
892 (scene_max[0] - scene_min[0]).max(1.0e-6_f32),
893 (scene_max[1] - scene_min[1]).max(1.0e-6_f32),
894 (scene_max[2] - scene_min[2]).max(1.0e-6_f32),
895 ];
896 let mut order: Vec<(u32, usize)> = aabbs
897 .iter()
898 .enumerate()
899 .map(|(i, a)| {
900 let c = a.centre();
901 let nx = (c[0] - scene_min[0]) / extent[0];
902 let ny = (c[1] - scene_min[1]) / extent[1];
903 let nz = (c[2] - scene_min[2]) / extent[2];
904 let code = morton_code(nx, ny, nz);
905 self.morton_codes.push(code);
906 (code, i)
907 })
908 .collect();
909 order.sort_by_key(|&(code, _)| code);
910 for &(_, prim_idx) in &order {
911 self.nodes
912 .push(BvhNodeGpu::leaf(aabbs[prim_idx].clone(), prim_idx as u32));
913 }
914 let mut level: Vec<usize> = (0..aabbs.len()).collect();
915 while level.len() > 1 {
916 let mut next = Vec::with_capacity(level.len().div_ceil(2));
917 let mut i = 0;
918 while i < level.len() {
919 if i + 1 < level.len() {
920 let l = level[i];
921 let r = level[i + 1];
922 let merged = self.nodes[l].aabb.merge(&self.nodes[r].aabb);
923 let parent = BvhNodeGpu::internal(merged, l as u32, r as u32);
924 let idx = self.nodes.len();
925 self.nodes.push(parent);
926 next.push(idx);
927 i += 2;
928 } else {
929 next.push(level[i]);
930 i += 1;
931 }
932 }
933 level = next;
934 }
935 self.root = level[0];
936 }
937 pub fn query(&self, query: &AabbGpu) -> Vec<u32> {
939 if self.nodes.is_empty() {
940 return Vec::new();
941 }
942 let mut result = Vec::new();
943 let mut stack = vec![self.root];
944 while let Some(idx) = stack.pop() {
945 let node = &self.nodes[idx];
946 if !node.aabb.overlaps(query) {
947 continue;
948 }
949 if node.is_leaf {
950 result.push(node.primitive_index);
951 } else {
952 stack.push(node.left_child as usize);
953 stack.push(node.right_child as usize);
954 }
955 }
956 result
957 }
958 pub fn depth(&self) -> usize {
960 if self.nodes.is_empty() {
961 return 0;
962 }
963 self.depth_from(self.root)
964 }
965 fn depth_from(&self, idx: usize) -> usize {
966 let node = &self.nodes[idx];
967 if node.is_leaf {
968 1
969 } else {
970 let ld = self.depth_from(node.left_child as usize);
971 let rd = self.depth_from(node.right_child as usize);
972 1 + ld.max(rd)
973 }
974 }
975 pub fn leaf_count(&self) -> usize {
977 self.nodes.iter().filter(|n| n.is_leaf).count()
978 }
979 pub fn sah_cost(&self) -> f32 {
983 self.nodes
984 .iter()
985 .filter(|n| !n.is_leaf)
986 .map(|n| n.aabb.surface_area())
987 .sum()
988 }
989}
990#[derive(Debug, Default)]
995pub struct GpuNarrowphase {
996 pub max_iterations: usize,
998 pub stats: CollisionKernelStats,
1000}
1001impl GpuNarrowphase {
1002 pub fn new(max_iterations: usize) -> Self {
1004 Self {
1005 max_iterations: max_iterations.max(4),
1006 stats: CollisionKernelStats::new(),
1007 }
1008 }
1009 pub fn gjk_distance(&self, a: &AabbGpu, b: &AabbGpu) -> GjkResult {
1011 let ca = a.centre();
1012 let cb = b.centre();
1013 let mut d = sub3f(ca, cb);
1014 if len_sq3f(d) < 1.0e-9_f32 {
1015 d = [1.0_f32, 0.0_f32, 0.0_f32];
1016 }
1017 let s0 = minkowski_support(a, b, d);
1018 let mut simplex: Vec<SupportPoint> = vec![s0];
1019 d = [-s0.v[0], -s0.v[1], -s0.v[2]];
1020 for _ in 0..self.max_iterations {
1021 if len_sq3f(d) < 1.0e-12_f32 {
1022 return GjkResult {
1023 intersecting: true,
1024 distance: 0.0_f32,
1025 closest_a: ca,
1026 closest_b: cb,
1027 axis: norm3f(sub3f(ca, cb)),
1028 };
1029 }
1030 let sup = minkowski_support(a, b, d);
1031 if dot3f(sup.v, d) < 0.0_f32 {
1032 let dist = len3f(d);
1033 let axis = norm3f(d);
1034 return GjkResult {
1035 intersecting: false,
1036 distance: dist,
1037 closest_a: aabb_support(a, axis),
1038 closest_b: aabb_support(b, [-axis[0], -axis[1], -axis[2]]),
1039 axis,
1040 };
1041 }
1042 simplex.push(sup);
1043 let (inter, new_d) = update_simplex(&mut simplex);
1044 if inter {
1045 return GjkResult {
1046 intersecting: true,
1047 distance: 0.0_f32,
1048 closest_a: ca,
1049 closest_b: cb,
1050 axis: norm3f(sub3f(ca, cb)),
1051 };
1052 }
1053 d = new_d;
1054 }
1055 GjkResult {
1056 intersecting: true,
1057 distance: 0.0_f32,
1058 closest_a: ca,
1059 closest_b: cb,
1060 axis: norm3f(sub3f(ca, cb)),
1061 }
1062 }
1063 pub fn sat_depth(&self, a: &AabbGpu, b: &AabbGpu) -> Option<(f32, [f32; 3])> {
1067 let mut min_depth = f32::INFINITY;
1068 let mut min_axis = [1.0_f32, 0.0_f32, 0.0_f32];
1069 for d in 0..3usize {
1070 let mut axis = [0.0_f32; 3];
1071 axis[d] = 1.0_f32;
1072 let a_min = a.min[d];
1073 let a_max = a.max[d];
1074 let b_min = b.min[d];
1075 let b_max = b.max[d];
1076 let overlap = a_max.min(b_max) - a_min.max(b_min);
1077 if overlap <= 0.0_f32 {
1078 return None;
1079 }
1080 if overlap < min_depth {
1081 min_depth = overlap;
1082 let sign = if (a.min[d] + a.max[d]) > (b.min[d] + b.max[d]) {
1083 1.0_f32
1084 } else {
1085 -1.0_f32
1086 };
1087 axis[d] = sign;
1088 min_axis = axis;
1089 }
1090 }
1091 Some((min_depth, min_axis))
1092 }
1093 pub fn dispatch(&mut self, pairs: &[CollisionPair], aabbs: &[AabbGpu]) -> Vec<ContactResult> {
1097 let mut contacts = Vec::new();
1098 self.stats.narrowphase_queries += pairs.len() as u64;
1099 for pair in pairs {
1100 let ai = pair.a as usize;
1101 let bi = pair.b as usize;
1102 if ai >= aabbs.len() || bi >= aabbs.len() {
1103 continue;
1104 }
1105 let gjk = self.gjk_distance(&aabbs[ai], &aabbs[bi]);
1106 if gjk.intersecting {
1107 let (depth, normal) = self
1108 .sat_depth(&aabbs[ai], &aabbs[bi])
1109 .unwrap_or((0.0_f32, gjk.axis));
1110 let ca = aabbs[ai].centre();
1111 let cb = aabbs[bi].centre();
1112 let contact_point = [
1113 (ca[0] + cb[0]) * 0.5_f32,
1114 (ca[1] + cb[1]) * 0.5_f32,
1115 (ca[2] + cb[2]) * 0.5_f32,
1116 ];
1117 contacts.push(ContactResult {
1118 prim_a: pair.a,
1119 prim_b: pair.b,
1120 contact_point,
1121 normal,
1122 depth,
1123 });
1124 self.stats.narrowphase_contacts += 1;
1125 }
1126 }
1127 contacts
1128 }
1129}
1130#[derive(Debug, Default)]
1135pub struct GpuContactCache {
1136 pub entries: Vec<ContactCacheEntry>,
1138 pub max_age_frames: u32,
1140}
1141impl GpuContactCache {
1142 pub fn new(max_age_frames: u32) -> Self {
1144 Self {
1145 entries: Vec::new(),
1146 max_age_frames: max_age_frames.max(1),
1147 }
1148 }
1149 pub fn find(&self, a: u32, b: u32) -> Option<&ContactCacheEntry> {
1151 let key = if a <= b { (a, b) } else { (b, a) };
1152 self.entries.iter().find(|e| e.key == key)
1153 }
1154 pub fn find_mut(&mut self, a: u32, b: u32) -> Option<&mut ContactCacheEntry> {
1156 let key = if a <= b { (a, b) } else { (b, a) };
1157 self.entries.iter_mut().find(|e| e.key == key)
1158 }
1159 pub fn insert(&mut self, pair: &CollisionPair, contact: &ContactResult) {
1161 let a = pair.a;
1162 let b = pair.b;
1163 let key = if a <= b { (a, b) } else { (b, a) };
1164 if let Some(e) = self.entries.iter_mut().find(|e| e.key == key) {
1165 e.contact_point = contact.contact_point;
1166 e.age_frames = 0;
1167 } else {
1168 self.entries.push(ContactCacheEntry::new(pair, contact));
1169 }
1170 }
1171 pub fn advance_frame(&mut self) {
1173 for e in self.entries.iter_mut() {
1174 e.age_frames += 1;
1175 }
1176 let max_age = self.max_age_frames;
1177 self.entries.retain(|e| e.age_frames <= max_age);
1178 }
1179 pub fn len(&self) -> usize {
1181 self.entries.len()
1182 }
1183 pub fn is_empty(&self) -> bool {
1185 self.entries.is_empty()
1186 }
1187 pub fn clear(&mut self) {
1189 self.entries.clear();
1190 }
1191}
1192#[derive(Debug, Default)]
1198pub struct GpuBroadphase {
1199 pub sweep_axis: usize,
1201 pub margin: f32,
1203 pub stats: CollisionKernelStats,
1205}
1206impl GpuBroadphase {
1207 pub fn new(sweep_axis: usize, margin: f32) -> Self {
1209 Self {
1210 sweep_axis: sweep_axis % 3,
1211 margin,
1212 stats: CollisionKernelStats::new(),
1213 }
1214 }
1215 pub fn choose_best_axis(aabbs: &[AabbGpu]) -> usize {
1219 if aabbs.is_empty() {
1220 return 0;
1221 }
1222 let mut spread = [0.0_f32; 3];
1223 for (d, s) in spread.iter_mut().enumerate() {
1224 let lo = aabbs.iter().map(|a| a.min[d]).fold(f32::INFINITY, f32::min);
1225 let hi = aabbs
1226 .iter()
1227 .map(|a| a.max[d])
1228 .fold(f32::NEG_INFINITY, f32::max);
1229 *s = hi - lo;
1230 }
1231 if spread[0] >= spread[1] && spread[0] >= spread[2] {
1232 0
1233 } else if spread[1] >= spread[2] {
1234 1
1235 } else {
1236 2
1237 }
1238 }
1239 pub fn dispatch(&mut self, aabbs: &[AabbGpu]) -> Vec<CollisionPair> {
1243 let axis = self.sweep_axis;
1244 let _n = aabbs.len();
1245 let mut pairs = Vec::new();
1246 let mut entries: Vec<SapEntry> = aabbs
1247 .iter()
1248 .enumerate()
1249 .map(|(i, a)| SapEntry {
1250 lo: a.min[axis] - self.margin,
1251 hi: a.max[axis] + self.margin,
1252 idx: i as u32,
1253 })
1254 .collect();
1255 entries.sort_by(|a, b| a.lo.partial_cmp(&b.lo).unwrap_or(std::cmp::Ordering::Equal));
1256 let mut tests = 0u64;
1257 let mut hits = 0u64;
1258 for (i, ei) in entries.iter().enumerate() {
1259 for ej in entries.iter().skip(i + 1) {
1260 if ej.lo > ei.hi {
1261 break;
1262 }
1263 tests += 1;
1264 let pi = ei.idx as usize;
1265 let pj = ej.idx as usize;
1266 let expanded_i = aabbs[pi].expanded(self.margin);
1267 if expanded_i.overlaps(&aabbs[pj]) {
1268 hits += 1;
1269 pairs.push(CollisionPair::new(ei.idx, ej.idx));
1270 }
1271 }
1272 }
1273 self.stats.broadphase_pairs_tested += tests;
1274 self.stats.broadphase_hits += hits;
1275 self.stats.bytes_transferred += std::mem::size_of_val(aabbs) as u64;
1276 pairs
1277 }
1278}