extern crate alloc; use alloc::vec; use alloc::vec::Vec; use core::ptr; use crate::math::Vec3; use crate::pxl8::*; const CHUNK_SIZE: i32 = PXL8_VXL_CHUNK_SIZE as i32; const CHUNK_VOLUME: usize = PXL8_VXL_CHUNK_VOLUME as usize; const WORLD_CHUNK_SIZE: f32 = PXL8_VXL_WORLD_CHUNK_SIZE as f32; const AIR: u8 = PXL8_VXL_BLOCK_AIR as u8; const GRASS: u8 = 3; const DIRT: u8 = 2; const STONE: u8 = 1; pub struct VoxelChunk { pub chunk: *mut pxl8_vxl_chunk, pub cx: i32, pub cy: i32, pub cz: i32, } impl VoxelChunk { pub fn new(cx: i32, cy: i32, cz: i32) -> Self { let chunk = unsafe { pxl8_vxl_chunk_create() }; Self { chunk, cx, cy, cz } } pub fn get(&self, x: i32, y: i32, z: i32) -> u8 { if self.chunk.is_null() { return AIR; } unsafe { pxl8_vxl_block_get(self.chunk, x, y, z) } } pub fn set(&mut self, x: i32, y: i32, z: i32, block: u8) { if self.chunk.is_null() { return; } unsafe { pxl8_vxl_block_set(self.chunk, x, y, z, block) } } pub fn fill(&mut self, block: u8) { if self.chunk.is_null() { return; } unsafe { pxl8_vxl_block_fill(self.chunk, block) } } pub fn is_uniform(&self) -> bool { if self.chunk.is_null() { return true; } unsafe { pxl8_vxl_chunk_is_uniform(self.chunk) } } pub fn rle_encode(&self) -> Vec { if self.chunk.is_null() { return Vec::new(); } let mut linear = vec![0u8; CHUNK_VOLUME]; unsafe { pxl8_vxl_chunk_linearize(self.chunk, linear.as_mut_ptr()); } let mut result = Vec::new(); let mut i = 0; while i < CHUNK_VOLUME { let block = linear[i]; let mut run_len = 1usize; while i + run_len < CHUNK_VOLUME && linear[i + run_len] == block && run_len < 256 { run_len += 1; } result.push(block); result.push((run_len - 1) as u8); i += run_len; } result } } impl Drop for VoxelChunk { fn drop(&mut self) { if !self.chunk.is_null() { unsafe { pxl8_vxl_chunk_destroy(self.chunk) }; self.chunk = ptr::null_mut(); } } } impl Clone for VoxelChunk { fn clone(&self) -> Self { let new_chunk = Self::new(self.cx, self.cy, self.cz); for z in 0..CHUNK_SIZE { for y in 0..CHUNK_SIZE { for x in 0..CHUNK_SIZE { let block = self.get(x, y, z); if block != AIR { unsafe { pxl8_vxl_block_set(new_chunk.chunk, x, y, z, block); } } } } } new_chunk } } pub struct VoxelWorld { pub chunks: Vec, pub seed: u64, } impl VoxelWorld { pub fn new(seed: u64) -> Self { Self { chunks: Vec::new(), seed, } } pub fn get_chunk(&self, cx: i32, cy: i32, cz: i32) -> Option<&VoxelChunk> { self.chunks.iter().find(|c| c.cx == cx && c.cy == cy && c.cz == cz) } pub fn get_chunk_mut(&mut self, cx: i32, cy: i32, cz: i32) -> Option<&mut VoxelChunk> { self.chunks.iter_mut().find(|c| c.cx == cx && c.cy == cy && c.cz == cz) } pub fn ensure_chunk(&mut self, cx: i32, cy: i32, cz: i32) -> &mut VoxelChunk { if self.get_chunk(cx, cy, cz).is_none() { let mut chunk = VoxelChunk::new(cx, cy, cz); generate_chunk(&mut chunk, self.seed); self.chunks.push(chunk); } self.get_chunk_mut(cx, cy, cz).unwrap() } pub fn world_to_chunk(x: f32) -> i32 { libm::floorf(x / WORLD_CHUNK_SIZE) as i32 } pub fn chunks_near(&self, pos: Vec3, radius: i32) -> Vec<(i32, i32, i32)> { let cx = Self::world_to_chunk(pos.x); let cy = Self::world_to_chunk(pos.y); let cz = Self::world_to_chunk(pos.z); let mut result = Vec::new(); for dz in -radius..=radius { for dy in -radius..=radius { for dx in -radius..=radius { result.push((cx + dx, cy + dy, cz + dz)); } } } result } pub fn load_chunks_around(&mut self, pos: Vec3, radius: i32) { let coords = self.chunks_near(pos, radius); for (cx, cy, cz) in coords { self.ensure_chunk(cx, cy, cz); } } pub fn find_surface_y(&mut self, world_x: f32, world_z: f32) -> f32 { let scale = PXL8_VXL_SCALE as f32; let block_x = libm::floorf(world_x / scale) as i32; let block_z = libm::floorf(world_z / scale) as i32; let cx = libm::floorf(block_x as f32 / CHUNK_SIZE as f32) as i32; let cz = libm::floorf(block_z as f32 / CHUNK_SIZE as f32) as i32; let lx = ((block_x % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE; let lz = ((block_z % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE; for cy in (-2..=2).rev() { self.ensure_chunk(cx, cy, cz); if let Some(chunk) = self.get_chunk(cx, cy, cz) { for ly in (0..CHUNK_SIZE).rev() { let block = chunk.get(lx, ly, lz); if block == GRASS { let world_y = (cy * CHUNK_SIZE + ly + 1) as f32 * scale; return world_y; } } } } 0.0 } } fn noise3d(x: i32, y: i32, z: i32, seed: u64) -> f32 { let h = hash(seed ^ (x as u64) ^ ((y as u64) << 21) ^ ((z as u64) << 42)); (h & 0xFFFF) as f32 / 65535.0 } fn hash(mut x: u64) -> u64 { x ^= x >> 33; x = x.wrapping_mul(0xff51afd7ed558ccd); x ^= x >> 33; x = x.wrapping_mul(0xc4ceb9fe1a85ec53); x ^= x >> 33; x } fn smoothstep(t: f32) -> f32 { t * t * (3.0 - 2.0 * t) } fn lerp(a: f32, b: f32, t: f32) -> f32 { a + (b - a) * t } fn value_noise_3d(x: f32, y: f32, z: f32, seed: u64) -> f32 { let x0 = libm::floorf(x) as i32; let y0 = libm::floorf(y) as i32; let z0 = libm::floorf(z) as i32; let x1 = x0 + 1; let y1 = y0 + 1; let z1 = z0 + 1; let tx = smoothstep(x - x0 as f32); let ty = smoothstep(y - y0 as f32); let tz = smoothstep(z - z0 as f32); let c000 = noise3d(x0, y0, z0, seed); let c100 = noise3d(x1, y0, z0, seed); let c010 = noise3d(x0, y1, z0, seed); let c110 = noise3d(x1, y1, z0, seed); let c001 = noise3d(x0, y0, z1, seed); let c101 = noise3d(x1, y0, z1, seed); let c011 = noise3d(x0, y1, z1, seed); let c111 = noise3d(x1, y1, z1, seed); let a00 = lerp(c000, c100, tx); let a10 = lerp(c010, c110, tx); let a01 = lerp(c001, c101, tx); let a11 = lerp(c011, c111, tx); let b0 = lerp(a00, a10, ty); let b1 = lerp(a01, a11, ty); lerp(b0, b1, tz) } fn fbm_3d(x: f32, y: f32, z: f32, seed: u64, octaves: u32) -> f32 { let mut value = 0.0; let mut amplitude = 1.0; let mut frequency = 1.0; let mut max_value = 0.0; for i in 0..octaves { value += amplitude * value_noise_3d( x * frequency, y * frequency, z * frequency, seed.wrapping_add(i as u64 * 1000) ); max_value += amplitude; amplitude *= 0.5; frequency *= 2.0; } value / max_value } fn fbm_2d(x: f32, z: f32, seed: u64, octaves: u32) -> f32 { let mut value = 0.0; let mut amplitude = 1.0; let mut frequency = 1.0; let mut max_value = 0.0; for i in 0..octaves { let x0 = libm::floorf(x * frequency) as i32; let z0 = libm::floorf(z * frequency) as i32; let x1 = x0 + 1; let z1 = z0 + 1; let tx = smoothstep(x * frequency - x0 as f32); let tz = smoothstep(z * frequency - z0 as f32); let offset_seed = seed.wrapping_add(i as u64 * 1000); let c00 = (hash(offset_seed ^ (x0 as u64) ^ ((z0 as u64) << 32)) & 0xFFFF) as f32 / 65535.0; let c10 = (hash(offset_seed ^ (x1 as u64) ^ ((z0 as u64) << 32)) & 0xFFFF) as f32 / 65535.0; let c01 = (hash(offset_seed ^ (x0 as u64) ^ ((z1 as u64) << 32)) & 0xFFFF) as f32 / 65535.0; let c11 = (hash(offset_seed ^ (x1 as u64) ^ ((z1 as u64) << 32)) & 0xFFFF) as f32 / 65535.0; let a = lerp(c00, c10, tx); let b = lerp(c01, c11, tx); value += amplitude * lerp(a, b, tz); max_value += amplitude; amplitude *= 0.5; frequency *= 2.0; } value / max_value } fn generate_chunk(chunk: &mut VoxelChunk, seed: u64) { let world_x = chunk.cx * CHUNK_SIZE; let world_y = chunk.cy * CHUNK_SIZE; let world_z = chunk.cz * CHUNK_SIZE; let mut height_cache = [[0i32; 32]; 32]; for lz in 0..32 { for lx in 0..32 { let wx = (world_x + lx as i32) as f32; let wz = (world_z + lz as i32) as f32; height_cache[lz][lx] = (fbm_2d(wx * 0.02, wz * 0.02, seed, 4) * 32.0) as i32; } } let mut density = [[[0.0f32; 33]; 33]; 33]; for lz in 0..33 { for ly in 0..33 { for lx in 0..33 { let wx = (world_x + lx as i32) as f32; let wy = (world_y + ly as i32) as f32; let wz = (world_z + lz as i32) as f32; let hx = lx.min(31); let hz = lz.min(31); let base_height = height_cache[hz][hx] as f32; let height_bias = (base_height - wy) * 0.1; let cave_noise = fbm_3d(wx * 0.05, wy * 0.05, wz * 0.05, seed.wrapping_add(1000), 2); density[lz][ly][lx] = height_bias + (cave_noise - 0.55); } } } for lz in 0..CHUNK_SIZE { for ly in 0..CHUNK_SIZE { for lx in 0..CHUNK_SIZE { let d = density[lz as usize][ly as usize][lx as usize]; if d <= 0.0 { continue; } let d_above = density[lz as usize][(ly + 1) as usize][lx as usize]; let is_surface = d_above <= 0.0; let block = if is_surface { GRASS } else { let wy = world_y + ly; let base_height = height_cache[lz as usize][lx as usize]; let depth = base_height - wy; if depth < 4 { DIRT } else { STONE } }; chunk.set(lx, ly, lz, block); } } } } impl Default for VoxelWorld { fn default() -> Self { Self::new(12345) } }