summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-16 23:52:34 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-16 23:52:34 -0700
commit3d476186f53d695c340c51dbd3aa14ff4621c520 (patch)
tree32e7c9ea79ca6de3160af4566f68273a20fa8e81
parentf68d8371a11b002f5494c7294551a8f955e69c0c (diff)
trivial placement
-rw-r--r--src/main.rs36
-rw-r--r--src/renderer/mod.rs19
-rw-r--r--src/renderer/ui.rs31
-rw-r--r--src/sim/cell/cell.rs5
-rw-r--r--src/sim/cell_sim/sim.rs54
-rw-r--r--src/sim/rb_sim/mod.rs57
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,
}
}
}