block_mesh/
greedy.rs

1mod merge_strategy;
2
3pub use merge_strategy::*;
4
5use crate::{bounds::assert_in_bounds, OrientedBlockFace, QuadBuffer, UnorientedQuad, Voxel, VoxelVisibility};
6
7use ilattice::glam::UVec3;
8use ilattice::prelude::Extent;
9use ndcopy::fill3;
10use ndshape::Shape;
11
12pub trait MergeVoxel: Voxel {
13    type MergeValue: Eq;
14
15    /// The value used to determine if this voxel can join a given quad in the mesh. This value will be constant for all voxels
16    /// in the same quad. Often this is some material identifier so that the same texture can be used for a full quad.
17    fn merge_value(&self) -> Self::MergeValue;
18}
19
20/// Contains the output from the [`greedy_quads`] algorithm. The quads can be used to generate a mesh. See the methods on
21/// [`OrientedBlockFace`] and [`UnorientedQuad`] for details.
22///
23/// This buffer can be reused between multiple calls of [`greedy_quads`] in order to avoid reallocations.
24pub struct GreedyQuadsBuffer {
25    pub quads: QuadBuffer,
26
27    // A single array is used for the visited mask because it allows us to index by the same strides as the voxels array. It
28    // also only requires a single allocation.
29    visited: Vec<bool>,
30}
31
32impl GreedyQuadsBuffer {
33    pub fn new(size: usize) -> Self {
34        Self {
35            quads: QuadBuffer::new(),
36            visited: vec![false; size],
37        }
38    }
39
40    pub fn reset(&mut self, size: usize) {
41        self.quads.reset();
42
43        if size != self.visited.len() {
44            self.visited = vec![false; size];
45        }
46    }
47}
48
49/// The "Greedy Meshing" algorithm described by Mikola Lysenko in the [0fps
50/// article](https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/).
51///
52/// All visible faces of voxels on the interior of `[min, max]` will be part of some [`UnorientedQuad`] returned via the
53/// `output` buffer. A 3x3x3 kernel will be applied to each point on the interior, hence the extra padding required on the
54/// boundary. `voxels` only needs to contain the set of points in `[min, max]`.
55///
56/// All quads created will have the same "merge value" as defined by the [`MergeVoxel`] trait. The quads can be post-processed
57/// into meshes as the user sees fit.
58pub fn greedy_quads<T, S>(
59    voxels: &[T],
60    voxels_shape: &S,
61    min: [u32; 3],
62    max: [u32; 3],
63    faces: &[OrientedBlockFace; 6],
64    output: &mut GreedyQuadsBuffer,
65) where
66    T: MergeVoxel,
67    S: Shape<3, Coord = u32>,
68{
69    greedy_quads_with_merge_strategy::<_, _, VoxelMerger<T>>(
70        voxels,
71        voxels_shape,
72        min,
73        max,
74        faces,
75        output,
76    )
77}
78
79/// Run the greedy meshing algorithm with a custom quad merging strategy using the [`MergeStrategy`] trait.
80pub fn greedy_quads_with_merge_strategy<T, S, Merger>(
81    voxels: &[T],
82    voxels_shape: &S,
83    min: [u32; 3],
84    max: [u32; 3],
85    faces: &[OrientedBlockFace; 6],
86    output: &mut GreedyQuadsBuffer,
87) where
88    T: Voxel,
89    S: Shape<3, Coord = u32>,
90    Merger: MergeStrategy<Voxel = T>,
91{
92    assert_in_bounds(voxels, voxels_shape, min, max);
93
94    let min = UVec3::from(min).as_ivec3();
95    let max = UVec3::from(max).as_ivec3();
96    let extent = Extent::from_min_and_max(min, max);
97
98    output.reset(voxels.len());
99    let GreedyQuadsBuffer {
100        visited,
101        quads: QuadBuffer { groups },
102    } = output;
103
104    let interior = extent.padded(-1); // Avoid accessing out of bounds with a 3x3x3 kernel.
105    let interior =
106        Extent::from_min_and_shape(interior.minimum.as_uvec3(), interior.shape.as_uvec3());
107
108    for (group, face) in groups.iter_mut().zip(faces.iter()) {
109        greedy_quads_for_face::<_, _, Merger>(voxels, voxels_shape, interior, face, visited, group);
110    }
111}
112
113fn greedy_quads_for_face<T, S, Merger>(
114    voxels: &[T],
115    voxels_shape: &S,
116    interior: Extent<UVec3>,
117    face: &OrientedBlockFace,
118    visited: &mut [bool],
119    quads: &mut Vec<UnorientedQuad>,
120) where
121    T: Voxel,
122    S: Shape<3, Coord = u32>,
123    Merger: MergeStrategy<Voxel = T>,
124{
125    visited.fill(false);
126
127    let OrientedBlockFace {
128        n_sign,
129        permutation,
130        n,
131        u,
132        v,
133        ..
134    } = face;
135
136    let [n_axis, u_axis, v_axis] = permutation.axes();
137    let i_n = n_axis.index();
138    let i_u = u_axis.index();
139    let i_v = v_axis.index();
140
141    let interior_shape = interior.shape.to_array();
142    let num_slices = interior_shape[i_n];
143    let mut slice_shape = [0; 3];
144    slice_shape[i_n] = 1;
145    slice_shape[i_u] = interior_shape[i_u];
146    slice_shape[i_v] = interior_shape[i_v];
147    let mut slice_extent = Extent::from_min_and_shape(interior.minimum, UVec3::from(slice_shape));
148
149    let n_stride = voxels_shape.linearize(n.to_array());
150    let u_stride = voxels_shape.linearize(u.to_array());
151    let v_stride = voxels_shape.linearize(v.to_array());
152    let face_strides = FaceStrides {
153        n_stride,
154        u_stride,
155        v_stride,
156        // The offset to the voxel sharing this cube face.
157        visibility_offset: if *n_sign > 0 {
158            n_stride
159        } else {
160            0u32.wrapping_sub(n_stride)
161        },
162    };
163
164    for _ in 0..num_slices {
165        let slice_ub = slice_extent.least_upper_bound().to_array();
166        let u_ub = slice_ub[i_u];
167        let v_ub = slice_ub[i_v];
168
169        for quad_min in slice_extent.iter3() {
170            let quad_min_array = quad_min.to_array();
171            let quad_min_index = voxels_shape.linearize(quad_min_array);
172            let quad_min_voxel = unsafe { voxels.get_unchecked(quad_min_index as usize) };
173            if unsafe {
174                !face_needs_mesh(
175                    quad_min_voxel,
176                    quad_min_index,
177                    face_strides.visibility_offset,
178                    voxels,
179                    visited,
180                )
181            } {
182                continue;
183            }
184            // We have at least one face that needs a mesh. We'll try to expand that face into the biggest quad we can find.
185
186            // These are the boundaries on quad width and height so it is contained in the slice.
187            let max_width = u_ub - quad_min_array[i_u];
188            let max_height = v_ub - quad_min_array[i_v];
189
190            let (quad_width, quad_height) = unsafe {
191                Merger::find_quad(
192                    quad_min_index,
193                    max_width,
194                    max_height,
195                    &face_strides,
196                    voxels,
197                    visited,
198                )
199            };
200            debug_assert!(quad_width >= 1);
201            debug_assert!(quad_width <= max_width);
202            debug_assert!(quad_height >= 1);
203            debug_assert!(quad_height <= max_height);
204
205            // Mark the quad as visited.
206            let mut quad_shape = [0; 3];
207            quad_shape[i_n] = 1;
208            quad_shape[i_u] = quad_width;
209            quad_shape[i_v] = quad_height;
210            fill3(quad_shape, true, visited, voxels_shape, quad_min_array);
211
212            quads.push(UnorientedQuad {
213                minimum: quad_min.to_array(),
214                width: quad_width,
215                height: quad_height,
216            });
217        }
218
219        // Move to the next slice.
220        slice_extent = slice_extent + *n;
221    }
222}
223
224/// Returns true iff the given `voxel` face needs to be meshed. This means that we haven't already meshed it, it is non-empty,
225/// and it's visible (not completely occluded by an adjacent voxel).
226pub(crate) unsafe fn face_needs_mesh<T>(
227    voxel: &T,
228    voxel_stride: u32,
229    visibility_offset: u32,
230    voxels: &[T],
231    visited: &[bool],
232) -> bool
233where
234    T: Voxel,
235{
236    if voxel.get_visibility() == VoxelVisibility::Empty || visited[voxel_stride as usize] {
237        return false;
238    }
239
240    let adjacent_voxel =
241        voxels.get_unchecked(voxel_stride.wrapping_add(visibility_offset) as usize);
242
243    // TODO: If the face lies between two transparent voxels, we choose not to mesh it. We might need to extend the IsOpaque
244    // trait with different levels of transparency to support this.
245    match adjacent_voxel.get_visibility() {
246        VoxelVisibility::Empty => true,
247        VoxelVisibility::Translucent => voxel.get_visibility() == VoxelVisibility::Opaque,
248        VoxelVisibility::Opaque => false,
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255    use crate::RIGHT_HANDED_Y_UP_CONFIG;
256    use ndshape::{ConstShape, ConstShape3u32};
257
258    #[test]
259    #[should_panic]
260    fn panics_with_max_out_of_bounds_access() {
261        let samples = [EMPTY; SampleShape::SIZE as usize];
262        let mut buffer = GreedyQuadsBuffer::new(samples.len());
263        greedy_quads(
264            &samples,
265            &SampleShape {},
266            [0; 3],
267            [34, 33, 33],
268            &RIGHT_HANDED_Y_UP_CONFIG.faces,
269            &mut buffer,
270        );
271    }
272
273    #[test]
274    #[should_panic]
275    fn panics_with_min_out_of_bounds_access() {
276        let samples = [EMPTY; SampleShape::SIZE as usize];
277        let mut buffer = GreedyQuadsBuffer::new(samples.len());
278        greedy_quads(
279            &samples,
280            &SampleShape {},
281            [0, 34, 0],
282            [33; 3],
283            &RIGHT_HANDED_Y_UP_CONFIG.faces,
284            &mut buffer,
285        );
286    }
287
288    type SampleShape = ConstShape3u32<34, 34, 34>;
289
290    /// Basic voxel type with one byte of texture layers
291    #[derive(Default, Clone, Copy, Eq, PartialEq)]
292    struct BoolVoxel(bool);
293
294    const EMPTY: BoolVoxel = BoolVoxel(false);
295
296    impl Voxel for BoolVoxel {
297        fn get_visibility(&self) -> VoxelVisibility {
298            if *self == EMPTY {
299                VoxelVisibility::Empty
300            } else {
301                VoxelVisibility::Opaque
302            }
303        }
304    }
305
306    impl MergeVoxel for BoolVoxel {
307        type MergeValue = Self;
308
309        fn merge_value(&self) -> Self::MergeValue {
310            *self
311        }
312    }
313}