use crate::grid::Grid; use crate::material::PROPS; pub struct RenderBuffer { pub pixels: Vec, pub width: u32, pub height: u32, light: Vec, block: Vec, zoom: f32, } 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], zoom: 1.0, } } 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; self.zoom = zoom; 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; } } } } let max_passes = (16.0 / zoom).max(6.0).min(24.0) as u32; for _pass in 0..max_passes { 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 base_falloff = if nx == px || ny == py { 2.0 } else { 3.0 }; let falloff = (base_falloff / self.zoom) as u32 + 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 } 16 => { let n = hash(x ^ (y >> 2), y ^ (x >> 2)); let highlight = ((x.wrapping_mul(13) ^ y.wrapping_mul(7)) & 31) as i32; if highlight < 3 { 25 + (highlight * 6) } else if highlight < 6 { 14 } else { ((n as i32 - 128) * 10) >> 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) } }