steel_core/worldgen/carver/
mask.rs1#[derive(Debug, Clone)]
10pub struct CarvingMask {
11 min_y: i32,
12 height: i32,
13 bits: Vec<u64>,
15}
16
17impl CarvingMask {
18 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
96 pub const fn min_y(&self) -> i32 {
97 self.min_y
98 }
99
100 #[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 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 assert_eq!(mask.index(0, -64, 0), 0);
136 assert_eq!(mask.index(15, -64, 0), 15);
138 assert_eq!(mask.index(0, -64, 1), 16);
140 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 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}