Skip to main content

steel_core/worldgen/carver/
mask.rs

1//! Per-chunk bitset marking which block positions have already been visited
2//! by a carver.
3//!
4//! Mirrors vanilla's `net.minecraft.world.level.chunk.CarvingMask`. Used by
5//! `WorldCarver` to avoid repeatedly processing the same position when
6//! multiple carver steps overlap.
7
8/// A `16 × height × 16` bitset of local block positions in a chunk.
9#[derive(Debug, Clone)]
10pub struct CarvingMask {
11    min_y: i32,
12    height: i32,
13    /// 256 bits per Y layer (`x` in low 4 bits, `z` in next 4 bits).
14    bits: Vec<u64>,
15}
16
17impl CarvingMask {
18    /// Creates an empty mask covering `[min_y, min_y + height)`.
19    #[must_use]
20    pub fn new(height: i32, min_y: i32) -> Self {
21        let total_bits = (256 * height) as usize;
22        let lanes = total_bits.div_ceil(64);
23        Self {
24            min_y,
25            height,
26            bits: vec![0; lanes],
27        }
28    }
29
30    /// Rebuilds a mask from Steel's packed `u64` bitset representation.
31    #[must_use]
32    pub fn from_packed_u64s(height: i32, min_y: i32, packed: &[u64]) -> Self {
33        let mut mask = Self::new(height, min_y);
34        let len = mask.bits.len().min(packed.len());
35        mask.bits[..len].copy_from_slice(&packed[..len]);
36        mask
37    }
38
39    /// Returns Steel's packed `u64` bitset representation, trimming trailing zeroes.
40    #[must_use]
41    pub fn to_packed_u64s(&self) -> Vec<u64> {
42        let len = self
43            .bits
44            .iter()
45            .rposition(|lane| *lane != 0)
46            .map_or(0, |idx| idx + 1);
47        self.bits[..len].to_vec()
48    }
49
50    /// Vanilla's `getIndex`: `x & 15 | (z & 15) << 4 | (y - min_y) << 8`.
51    #[inline]
52    const fn index(&self, x: i32, y: i32, z: i32) -> usize {
53        let xi = (x & 15) as u32;
54        let zi = ((z & 15) as u32) << 4;
55        let yi = ((y - self.min_y) as u32) << 8;
56        (xi | zi | yi) as usize
57    }
58
59    /// Marks `(x, y, z)` as carved.
60    #[inline]
61    pub fn set(&mut self, x: i32, y: i32, z: i32) {
62        let idx = self.index(x, y, z);
63        let lane = idx / 64;
64        let bit = idx % 64;
65        self.bits[lane] |= 1u64 << bit;
66    }
67
68    /// Marks `(x, y, z)` as carved if it was not already marked.
69    ///
70    /// Returns `true` when this call set the bit, or `false` when a previous
71    /// carver step had already visited the position.
72    #[inline]
73    pub fn set_if_unset(&mut self, x: i32, y: i32, z: i32) -> bool {
74        let idx = self.index(x, y, z);
75        let lane = idx / 64;
76        let bit = 1u64 << (idx % 64);
77        if self.bits[lane] & bit != 0 {
78            return false;
79        }
80        self.bits[lane] |= bit;
81        true
82    }
83
84    /// Returns whether `(x, y, z)` has been carved.
85    #[inline]
86    #[must_use]
87    pub fn get(&self, x: i32, y: i32, z: i32) -> bool {
88        let idx = self.index(x, y, z);
89        let lane = idx / 64;
90        let bit = idx % 64;
91        (self.bits[lane] >> bit) & 1 != 0
92    }
93
94    /// Y range bound at construction.
95    #[must_use]
96    pub const fn min_y(&self) -> i32 {
97        self.min_y
98    }
99
100    /// Height in blocks.
101    #[must_use]
102    pub const fn height(&self) -> i32 {
103        self.height
104    }
105}
106
107#[cfg(test)]
108mod test {
109    use super::*;
110
111    #[test]
112    fn set_and_get_roundtrip() {
113        let mut mask = CarvingMask::new(384, -64);
114        assert!(!mask.get(5, 10, 7));
115        mask.set(5, 10, 7);
116        assert!(mask.get(5, 10, 7));
117        // Neighbors untouched
118        assert!(!mask.get(4, 10, 7));
119        assert!(!mask.get(5, 11, 7));
120        assert!(!mask.get(5, 10, 8));
121    }
122
123    #[test]
124    fn set_if_unset_reports_first_visit() {
125        let mut mask = CarvingMask::new(384, -64);
126        assert!(mask.set_if_unset(5, 10, 7));
127        assert!(!mask.set_if_unset(5, 10, 7));
128        assert!(mask.get(5, 10, 7));
129    }
130
131    #[test]
132    fn indexing_matches_vanilla_layout() {
133        let mask = CarvingMask::new(384, -64);
134        // x=0, z=0, y=min_y → index 0
135        assert_eq!(mask.index(0, -64, 0), 0);
136        // x=15, z=0, y=min_y → 15
137        assert_eq!(mask.index(15, -64, 0), 15);
138        // x=0, z=1, y=min_y → 16
139        assert_eq!(mask.index(0, -64, 1), 16);
140        // x=0, z=0, y=min_y+1 → 256
141        assert_eq!(mask.index(0, -63, 0), 256);
142    }
143
144    #[test]
145    fn x_and_z_are_masked_to_chunk_local() {
146        let mut mask = CarvingMask::new(384, -64);
147        // Chunk-local: 17 → 1, 18 → 2
148        mask.set(17, 0, 18);
149        assert!(mask.get(1, 0, 2));
150        assert!(mask.get(17, 0, 18));
151    }
152
153    #[test]
154    fn packed_u64s_roundtrip_preserves_set_bits() {
155        let mut mask = CarvingMask::new(384, -64);
156        mask.set(3, -10, 5);
157        mask.set(15, 319, 15);
158
159        let restored = CarvingMask::from_packed_u64s(384, -64, &mask.to_packed_u64s());
160
161        assert!(restored.get(3, -10, 5));
162        assert!(restored.get(15, 319, 15));
163        assert!(!restored.get(4, -10, 5));
164    }
165
166    #[test]
167    fn empty_packed_u64s_are_omitted() {
168        let mask = CarvingMask::new(384, -64);
169        assert!(mask.to_packed_u64s().is_empty());
170    }
171}