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 {
|
pub trait Positions: Sync {
|
||||||
fn len(&self) -> usize;
|
fn len(&self) -> usize;
|
||||||
fn get(&self, index: u32) -> (f32, f32);
|
fn get(&self, index: u32) -> (f32, f32);
|
||||||
|
fn radius(&self, index: u32) -> f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read-write access to entity kinematics. The collision system never
|
/// Read-write access to entity kinematics. The collision system never
|
||||||
@@ -35,11 +36,10 @@ pub struct SpatialGrid {
|
|||||||
inv_cell_size: f32,
|
inv_cell_size: f32,
|
||||||
grid_dim: usize,
|
grid_dim: usize,
|
||||||
total_cells: usize,
|
total_cells: usize,
|
||||||
// Counting-sort buffers, reused across frames
|
|
||||||
cell_counts: Vec<u32>,
|
cell_counts: Vec<u32>,
|
||||||
cell_offsets: Vec<u32>,
|
cell_offsets: Vec<u32>,
|
||||||
sorted: Vec<u32>, // entity indices sorted by cell
|
sorted: Vec<u32>,
|
||||||
pairs: Vec<(u32, u32)>, // (cell_index, entity_index) staging
|
pairs: Vec<(u32, u32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpatialGrid {
|
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) {
|
pub fn rebuild(&mut self, positions: &impl Positions) {
|
||||||
let n = positions.len();
|
let n = positions.len();
|
||||||
|
|
||||||
// Ensure buffers are big enough
|
|
||||||
if self.sorted.len() < n {
|
if self.sorted.len() < n {
|
||||||
self.sorted.resize(n, 0);
|
self.sorted.resize(n, 0);
|
||||||
}
|
}
|
||||||
@@ -69,7 +67,6 @@ impl SpatialGrid {
|
|||||||
self.cell_counts.iter_mut().for_each(|c| *c = 0);
|
self.cell_counts.iter_mut().for_each(|c| *c = 0);
|
||||||
self.pairs.clear();
|
self.pairs.clear();
|
||||||
|
|
||||||
// Pass 1: bin every entity
|
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
let (x, y) = positions.get(i as u32);
|
let (x, y) = positions.get(i as u32);
|
||||||
if let Some(ci) = self.cell_of(x, y) {
|
if let Some(ci) = self.cell_of(x, y) {
|
||||||
@@ -78,14 +75,12 @@ impl SpatialGrid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 2: prefix sum
|
|
||||||
let mut running = 0u32;
|
let mut running = 0u32;
|
||||||
for i in 0..self.total_cells {
|
for i in 0..self.total_cells {
|
||||||
self.cell_offsets[i] = running;
|
self.cell_offsets[i] = running;
|
||||||
running += self.cell_counts[i];
|
running += self.cell_counts[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 3: scatter into sorted order
|
|
||||||
let mut cursors = self.cell_offsets.clone();
|
let mut cursors = self.cell_offsets.clone();
|
||||||
for &(ci, ei) in &self.pairs {
|
for &(ci, ei) in &self.pairs {
|
||||||
let slot = cursors[ci as usize] as usize;
|
let slot = cursors[ci as usize] as usize;
|
||||||
@@ -110,12 +105,11 @@ impl SpatialGrid {
|
|||||||
// Collision detection — free function, parallel, read-only
|
// 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.
|
/// Returns total number of collision pairs found.
|
||||||
pub fn detect_collisions(
|
pub fn detect_collisions(
|
||||||
grid: &SpatialGrid,
|
grid: &SpatialGrid,
|
||||||
positions: &impl Positions,
|
positions: &impl Positions,
|
||||||
collision_dist_sq: f32,
|
|
||||||
) -> u64 {
|
) -> u64 {
|
||||||
const NEIGHBORS: [(i32, i32); 4] = [(1, 0), (0, 1), (-1, 1), (1, 1)];
|
const NEIGHBORS: [(i32, i32); 4] = [(1, 0), (0, 1), (-1, 1), (1, 1)];
|
||||||
|
|
||||||
@@ -133,12 +127,17 @@ pub fn detect_collisions(
|
|||||||
|
|
||||||
// Intra-cell
|
// Intra-cell
|
||||||
for i in 0..a_count {
|
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 {
|
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 dx = px - qx;
|
||||||
let dy = py - qy;
|
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;
|
row_hits += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,12 +157,17 @@ pub fn detect_collisions(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for i in 0..a_count {
|
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 {
|
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 dx = px - qx;
|
||||||
let dy = py - qy;
|
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;
|
row_hits += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,6 +241,7 @@ struct ProjectileStore {
|
|||||||
y: Vec<f32>,
|
y: Vec<f32>,
|
||||||
vx: Vec<f32>,
|
vx: Vec<f32>,
|
||||||
vy: Vec<f32>,
|
vy: Vec<f32>,
|
||||||
|
radius: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Positions for ProjectileStore {
|
impl Positions for ProjectileStore {
|
||||||
@@ -246,6 +251,10 @@ impl Positions for ProjectileStore {
|
|||||||
let i = index as usize;
|
let i = index as usize;
|
||||||
(self.x[i], self.y[i])
|
(self.x[i], self.y[i])
|
||||||
}
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
fn radius(&self, index: u32) -> f32 {
|
||||||
|
self.radius[index as usize]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Kinematics for ProjectileStore {
|
impl Kinematics for ProjectileStore {
|
||||||
@@ -274,10 +283,11 @@ impl Rng {
|
|||||||
fn next_signed(&mut self) -> f32 { self.next_f32() * 2.0 - 1.0 }
|
fn next_signed(&mut self) -> f32 { self.next_f32() * 2.0 - 1.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
const NUM_PROJECTILES: usize = 200_000;
|
const NUM_PROJECTILES: usize = 500_000;
|
||||||
const WORLD_SIZE: f32 = 4000.0;
|
const WORLD_SIZE: f32 = 14000.0;
|
||||||
const PROJECTILE_RADIUS: f32 = 2.0;
|
const MIN_RADIUS: f32 = 1.0; // small bullets
|
||||||
const CELL_SIZE: f32 = 8.0;
|
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 GRID_DIM: usize = (WORLD_SIZE / CELL_SIZE) as usize;
|
||||||
const TICK_DT: f32 = 1.0 / 60.0;
|
const TICK_DT: f32 = 1.0 / 60.0;
|
||||||
const MAX_SPEED: f32 = 200.0;
|
const MAX_SPEED: f32 = 200.0;
|
||||||
@@ -292,10 +302,10 @@ fn main() {
|
|||||||
y: (0..NUM_PROJECTILES).map(|_| rng.next_f32() * WORLD_SIZE).collect(),
|
y: (0..NUM_PROJECTILES).map(|_| rng.next_f32() * WORLD_SIZE).collect(),
|
||||||
vx: (0..NUM_PROJECTILES).map(|_| rng.next_signed() * MAX_SPEED).collect(),
|
vx: (0..NUM_PROJECTILES).map(|_| rng.next_signed() * MAX_SPEED).collect(),
|
||||||
vy: (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 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_collisions: u64 = 0;
|
||||||
let mut total_physics_time = Duration::ZERO;
|
let mut total_physics_time = Duration::ZERO;
|
||||||
@@ -307,7 +317,7 @@ fn main() {
|
|||||||
Projectiles: {NUM_PROJECTILES}\n\
|
Projectiles: {NUM_PROJECTILES}\n\
|
||||||
World: {WORLD_SIZE}x{WORLD_SIZE}\n\
|
World: {WORLD_SIZE}x{WORLD_SIZE}\n\
|
||||||
Cell size: {CELL_SIZE} ({GRID_DIM}x{GRID_DIM} = {} cells)\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\
|
Ticks: {NUM_TICKS} ({} seconds at 60hz)\n\
|
||||||
Rayon threads: {}\n",
|
Rayon threads: {}\n",
|
||||||
GRID_DIM * GRID_DIM,
|
GRID_DIM * GRID_DIM,
|
||||||
@@ -338,7 +348,7 @@ fn main() {
|
|||||||
|
|
||||||
// --- Collision (knows nothing about movement or storage) ---
|
// --- Collision (knows nothing about movement or storage) ---
|
||||||
let t_coll = Instant::now();
|
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();
|
let coll_elapsed = t_coll.elapsed();
|
||||||
total_collision_time += coll_elapsed;
|
total_collision_time += coll_elapsed;
|
||||||
total_collisions += frame_collisions;
|
total_collisions += frame_collisions;
|
||||||
|
|||||||
Reference in New Issue
Block a user