summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main.rs389
-rw-r--r--src/renderer/mod.rs36
-rw-r--r--src/renderer/ui.rs19
-rw-r--r--src/sim/cell/materials/fire.rs2
-rw-r--r--src/sim/cell/materials/gas.rs2
-rw-r--r--src/sim/cell/materials/liquid.rs2
-rw-r--r--src/sim/cell/materials/mod.rs2
-rw-r--r--src/sim/cell/materials/powder.rs2
-rw-r--r--src/sim/cell_manager/chunk.rs (renamed from src/sim/cell_sim/chunk.rs)0
-rw-r--r--src/sim/cell_manager/manager.rs (renamed from src/sim/cell_sim/world.rs)29
-rw-r--r--src/sim/cell_manager/mod.rs (renamed from src/sim/cell_sim/mod.rs)2
-rw-r--r--src/sim/cell_manager/sim.rs (renamed from src/sim/cell_sim/sim.rs)6
-rw-r--r--src/sim/entity/mod.rs28
-rw-r--r--src/sim/lib/force.rs24
-rw-r--r--src/sim/mod.rs69
-rw-r--r--src/sim/particle_manager/mod.rs (renamed from src/sim/particle_sim/mod.rs)6
-rw-r--r--src/sim/particle_manager/particle.rs (renamed from src/sim/particle_sim/particle.rs)0
-rw-r--r--src/sim/rb_manager/debug_ops.rs (renamed from src/sim/rb_sim/debug_ops.rs)4
-rw-r--r--src/sim/rb_manager/debug_render.rs (renamed from src/sim/rb_sim/debug_render.rs)0
-rw-r--r--src/sim/rb_manager/mod.rs (renamed from src/sim/rb_sim/mod.rs)12
-rw-r--r--src/sim/rb_manager/rb_entity.rs (renamed from src/sim/rb_sim/rb_entity.rs)0
-rw-r--r--src/sim/sim_manager/mod.rs152
-rw-r--r--src/sim/sim_manager/utils.rs62
23 files changed, 432 insertions, 416 deletions
diff --git a/src/main.rs b/src/main.rs
index caedc23..3976d71 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,8 +4,8 @@ mod renderer;
mod sim;
use futures::executor;
-use glam::{IVec2, Vec2};
-use rand::{random_range, random_ratio};
+use glam::Vec2;
+use rand::random_range;
use std::{collections::VecDeque, sync::Arc, time::Instant};
use winit::{
application::ApplicationHandler,
@@ -20,18 +20,15 @@ use winit::{
use crate::{
camera::Camera,
- config::{PHYSICS_DELTA_TIME, PHYSICS_FPS, SIM_FPS, WINDOW_TITLE},
+ config::WINDOW_TITLE,
renderer::RendererState,
sim::{
- cell::{
- cell::Cell,
- materials::{MaterialId, fire::FireCellView},
- },
- cell_sim::{sim::sim_tick, world::World},
+ cell::{cell::Cell, materials::MaterialId},
+ cell_manager::manager::CellManager,
lib::force::apply_explosion,
- particle_sim::{ParticleManager, particle::Particle},
- rb_sim::{DebugRenderMode, RbSimManager, debug_ops::DebugOperator},
- write_rb_entity_to_world,
+ particle_manager::particle::Particle,
+ rb_manager::{DebugRenderMode, debug_ops::DebugOperator},
+ sim_manager::SimManager,
},
};
@@ -74,34 +71,19 @@ struct Diagnostics {
}
struct App {
+ // core
window: Option<Arc<Window>>,
renderer_state: Option<RendererState>,
+ // game
input: Input,
-
camera: Option<Camera>,
+ sim_manager: Option<SimManager>,
- // grid
- world: Option<World>,
-
- // physics
- rb_sim_manager: Option<RbSimManager>,
- particle_manager: Option<ParticleManager>,
-
- // sim state
- // the last/current (not yet completed) seqno
- sim_seqno: u64,
- sim_paused: bool,
- ignore_pause_next_tick: bool,
- last_sim_update: Instant,
- sim_updates_due: f32,
-
- // physics state
- last_physics_update: Instant,
- physics_updates_due: f32,
-
+ // renderer
last_render: Instant,
+ // state
config: Config,
diagnostics: Diagnostics,
}
@@ -115,171 +97,85 @@ impl App {
camera.handle_camera_input(&self.input, delta_time)
}
- // --TEST SPAWNING--
- if self.input.trigger_test_1
- && let Some(lm) = self.input.last_mouse_world_pos
- && let Some(rbsm) = &mut self.rb_sim_manager
- {
- self.input.trigger_test_1 = false;
- rbsm.test_spawn_box(lm.0, lm.1, self.config.dropper_material);
- }
-
- if self.input.trigger_test_2
- && let Some(lm) = self.input.last_mouse_world_pos
- && let Some(rbsm) = &mut self.rb_sim_manager
- {
- self.input.trigger_test_2 = false;
- rbsm.test_spawn_ball(lm.0, lm.1, self.config.dropper_material);
- }
-
- if self.input.trigger_test_3
- && let Some(lm) = self.input.last_mouse_world_pos
- && let Some(pm) = &mut self.particle_manager
- {
- self.input.trigger_test_3 = false;
- for i in 0..100 {
- pm.particles.push(Particle {
- position: Vec2::new(
- lm.0 + random_range(-25.0..25.0),
- lm.1 + random_range(-25.0..25.0),
- ),
- velocity: Vec2::ZERO,
- material: MaterialId::Sand,
- life: 2.0,
- })
+ if let Some(sim) = &mut self.sim_manager {
+ // --TEST SPAWNING--
+ if self.input.trigger_test_1
+ && let Some(lm) = self.input.last_mouse_world_pos
+ {
+ self.input.trigger_test_1 = false;
+ sim.rb_manager
+ .test_spawn_box(lm.0, lm.1, self.config.dropper_material);
}
- }
- if self.input.trigger_test_4
- && let Some(lm) = self.input.last_mouse_world_pos
- && let Some(world) = &mut self.world
- && let Some(pm) = &mut self.particle_manager
- && let Some(rbsm) = &mut self.rb_sim_manager
- {
- let mouse = Vec2::new(lm.0, lm.1);
- self.input.trigger_test_4 = false;
- apply_explosion(
- mouse,
- 30,
- Vec2::new(0.0, -0.6),
- 300.0,
- self.sim_seqno,
- pm,
- world,
- rbsm,
- );
- }
-
- // --TEST DRAWING--
- if (self.input.is_lmb_pressed || self.input.is_rmb_pressed)
- && let Some(lm) = self.input.last_mouse_world_pos
- {
- // start with the bounding box of the drawing brush circle + some margin
- // clamp the bounding box to the board sie
- let bb_xl = (lm.0 - self.config.brush_radius).round() as i32;
- let bb_xu = (lm.0 + self.config.brush_radius).round() as i32;
- let bb_yl = (lm.1 - self.config.brush_radius).round() as i32;
- let bb_yu = (lm.1 + self.config.brush_radius).round() as i32;
+ if self.input.trigger_test_2
+ && let Some(lm) = self.input.last_mouse_world_pos
+ {
+ self.input.trigger_test_2 = false;
+ sim.rb_manager
+ .test_spawn_ball(lm.0, lm.1, self.config.dropper_material);
+ }
- // for each point, check if the distance is less than the brush size and write the pixel
- for x in bb_xl..bb_xu {
- for y in bb_yl..bb_yu {
- let r = random_range(0.0..1.0);
- if ((x - lm.0.round() as i32).pow(2) + (y - lm.1.round() as i32).pow(2))
- < (self.config.brush_radius as i32).pow(2)
- && r > 0.9
- {
- let cell = if self.input.is_lmb_pressed {
- let mut cell = Cell::from_material(self.config.brush_material);
- cell.match_parity(self.sim_seqno);
- cell
- } else {
- Cell::void()
- };
- // ensure we simulate on the first tick
- if let Some(world) = &mut self.world {
- world.set_cell_from_game_position(
- x, y, cell, false, // wake the chunk
- )
- }
- }
+ if self.input.trigger_test_3
+ && let Some(lm) = self.input.last_mouse_world_pos
+ {
+ self.input.trigger_test_3 = false;
+ for _ in 0..100 {
+ sim.particle_manager.particles.push(Particle {
+ position: Vec2::new(
+ lm.0 + random_range(-25.0..25.0),
+ lm.1 + random_range(-25.0..25.0),
+ ),
+ velocity: Vec2::ZERO,
+ material: MaterialId::Sand,
+ life: 2.0,
+ })
}
}
- }
- }
- // called SIM_FPS times per second
- // will be called before the render and before the physics update(s)
- // may be called multiple times if the sim time is behind
- fn sim_update(&mut self) {
- if let Some(world) = &mut self.world
- && let Some(rbsm) = &mut self.rb_sim_manager
- {
- // before we tick, write all the rb entities into the sim world
- // TODO optimize
- let entity_ids: Vec<u32> = rbsm.rb_entities.keys().copied().collect();
- let mut cells_written_by_entity: Vec<(u32, Vec<(u8, u8, i32, i32)>)> = Vec::new();
- for entity_id in entity_ids {
- let cells_written =
- write_rb_entity_to_world(world, rbsm, entity_id, self.sim_seqno);
- cells_written_by_entity.push((entity_id, cells_written));
+ if self.input.trigger_test_4
+ && let Some(lm) = self.input.last_mouse_world_pos
+ {
+ let mouse = Vec2::new(lm.0, lm.1);
+ self.input.trigger_test_4 = false;
+ apply_explosion(sim, mouse, 30, Vec2::new(0.0, -0.6), 300.0);
}
- sim_tick(world, self.sim_seqno, self.config.use_threading);
- self.sim_seqno += 1;
+ // --TEST DRAWING--
+ if (self.input.is_lmb_pressed || self.input.is_rmb_pressed)
+ && let Some(lm) = self.input.last_mouse_world_pos
+ {
+ // start with the bounding box of the drawing brush circle + some margin
+ // clamp the bounding box to the board sie
+ let bb_xl = (lm.0 - self.config.brush_radius).round() as i32;
+ let bb_xu = (lm.0 + self.config.brush_radius).round() as i32;
+ let bb_yl = (lm.1 - self.config.brush_radius).round() as i32;
+ let bb_yu = (lm.1 + self.config.brush_radius).round() as i32;
- // after we tick, remove the written rb cells and update the entities
- // TODO optimize
- for (entity_id, cells_written) in cells_written_by_entity {
- let rb_entity = rbsm.rb_entities.get_mut(&entity_id).unwrap();
- for (lx, ly, x, y) in cells_written {
- // update the entity
- // TODO we should skip cells that weren't changed?
- // TODO optimize
- let new_local_cell = world.get_cell_from_game_position(x, y).unwrap();
- if new_local_cell.material != MaterialId::Void && !new_local_cell.rb() {
- panic!(
- "Someone swapped into this rb's cell! ({x}, {y}, {m:#?}, {f})",
- m = new_local_cell.material,
- f = new_local_cell.flags
- );
- }
- rb_entity.set_cell_at_local_position(lx, ly, new_local_cell);
- // update the world
- world.set_cell_from_game_position(x, y, Cell::void(), false);
- }
- }
- }
- }
- // called PHYSICS_FPS times per second
- // will be called before the render
- // may be called multiple times if the physics time is behind
- // physics_delta_time is statically 1/PHYSICS_FPS
- fn physics_update(&mut self, physics_delta_time: f32) {
- // before we move the rigidbodies, upsert the current terrain state
- // TODO use the chunk sleeping, and make this range dynamic
- if let Some(physics_manager) = &mut self.rb_sim_manager {
- if let Some(world) = &self.world {
- for cx in -5..5 {
- for cy in -5..5 {
- if let Some(chunk) = world
- .chunk_position_to_chunk_idx
- .get(&(cx, cy))
- .map(|&idx| &world.chunks[idx])
- && !chunk.sleeping
+ // for each point, check if the distance is less than the brush size and write the pixel
+ for x in bb_xl..bb_xu {
+ for y in bb_yl..bb_yu {
+ let r = random_range(0.0..1.0);
+ if ((x - lm.0.round() as i32).pow(2) + (y - lm.1.round() as i32).pow(2))
+ < (self.config.brush_radius as i32).pow(2)
+ && r > 0.9
{
- physics_manager.upsert_chunk_collider(cx, cy, chunk);
+ let cell = if self.input.is_lmb_pressed {
+ let mut cell = Cell::from_material(self.config.brush_material);
+ // ensure we simulate on the first tick
+ cell.match_parity(sim.cell_manager.seqno);
+ cell
+ } else {
+ Cell::void()
+ };
+ sim.cell_manager.set_cell_from_game_position(
+ x, y, cell, false, // wake the chunk
+ )
}
}
}
}
- physics_manager.rb_tick(physics_delta_time);
- }
- // move the particles
- if let Some(particle_manager) = &mut self.particle_manager
- && let Some(world) = &mut self.world
- {
- particle_manager.particle_tick(world, physics_delta_time);
+
+ sim.update(&self.config, delta_time);
}
}
}
@@ -311,19 +207,7 @@ impl Default for App {
camera: None,
- world: Some(World::from_default_size()),
-
- rb_sim_manager: Some(RbSimManager::new()),
- particle_manager: Some(ParticleManager::new()),
-
- sim_seqno: 0,
- sim_paused: false,
- ignore_pause_next_tick: false,
- last_sim_update: Instant::now(),
- sim_updates_due: 0.0,
-
- last_physics_update: Instant::now(),
- physics_updates_due: 0.0,
+ sim_manager: Some(SimManager::new()),
last_render: Instant::now(),
@@ -392,51 +276,31 @@ impl ApplicationHandler for App {
},
..
} => {
- let pressed = state.is_pressed();
- match code {
- KeyCode::KeyW => self.input.is_up_pressed = pressed,
- KeyCode::KeyA => self.input.is_left_pressed = pressed,
- KeyCode::KeyS => self.input.is_down_pressed = pressed,
- KeyCode::KeyD => self.input.is_right_pressed = pressed,
- KeyCode::KeyC => self.world = Some(World::from_default_size()),
- KeyCode::KeyP => {
- self.particle_manager
- .as_mut()
- .map(|pm| pm.particles = Vec::new());
- }
- KeyCode::KeyV => {
- if let Some(rbsm) = self.rb_sim_manager.as_mut() {
- let entity_ids: Vec<u32> = rbsm.rb_entities.keys().copied().collect();
+ if let Some(sim) = &mut self.sim_manager {
+ let pressed = state.is_pressed();
+ match code {
+ KeyCode::KeyW => self.input.is_up_pressed = pressed,
+ KeyCode::KeyA => self.input.is_left_pressed = pressed,
+ KeyCode::KeyS => self.input.is_down_pressed = pressed,
+ KeyCode::KeyD => self.input.is_right_pressed = pressed,
+ KeyCode::KeyC => sim.cell_manager = CellManager::from_default_size(),
+ KeyCode::KeyP => sim.particle_manager.particles = Vec::new(),
+ KeyCode::KeyV => {
+ let entity_ids: Vec<u32> =
+ sim.rb_manager.rb_entities.keys().copied().collect();
for entity_id in entity_ids {
- rbsm.destroy_rb_entity(entity_id);
+ sim.rb_manager.destroy_rb_entity(entity_id);
}
}
+ KeyCode::Space if pressed => sim.paused = !sim.paused,
+ KeyCode::KeyX if pressed => sim.ignore_pause_next_tick = true,
+ KeyCode::Digit1 if pressed && !repeat => self.input.trigger_test_1 = true,
+ KeyCode::Digit2 if pressed && !repeat => self.input.trigger_test_2 = true,
+ KeyCode::Digit3 if pressed && !repeat => self.input.trigger_test_3 = true,
+ KeyCode::Digit4 if pressed && !repeat => self.input.trigger_test_4 = true,
+ KeyCode::Digit5 if pressed && !repeat => self.input.trigger_test_5 = true,
+ _ => {}
}
- KeyCode::KeyP if pressed && !repeat => {
- if let Some(rbsm) = self.rb_sim_manager.as_mut() {
- for &id in rbsm.rb_entities.keys() {
- write_rb_entity_to_world(
- self.world.as_mut().unwrap(),
- rbsm,
- id,
- self.sim_seqno,
- );
- }
-
- let entity_ids: Vec<u32> = rbsm.rb_entities.keys().copied().collect();
- for entity_id in entity_ids {
- rbsm.destroy_rb_entity(entity_id);
- }
- }
- }
- KeyCode::Space if pressed => self.sim_paused = !self.sim_paused,
- KeyCode::KeyX if pressed => self.ignore_pause_next_tick = true,
- KeyCode::Digit1 if pressed && !repeat => self.input.trigger_test_1 = true,
- KeyCode::Digit2 if pressed && !repeat => self.input.trigger_test_2 = true,
- KeyCode::Digit3 if pressed && !repeat => self.input.trigger_test_3 = true,
- KeyCode::Digit4 if pressed && !repeat => self.input.trigger_test_4 = true,
- KeyCode::Digit5 if pressed && !repeat => self.input.trigger_test_5 = true,
- _ => {}
}
}
WindowEvent::CursorMoved { position, .. } => {
@@ -448,7 +312,7 @@ impl ApplicationHandler for App {
self.input.last_mouse_world_pos = Some(world_pos);
let ((cx, cy), (lx, ly)) =
- World::split_game_position(world_pos.0 as i32, world_pos.1 as i32);
+ CellManager::split_game_position(world_pos.0 as i32, world_pos.1 as i32);
self.input.last_mouse_chunk_pos = Some((cx, cy));
self.input.last_mouse_local_pos = Some((lx, ly));
}
@@ -492,61 +356,14 @@ impl ApplicationHandler for App {
self.diagnostics.fps = 1.0 / average_frame_time;
- // UPDATE
self.update(delta_time);
- // SIM UPDATE
- let secs_since_last_sim_update = (now - self.last_sim_update).as_secs_f32();
- let expected_secs_since_last_sim_update = 1.0 / SIM_FPS as f32;
-
- self.last_sim_update = now;
- if self.sim_paused && self.ignore_pause_next_tick {
- self.sim_update();
- } else if !self.sim_paused {
- self.sim_updates_due +=
- secs_since_last_sim_update / expected_secs_since_last_sim_update;
- let mut updates_done = 0;
- // don't ever update more than 3 times per frame, or else we can get a pseudo deadlock
- while self.sim_updates_due >= 1.0 && updates_done < 3 {
- self.sim_update();
- self.sim_updates_due -= 1.0;
- updates_done += 1;
- }
- self.sim_updates_due = self.sim_updates_due.min(3.0);
- }
-
- // PHYSICS UPDATE
- let secs_since_last_physics_update = (now - self.last_physics_update).as_secs_f32();
- let expected_secs_since_last_physics_update = 1.0 / PHYSICS_FPS as f32;
-
- self.last_physics_update = now;
- if self.sim_paused && self.ignore_pause_next_tick {
- self.physics_update(PHYSICS_DELTA_TIME);
- } else if !self.sim_paused {
- self.physics_updates_due +=
- secs_since_last_physics_update / expected_secs_since_last_physics_update;
- let mut updates_done = 0;
- // don't ever update more than 3 times per frame, or else we can get a pseudo deadlock
- while self.physics_updates_due >= 1.0 && updates_done < 3 {
- self.physics_update(PHYSICS_DELTA_TIME);
- self.physics_updates_due -= 1.0;
- updates_done += 1;
- }
- self.physics_updates_due = self.physics_updates_due.min(3.0);
- }
-
- self.ignore_pause_next_tick = false;
-
if let Some(renderer_state) = &mut self.renderer_state
- && let Some(world) = &mut self.world
- && let Some(rb_sim_manager) = &mut self.rb_sim_manager
- && let Some(particle_manager) = &mut self.particle_manager
+ && let Some(sim) = &mut self.sim_manager
&& let Some(camera) = &mut self.camera
{
renderer_state.render(
- world,
- rb_sim_manager,
- particle_manager,
+ sim,
camera,
&mut self.config,
&self.diagnostics,
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 233d66b..403eeb2 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -12,9 +12,10 @@ use crate::{
renderer::ui::draw_egui,
sim::{
cell::materials::MaterialId,
- cell_sim::world::World,
- particle_sim::ParticleManager,
- rb_sim::{RbSimManager, debug_render::DebugVertex, rb_entity::RbEntity},
+ cell_manager::manager::CellManager,
+ particle_manager::ParticleManager,
+ rb_manager::{RbManager, debug_render::DebugVertex, rb_entity::RbEntity},
+ sim_manager::SimManager,
},
};
@@ -573,9 +574,7 @@ impl RendererState {
pub fn render(
&mut self,
- world: &mut World,
- rb_sim_manager: &mut RbSimManager,
- particle_manager: &mut ParticleManager,
+ sim: &mut SimManager,
camera: &mut Camera,
config: &mut Config,
diagnostics: &Diagnostics,
@@ -627,7 +626,7 @@ impl RendererState {
.resizable(false)
// TODO collapse button
.show_collapsible(ui, &mut true, |panel_ui| {
- draw_egui(panel_ui, config, camera, diagnostics, input, world)
+ draw_egui(panel_ui, config, camera, diagnostics, input, sim)
});
});
@@ -671,8 +670,8 @@ impl RendererState {
{
puffin::profile_scope!("Upload chunk cells");
- for &idx in world.chunk_position_to_chunk_idx.values() {
- let chunk = &mut world.chunks[idx];
+ for &idx in sim.cell_manager.chunk_position_to_chunk_idx.values() {
+ let chunk = &mut sim.cell_manager.chunks[idx];
if !chunk.needs_texture_update {
continue;
}
@@ -694,7 +693,7 @@ impl RendererState {
puffin::profile_scope!("Upload entity cells");
// TODO optimize, this is very inefficient, just build it per frame with positions and skip the lookup?
self.renderer_rb_entities.drain();
- for entity in rb_sim_manager.rb_entities.values() {
+ for entity in sim.rb_manager.rb_entities.values() {
// TODO add "needs texture update"?
for i in 0..entity.cells.len() {
cell_buffer[i] = entity.cells[i].material as u8;
@@ -717,15 +716,17 @@ impl RendererState {
{
puffin::profile_scope!("Build instances");
let (xl, xu, yl, yu) = camera.viewport_bounds_world();
- let ((cxl, cyl), _) = World::split_game_position(xl.floor() as i32, yl.floor() as i32);
- let ((cxu, cyu), _) = World::split_game_position(xu.floor() as i32, yu.floor() as i32);
+ let ((cxl, cyl), _) =
+ CellManager::split_game_position(xl.floor() as i32, yl.floor() as i32);
+ let ((cxu, cyu), _) =
+ CellManager::split_game_position(xu.floor() as i32, yu.floor() as i32);
let half_size = [CHUNK_SIZE as f32 / 2.0, CHUNK_SIZE as f32 / 2.0];
let dims = [CHUNK_SIZE as u32, CHUNK_SIZE as u32];
for cx in cxl..=cxu {
for cy in cyl..=cyu {
- if let Some(idx) = world.chunk_position_to_chunk_idx.get(&(cx, cy)) {
+ if let Some(idx) = sim.cell_manager.chunk_position_to_chunk_idx.get(&(cx, cy)) {
instances.push(RendererInstance {
centre: [
(cx * CHUNK_SIZE) as f32 + half_size[0],
@@ -742,8 +743,8 @@ impl RendererState {
}
for (id, slot) in &self.renderer_rb_entities {
- if let Some(&RbEntity { width, height, .. }) = rb_sim_manager.rb_entities.get(id)
- && let Some((x, y, cos, sin)) = rb_sim_manager.get_rb_entity_transform(*id)
+ if let Some(&RbEntity { width, height, .. }) = sim.rb_manager.rb_entities.get(id)
+ && let Some((x, y, cos, sin)) = sim.rb_manager.get_rb_entity_transform(*id)
{
instances.push(RendererInstance {
centre: [x, y],
@@ -765,7 +766,8 @@ impl RendererState {
{
puffin::profile_scope!("Build particles");
// TODO could cull this to visible, maybe it's slower?
- particles = particle_manager
+ particles = sim
+ .particle_manager
.particles
.iter()
.map(|p| RendererParticle {
@@ -781,7 +783,7 @@ impl RendererState {
let debug_vertex_count = if config.debug_render {
puffin::profile_scope!("Build physics debug lines");
- let vertices = rb_sim_manager.debug_render(config.debug_render_mode);
+ let vertices = sim.rb_manager.debug_render(config.debug_render_mode);
if vertices.is_empty() {
0
diff --git a/src/renderer/ui.rs b/src/renderer/ui.rs
index c4f7585..4c6d61c 100644
--- a/src/renderer/ui.rs
+++ b/src/renderer/ui.rs
@@ -2,7 +2,10 @@ use egui::{Color32, Stroke, Ui, epaint::CircleShape};
use crate::{
Camera, Config, Diagnostics, Input,
- sim::{cell::materials::MaterialId, cell_sim::world::World, rb_sim::DebugRenderMode},
+ sim::{
+ cell::materials::MaterialId, cell_manager::manager::CellManager,
+ rb_manager::DebugRenderMode, sim_manager::SimManager,
+ },
};
const DEBUG_RENDER_MODES: [(&str, DebugRenderMode); 6] = [
@@ -20,7 +23,7 @@ pub fn draw_egui<'a>(
camera: &mut Camera,
diagnostics: &Diagnostics,
input: &Input,
- world: &World,
+ sim: &SimManager,
) {
puffin::profile_function!();
ui.heading("Config");
@@ -109,8 +112,11 @@ pub fn draw_egui<'a>(
if let Some(p) = input.last_mouse_chunk_pos {
ui.label(format!("Mouse (chunk): x,y=({x}, {y})", x = p.0, y = p.1,));
- if let Some(&chunk) = world.chunk_position_to_chunk_idx.get(&p) {
- ui.label(format!("Sleeping={}", world.chunks[chunk].sleeping));
+ if let Some(&chunk) = sim.cell_manager.chunk_position_to_chunk_idx.get(&p) {
+ ui.label(format!(
+ "Sleeping={}",
+ sim.cell_manager.chunks[chunk].sleeping
+ ));
}
}
@@ -120,7 +126,10 @@ pub fn draw_egui<'a>(
if let Some((x, y)) = input.last_mouse_world_pos {
ui.heading("Entity");
- if let Some(cell) = world.get_cell_from_game_position(x.round() as i32, y.round() as i32) {
+ if let Some(cell) = sim
+ .cell_manager
+ .get_cell_from_game_position(x.round() as i32, y.round() as i32)
+ {
let material = cell.material.def();
let cell_label = ui.label(
egui::RichText::new(format!("Cell: {}", material.name)).color(Color32::LIGHT_BLUE),
diff --git a/src/sim/cell/materials/fire.rs b/src/sim/cell/materials/fire.rs
index 8667cea..8cabe30 100644
--- a/src/sim/cell/materials/fire.rs
+++ b/src/sim/cell/materials/fire.rs
@@ -2,7 +2,7 @@ use rand::RngExt;
use crate::sim::{
cell::{cell::Cell, materials::MaterialId},
- cell_sim::sim::{PostUpdateAction, UpdateCtx},
+ cell_manager::sim::{PostUpdateAction, UpdateCtx},
};
pub trait FireCellView {
diff --git a/src/sim/cell/materials/gas.rs b/src/sim/cell/materials/gas.rs
index af8f4fd..229d0e9 100644
--- a/src/sim/cell/materials/gas.rs
+++ b/src/sim/cell/materials/gas.rs
@@ -1,6 +1,6 @@
use rand::RngExt;
-use crate::sim::cell_sim::sim::{PostUpdateAction, UpdateCtx};
+use crate::sim::cell_manager::sim::{PostUpdateAction, UpdateCtx};
#[inline]
pub fn sim_update(ctx: &mut UpdateCtx) -> PostUpdateAction {
diff --git a/src/sim/cell/materials/liquid.rs b/src/sim/cell/materials/liquid.rs
index bff0ac0..6c3bdcd 100644
--- a/src/sim/cell/materials/liquid.rs
+++ b/src/sim/cell/materials/liquid.rs
@@ -1,4 +1,4 @@
-use crate::sim::cell_sim::sim::{PostUpdateAction, UpdateCtx};
+use crate::sim::cell_manager::sim::{PostUpdateAction, UpdateCtx};
#[inline]
pub fn sim_update(ctx: &mut UpdateCtx) -> PostUpdateAction {
diff --git a/src/sim/cell/materials/mod.rs b/src/sim/cell/materials/mod.rs
index ee2b4d0..5439cca 100644
--- a/src/sim/cell/materials/mod.rs
+++ b/src/sim/cell/materials/mod.rs
@@ -1,4 +1,4 @@
-use crate::sim::cell_sim::sim::{PostUpdateAction, UpdateCtx};
+use crate::sim::cell_manager::sim::{PostUpdateAction, UpdateCtx};
pub mod fire;
pub mod gas;
diff --git a/src/sim/cell/materials/powder.rs b/src/sim/cell/materials/powder.rs
index e08ea1f..8c916e4 100644
--- a/src/sim/cell/materials/powder.rs
+++ b/src/sim/cell/materials/powder.rs
@@ -1,4 +1,4 @@
-use crate::sim::cell_sim::sim::{PostUpdateAction, UpdateCtx};
+use crate::sim::cell_manager::sim::{PostUpdateAction, UpdateCtx};
#[inline]
pub fn sim_update(ctx: &mut UpdateCtx) -> PostUpdateAction {
diff --git a/src/sim/cell_sim/chunk.rs b/src/sim/cell_manager/chunk.rs
index 3fe5056..3fe5056 100644
--- a/src/sim/cell_sim/chunk.rs
+++ b/src/sim/cell_manager/chunk.rs
diff --git a/src/sim/cell_sim/world.rs b/src/sim/cell_manager/manager.rs
index 924639a..094f345 100644
--- a/src/sim/cell_sim/world.rs
+++ b/src/sim/cell_manager/manager.rs
@@ -2,24 +2,24 @@ use fxhash::FxHashMap;
use crate::{
config::CHUNK_SIZE,
- sim::{cell::cell::Cell, cell_sim::chunk::Chunk},
+ sim::{
+ cell::cell::Cell,
+ cell_manager::{chunk::Chunk, sim::sim_tick},
+ },
};
-pub struct World {
+pub struct CellManager {
+ pub seqno: u64,
pub chunks: Vec<Chunk>,
// TODO FxFxHashMap?
pub chunk_position_to_chunk_idx: FxHashMap<(i32, i32), usize>,
}
-impl World {
+impl CellManager {
#[inline]
pub fn split_game_position(x: i32, y: i32) -> ((i32, i32), (u8, u8)) {
(
- (
- // TODO is this cast expensive?
- x.div_euclid(CHUNK_SIZE),
- y.div_euclid(CHUNK_SIZE),
- ),
+ (x.div_euclid(CHUNK_SIZE), y.div_euclid(CHUNK_SIZE)),
(
x.rem_euclid(CHUNK_SIZE) as u8,
y.rem_euclid(CHUNK_SIZE) as u8,
@@ -29,7 +29,7 @@ impl World {
// VERY EXPENSIVE
pub fn get_cell_from_game_position(&self, x: i32, y: i32) -> Option<Cell> {
- let ((cx, cy), (dx, dy)) = World::split_game_position(x, y);
+ let ((cx, cy), (dx, dy)) = CellManager::split_game_position(x, y);
self.chunk_position_to_chunk_idx
.get(&(cx, cy))
.map(|&idx| self.chunks[idx].get_cell_at_local_position(dx, dy))
@@ -37,7 +37,7 @@ impl World {
// VERY EXPENSIVE
pub fn set_cell_from_game_position(&mut self, x: i32, y: i32, cell: Cell, sleeping: bool) {
- let ((cx, cy), (dx, dy)) = World::split_game_position(x, y);
+ let ((cx, cy), (dx, dy)) = CellManager::split_game_position(x, y);
if let Some(&idx) = self.chunk_position_to_chunk_idx.get(&(cx, cy)) {
self.chunks[idx].set_cell_at_local_position(dx, dy, cell);
// this is a temporary hack
@@ -52,8 +52,15 @@ impl World {
self.chunks.push(chunk);
}
+ pub fn tick(&mut self, use_threading: bool) {
+ let seqno = self.seqno;
+ sim_tick(self, seqno, use_threading);
+ self.seqno += 1;
+ }
+
pub fn from_default_size() -> Self {
- let mut world = World {
+ let mut world = CellManager {
+ seqno: 0,
chunks: Vec::new(),
chunk_position_to_chunk_idx: FxHashMap::default(),
};
diff --git a/src/sim/cell_sim/mod.rs b/src/sim/cell_manager/mod.rs
index b7d0597..67d7614 100644
--- a/src/sim/cell_sim/mod.rs
+++ b/src/sim/cell_manager/mod.rs
@@ -1,3 +1,3 @@
pub mod chunk;
+pub mod manager;
pub mod sim;
-pub mod world;
diff --git a/src/sim/cell_sim/sim.rs b/src/sim/cell_manager/sim.rs
index fbaf2f0..b79608e 100644
--- a/src/sim/cell_sim/sim.rs
+++ b/src/sim/cell_manager/sim.rs
@@ -8,7 +8,7 @@ use crate::{
config::{CHUNK_SIZE, SETTLED_THRESOHLD},
sim::{
cell::{cell::Cell, materials::MaterialDef},
- cell_sim::{chunk::Chunk, world::World},
+ cell_manager::{chunk::Chunk, manager::CellManager},
},
};
@@ -220,7 +220,7 @@ impl UpdateCtx<'_, '_, '_> {
}
}
-pub fn sim_tick_chunk(chunks: &mut [Option<&mut Chunk>; 9], seqno: u64) {
+fn sim_tick_chunk(chunks: &mut [Option<&mut Chunk>; 9], seqno: u64) {
puffin::profile_function!();
let seqno_parity = (seqno as u8) & 0b1;
let mut rng = SmallRng::seed_from_u64(seqno);
@@ -280,7 +280,7 @@ pub fn sim_tick_chunk(chunks: &mut [Option<&mut Chunk>; 9], seqno: u64) {
}
}
-pub fn sim_tick(world: &mut World, seqno: u64, use_threading: bool) {
+pub fn sim_tick(world: &mut CellManager, seqno: u64, use_threading: bool) {
puffin::profile_function!();
let mut columns: FxHashMap<i32, Vec<i32>> = FxHashMap::default();
diff --git a/src/sim/entity/mod.rs b/src/sim/entity/mod.rs
new file mode 100644
index 0000000..ab10ea8
--- /dev/null
+++ b/src/sim/entity/mod.rs
@@ -0,0 +1,28 @@
+// use glam::{IVec2, Vec2};
+// use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
+
+// use crate::sim::cell::cell::Cell;
+
+// pub struct EntityCells {
+// pub size: IVec2,
+// pub cells: Vec<Cell>,
+// }
+// pub trait EntityBehaviour {
+// fn update (&mut self) -> ();
+// }
+
+// pub struct Entity {
+// pub rb_h: Option<RigidBodyHandle>,
+// pub collider_h: Option<ColliderHandle>,
+// pub cells: EntityCells,
+// pub behaviour:
+// }
+
+// impl Entity {
+// pub fn position(&self, rbsm:) -> Vec2 {
+
+// }
+// pub fn destroy(&mut self) -> {
+
+// }
+// }
diff --git a/src/sim/lib/force.rs b/src/sim/lib/force.rs
index 50b5c70..e8f7e90 100644
--- a/src/sim/lib/force.rs
+++ b/src/sim/lib/force.rs
@@ -6,20 +6,16 @@ use crate::sim::{
cell::Cell,
materials::{MaterialId, fire::FireCellView},
},
- cell_sim::world::World,
- particle_sim::{ParticleManager, particle::Particle},
- rb_sim::RbSimManager,
+ particle_manager::particle::Particle,
+ sim_manager::SimManager,
};
pub fn apply_explosion(
+ sim: &mut SimManager,
centre: Vec2,
radius: i32,
force_offset: Vec2,
force_multiplier: f32,
- next_seqno: u64,
- particle_manager: &mut ParticleManager,
- world: &mut World,
- rbsm: &mut RbSimManager,
) {
let get_vel = |pos: Vec2| {
let d = pos.distance(centre);
@@ -38,17 +34,18 @@ pub fn apply_explosion(
// if there's a cell, convert it to a particle
let pos = Vec2::new(centre.x + x as f32, centre.y + y as f32);
let ipos = pos.round().as_ivec2();
- if let Some(cell) = world.get_cell_from_game_position(ipos.x, ipos.y) {
+ if let Some(cell) = sim.cell_manager.get_cell_from_game_position(ipos.x, ipos.y) {
let mut fire = Cell::from_material(MaterialId::Fire);
fire.set_ticks_lived(300);
- fire.match_parity(next_seqno);
+ fire.match_parity(sim.cell_manager.seqno);
if cell.material == MaterialId::Void && random_range(0.0..1.0) > 0.5 {
if random_range(0.0..1.0) > 0.1 {
- world.set_cell_from_game_position(ipos.x, ipos.y, fire, false);
+ sim.cell_manager
+ .set_cell_from_game_position(ipos.x, ipos.y, fire, false);
} else {
let vel = get_vel(pos);
- particle_manager.particles.push(Particle::new(
+ sim.particle_manager.particles.push(Particle::new(
pos,
vel,
MaterialId::Fire,
@@ -56,10 +53,11 @@ pub fn apply_explosion(
));
}
} else {
- world.set_cell_from_game_position(ipos.x, ipos.y, fire, false);
+ sim.cell_manager
+ .set_cell_from_game_position(ipos.x, ipos.y, fire, false);
if random_range(0.0..1.0) > 0.6 {
let vel = get_vel(pos);
- particle_manager.particles.push(Particle::new(
+ sim.particle_manager.particles.push(Particle::new(
pos,
vel,
cell.material,
diff --git a/src/sim/mod.rs b/src/sim/mod.rs
index d9e358b..95fb2db 100644
--- a/src/sim/mod.rs
+++ b/src/sim/mod.rs
@@ -1,66 +1,7 @@
-use crate::sim::{cell::materials::MaterialId, cell_sim::world::World, rb_sim::RbSimManager};
-
pub mod cell;
-pub mod cell_sim;
+pub mod cell_manager;
+pub mod entity;
pub mod lib;
-pub mod particle_sim;
-pub mod rb_sim;
-
-pub fn write_rb_entity_to_world(
- world: &mut World,
- rb_sim_manager: &RbSimManager,
- rb_entity_id: u32,
- // make sure these cells will be simulated
- seqno: u64,
- // (entity_x, entity_y, cell_x, cell_y)
-) -> Vec<(u8, u8, i32, i32)> {
- let mut cells_written: Vec<(u8, u8, i32, i32)> = Vec::new();
- if let Some(rb_entity) = rb_sim_manager.rb_entities.get(&rb_entity_id)
- && let Some((rb_x, rb_y, cos, sin)) = rb_sim_manager.get_rb_entity_transform(rb_entity_id)
- {
- let (half_size_x, half_size_y) =
- (rb_entity.width as f32 / 2.0, rb_entity.height as f32 / 2.0);
- // half-extent of the rotated grid's axis-aligned bounding box, plus a cell of margin
- let (radius_x, radius_y) = (
- half_size_x * (cos.abs() + sin.abs()) + 1.0,
- half_size_y * (cos.abs() + sin.abs()) + 1.0,
- );
-
- let world_xl = (rb_x - radius_x).floor() as i32;
- let world_xu = (rb_x + radius_x).ceil() as i32;
- let world_yl = (rb_y - radius_y).floor() as i32;
- let world_yu = (rb_y + radius_y).ceil() as i32;
-
- for world_x in world_xl..=world_xu {
- for world_y in world_yl..=world_yu {
- if let Some(cur_world_cell) = world.get_cell_from_game_position(world_x, world_y)
- && cur_world_cell.material == MaterialId::Void
- {
- // same as shader
- let d = (world_x as f32 + 0.5 - rb_x, world_y as f32 + 0.5 - rb_y);
- let q = (d.0.floor() + 0.5, d.1.floor() + 0.5);
- let (lx, ly) = (
- (q.0 * cos + q.1 * sin + half_size_x).floor() as i32,
- (-q.0 * sin + q.1 * cos + half_size_y).floor() as i32,
- );
-
- if lx < 0 || ly < 0 || lx >= rb_entity.width || ly >= rb_entity.height {
- continue;
- }
-
- let mut cell = rb_entity.get_cell_at_local_position(lx as u8, ly as u8);
- if cell.material == MaterialId::Void {
- continue;
- }
-
- cell.match_parity(seqno);
-
- // TODO: OPTIMIZE!!
- world.set_cell_from_game_position(world_x, world_y, cell, false);
- cells_written.push((lx as u8, ly as u8, world_x, world_y));
- }
- }
- }
- }
- cells_written
-}
+pub mod particle_manager;
+pub mod rb_manager;
+pub mod sim_manager;
diff --git a/src/sim/particle_sim/mod.rs b/src/sim/particle_manager/mod.rs
index 84a728a..e5e47eb 100644
--- a/src/sim/particle_sim/mod.rs
+++ b/src/sim/particle_manager/mod.rs
@@ -2,9 +2,9 @@ use crate::{
config::PIXELS_TO_METRES,
sim::{
cell::{cell::Cell, materials::MaterialForm},
- cell_sim::world::World,
+ cell_manager::manager::CellManager,
lib::ray::AwDda,
- particle_sim::particle::Particle,
+ particle_manager::particle::Particle,
},
};
@@ -17,7 +17,7 @@ pub struct ParticleManager {
const PARTICLE_GRAVITY: f32 = 9.81 * PIXELS_TO_METRES;
impl ParticleManager {
- pub fn particle_tick(&mut self, world: &mut World, delta_time: f32) {
+ pub fn tick(&mut self, world: &mut CellManager, delta_time: f32) {
puffin::profile_function!();
let mut i = 0;
'outer: while i < self.particles.len() {
diff --git a/src/sim/particle_sim/particle.rs b/src/sim/particle_manager/particle.rs
index cc7dd28..cc7dd28 100644
--- a/src/sim/particle_sim/particle.rs
+++ b/src/sim/particle_manager/particle.rs
diff --git a/src/sim/rb_sim/debug_ops.rs b/src/sim/rb_manager/debug_ops.rs
index d6852da..cb56256 100644
--- a/src/sim/rb_sim/debug_ops.rs
+++ b/src/sim/rb_manager/debug_ops.rs
@@ -2,7 +2,7 @@ use glam::{ivec2, vec2};
use crate::sim::{
cell::{cell::Cell, materials::MaterialId},
- rb_sim::RbSimManager,
+ rb_manager::RbManager,
};
pub trait DebugOperator {
@@ -10,7 +10,7 @@ pub trait DebugOperator {
fn test_spawn_ball(&mut self, x: f32, y: f32, material: MaterialId) -> ();
}
-impl DebugOperator for RbSimManager {
+impl DebugOperator for RbManager {
fn test_spawn_box(&mut self, x: f32, y: f32, material: MaterialId) {
let w = 10;
let h = 10;
diff --git a/src/sim/rb_sim/debug_render.rs b/src/sim/rb_manager/debug_render.rs
index 342fc5b..342fc5b 100644
--- a/src/sim/rb_sim/debug_render.rs
+++ b/src/sim/rb_manager/debug_render.rs
diff --git a/src/sim/rb_sim/mod.rs b/src/sim/rb_manager/mod.rs
index f81d5f1..2c64f12 100644
--- a/src/sim/rb_sim/mod.rs
+++ b/src/sim/rb_manager/mod.rs
@@ -10,9 +10,9 @@ use crate::{
config::{CHUNK_SIZE, PHYSICS_DELTA_TIME, PIXELS_TO_METRES},
sim::{
cell::cell::Cell,
- cell_sim::chunk::Chunk,
+ cell_manager::chunk::Chunk,
lib::marching_squares::{Marchable, marching_squares_vertex_trace},
- rb_sim::{
+ rb_manager::{
debug_render::{DebugLineBuffer, DebugVertex},
rb_entity::RbEntity,
},
@@ -65,7 +65,7 @@ impl PhysicsManager {
}
}
-pub struct RbSimManager {
+pub struct RbManager {
chunk_colliders: FxHashMap<(i32, i32), geometry::ColliderHandle>,
pub rb_entities: FxHashMap<u32, RbEntity>,
@@ -74,8 +74,8 @@ pub struct RbSimManager {
debug_line_buffer: DebugLineBuffer,
}
-impl RbSimManager {
- pub fn rb_tick(&mut self, delta_time: f32) {
+impl RbManager {
+ pub fn tick(&mut self, _delta_time: f32) {
let gravity = vec2(0.0, 9.81);
self.physics_manager.physics_pipeline.step(
@@ -277,7 +277,7 @@ impl RbSimManager {
}
pub fn new() -> Self {
- RbSimManager {
+ RbManager {
rb_entities: FxHashMap::default(),
chunk_colliders: FxHashMap::default(),
diff --git a/src/sim/rb_sim/rb_entity.rs b/src/sim/rb_manager/rb_entity.rs
index 18a6693..18a6693 100644
--- a/src/sim/rb_sim/rb_entity.rs
+++ b/src/sim/rb_manager/rb_entity.rs
diff --git a/src/sim/sim_manager/mod.rs b/src/sim/sim_manager/mod.rs
new file mode 100644
index 0000000..a98f3b2
--- /dev/null
+++ b/src/sim/sim_manager/mod.rs
@@ -0,0 +1,152 @@
+use std::time::Instant;
+
+use crate::{
+ Config,
+ config::{PHYSICS_DELTA_TIME, PHYSICS_FPS, SIM_FPS},
+ sim::{
+ cell::{cell::Cell, materials::MaterialId},
+ cell_manager::manager::CellManager,
+ particle_manager::ParticleManager,
+ rb_manager::RbManager,
+ sim_manager::utils::write_rb_entity_to_world,
+ },
+};
+
+mod utils;
+
+pub struct SimManager {
+ // timing
+ pub paused: bool,
+ pub ignore_pause_next_tick: bool,
+ pub last_cell_update: Instant,
+ pub cell_updates_due: f32,
+ pub last_physics_update: Instant,
+ pub physics_updates_due: f32,
+
+ // systems
+ pub cell_manager: CellManager,
+ pub rb_manager: RbManager,
+ pub particle_manager: ParticleManager,
+}
+
+impl SimManager {
+ pub fn new() -> Self {
+ SimManager {
+ paused: false,
+ ignore_pause_next_tick: false,
+ last_cell_update: Instant::now(),
+ cell_updates_due: 0.0,
+ last_physics_update: Instant::now(),
+ physics_updates_due: 0.0,
+ cell_manager: CellManager::from_default_size(),
+ rb_manager: RbManager::new(),
+ particle_manager: ParticleManager::new(),
+ }
+ }
+
+ fn cell_update(&mut self, config: &Config) {
+ // before we tick, write all the rb entities into the sim world
+ // TODO optimize
+ let entity_ids: Vec<u32> = self.rb_manager.rb_entities.keys().copied().collect();
+ let mut cells_written_by_entity: Vec<(u32, Vec<(u8, u8, i32, i32)>)> = Vec::new();
+
+ for entity_id in entity_ids {
+ let cells_written = write_rb_entity_to_world(self, entity_id, self.cell_manager.seqno);
+ cells_written_by_entity.push((entity_id, cells_written));
+ }
+
+ self.cell_manager.tick(config.use_threading);
+
+ // after we tick, remove the written rb cells and update the entities
+ // TODO optimize
+ for (entity_id, cells_written) in cells_written_by_entity {
+ let rb_entity = self.rb_manager.rb_entities.get_mut(&entity_id).unwrap();
+ for (lx, ly, x, y) in cells_written {
+ // update the entity
+ // TODO we should skip cells that weren't changed?
+ // TODO optimize
+ let new_local_cell = self.cell_manager.get_cell_from_game_position(x, y).unwrap();
+ if new_local_cell.material != MaterialId::Void && !new_local_cell.rb() {
+ panic!(
+ "Someone swapped into this rb's cell! ({x}, {y}, {m:#?}, {f})",
+ m = new_local_cell.material,
+ f = new_local_cell.flags
+ );
+ }
+ rb_entity.set_cell_at_local_position(lx, ly, new_local_cell);
+ // update the world
+ self.cell_manager
+ .set_cell_from_game_position(x, y, Cell::void(), false);
+ }
+ }
+ }
+
+ fn physics_update(&mut self, delta_time: f32) {
+ // before we move the rigidbodies, upsert the current terrain state
+ // TODO use the chunk sleeping, and make this range dynamic
+ for cx in -5..5 {
+ for cy in -5..5 {
+ if let Some(chunk) = self
+ .cell_manager
+ .chunk_position_to_chunk_idx
+ .get(&(cx, cy))
+ .map(|&idx| &self.cell_manager.chunks[idx])
+ && !chunk.sleeping
+ {
+ self.rb_manager.upsert_chunk_collider(cx, cy, chunk);
+ }
+ }
+ }
+
+ // move the rbs
+ self.rb_manager.tick(delta_time);
+
+ // move the particles
+ self.particle_manager
+ .tick(&mut self.cell_manager, delta_time);
+ }
+
+ pub fn update(&mut self, config: &Config, _delta_time: f32) -> () {
+ let now = Instant::now();
+ let secs_since_last_cell_update = (now - self.last_cell_update).as_secs_f32();
+ let expected_secs_since_last_cell_update = 1.0 / SIM_FPS as f32;
+
+ self.last_cell_update = now;
+ if self.paused && self.ignore_pause_next_tick {
+ self.cell_update(config);
+ } else if !self.paused {
+ self.cell_updates_due +=
+ secs_since_last_cell_update / expected_secs_since_last_cell_update;
+ let mut cell_updates_done = 0;
+ // don't ever update more than 3 times per frame, or else we can get a pseudo deadlock
+ while self.cell_updates_due >= 1.0 && cell_updates_done < 3 {
+ self.cell_update(config);
+ self.cell_updates_due -= 1.0;
+ cell_updates_done += 1;
+ }
+ self.cell_updates_due = self.cell_updates_due.min(3.0);
+ }
+
+ // PHYSICS UPDATE
+ let secs_since_last_physics_update = (now - self.last_physics_update).as_secs_f32();
+ let expected_secs_since_last_physics_update = 1.0 / PHYSICS_FPS as f32;
+
+ self.last_physics_update = now;
+ if self.paused && self.ignore_pause_next_tick {
+ self.physics_update(PHYSICS_DELTA_TIME);
+ } else if !self.paused {
+ self.physics_updates_due +=
+ secs_since_last_physics_update / expected_secs_since_last_physics_update;
+ let mut updates_done = 0;
+ // don't ever update more than 3 times per frame, or else we can get a pseudo deadlock
+ while self.physics_updates_due >= 1.0 && updates_done < 3 {
+ self.physics_update(PHYSICS_DELTA_TIME);
+ self.physics_updates_due -= 1.0;
+ updates_done += 1;
+ }
+ self.physics_updates_due = self.physics_updates_due.min(3.0);
+ }
+
+ self.ignore_pause_next_tick = false;
+ }
+}
diff --git a/src/sim/sim_manager/utils.rs b/src/sim/sim_manager/utils.rs
new file mode 100644
index 0000000..b636186
--- /dev/null
+++ b/src/sim/sim_manager/utils.rs
@@ -0,0 +1,62 @@
+use crate::sim::{cell::materials::MaterialId, sim_manager::SimManager};
+
+pub fn write_rb_entity_to_world(
+ sim: &mut SimManager,
+ rb_entity_id: u32,
+ // make sure these cells will be simulated
+ seqno: u64,
+ // (entity_x, entity_y, cell_x, cell_y)
+) -> Vec<(u8, u8, i32, i32)> {
+ let mut cells_written: Vec<(u8, u8, i32, i32)> = Vec::new();
+ if let Some(rb_entity) = sim.rb_manager.rb_entities.get(&rb_entity_id)
+ && let Some((rb_x, rb_y, cos, sin)) = sim.rb_manager.get_rb_entity_transform(rb_entity_id)
+ {
+ let (half_size_x, half_size_y) =
+ (rb_entity.width as f32 / 2.0, rb_entity.height as f32 / 2.0);
+ // half-extent of the rotated grid's axis-aligned bounding box, plus a cell of margin
+ let (radius_x, radius_y) = (
+ half_size_x * (cos.abs() + sin.abs()) + 1.0,
+ half_size_y * (cos.abs() + sin.abs()) + 1.0,
+ );
+
+ let world_xl = (rb_x - radius_x).floor() as i32;
+ let world_xu = (rb_x + radius_x).ceil() as i32;
+ let world_yl = (rb_y - radius_y).floor() as i32;
+ let world_yu = (rb_y + radius_y).ceil() as i32;
+
+ for world_x in world_xl..=world_xu {
+ for world_y in world_yl..=world_yu {
+ if let Some(cur_world_cell) = sim
+ .cell_manager
+ .get_cell_from_game_position(world_x, world_y)
+ && cur_world_cell.material == MaterialId::Void
+ {
+ // same as shader
+ let d = (world_x as f32 + 0.5 - rb_x, world_y as f32 + 0.5 - rb_y);
+ let q = (d.0.floor() + 0.5, d.1.floor() + 0.5);
+ let (lx, ly) = (
+ (q.0 * cos + q.1 * sin + half_size_x).floor() as i32,
+ (-q.0 * sin + q.1 * cos + half_size_y).floor() as i32,
+ );
+
+ if lx < 0 || ly < 0 || lx >= rb_entity.width || ly >= rb_entity.height {
+ continue;
+ }
+
+ let mut cell = rb_entity.get_cell_at_local_position(lx as u8, ly as u8);
+ if cell.material == MaterialId::Void {
+ continue;
+ }
+
+ cell.match_parity(seqno);
+
+ // TODO: OPTIMIZE!!
+ sim.cell_manager
+ .set_cell_from_game_position(world_x, world_y, cell, false);
+ cells_written.push((lx as u8, ly as u8, world_x, world_y));
+ }
+ }
+ }
+ }
+ cells_written
+}