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,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