diff options
Diffstat (limited to 'src/sim')
| -rw-r--r-- | src/sim/board.rs | 24 | ||||
| -rw-r--r-- | src/sim/materials.rs | 25 | ||||
| -rw-r--r-- | src/sim/overlay.rs | 19 | ||||
| -rw-r--r-- | src/sim/sim.rs | 135 |
4 files changed, 177 insertions, 26 deletions
diff --git a/src/sim/board.rs b/src/sim/board.rs index 4cc5756..7af19be 100644 --- a/src/sim/board.rs +++ b/src/sim/board.rs @@ -1,24 +1,24 @@ -use crate::config::{PIXEL_BUFFER_HEIGHT, PIXEL_BUFFER_WIDTH}; - -type MaterialId = u16; +use crate::{ + config::{PIXEL_BUFFER_HEIGHT, PIXEL_BUFFER_WIDTH}, + sim::materials::MaterialId, +}; #[derive(Clone, Copy)] pub struct Cell { pub material: MaterialId, - pub velocity_x: i8, - pub velocity_y: i8, pub flags: u8, } impl Cell { - fn empty() -> Cell { + pub fn void() -> Cell { Cell { - material: 0, - velocity_x: 0, - velocity_y: 0, + material: MaterialId::Void, flags: 0, } } + pub fn from_material(material: MaterialId) -> Cell { + Cell { material, flags: 0 } + } } pub struct Board { @@ -40,8 +40,8 @@ impl Board { let idx = self.position_to_index(x, y).unwrap(); self.cells[idx] = c; } - pub fn cell_at_position(&self, x: i32, y: i32) -> Option<&Cell> { - Some(&self.cells[self.position_to_index(x, y)?]) + pub fn cell_at_position(&self, x: i32, y: i32) -> Option<Cell> { + Some(self.cells[self.position_to_index(x, y)?]) } pub fn position_to_index(&self, x: i32, y: i32) -> Option<usize> { let board_x = x + self.size_x as i32 / 2; @@ -61,7 +61,7 @@ impl Board { pub fn empty() -> Board { let size_x = PIXEL_BUFFER_WIDTH * 2; let size_y = PIXEL_BUFFER_HEIGHT * 2; - let cells: Vec<Cell> = vec![Cell::empty(); (size_x * size_y) as usize]; + let cells: Vec<Cell> = vec![Cell::void(); (size_x * size_y) as usize]; Board { size_x, diff --git a/src/sim/materials.rs b/src/sim/materials.rs index 13a45f3..8b21618 100644 --- a/src/sim/materials.rs +++ b/src/sim/materials.rs @@ -4,19 +4,38 @@ pub struct Material<'a> { pub r: u8, pub g: u8, pub b: u8, + + pub density: u8, +} + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum MaterialId { + Void, + Sand, + Water, } -pub static MATERIALS: [Material; 2] = [ +pub static MATERIALS: [Material; 3] = [ Material { name: "Void", r: 0x00, g: 0x00, b: 0x00, + density: 0, }, Material { name: "Sand", - r: 0xFF, - g: 0x00, + r: 0xDE, + g: 0xCB, + b: 0x85, + density: 50, + }, + Material { + name: "Water", + r: 0x38, + g: 0xA9, b: 0xFF, + density: 40, }, ]; diff --git a/src/sim/overlay.rs b/src/sim/overlay.rs index 36a810b..7c5d5a4 100644 --- a/src/sim/overlay.rs +++ b/src/sim/overlay.rs @@ -1,3 +1,5 @@ +use core::range::Range; + use crate::{Config, Input, sim::board::Board}; pub fn create_compute_combined_overlay_offset( @@ -5,7 +7,8 @@ pub fn create_compute_combined_overlay_offset( config: &Config, input: &Input, ) -> impl Fn(i32, i32) -> (u8, u8, u8, u8) { - |x: i32, y: i32| { + // TODO fix this move? + move |x: i32, y: i32| { // could allow negative offsets too let mut offset: (u8, u8, u8, u8) = (0x00, 0x00, 0x00, 0x00); @@ -35,14 +38,12 @@ pub fn create_compute_combined_overlay_offset( } // brush/selection - if (((x - input.last_mouse_pos_on_board.0).pow(2) - + (y - input.last_mouse_pos_on_board.1).pow(2)) as f32) - .sqrt() - < config.brush_size as f32 - { - offset.0 = offset.0.saturating_add(0xAA); - offset.1 = offset.1.saturating_add(0x00); - offset.2 = offset.2.saturating_add(0xAA); + if input.last_mouse_pos_on_board.is_some_and(|p| { + ((x - p.0).pow(2) + (y - p.1).pow(2)) < (config.brush_radius as i32).pow(2) + }) { + offset.0 = offset.0.saturating_add(0x82); + offset.1 = offset.1.saturating_add(0xA1); + offset.2 = offset.2.saturating_add(0xAD); } return offset; diff --git a/src/sim/sim.rs b/src/sim/sim.rs index bb68306..186483d 100644 --- a/src/sim/sim.rs +++ b/src/sim/sim.rs @@ -1,3 +1,134 @@ -use crate::Board; +use core::range::Range; -fn sim_tick(board: Board, seqno: u64) {} +use crate::{ + Board, + sim::materials::{MATERIALS, MaterialId}, +}; + +// TODO: chunks +pub fn sim_tick(board: &mut Board, seqno: u64, delta_time: f32) { + // scan bottom to top to enable contiguous falling + let seqno_parity = (seqno as u8) & 0b1; + + let bx = (board.size_x / 2) as i32; + let by = (board.size_y / 2) as i32; + for y in (-by..by + 1).rev() { + // invert scan order on every other frame + for col in -bx..bx + 1 { + let x = if seqno_parity == 0 { col } else { -col }; + + let cell = board.cell_at_position(x, y); + if let Some(cell) = cell + && cell.flags & 0b1 == seqno_parity + { + let mut cur = cell.clone(); + // flip the parity bit + cur.flags = cur.flags ^ 0b1; + + let material = &MATERIALS[cur.material as usize]; + + match cur.material { + MaterialId::Void => {} + // TODO abstract density based movement + MaterialId::Sand => { + for candidate in [ + (x, y + 1), + (x - 1 + 2 * seqno_parity as i32, y + 1), + (x + 1 - 2 * seqno_parity as i32, y + 1), + ] { + let target = board.cell_at_position(candidate.0, candidate.1); + if let Some(target) = target + && MATERIALS[target.material as usize].density < material.density + { + // swap the cells + board.set_cell_at_position(x, y, target); + board.set_cell_at_position(candidate.0, candidate.1, cur); + break; + } + } + } + MaterialId::Water => 'water: { + // if the water can fall, do so + for candidate in [ + (x, y + 1), + (x - 1 + 2 * seqno_parity as i32, y + 1), + (x + 1 - 2 * seqno_parity as i32, y + 1), + ] { + let target = board.cell_at_position(candidate.0, candidate.1); + if let Some(target) = target + && MATERIALS[target.material as usize].density < material.density + { + // swap the cells + board.set_cell_at_position(x, y, target); + board.set_cell_at_position(candidate.0, candidate.1, cur); + break 'water; + } + } + // if the water can't fall, check if we can move left or right + // these are inverted on parity so that we don't preference a direction + let left_target = board.cell_at_position(x - 1, y); + let can_move_left = left_target.is_some_and(|c| { + MATERIALS[c.material as usize].density < material.density + }); + let right_target = board.cell_at_position(x + 1, y); + let can_move_right = right_target.is_some_and(|c| { + MATERIALS[c.material as usize].density < material.density + }); + + // we can't move down or to other side, so we're stuck + if !can_move_left && !can_move_right { + break 'water; + } + + // find the closest hole within 20 pixels (TODO optimize) + // a hole is any space below us with a lesser density + // prevents equidistance stuck state + let starting_side = if seqno_parity == 0 { 1 } else { -1 }; + for i in 0..20 { + let side = if i % 2 == 0 { + starting_side + } else { + -starting_side + }; + if (side == 1 && !can_move_right) || (side == -1 && !can_move_left) { + continue; + } + + let offset = side * (1 + i / 2); + + let target = board.cell_at_position(x + offset, y + 1); + if let Some(target) = target + && MATERIALS[target.material as usize].density < material.density + { + // we identified a hole and we know that the space on this side is open + // move toward the hole + let mut new_target = + if side == 1 { right_target } else { left_target }.clone(); + // new_target.flags = new_target.flags ^ 0b1; + // safe to unwrap + board.set_cell_at_position(x, y, new_target.unwrap()); + board.set_cell_at_position(x + side, y, cur); + break 'water; + } + } + + // we didn't find a hole, so just move "randomly" on the same surface + // TODO when to settle? + let (target, target_x) = if !can_move_left { + (right_target, 1) + } else if !can_move_right { + (left_target, -1) + } else if seqno_parity % 2 == 1 { + (right_target, 1) + } else { + (left_target, -1) + }; + + board.set_cell_at_position(x, y, target.unwrap()); + board.set_cell_at_position(x + target_x, y, cur); + } + } + } + } + } +} |
