Initial commit: AtomicEngine v0.2
- Rust/WASM physics engine (grid, material, physics, render_buffer) - 16 material types (stone, sand, water, wood, fire, lava, glass, ice, oil, acid, steam, spark, smoke + player placeholder) - Fire system (fuel-based, flammability-scaled consumption, spread, smoke/sparks) - Lighting system (emission, flood-fill propagation, glow, reflection) - Per-material pixel texturing (wood grain, dirt specks, stone noise, flame variation) - WebGL2 renderer with camera (WASD move, mouse wheel zoom) - Native x86 debug binary (identical dimensions, FPS counter) - Large world 1024x768 with active-region simulation (camera + 200px margin) - UI material palette + FPS display
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
pub struct Grid {
|
||||
pub materials: Vec<u8>,
|
||||
pub healths: Vec<u8>,
|
||||
pub temps: Vec<u8>,
|
||||
pub flags: Vec<u8>,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
pub const FLAG_UPDATED: u8 = 0b0000_0001;
|
||||
pub const FLAG_PLAYER: u8 = 0b0000_0010;
|
||||
|
||||
impl Grid {
|
||||
pub fn new(width: u32, height: u32) -> Self {
|
||||
let size = (width * height) as usize;
|
||||
Self {
|
||||
materials: vec![0; size],
|
||||
healths: vec![255; size],
|
||||
temps: vec![0; size],
|
||||
flags: vec![0; size],
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index(&self, x: u32, y: u32) -> usize {
|
||||
(y * self.width + x) as usize
|
||||
}
|
||||
|
||||
pub fn in_bounds(&self, x: i32, y: i32) -> bool {
|
||||
x >= 0 && x < self.width as i32 && y >= 0 && y < self.height as i32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, x: u32, y: u32) -> u8 {
|
||||
self.materials[self.index(x, y)]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_signed(&self, x: i32, y: i32) -> u8 {
|
||||
if self.in_bounds(x, y) {
|
||||
self.materials[self.index(x as u32, y as u32)]
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set(&mut self, x: u32, y: u32, material: u8) {
|
||||
let idx = self.index(x, y);
|
||||
self.materials[idx] = material;
|
||||
self.flags[idx] |= FLAG_UPDATED;
|
||||
if material == 6 {
|
||||
self.temps[idx] = 200;
|
||||
self.healths[idx] = 255;
|
||||
} else if material == 9 {
|
||||
self.temps[idx] = 200;
|
||||
self.healths[idx] = 200;
|
||||
} else if material == 11 {
|
||||
self.temps[idx] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn swap(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) {
|
||||
let i1 = self.index(x1, y1);
|
||||
let i2 = self.index(x2, y2);
|
||||
self.materials.swap(i1, i2);
|
||||
self.healths.swap(i1, i2);
|
||||
self.temps.swap(i1, i2);
|
||||
self.flags[i1] |= FLAG_UPDATED;
|
||||
self.flags[i2] |= FLAG_UPDATED;
|
||||
}
|
||||
|
||||
pub fn is_empty(&self, x: u32, y: u32) -> bool {
|
||||
self.get(x, y) == 0
|
||||
}
|
||||
|
||||
pub fn clear_flags(&mut self) {
|
||||
for f in &mut self.flags {
|
||||
*f &= !FLAG_UPDATED;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_rect(&mut self, x: u32, y: u32, w: u32, h: u32, material: u8) {
|
||||
for dy in 0..h {
|
||||
for dx in 0..w {
|
||||
let px = x + dx;
|
||||
let py = y + dy;
|
||||
if px < self.width && py < self.height {
|
||||
self.set(px, py, material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_circle(&mut self, cx: u32, cy: u32, radius: u32, material: u8) {
|
||||
let r = radius as i32;
|
||||
for dy in -r..=r {
|
||||
for dx in -r..=r {
|
||||
if dx * dx + dy * dy <= r * r {
|
||||
let px = (cx as i32 + dx) as u32;
|
||||
let py = (cy as i32 + dy) as u32;
|
||||
if px < self.width && py < self.height {
|
||||
self.set(px, py, material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
pub mod grid;
|
||||
pub mod material;
|
||||
pub mod physics;
|
||||
pub mod render_buffer;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use grid::Grid;
|
||||
use physics::Physics;
|
||||
use render_buffer::RenderBuffer;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
thread_local! {
|
||||
static ENGINE: RefCell<Option<Engine>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
struct Engine {
|
||||
grid: Grid,
|
||||
physics: Physics,
|
||||
render_buffer: RenderBuffer,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn start() {
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
fn with_engine<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Engine) -> R,
|
||||
{
|
||||
ENGINE.with(|cell| {
|
||||
let mut opt = cell.borrow_mut();
|
||||
let engine = opt.as_mut().expect("Engine not initialized. Call init() first.");
|
||||
f(engine)
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn init(width: u32, height: u32) {
|
||||
let engine = Engine {
|
||||
grid: Grid::new(width, height),
|
||||
physics: Physics::new(),
|
||||
render_buffer: RenderBuffer::new(320, 180),
|
||||
};
|
||||
ENGINE.with(|cell| {
|
||||
*cell.borrow_mut() = Some(engine);
|
||||
});
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_width() -> u32 {
|
||||
with_engine(|e| e.grid.width)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_height() -> u32 {
|
||||
with_engine(|e| e.grid.height)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn fill_rect(x: u32, y: u32, w: u32, h: u32, material: u8) {
|
||||
with_engine(|e| e.grid.fill_rect(x, y, w, h, material));
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn fill_circle(x: u32, y: u32, radius: u32, material: u8) {
|
||||
with_engine(|e| e.grid.fill_circle(x, y, radius, material));
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn simulate(cam_x: i32, cam_y: i32, zoom: f32) {
|
||||
with_engine(|e| {
|
||||
let rw = e.render_buffer.width;
|
||||
let rh = e.render_buffer.height;
|
||||
e.physics.update(&mut e.grid, cam_x, cam_y, rw, rh, zoom);
|
||||
});
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn render_buffer_ptr() -> *const u8 {
|
||||
ENGINE.with(|cell| {
|
||||
let opt = cell.borrow();
|
||||
let engine = opt.as_ref().expect("Engine not initialized.");
|
||||
engine.render_buffer.pixels.as_ptr()
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn render_buffer_len() -> usize {
|
||||
ENGINE.with(|cell| {
|
||||
let opt = cell.borrow();
|
||||
let engine = opt.as_ref().expect("Engine not initialized.");
|
||||
engine.render_buffer.pixels.len()
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn render_width() -> u32 {
|
||||
with_engine(|e| e.render_buffer.width)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn render_height() -> u32 {
|
||||
with_engine(|e| e.render_buffer.height)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn update_render(cam_x: i32, cam_y: i32, zoom: f32) {
|
||||
with_engine(|e| e.render_buffer.render(&e.grid, cam_x, cam_y, zoom));
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn reset() {
|
||||
with_engine(|e| {
|
||||
e.grid = Grid::new(e.grid.width, e.grid.height);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
mod grid;
|
||||
mod material;
|
||||
mod physics;
|
||||
mod render_buffer;
|
||||
|
||||
use grid::Grid;
|
||||
use physics::Physics;
|
||||
use render_buffer::RenderBuffer;
|
||||
|
||||
fn main() {
|
||||
let width = 1024u32;
|
||||
let height = 768u32;
|
||||
let rw = 320u32;
|
||||
let rh = 180u32;
|
||||
let mut grid = Grid::new(width, height);
|
||||
let mut physics = Physics::new();
|
||||
let mut rb = RenderBuffer::new(rw, rh);
|
||||
|
||||
grid.fill_rect(440, 720, 144, 48, 1);
|
||||
grid.fill_rect(460, 700, 104, 20, 2);
|
||||
grid.fill_rect(470, 600, 84, 12, 5);
|
||||
grid.fill_circle(490, 560, 20, 3);
|
||||
grid.fill_rect(430, 650, 50, 30, 4);
|
||||
grid.fill_rect(540, 650, 50, 30, 6);
|
||||
grid.fill_rect(500, 640, 10, 18, 11);
|
||||
grid.fill_rect(480, 630, 64, 24, 5);
|
||||
grid.fill_rect(480, 618, 64, 12, 9);
|
||||
|
||||
let cam_x = 512i32;
|
||||
let cam_y = 600i32;
|
||||
let zoom = 2.0f32;
|
||||
|
||||
let mut frame = 0;
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
loop {
|
||||
physics.update(&mut grid, cam_x, cam_y, rw, rh, zoom);
|
||||
rb.render(&grid, cam_x, cam_y, zoom);
|
||||
frame += 1;
|
||||
|
||||
if frame % 60 == 0 {
|
||||
let elapsed = start.elapsed();
|
||||
let fps = frame as f64 / elapsed.as_secs_f64();
|
||||
let (fire, wood, spark, smoke) = count_mats(&grid);
|
||||
|
||||
println!("--- {}s (frame {}) fps: {:.0} ---", frame / 60, frame, fps);
|
||||
println!("fire: {}, wood: {}, spark: {}, smoke: {}", fire, wood, spark, smoke);
|
||||
|
||||
if fire == 0 {
|
||||
let elapsed = start.elapsed();
|
||||
let fps = frame as f64 / elapsed.as_secs_f64();
|
||||
println!("Fire died at frame {}, avg fps: {:.0}", frame, fps);
|
||||
break;
|
||||
}
|
||||
if frame >= 3600 {
|
||||
println!("Timeout at 60s");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn count_mats(grid: &Grid) -> (u32, u32, u32, u32) {
|
||||
let mut fire = 0u32;
|
||||
let mut wood = 0u32;
|
||||
let mut spark = 0u32;
|
||||
let mut smoke = 0u32;
|
||||
for y in 0..grid.height {
|
||||
for x in 0..grid.width {
|
||||
match grid.get(x, y) {
|
||||
5 => wood += 1,
|
||||
9 => fire += 1,
|
||||
14 => spark += 1,
|
||||
15 => smoke += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
(fire, wood, spark, smoke)
|
||||
}
|
||||
|
||||
fn temp_range(grid: &Grid) -> (u8, u8) {
|
||||
let mut min = 255u8;
|
||||
let mut max = 0u8;
|
||||
for y in 0..grid.height {
|
||||
for x in 0..grid.width {
|
||||
let idx = grid.index(x, y);
|
||||
let t = grid.temps[idx];
|
||||
if t > 0 {
|
||||
min = min.min(t);
|
||||
max = max.max(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
if min == 255 { min = 0; }
|
||||
(min, max)
|
||||
}
|
||||
|
||||
fn print_grid(grid: &Grid) {
|
||||
for y in 0..grid.height {
|
||||
for x in 0..grid.width {
|
||||
let c = match grid.get(x, y) {
|
||||
5 => 'W',
|
||||
9 => 'F',
|
||||
14 => '*',
|
||||
15 => '%',
|
||||
0 => '.',
|
||||
1 => '#',
|
||||
4 => '~',
|
||||
6 => 'L',
|
||||
8 => '\'',
|
||||
_ => '?',
|
||||
};
|
||||
print!("{}", c);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum Material {
|
||||
Air = 0,
|
||||
Stone = 1,
|
||||
Dirt = 2,
|
||||
Sand = 3,
|
||||
Water = 4,
|
||||
Wood = 5,
|
||||
Lava = 6,
|
||||
Player = 7,
|
||||
Steam = 8,
|
||||
Fire = 9,
|
||||
Glass = 10,
|
||||
Ice = 11,
|
||||
Oil = 12,
|
||||
Acid = 13,
|
||||
Spark = 14,
|
||||
Smoke = 15,
|
||||
}
|
||||
|
||||
impl Material {
|
||||
pub fn from_u8(val: u8) -> Material {
|
||||
match val {
|
||||
0 => Material::Air,
|
||||
1 => Material::Stone,
|
||||
2 => Material::Dirt,
|
||||
3 => Material::Sand,
|
||||
4 => Material::Water,
|
||||
5 => Material::Wood,
|
||||
6 => Material::Lava,
|
||||
7 => Material::Player,
|
||||
8 => Material::Steam,
|
||||
9 => Material::Fire,
|
||||
10 => Material::Glass,
|
||||
11 => Material::Ice,
|
||||
12 => Material::Oil,
|
||||
13 => Material::Acid,
|
||||
14 => Material::Spark,
|
||||
15 => Material::Smoke,
|
||||
_ => Material::Air,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MaterialProps {
|
||||
pub color: [u8; 4],
|
||||
pub density: u8,
|
||||
pub is_powder: bool,
|
||||
pub is_liquid: bool,
|
||||
pub is_gas: bool,
|
||||
pub is_solid: bool,
|
||||
pub is_static: bool,
|
||||
pub flammability: u8,
|
||||
pub melt_temp: u8,
|
||||
pub boil_temp: u8,
|
||||
pub heat_conduct: u8,
|
||||
pub acid_resist: u8,
|
||||
pub light_emit: u8,
|
||||
pub light_block: u8,
|
||||
}
|
||||
|
||||
pub const M: usize = 16;
|
||||
pub static PROPS: [MaterialProps; M] = [
|
||||
MaterialProps { color: [0, 0, 0, 0], density: 0, is_powder: false, is_liquid: false, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 255, boil_temp: 255, heat_conduct: 0, acid_resist: 255, light_emit: 0, light_block: 0 },
|
||||
MaterialProps { color: [120, 120, 130, 255], density: 100, is_powder: false, is_liquid: false, is_gas: false, is_solid: true, is_static: true, flammability: 0, melt_temp: 220, boil_temp: 255, heat_conduct: 3, acid_resist: 200, light_emit: 0, light_block: 255 },
|
||||
MaterialProps { color: [101, 67, 33, 255], density: 60, is_powder: true, is_liquid: false, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 200, boil_temp: 255, heat_conduct: 2, acid_resist: 100, light_emit: 0, light_block: 255 },
|
||||
MaterialProps { color: [194, 178, 128, 255], density: 40, is_powder: true, is_liquid: false, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 200, boil_temp: 255, heat_conduct: 5, acid_resist: 150, light_emit: 0, light_block: 200 },
|
||||
MaterialProps { color: [30, 60, 200, 180], density: 10, is_powder: false, is_liquid: true, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 0, boil_temp: 100, heat_conduct: 10, acid_resist: 200, light_emit: 0, light_block: 80 },
|
||||
MaterialProps { color: [90, 60, 30, 255], density: 80, is_powder: false, is_liquid: false, is_gas: false, is_solid: true, is_static: false, flammability: 30, melt_temp: 240, boil_temp: 255, heat_conduct: 1, acid_resist: 80, light_emit: 0, light_block: 255 },
|
||||
MaterialProps { color: [255, 100, 0, 255], density: 50, is_powder: false, is_liquid: true, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 200, boil_temp: 255, heat_conduct: 20, acid_resist: 255, light_emit: 200, light_block: 60 },
|
||||
MaterialProps { color: [50, 200, 50, 255], density: 90, is_powder: false, is_liquid: false, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 255, boil_temp: 255, heat_conduct: 5, acid_resist: 255, light_emit: 0, light_block: 100 },
|
||||
MaterialProps { color: [200, 200, 220, 140], density: 1, is_powder: false, is_liquid: false, is_gas: true, is_solid: false, is_static: false, flammability: 0, melt_temp: 0, boil_temp: 0, heat_conduct: 5, acid_resist: 255, light_emit: 0, light_block: 20 },
|
||||
MaterialProps { color: [255, 180, 30, 255], density: 2, is_powder: false, is_liquid: false, is_gas: false, is_solid: false, is_static: false, flammability: 255, melt_temp: 255, boil_temp: 255, heat_conduct: 50, acid_resist: 255, light_emit: 255, light_block: 30 },
|
||||
MaterialProps { color: [180, 210, 220, 255], density: 95, is_powder: false, is_liquid: false, is_gas: false, is_solid: true, is_static: false, flammability: 0, melt_temp: 160, boil_temp: 255, heat_conduct: 1, acid_resist: 250, light_emit: 0, light_block: 40 },
|
||||
MaterialProps { color: [180, 220, 255, 255], density: 85, is_powder: false, is_liquid: false, is_gas: false, is_solid: true, is_static: false, flammability: 0, melt_temp: 5, boil_temp: 100, heat_conduct: 8, acid_resist: 200, light_emit: 0, light_block: 100 },
|
||||
MaterialProps { color: [180, 140, 40, 200], density: 15, is_powder: false, is_liquid: true, is_gas: false, is_solid: false, is_static: false, flammability: 80, melt_temp: 0, boil_temp: 120, heat_conduct: 3, acid_resist: 200, light_emit: 0, light_block: 60 },
|
||||
MaterialProps { color: [100, 255, 60, 200], density: 30, is_powder: false, is_liquid: true, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 0, boil_temp: 150, heat_conduct: 15, acid_resist: 255, light_emit: 0, light_block: 80 },
|
||||
MaterialProps { color: [255, 180, 40, 255], density: 1, is_powder: false, is_liquid: false, is_gas: true, is_solid: false, is_static: false, flammability: 0, melt_temp: 255, boil_temp: 255, heat_conduct: 30, acid_resist: 255, light_emit: 180, light_block: 30 },
|
||||
MaterialProps { color: [160, 155, 150, 200], density: 1, is_powder: false, is_liquid: false, is_gas: true, is_solid: false, is_static: false, flammability: 0, melt_temp: 255, boil_temp: 255, heat_conduct: 5, acid_resist: 255, light_emit: 0, light_block: 80 },
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Interaction {
|
||||
pub attraction: i8,
|
||||
pub can_merge: bool,
|
||||
pub bond_strength: u8,
|
||||
}
|
||||
|
||||
impl Interaction {
|
||||
const fn none() -> Self {
|
||||
Interaction { attraction: 0, can_merge: false, bond_strength: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interact(a: u8, b: u8) -> Interaction {
|
||||
match (a, b) {
|
||||
(1, 4) | (4, 1) => Interaction { attraction: 5, can_merge: false, bond_strength: 3 },
|
||||
(1, 6) | (6, 1) => Interaction { attraction: 0, can_merge: false, bond_strength: 0 },
|
||||
(3, 4) | (4, 3) => Interaction { attraction: 8, can_merge: false, bond_strength: 0 },
|
||||
(4, 6) | (6, 4) => Interaction { attraction: -20, can_merge: false, bond_strength: 0 },
|
||||
(5, 9) | (9, 5) => Interaction { attraction: 0, can_merge: false, bond_strength: 0 },
|
||||
(5, 4) | (4, 5) => Interaction { attraction: 10, can_merge: false, bond_strength: 2 },
|
||||
(11, 4) | (4, 11) => Interaction { attraction: 15, can_merge: false, bond_strength: 5 },
|
||||
(11, 6) | (6, 11) => Interaction { attraction: -30, can_merge: false, bond_strength: 0 },
|
||||
(9, 4) | (4, 9) => Interaction { attraction: -10, can_merge: false, bond_strength: 0 },
|
||||
(12, 9) | (9, 12) => Interaction { attraction: 0, can_merge: false, bond_strength: 0 },
|
||||
(13, 1) | (1, 13) => Interaction { attraction: 0, can_merge: false, bond_strength: 0 },
|
||||
(13, 3) | (3, 13) | (13, 5) | (5, 13) => Interaction { attraction: 0, can_merge: false, bond_strength: 0 },
|
||||
_ => {
|
||||
if a == b {
|
||||
if a == 4 || a == 6 || a == 12 || a == 13 {
|
||||
Interaction { attraction: 20, can_merge: true, bond_strength: 1 }
|
||||
} else if a == 3 || a == 2 {
|
||||
Interaction { attraction: 5, can_merge: false, bond_strength: 0 }
|
||||
} else if a == 1 || a == 10 || a == 11 {
|
||||
Interaction { attraction: 30, can_merge: false, bond_strength: 10 }
|
||||
} else if a == 5 {
|
||||
Interaction { attraction: 15, can_merge: false, bond_strength: 8 }
|
||||
} else {
|
||||
Interaction::none()
|
||||
}
|
||||
} else {
|
||||
Interaction::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform(temp: u8, material: u8) -> u8 {
|
||||
match material {
|
||||
4 => if temp >= 100 { 8 } else { 4 },
|
||||
11 => if temp >= 5 { 4 } else { 11 },
|
||||
6 => if temp <= 5 { 1 } else { 6 },
|
||||
8 => if temp <= 80 { 4 } else { 8 },
|
||||
5 => if temp >= 240 { 9 } else { 5 },
|
||||
12 => if temp >= 120 { 9 } else { 12 },
|
||||
_ => material,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
use crate::grid::Grid;
|
||||
use crate::material::{self, PROPS};
|
||||
|
||||
const MOORE_X: [i32; 8] = [-1, 0, 1, -1, 1, -1, 0, 1];
|
||||
const MOORE_Y: [i32; 8] = [-1, -1, -1, 0, 0, 1, 1, 1];
|
||||
|
||||
pub struct Physics {
|
||||
rand_state: u32,
|
||||
frame: u32,
|
||||
}
|
||||
|
||||
impl Physics {
|
||||
pub fn new() -> Self {
|
||||
Self { rand_state: 12345, frame: 0 }
|
||||
}
|
||||
|
||||
fn rand(&mut self) -> u32 {
|
||||
self.rand_state = self.rand_state.wrapping_mul(1103515245).wrapping_add(12345);
|
||||
self.rand_state >> 16
|
||||
}
|
||||
|
||||
fn rand_dir(&mut self) -> i32 {
|
||||
if self.rand() & 1 == 0 { -1 } else { 1 }
|
||||
}
|
||||
|
||||
pub fn update(&mut self, grid: &mut Grid, cam_x: i32, cam_y: i32, rw: u32, rh: u32, zoom: f32) {
|
||||
self.frame = self.frame.wrapping_add(1);
|
||||
let w = grid.width as i32;
|
||||
let h = grid.height as i32;
|
||||
|
||||
let margin = 200;
|
||||
let vw = (rw as f32 / zoom) as i32;
|
||||
let vh = (rh as f32 / zoom) as i32;
|
||||
let x0 = (cam_x - vw / 2 - margin).max(0) as u32;
|
||||
let y0 = (cam_y - vh / 2 - margin).max(0) as u32;
|
||||
let x1 = (cam_x + vw / 2 + margin).min(w) as u32;
|
||||
let y1 = (cam_y + vh / 2 + margin).min(h) as u32;
|
||||
|
||||
self.update_fire(grid, x0, y0, x1, y1);
|
||||
|
||||
for y in (y0..y1).rev() {
|
||||
let parity = (self.frame & 1) as u32;
|
||||
let xs = if x0 & 1 == parity { x0 } else { x0 + 1 };
|
||||
for x in (xs..x1).step_by(2) {
|
||||
let idx = grid.index(x, y);
|
||||
let mat = grid.materials[idx];
|
||||
if mat == 0 {
|
||||
continue;
|
||||
}
|
||||
let temp = grid.temps[idx];
|
||||
|
||||
self.transfer_heat(grid, x, y, mat, temp);
|
||||
self.react(grid, x, y, mat);
|
||||
|
||||
let mat = grid.materials[grid.index(x, y)];
|
||||
let temp = grid.temps[grid.index(x, y)];
|
||||
self.transform(grid, x, y, mat, temp);
|
||||
|
||||
let mat = grid.materials[grid.index(x, y)];
|
||||
if mat == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let props = &PROPS[mat as usize];
|
||||
|
||||
if props.is_powder {
|
||||
self.update_powder(grid, x, y, mat);
|
||||
} else if props.is_liquid {
|
||||
self.update_liquid(grid, x, y, mat);
|
||||
} else if props.is_solid && !props.is_static {
|
||||
self.update_solid(grid, x, y, mat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.frame % 2 == 0 {
|
||||
self.update_gases(grid, x0, y0, x1, y1);
|
||||
}
|
||||
self.update_ephemeral(grid, x0, y0, x1, y1);
|
||||
}
|
||||
|
||||
fn transfer_heat(&mut self, grid: &mut Grid, x: u32, y: u32, mat: u8, temp: u8) {
|
||||
let props = &PROPS[mat as usize];
|
||||
if props.heat_conduct == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let idx = grid.index(x, y);
|
||||
for i in 0..8 {
|
||||
let nx = x as i32 + MOORE_X[i];
|
||||
let ny = y as i32 + MOORE_Y[i];
|
||||
if !grid.in_bounds(nx, ny) {
|
||||
continue;
|
||||
}
|
||||
let ni = grid.index(nx as u32, ny as u32);
|
||||
let nmat = grid.materials[ni];
|
||||
if nmat == 0 {
|
||||
continue;
|
||||
}
|
||||
let ntemp = grid.temps[ni];
|
||||
if temp == ntemp {
|
||||
continue;
|
||||
}
|
||||
|
||||
let diff = if temp > ntemp {
|
||||
let d = temp - ntemp;
|
||||
let rate = (props.heat_conduct as u32 * d as u32) / 255;
|
||||
((rate + 1) / 2) as u8
|
||||
} else {
|
||||
let d = ntemp - temp;
|
||||
let nprops = &PROPS[nmat as usize];
|
||||
let rate = (nprops.heat_conduct as u32 * d as u32) / 255;
|
||||
((rate + 1) / 2) as u8
|
||||
};
|
||||
|
||||
if diff == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if temp > ntemp {
|
||||
grid.temps[idx] = temp.saturating_sub(diff);
|
||||
grid.temps[ni] = ntemp.saturating_add(diff);
|
||||
} else {
|
||||
grid.temps[idx] = temp.saturating_add(diff);
|
||||
grid.temps[ni] = ntemp.saturating_sub(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn react(&mut self, grid: &mut Grid, x: u32, y: u32, mat: u8) {
|
||||
let idx = grid.index(x, y);
|
||||
|
||||
for i in 0..8 {
|
||||
let nx = x as i32 + MOORE_X[i];
|
||||
let ny = y as i32 + MOORE_Y[i];
|
||||
if !grid.in_bounds(nx, ny) {
|
||||
continue;
|
||||
}
|
||||
let ni = grid.index(nx as u32, ny as u32);
|
||||
let nmat = grid.materials[ni];
|
||||
if nmat == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
match (mat, nmat) {
|
||||
(4, 6) | (6, 4) => {
|
||||
if self.rand() % 4 == 0 {
|
||||
if mat == 4 {
|
||||
grid.materials[idx] = 8;
|
||||
grid.materials[ni] = 1;
|
||||
grid.temps[idx] = 100;
|
||||
grid.temps[ni] = 10;
|
||||
} else {
|
||||
grid.materials[idx] = 1;
|
||||
grid.materials[ni] = 8;
|
||||
grid.temps[idx] = 10;
|
||||
grid.temps[ni] = 100;
|
||||
}
|
||||
grid.flags[idx] |= crate::grid::FLAG_UPDATED;
|
||||
grid.flags[ni] |= crate::grid::FLAG_UPDATED;
|
||||
}
|
||||
}
|
||||
(6, 5) | (5, 6) | (6, 12) | (12, 6) => {
|
||||
if self.rand() % 2 == 0 {
|
||||
if mat == 6 {
|
||||
grid.materials[ni] = 9;
|
||||
grid.temps[ni] = 200;
|
||||
grid.healths[ni] = 200;
|
||||
} else {
|
||||
grid.materials[idx] = 9;
|
||||
grid.temps[idx] = 200;
|
||||
grid.healths[idx] = 200;
|
||||
}
|
||||
grid.flags[idx] |= crate::grid::FLAG_UPDATED;
|
||||
grid.flags[ni] |= crate::grid::FLAG_UPDATED;
|
||||
}
|
||||
}
|
||||
(4, 9) => {
|
||||
if self.rand() % 2 == 0 {
|
||||
grid.materials[idx] = 8;
|
||||
grid.materials[ni] = 0;
|
||||
grid.temps[idx] = 90;
|
||||
grid.temps[ni] = 0;
|
||||
grid.flags[idx] |= crate::grid::FLAG_UPDATED;
|
||||
grid.flags[ni] |= crate::grid::FLAG_UPDATED;
|
||||
}
|
||||
}
|
||||
(13, 5) | (13, 3) => {
|
||||
if self.rand() % 4 == 0 {
|
||||
grid.materials[ni] = 0;
|
||||
grid.healths[idx] = grid.healths[idx].saturating_sub(5);
|
||||
grid.flags[ni] |= crate::grid::FLAG_UPDATED;
|
||||
}
|
||||
}
|
||||
(13, 1) | (13, 2) => {
|
||||
if self.rand() % 8 == 0 {
|
||||
grid.materials[ni] = 0;
|
||||
grid.healths[idx] = grid.healths[idx].saturating_sub(2);
|
||||
grid.flags[ni] |= crate::grid::FLAG_UPDATED;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn transform(&mut self, grid: &mut Grid, x: u32, y: u32, mat: u8, temp: u8) {
|
||||
let idx = grid.index(x, y);
|
||||
let new_mat = material::transform(temp, mat);
|
||||
if new_mat != mat {
|
||||
grid.materials[idx] = new_mat;
|
||||
grid.flags[idx] |= crate::grid::FLAG_UPDATED;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_fire(&mut self, grid: &mut Grid, x0: u32, y0: u32, x1: u32, y1: u32) {
|
||||
for y in (y0..y1).rev() {
|
||||
for x in x0..x1 {
|
||||
let idx = grid.index(x, y);
|
||||
if grid.materials[idx] != 9 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut extinguish = false;
|
||||
for i in 0..8 {
|
||||
let nx = x as i32 + MOORE_X[i];
|
||||
let ny = y as i32 + MOORE_Y[i];
|
||||
if !grid.in_bounds(nx, ny) { continue; }
|
||||
let ni = grid.index(nx as u32, ny as u32);
|
||||
let nmat = grid.materials[ni];
|
||||
if nmat == 4 || nmat == 11 {
|
||||
grid.materials[idx] = 0;
|
||||
grid.temps[idx] = 0;
|
||||
grid.flags[idx] |= crate::grid::FLAG_UPDATED;
|
||||
if nmat == 4 {
|
||||
grid.materials[ni] = 8;
|
||||
grid.temps[ni] = 90;
|
||||
} else {
|
||||
grid.materials[ni] = 4;
|
||||
grid.temps[ni] = 5;
|
||||
}
|
||||
grid.flags[ni] |= crate::grid::FLAG_UPDATED;
|
||||
extinguish = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if extinguish {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut on_fuel = false;
|
||||
if y + 1 < grid.height {
|
||||
let below_idx = grid.index(x, y + 1);
|
||||
let below_mat = grid.materials[below_idx];
|
||||
if PROPS[below_mat as usize].flammability > 0 && below_mat != 9 {
|
||||
on_fuel = true;
|
||||
let flam = PROPS[below_mat as usize].flammability as u32;
|
||||
let threshold = (300 / flam).max(3);
|
||||
if self.rand() % threshold == 0 {
|
||||
let bh = grid.healths[below_idx];
|
||||
if bh <= 1 {
|
||||
grid.materials[below_idx] = 0;
|
||||
grid.flags[below_idx] |= crate::grid::FLAG_UPDATED;
|
||||
} else {
|
||||
grid.healths[below_idx] = bh - 1;
|
||||
}
|
||||
}
|
||||
if y > 0 && self.rand() % 6 == 0 {
|
||||
for &sdx in &[0, -1, 1] {
|
||||
let sx = x as i32 + sdx;
|
||||
if grid.in_bounds(sx, y as i32 - 1) {
|
||||
let si = grid.index(sx as u32, y - 1);
|
||||
if grid.materials[si] == 0 {
|
||||
grid.materials[si] = 15;
|
||||
grid.temps[si] = 50;
|
||||
grid.healths[si] = 140 + (self.rand() % 80) as u8;
|
||||
grid.flags[si] |= crate::grid::FLAG_UPDATED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !on_fuel && y + 1 < grid.height && grid.get(x, y + 1) == 9 {
|
||||
on_fuel = true;
|
||||
}
|
||||
if !on_fuel {
|
||||
for i in 0..8 {
|
||||
let nx = x as i32 + MOORE_X[i];
|
||||
let ny = y as i32 + MOORE_Y[i];
|
||||
if !grid.in_bounds(nx, ny) { continue; }
|
||||
let ni = grid.index(nx as u32, ny as u32);
|
||||
let nmat = grid.materials[ni];
|
||||
if nmat == 9 { continue; }
|
||||
if PROPS[nmat as usize].flammability > 0 {
|
||||
on_fuel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fh = grid.healths[idx];
|
||||
if on_fuel {
|
||||
grid.healths[idx] = fh;
|
||||
} else {
|
||||
grid.healths[idx] = fh.saturating_sub(6);
|
||||
}
|
||||
|
||||
if grid.healths[idx] == 0 {
|
||||
grid.materials[idx] = 0;
|
||||
grid.temps[idx] = 0;
|
||||
grid.flags[idx] |= crate::grid::FLAG_UPDATED;
|
||||
continue;
|
||||
}
|
||||
|
||||
grid.temps[idx] = 200;
|
||||
|
||||
for i in 0..8 {
|
||||
let nx = x as i32 + MOORE_X[i];
|
||||
let ny = y as i32 + MOORE_Y[i];
|
||||
if !grid.in_bounds(nx, ny) { continue; }
|
||||
let ni = grid.index(nx as u32, ny as u32);
|
||||
let nmat = grid.materials[ni];
|
||||
if nmat == 0 { continue; }
|
||||
|
||||
if PROPS[nmat as usize].flammability > 0 && nmat != 9 && self.rand() % 250 == 0 {
|
||||
grid.materials[ni] = 9;
|
||||
grid.temps[ni] = 200;
|
||||
grid.healths[ni] = 40;
|
||||
grid.flags[ni] |= crate::grid::FLAG_UPDATED;
|
||||
}
|
||||
}
|
||||
|
||||
if y > 0 && self.rand() % 5 == 0 {
|
||||
let above = grid.get(x, y - 1);
|
||||
if above == 0 {
|
||||
let ai = grid.index(x, y - 1);
|
||||
grid.materials[ai] = 14;
|
||||
grid.temps[ai] = 180;
|
||||
grid.healths[ai] = 4 + (self.rand() % 8) as u8;
|
||||
grid.flags[ai] |= crate::grid::FLAG_UPDATED;
|
||||
} else {
|
||||
let sdir = self.rand_dir();
|
||||
let sx = x as i32 + sdir;
|
||||
if grid.in_bounds(sx, y as i32 - 1) && grid.get(sx as u32, y - 1) == 0 {
|
||||
let si = grid.index(sx as u32, y - 1);
|
||||
grid.materials[si] = 14;
|
||||
grid.temps[si] = 180;
|
||||
grid.healths[si] = 4 + (self.rand() % 8) as u8;
|
||||
grid.flags[si] |= crate::grid::FLAG_UPDATED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if y > 0 && self.rand() % 16 == 0 {
|
||||
let above = grid.get(x, y - 1);
|
||||
if above == 0 {
|
||||
let ai = grid.index(x, y - 1);
|
||||
grid.materials[ai] = 14;
|
||||
grid.temps[ai] = 150;
|
||||
grid.healths[ai] = 30 + (self.rand() % 50) as u8;
|
||||
grid.flags[ai] |= crate::grid::FLAG_UPDATED;
|
||||
} else {
|
||||
let sdir = self.rand_dir();
|
||||
let sx = x as i32 + sdir;
|
||||
if grid.in_bounds(sx, y as i32 - 1) && grid.get(sx as u32, y - 1) == 0 {
|
||||
let si = grid.index(sx as u32, y - 1);
|
||||
grid.materials[si] = 14;
|
||||
grid.temps[si] = 150;
|
||||
grid.healths[si] = 30 + (self.rand() % 50) as u8;
|
||||
grid.flags[si] |= crate::grid::FLAG_UPDATED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_powder(&mut self, grid: &mut Grid, x: u32, y: u32, _mat: u8) {
|
||||
if y + 1 >= grid.height {
|
||||
return;
|
||||
}
|
||||
|
||||
let below = grid.get(x, y + 1);
|
||||
let bprops = &PROPS[below as usize];
|
||||
if below == 0 || bprops.is_liquid || bprops.is_gas {
|
||||
grid.swap(x, y, x, y + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
let dir = self.rand_dir();
|
||||
let lx = x as i32 + dir;
|
||||
let rx = x as i32 - dir;
|
||||
|
||||
if grid.in_bounds(lx, y as i32 + 1) && grid.get(lx as u32, y + 1) == 0 {
|
||||
grid.swap(x, y, lx as u32, y + 1);
|
||||
} else if grid.in_bounds(rx, y as i32 + 1) && grid.get(rx as u32, y + 1) == 0 {
|
||||
grid.swap(x, y, rx as u32, y + 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_liquid(&mut self, grid: &mut Grid, x: u32, y: u32, mat: u8) {
|
||||
if y + 1 >= grid.height {
|
||||
return;
|
||||
}
|
||||
|
||||
let mprops = &PROPS[mat as usize];
|
||||
|
||||
let below = grid.get(x, y + 1);
|
||||
let bprops = &PROPS[below as usize];
|
||||
let can_fall = below == 0 || bprops.is_gas
|
||||
|| (bprops.is_liquid && mprops.density > bprops.density);
|
||||
|
||||
if can_fall {
|
||||
let was_liquid = bprops.is_liquid;
|
||||
grid.swap(x, y, x, y + 1);
|
||||
if was_liquid {
|
||||
let pdir = self.rand_dir();
|
||||
for dist in 1..=4i32 {
|
||||
let sx = x as i32 + pdir * dist;
|
||||
if !grid.in_bounds(sx, y as i32 + 1) { break; }
|
||||
let sxu = sx as u32;
|
||||
if grid.get(sxu, y + 1) == 0 {
|
||||
grid.swap(x, y + 1, sxu, y + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let dir = self.rand_dir();
|
||||
for &d in &[dir, -dir] {
|
||||
let dx = x as i32 + d;
|
||||
if grid.in_bounds(dx, y as i32 + 1) {
|
||||
let db = grid.get(dx as u32, y + 1);
|
||||
let dp = &PROPS[db as usize];
|
||||
if db == 0 || dp.is_gas || (dp.is_liquid && mprops.density > dp.density) {
|
||||
grid.swap(x, y, dx as u32, y + 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let max_spread = 10 + (self.rand() % 40) as i32;
|
||||
for &d in &[dir, -dir] {
|
||||
for dist in 1..=max_spread {
|
||||
let sx = x as i32 + d * dist;
|
||||
if !grid.in_bounds(sx, y as i32) { break; }
|
||||
let sxu = sx as u32;
|
||||
let cell = grid.get(sxu, y);
|
||||
if cell != 0 && !PROPS[cell as usize].is_liquid { break; }
|
||||
let below_cell = grid.get_signed(sx, y as i32 + 1);
|
||||
if cell == 0 && below_cell != 0 {
|
||||
grid.swap(x, y, sxu, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_solid(&mut self, grid: &mut Grid, x: u32, y: u32, _mat: u8) {
|
||||
if y + 1 >= grid.height {
|
||||
return;
|
||||
}
|
||||
|
||||
let below = grid.get(x, y + 1);
|
||||
let bprops = &PROPS[below as usize];
|
||||
if below == 0 || bprops.is_liquid || bprops.is_gas {
|
||||
grid.swap(x, y, x, y + 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_gases(&mut self, grid: &mut Grid, x0: u32, y0: u32, x1: u32, y1: u32) {
|
||||
for y in y0..y1 {
|
||||
for x in x0..x1 {
|
||||
let idx = grid.index(x, y);
|
||||
let mat = grid.materials[idx];
|
||||
if mat == 0 { continue; }
|
||||
let props = &PROPS[mat as usize];
|
||||
if !props.is_gas { continue; }
|
||||
if y == 0 { continue; }
|
||||
|
||||
let above = grid.get(x, y - 1);
|
||||
if above == 0 {
|
||||
grid.swap(x, y, x, y - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
let dir = self.rand_dir();
|
||||
let dx = x as i32 + dir;
|
||||
if grid.in_bounds(dx, y as i32 - 1) {
|
||||
let dau = dx as u32;
|
||||
if grid.get(dau, y - 1) == 0 {
|
||||
grid.swap(x, y, dau, y - 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let odx = x as i32 - dir;
|
||||
if grid.in_bounds(odx, y as i32) && grid.get(odx as u32, y) == 0 {
|
||||
grid.swap(x, y, odx as u32, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_ephemeral(&mut self, grid: &mut Grid, x0: u32, y0: u32, x1: u32, y1: u32) {
|
||||
for y in y0..y1 {
|
||||
for x in x0..x1 {
|
||||
let idx = grid.index(x, y);
|
||||
let mat = grid.materials[idx];
|
||||
if mat != 14 && mat != 15 {
|
||||
continue;
|
||||
}
|
||||
let hh = grid.healths[idx];
|
||||
if hh <= 1 {
|
||||
grid.materials[idx] = 0;
|
||||
grid.temps[idx] = 0;
|
||||
grid.flags[idx] |= crate::grid::FLAG_UPDATED;
|
||||
} else {
|
||||
grid.healths[idx] = hh - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
use crate::grid::Grid;
|
||||
use crate::material::PROPS;
|
||||
|
||||
pub struct RenderBuffer {
|
||||
pub pixels: Vec<u8>,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
light: Vec<u8>,
|
||||
block: Vec<u8>,
|
||||
}
|
||||
|
||||
impl RenderBuffer {
|
||||
pub fn new(width: u32, height: u32) -> Self {
|
||||
let size = (width * height * 4) as usize;
|
||||
let lsize = (width * height) as usize;
|
||||
Self {
|
||||
pixels: vec![0; size],
|
||||
width,
|
||||
height,
|
||||
light: vec![0; lsize],
|
||||
block: vec![0; lsize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, grid: &Grid, cam_x: i32, cam_y: i32, zoom: f32) {
|
||||
let rw = self.width as i32;
|
||||
let rh = self.height as i32;
|
||||
let total = (rw * rh) as usize;
|
||||
|
||||
for i in 0..total {
|
||||
self.light[i] = 0;
|
||||
self.block[i] = 0;
|
||||
}
|
||||
|
||||
for py in 0..rh {
|
||||
let gy = cam_y + ((py as f32 - rh as f32 / 2.0) / zoom) as i32;
|
||||
for px in 0..rw {
|
||||
let gx = cam_x + ((px as f32 - rw as f32 / 2.0) / zoom) as i32;
|
||||
let idx = (py * rw + px) as usize;
|
||||
if grid.in_bounds(gx, gy) {
|
||||
let gi = grid.index(gx as u32, gy as u32);
|
||||
let mat = grid.materials[gi];
|
||||
if mat > 0 {
|
||||
let props = &PROPS[mat as usize];
|
||||
self.light[idx] = props.light_emit;
|
||||
self.block[idx] = props.light_block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _pass in 0..12 {
|
||||
let dir = _pass & 1;
|
||||
let mut changed = false;
|
||||
if dir == 0 {
|
||||
for py in 0..rh {
|
||||
for px in 0..rw {
|
||||
if self.spread_light(py, px, rw, rh) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for py in (0..rh).rev() {
|
||||
for px in (0..rw).rev() {
|
||||
if self.spread_light(py, px, rw, rh) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for py in 0..rh {
|
||||
for px in 0..rw {
|
||||
let gx = cam_x + ((px as f32 - rw as f32 / 2.0) / zoom) as i32;
|
||||
let gy = cam_y + ((py as f32 - rh as f32 / 2.0) / zoom) as i32;
|
||||
|
||||
let color = if grid.in_bounds(gx, gy) {
|
||||
let gi = grid.index(gx as u32, gy as u32);
|
||||
let mat = grid.materials[gi];
|
||||
let li = (py * rw + px) as usize;
|
||||
let lvl = self.light[li] as u32;
|
||||
|
||||
if mat == 0 {
|
||||
let bg = [20u32, 20, 30];
|
||||
let glow_r = 255u32;
|
||||
let glow_g = 160u32;
|
||||
let glow_b = 40u32;
|
||||
let t = ((lvl * lvl) / 255).min(255);
|
||||
let r = (bg[0] * (255 - t) + glow_r * t) / 255;
|
||||
let g = (bg[1] * (255 - t) + glow_g * t) / 255;
|
||||
let b = (bg[2] * (255 - t) + glow_b * t) / 255;
|
||||
[r as u8, g as u8, b as u8, 255]
|
||||
} else {
|
||||
let props = &PROPS[mat as usize];
|
||||
let base = props.color;
|
||||
let temp = grid.temps[gi];
|
||||
let t = (temp as f32 / 200.0).min(1.0);
|
||||
|
||||
let mut r = (base[0] as f32 + t * 60.0) as u8;
|
||||
let mut g = (base[1] as f32 * (1.0 - t * 0.4)) as u8;
|
||||
let mut b = (base[2] as f32 * (1.0 - t * 0.6)) as u8;
|
||||
|
||||
if mat == 9 || mat == 6 {
|
||||
let (fr, fg, fb) = flame_variation(gx as u32, gy as u32, mat);
|
||||
r = (r as i32 + fr).clamp(0, 255) as u8;
|
||||
g = (g as i32 + fg).clamp(0, 255) as u8;
|
||||
b = (b as i32 + fb).clamp(0, 255) as u8;
|
||||
} else {
|
||||
let var = texture_offset(gx as u32, gy as u32, mat);
|
||||
r = (r as i32 + var).clamp(0, 255) as u8;
|
||||
g = (g as i32 + var).clamp(0, 255) as u8;
|
||||
b = (b as i32 + var).clamp(0, 255) as u8;
|
||||
}
|
||||
|
||||
if props.light_emit == 0 {
|
||||
let ambient = 150u32;
|
||||
let add = lvl;
|
||||
r = ((r as u32 * ambient >> 8) + add).min(255) as u8;
|
||||
g = ((g as u32 * ambient >> 8) + add).min(255) as u8;
|
||||
b = ((b as u32 * ambient >> 8) + add).min(255) as u8;
|
||||
}
|
||||
|
||||
[r, g, b, base[3]]
|
||||
}
|
||||
} else {
|
||||
[20, 20, 30, 255]
|
||||
};
|
||||
|
||||
let pi = ((py * rw + px) * 4) as usize;
|
||||
self.pixels[pi] = color[0];
|
||||
self.pixels[pi + 1] = color[1];
|
||||
self.pixels[pi + 2] = color[2];
|
||||
self.pixels[pi + 3] = color[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spread_light(&mut self, py: i32, px: i32, rw: i32, rh: i32) -> bool {
|
||||
let idx = (py * rw + px) as usize;
|
||||
let cur = self.light[idx] as u32;
|
||||
let blk = (self.block[idx] as u32).min(200);
|
||||
|
||||
let neighbors: [(i32, i32); 8] = [
|
||||
(px - 1, py - 1), (px, py - 1), (px + 1, py - 1),
|
||||
(px - 1, py), (px + 1, py),
|
||||
(px - 1, py + 1), (px, py + 1), (px + 1, py + 1),
|
||||
];
|
||||
|
||||
let mut best = cur;
|
||||
for &(nx, ny) in &neighbors {
|
||||
if nx >= 0 && nx < rw && ny >= 0 && ny < rh {
|
||||
let ni = (ny * rw + nx) as usize;
|
||||
let nlight = self.light[ni] as u32;
|
||||
if nlight <= 1 { continue; }
|
||||
let falloff = (if nx == px || ny == py { 2 } else { 3 }) + blk;
|
||||
if nlight > falloff {
|
||||
let incoming = nlight - falloff;
|
||||
if incoming > best {
|
||||
best = incoming;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if blk > 0 && blk < 180 && best > cur && best > 4 {
|
||||
for &(nx, ny) in &neighbors {
|
||||
if nx >= 0 && nx < rw && ny >= 0 && ny < rh {
|
||||
let ni = (ny * rw + nx) as usize;
|
||||
let nlight = self.light[ni] as u32;
|
||||
if nlight >= best { continue; }
|
||||
let reflect = best >> 1;
|
||||
if reflect > nlight {
|
||||
self.light[ni] = reflect as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if best != cur {
|
||||
self.light[idx] = best as u8;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn texture_offset(x: u32, y: u32, mat: u8) -> i32 {
|
||||
match mat {
|
||||
5 => {
|
||||
let wobble = ((y.wrapping_mul(5) ^ y.wrapping_shr(2)) & 3) as i32 - 1;
|
||||
let pos = (x as i32).wrapping_add(wobble);
|
||||
let grain = pos % 5;
|
||||
let line_id = (pos / 5) as u32;
|
||||
let darkness = hash(line_id, 0) & 15;
|
||||
if grain == 0 {
|
||||
-12 - darkness as i32
|
||||
} else if grain == 1 {
|
||||
-5
|
||||
} else {
|
||||
let n = hash(x.wrapping_add(y >> 1), y);
|
||||
((n as i32 - 128) * 7) >> 7
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
let n = hash(x.wrapping_mul(3) >> 2, y.wrapping_mul(3) >> 2);
|
||||
((n as i32 - 128) * 20) >> 7
|
||||
}
|
||||
1 | 10 => {
|
||||
let n = hash(x, y);
|
||||
((n as i32 - 128) * 10) >> 7
|
||||
}
|
||||
3 => {
|
||||
let n = hash(x.wrapping_add(y), y);
|
||||
((n as i32 - 128) * 6) >> 7
|
||||
}
|
||||
11 => {
|
||||
let n = hash(x, y.wrapping_mul(y));
|
||||
((n as i32 - 128) * 5) >> 7
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn hash(x: u32, y: u32) -> u8 {
|
||||
let h = x.wrapping_mul(374761393)
|
||||
.wrapping_add(y.wrapping_mul(668265263));
|
||||
(h ^ h.wrapping_shr(13)).wrapping_mul(1274126177) as u8
|
||||
}
|
||||
|
||||
fn flame_variation(x: u32, y: u32, mat: u8) -> (i32, i32, i32) {
|
||||
let h = hash(x, y) as i32;
|
||||
let r_var = ((h - 128) * 6) >> 7;
|
||||
if mat == 9 {
|
||||
let g_var = ((h.wrapping_sub(40) as i32 - 128) * 12) >> 7;
|
||||
let b_var = ((h.wrapping_add(60) as i32 - 128) * 8) >> 7;
|
||||
(-r_var, g_var, b_var)
|
||||
} else {
|
||||
let g_var = ((h - 128) * 8) >> 7;
|
||||
let b_var = ((h - 128) * 5) >> 7;
|
||||
(r_var, g_var, b_var)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user