1use glam::IVec3;
21use roxlap_formats::edit::{set_cube, set_rect, set_sphere};
22
23use crate::addr::{voxel_split, GridLocalPos};
24use crate::{Grid, CHUNK_SIZE_XY, CHUNK_SIZE_Z};
25
26#[inline]
30fn chunk_size_ivec3() -> IVec3 {
31 #[allow(clippy::cast_possible_wrap)]
32 IVec3::new(
33 CHUNK_SIZE_XY as i32,
34 CHUNK_SIZE_XY as i32,
35 CHUNK_SIZE_Z as i32,
36 )
37}
38
39impl Grid {
40 pub fn set_voxel(&mut self, voxel: IVec3, color: Option<u32>) {
50 let (chunk_idx, in_chunk) = voxel_split(voxel);
51 if color.is_some() {
52 let vxl = self.ensure_chunk(chunk_idx);
53 #[allow(clippy::cast_possible_wrap)]
54 set_cube(
55 vxl,
56 in_chunk.x as i32,
57 in_chunk.y as i32,
58 in_chunk.z as i32,
59 color,
60 );
61 } else if let Some(vxl) = self.chunks.get_mut(&chunk_idx) {
62 #[allow(clippy::cast_possible_wrap)]
63 set_cube(
64 vxl,
65 in_chunk.x as i32,
66 in_chunk.y as i32,
67 in_chunk.z as i32,
68 None,
69 );
70 }
71 }
72
73 pub fn set_rect(&mut self, lo: IVec3, hi: IVec3, color: Option<u32>) {
85 let lo_n = lo.min(hi);
86 let hi_n = lo.max(hi);
87 let (lo_c, _) = voxel_split(lo_n);
88 let (hi_c, _) = voxel_split(hi_n);
89 let cs = chunk_size_ivec3();
90
91 for cz in lo_c.z..=hi_c.z {
92 for cy in lo_c.y..=hi_c.y {
93 for cx in lo_c.x..=hi_c.x {
94 let chunk_idx = IVec3::new(cx, cy, cz);
95 let chunk_origin = chunk_idx * cs;
96 let chunk_end = chunk_origin + cs - IVec3::ONE;
97 let local_lo = lo_n.max(chunk_origin) - chunk_origin;
98 let local_hi = hi_n.min(chunk_end) - chunk_origin;
99 apply_set_rect(self, chunk_idx, local_lo, local_hi, color);
100 }
101 }
102 }
103 }
104
105 pub fn set_sphere(&mut self, centre: IVec3, radius: u32, color: Option<u32>) {
120 #[allow(clippy::cast_possible_wrap)]
121 let r_i = radius as i32;
122 let lo = centre - IVec3::splat(r_i);
123 let hi = centre + IVec3::splat(r_i);
124 let (lo_c, _) = voxel_split(lo);
125 let (hi_c, _) = voxel_split(hi);
126 let cs = chunk_size_ivec3();
127
128 for cz in lo_c.z..=hi_c.z {
129 for cy in lo_c.y..=hi_c.y {
130 for cx in lo_c.x..=hi_c.x {
131 let chunk_idx = IVec3::new(cx, cy, cz);
132 let chunk_origin = chunk_idx * cs;
133 let local_centre = centre - chunk_origin;
134 apply_set_sphere(self, chunk_idx, local_centre, radius, color);
135 }
136 }
137 }
138 }
139}
140
141fn apply_set_rect(
142 grid: &mut Grid,
143 chunk_idx: IVec3,
144 local_lo: IVec3,
145 local_hi: IVec3,
146 color: Option<u32>,
147) {
148 if color.is_some() {
149 let vxl = grid.ensure_chunk(chunk_idx);
150 set_rect(vxl, local_lo.into(), local_hi.into(), color);
151 } else if let Some(vxl) = grid.chunks.get_mut(&chunk_idx) {
152 set_rect(vxl, local_lo.into(), local_hi.into(), None);
153 }
154}
155
156fn apply_set_sphere(
157 grid: &mut Grid,
158 chunk_idx: IVec3,
159 local_centre: IVec3,
160 radius: u32,
161 color: Option<u32>,
162) {
163 if color.is_some() {
164 let vxl = grid.ensure_chunk(chunk_idx);
165 set_sphere(vxl, local_centre.into(), radius, color);
166 } else if let Some(vxl) = grid.chunks.get_mut(&chunk_idx) {
167 set_sphere(vxl, local_centre.into(), radius, None);
168 }
169}
170
171#[must_use]
178pub fn voxel_at(local: &GridLocalPos) -> IVec3 {
179 crate::addr::voxel_global(local.chunk, local.voxel)
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use crate::chunks::tests::voxel_is_solid;
186 use crate::GridTransform;
187
188 const TEST_COL: u32 = 0x80_aa_bb_cc;
189
190 #[test]
191 fn set_voxel_inserts_in_correct_chunk() {
192 let mut g = Grid::new(GridTransform::identity());
195 g.set_voxel(IVec3::new(5, 6, 7), Some(TEST_COL));
196 let vxl = g.chunk(IVec3::ZERO).expect("chunk created");
197 assert!(voxel_is_solid(vxl, 5, 6, 7));
198 assert!(!voxel_is_solid(vxl, 5, 6, 8));
200 assert_eq!(g.chunk_count(), 1);
201 }
202
203 #[test]
204 fn set_voxel_negative_coords_use_neg_chunk() {
205 let mut g = Grid::new(GridTransform::identity());
208 g.set_voxel(IVec3::new(-1, 0, 0), Some(TEST_COL));
209 assert!(g.chunk(IVec3::new(-1, 0, 0)).is_some());
210 let vxl = g.chunk(IVec3::new(-1, 0, 0)).unwrap();
211 assert!(voxel_is_solid(vxl, CHUNK_SIZE_XY - 1, 0, 0));
212 assert!(g.chunk(IVec3::ZERO).is_none());
214 }
215
216 #[test]
217 fn set_voxel_carve_then_insert_round_trips() {
218 let mut g = Grid::new(GridTransform::identity());
219 g.set_voxel(IVec3::new(10, 10, 10), Some(TEST_COL));
220 assert!(voxel_is_solid(g.chunk(IVec3::ZERO).unwrap(), 10, 10, 10));
221 g.set_voxel(IVec3::new(10, 10, 10), None);
222 assert!(!voxel_is_solid(g.chunk(IVec3::ZERO).unwrap(), 10, 10, 10));
223 }
224
225 #[test]
226 fn set_voxel_carve_in_missing_chunk_is_noop() {
227 let mut g = Grid::new(GridTransform::identity());
230 g.set_voxel(IVec3::new(5, 5, 5), None);
231 assert_eq!(g.chunk_count(), 0);
232 }
233
234 #[test]
235 fn set_rect_within_one_chunk() {
236 let mut g = Grid::new(GridTransform::identity());
237 g.set_rect(IVec3::new(0, 0, 0), IVec3::new(3, 3, 3), Some(TEST_COL));
238 assert_eq!(g.chunk_count(), 1);
239 let vxl = g.chunk(IVec3::ZERO).unwrap();
240 for z in 0..=3 {
241 for y in 0..=3 {
242 for x in 0..=3 {
243 assert!(voxel_is_solid(vxl, x, y, z), "({x},{y},{z}) air");
244 }
245 }
246 }
247 assert!(!voxel_is_solid(vxl, 4, 0, 0));
249 assert!(!voxel_is_solid(vxl, 0, 4, 0));
250 assert!(!voxel_is_solid(vxl, 0, 0, 4));
251 }
252
253 #[test]
254 fn set_rect_spans_two_chunks_x() {
255 let mut g = Grid::new(GridTransform::identity());
258 g.set_rect(IVec3::new(126, 0, 0), IVec3::new(129, 0, 0), Some(TEST_COL));
259 assert_eq!(g.chunk_count(), 2);
260
261 let v0 = g.chunk(IVec3::ZERO).unwrap();
263 assert!(voxel_is_solid(v0, 126, 0, 0));
264 assert!(voxel_is_solid(v0, 127, 0, 0));
265 assert!(!voxel_is_solid(v0, 125, 0, 0));
266
267 let v1 = g.chunk(IVec3::new(1, 0, 0)).unwrap();
269 assert!(voxel_is_solid(v1, 0, 0, 0));
270 assert!(voxel_is_solid(v1, 1, 0, 0));
271 assert!(!voxel_is_solid(v1, 2, 0, 0));
272 }
273
274 #[test]
275 fn set_rect_spans_z_boundary() {
276 let mut g = Grid::new(GridTransform::identity());
279 g.set_rect(IVec3::new(0, 0, 254), IVec3::new(0, 0, 257), Some(TEST_COL));
280 assert_eq!(g.chunk_count(), 2);
281 let v0 = g.chunk(IVec3::ZERO).unwrap();
282 assert!(voxel_is_solid(v0, 0, 0, 254));
283 assert!(voxel_is_solid(v0, 0, 0, 255));
284 let v1 = g.chunk(IVec3::new(0, 0, 1)).unwrap();
285 assert!(voxel_is_solid(v1, 0, 0, 0));
286 assert!(voxel_is_solid(v1, 0, 0, 1));
287 assert!(!voxel_is_solid(v1, 0, 0, 2));
288 }
289
290 #[test]
291 fn set_rect_unsorted_lo_hi_normalised() {
292 let mut g1 = Grid::new(GridTransform::identity());
294 let mut g2 = Grid::new(GridTransform::identity());
295 g1.set_rect(IVec3::new(0, 0, 0), IVec3::new(3, 3, 3), Some(TEST_COL));
296 g2.set_rect(IVec3::new(3, 3, 3), IVec3::new(0, 0, 0), Some(TEST_COL));
297 let v1 = g1.chunk(IVec3::ZERO).unwrap();
298 let v2 = g2.chunk(IVec3::ZERO).unwrap();
299 for z in 0..=3 {
300 for y in 0..=3 {
301 for x in 0..=3 {
302 assert_eq!(voxel_is_solid(v1, x, y, z), voxel_is_solid(v2, x, y, z));
303 }
304 }
305 }
306 }
307
308 #[test]
309 fn set_sphere_within_one_chunk() {
310 let mut g = Grid::new(GridTransform::identity());
311 g.set_sphere(IVec3::new(64, 64, 100), 5, Some(TEST_COL));
312 assert_eq!(g.chunk_count(), 1);
313 let vxl = g.chunk(IVec3::ZERO).unwrap();
314 assert!(voxel_is_solid(vxl, 64, 64, 100));
316 assert!(voxel_is_solid(vxl, 65, 64, 100));
318 assert!(voxel_is_solid(vxl, 64, 64, 105));
319 assert!(!voxel_is_solid(vxl, 70, 64, 100));
321 }
322
323 #[test]
324 fn set_sphere_spans_chunk_boundary() {
325 let mut g = Grid::new(GridTransform::identity());
328 g.set_sphere(IVec3::new(127, 64, 100), 4, Some(TEST_COL));
329 assert_eq!(g.chunk_count(), 2);
331
332 let v0 = g.chunk(IVec3::ZERO).unwrap();
333 assert!(voxel_is_solid(v0, 127, 64, 100));
336 assert!(voxel_is_solid(v0, 124, 64, 100));
338
339 let v1 = g.chunk(IVec3::new(1, 0, 0)).unwrap();
340 assert!(voxel_is_solid(v1, 0, 64, 100));
343 assert!(voxel_is_solid(v1, 2, 64, 100));
345 }
346
347 #[test]
348 fn set_voxel_dispatches_to_correct_chunk_on_y_z_axes() {
349 let mut g = Grid::new(GridTransform::identity());
353 g.set_voxel(IVec3::new(200, 300, 500), Some(TEST_COL));
354 let vxl = g
355 .chunk(IVec3::new(1, 2, 1))
356 .expect("expected chunk (1, 2, 1)");
357 assert!(voxel_is_solid(vxl, 72, 44, 244));
358 }
359}