diff options
Diffstat (limited to 'src/sim')
| -rw-r--r-- | src/sim/cell/cell.rs | 24 | ||||
| -rw-r--r-- | src/sim/cell/materials/fire.rs | 1 | ||||
| -rw-r--r-- | src/sim/cell/materials/gas.rs | 78 | ||||
| -rw-r--r-- | src/sim/cell/materials/liquid.rs (renamed from src/sim/cell/materials/water.rs) | 0 | ||||
| -rw-r--r-- | src/sim/cell/materials/mod.rs | 42 | ||||
| -rw-r--r-- | src/sim/cell/materials/powder.rs (renamed from src/sim/cell/materials/sand.rs) | 0 | ||||
| -rw-r--r-- | src/sim/cell/materials/smoke.rs | 20 | ||||
| -rw-r--r-- | src/sim/cell_sim/chunk.rs | 9 | ||||
| -rw-r--r-- | src/sim/cell_sim/sim.rs | 32 | ||||
| -rw-r--r-- | src/sim/rb_sim/mod.rs | 35 |
10 files changed, 112 insertions, 129 deletions
diff --git a/src/sim/cell/cell.rs b/src/sim/cell/cell.rs index d567893..edb2402 100644 --- a/src/sim/cell/cell.rs +++ b/src/sim/cell/cell.rs @@ -10,6 +10,7 @@ pub struct Cell { impl Cell { const FLAG_PARITY: u8 = 0b0000_0001; const FLAG_RB: u8 = 0b0000_0010; + const MASK_SETTLED: u8 = 0b0001_1100; #[inline] pub fn parity(self) -> u8 { @@ -33,6 +34,7 @@ impl Cell { pub fn rb(self) -> bool { (self.flags & Self::FLAG_RB) == Self::FLAG_RB } + #[inline] pub fn set_rb(&mut self, rb: bool) { if rb { self.flags |= Self::FLAG_RB @@ -40,6 +42,28 @@ impl Cell { self.flags = self.flags & !Self::FLAG_RB } } + + #[inline] + pub fn settled(self) -> u8 { + (self.flags & Self::MASK_SETTLED) >> 2 + } + #[inline] + fn set_settled(&mut self, settled: u8) { + debug_assert!(settled <= 7); + self.flags = (settled << 2) | (self.flags & !Self::MASK_SETTLED); + } + #[inline] + pub fn reset_settled(&mut self) { + self.flags &= !Self::MASK_SETTLED; + } + + #[inline] + pub fn increment_settled(&mut self) { + let s = self.settled(); + if s < 7 { + self.set_settled(s + 1); + } + } } impl Cell { diff --git a/src/sim/cell/materials/fire.rs b/src/sim/cell/materials/fire.rs index e0416b0..dd1ff8c 100644 --- a/src/sim/cell/materials/fire.rs +++ b/src/sim/cell/materials/fire.rs @@ -25,6 +25,7 @@ impl FireCellView for Cell { } // TODO optimize number of rng calls? +// TODO wtf is fire #[inline] pub fn sim_update(ctx: &mut UpdateCtx) { let ticks_lived = ctx.cell.get_ticks_lived(); diff --git a/src/sim/cell/materials/gas.rs b/src/sim/cell/materials/gas.rs index c0cd9f9..2ee7c00 100644 --- a/src/sim/cell/materials/gas.rs +++ b/src/sim/cell/materials/gas.rs @@ -1,76 +1,20 @@ +use rand::RngExt; + use crate::sim::cell_sim::sim::UpdateCtx; #[inline] pub fn sim_update(ctx: &mut UpdateCtx) { - // if the water can fall, do so - if ctx.candidates_swap(&[ - (0, -1), - (-1 + 2 * ctx.seqno_parity as i32, -1), - (1 - 2 * ctx.seqno_parity as i32, -1), - ]) { + // only allow upward movement some of the time to limit movement speed + if ctx.rng.random_range(0.0..1.0) > 0.8 && ctx.candidates_swap(&[(0, -1)]) { return; } - - // if the water can't fall, check if we can move left or right - // these are inverted on parity so that we don't preference a direction - let left_target = ctx.get_cell(-1, 0); - let can_move_left = - left_target.is_some_and(|c| c.material.def().density < ctx.material.density); - let right_target = ctx.get_cell(1, 0); - let can_move_right = - right_target.is_some_and(|c| c.material.def().density < ctx.material.density); - - // we can't move down or to other side, so we're stuck - if !can_move_left && !can_move_right { + // same for each horizontal direction + if ctx.rng.random_range(0.0..1.0) > 0.8 + && ctx.candidates_swap(&[(1 - ctx.seqno_parity as i32 * 2, 0)]) + { return; } - - // if we can't move left, just move right - if !can_move_left { - ctx.candidates_swap(&[(1, 0)]); - return; - } - // and vice versa - if !can_move_right { - ctx.candidates_swap(&[(-1, 0)]); - return; - } - - // find the closest hole within 20 pixels (TODO optimize) - // a hole is any space below us with a lesser density - // prevents equidistance stuck state - let starting_side = if ctx.seqno_parity == 0 { 1 } else { -1 }; - for i in 0..20 { - let side = if i % 2 == 0 { - starting_side - } else { - -starting_side - }; - - let offset = side * (1 + i / 2); - let hole_target = ctx.get_cell(offset, -1); - if let Some(target) = hole_target - && target.material.def().density < ctx.material.density - { - // we identified a hole and we know that the space on this side is open - // move toward the hole - // new_target.flags = new_target.flags ^ 0b1; - ctx.candidates_swap(&[(side, 0)]); - return; - } - } - - // we didn't find a hole, so just move "randomly" on the same surface - // TODO when to settle? - let target_x = if !can_move_left { - 1 - } else if !can_move_right { - -1 - } else if ctx.seqno_parity % 2 == 1 { - 1 - } else { - -1 - }; - - ctx.candidates_swap(&[(target_x, 0)]); + if ctx.rng.random_range(0.0..1.0) > 0.8 + && ctx.candidates_swap(&[(-1 + ctx.seqno_parity as i32 * 2, 0)]) + {} } diff --git a/src/sim/cell/materials/water.rs b/src/sim/cell/materials/liquid.rs index 4b848fb..4b848fb 100644 --- a/src/sim/cell/materials/water.rs +++ b/src/sim/cell/materials/liquid.rs diff --git a/src/sim/cell/materials/mod.rs b/src/sim/cell/materials/mod.rs index 0d19696..9bcf5e4 100644 --- a/src/sim/cell/materials/mod.rs +++ b/src/sim/cell/materials/mod.rs @@ -2,9 +2,8 @@ use crate::sim::cell_sim::sim::UpdateCtx; mod fire; mod gas; -mod sand; -mod smoke; -mod water; +mod liquid; +mod powder; #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -13,71 +12,82 @@ pub enum MaterialId { Sand, Wood, Water, - Gas, Fire, Smoke, } +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum MaterialForm { + Void, + Gas, + Liquid, + Powder, + Solid, +} + pub struct MaterialDef { pub name: &'static str, pub color: (u8, u8, u8, u8), pub density: u8, + pub form: MaterialForm, pub sim_update: Option<fn(ctx: &mut UpdateCtx) -> ()>, } -static MATERIALS: [MaterialDef; 7] = [ +static MATERIALS: [MaterialDef; 6] = [ MaterialDef { name: "Void", color: (0x00, 0x00, 0x00, 0x00), density: 0, + // form usually but not always determines the update fn + form: MaterialForm::Void, sim_update: None, }, MaterialDef { name: "Sand", color: (0xDE, 0xCB, 0x85, 0xFF), density: 50, - sim_update: Some(sand::sim_update), + form: MaterialForm::Powder, + sim_update: Some(powder::sim_update), }, MaterialDef { name: "Wood", color: (0x85, 0x56, 0x1D, 0xFF), density: 50, + form: MaterialForm::Solid, sim_update: None, }, MaterialDef { name: "Water", color: (0x38, 0xA9, 0xFF, 0xFF), density: 40, - sim_update: Some(water::sim_update), - }, - MaterialDef { - name: "Gas", - color: (0xBD, 0xFF, 0xE4, 0xAA), - density: 10, - sim_update: Some(gas::sim_update), + form: MaterialForm::Liquid, + sim_update: Some(liquid::sim_update), }, + // TODO this is not a material MaterialDef { name: "Fire", color: (0xFC, 0x66, 0x00, 0xAA), // for now this matches wood density: 50, + form: MaterialForm::Gas, sim_update: Some(fire::sim_update), }, MaterialDef { name: "Smoke", color: (0x32, 0x35, 0x36, 0xAA), density: 15, - sim_update: Some(smoke::sim_update), + form: MaterialForm::Gas, + sim_update: Some(gas::sim_update), }, ]; impl MaterialId { - pub const ALL: [MaterialId; 7] = [ + pub const ALL: [MaterialId; 6] = [ MaterialId::Void, MaterialId::Sand, MaterialId::Wood, MaterialId::Water, - MaterialId::Gas, MaterialId::Fire, MaterialId::Smoke, ]; diff --git a/src/sim/cell/materials/sand.rs b/src/sim/cell/materials/powder.rs index d2d1265..d2d1265 100644 --- a/src/sim/cell/materials/sand.rs +++ b/src/sim/cell/materials/powder.rs diff --git a/src/sim/cell/materials/smoke.rs b/src/sim/cell/materials/smoke.rs deleted file mode 100644 index 2ee7c00..0000000 --- a/src/sim/cell/materials/smoke.rs +++ /dev/null @@ -1,20 +0,0 @@ -use rand::RngExt; - -use crate::sim::cell_sim::sim::UpdateCtx; - -#[inline] -pub fn sim_update(ctx: &mut UpdateCtx) { - // only allow upward movement some of the time to limit movement speed - if ctx.rng.random_range(0.0..1.0) > 0.8 && ctx.candidates_swap(&[(0, -1)]) { - return; - } - // same for each horizontal direction - if ctx.rng.random_range(0.0..1.0) > 0.8 - && ctx.candidates_swap(&[(1 - ctx.seqno_parity as i32 * 2, 0)]) - { - return; - } - if ctx.rng.random_range(0.0..1.0) > 0.8 - && ctx.candidates_swap(&[(-1 + ctx.seqno_parity as i32 * 2, 0)]) - {} -} diff --git a/src/sim/cell_sim/chunk.rs b/src/sim/cell_sim/chunk.rs index c7d619b..85d2c09 100644 --- a/src/sim/cell_sim/chunk.rs +++ b/src/sim/cell_sim/chunk.rs @@ -1,7 +1,10 @@ use crate::{ config::{CELLS_IN_CHUNK, CHUNK_SIZE}, sim::{ - cell::{cell::Cell, materials::MaterialId}, + cell::{ + cell::Cell, + materials::{MaterialForm, MaterialId}, + }, lib::marching_squares::Marchable, }, }; @@ -36,7 +39,9 @@ impl Marchable for Chunk { if x < 0 || x >= CHUNK_SIZE || y < 0 || y >= CHUNK_SIZE { false } else { - self.get_cell_at_local_position(x as u8, y as u8).material != MaterialId::Void + // we only build a path for solid cells or settled powder cells + let cell = self.get_cell_at_local_position(x as u8, y as u8); + cell.material.def().form == MaterialForm::Solid || cell.settled() > 4 } } fn size(&self) -> (i32, i32) { diff --git a/src/sim/cell_sim/sim.rs b/src/sim/cell_sim/sim.rs index 8831bb4..4b9f05f 100644 --- a/src/sim/cell_sim/sim.rs +++ b/src/sim/cell_sim/sim.rs @@ -5,14 +5,10 @@ use rand::{Rng, SeedableRng, rngs::SmallRng}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use crate::{ - config::{CELLS_IN_CHUNK, CHUNK_SIZE}, + config::CHUNK_SIZE, sim::{ - cell::{ - cell::Cell, - materials::{MaterialDef, MaterialId}, - }, + cell::{cell::Cell, materials::MaterialDef}, cell_sim::{chunk::Chunk, world::World}, - rb_sim::{RbSimManager, rb_entity::RbEntity}, }, }; @@ -87,6 +83,7 @@ pub struct UpdateCtx<'a, 'b, 'c> { pub material: &'c MaterialDef, pub rng: &'c mut dyn Rng, + swapped: bool, } impl UpdateCtx<'_, '_, '_> { @@ -99,8 +96,8 @@ impl UpdateCtx<'_, '_, '_> { pub fn set_cell(&mut self, dx: i32, dy: i32, cell: Cell) { // cannot move out of the neighbourhood, but also cannot move to the edge of the neighbourhood // as this would wake a chunk outside of the neighbourhood - debug_assert!(dx > -15 && dx < 15); - debug_assert!(dy > -15 && dy < 15); + debug_assert!(dx > -CHUNK_SIZE + 1 && dx < CHUNK_SIZE - 1); + debug_assert!(dy > -CHUNK_SIZE + 1 && dy < CHUNK_SIZE - 1); let x = self.x + dx; let y = self.y + dy; set_cell(self.chunks, x, y, cell); @@ -113,10 +110,14 @@ impl UpdateCtx<'_, '_, '_> { pub fn candidates_swap(&mut self, candidates: &[(i32, i32)]) -> bool { for &(dx, dy) in candidates { - let candidate_cell = self.get_cell(dx, dy); - if candidate_cell.is_some_and(|c| c.material.def().density < self.material.density) { - self.set_cell(0, 0, candidate_cell.unwrap()); + if let Some(mut candidate_cell) = self.get_cell(dx, dy) + && candidate_cell.material.def().density < self.material.density + { + candidate_cell.reset_settled(); + self.cell.reset_settled(); + self.set_cell(0, 0, candidate_cell); self.set_cell(dx, dy, *self.cell); + self.swapped = true; return true; } } @@ -145,8 +146,6 @@ pub fn sim_tick_chunk(chunks: &mut [Option<&mut Chunk>; 9], seqno: u64) { && cell.parity() == seqno_parity { cell.flip_parity(); - // apply the flipped parity in case the sim target doesn't - set_cell(chunks, x, y, cell); let mut update_ctx = UpdateCtx { chunks, @@ -160,9 +159,16 @@ pub fn sim_tick_chunk(chunks: &mut [Option<&mut Chunk>; 9], seqno: u64) { // TODO this is platform-dependent, will break for multiplayer rng: &mut rng, + swapped: false, }; update(&mut update_ctx); + + if !update_ctx.swapped { + // the cell didn't move, so it's more settled, and we also need to update its state for parity + cell.increment_settled(); + set_cell(chunks, x, y, cell); + } } } } diff --git a/src/sim/rb_sim/mod.rs b/src/sim/rb_sim/mod.rs index d986962..17f605e 100644 --- a/src/sim/rb_sim/mod.rs +++ b/src/sim/rb_sim/mod.rs @@ -129,7 +129,9 @@ impl RbSimManager { height: h, }; - let collider = self.convex_hull_collider_from_marchable(&entity).build(); + let collider = self + .convex_hull_collider_from_marchable(&entity, None) + .build(); self.rb_entities.insert(id, entity); self.physics_manager.collider_set.insert_with_parent( @@ -175,16 +177,22 @@ impl RbSimManager { } } - fn polyline_from_marchable(&self, marchable: &impl Marchable) -> (Vec<Vec2>, Vec<[u32; 2]>) { + fn polyline_from_marchable( + &self, + marchable: &impl Marchable, + minimum_verts: Option<u32>, + ) -> (Vec<Vec2>, Vec<[u32; 2]>) { let (w, h) = marchable.size(); let polys = marching_squares_vertex_trace(marchable, w, h); let mut vertices = Vec::new(); let mut indices = Vec::new(); + let minimum_verts = minimum_verts.unwrap_or(3); + for poly in polys { let v = vertices.len() as u32; let p = poly.len() as u32; - if p < 3 { + if p < minimum_verts { continue; } for i in 0..p { @@ -202,8 +210,9 @@ impl RbSimManager { fn polyline_collider_from_marchable( &self, marchable: &impl Marchable, + minimum_verts: Option<u32>, ) -> geometry::ColliderBuilder { - let (vertices, indices) = self.polyline_from_marchable(marchable); + let (vertices, indices) = self.polyline_from_marchable(marchable, minimum_verts); geometry::ColliderBuilder::polyline(vertices, Some(indices)) } @@ -211,18 +220,22 @@ impl RbSimManager { fn convex_hull_collider_from_marchable( &self, marchable: &impl Marchable, + minimum_verts: Option<u32>, ) -> geometry::ColliderBuilder { - let (vertices, indices) = self.polyline_from_marchable(marchable); + let (vertices, indices) = self.polyline_from_marchable(marchable, minimum_verts); geometry::ColliderBuilder::convex_decomposition(&vertices, &indices) } pub fn upsert_chunk_collider(&mut self, cx: i32, cy: i32, chunk: &Chunk) { - let collider = self.polyline_collider_from_marchable(chunk).translation( - vec2( - (cx as f32 + 0.5) * CHUNK_SIZE as f32, - (cy as f32 + 0.5) * CHUNK_SIZE as f32, - ) / PIXELS_TO_METRES, - ); + puffin::profile_function!(); + let collider = self + .polyline_collider_from_marchable(chunk, Some(20)) + .translation( + vec2( + (cx as f32 + 0.5) * CHUNK_SIZE as f32, + (cy as f32 + 0.5) * CHUNK_SIZE as f32, + ) / PIXELS_TO_METRES, + ); if let Some(handle) = self.chunk_colliders.remove(&(cx, cy)) { self.physics_manager.collider_set.remove( |
