Updated to match constraints better.

This commit is contained in:
2026-04-08 20:47:12 -05:00
parent 9c7a5dfcd4
commit cac4178600
+33 -23
View File
@@ -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<u32>,
cell_offsets: Vec<u32>,
sorted: Vec<u32>, // entity indices sorted by cell
pairs: Vec<(u32, u32)>, // (cell_index, entity_index) staging
sorted: Vec<u32>,
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<f32>,
vx: Vec<f32>,
vy: Vec<f32>,
radius: Vec<f32>,
}
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;