Updated to match constraints better.
This commit is contained in:
+33
-23
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user