diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 389 | ||||
| -rw-r--r-- | src/renderer/mod.rs | 36 | ||||
| -rw-r--r-- | src/renderer/ui.rs | 19 | ||||
| -rw-r--r-- | src/sim/cell/materials/fire.rs | 2 | ||||
| -rw-r--r-- | src/sim/cell/materials/gas.rs | 2 | ||||
| -rw-r--r-- | src/sim/cell/materials/liquid.rs | 2 | ||||
| -rw-r--r-- | src/sim/cell/materials/mod.rs | 2 | ||||
| -rw-r--r-- | src/sim/cell/materials/powder.rs | 2 | ||||
| -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.rs | 28 | ||||
| -rw-r--r-- | src/sim/lib/force.rs | 24 | ||||
| -rw-r--r-- | src/sim/mod.rs | 69 | ||||
| -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.rs | 152 | ||||
| -rw-r--r-- | src/sim/sim_manager/utils.rs | 62 |
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 +} |
