use crate::ao::{face_ao, sample_block_opaque};
use crate::chunk::{Chunk, ChunkNeighbors, Face};
use crate::error::MeshError;
use crate::mesh_output::MeshOutput;
use crate::mesher::Mesher;
pub struct GreedyMesher {
mask: Vec<u16>,
ao_mask: Vec<[u8; 4]>,
chunk_size: usize,
}
impl GreedyMesher {
pub fn new() -> Self {
Self::with_chunk_size(32)
}
pub fn with_chunk_size(size: usize) -> Self {
let area = size * size;
Self {
mask: vec![0u16; area],
ao_mask: vec![[0u8; 4]; area],
chunk_size: size,
}
}
}
impl Default for GreedyMesher {
fn default() -> Self {
Self::new()
}
}
impl Mesher for GreedyMesher {
fn mesh(
&mut self,
chunk: &Chunk,
neighbors: &ChunkNeighbors,
output: &mut MeshOutput,
) -> Result<(), MeshError> {
let size = chunk.size();
debug_assert_eq!(size, self.chunk_size, "chunk size mismatch with mesher");
for &face in &Face::ALL {
let normal = face.normal();
for d in 0..size {
self.clear_mask();
for v in 0..size {
for u in 0..size {
let pos = compose_coords(u, v, d, face, size);
let block = chunk.get(pos[0], pos[1], pos[2]);
if block == 0 {
continue;
}
let nx = pos[0] as i32 + normal[0];
let ny = pos[1] as i32 + normal[1];
let nz = pos[2] as i32 + normal[2];
let neighbor_opaque = sample_block_opaque(chunk, neighbors, nx, ny, nz);
if !neighbor_opaque {
let idx = u + v * size;
self.mask[idx] = block;
let ao = face_ao(
chunk,
neighbors,
pos[0] as i32,
pos[1] as i32,
pos[2] as i32,
face,
);
self.ao_mask[idx] = [
(ao[0] * 3.0).round() as u8,
(ao[1] * 3.0).round() as u8,
(ao[2] * 3.0).round() as u8,
(ao[3] * 3.0).round() as u8,
];
}
}
}
for v in 0..size {
let mut u = 0;
while u < size {
let idx = u + v * size;
let block_id = self.mask[idx];
if block_id == 0 {
u += 1;
continue;
}
let ao_val = self.ao_mask[idx];
let mut w = 1;
while u + w < size {
let next_idx = (u + w) + v * size;
if self.mask[next_idx] != block_id || self.ao_mask[next_idx] != ao_val {
break;
}
w += 1;
}
let mut h = 1;
'expand_h: while v + h < size {
for du in 0..w {
let next_idx = (u + du) + (v + h) * size;
if self.mask[next_idx] != block_id
|| self.ao_mask[next_idx] != ao_val
{
break 'expand_h;
}
}
h += 1;
}
let positions = quad_positions(u, v, d, w, h, face);
let ao_f32 = [
ao_val[0] as f32 / 3.0,
ao_val[1] as f32 / 3.0,
ao_val[2] as f32 / 3.0,
ao_val[3] as f32 / 3.0,
];
output.push_quad(&positions, face.normal_f32(), ao_f32, block_id);
for dv in 0..h {
for du in 0..w {
let clear_idx = (u + du) + (v + dv) * size;
self.mask[clear_idx] = 0;
}
}
u += w;
}
}
}
}
Ok(())
}
}
impl GreedyMesher {
fn clear_mask(&mut self) {
for v in self.mask.iter_mut() {
*v = 0;
}
}
}
#[inline]
fn compose_coords(u: usize, v: usize, d: usize, face: Face, _size: usize) -> [usize; 3] {
let (u_axis, v_axis) = face.tangent_axes();
let n_axis = face.normal_axis();
let mut pos = [0usize; 3];
pos[u_axis] = u;
pos[v_axis] = v;
pos[n_axis] = d;
pos
}
#[inline]
fn quad_positions(u: usize, v: usize, d: usize, w: usize, h: usize, face: Face) -> [[f32; 3]; 4] {
let (u_axis, v_axis) = face.tangent_axes();
let n_axis = face.normal_axis();
let depth = if face.is_positive() {
(d + 1) as f32
} else {
d as f32
};
let u0 = u as f32;
let v0 = v as f32;
let u1 = (u + w) as f32;
let v1 = (v + h) as f32;
let mut positions = [[0.0f32; 3]; 4];
positions[0][u_axis] = u0;
positions[0][v_axis] = v0;
positions[0][n_axis] = depth;
positions[1][u_axis] = u1;
positions[1][v_axis] = v0;
positions[1][n_axis] = depth;
positions[2][u_axis] = u1;
positions[2][v_axis] = v1;
positions[2][n_axis] = depth;
positions[3][u_axis] = u0;
positions[3][v_axis] = v1;
positions[3][n_axis] = depth;
positions
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunk::CHUNK_SIZE;
fn mesh_chunk(chunk: &Chunk, neighbors: &ChunkNeighbors) -> MeshOutput {
let mut mesher = GreedyMesher::with_chunk_size(chunk.size());
let mut output = MeshOutput::new();
mesher.mesh(chunk, neighbors, &mut output).unwrap();
output
}
#[test]
fn empty_chunk() {
let chunk = Chunk::new_default();
let neighbors = ChunkNeighbors::empty(CHUNK_SIZE);
let output = mesh_chunk(&chunk, &neighbors);
assert!(output.is_empty());
}
#[test]
fn solid_chunk() {
let mut chunk = Chunk::new_default();
for z in 0..CHUNK_SIZE {
for y in 0..CHUNK_SIZE {
for x in 0..CHUNK_SIZE {
chunk.set(x, y, z, 1);
}
}
}
let neighbors = ChunkNeighbors::empty(CHUNK_SIZE);
let output = mesh_chunk(&chunk, &neighbors);
assert_eq!(output.vertex_count(), 24); assert_eq!(output.index_count(), 36); }
#[test]
fn single_block() {
let mut chunk = Chunk::new_default();
chunk.set(16, 16, 16, 1);
let neighbors = ChunkNeighbors::empty(CHUNK_SIZE);
let output = mesh_chunk(&chunk, &neighbors);
assert_eq!(output.vertex_count(), 24); assert_eq!(output.index_count(), 36); }
#[test]
fn two_adjacent_blocks_cull_shared_face() {
let mut chunk = Chunk::new_default();
chunk.set(0, 0, 0, 1);
chunk.set(1, 0, 0, 1);
let neighbors = ChunkNeighbors::empty(CHUNK_SIZE);
let output = mesh_chunk(&chunk, &neighbors);
assert_eq!(output.vertex_count(), 24);
assert_eq!(output.index_count(), 36);
}
#[test]
fn different_block_ids_dont_merge() {
let mut chunk = Chunk::new_default();
chunk.set(0, 0, 0, 1);
chunk.set(1, 0, 0, 2); let neighbors = ChunkNeighbors::empty(CHUNK_SIZE);
let output = mesh_chunk(&chunk, &neighbors);
assert_eq!(output.vertex_count(), 40); assert_eq!(output.index_count(), 60); }
#[test]
fn cross_chunk_boundary_culling() {
let mut chunk = Chunk::new_default();
chunk.set(31, 0, 0, 1);
let mut neighbors = ChunkNeighbors::empty(CHUNK_SIZE);
let mut pos_x_border = vec![0u16; CHUNK_SIZE * CHUNK_SIZE];
pos_x_border[0 + 0 * CHUNK_SIZE] = 1; neighbors.set_face(Face::PosX, pos_x_border);
let output = mesh_chunk(&chunk, &neighbors);
assert_eq!(output.vertex_count(), 20); assert_eq!(output.index_count(), 30); }
#[test]
fn checkerboard_no_merge() {
let mut chunk = Chunk::new(4).unwrap();
let mut expected_faces = 0u32;
for z in 0..4 {
for y in 0..4 {
for x in 0..4 {
if (x + y + z) % 2 == 0 {
chunk.set(x, y, z, 1);
for face in &Face::ALL {
let n = face.normal();
let nx = x as i32 + n[0];
let ny = y as i32 + n[1];
let nz = z as i32 + n[2];
if nx < 0 || nx >= 4 || ny < 0 || ny >= 4 || nz < 0 || nz >= 4 {
expected_faces += 1;
} else if (nx + ny + nz) % 2 != 0 {
expected_faces += 1;
}
}
}
}
}
}
let neighbors = ChunkNeighbors::empty(4);
let mut mesher = GreedyMesher::with_chunk_size(4);
let mut output = MeshOutput::new();
mesher.mesh(&chunk, &neighbors, &mut output).unwrap();
assert_eq!(output.vertex_count(), expected_faces * 4);
}
#[test]
fn mesh_output_reuse() {
let mut chunk = Chunk::new_default();
chunk.set(10, 10, 10, 1);
let neighbors = ChunkNeighbors::empty(CHUNK_SIZE);
let mut mesher = GreedyMesher::new();
let mut output = MeshOutput::with_capacity(100);
mesher.mesh(&chunk, &neighbors, &mut output).unwrap();
let first_vertex_count = output.vertex_count();
let first_index_count = output.index_count();
assert!(!output.is_empty());
output.clear();
mesher.mesh(&chunk, &neighbors, &mut output).unwrap();
assert_eq!(output.vertex_count(), first_vertex_count);
assert_eq!(output.index_count(), first_index_count);
}
#[test]
fn small_chunk_surface() {
let mut chunk = Chunk::new(4).unwrap();
for z in 0..4 {
for y in 0..2 {
for x in 0..4 {
chunk.set(x, y, z, 1);
}
}
}
let neighbors = ChunkNeighbors::empty(4);
let mut mesher = GreedyMesher::with_chunk_size(4);
let mut output = MeshOutput::new();
mesher.mesh(&chunk, &neighbors, &mut output).unwrap();
assert!(!output.is_empty());
assert_eq!(output.vertex_count(), 24);
assert_eq!(output.index_count(), 36);
}
#[test]
fn compose_coords_roundtrip() {
let size = 8;
for face in &Face::ALL {
for d in 0..size {
for v in 0..size {
for u in 0..size {
let pos = compose_coords(u, v, d, *face, size);
assert!(pos[0] < size);
assert!(pos[1] < size);
assert!(pos[2] < size);
}
}
}
}
}
#[test]
fn quad_positions_positive_face() {
let positions = quad_positions(2, 3, 5, 4, 2, Face::PosX);
assert_eq!(positions[0][0], 6.0); assert_eq!(positions[0][1], 2.0); assert_eq!(positions[0][2], 3.0);
assert_eq!(positions[2][1], 6.0); assert_eq!(positions[2][2], 5.0); }
#[test]
fn quad_positions_negative_face() {
let positions = quad_positions(0, 0, 5, 1, 1, Face::NegX);
assert_eq!(positions[0][0], 5.0);
}
}