318 lines
10 KiB
Rust
318 lines
10 KiB
Rust
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<GreedyQuad> {
|
|
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"
|
|
}
|
|
}
|