Skip to main content

oxiphysics_collision/parallel_collision/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::HashMap;
6use std::sync::atomic::{AtomicUsize, Ordering};
7use std::sync::{Arc, Mutex, RwLock};
8
9use super::functions::{
10    NodeIdx, aabb_all_pairs_overlap, add3, max3, min3, par_box_box, par_sphere_box,
11    par_sphere_sphere, scale3, sub3,
12};
13
14/// Thread-safe union-find for parallel island detection.
15///
16/// Uses path-compression and union-by-rank. Multiple threads may call
17/// `find` concurrently; `union` takes an exclusive lock.
18#[derive(Debug)]
19pub struct ParallelUnionFind {
20    pub(super) parent: Mutex<Vec<usize>>,
21    pub(super) rank: Mutex<Vec<usize>>,
22    pub(super) n: usize,
23}
24impl ParallelUnionFind {
25    /// Create a new union-find for `n` elements.
26    pub fn new(n: usize) -> Self {
27        Self {
28            parent: Mutex::new((0..n).collect()),
29            rank: Mutex::new(vec![0; n]),
30            n,
31        }
32    }
33    /// Find the root of the set containing `x` with path compression.
34    pub fn find(&self, x: usize) -> usize {
35        let mut parent = self.parent.lock().unwrap_or_else(|e| e.into_inner());
36        let mut root = x;
37        while parent[root] != root {
38            root = parent[root];
39        }
40        let mut node = x;
41        while parent[node] != root {
42            let next = parent[node];
43            parent[node] = root;
44            node = next;
45        }
46        root
47    }
48    /// Union the sets containing `x` and `y`.
49    pub fn union(&self, x: usize, y: usize) {
50        let rx = self.find(x);
51        let ry = self.find(y);
52        if rx == ry {
53            return;
54        }
55        let mut parent = self.parent.lock().unwrap_or_else(|e| e.into_inner());
56        let mut rank = self.rank.lock().unwrap_or_else(|e| e.into_inner());
57        if rank[rx] < rank[ry] {
58            parent[rx] = ry;
59        } else if rank[rx] > rank[ry] {
60            parent[ry] = rx;
61        } else {
62            parent[ry] = rx;
63            rank[rx] += 1;
64        }
65    }
66    /// Collect all islands as groups of element indices.
67    pub fn islands(&self) -> Vec<Vec<usize>> {
68        let mut map: HashMap<usize, Vec<usize>> = HashMap::new();
69        for i in 0..self.n {
70            let root = self.find(i);
71            map.entry(root).or_default().push(i);
72        }
73        map.into_values().collect()
74    }
75    /// Number of distinct connected components.
76    pub fn num_components(&self) -> usize {
77        let mut roots = std::collections::HashSet::new();
78        for i in 0..self.n {
79            roots.insert(self.find(i));
80        }
81        roots.len()
82    }
83}
84/// Axis-aligned bounding box using plain `[f64; 3]` components.
85///
86/// Designed for SIMD-friendly packing in structure-of-arrays layouts.
87#[derive(Debug, Clone, Copy, PartialEq)]
88pub struct ParAabb {
89    /// Minimum corner `[x_min, y_min, z_min]`.
90    pub min: [f64; 3],
91    /// Maximum corner `[x_max, y_max, z_max]`.
92    pub max: [f64; 3],
93}
94impl ParAabb {
95    /// Create a new AABB from min/max corners.
96    pub fn new(min: [f64; 3], max: [f64; 3]) -> Self {
97        Self { min, max }
98    }
99    /// Create an AABB from a center point and half-extents.
100    pub fn from_center_half_extents(center: [f64; 3], half: [f64; 3]) -> Self {
101        Self {
102            min: sub3(center, half),
103            max: add3(center, half),
104        }
105    }
106    /// Merge two AABBs returning the smallest enclosing AABB.
107    pub fn merge(&self, other: &Self) -> Self {
108        Self {
109            min: min3(self.min, other.min),
110            max: max3(self.max, other.max),
111        }
112    }
113    /// Return the center of the AABB.
114    pub fn center(&self) -> [f64; 3] {
115        scale3(add3(self.min, self.max), 0.5)
116    }
117    /// Return the half-extents of the AABB.
118    pub fn half_extents(&self) -> [f64; 3] {
119        scale3(sub3(self.max, self.min), 0.5)
120    }
121    /// Surface area of the AABB (for SAH heuristic).
122    pub fn surface_area(&self) -> f64 {
123        let d = sub3(self.max, self.min);
124        2.0 * (d[0] * d[1] + d[1] * d[2] + d[2] * d[0])
125    }
126    /// Test whether two AABBs overlap.
127    ///
128    /// Vectorized-friendly: checks all axes without branching.
129    #[inline(always)]
130    pub fn overlaps(&self, other: &Self) -> bool {
131        self.min[0] <= other.max[0]
132            && self.max[0] >= other.min[0]
133            && self.min[1] <= other.max[1]
134            && self.max[1] >= other.min[1]
135            && self.min[2] <= other.max[2]
136            && self.max[2] >= other.min[2]
137    }
138    /// Expand the AABB by `margin` on all sides.
139    pub fn expanded(&self, margin: f64) -> Self {
140        Self {
141            min: [
142                self.min[0] - margin,
143                self.min[1] - margin,
144                self.min[2] - margin,
145            ],
146            max: [
147                self.max[0] + margin,
148                self.max[1] + margin,
149                self.max[2] + margin,
150            ],
151        }
152    }
153    /// Check if this AABB contains a point.
154    pub fn contains_point(&self, p: [f64; 3]) -> bool {
155        p[0] >= self.min[0]
156            && p[0] <= self.max[0]
157            && p[1] >= self.min[1]
158            && p[1] <= self.max[1]
159            && p[2] >= self.min[2]
160            && p[2] <= self.max[2]
161    }
162}
163/// Result of a CCD query between two swept spheres.
164#[derive(Debug, Clone, Copy)]
165pub struct CcdHit {
166    /// Time of impact in `[0, 1]`.
167    pub toi: f64,
168    /// Index of body A.
169    pub body_a: u32,
170    /// Index of body B.
171    pub body_b: u32,
172    /// Contact normal at the time of impact.
173    pub normal: [f64; 3],
174}
175/// Descriptor for a GPU-mapped AABB buffer.
176///
177/// Holds layout information for a flat byte buffer that can be directly
178/// uploaded to a compute shader. All fields are `u32` for alignment.
179#[derive(Debug, Clone, Copy)]
180pub struct GpuAabbBufferDesc {
181    /// Byte offset of the min_x array.
182    pub offset_min_x: u32,
183    /// Byte offset of the min_y array.
184    pub offset_min_y: u32,
185    /// Byte offset of the min_z array.
186    pub offset_min_z: u32,
187    /// Byte offset of the max_x array.
188    pub offset_max_x: u32,
189    /// Byte offset of the max_y array.
190    pub offset_max_y: u32,
191    /// Byte offset of the max_z array.
192    pub offset_max_z: u32,
193    /// Number of AABBs in the buffer.
194    pub count: u32,
195    /// Stride between elements in bytes (typically 4 for f32).
196    pub stride: u32,
197}
198impl GpuAabbBufferDesc {
199    /// Compute descriptor for a tightly-packed f32 SoA buffer of `count` AABBs.
200    pub fn new_f32_soa(count: u32) -> Self {
201        let stride = 4u32;
202        let array_bytes = count * stride;
203        Self {
204            offset_min_x: 0,
205            offset_min_y: array_bytes,
206            offset_min_z: array_bytes * 2,
207            offset_max_x: array_bytes * 3,
208            offset_max_y: array_bytes * 4,
209            offset_max_z: array_bytes * 5,
210            count,
211            stride,
212        }
213    }
214    /// Total byte size of the GPU buffer.
215    pub fn total_bytes(&self) -> u32 {
216        self.count * self.stride * 6
217    }
218    /// Serialize a `SoaAabbBuffer` to a flat `Vec`u8` for GPU upload.
219    pub fn serialize_soa(buf: &SoaAabbBuffer) -> Vec<u8> {
220        let n = buf.len();
221        let mut out = Vec::with_capacity(n * 6 * 4);
222        let write_slice = |dst: &mut Vec<u8>, src: &[f32]| {
223            for &v in src {
224                dst.extend_from_slice(&v.to_le_bytes());
225            }
226        };
227        write_slice(&mut out, &buf.min_x);
228        write_slice(&mut out, &buf.min_y);
229        write_slice(&mut out, &buf.min_z);
230        write_slice(&mut out, &buf.max_x);
231        write_slice(&mut out, &buf.max_y);
232        write_slice(&mut out, &buf.max_z);
233        out
234    }
235}
236#[derive(Debug, Clone)]
237pub(super) struct AabbTreeInner {
238    pub(super) nodes: Vec<ParAabbNode>,
239    pub(super) root: NodeIdx,
240    pub(super) free_list: Vec<NodeIdx>,
241}
242impl AabbTreeInner {
243    fn new() -> Self {
244        Self {
245            nodes: Vec::new(),
246            root: u32::MAX,
247            free_list: Vec::new(),
248        }
249    }
250    fn alloc_node(&mut self) -> NodeIdx {
251        if let Some(idx) = self.free_list.pop() {
252            idx
253        } else {
254            let idx = self.nodes.len() as NodeIdx;
255            self.nodes.push(ParAabbNode {
256                aabb: ParAabb::default(),
257                left: u32::MAX,
258                right: u32::MAX,
259                handle: u32::MAX,
260                height: 0,
261            });
262            idx
263        }
264    }
265    /// Insert a leaf with the given AABB and body handle.
266    fn insert(&mut self, aabb: ParAabb, handle: u32) -> NodeIdx {
267        let leaf = self.alloc_node();
268        self.nodes[leaf as usize].aabb = aabb;
269        self.nodes[leaf as usize].handle = handle;
270        self.nodes[leaf as usize].left = u32::MAX;
271        self.nodes[leaf as usize].right = u32::MAX;
272        self.nodes[leaf as usize].height = 0;
273        if self.root == u32::MAX {
274            self.root = leaf;
275            return leaf;
276        }
277        let mut best = self.root;
278        loop {
279            let node = &self.nodes[best as usize];
280            if node.is_leaf() {
281                break;
282            }
283            let merged_area = node.aabb.merge(&aabb).surface_area();
284            let left = node.left;
285            let right = node.right;
286            let la = self.nodes[left as usize].aabb.merge(&aabb).surface_area();
287            let ra = self.nodes[right as usize].aabb.merge(&aabb).surface_area();
288            if merged_area < la.min(ra) {
289                break;
290            }
291            best = if la < ra { left } else { right };
292        }
293        let old_parent = self.alloc_node();
294        let sib_aabb = self.nodes[best as usize].aabb;
295        self.nodes[old_parent as usize].aabb = sib_aabb.merge(&aabb);
296        self.nodes[old_parent as usize].left = best;
297        self.nodes[old_parent as usize].right = leaf;
298        let sib_h = self.nodes[best as usize].height;
299        self.nodes[old_parent as usize].height = sib_h + 1;
300        self.nodes[old_parent as usize].handle = u32::MAX;
301        if self.root == best {
302            self.root = old_parent;
303        }
304        leaf
305    }
306    /// Query all leaf handles whose AABB overlaps `query`.
307    fn query_overlaps(&self, query: &ParAabb) -> Vec<u32> {
308        let mut result = Vec::new();
309        if self.root == u32::MAX {
310            return result;
311        }
312        let mut stack = vec![self.root];
313        while let Some(idx) = stack.pop() {
314            let node = &self.nodes[idx as usize];
315            if !node.aabb.overlaps(query) {
316                continue;
317            }
318            if node.is_leaf() {
319                result.push(node.handle);
320            } else {
321                if node.left != u32::MAX {
322                    stack.push(node.left);
323                }
324                if node.right != u32::MAX {
325                    stack.push(node.right);
326                }
327            }
328        }
329        result
330    }
331}
332/// Discriminant for parallel narrowphase shape kinds.
333#[derive(Debug, Clone, Copy)]
334pub enum ParShapeKind {
335    /// Sphere shape.
336    Sphere(ParSphere),
337    /// Axis-aligned box shape.
338    Box(ParBox),
339}
340/// State of a body for CCD sweep.
341#[derive(Debug, Clone, Copy)]
342pub struct CcdBodyState {
343    /// Position at the start of the time step.
344    pub pos0: [f64; 3],
345    /// Position at the end of the time step.
346    pub pos1: [f64; 3],
347    /// Radius (for sphere-based CCD).
348    pub radius: f64,
349    /// Body index.
350    pub index: u32,
351}
352impl CcdBodyState {
353    /// Create a new CCD body state.
354    pub fn new(pos0: [f64; 3], pos1: [f64; 3], radius: f64, index: u32) -> Self {
355        Self {
356            pos0,
357            pos1,
358            radius,
359            index,
360        }
361    }
362    /// Compute the swept AABB for CCD broadphase culling.
363    pub fn swept_aabb(&self) -> ParAabb {
364        let margin = [self.radius; 3];
365        let mn0 = sub3(self.pos0, margin);
366        let mx0 = add3(self.pos0, margin);
367        let mn1 = sub3(self.pos1, margin);
368        let mx1 = add3(self.pos1, margin);
369        ParAabb {
370            min: min3(mn0, mn1),
371            max: max3(mx0, mx1),
372        }
373    }
374}
375/// GPU-uploadable BVH tree serialized as a flat array of `GpuBvhNode`.
376#[derive(Debug, Clone)]
377pub struct GpuBvhBuffer {
378    /// Flat node array.
379    pub nodes: Vec<GpuBvhNode>,
380}
381impl GpuBvhBuffer {
382    /// Create an empty GPU BVH buffer.
383    pub fn new() -> Self {
384        Self { nodes: Vec::new() }
385    }
386    /// Build a simple linear BVH from a list of AABBs and handles.
387    ///
388    /// Uses median-split on the longest axis (SAH is deferred to GPU).
389    pub fn build(aabbs: &[(ParAabb, u32)]) -> Self {
390        let mut buf = Self::new();
391        if aabbs.is_empty() {
392            return buf;
393        }
394        Self::build_recursive(&mut buf.nodes, aabbs);
395        buf
396    }
397    fn build_recursive(nodes: &mut Vec<GpuBvhNode>, aabbs: &[(ParAabb, u32)]) -> u32 {
398        if aabbs.len() == 1 {
399            let (aabb, handle) = &aabbs[0];
400            let node = GpuBvhNode::leaf(
401                [aabb.min[0] as f32, aabb.min[1] as f32, aabb.min[2] as f32],
402                [aabb.max[0] as f32, aabb.max[1] as f32, aabb.max[2] as f32],
403                *handle,
404            );
405            let idx = nodes.len() as u32;
406            nodes.push(node);
407            return idx;
408        }
409        let mut combined = aabbs[0].0;
410        for (a, _) in aabbs.iter().skip(1) {
411            combined = combined.merge(a);
412        }
413        let extent = sub3(combined.max, combined.min);
414        let axis = if extent[0] >= extent[1] && extent[0] >= extent[2] {
415            0
416        } else if extent[1] >= extent[2] {
417            1
418        } else {
419            2
420        };
421        let mut sorted: Vec<_> = aabbs.to_vec();
422        sorted.sort_by(|(a, _), (b, _)| {
423            let ca = (a.min[axis] + a.max[axis]) * 0.5;
424            let cb = (b.min[axis] + b.max[axis]) * 0.5;
425            ca.partial_cmp(&cb).unwrap_or(std::cmp::Ordering::Equal)
426        });
427        let mid = sorted.len() / 2;
428        let my_idx = nodes.len() as u32;
429        nodes.push(GpuBvhNode::internal([0.0; 3], [0.0; 3], 0, 0));
430        let left = Self::build_recursive(nodes, &sorted[..mid]);
431        let right = Self::build_recursive(nodes, &sorted[mid..]);
432        nodes[my_idx as usize] = GpuBvhNode::internal(
433            [
434                combined.min[0] as f32,
435                combined.min[1] as f32,
436                combined.min[2] as f32,
437            ],
438            [
439                combined.max[0] as f32,
440                combined.max[1] as f32,
441                combined.max[2] as f32,
442            ],
443            left,
444            right,
445        );
446        my_idx
447    }
448    /// Number of nodes in the BVH.
449    pub fn node_count(&self) -> usize {
450        self.nodes.len()
451    }
452    /// Serialize to bytes for GPU upload.
453    pub fn to_bytes(&self) -> Vec<u8> {
454        let mut out = Vec::with_capacity(self.nodes.len() * 32);
455        for node in &self.nodes {
456            for &v in &node.min {
457                out.extend_from_slice(&v.to_le_bytes());
458            }
459            out.extend_from_slice(&node.left_or_leaf.to_le_bytes());
460            for &v in &node.max {
461                out.extend_from_slice(&v.to_le_bytes());
462            }
463            out.extend_from_slice(&node.right_or_handle.to_le_bytes());
464        }
465        out
466    }
467}
468/// A contact point produced by narrowphase.
469#[derive(Debug, Clone, Copy)]
470pub struct ParContact {
471    /// Index of body A.
472    pub body_a: u32,
473    /// Index of body B.
474    pub body_b: u32,
475    /// Contact normal pointing from B toward A.
476    pub normal: [f64; 3],
477    /// Penetration depth (positive = overlap).
478    pub depth: f64,
479    /// Contact point in world space.
480    pub point: [f64; 3],
481}
482/// Frame-level parallel collision result.
483#[derive(Debug, Clone)]
484pub struct ParCollisionResult {
485    /// All reduced contacts.
486    pub contacts: Vec<ParContact>,
487    /// Simulation islands (groups of touching bodies).
488    pub islands: Vec<Vec<usize>>,
489    /// CCD hits this frame (empty if CCD disabled).
490    pub ccd_hits: Vec<CcdHit>,
491}
492/// A box shape (OBB) for narrowphase tests, represented by center + half-extents.
493///
494/// For simplicity the box is axis-aligned (AABB) in this narrowphase.
495#[derive(Debug, Clone, Copy)]
496pub struct ParBox {
497    /// Center of the box in world space.
498    pub center: [f64; 3],
499    /// Half-extents along each axis.
500    pub half_extents: [f64; 3],
501}
502/// A sphere shape for narrowphase tests.
503#[derive(Debug, Clone, Copy)]
504pub struct ParSphere {
505    /// Center of the sphere in world space.
506    pub center: [f64; 3],
507    /// Radius of the sphere.
508    pub radius: f64,
509}
510/// A node in the parallel AABB tree.
511#[derive(Debug, Clone)]
512pub struct ParAabbNode {
513    /// Tight AABB enclosing this node's subtree.
514    pub aabb: ParAabb,
515    /// Index of the left child (`NodeIdx::MAX` = leaf).
516    pub left: NodeIdx,
517    /// Index of the right child (`NodeIdx::MAX` = leaf).
518    pub right: NodeIdx,
519    /// Body handle if this is a leaf node.
520    pub handle: u32,
521    /// Height in the tree (0 = leaf).
522    pub height: i32,
523}
524impl ParAabbNode {
525    /// Returns `true` if this is a leaf node.
526    pub fn is_leaf(&self) -> bool {
527        self.left == u32::MAX
528    }
529}
530/// Structure-of-Arrays layout for AABB data.
531///
532/// Stores min/max for each axis in separate arrays, enabling SIMD-wide
533/// loads. Suitable for vectorized broadphase queries.
534#[derive(Debug, Clone)]
535pub struct SoaAabbBuffer {
536    /// x-component of min corners.
537    pub min_x: Vec<f32>,
538    /// y-component of min corners.
539    pub min_y: Vec<f32>,
540    /// z-component of min corners.
541    pub min_z: Vec<f32>,
542    /// x-component of max corners.
543    pub max_x: Vec<f32>,
544    /// y-component of max corners.
545    pub max_y: Vec<f32>,
546    /// z-component of max corners.
547    pub max_z: Vec<f32>,
548    /// Body handle associated with each AABB slot.
549    pub handles: Vec<u32>,
550}
551impl SoaAabbBuffer {
552    /// Create an empty SoA AABB buffer.
553    pub fn new() -> Self {
554        Self {
555            min_x: Vec::new(),
556            min_y: Vec::new(),
557            min_z: Vec::new(),
558            max_x: Vec::new(),
559            max_y: Vec::new(),
560            max_z: Vec::new(),
561            handles: Vec::new(),
562        }
563    }
564    /// Push an AABB into the buffer.
565    pub fn push(&mut self, aabb: &ParAabb, handle: u32) {
566        self.min_x.push(aabb.min[0] as f32);
567        self.min_y.push(aabb.min[1] as f32);
568        self.min_z.push(aabb.min[2] as f32);
569        self.max_x.push(aabb.max[0] as f32);
570        self.max_y.push(aabb.max[1] as f32);
571        self.max_z.push(aabb.max[2] as f32);
572        self.handles.push(handle);
573    }
574    /// Number of AABBs stored.
575    pub fn len(&self) -> usize {
576        self.handles.len()
577    }
578    /// Returns `true` if the buffer contains no AABBs.
579    pub fn is_empty(&self) -> bool {
580        self.handles.is_empty()
581    }
582    /// Query a single AABB against the buffer, returning matching indices.
583    pub fn query(&self, aabb: &ParAabb) -> Vec<usize> {
584        let qminx = aabb.min[0] as f32;
585        let qminy = aabb.min[1] as f32;
586        let qminz = aabb.min[2] as f32;
587        let qmaxx = aabb.max[0] as f32;
588        let qmaxy = aabb.max[1] as f32;
589        let qmaxz = aabb.max[2] as f32;
590        let mut result = Vec::new();
591        let n = self.handles.len();
592        for i in 0..n {
593            let hit = (qminx <= self.max_x[i])
594                & (qmaxx >= self.min_x[i])
595                & (qminy <= self.max_y[i])
596                & (qmaxy >= self.min_y[i])
597                & (qminz <= self.max_z[i])
598                & (qmaxz >= self.min_z[i]);
599            if hit {
600                result.push(i);
601            }
602        }
603        result
604    }
605    /// Rebuild the buffer from a slice of (aabb, handle) pairs.
606    pub fn rebuild(&mut self, entries: &[(ParAabb, u32)]) {
607        self.min_x.clear();
608        self.min_y.clear();
609        self.min_z.clear();
610        self.max_x.clear();
611        self.max_y.clear();
612        self.max_z.clear();
613        self.handles.clear();
614        for (aabb, handle) in entries {
615            self.push(aabb, *handle);
616        }
617    }
618}
619/// Work-stealing broadphase queue.
620///
621/// Divides candidate pair generation into chunks that can be stolen by
622/// worker threads when their local queues become empty.
623#[derive(Debug)]
624pub struct WorkStealingBroadphase {
625    pub(super) aabbs: Arc<Vec<ParAabb>>,
626    pub(super) chunk_size: usize,
627    pub(super) pending: Mutex<Vec<BroadphaseWorkItem>>,
628}
629impl WorkStealingBroadphase {
630    /// Create a new work-stealing broadphase for the given set of AABBs.
631    pub fn new(aabbs: Vec<ParAabb>, chunk_size: usize) -> Self {
632        let pairs = aabb_all_pairs_overlap(&aabbs);
633        let pending = pairs
634            .into_iter()
635            .map(|(a, b)| BroadphaseWorkItem {
636                body_a: a,
637                body_b: b,
638            })
639            .collect();
640        Self {
641            aabbs: Arc::new(aabbs),
642            chunk_size,
643            pending: Mutex::new(pending),
644        }
645    }
646    /// Steal up to `chunk_size` work items.
647    pub fn steal(&self) -> Vec<BroadphaseWorkItem> {
648        let mut q = self.pending.lock().unwrap_or_else(|e| e.into_inner());
649        let n = self.chunk_size.min(q.len());
650        q.drain(..n).collect()
651    }
652    /// Returns `true` when no more work items remain.
653    pub fn is_done(&self) -> bool {
654        self.pending
655            .lock()
656            .unwrap_or_else(|e| e.into_inner())
657            .is_empty()
658    }
659    /// Total number of candidate pairs remaining.
660    pub fn remaining(&self) -> usize {
661        self.pending.lock().unwrap_or_else(|e| e.into_inner()).len()
662    }
663    /// Access the underlying AABB slice.
664    pub fn aabbs(&self) -> &[ParAabb] {
665        &self.aabbs
666    }
667}
668/// Thread-safe AABB tree for parallel broadphase collision detection.
669///
670/// The tree is built once per frame and queried from multiple threads
671/// simultaneously via `Arc<RwLock<>>`. Insertions/removals lock the writer,
672/// queries only need a read lock.
673#[derive(Debug)]
674pub struct ParallelAabbTree {
675    pub(super) inner: RwLock<AabbTreeInner>,
676}
677impl ParallelAabbTree {
678    /// Create an empty parallel AABB tree.
679    pub fn new() -> Self {
680        Self {
681            inner: RwLock::new(AabbTreeInner::new()),
682        }
683    }
684    /// Insert a body into the tree.
685    pub fn insert(&self, aabb: ParAabb, handle: u32) -> NodeIdx {
686        self.inner
687            .write()
688            .unwrap_or_else(|e| e.into_inner())
689            .insert(aabb, handle)
690    }
691    /// Query overlapping bodies for a given AABB.
692    ///
693    /// Multiple threads can call this simultaneously.
694    pub fn query(&self, aabb: &ParAabb) -> Vec<u32> {
695        self.inner
696            .read()
697            .unwrap_or_else(|e| e.into_inner())
698            .query_overlaps(aabb)
699    }
700    /// Number of nodes currently allocated.
701    pub fn node_count(&self) -> usize {
702        self.inner
703            .read()
704            .unwrap_or_else(|e| e.into_inner())
705            .nodes
706            .len()
707    }
708}
709/// A batch of shape pairs to be processed in parallel.
710#[derive(Debug)]
711pub struct NarrowphaseBatch {
712    /// Pairs to process: each entry is (index_a, kind_a, index_b, kind_b).
713    pub pairs: Vec<(u32, ParShapeKind, u32, ParShapeKind)>,
714}
715impl NarrowphaseBatch {
716    /// Create an empty batch.
717    pub fn new() -> Self {
718        Self { pairs: Vec::new() }
719    }
720    /// Add a pair to the batch.
721    pub fn push(&mut self, a_idx: u32, a: ParShapeKind, b_idx: u32, b: ParShapeKind) {
722        self.pairs.push((a_idx, a, b_idx, b));
723    }
724    /// Process all pairs sequentially, returning all contacts.
725    ///
726    /// In a real engine this loop body would be dispatched across threads.
727    pub fn process(&self) -> Vec<ParContact> {
728        let mut contacts = Vec::new();
729        for &(a_idx, ref a_shape, b_idx, ref b_shape) in &self.pairs {
730            match (a_shape, b_shape) {
731                (ParShapeKind::Sphere(sa), ParShapeKind::Sphere(sb)) => {
732                    if let Some(c) = par_sphere_sphere(a_idx, b_idx, sa, sb) {
733                        contacts.push(c);
734                    }
735                }
736                (ParShapeKind::Sphere(sa), ParShapeKind::Box(bb)) => {
737                    if let Some(c) = par_sphere_box(a_idx, b_idx, sa, bb) {
738                        contacts.push(c);
739                    }
740                }
741                (ParShapeKind::Box(ba), ParShapeKind::Sphere(sb)) => {
742                    if let Some(mut c) = par_sphere_box(b_idx, a_idx, sb, ba) {
743                        c.normal = scale3(c.normal, -1.0);
744                        c.body_a = a_idx;
745                        c.body_b = b_idx;
746                        contacts.push(c);
747                    }
748                }
749                (ParShapeKind::Box(_ba), ParShapeKind::Box(_bb)) => {
750                    if let (ParShapeKind::Box(ba_), ParShapeKind::Box(bb_)) = (a_shape, b_shape)
751                        && let Some(c) = par_box_box(a_idx, b_idx, ba_, bb_)
752                    {
753                        contacts.push(c);
754                    }
755                }
756            }
757        }
758        contacts
759    }
760}
761/// A GPU-mapped BVH node for use in compute shaders.
762///
763/// Packed as a 32-byte struct (two `\[f32; 3\]` + two `u32`).
764#[derive(Debug, Clone, Copy)]
765#[repr(C)]
766pub struct GpuBvhNode {
767    /// AABB min corner (f32 for bandwidth efficiency).
768    pub min: [f32; 3],
769    /// Left child index, or `u32::MAX` for leaf.
770    pub left_or_leaf: u32,
771    /// AABB max corner.
772    pub max: [f32; 3],
773    /// Right child index, or body handle for leaf.
774    pub right_or_handle: u32,
775}
776impl GpuBvhNode {
777    /// Create a leaf node.
778    pub fn leaf(min: [f32; 3], max: [f32; 3], handle: u32) -> Self {
779        Self {
780            min,
781            max,
782            left_or_leaf: u32::MAX,
783            right_or_handle: handle,
784        }
785    }
786    /// Create an internal node.
787    pub fn internal(min: [f32; 3], max: [f32; 3], left: u32, right: u32) -> Self {
788        Self {
789            min,
790            max,
791            left_or_leaf: left,
792            right_or_handle: right,
793        }
794    }
795    /// Returns `true` if this is a leaf node.
796    pub fn is_leaf(&self) -> bool {
797        self.left_or_leaf == u32::MAX
798    }
799}
800/// Lock-free contact buffer using atomic index for concurrent writes.
801///
802/// Multiple threads can write contacts via `push_contact` using an atomic
803/// slot counter. Each slot is protected by a `Mutex` for safe parallel access.
804/// The buffer must be pre-allocated to capacity before parallel writes begin.
805#[derive(Debug)]
806pub struct LockFreeContactBuffer {
807    pub(super) contacts: Vec<Mutex<Option<ParContact>>>,
808    pub(super) write_idx: AtomicUsize,
809    pub(super) capacity: usize,
810}
811impl LockFreeContactBuffer {
812    /// Create a new lock-free contact buffer with the given capacity.
813    pub fn new(capacity: usize) -> Self {
814        let mut contacts = Vec::with_capacity(capacity);
815        for _ in 0..capacity {
816            contacts.push(Mutex::new(None));
817        }
818        Self {
819            contacts,
820            write_idx: AtomicUsize::new(0),
821            capacity,
822        }
823    }
824    /// Push a contact, returning the slot index or `None` if full.
825    pub fn push_contact(&self, contact: ParContact) -> Option<usize> {
826        let idx = self.write_idx.fetch_add(1, Ordering::AcqRel);
827        if idx < self.capacity {
828            *self.contacts[idx].lock().unwrap_or_else(|e| e.into_inner()) = Some(contact);
829            Some(idx)
830        } else {
831            self.write_idx.fetch_sub(1, Ordering::AcqRel);
832            None
833        }
834    }
835    /// Number of contacts written so far.
836    pub fn len(&self) -> usize {
837        self.write_idx.load(Ordering::Acquire).min(self.capacity)
838    }
839    /// Returns `true` if no contacts have been written.
840    pub fn is_empty(&self) -> bool {
841        self.len() == 0
842    }
843    /// Reset the buffer for reuse each frame.
844    pub fn reset(&self) {
845        let old = self.write_idx.swap(0, Ordering::AcqRel);
846        for i in 0..old.min(self.capacity) {
847            *self.contacts[i].lock().unwrap_or_else(|e| e.into_inner()) = None;
848        }
849    }
850    /// Collect all written contacts into a `Vec`.
851    pub fn collect(&self) -> Vec<ParContact> {
852        let n = self.len();
853        let mut out = Vec::with_capacity(n);
854        for i in 0..n {
855            if let Some(c) = *self.contacts[i].lock().unwrap_or_else(|e| e.into_inner()) {
856                out.push(c);
857            }
858        }
859        out
860    }
861}
862/// Configuration for the parallel collision pipeline.
863#[derive(Debug, Clone)]
864pub struct ParCollisionConfig {
865    /// Maximum number of contacts per body pair after reduction.
866    pub max_contacts_per_pair: usize,
867    /// AABB expansion margin for predictive contacts (world units).
868    pub aabb_margin: f64,
869    /// Whether to run CCD sweep this frame.
870    pub enable_ccd: bool,
871}
872/// A work item for the work-stealing broadphase scheduler.
873#[derive(Debug, Clone)]
874pub struct BroadphaseWorkItem {
875    /// Index of body A.
876    pub body_a: usize,
877    /// Index of body B.
878    pub body_b: usize,
879}