summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-16 17:29:42 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-16 17:29:42 -0700
commitf177cc716c5f2aa5b50b14ccbb421de89e3a7854 (patch)
treedb06f9f44251d6d90e5e9709d47e6cc4397b9443 /src
parent40e9d818195824749293dff3afcfdd5c1432adbe (diff)
wip
Diffstat (limited to 'src')
-rw-r--r--src/config.rs2
-rw-r--r--src/main.rs32
-rw-r--r--src/renderer/mod.rs250
-rw-r--r--src/renderer/ui.rs2
-rw-r--r--src/sim/cell/cell.rs (renamed from src/sim/cell.rs)2
-rw-r--r--src/sim/cell/materials/fire.rs (renamed from src/sim/materials/fire.rs)5
-rw-r--r--src/sim/cell/materials/gas.rs (renamed from src/sim/materials/gas.rs)2
-rw-r--r--src/sim/cell/materials/mod.rs (renamed from src/sim/materials/mod.rs)2
-rw-r--r--src/sim/cell/materials/sand.rs (renamed from src/sim/materials/sand.rs)2
-rw-r--r--src/sim/cell/materials/smoke.rs (renamed from src/sim/materials/smoke.rs)2
-rw-r--r--src/sim/cell/materials/water.rs (renamed from src/sim/materials/water.rs)2
-rw-r--r--src/sim/cell/mod.rs2
-rw-r--r--src/sim/cell_sim/chunk.rs (renamed from src/sim/chunk.rs)2
-rw-r--r--src/sim/cell_sim/mod.rs4
-rw-r--r--src/sim/cell_sim/overlay.rs (renamed from src/sim/overlay.rs)2
-rw-r--r--src/sim/cell_sim/sim.rs (renamed from src/sim/sim.rs)5
-rw-r--r--src/sim/cell_sim/world.rs (renamed from src/sim/world.rs)2
-rw-r--r--src/sim/mod.rs7
-rw-r--r--src/sim/rb_sim/mod.rs131
-rw-r--r--src/sim/rb_sim/rb_entity.rs24
20 files changed, 393 insertions, 89 deletions
diff --git a/src/config.rs b/src/config.rs
index 6a9d392..8739c53 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -6,6 +6,6 @@ pub const CELLS_IN_CHUNK: usize = (CHUNK_SIZE * CHUNK_SIZE) as usize;
pub const CAMERA_MOVEMENT_SPEED: f32 = 40.0;
pub const SIM_FPS: u32 = 120;
-pub const SIM_DELTA_TIME: f32 = 1.0 / SIM_FPS as f32;
+// pub const SIM_DELTA_TIME: f32 = 1.0 / SIM_FPS as f32;
pub const PHYSICS_FPS: u32 = 60;
pub const PHYSICS_DELTA_TIME: f32 = 1.0 / PHYSICS_FPS as f32;
diff --git a/src/main.rs b/src/main.rs
index bb4cb27..a01dbbd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -19,9 +19,13 @@ use winit::{
use crate::{
camera::Camera,
- config::{PHYSICS_DELTA_TIME, PHYSICS_FPS, SIM_DELTA_TIME, SIM_FPS, WINDOW_TITLE},
+ config::{PHYSICS_DELTA_TIME, PHYSICS_FPS, SIM_FPS, WINDOW_TITLE},
renderer::RendererState,
- sim::{cell::Cell, materials::MaterialId, sim::sim_tick, world::World},
+ sim::{
+ cell::{cell::Cell, materials::MaterialId},
+ cell_sim::{sim::sim_tick, world::World},
+ rb_sim::{PhysicsManager, RbSimManager},
+ },
};
pub type Error = Box<dyn std::error::Error>;
@@ -58,8 +62,12 @@ struct App {
camera: Option<Camera>,
+ // grid
world: Option<World>,
+ // physics
+ rb_sim_manager: Option<RbSimManager>,
+
// sim state
// the last/current (not yet completed) seqno
sim_seqno: u64,
@@ -122,8 +130,7 @@ impl App {
// 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
- // sim_delta_time is statically 1/SIM_FPS
- fn sim_update(&mut self, _sim_delta_time: f32) {
+ fn sim_update(&mut self) {
if let Some(world) = &mut self.world {
sim_tick(world, self.sim_seqno, self.config.use_threading);
self.sim_seqno += 1;
@@ -133,7 +140,11 @@ impl App {
// 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) {}
+ fn physics_update(&mut self, physics_delta_time: f32) {
+ if let Some(physics_manager) = &mut self.rb_sim_manager {
+ physics_manager.rb_tick(physics_delta_time);
+ }
+ }
}
impl Default for App {
@@ -157,6 +168,8 @@ impl Default for App {
world: Some(World::from_default_size()),
+ rb_sim_manager: Some(RbSimManager::new()),
+
sim_seqno: 0,
sim_paused: false,
ignore_pause_next_tick: false,
@@ -191,6 +204,9 @@ impl ApplicationHandler for App {
self.window = Some(window.clone());
self.renderer_state = Some(executor::block_on(RendererState::new(window.clone())));
+ if let Some(rbsm) = &mut self.rb_sim_manager {
+ rbsm.test();
+ }
let size = window.inner_size();
self.camera = Some(Camera::new((size.width as i32, size.height as i32)));
@@ -300,7 +316,7 @@ impl ApplicationHandler for App {
self.last_sim_update = now;
if self.sim_paused && self.ignore_pause_next_tick {
- self.sim_update(SIM_DELTA_TIME);
+ self.sim_update();
self.ignore_pause_next_tick = false;
} else if !self.sim_paused {
self.sim_updates_due +=
@@ -308,7 +324,7 @@ impl ApplicationHandler for App {
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(SIM_DELTA_TIME);
+ self.sim_update();
updates_done += 1;
}
self.sim_updates_due -= updates_done as f32;
@@ -333,10 +349,12 @@ impl ApplicationHandler for App {
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(camera) = &mut self.camera
{
renderer_state.render(
world,
+ rb_sim_manager,
camera,
&mut self.config,
&self.diagnostics,
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 5231bc3..e022fe7 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -2,6 +2,7 @@ mod ui;
use std::sync::Arc;
+use fxhash::FxHashMap;
use winit::window::Window;
use crate::{
@@ -9,7 +10,7 @@ use crate::{
camera::Camera,
config::{CELLS_IN_CHUNK, CHUNK_SIZE},
renderer::ui::draw_egui,
- sim::world::World,
+ sim::{cell_sim::world::World, rb_sim::RbSimManager},
};
struct RendererChunk {
@@ -17,6 +18,11 @@ struct RendererChunk {
bind_group: wgpu::BindGroup,
}
+struct RendererRbEntity {
+ texture: wgpu::Texture,
+ bind_group: wgpu::BindGroup,
+}
+
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct ChunkData {
@@ -42,6 +48,8 @@ pub struct RendererState {
pixels_pipeline: wgpu::RenderPipeline,
camera_uniform_buffer: wgpu::Buffer,
camera_uniform_bind_group: wgpu::BindGroup,
+
+ renderer_rb_entities: FxHashMap<u32, RendererRbEntity>,
}
impl RendererState {
@@ -270,6 +278,8 @@ impl RendererState {
pixels_pipeline,
camera_uniform_bind_group,
camera_uniform_buffer,
+
+ renderer_rb_entities: FxHashMap::default(),
}
}
@@ -285,6 +295,7 @@ impl RendererState {
pub fn render(
&mut self,
world: &mut World,
+ rb_sim_manager: &RbSimManager,
camera: &mut Camera,
config: &mut Config,
diagnostics: &Diagnostics,
@@ -420,81 +431,192 @@ impl RendererState {
}
{
- puffin::profile_scope!("Main render pass");
- let mut render_pass: wgpu::RenderPass<'_> =
- encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("Render Pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: &view,
- resolve_target: None,
- depth_slice: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear(wgpu::Color {
- r: 0.0,
- g: 0.0,
- b: 0.0,
- a: 1.0,
- }),
- store: wgpu::StoreOp::Store,
+ puffin::profile_scope!("Upload entity textures");
+ for entity in rb_sim_manager.rb_entities.values() {
+ // TODO add "needs texture update"?
+
+ // TODO use material palette to improve bandwidth of upload
+ for i in 0..CELLS_IN_CHUNK {
+ let material = entity.cells[i].material.def();
+ chunk_buffer[i * 4] = material.color.0;
+ chunk_buffer[i * 4 + 1] = material.color.1;
+ chunk_buffer[i * 4 + 2] = material.color.2;
+ chunk_buffer[i * 4 + 3] = material.color.3;
+ }
+
+ let render_entity = &self.renderer_rb_entities.entry(entity.id).or_insert({
+ let texture = self.device.create_texture(&wgpu::TextureDescriptor {
+ label: None,
+ mip_level_count: 1,
+ sample_count: 1,
+ usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
+ format: wgpu_types::TextureFormat::Rgba8UnormSrgb,
+ size: wgpu::Extent3d {
+ width: CHUNK_SIZE as u32,
+ height: CHUNK_SIZE as u32,
+ depth_or_array_layers: 1,
},
- })],
- depth_stencil_attachment: None,
- occlusion_query_set: None,
- timestamp_writes: None,
- multiview_mask: None,
+ dimension: wgpu::TextureDimension::D2,
+ view_formats: &[],
+ });
+
+ let bind_group_layout =
+ self.device
+ .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: None,
+ entries: &[wgpu::BindGroupLayoutEntry {
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float {
+ filterable: true,
+ },
+ view_dimension: wgpu::TextureViewDimension::D2,
+ multisampled: false,
+ },
+ binding: 0,
+ count: None,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ }],
+ });
+
+ let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: None,
+ layout: &bind_group_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(&texture.create_view(
+ &wgpu::TextureViewDescriptor {
+ dimension: Some(wgpu::TextureViewDimension::D2),
+ usage: Some(
+ wgpu::TextureUsages::TEXTURE_BINDING
+ | wgpu::TextureUsages::COPY_DST,
+ ),
+ ..wgpu::TextureViewDescriptor::default()
+ },
+ )),
+ }],
+ });
+
+ RendererRbEntity {
+ texture,
+ bind_group,
+ }
});
- render_pass.set_pipeline(&self.pixels_pipeline);
+ self.queue.write_texture(
+ wgpu::TexelCopyTextureInfo {
+ texture: &render_entity.texture,
+ aspect: wgpu::TextureAspect::All,
+ mip_level: 0,
+ origin: wgpu::Origin3d::ZERO,
+ },
+ &chunk_buffer,
+ wgpu::TexelCopyBufferLayout {
+ bytes_per_row: Some(CHUNK_SIZE as u32 * 4),
+ offset: 0,
+ rows_per_image: Some(CHUNK_SIZE as u32),
+ },
+ wgpu::Extent3d {
+ width: CHUNK_SIZE as u32,
+ height: CHUNK_SIZE as u32,
+ depth_or_array_layers: 1,
+ },
+ );
+ }
+
+ {
+ puffin::profile_scope!("Main render pass");
+ let mut render_pass: wgpu::RenderPass<'_> =
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("Render Pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: &view,
+ resolve_target: None,
+ depth_slice: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Clear(wgpu::Color {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 1.0,
+ }),
+ store: wgpu::StoreOp::Store,
+ },
+ })],
+ depth_stencil_attachment: None,
+ occlusion_query_set: None,
+ timestamp_writes: None,
+ multiview_mask: None,
+ });
- render_pass.set_bind_group(0, &self.camera_uniform_bind_group, &[]);
+ render_pass.set_pipeline(&self.pixels_pipeline);
- 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);
+ render_pass.set_bind_group(0, &self.camera_uniform_bind_group, &[]);
- // TODO only visible chunks
- for cx in cxl..=cxu {
- for cy in cyl..=cyu {
- if let Some(idx) = world.chunk_position_to_chunk_idx.get(&(cx, cy)) {
- let render_chunk = &self.renderer_chunks[*idx];
- render_pass.set_bind_group(1, &render_chunk.bind_group, &[]);
- render_pass
- .set_immediates(0, bytemuck::bytes_of(&ChunkData { origin: [cx, cy] }));
- render_pass.draw(0..4, 0..1);
+ 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);
+
+ for cx in cxl..=cxu {
+ for cy in cyl..=cyu {
+ if let Some(idx) = world.chunk_position_to_chunk_idx.get(&(cx, cy)) {
+ let render_chunk = &self.renderer_chunks[*idx];
+ render_pass.set_bind_group(1, &render_chunk.bind_group, &[]);
+ render_pass.set_immediates(
+ 0,
+ bytemuck::bytes_of(&ChunkData { origin: [cx, cy] }),
+ );
+ render_pass.draw(0..4, 0..1);
+ }
}
}
+
+ // TODO this is inefficient
+ for rb_entity in rb_sim_manager.rb_entities.keys() {
+ let renderer_rb_entity = self.renderer_rb_entities.get(rb_entity).unwrap();
+ let (x, y) = rb_sim_manager.get_rb_entity_position(*rb_entity).unwrap();
+ render_pass.set_bind_group(1, &renderer_rb_entity.bind_group, &[]);
+ render_pass.set_immediates(
+ 0,
+ bytemuck::bytes_of(&ChunkData {
+ origin: [x.round() as i32, y.round() as i32],
+ }),
+ );
+ render_pass.draw(0..4, 0..1);
+ }
}
- }
- {
- puffin::profile_scope!("Egui render pass");
- let mut egui_pass = encoder
- .begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("egui pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: &view,
- resolve_target: None,
- depth_slice: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: wgpu::StoreOp::Store,
- },
- })],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- multiview_mask: None,
- })
- .forget_lifetime();
+ {
+ puffin::profile_scope!("Egui render pass");
+ let mut egui_pass = encoder
+ .begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("egui pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: &view,
+ resolve_target: None,
+ depth_slice: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: wgpu::StoreOp::Store,
+ },
+ })],
+ depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
+ multiview_mask: None,
+ })
+ .forget_lifetime();
- self.egui_renderer
- .render(&mut egui_pass, &clipped_primitives, &screen_descriptor);
- }
+ self.egui_renderer
+ .render(&mut egui_pass, &clipped_primitives, &screen_descriptor);
+ }
- {
- puffin::profile_scope!("Submit queue and present");
- self.queue.submit(std::iter::once(encoder.finish()));
- output.present();
+ {
+ puffin::profile_scope!("Submit queue and present");
+ self.queue.submit(std::iter::once(encoder.finish()));
+ output.present();
+ }
}
}
}
diff --git a/src/renderer/ui.rs b/src/renderer/ui.rs
index 2e4ebc4..77af59d 100644
--- a/src/renderer/ui.rs
+++ b/src/renderer/ui.rs
@@ -2,7 +2,7 @@ use egui::{Color32, Stroke, Ui, epaint::CircleShape};
use crate::{
Camera, Config, Diagnostics, Input,
- sim::{materials::MaterialId, world::World},
+ sim::{cell::materials::MaterialId, cell_sim::world::World},
};
pub fn draw_egui<'a>(
diff --git a/src/sim/cell.rs b/src/sim/cell/cell.rs
index d3ca302..d35fa76 100644
--- a/src/sim/cell.rs
+++ b/src/sim/cell/cell.rs
@@ -1,4 +1,4 @@
-use crate::sim::materials::MaterialId;
+use crate::sim::cell::materials::MaterialId;
#[derive(Clone, Copy)]
pub struct Cell {
diff --git a/src/sim/materials/fire.rs b/src/sim/cell/materials/fire.rs
index 97027d5..e0416b0 100644
--- a/src/sim/materials/fire.rs
+++ b/src/sim/cell/materials/fire.rs
@@ -1,6 +1,9 @@
use rand::RngExt;
-use crate::sim::{cell::Cell, materials::MaterialId, sim::UpdateCtx};
+use crate::sim::{
+ cell::{cell::Cell, materials::MaterialId},
+ cell_sim::sim::UpdateCtx,
+};
trait FireCellView {
fn get_ticks_lived(self) -> u16;
diff --git a/src/sim/materials/gas.rs b/src/sim/cell/materials/gas.rs
index 8d9f42b..c0cd9f9 100644
--- a/src/sim/materials/gas.rs
+++ b/src/sim/cell/materials/gas.rs
@@ -1,4 +1,4 @@
-use crate::sim::sim::UpdateCtx;
+use crate::sim::cell_sim::sim::UpdateCtx;
#[inline]
pub fn sim_update(ctx: &mut UpdateCtx) {
diff --git a/src/sim/materials/mod.rs b/src/sim/cell/materials/mod.rs
index 10f0a06..a55ed51 100644
--- a/src/sim/materials/mod.rs
+++ b/src/sim/cell/materials/mod.rs
@@ -1,4 +1,4 @@
-use crate::sim::sim::UpdateCtx;
+use crate::sim::cell_sim::sim::UpdateCtx;
mod fire;
mod gas;
diff --git a/src/sim/materials/sand.rs b/src/sim/cell/materials/sand.rs
index ac8889f..d2d1265 100644
--- a/src/sim/materials/sand.rs
+++ b/src/sim/cell/materials/sand.rs
@@ -1,4 +1,4 @@
-use crate::sim::sim::UpdateCtx;
+use crate::sim::cell_sim::sim::UpdateCtx;
#[inline]
pub fn sim_update(ctx: &mut UpdateCtx) {
diff --git a/src/sim/materials/smoke.rs b/src/sim/cell/materials/smoke.rs
index 5240bc9..2ee7c00 100644
--- a/src/sim/materials/smoke.rs
+++ b/src/sim/cell/materials/smoke.rs
@@ -1,6 +1,6 @@
use rand::RngExt;
-use crate::sim::sim::UpdateCtx;
+use crate::sim::cell_sim::sim::UpdateCtx;
#[inline]
pub fn sim_update(ctx: &mut UpdateCtx) {
diff --git a/src/sim/materials/water.rs b/src/sim/cell/materials/water.rs
index 1ba8eb4..4b848fb 100644
--- a/src/sim/materials/water.rs
+++ b/src/sim/cell/materials/water.rs
@@ -1,4 +1,4 @@
-use crate::sim::sim::UpdateCtx;
+use crate::sim::cell_sim::sim::UpdateCtx;
#[inline]
pub fn sim_update(ctx: &mut UpdateCtx) {
diff --git a/src/sim/cell/mod.rs b/src/sim/cell/mod.rs
new file mode 100644
index 0000000..2d2175c
--- /dev/null
+++ b/src/sim/cell/mod.rs
@@ -0,0 +1,2 @@
+pub mod cell;
+pub mod materials;
diff --git a/src/sim/chunk.rs b/src/sim/cell_sim/chunk.rs
index 478af12..116a38a 100644
--- a/src/sim/chunk.rs
+++ b/src/sim/cell_sim/chunk.rs
@@ -1,6 +1,6 @@
use crate::{
config::{CELLS_IN_CHUNK, CHUNK_SIZE},
- sim::cell::Cell,
+ sim::cell::cell::Cell,
};
pub struct Chunk {
diff --git a/src/sim/cell_sim/mod.rs b/src/sim/cell_sim/mod.rs
new file mode 100644
index 0000000..a1db2a1
--- /dev/null
+++ b/src/sim/cell_sim/mod.rs
@@ -0,0 +1,4 @@
+pub mod chunk;
+pub mod overlay;
+pub mod sim;
+pub mod world;
diff --git a/src/sim/overlay.rs b/src/sim/cell_sim/overlay.rs
index c9fdf17..ee494ae 100644
--- a/src/sim/overlay.rs
+++ b/src/sim/cell_sim/overlay.rs
@@ -1,4 +1,4 @@
-use crate::{Config, Input, sim::world::World};
+use crate::{Config, Input, sim::cell_sim::world::World};
pub fn create_compute_combined_overlay_offset(
world: &World,
diff --git a/src/sim/sim.rs b/src/sim/cell_sim/sim.rs
index b9f4b76..fd9b6c9 100644
--- a/src/sim/sim.rs
+++ b/src/sim/cell_sim/sim.rs
@@ -6,7 +6,10 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use crate::{
config::CHUNK_SIZE,
- sim::{cell::Cell, chunk::Chunk, materials::MaterialDef, world::World},
+ sim::{
+ cell::{cell::Cell, materials::MaterialDef},
+ cell_sim::{chunk::Chunk, world::World},
+ },
};
struct ChunkAccess<'a> {
diff --git a/src/sim/world.rs b/src/sim/cell_sim/world.rs
index d54906c..924639a 100644
--- a/src/sim/world.rs
+++ b/src/sim/cell_sim/world.rs
@@ -2,7 +2,7 @@ use fxhash::FxHashMap;
use crate::{
config::CHUNK_SIZE,
- sim::{cell::Cell, chunk::Chunk},
+ sim::{cell::cell::Cell, cell_sim::chunk::Chunk},
};
pub struct World {
diff --git a/src/sim/mod.rs b/src/sim/mod.rs
index 7553f89..63a9c56 100644
--- a/src/sim/mod.rs
+++ b/src/sim/mod.rs
@@ -1,6 +1,3 @@
pub mod cell;
-pub mod chunk;
-pub mod materials;
-pub mod overlay;
-pub mod sim;
-pub mod world;
+pub mod cell_sim;
+pub mod rb_sim;
diff --git a/src/sim/rb_sim/mod.rs b/src/sim/rb_sim/mod.rs
new file mode 100644
index 0000000..1818052
--- /dev/null
+++ b/src/sim/rb_sim/mod.rs
@@ -0,0 +1,131 @@
+pub mod rb_entity;
+
+use fxhash::FxHashMap;
+use rapier2d::{
+ dynamics::{self},
+ geometry,
+ glamx::vec2,
+ math, prelude,
+};
+
+use crate::{
+ config::{CELLS_IN_CHUNK, PHYSICS_DELTA_TIME},
+ sim::{
+ cell::{cell::Cell, materials::MaterialId},
+ rb_sim::rb_entity::RbEntity,
+ },
+};
+
+pub struct PhysicsManager {
+ rigid_body_set: prelude::RigidBodySet,
+ collider_set: prelude::ColliderSet,
+ physics_pipeline: prelude::PhysicsPipeline,
+ integration_parameters: prelude::IntegrationParameters,
+ island_manager: prelude::IslandManager,
+ broad_phase: prelude::DefaultBroadPhase,
+ narrow_phase: prelude::NarrowPhase,
+ impulse_joint_set: prelude::ImpulseJointSet,
+ multibody_joint_set: prelude::MultibodyJointSet,
+ ccd_solver: prelude::CCDSolver,
+}
+
+impl PhysicsManager {
+ pub fn new() -> Self {
+ PhysicsManager {
+ rigid_body_set: prelude::RigidBodySet::new(),
+ collider_set: prelude::ColliderSet::new(),
+ physics_pipeline: prelude::PhysicsPipeline::new(),
+ integration_parameters: prelude::IntegrationParameters {
+ // 20 pixels = 1 meter
+ length_unit: 20.0,
+ dt: PHYSICS_DELTA_TIME,
+ ..prelude::IntegrationParameters::default()
+ },
+ island_manager: prelude::IslandManager::new(),
+ broad_phase: prelude::DefaultBroadPhase::new(),
+ narrow_phase: prelude::NarrowPhase::new(),
+ impulse_joint_set: prelude::ImpulseJointSet::new(),
+ multibody_joint_set: prelude::MultibodyJointSet::new(),
+ ccd_solver: prelude::CCDSolver::new(),
+ }
+ }
+}
+
+pub struct RbSimManager {
+ physics_manager: PhysicsManager,
+ pub rb_entities: FxHashMap<u32, RbEntity>,
+}
+
+impl RbSimManager {
+ pub fn rb_tick(&mut self, delta_time: f32) {
+ let gravity = vec2(0.0, -9.81);
+
+ self.physics_manager.physics_pipeline.step(
+ gravity,
+ &self.physics_manager.integration_parameters,
+ &mut self.physics_manager.island_manager,
+ &mut self.physics_manager.broad_phase,
+ &mut self.physics_manager.narrow_phase,
+ &mut self.physics_manager.rigid_body_set,
+ &mut self.physics_manager.collider_set,
+ &mut self.physics_manager.impulse_joint_set,
+ &mut self.physics_manager.multibody_joint_set,
+ &mut self.physics_manager.ccd_solver,
+ &(),
+ &(),
+ );
+ }
+
+ pub fn get_rb_entity_position(&self, entity_id: u32) -> Option<(f32, f32)> {
+ let entity = self.rb_entities.get(&entity_id);
+ match entity {
+ Some(entity) => {
+ let rb = self.physics_manager.rigid_body_set.get(entity.rb_parent);
+ rb.map(|rb| (rb.position().translation.x, rb.position().translation.y))
+ }
+ None => return None,
+ }
+ }
+
+ pub fn test(&mut self) {
+ /* Create the ground. */
+ let collider = geometry::ColliderBuilder::cuboid(100.0, 0.1).build();
+ self.physics_manager.collider_set.insert(collider);
+
+ /* Create the bouncing ball. */
+ let rigid_body = dynamics::RigidBodyBuilder::dynamic()
+ .translation(math::Vector::new(0.0, 10.0))
+ .build();
+ let collider = geometry::ColliderBuilder::ball(0.5)
+ .restitution(0.7)
+ .build();
+ let ball_body_handle = self.physics_manager.rigid_body_set.insert(rigid_body);
+ self.physics_manager.collider_set.insert_with_parent(
+ collider,
+ ball_body_handle,
+ &mut self.physics_manager.rigid_body_set,
+ );
+
+ let test_cells = Box::new([Cell::void(); CELLS_IN_CHUNK]);
+ let mut rb_entity = RbEntity {
+ id: 0,
+ cells: test_cells,
+ rb_parent: ball_body_handle,
+ };
+
+ for x in 0..10 {
+ for y in 0..10 {
+ rb_entity.set_cell_at_local_position(x, y, Cell::from_material(MaterialId::Wood));
+ }
+ }
+
+ self.rb_entities.insert(0, rb_entity);
+ }
+
+ pub fn new() -> Self {
+ RbSimManager {
+ physics_manager: PhysicsManager::new(),
+ rb_entities: FxHashMap::default(),
+ }
+ }
+}
diff --git a/src/sim/rb_sim/rb_entity.rs b/src/sim/rb_sim/rb_entity.rs
new file mode 100644
index 0000000..50243bc
--- /dev/null
+++ b/src/sim/rb_sim/rb_entity.rs
@@ -0,0 +1,24 @@
+use rapier2d::prelude;
+
+use crate::{
+ config::{CELLS_IN_CHUNK, CHUNK_SIZE},
+ sim::cell::cell::Cell,
+};
+
+pub struct RbEntity {
+ pub id: u32,
+ // TODO resizable
+ pub cells: Box<[Cell; CELLS_IN_CHUNK]>,
+ pub rb_parent: prelude::RigidBodyHandle,
+}
+
+impl RbEntity {
+ #[inline]
+ pub fn get_cell_at_local_position(&self, x: u8, y: u8) -> Cell {
+ self.cells[x as usize + y as usize * CHUNK_SIZE as usize]
+ }
+ #[inline]
+ pub fn set_cell_at_local_position(&mut self, x: u8, y: u8, cell: Cell) {
+ self.cells[x as usize + y as usize * CHUNK_SIZE as usize] = cell;
+ }
+}