diff options
| author | Kai Stevenson <kai@kaistevenson.com> | 2026-08-16 23:52:34 -0700 |
|---|---|---|
| committer | Kai Stevenson <kai@kaistevenson.com> | 2026-08-16 23:52:34 -0700 |
| commit | 3d476186f53d695c340c51dbd3aa14ff4621c520 (patch) | |
| tree | 32e7c9ea79ca6de3160af4566f68273a20fa8e81 /src | |
| parent | f68d8371a11b002f5494c7294551a8f955e69c0c (diff) | |
trivial placement
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 36 | ||||
| -rw-r--r-- | src/renderer/mod.rs | 19 | ||||
| -rw-r--r-- | src/renderer/ui.rs | 31 | ||||
| -rw-r--r-- | src/sim/cell/cell.rs | 5 | ||||
| -rw-r--r-- | src/sim/cell_sim/sim.rs | 54 | ||||
| -rw-r--r-- | src/sim/rb_sim/mod.rs | 57 |
6 files changed, 166 insertions, 36 deletions
diff --git a/src/main.rs b/src/main.rs index 976a272..ccc5ec5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod renderer; mod sim; use futures::executor; +use fxhash::FxHashMap; use rand::random_range; use std::{collections::VecDeque, sync::Arc, time::Instant}; use winit::{ @@ -23,8 +24,11 @@ use crate::{ renderer::RendererState, sim::{ cell::{cell::Cell, materials::MaterialId}, - cell_sim::{sim::sim_tick, world::World}, - rb_sim::{PhysicsManager, RbSimManager}, + cell_sim::{ + sim::{sim_tick, write_rb_entity_to_world}, + world::World, + }, + rb_sim::RbSimManager, }, }; @@ -34,6 +38,7 @@ pub type Result<T> = std::result::Result<T, Error>; struct Config { brush_radius: f32, brush_material: MaterialId, + dropper_material: MaterialId, use_threading: bool, } @@ -102,7 +107,7 @@ impl App { && let Some(rbsm) = &mut self.rb_sim_manager { self.input.trigger_test_1 = false; - rbsm.test_spawn_box(lm.0, lm.1); + rbsm.test_spawn_box(lm.0, lm.1, self.config.dropper_material); } // --TEST DRAWING-- @@ -125,8 +130,8 @@ impl App { && r > 0.9 { let mut cell = Cell::from_material(self.config.brush_material); + cell.match_parity(self.sim_seqno); // ensure we simulate on the first tick - cell.flags = (self.sim_seqno as u8) & 0b1; if let Some(world) = &mut self.world { world.set_cell_from_game_position( x, y, cell, false, // wake the chunk @@ -196,6 +201,7 @@ impl Default for App { use_threading: true, brush_radius: 10.0, brush_material: MaterialId::Sand, + dropper_material: MaterialId::Sand, }, diagnostics: Diagnostics { fps: 0.0, @@ -263,6 +269,28 @@ impl ApplicationHandler for App { 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::KeyV => { + self.rb_sim_manager + .as_mut() + .map(|rbsm| rbsm.rb_entities = FxHashMap::default()); + } + 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::KeyQ if pressed && !repeat => self.input.trigger_test_1 = true, diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 39a507b..3076894 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -492,15 +492,16 @@ impl RendererState { } for (id, slot) in &self.renderer_rb_entities { - let (x, y, cos, sin) = rb_sim_manager.get_rb_entity_transform(*id).unwrap(); - instances.push(Instance { - centre: [x, y], - cos_sin: [cos, sin], - half_size, - dims, - cell_offset: (slot * CELLS_IN_CHUNK) as u32, - _padding: 0, - }); + if let Some((x, y, cos, sin)) = rb_sim_manager.get_rb_entity_transform(*id) { + instances.push(Instance { + centre: [x, y], + cos_sin: [cos, sin], + half_size, + dims, + cell_offset: (slot * CELLS_IN_CHUNK) as u32, + _padding: 0, + }); + } } self.queue diff --git a/src/renderer/ui.rs b/src/renderer/ui.rs index 77af59d..590c720 100644 --- a/src/renderer/ui.rs +++ b/src/renderer/ui.rs @@ -20,7 +20,7 @@ pub fn draw_egui<'a>( let material = config.brush_material.def(); // material combobox - egui::ComboBox::from_label("Select a material") + egui::ComboBox::from_label("Select a brush material") .selected_text(format!( "Material: [{}, d={}]", material.name, material.density, @@ -43,6 +43,35 @@ pub fn draw_egui<'a>( } }); + let dropper_material = config.brush_material.def(); + + egui::ComboBox::from_label("Select a dropper material") + .selected_text(format!( + "Material: [{}, d={}]", + dropper_material.name, dropper_material.density, + )) + .icon(move |ui, rect, _, _| { + ui.painter().add(egui::Shape::Circle(CircleShape { + center: rect.center(), + radius: rect.height() / 2.0, + stroke: Stroke::NONE, + fill: Color32::from_rgb( + dropper_material.color.0, + dropper_material.color.1, + dropper_material.color.2, + ), + })); + }) + .show_ui(ui, |ui| { + for material_id in MaterialId::ALL { + ui.selectable_value( + &mut config.dropper_material, + material_id, + material_id.def().name, + ); + } + }); + ui.checkbox(&mut config.use_threading, "Use multithreading"); ui.label(format!("Real FPS: {}", diagnostics.fps)); ui.heading("Camera"); diff --git a/src/sim/cell/cell.rs b/src/sim/cell/cell.rs index d35fa76..2664721 100644 --- a/src/sim/cell/cell.rs +++ b/src/sim/cell/cell.rs @@ -18,6 +18,11 @@ impl Cell { pub fn flip_parity(&mut self) { self.flags ^= Self::FLAG_PARITY; } + #[inline] + pub fn match_parity(&mut self, seqno: u64) { + // TODO this will break with more flags + self.flags = (seqno as u8) & 0b1; + } } impl Cell { diff --git a/src/sim/cell_sim/sim.rs b/src/sim/cell_sim/sim.rs index fd9b6c9..0f02a23 100644 --- a/src/sim/cell_sim/sim.rs +++ b/src/sim/cell_sim/sim.rs @@ -5,10 +5,14 @@ use rand::{Rng, SeedableRng, rngs::SmallRng}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use crate::{ - config::CHUNK_SIZE, + config::{CELLS_IN_CHUNK, CHUNK_SIZE}, sim::{ - cell::{cell::Cell, materials::MaterialDef}, + cell::{ + cell::Cell, + materials::{MaterialDef, MaterialId}, + }, cell_sim::{chunk::Chunk, world::World}, + rb_sim::{RbSimManager, rb_entity::RbEntity}, }, }; @@ -177,6 +181,52 @@ const NEIGHBORHOOD_OFFSETS: [(i32, i32); 9] = [ (1, 1), ]; +pub fn write_rb_entity_to_world( + world: &mut World, + rb_sim_manager: &RbSimManager, + rb_entity_id: u32, + next_seqno: u64, +) { + 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 = CHUNK_SIZE as f32 / 2.0; + // half-extent of the rotated grid's axis-aligned bounding box, plus a cell of margin + let radius = half_size * (cos.abs() + sin.abs()) + 1.0; + + let world_xl = (rb_x - radius).floor() as i32; + let world_xu = (rb_x + radius).ceil() as i32; + let world_yl = (rb_y - radius).floor() as i32; + let world_yu = (rb_y + radius).ceil() as i32; + + for world_x in world_xl..=world_xu { + for world_y in world_yl..=world_yu { + // 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).floor() as i32, + (-q.0 * sin + q.1 * cos + half_size).floor() as i32, + ); + + if lx < 0 || ly < 0 || lx >= CHUNK_SIZE || ly >= CHUNK_SIZE { + 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(next_seqno); + + // TODO: OPTIMIZE!! + world.set_cell_from_game_position(world_x, world_y, cell, false); + } + } + } +} + pub fn sim_tick(world: &mut World, seqno: u64, use_threading: bool) { puffin::profile_function!(); diff --git a/src/sim/rb_sim/mod.rs b/src/sim/rb_sim/mod.rs index d76a721..10172a1 100644 --- a/src/sim/rb_sim/mod.rs +++ b/src/sim/rb_sim/mod.rs @@ -54,7 +54,7 @@ impl PhysicsManager { pub struct RbSimManager { physics_manager: PhysicsManager, pub rb_entities: FxHashMap<u32, RbEntity>, - last_id: u32, + next_id: u32, } impl RbSimManager { @@ -77,6 +77,36 @@ impl RbSimManager { ); } + pub fn create_rb_entity( + &mut self, + cells: Box<[Cell; CELLS_IN_CHUNK]>, + rb_parent: prelude::RigidBodyHandle, + ) { + let id = self.next_id; + self.next_id += 1; + let entity: RbEntity = RbEntity { + id, + cells, + rb_parent, + }; + + self.rb_entities.insert(id, entity); + } + + pub fn destroy_rb_entity(&mut self, entity_id: u32) { + if let Some(entity) = self.rb_entities.get(&entity_id) { + self.physics_manager.rigid_body_set.remove( + entity.rb_parent, + &mut self.physics_manager.island_manager, + &mut self.physics_manager.collider_set, + &mut self.physics_manager.impulse_joint_set, + &mut self.physics_manager.multibody_joint_set, + true, + ); + self.rb_entities.remove(&entity_id); + } + } + pub fn get_rb_entity_transform(&self, entity_id: u32) -> Option<(f32, f32, f32, f32)> { let entity = self.rb_entities.get(&entity_id); match entity { @@ -109,7 +139,7 @@ impl RbSimManager { self.physics_manager.collider_set.insert(collider); } - pub fn test_spawn_box(&mut self, x: f32, y: f32) { + pub fn test_spawn_box(&mut self, x: f32, y: f32, material: MaterialId) { /* Create the bouncing ball. */ let rigid_body = dynamics::RigidBodyBuilder::dynamic() .translation(math::Vector::new( @@ -127,36 +157,23 @@ impl RbSimManager { &mut self.physics_manager.rigid_body_set, ); - let id = self.last_id; - self.last_id += 1; - - let test_cells = Box::new([Cell::void(); CELLS_IN_CHUNK]); - let mut rb_entity = RbEntity { - id, - cells: test_cells, - rb_parent: ball_body_handle, - }; + let mut test_cells = Box::new([Cell::void(); CELLS_IN_CHUNK]); - let mut idx = 0; for x in CHUNK_SIZE / 2 - 5..CHUNK_SIZE / 2 + 5 { for y in CHUNK_SIZE / 2 - 5..CHUNK_SIZE / 2 + 5 { - idx += 1; - rb_entity.set_cell_at_local_position( - x as u8, - y as u8, - Cell::from_material(MaterialId::from_index(1 + idx % 6)), - ); + let cell_idx = x + y * CHUNK_SIZE; + test_cells[cell_idx as usize] = Cell::from_material(material); } } - self.rb_entities.insert(id, rb_entity); + self.create_rb_entity(test_cells, ball_body_handle); } pub fn new() -> Self { RbSimManager { physics_manager: PhysicsManager::new(), rb_entities: FxHashMap::default(), - last_id: 0, + next_id: 0, } } } |
