Skip to main content

block_mesh_bgm/
lib.rs

1//! `block-mesh`-compatible binary greedy meshing.
2//!
3//! [`binary_greedy_quads`] mirrors the call shape of
4//! [`block_mesh::greedy_quads`], but it reaches the same kind of `QuadBuffer`
5//! through a different internal pipeline:
6//!
7//! 1. Validate the padded query extent and precompute a few indexing facts.
8//! 2. Build packed occupancy columns, one `u64` per orthogonal column.
9//! 3. Turn those columns into visible-face rows with cheap bitwise tests.
10//! 4. Merge each face slice into quads, either as unit quads or with a
11//!    carry-based greedy row merger.
12//!
13//! The implementation is split into the same stages in source:
14//!
15//! - `context`: query validation and precomputed layout facts
16//! - `prep`: occupancy columns and visible-face rows
17//! - `merge`: unit emission and carry-based greedy merging
18//! - `ao`: AO-safe alternate merge policy built on binary occupancy masks
19//! - `face`: face-orientation helpers shared by the other stages
20//!
21//! The crate exposes two public entry points:
22//!
23//! - [`binary_greedy_quads`]: the current maximum-merge implementation with no
24//!   extra policy checks
25//! - [`binary_greedy_quads_ao_safe`]: the same visibility pipeline, but with
26//!   AO-safe merge restrictions for opaque faces
27//!
28//! # AO-safe mode
29//!
30//! AO-safe mode keeps the same prep pipeline as vanilla meshing. The difference
31//! is only in how face rows are merged:
32//!
33//! - vanilla merging only checks `MergeVoxel::merge_value()`
34//! - AO-safe merging also looks at opaque occupancy in the plane just outside
35//!   the visible face
36//! - from that exterior plane it derives whole-row binary masks that prove
37//!   which cells must stay unit, which may only merge in one direction, and
38//!   which are still free to use the normal carry merge
39//!
40//! That keeps the AO rule in terms of the same binary data the mesher already
41//! uses elsewhere, instead of carrying per-cell lighting data through the hot
42//! merge loop.
43//!
44//! # Terminology
45//!
46//! The source uses three axis names repeatedly:
47//!
48//! - `n_axis`: the face normal axis
49//! - `outer_axis`: the axis that advances from one row to the next within a
50//!   face slice
51//! - `bit_axis`: the axis packed into the row's `u64` bitset
52//!
53//! Every meshed query is also split into two nested boxes:
54//!
55//! - the **query extent** `[min, max]`, which includes one voxel of padding on
56//!   every side
57//! - the **interior extent** `[min + 1, max - 1]`, whose faces may actually
58//!   produce quads
59//!
60//! The padding is never emitted. It only exists so the mesher can decide
61//! whether an interior face is visible.
62//!
63//! # Why the `62`-voxel limit exists
64//!
65//! Each axis of the padded query is packed into one `u64`. That leaves room for
66//! at most `62` interior voxels plus the two required padding voxels.
67//!
68//! # Example
69//!
70//! ```
71//! use block_mesh::ndshape::{ConstShape, ConstShape3u32};
72//! use block_mesh::{
73//!     MergeVoxel, Voxel, VoxelVisibility, RIGHT_HANDED_Y_UP_CONFIG,
74//! };
75//! use block_mesh_bgm::{binary_greedy_quads, BinaryGreedyQuadsBuffer};
76//!
77//! #[derive(Clone, Copy, Debug, Eq, PartialEq)]
78//! struct BoolVoxel(bool);
79//!
80//! const EMPTY: BoolVoxel = BoolVoxel(false);
81//! const FULL: BoolVoxel = BoolVoxel(true);
82//!
83//! impl Voxel for BoolVoxel {
84//!     fn get_visibility(&self) -> VoxelVisibility {
85//!         if *self == EMPTY {
86//!             VoxelVisibility::Empty
87//!         } else {
88//!             VoxelVisibility::Opaque
89//!         }
90//!     }
91//! }
92//!
93//! impl MergeVoxel for BoolVoxel {
94//!     type MergeValue = Self;
95//!
96//!     fn merge_value(&self) -> Self::MergeValue {
97//!         *self
98//!     }
99//! }
100//!
101//! type ChunkShape = ConstShape3u32<18, 18, 18>;
102//!
103//! let mut voxels = [EMPTY; ChunkShape::SIZE as usize];
104//! for i in 0..ChunkShape::SIZE {
105//!     let [x, y, z] = ChunkShape::delinearize(i);
106//!     voxels[i as usize] = if ((x * x + y * y + z * z) as f32).sqrt() < 15.0 {
107//!         FULL
108//!     } else {
109//!         EMPTY
110//!     };
111//! }
112//!
113//! let mut buffer = BinaryGreedyQuadsBuffer::new();
114//! binary_greedy_quads(
115//!     &voxels,
116//!     &ChunkShape {},
117//!     [0; 3],
118//!     [17; 3],
119//!     &RIGHT_HANDED_Y_UP_CONFIG.faces,
120//!     &mut buffer,
121//! );
122//!
123//! assert!(buffer.quads.num_quads() > 0);
124//! ```
125//!
126//! The AO-safe entry point uses the same buffer type:
127//!
128//! ```
129//! # use block_mesh::ndshape::{ConstShape, ConstShape3u32};
130//! # use block_mesh::{
131//! #     MergeVoxel, Voxel, VoxelVisibility, RIGHT_HANDED_Y_UP_CONFIG,
132//! # };
133//! # use block_mesh_bgm::{binary_greedy_quads_ao_safe, BinaryGreedyQuadsBuffer};
134//! # #[derive(Clone, Copy, Debug, Eq, PartialEq)]
135//! # struct BoolVoxel(bool);
136//! # impl Voxel for BoolVoxel {
137//! #     fn get_visibility(&self) -> VoxelVisibility {
138//! #         if self.0 {
139//! #             VoxelVisibility::Opaque
140//! #         } else {
141//! #             VoxelVisibility::Empty
142//! #         }
143//! #     }
144//! # }
145//! # impl MergeVoxel for BoolVoxel {
146//! #     type MergeValue = Self;
147//! #     fn merge_value(&self) -> Self::MergeValue {
148//! #         *self
149//! #     }
150//! # }
151//! # type ChunkShape = ConstShape3u32<4, 4, 4>;
152//! # let voxels = [BoolVoxel(false); ChunkShape::SIZE as usize];
153//! let mut buffer = BinaryGreedyQuadsBuffer::new();
154//! binary_greedy_quads_ao_safe(
155//!     &voxels,
156//!     &ChunkShape {},
157//!     [0; 3],
158//!     [3; 3],
159//!     &RIGHT_HANDED_Y_UP_CONFIG.faces,
160//!     &mut buffer,
161//! );
162//! ```
163#![warn(missing_docs)]
164
165mod ao;
166mod context;
167mod face;
168mod merge;
169mod prep;
170
171use ao::{mesh_face_rows_ao_safe, AoScratch};
172use block_mesh::{MergeVoxel, OrientedBlockFace, QuadBuffer};
173use context::MeshingContext;
174use merge::mesh_face_rows;
175use ndshape::Shape;
176use prep::{build_axis_columns, build_visible_row_pair, reset_columns, reset_visible_rows};
177
178/// Reusable output and scratch storage for [`binary_greedy_quads`] and
179/// [`binary_greedy_quads_ao_safe`].
180///
181/// Reusing one buffer per worker thread keeps the hot meshing path focused on
182/// bitwise work instead of repeated heap growth.
183#[derive(Default)]
184pub struct BinaryGreedyQuadsBuffer {
185    /// Output quads grouped by face, matching `block-mesh`'s `QuadBuffer`.
186    pub quads: QuadBuffer,
187    // Stage 1 scratch: packed opaque occupancy columns for each candidate bit axis.
188    opaque_cols: [Vec<u64>; 3],
189    // Stage 1 scratch: packed translucent occupancy columns with the same layout.
190    trans_cols: [Vec<u64>; 3],
191    // Stage 2 scratch: visibility rows for the negative face of one normal axis.
192    visible_rows: Vec<u64>,
193    // Stage 2 scratch: visibility rows for the positive face of the same axis.
194    visible_rows_alt: Vec<u64>,
195    // Stage 4 scratch: carry lengths for the current face slice.
196    carry_runs: Vec<u8>,
197    // AO-safe scratch: row masks derived from exterior-plane occupancy plus
198    // AO-specific carry state.
199    ao_scratch: AoScratch,
200}
201
202impl BinaryGreedyQuadsBuffer {
203    /// Creates an empty reusable output/scratch buffer.
204    #[inline]
205    #[must_use]
206    pub fn new() -> Self {
207        Self::default()
208    }
209}
210
211/// Generates greedy quads using a binary-mask-backed implementation.
212///
213/// The public API mirrors [`block_mesh::greedy_quads`]. The output lands in
214/// [`BinaryGreedyQuadsBuffer::quads`], which uses the same public
215/// `block-mesh` types as the upstream mesher.
216///
217/// # Output contract
218///
219/// This implementation preserves the set of visible unit faces that
220/// `block_mesh::greedy_quads` would emit for supported inputs, and it usually
221/// stays very close to the upstream quad count. It does not promise the exact
222/// same quad partition.
223///
224/// # Supported extents
225///
226/// The interior query extent may be at most `62` voxels wide on each axis.
227/// Including the required one-voxel border, the full query box may therefore be
228/// at most `64` voxels wide on each axis.
229pub fn binary_greedy_quads<T, S>(
230    voxels: &[T],
231    voxels_shape: &S,
232    min: [u32; 3],
233    max: [u32; 3],
234    faces: &[OrientedBlockFace; 6],
235    output: &mut BinaryGreedyQuadsBuffer,
236) where
237    T: MergeVoxel,
238    S: Shape<3, Coord = u32>,
239{
240    binary_greedy_quads_impl::<T, S, false>(voxels, voxels_shape, min, max, faces, output);
241}
242
243/// Generates greedy quads with AO-safe merge restrictions for opaque faces.
244///
245/// This uses the same visibility pipeline as [`binary_greedy_quads`], but it
246/// refuses merges that would cross AO boundaries on opaque faces. Translucent
247/// faces keep the same merge rule they use in the vanilla path.
248///
249/// This only changes the merge policy. It does not emit AO values or lighting
250/// data for you.
251pub fn binary_greedy_quads_ao_safe<T, S>(
252    voxels: &[T],
253    voxels_shape: &S,
254    min: [u32; 3],
255    max: [u32; 3],
256    faces: &[OrientedBlockFace; 6],
257    output: &mut BinaryGreedyQuadsBuffer,
258) where
259    T: MergeVoxel,
260    S: Shape<3, Coord = u32>,
261{
262    binary_greedy_quads_impl::<T, S, true>(voxels, voxels_shape, min, max, faces, output);
263}
264
265fn binary_greedy_quads_impl<T, S, const AO_SAFE: bool>(
266    voxels: &[T],
267    voxels_shape: &S,
268    min: [u32; 3],
269    max: [u32; 3],
270    faces: &[OrientedBlockFace; 6],
271    output: &mut BinaryGreedyQuadsBuffer,
272) where
273    T: MergeVoxel,
274    S: Shape<3, Coord = u32>,
275{
276    let Some(context) = MeshingContext::new(voxels, voxels_shape, min, max, faces) else {
277        output.quads.reset();
278        return;
279    };
280
281    output.quads.reset();
282
283    let BinaryGreedyQuadsBuffer {
284        quads,
285        opaque_cols,
286        trans_cols,
287        visible_rows,
288        visible_rows_alt,
289        carry_runs,
290        ao_scratch,
291    } = output;
292
293    // Stage 1: build reusable occupancy columns for all three candidate bit axes.
294    reset_columns(opaque_cols, trans_cols, context.query_shape);
295    let has_translucent = build_axis_columns(
296        voxels,
297        min,
298        context.query_shape,
299        context.strides,
300        opaque_cols,
301        trans_cols,
302    );
303
304    for n_axis in 0..3 {
305        let slice = context.slice_plan(n_axis);
306        let [neg_face_index, pos_face_index] = context.face_indices[n_axis];
307        let neg_axes = context.face_axes[neg_face_index];
308        let pos_axes = context.face_axes[pos_face_index];
309
310        // Stage 2: derive visible-face rows for both signs of the current normal axis.
311        reset_visible_rows(visible_rows, slice.total_rows());
312        reset_visible_rows(visible_rows_alt, slice.total_rows());
313        let (neg_unit_only, pos_unit_only) = build_visible_row_pair(
314            context.query_shape,
315            slice.bit_axis,
316            slice.bit_len,
317            slice.outer_len,
318            slice.n_len,
319            n_axis,
320            slice.outer_axis,
321            &opaque_cols[slice.bit_axis],
322            &trans_cols[slice.bit_axis],
323            has_translucent,
324            visible_rows,
325            visible_rows_alt,
326        );
327
328        // Stage 3 and 4: emit unit quads for fragmented slices, otherwise
329        // merge rows with either the vanilla maximum-merge path or one of the
330        // alternate merge policies.
331        if AO_SAFE {
332            mesh_face_rows_ao_safe(
333                voxels,
334                &context,
335                slice,
336                visible_rows,
337                neg_unit_only,
338                neg_axes,
339                &opaque_cols[slice.bit_axis],
340                ao_scratch,
341                &mut quads.groups[neg_face_index],
342            );
343            mesh_face_rows_ao_safe(
344                voxels,
345                &context,
346                slice,
347                visible_rows_alt,
348                pos_unit_only,
349                pos_axes,
350                &opaque_cols[slice.bit_axis],
351                ao_scratch,
352                &mut quads.groups[pos_face_index],
353            );
354        } else {
355            mesh_face_rows(
356                voxels,
357                &context,
358                slice,
359                visible_rows,
360                neg_unit_only,
361                neg_axes,
362                carry_runs,
363                &mut quads.groups[neg_face_index],
364            );
365            mesh_face_rows(
366                voxels,
367                &context,
368                slice,
369                visible_rows_alt,
370                pos_unit_only,
371                pos_axes,
372                carry_runs,
373                &mut quads.groups[pos_face_index],
374            );
375        }
376    }
377}
378
379#[inline]
380pub(crate) fn bit_mask(start: usize, width: usize) -> u64 {
381    ((1u64 << width) - 1) << start
382}