use crate::chunk::chunk::CHUNK_SIZE; use crate::chunk::{SubChunk, ChunkMesh}; use crate::editor::voxel_registry::VoxelRegistry; use crate::meshing::Mesher; use godot::prelude::*; #[derive(Debug, Clone, Copy)] pub struct BinaryGreedyMesher; #[derive(Debug, Clone, Copy)] struct GreedyQuad { x: u32, y: u32, w: u32, h: u32, } impl BinaryGreedyMesher { pub fn new() -> Self { Self } pub fn generate_mesh(&self, chunk: &SubChunk, mesh: &mut ChunkMesh) { mesh.vertices.clear(); mesh.normals.clear(); mesh.indices.clear(); // Generate mesh for each of the 6 faces self.mesh_face(chunk, mesh, 0); // -X (left) self.mesh_face(chunk, mesh, 1); // +X (right) self.mesh_face(chunk, mesh, 2); // -Y (down) self.mesh_face(chunk, mesh, 3); // +Y (up) self.mesh_face(chunk, mesh, 4); // -Z (back) self.mesh_face(chunk, mesh, 5); // +Z (forward) } fn mesh_face(&self, chunk: &SubChunk, mesh: &mut ChunkMesh, face_dir: usize) { let size = CHUNK_SIZE as usize; // For each slice perpendicular to the face direction for axis in 0..size { // Generate binary mask for this slice let mut face_mask = [0u32; 32]; for row in 0..size { for col in 0..size { let (x, y, z) = self.get_coords_for_face(face_dir, axis, row, col); if x >= size || y >= size || z >= size { continue; } let pos = Vector3i::new(x as i32, y as i32, z as i32); let is_solid = chunk.get_voxel( pos, Vector3i { x: 32, y: 32, z: 32, }, ); // Check if neighbor in face direction is air let (nx, ny, nz) = self.get_neighbor_coords(face_dir, x, y, z); let neighbor_is_air = if nx < 0 || nx >= size as i32 || ny < 0 || ny >= size as i32 || nz < 0 || nz >= size as i32 { false // Outside chunk = air } else { !chunk.get_voxel( Vector3i::new(nx, ny, nz), Vector3i { x: 32, y: 32, z: 32, }, ) }; // Face is visible if this voxel is solid and neighbor is air if is_solid && neighbor_is_air { face_mask[row] |= 1 << col; } } } // Greedy mesh the binary mask let quads = self.greedy_mesh_binary_plane(face_mask); // Add quads to mesh for quad in quads { self.add_quad_to_mesh(mesh, quad, face_dir, axis, chunk); } } } #[inline] fn get_coords_for_face( &self, face_dir: usize, axis: usize, row: usize, col: usize, ) -> (usize, usize, usize) { match face_dir { 0 => (axis, col, row), // -X: axis=x, row=z, col=y 1 => (axis, col, row), // +X: axis=x, row=z, col=y 2 => (row, axis, col), // -Y: axis=y, row=x, col=z 3 => (row, axis, col), // +Y: axis=y, row=x, col=z 4 => (row, col, axis), // -Z: axis=z, row=x, col=y 5 => (row, col, axis), // +Z: axis=z, row=x, col=y _ => unreachable!(), } } #[inline] fn get_neighbor_coords( &self, face_dir: usize, x: usize, y: usize, z: usize, ) -> (i32, i32, i32) { let (x, y, z) = (x as i32, y as i32, z as i32); match face_dir { 0 => (x - 1, y, z), // -X 1 => (x + 1, y, z), // +X 2 => (x, y - 1, z), // -Y 3 => (x, y + 1, z), // +Y 4 => (x, y, z - 1), // -Z 5 => (x, y, z + 1), // +Z _ => unreachable!(), } } fn greedy_mesh_binary_plane(&self, mut data: [u32; 32]) -> Vec { let mut quads = Vec::new(); let size = CHUNK_SIZE as u32; for row in 0..data.len() { let mut y = 0; while y < size { // Find first solid bit (skip zeros) y += (data[row] >> y).trailing_zeros(); if y >= size { break; // Reached end of row } // Count consecutive solid bits (height) let h = (data[row] >> y).trailing_ones(); // Create a mask for these bits let h_as_mask = if h >= 32 { u32::MAX } else { (1u32 << h) - 1 }; let mask = h_as_mask << y; // Try to expand horizontally let mut w = 1; while row + w < size as usize { // Check if the next row has the same pattern at this position let next_row_bits = (data[row + w] >> y) & h_as_mask; if next_row_bits != h_as_mask { break; // Can't expand further } // Clear the bits we're consuming data[row + w] &= !mask; w += 1; } quads.push(GreedyQuad { x: row as u32, y, w: w as u32, h, }); y += h; } } quads } fn add_quad_to_mesh( &self, mesh: &mut ChunkMesh, quad: GreedyQuad, face_dir: usize, axis: usize, chunk: &SubChunk, ) { let base_idx = mesh.vertices.len() as i32; // Convert quad coordinates back to 3D positions let vertices = self.get_quad_vertices(quad, face_dir, axis); let normal = self.get_face_normal(face_dir); // Base color based on face direction with simple directional shading (no per-vertex AO) let (r, g, b) = match face_dir { 0 => (0.55, 0.55, 0.55), // -X (left) - darker gray 1 => (0.65, 0.65, 0.65), // +X (right) - lighter gray 2 => (0.35, 0.35, 0.35), // -Y (down) - dark gray 3 => (0.5, 0.8, 0.3), // +Y (up) - grass green (brightest) 4 => (0.50, 0.50, 0.50), // -Z (back) - medium gray 5 => (0.60, 0.60, 0.60), // +Z (forward) - lighter gray _ => (1.0, 1.0, 1.0), }; let color = Color::from_rgb(r, g, b); // Add vertices with consistent color (no per-vertex variation) for vertex in &vertices { mesh.vertices.push(*vertex); mesh.normals.push(normal); mesh.colors.push(color); } // Add indices for two triangles mesh.indices.push(base_idx); mesh.indices.push(base_idx + 1); mesh.indices.push(base_idx + 2); mesh.indices.push(base_idx); mesh.indices.push(base_idx + 2); mesh.indices.push(base_idx + 3); } fn get_quad_vertices(&self, quad: GreedyQuad, face_dir: usize, axis: usize) -> [Vector3; 4] { let x = quad.x as f32; let y = quad.y as f32; let w = quad.w as f32; let h = quad.h as f32; let a = axis as f32; match face_dir { 0 => [ // -X (left) Vector3::new(a, y, x), Vector3::new(a, y + h, x), Vector3::new(a, y + h, x + w), Vector3::new(a, y, x + w), ], 1 => [ // +X (right) Vector3::new(a + 1.0, y, x), Vector3::new(a + 1.0, y, x + w), Vector3::new(a + 1.0, y + h, x + w), Vector3::new(a + 1.0, y + h, x), ], 2 => [ // -Y (down) Vector3::new(x, a, y), Vector3::new(x, a, y + h), Vector3::new(x + w, a, y + h), Vector3::new(x + w, a, y), ], 3 => [ // +Y (up) Vector3::new(x, a + 1.0, y), Vector3::new(x + w, a + 1.0, y), Vector3::new(x + w, a + 1.0, y + h), Vector3::new(x, a + 1.0, y + h), ], 4 => [ // -Z (back) Vector3::new(x, y, a), Vector3::new(x + w, y, a), Vector3::new(x + w, y + h, a), Vector3::new(x, y + h, a), ], 5 => [ // +Z (forward) Vector3::new(x, y, a + 1.0), Vector3::new(x, y + h, a + 1.0), Vector3::new(x + w, y + h, a + 1.0), Vector3::new(x + w, y, a + 1.0), ], _ => unreachable!(), } } fn get_face_normal(&self, face_dir: usize) -> Vector3 { match face_dir { 0 => Vector3::new(-1.0, 0.0, 0.0), // -X 1 => Vector3::new(1.0, 0.0, 0.0), // +X 2 => Vector3::new(0.0, -1.0, 0.0), // -Y 3 => Vector3::new(0.0, 1.0, 0.0), // +Y 4 => Vector3::new(0.0, 0.0, -1.0), // -Z 5 => Vector3::new(0.0, 0.0, 1.0), // +Z _ => unreachable!(), } } } impl Mesher for BinaryGreedyMesher { fn generate_mesh(&self, chunk: &SubChunk, mesh: &mut ChunkMesh) { self.generate_mesh(chunk, mesh); } fn generate_mesh_with_registry( &self, chunk: &SubChunk, _registry: &VoxelRegistry, mesh: &mut ChunkMesh, ) { // Binary greedy mesher doesn't use registry (only solid/air) self.generate_mesh(chunk, mesh); } fn get_name(&self) -> &str { "BinaryGreedyMesher" } }