Files
Verbatim/src/world/cellular.rs
T
Emil 159b7c6f61 fix: surface renderer errors, clean up all warnings
- Renderer init/render/shutdown errors now printed instead of silently
  swallowed with let _ =
- Auto-fixed all unused imports and variables (cargo fix)
- Zero compiler warnings
2026-06-20 22:00:36 +03:00

454 lines
15 KiB
Rust

use crate::world::cell::{Cell, MaterialId};
use crate::world::grid::Grid;
pub struct CellularAutomaton {
tick: u64,
rng_state: u64,
}
impl CellularAutomaton {
pub fn new() -> Self {
Self {
tick: 0,
rng_state: 0x1234567890ABCDEF,
}
}
#[inline]
fn rand(&mut self) -> u32 {
self.rng_state ^= self.rng_state << 13;
self.rng_state ^= self.rng_state >> 7;
self.rng_state ^= self.rng_state << 17;
(self.rng_state & 0xFFFFFFFF) as u32
}
#[inline]
fn rand_bool(&mut self) -> bool {
self.rand() & 1 == 1
}
pub fn step(&mut self, grid: &mut Grid) {
grid.reset_tick_flags();
let flip = self.rand_bool();
let h = grid.height;
let w = grid.width;
for y_idx in (0..h).rev() {
let y = y_idx as i32;
let xs: Vec<i32> = if flip {
(0..w as i32).collect()
} else {
(0..w as i32).rev().collect()
};
for x in xs {
let cell = grid.get(x, y);
if cell.updated_this_tick || cell.is_empty() || cell.is_static() {
continue;
}
match cell.material {
MaterialId::Sand => self.update_sand(grid, x, y),
MaterialId::Water => self.update_water(grid, x, y),
MaterialId::Lava => self.update_lava(grid, x, y),
MaterialId::Steam => self.update_steam(grid, x, y),
MaterialId::Fire => self.update_fire(grid, x, y),
MaterialId::Smoke => self.update_smoke(grid, x, y),
MaterialId::Acid => self.update_acid(grid, x, y),
MaterialId::Flesh => self.update_flesh(grid, x, y),
MaterialId::Grass => self.update_grass(grid, x, y),
MaterialId::Dirt => self.update_dirt(grid, x, y),
_ => {}
}
}
}
self.heat_transfer(grid);
self.tick += 1;
}
fn try_move_down(&mut self, grid: &mut Grid, x: i32, y: i32, _mat: MaterialId, density: f32) {
let below = grid.get(x, y + 1);
if below.is_empty() || (below.is_liquid() && below.density() < density) {
let src = grid.get(x, y);
let i_dst = grid.idx(x, y + 1);
let i_src = grid.idx(x, y);
grid.cells[i_dst] = src;
grid.cells[i_dst].updated_this_tick = true;
grid.cells[i_src] = Cell::empty();
return;
}
let dir = if self.rand_bool() { 1 } else { -1 };
let dl = grid.get(x - dir, y + 1);
let dr = grid.get(x + dir, y + 1);
let can_left = grid.in_bounds(x - dir, y + 1)
&& (dl.is_empty() || (dl.is_liquid() && dl.density() < density));
let can_right = grid.in_bounds(x + dir, y + 1)
&& (dr.is_empty() || (dr.is_liquid() && dr.density() < density));
if can_left && can_right {
if self.rand_bool() {
self.do_swap(grid, x, y, x - dir, y + 1);
} else {
self.do_swap(grid, x, y, x + dir, y + 1);
}
} else if can_left {
self.do_swap(grid, x, y, x - dir, y + 1);
} else if can_right {
self.do_swap(grid, x, y, x + dir, y + 1);
}
}
#[inline]
fn do_swap(&self, grid: &mut Grid, x1: i32, y1: i32, x2: i32, y2: i32) {
let a = grid.get(x1, y1);
let b = grid.get(x2, y2);
let i1 = grid.idx(x1, y1);
let i2 = grid.idx(x2, y2);
grid.cells[i1] = b;
grid.cells[i2] = a;
grid.cells[i2].updated_this_tick = true;
}
fn update_sand(&mut self, grid: &mut Grid, x: i32, y: i32) {
self.try_move_down(grid, x, y, MaterialId::Sand, 1.5);
}
fn update_water(&mut self, grid: &mut Grid, x: i32, y: i32) {
let below = grid.get(x, y + 1);
if below.is_empty() || (below.is_liquid() && below.density() < 1.0) {
self.do_swap(grid, x, y, x, y + 1);
return;
}
let dir = if self.rand_bool() { 1 } else { -1 };
let dl = grid.get(x - dir, y + 1);
let dr = grid.get(x + dir, y + 1);
let can_dl = grid.in_bounds(x - dir, y + 1)
&& (dl.is_empty() || (dl.is_liquid() && dl.density() < 1.0));
let can_dr = grid.in_bounds(x + dir, y + 1)
&& (dr.is_empty() || (dr.is_liquid() && dr.density() < 1.0));
if can_dl && can_dr {
if self.rand_bool() {
self.do_swap(grid, x, y, x - dir, y + 1);
} else {
self.do_swap(grid, x, y, x + dir, y + 1);
}
} else if can_dl {
self.do_swap(grid, x, y, x - dir, y + 1);
} else if can_dr {
self.do_swap(grid, x, y, x + dir, y + 1);
} else {
let can_l = grid.in_bounds(x - dir, y) && grid.get(x - dir, y).is_empty();
let can_r = grid.in_bounds(x + dir, y) && grid.get(x + dir, y).is_empty();
if can_l && can_r {
if self.rand_bool() {
self.do_swap(grid, x, y, x - dir, y);
} else {
self.do_swap(grid, x, y, x + dir, y);
}
} else if can_l {
self.do_swap(grid, x, y, x - dir, y);
} else if can_r {
self.do_swap(grid, x, y, x + dir, y);
}
}
let cell = grid.get(x, y);
if cell.temp > 100.0 {
let mut new = cell;
new.material = MaterialId::Steam;
new.temp = 110.0;
let i = grid.idx(x, y);
grid.cells[i] = new;
}
}
fn update_lava(&mut self, grid: &mut Grid, x: i32, y: i32) {
let cell = grid.get(x, y);
if cell.temp < 400.0 {
let mut new = cell;
new.material = MaterialId::Stone;
let i = grid.idx(x, y);
grid.cells[i] = new;
return;
}
let below = grid.get(x, y + 1);
if below.is_empty() {
self.do_swap(grid, x, y, x, y + 1);
return;
}
let dir = if self.rand_bool() { 1 } else { -1 };
if grid.in_bounds(x - dir, y + 1) && grid.get(x - dir, y + 1).is_empty() {
self.do_swap(grid, x, y, x - dir, y + 1);
return;
}
if grid.in_bounds(x + dir, y + 1) && grid.get(x + dir, y + 1).is_empty() {
self.do_swap(grid, x, y, x + dir, y + 1);
return;
}
if self.rand() % 10 == 0 {
if grid.in_bounds(x - dir, y) && grid.get(x - dir, y).is_empty() {
self.do_swap(grid, x, y, x - dir, y);
} else if grid.in_bounds(x + dir, y) && grid.get(x + dir, y).is_empty() {
self.do_swap(grid, x, y, x + dir, y);
}
}
self.lava_interact(grid, x, y);
}
fn lava_interact(&mut self, grid: &mut Grid, x: i32, y: i32) {
for &(dx, dy) in &NEIGHBORS4 {
let nx = x + dx;
let ny = y + dy;
if !grid.in_bounds(nx, ny) {
continue;
}
let neighbor = grid.get(nx, ny);
match neighbor.material {
MaterialId::Water => {
let i_n = grid.idx(nx, ny);
grid.cells[i_n] = Cell::new(MaterialId::Steam);
let lava = grid.get(x, y);
let mut new_lava = lava;
new_lava.temp -= 50.0;
let i_l = grid.idx(x, y);
grid.cells[i_l] = new_lava;
}
MaterialId::Wood | MaterialId::Grass | MaterialId::Flesh if neighbor.temp < 300.0 => {
let i_n = grid.idx(nx, ny);
grid.cells[i_n] = Cell::new(MaterialId::Fire);
}
MaterialId::Sand if neighbor.temp > 1700.0 => {
let i_n = grid.idx(nx, ny);
grid.cells[i_n] = Cell::new(MaterialId::Stone);
}
_ => {}
}
}
}
fn update_steam(&mut self, grid: &mut Grid, x: i32, y: i32) {
let cell = grid.get(x, y);
if cell.temp < 80.0 {
let mut new = cell;
new.material = MaterialId::Water;
new.temp = 50.0;
let i = grid.idx(x, y);
grid.cells[i] = new;
return;
}
if y > 0 && grid.get(x, y - 1).is_empty() {
self.do_swap(grid, x, y, x, y - 1);
return;
}
let dir = if self.rand_bool() { 1 } else { -1 };
if grid.in_bounds(x - dir, y - 1) && grid.get(x - dir, y - 1).is_empty() {
self.do_swap(grid, x, y, x - dir, y - 1);
return;
}
if grid.in_bounds(x + dir, y - 1) && grid.get(x + dir, y - 1).is_empty() {
self.do_swap(grid, x, y, x + dir, y - 1);
return;
}
if self.rand() % 3 == 0 {
if grid.in_bounds(x - dir, y) && grid.get(x - dir, y).is_empty() {
self.do_swap(grid, x, y, x - dir, y);
} else if grid.in_bounds(x + dir, y) && grid.get(x + dir, y).is_empty() {
self.do_swap(grid, x, y, x + dir, y);
}
}
}
fn update_fire(&mut self, grid: &mut Grid, x: i32, y: i32) {
let cell = grid.get(x, y);
if cell.temp < 100.0 || self.rand() % 20 == 0 {
let i = grid.idx(x, y);
if self.rand() % 3 == 0 {
grid.cells[i] = Cell::new(MaterialId::Smoke);
} else {
grid.cells[i] = Cell::empty();
}
return;
}
let mut new = cell;
new.temp -= 15.0;
let i = grid.idx(x, y);
grid.cells[i] = new;
if y > 0 && grid.get(x, y - 1).is_empty() && self.rand() % 2 == 0 {
self.do_swap(grid, x, y, x, y - 1);
}
for &(dx, dy) in &NEIGHBORS4 {
let nx = x + dx;
let ny = y + dy;
if !grid.in_bounds(nx, ny) {
continue;
}
let neighbor = grid.get(nx, ny);
let reg = crate::world::material::MaterialRegistry::instance();
let mat = reg.get(neighbor.material);
if mat.flammable && neighbor.temp < mat.ignition_temp {
let mut new_n = neighbor;
new_n.material = MaterialId::Fire;
new_n.temp = 400.0;
let i_n = grid.idx(nx, ny);
grid.cells[i_n] = new_n;
}
}
}
fn update_smoke(&mut self, grid: &mut Grid, x: i32, y: i32) {
if self.rand() % 60 == 0 {
let i = grid.idx(x, y);
grid.cells[i] = Cell::empty();
return;
}
if y > 0 && grid.get(x, y - 1).is_empty() {
self.do_swap(grid, x, y, x, y - 1);
return;
}
let dir = if self.rand_bool() { 1 } else { -1 };
if grid.in_bounds(x - dir, y - 1) && grid.get(x - dir, y - 1).is_empty() {
self.do_swap(grid, x, y, x - dir, y - 1);
} else if grid.in_bounds(x + dir, y - 1) && grid.get(x + dir, y - 1).is_empty() {
self.do_swap(grid, x, y, x + dir, y - 1);
} else if grid.in_bounds(x - dir, y) && grid.get(x - dir, y).is_empty() {
self.do_swap(grid, x, y, x - dir, y);
} else if grid.in_bounds(x + dir, y) && grid.get(x + dir, y).is_empty() {
self.do_swap(grid, x, y, x + dir, y);
}
}
fn update_acid(&mut self, grid: &mut Grid, x: i32, y: i32) {
for &(dx, dy) in &NEIGHBORS4 {
let nx = x + dx;
let ny = y + dy;
if !grid.in_bounds(nx, ny) {
continue;
}
let neighbor = grid.get(nx, ny);
if neighbor.material != MaterialId::Empty
&& neighbor.material != MaterialId::Acid
&& neighbor.material != MaterialId::Stone
&& self.rand() % 4 == 0
{
let i_n = grid.idx(nx, ny);
grid.cells[i_n] = Cell::empty();
if self.rand() % 2 == 0 {
let i = grid.idx(x, y);
grid.cells[i] = Cell::empty();
return;
}
}
}
let below = grid.get(x, y + 1);
if below.is_empty() || (below.is_liquid() && below.density() < 1.2) {
self.do_swap(grid, x, y, x, y + 1);
return;
}
let dir = if self.rand_bool() { 1 } else { -1 };
if grid.in_bounds(x - dir, y + 1) && grid.get(x - dir, y + 1).is_empty() {
self.do_swap(grid, x, y, x - dir, y + 1);
} else if grid.in_bounds(x + dir, y + 1) && grid.get(x + dir, y + 1).is_empty() {
self.do_swap(grid, x, y, x + dir, y + 1);
} else if grid.in_bounds(x - dir, y) && grid.get(x - dir, y).is_empty() {
self.do_swap(grid, x, y, x - dir, y);
} else if grid.in_bounds(x + dir, y) && grid.get(x + dir, y).is_empty() {
self.do_swap(grid, x, y, x + dir, y);
}
}
fn update_flesh(&mut self, grid: &mut Grid, x: i32, y: i32) {
let cell = grid.get(x, y);
if cell.temp > 200.0 {
let mut new = cell;
new.material = MaterialId::Fire;
new.temp = 400.0;
let i = grid.idx(x, y);
grid.cells[i] = new;
}
}
fn update_grass(&mut self, grid: &mut Grid, x: i32, y: i32) {
let cell = grid.get(x, y);
if cell.temp > 250.0 {
let i = grid.idx(x, y);
grid.cells[i] = Cell::new(MaterialId::Fire);
}
}
fn update_dirt(&mut self, grid: &mut Grid, x: i32, y: i32) {
let cell = grid.get(x, y);
if cell.temp < 0.0 {
let mut new = cell;
new.material = MaterialId::Stone;
let i = grid.idx(x, y);
grid.cells[i] = new;
}
}
fn heat_transfer(&mut self, grid: &mut Grid) {
let w = grid.width;
let h = grid.height;
let temps: Vec<f32> = grid.cells.iter().map(|c| c.temp).collect();
for y in 0..h {
for x in 0..w {
let i = y * w + x;
let cell = grid.cells[i];
if cell.is_empty() || cell.is_static() {
continue;
}
let reg = crate::world::material::MaterialRegistry::instance();
let mat = reg.get(cell.material);
let k = mat.heat_conductivity;
if k == 0.0 {
continue;
}
let mut sum = 0.0;
let mut count = 0;
for &(dx, dy) in &NEIGHBORS4 {
let nx = x as i32 + dx;
let ny = y as i32 + dy;
if nx < 0 || nx >= w as i32 || ny < 0 || ny >= h as i32 {
continue;
}
let ni = ny as usize * w + nx as usize;
sum += temps[ni];
count += 1;
}
if count > 0 {
let avg = sum / count as f32;
let mut new = cell;
new.temp += (avg - cell.temp) * k * 0.1;
grid.cells[i] = new;
}
}
}
}
pub fn tick_count(&self) -> u64 {
self.tick
}
}
const NEIGHBORS4: [(i32, i32); 4] = [(0, -1), (0, 1), (-1, 0), (1, 0)];