From 6f67586b8fc6efdb86d0c9a9744c546da97c4dab Mon Sep 17 00:00:00 2001 From: Kai Stevenson Date: Tue, 11 Aug 2026 00:46:56 -0700 Subject: physics for sand and water --- src/sim/sim.rs | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 2 deletions(-) (limited to 'src/sim/sim.rs') 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); + } + } + } + } + } +} -- cgit v1.3.1