diff --git a/collision-bench/src/main.rs b/collision-bench/src/main.rs index 4948b45..13b164b 100644 --- a/collision-bench/src/main.rs +++ b/collision-bench/src/main.rs @@ -10,6 +10,7 @@ use std::time::{Duration, Instant}; pub trait Positions: Sync { fn len(&self) -> usize; fn get(&self, index: u32) -> (f32, f32); + fn radius(&self, index: u32) -> f32; } /// Read-write access to entity kinematics. The collision system never @@ -35,11 +36,10 @@ pub struct SpatialGrid { inv_cell_size: f32, grid_dim: usize, total_cells: usize, - // Counting-sort buffers, reused across frames cell_counts: Vec, cell_offsets: Vec, - sorted: Vec, // entity indices sorted by cell - pairs: Vec<(u32, u32)>, // (cell_index, entity_index) staging + sorted: Vec, + pairs: Vec<(u32, u32)>, } impl SpatialGrid { @@ -57,11 +57,9 @@ impl SpatialGrid { } } - /// Rebuild the grid from scratch. Call once per tick. pub fn rebuild(&mut self, positions: &impl Positions) { let n = positions.len(); - // Ensure buffers are big enough if self.sorted.len() < n { self.sorted.resize(n, 0); } @@ -69,7 +67,6 @@ impl SpatialGrid { self.cell_counts.iter_mut().for_each(|c| *c = 0); self.pairs.clear(); - // Pass 1: bin every entity for i in 0..n { let (x, y) = positions.get(i as u32); if let Some(ci) = self.cell_of(x, y) { @@ -78,14 +75,12 @@ impl SpatialGrid { } } - // Pass 2: prefix sum let mut running = 0u32; for i in 0..self.total_cells { self.cell_offsets[i] = running; running += self.cell_counts[i]; } - // Pass 3: scatter into sorted order let mut cursors = self.cell_offsets.clone(); for &(ci, ei) in &self.pairs { let slot = cursors[ci as usize] as usize; @@ -110,12 +105,11 @@ impl SpatialGrid { // Collision detection — free function, parallel, read-only // ============================================================ -/// Detect all overlapping pairs and report them through the handler. +/// Detect all overlapping pairs using per-entity radii. /// Returns total number of collision pairs found. pub fn detect_collisions( grid: &SpatialGrid, positions: &impl Positions, - collision_dist_sq: f32, ) -> u64 { const NEIGHBORS: [(i32, i32); 4] = [(1, 0), (0, 1), (-1, 1), (1, 1)]; @@ -133,12 +127,17 @@ pub fn detect_collisions( // Intra-cell for i in 0..a_count { - let (px, py) = positions.get(grid.sorted[a_start + i]); + let ai = grid.sorted[a_start + i]; + let (px, py) = positions.get(ai); + let ra = positions.radius(ai); for j in (i + 1)..a_count { - let (qx, qy) = positions.get(grid.sorted[a_start + j]); + let bj = grid.sorted[a_start + j]; + let (qx, qy) = positions.get(bj); + let rb = positions.radius(bj); let dx = px - qx; let dy = py - qy; - if dx * dx + dy * dy <= collision_dist_sq { + let dist = ra + rb; + if dx * dx + dy * dy <= dist * dist { row_hits += 1; } } @@ -158,12 +157,17 @@ pub fn detect_collisions( continue; } for i in 0..a_count { - let (px, py) = positions.get(grid.sorted[a_start + i]); + let ai = grid.sorted[a_start + i]; + let (px, py) = positions.get(ai); + let ra = positions.radius(ai); for j in 0..b_count { - let (qx, qy) = positions.get(grid.sorted[b_start + j]); + let bj = grid.sorted[b_start + j]; + let (qx, qy) = positions.get(bj); + let rb = positions.radius(bj); let dx = px - qx; let dy = py - qy; - if dx * dx + dy * dy <= collision_dist_sq { + let dist = ra + rb; + if dx * dx + dy * dy <= dist * dist { row_hits += 1; } } @@ -237,6 +241,7 @@ struct ProjectileStore { y: Vec, vx: Vec, vy: Vec, + radius: Vec, } impl Positions for ProjectileStore { @@ -246,6 +251,10 @@ impl Positions for ProjectileStore { let i = index as usize; (self.x[i], self.y[i]) } + #[inline(always)] + fn radius(&self, index: u32) -> f32 { + self.radius[index as usize] + } } impl Kinematics for ProjectileStore { @@ -274,10 +283,11 @@ impl Rng { fn next_signed(&mut self) -> f32 { self.next_f32() * 2.0 - 1.0 } } -const NUM_PROJECTILES: usize = 200_000; -const WORLD_SIZE: f32 = 4000.0; -const PROJECTILE_RADIUS: f32 = 2.0; -const CELL_SIZE: f32 = 8.0; +const NUM_PROJECTILES: usize = 500_000; +const WORLD_SIZE: f32 = 14000.0; +const MIN_RADIUS: f32 = 1.0; // small bullets +const MAX_RADIUS: f32 = 8.0; // big ships / missiles +const CELL_SIZE: f32 = 20.0; // >= 2 * MAX_RADIUS so neighbors always suffice const GRID_DIM: usize = (WORLD_SIZE / CELL_SIZE) as usize; const TICK_DT: f32 = 1.0 / 60.0; const MAX_SPEED: f32 = 200.0; @@ -292,10 +302,10 @@ fn main() { y: (0..NUM_PROJECTILES).map(|_| rng.next_f32() * WORLD_SIZE).collect(), vx: (0..NUM_PROJECTILES).map(|_| rng.next_signed() * MAX_SPEED).collect(), vy: (0..NUM_PROJECTILES).map(|_| rng.next_signed() * MAX_SPEED).collect(), + radius: (0..NUM_PROJECTILES).map(|_| MIN_RADIUS + rng.next_f32() * (MAX_RADIUS - MIN_RADIUS)).collect(), }; let mut grid = SpatialGrid::new(CELL_SIZE, GRID_DIM); - let collision_dist_sq = (PROJECTILE_RADIUS * 2.0) * (PROJECTILE_RADIUS * 2.0); let mut total_collisions: u64 = 0; let mut total_physics_time = Duration::ZERO; @@ -307,7 +317,7 @@ fn main() { Projectiles: {NUM_PROJECTILES}\n\ World: {WORLD_SIZE}x{WORLD_SIZE}\n\ Cell size: {CELL_SIZE} ({GRID_DIM}x{GRID_DIM} = {} cells)\n\ - Projectile radius: {PROJECTILE_RADIUS}\n\ + Radii: {MIN_RADIUS} - {MAX_RADIUS}\n\ Ticks: {NUM_TICKS} ({} seconds at 60hz)\n\ Rayon threads: {}\n", GRID_DIM * GRID_DIM, @@ -338,7 +348,7 @@ fn main() { // --- Collision (knows nothing about movement or storage) --- let t_coll = Instant::now(); - let frame_collisions = detect_collisions(&grid, &store, collision_dist_sq); + let frame_collisions = detect_collisions(&grid, &store); let coll_elapsed = t_coll.elapsed(); total_collision_time += coll_elapsed; total_collisions += frame_collisions;