use godot::prelude::*; pub const CHUNK_SIZE: i32 = 32; pub const ARR_SIZE: usize = CHUNK_SIZE.pow(3) as usize; // 32x32x32 = 32768 bits, stored in u32s (32 bits each) = 1024 u32s pub const BITPACKED_SIZE: usize = ARR_SIZE / 32; #[derive(Debug)] pub struct Chunk { // Bitpacked voxel data: 0 = air, 1 = solid pub voxels: Vec, // WORLD COORDINATES pub world_position: (f64, f64, f64), modified: bool, } impl Chunk { /// pos_z and pos_x are `world` coordinates of the chunk, inside the world pub fn new(world_pos_x: f64, world_pos_y: f64, world_pos_z: f64, size: Vector3i) -> Self { Chunk { voxels: vec![0u32; BITPACKED_SIZE], world_position: (world_pos_x, world_pos_y, world_pos_z), modified: false, } } #[inline] pub fn get_voxel_index(&self, local_pos: Vector3i, chunk_size: Vector3i) -> usize { ((local_pos.x * chunk_size.y * chunk_size.z) + (local_pos.y * chunk_size.x) + local_pos.z) as usize } #[inline] pub fn is_voxel_within_bounds(&self, local_pos: Vector3i, chunk_size: Vector3i) -> bool { local_pos.x >= 0 && local_pos.x < chunk_size.x && local_pos.y >= 0 && local_pos.y < chunk_size.y && local_pos.z >= 0 && local_pos.z < chunk_size.z } #[inline] pub fn get_voxel(&self, local_pos: Vector3i, chunk_size: Vector3i) -> bool { debug_assert!( self.is_voxel_within_bounds(local_pos, chunk_size), "Voxel position {:?} out of chunk bounds", local_pos ); let index = self.get_voxel_index(local_pos, chunk_size); debug_assert!( index < ARR_SIZE, "Voxel index {:?} out of chunk bounds", index ); // Get the u32 containing this bit and the bit position within it let word_index = index / chunk_size.x as usize; let bit_index = index % chunk_size.x as usize; (self.voxels[word_index] & (1 << bit_index)) != 0 } pub fn set_voxel(&mut self, voxel_pos: Vector3i, is_solid: bool, chunk_size: Vector3i) { debug_assert!( self.is_voxel_within_bounds(voxel_pos, chunk_size), "Voxel position {:?} out of chunk bounds", voxel_pos ); let index = self.get_voxel_index(voxel_pos, chunk_size); let word_index = index / chunk_size.x as usize; let bit_index = index % chunk_size.x as usize; if is_solid { self.voxels[word_index] |= 1 << bit_index; } else { self.voxels[word_index] &= !(1 << bit_index); } self.modified = true; } pub fn fill(&mut self, is_solid: bool) { let fill_value = if is_solid { u32::MAX } else { 0 }; self.voxels.fill(fill_value); self.modified = true; } pub fn get_position(&self) -> (f64, f64, f64) { self.world_position } pub fn get_world_bounds(&self, chunk_size: Vector3i) -> (Vector3, Vector3) { let (x, y, z) = self.world_position; let min = Vector3::new( (x * chunk_size.x as f64) as f32, (y * chunk_size.y as f64) as f32, (z * chunk_size.z as f64) as f32, ); let max = Vector3::new( ((x + 1.0) * chunk_size.x as f64) as f32, ((y + 1.0) * chunk_size.y as f64) as f32, ((z + 1.0) * chunk_size.z as f64) as f32, ); (min, max) } }