extern crate alloc; use alloc::vec::Vec; use libm::floorf; use crate::math::Vec3; pub const CHUNK_SIZE: usize = 32; pub const CHUNK_VOLUME: usize = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; pub const AIR: u8 = 0; pub const STONE: u8 = 1; pub const DIRT: u8 = 2; pub const GRASS: u8 = 3; #[derive(Clone)] pub struct VoxelChunk { pub blocks: [u8; CHUNK_VOLUME], pub cx: i32, pub cy: i32, pub cz: i32, } impl VoxelChunk { pub fn new(cx: i32, cy: i32, cz: i32) -> Self { Self { blocks: [AIR; CHUNK_VOLUME], cx, cy, cz, } } pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { if !self.is_solid_radius(to.x, to.y, to.z, radius) { return to; } let x_ok = !self.is_solid_radius(to.x, from.y, from.z, radius); let y_ok = !self.is_solid_radius(from.x, to.y, from.z, radius); let z_ok = !self.is_solid_radius(from.x, from.y, to.z, radius); let mut result = from; if x_ok { result.x = to.x; } if y_ok { result.y = to.y; } if z_ok { result.z = to.z; } result } fn world_to_local(x: f32, chunk_coord: i32) -> usize { let chunk_base = chunk_coord as f32 * CHUNK_SIZE as f32; let local = (x - chunk_base) as usize; local.min(CHUNK_SIZE - 1) } fn is_solid_at(&self, x: f32, y: f32, z: f32) -> bool { let lx = Self::world_to_local(x, self.cx); let ly = Self::world_to_local(y, self.cy); let lz = Self::world_to_local(z, self.cz); self.get(lx, ly, lz) != AIR } fn is_solid_radius(&self, x: f32, y: f32, z: f32, radius: f32) -> bool { self.is_solid_at(x - radius, y, z) || self.is_solid_at(x + radius, y, z) || self.is_solid_at(x, y - radius, z) || self.is_solid_at(x, y + radius, z) || self.is_solid_at(x, y, z - radius) || self.is_solid_at(x, y, z + radius) } pub fn index(x: usize, y: usize, z: usize) -> usize { x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE } pub fn get(&self, x: usize, y: usize, z: usize) -> u8 { if x >= CHUNK_SIZE || y >= CHUNK_SIZE || z >= CHUNK_SIZE { return AIR; } self.blocks[Self::index(x, y, z)] } pub fn set(&mut self, x: usize, y: usize, z: usize, block: u8) { if x < CHUNK_SIZE && y < CHUNK_SIZE && z < CHUNK_SIZE { self.blocks[Self::index(x, y, z)] = block; } } pub fn rle_encode(&self) -> Vec { let mut result = Vec::new(); let mut i = 0; while i < CHUNK_VOLUME { let block = self.blocks[i]; let mut run_len = 1usize; while i + run_len < CHUNK_VOLUME && self.blocks[i + run_len] == block && run_len < 256 { run_len += 1; } result.push(block); result.push((run_len - 1) as u8); i += run_len; } result } } 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 { floorf(x / CHUNK_SIZE as f32) as i32 } pub fn world_to_local(x: f32) -> usize { let chunk = floorf(x / CHUNK_SIZE as f32); let local = (x - chunk * CHUNK_SIZE as f32) as usize; local.min(CHUNK_SIZE - 1) } pub fn is_solid(&self, x: f32, y: f32, z: f32) -> bool { let cx = Self::world_to_chunk(x); let cy = Self::world_to_chunk(y); let cz = Self::world_to_chunk(z); let lx = Self::world_to_local(x); let ly = Self::world_to_local(y); let lz = Self::world_to_local(z); match self.get_chunk(cx, cy, cz) { Some(chunk) => chunk.get(lx, ly, lz) != AIR, None => false, } } pub fn is_solid_radius(&self, x: f32, y: f32, z: f32, radius: f32) -> bool { self.is_solid(x - radius, y, z) || self.is_solid(x + radius, y, z) || self.is_solid(x, y - radius, z) || self.is_solid(x, y + radius, z) || self.is_solid(x, y, z - radius) || self.is_solid(x, y, z + radius) } pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { if !self.is_solid_radius(to.x, to.y, to.z, radius) { return to; } let x_ok = !self.is_solid_radius(to.x, from.y, from.z, radius); let y_ok = !self.is_solid_radius(from.x, to.y, from.z, radius); let z_ok = !self.is_solid_radius(from.x, from.y, to.z, radius); let mut result = from; if x_ok { result.x = to.x; } if y_ok { result.y = to.y; } if z_ok { result.z = to.z; } result } 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); } } } 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 noise2d(x: i32, z: i32, seed: u64) -> f32 { let h = hash(seed ^ (x as u64) ^ ((z as u64) << 32)); (h & 0xFFFF) as f32 / 65535.0 } 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(x: f32, z: f32, seed: u64) -> f32 { let x0 = floorf(x) as i32; let z0 = floorf(z) as i32; let x1 = x0 + 1; let z1 = z0 + 1; let tx = smoothstep(x - x0 as f32); let tz = smoothstep(z - z0 as f32); let c00 = noise2d(x0, z0, seed); let c10 = noise2d(x1, z0, seed); let c01 = noise2d(x0, z1, seed); let c11 = noise2d(x1, z1, seed); let a = lerp(c00, c10, tx); let b = lerp(c01, c11, tx); lerp(a, b, tz) } fn fbm(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 { value += amplitude * value_noise(x * frequency, z * frequency, seed.wrapping_add(i as u64 * 1000)); 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 as i32; let world_y = chunk.cy * CHUNK_SIZE as i32; let world_z = chunk.cz * CHUNK_SIZE as i32; for lz in 0..CHUNK_SIZE { for lx in 0..CHUNK_SIZE { let wx = (world_x + lx as i32) as f32; let wz = (world_z + lz as i32) as f32; let height = fbm(wx * 0.02, wz * 0.02, seed, 4) * 32.0 + 16.0; let height = height as i32; for ly in 0..CHUNK_SIZE { let wy = world_y + ly as i32; let block = if wy > height { AIR } else if wy == height { GRASS } else if wy > height - 4 { DIRT } else { STONE }; chunk.set(lx, ly, lz, block); } } } } impl Default for VoxelWorld { fn default() -> Self { Self::new(12345) } }