1#![allow(dead_code)] pub const MAXZDIM: i32 = 256;
32
33pub fn delslab(b2: &mut [i32], y0: i32, mut y1: i32) {
46 if y1 >= MAXZDIM {
47 y1 = MAXZDIM - 1;
48 }
49 if y0 >= y1 || b2.is_empty() {
50 return;
51 }
52 let mut z = 0usize;
53 while y0 >= b2[z + 1] {
54 z += 2;
55 }
56 if y0 > b2[z] {
57 if y1 < b2[z + 1] {
58 let mut i = z;
62 while b2[i + 1] < MAXZDIM {
63 i += 2;
64 }
65 while i > z {
66 b2[i + 3] = b2[i + 1];
67 b2[i + 2] = b2[i];
68 i -= 2;
69 }
70 b2[z + 3] = b2[z + 1];
71 b2[z + 1] = y0;
72 b2[z + 2] = y1;
73 return;
74 }
75 b2[z + 1] = y0;
78 z += 2;
79 }
80 if y1 >= b2[z + 1] {
81 let mut i = z + 2;
85 while y1 >= b2[i + 1] {
86 i += 2;
87 }
88 let delta = i - z;
89 b2[z] = b2[i];
90 b2[z + 1] = b2[i + 1];
91 while b2[i + 1] < MAXZDIM {
92 i += 2;
93 b2[i - delta] = b2[i];
94 b2[i - delta + 1] = b2[i + 1];
95 }
96 }
97 if y1 > b2[z] {
98 b2[z] = y1;
100 }
101}
102
103pub fn insslab(b2: &mut [i32], y0: i32, y1: i32) {
118 if y0 >= y1 || b2.is_empty() {
119 return;
120 }
121 let mut z = 0usize;
122 while y0 > b2[z + 1] {
123 z += 2;
124 }
125 if y1 < b2[z] {
126 let mut i = z;
130 while b2[i + 1] < MAXZDIM {
131 i += 2;
132 }
133 loop {
134 b2[i + 3] = b2[i + 1];
135 b2[i + 2] = b2[i];
136 if i == z {
137 break;
138 }
139 i -= 2;
140 }
141 b2[z + 1] = y1;
142 b2[z] = y0;
143 return;
144 }
145 if y0 < b2[z] {
146 b2[z] = y0;
148 }
149 if y1 >= b2[z + 2] && b2[z + 1] < MAXZDIM {
150 let mut i = z + 2;
154 while y1 >= b2[i + 2] && b2[i + 1] < MAXZDIM {
155 i += 2;
156 }
157 let delta = i - z;
158 b2[z + 1] = b2[i + 1];
159 while b2[i + 1] < MAXZDIM {
160 i += 2;
161 b2[i - delta] = b2[i];
162 b2[i - delta + 1] = b2[i + 1];
163 }
164 }
165 if y1 > b2[z + 1] {
166 b2[z + 1] = y1;
168 }
169}
170
171pub fn expandrle(slab: &[u8], uind: &mut [i32]) {
184 uind[0] = i32::from(slab[1]);
185 let mut i = 2usize;
186 let mut v = 0usize;
187 while slab[v] != 0 {
188 v += usize::from(slab[v]) * 4;
189 if slab[v + 3] >= slab[v + 1] {
190 continue;
191 }
192 uind[i - 1] = i32::from(slab[v + 3]);
193 uind[i] = i32::from(slab[v + 1]);
194 i += 2;
195 }
196 uind[i - 1] = MAXZDIM;
197}
198
199#[derive(Debug)]
203struct ColorRange<'s> {
204 z_start: i32,
205 z_end: i32,
206 colors: &'s [u8],
209}
210
211fn build_color_table(slab: &[u8]) -> Vec<ColorRange<'_>> {
216 let mut ranges = Vec::new();
217 let mut v = 0usize;
218 loop {
219 let z_start = i32::from(slab[v + 1]);
220 let z1c = i32::from(slab[v + 2]);
221 let z_end = z1c + 1;
222 let n_voxels = usize::try_from((z_end - z_start).max(0)).expect("voxel count >= 0");
223 let off = v + 4;
224 ranges.push(ColorRange {
225 z_start,
226 z_end,
227 colors: &slab[off..off + n_voxels * 4],
228 });
229
230 let nextptr = slab[v];
231 if nextptr == 0 {
232 break;
233 }
234 let prev_v = v;
235 v += usize::from(nextptr) * 4;
236 let ze = i32::from(slab[v + 3]);
237 let prev_z1 = i32::from(slab[prev_v + 1]);
244 let prev_z1c = i32::from(slab[prev_v + 2]);
245 let prev_nextptr = i32::from(slab[prev_v]);
246 let ceil_z_start = ze + prev_z1c - prev_z1 - prev_nextptr + 2;
247 let ceil_z_end = ze;
248 let ceil_n =
249 usize::try_from((ceil_z_end - ceil_z_start).max(0)).expect("ceiling voxel count >= 0");
250 let ceil_start = v - ceil_n * 4;
252 ranges.push(ColorRange {
253 z_start: ceil_z_start,
254 z_end: ceil_z_end,
255 colors: &slab[ceil_start..v],
256 });
257 }
258 ranges.push(ColorRange {
259 z_start: MAXZDIM,
260 z_end: MAXZDIM,
261 colors: &[],
262 });
263 ranges
264}
265
266#[allow(
286 clippy::too_many_arguments,
287 clippy::too_many_lines,
288 clippy::missing_panics_doc
289)]
290pub(crate) fn compilerle(
291 n0: &[i32],
292 n1: &[i32],
293 n2: &[i32],
294 n3: &[i32],
295 n4: &[i32],
296 cbuf: &mut [u8],
297 original_column: &[u8],
298 px: i32,
299 py: i32,
300 colfunc: &mut dyn FnMut(i32, i32, i32) -> i32,
301) -> usize {
302 let tbuf2 = build_color_table(original_column);
303
304 let mut p_z: i32 = n0[0];
305 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
310 let to_u8 = |v: i32| (v & 0xff) as u8;
311
312 cbuf[1] = to_u8(p_z);
313 let mut ze: i32 = n0[1];
314 cbuf[2] = to_u8(ze - 1);
315 cbuf[3] = 0;
316
317 let mut i = 0usize;
318 let mut onext = 0usize;
319 let mut ic = 0usize;
320 let mut ia: i32 = 15;
321 let mut n = 4usize;
322 let mut zend = if ze == MAXZDIM { -1 } else { ze - 1 };
323
324 let mut n1_idx = 0usize;
325 let mut n2_idx = 0usize;
326 let mut n3_idx = 0usize;
327 let mut n4_idx = 0usize;
328
329 'outer: loop {
330 let mut dacnt = 0;
331 'middle: loop {
332 let exit_to_rlendit2 = loop {
334 while p_z >= tbuf2[ic].z_end {
335 ic += 1;
336 }
337 let color: i32 = if p_z >= tbuf2[ic].z_start {
338 let off =
339 usize::try_from((p_z - tbuf2[ic].z_start) * 4).expect("color offset >= 0");
340 let bytes = &tbuf2[ic].colors[off..off + 4];
341 i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
342 } else {
343 colfunc(px, py, p_z)
344 };
345 cbuf[n..n + 4].copy_from_slice(&color.to_le_bytes());
346 n += 4;
347 p_z += 1;
348 if p_z >= ze {
349 break true; }
351 while p_z >= n1[n1_idx] {
352 n1_idx += 1;
353 ia ^= 1;
354 }
355 while p_z >= n2[n2_idx] {
356 n2_idx += 1;
357 ia ^= 2;
358 }
359 while p_z >= n3[n3_idx] {
360 n3_idx += 1;
361 ia ^= 4;
362 }
363 while p_z >= n4[n4_idx] {
364 n4_idx += 1;
365 ia ^= 8;
366 }
367 if !(ia != 0 || p_z == zend) {
368 break false; }
370 };
371
372 if exit_to_rlendit2 {
373 if ze >= MAXZDIM {
374 break 'outer;
375 }
376 i += 2;
377 cbuf[onext] = u8::try_from((n - onext) >> 2).expect("slab dword count fits in u8");
378 onext = n;
379 p_z = n0[i];
380 cbuf[n + 1] = to_u8(p_z);
381 cbuf[n + 3] = to_u8(ze);
382 ze = n0[i + 1];
383 cbuf[n + 2] = to_u8(ze - 1);
384 n += 4;
385 zend = if ze == MAXZDIM { -1 } else { ze - 1 };
386 break 'middle; }
388
389 if dacnt == 0 {
392 cbuf[onext + 2] = to_u8(p_z - 1);
393 dacnt = 1;
394 } else {
395 cbuf[onext] = u8::try_from((n - onext) >> 2).expect("slab dword count fits in u8");
396 onext = n;
397 cbuf[n + 1] = to_u8(p_z);
398 cbuf[n + 2] = to_u8(p_z - 1);
399 cbuf[n + 3] = to_u8(p_z);
400 n += 4;
401 }
402
403 let n1_v = n1[n1_idx];
405 let n2_v = n2[n2_idx];
406 let n3_v = n3[n3_idx];
407 let n4_v = n4[n4_idx];
408 if n1_v < n2_v && n1_v < n3_v && n1_v < n4_v {
409 if n1_v >= ze {
410 p_z = ze - 1;
411 } else {
412 p_z = n1_v;
413 n1_idx += 1;
414 ia ^= 1;
415 }
416 } else if n2_v < n3_v && n2_v < n4_v {
417 if n2_v >= ze {
418 p_z = ze - 1;
419 } else {
420 p_z = n2_v;
421 n2_idx += 1;
422 ia ^= 2;
423 }
424 } else if n3_v < n4_v {
425 if n3_v >= ze {
426 p_z = ze - 1;
427 } else {
428 p_z = n3_v;
429 n3_idx += 1;
430 ia ^= 4;
431 }
432 } else if n4_v >= ze {
433 p_z = ze - 1;
434 } else {
435 p_z = n4_v;
436 n4_idx += 1;
437 ia ^= 8;
438 }
439
440 if p_z == MAXZDIM - 1 {
441 break 'outer;
442 }
443 }
445 }
446
447 cbuf[onext] = 0;
448 n
449}
450
451use crate::vxl::Vxl;
480
481pub(crate) const SCPITCH: usize = 256;
486
487pub(crate) const MAXCSIZ: usize = 1028;
490
491const SCOY_NONE: i32 = i32::MIN;
494
495const SCOYM3_INITIAL: usize = SCPITCH * 6;
497
498const SCOYM3_WRAP: usize = SCPITCH * 9;
501
502pub struct ScumCtx<'v> {
514 vxl: &'v mut Vxl,
515 radar: Vec<i32>,
517 cbuf: Vec<u8>,
519 colfunc: Box<dyn FnMut(i32, i32, i32) -> i32 + 'v>,
522
523 scoy: i32,
525 scoym3: usize,
526 scx0: i32,
527 scx1: i32,
528 scox0: i32,
529 scox1: i32,
530 scoox0: i32,
531 scoox1: i32,
532 scex0: i32,
533 scex1: i32,
534 sceox0: i32,
535 sceox1: i32,
536
537 last_scum2: Option<(i32, i32)>,
544}
545
546#[allow(
547 clippy::cast_possible_truncation,
548 clippy::cast_possible_wrap,
549 clippy::cast_sign_loss,
550 clippy::if_not_else,
551 clippy::similar_names
552)]
553impl<'v> ScumCtx<'v> {
554 pub fn new(vxl: &'v mut Vxl) -> Self {
562 assert!(
563 !vxl.vbit.is_empty(),
564 "ScumCtx::new requires Vxl::reserve_edit_capacity to be called first"
565 );
566 let radar_size = (vxl.vsid as usize + 4) * 3 * SCPITCH;
567 Self {
568 vxl,
569 radar: vec![0i32; radar_size],
570 cbuf: vec![0u8; MAXCSIZ],
571 colfunc: Box::new(|_, _, _| 0),
572 scoy: SCOY_NONE,
573 scoym3: SCOYM3_INITIAL,
574 scx0: 0,
575 scx1: 0,
576 scox0: 0,
577 scox1: 0,
578 scoox0: 0,
579 scoox1: 0,
580 scex0: 0,
581 scex1: 0,
582 sceox0: 0,
583 sceox1: 0,
584 last_scum2: None,
585 }
586 }
587
588 pub fn set_colfunc<F>(&mut self, f: F)
591 where
592 F: FnMut(i32, i32, i32) -> i32 + 'v,
593 {
594 self.colfunc = Box::new(f);
595 }
596
597 pub fn scum2(&mut self, x: i32, y: i32) -> Option<&mut [i32]> {
603 let vsid = self.vxl.vsid as i32;
604 if x < 0 || x >= vsid || y < 0 || y >= vsid {
605 return None;
606 }
607
608 if y != self.scoy {
609 if self.scoy != SCOY_NONE {
610 self.scum2_line();
611 while self.scoy < y - 1 {
612 self.scx0 = i32::MAX;
613 self.scx1 = i32::MIN;
614 self.advance_row();
615 self.scum2_line();
616 }
617 self.advance_row();
618 } else {
619 self.scoox0 = i32::MAX;
620 self.scox0 = i32::MAX;
621 self.sceox0 = x + 1;
622 self.scex0 = x + 1;
623 self.sceox1 = x;
624 self.scex1 = x;
625 self.scoy = y;
626 self.scoym3 = SCOYM3_INITIAL;
627 }
628 self.scx0 = x;
629 } else {
630 while self.scx1 < x - 1 {
633 self.scx1 += 1;
634 let scx1 = self.scx1;
635 self.expand_column_into_row(scx1, y, self.scoym3);
636 }
637 }
638
639 let radar_idx = self.scoym3 + (x as usize) * SCPITCH * 3;
640 self.scx1 = x;
641 self.expand_column_into_row(x, y, self.scoym3);
642 self.last_scum2 = Some((x, y));
643 Some(&mut self.radar[radar_idx..radar_idx + SCPITCH])
644 }
645
646 pub fn with_column<F>(&mut self, x: i32, y: i32, f: F) -> bool
657 where
658 F: FnOnce(&mut [i32]),
659 {
660 if self.last_scum2 != Some((x, y)) && self.scum2(x, y).is_none() {
661 return false;
662 }
663 let radar_idx = self.scoym3 + (x as usize) * SCPITCH * 3;
666 let b2 = &mut self.radar[radar_idx..radar_idx + SCPITCH];
667 f(b2);
668 true
669 }
670
671 pub fn finish(mut self) {
674 if self.scoy == SCOY_NONE {
675 return;
676 }
677 for _ in 0..2 {
678 self.scum2_line();
679 self.scx0 = i32::MAX;
680 self.scx1 = i32::MIN;
681 self.advance_row();
682 }
683 self.scum2_line();
684 self.scoy = SCOY_NONE;
685 }
686
687 fn advance_row(&mut self) {
693 self.scoy += 1;
694 self.scoym3 += SCPITCH;
695 if self.scoym3 == SCOYM3_WRAP {
696 self.scoym3 = SCOYM3_INITIAL;
697 }
698 self.last_scum2 = None;
699 }
700
701 fn expand_column_into_row(&mut self, x: i32, y: i32, row_base: usize) {
706 let vsid = self.vxl.vsid as i32;
707 let radar_idx_signed = (row_base as isize) + (x as isize) * (SCPITCH as isize) * 3;
709 if radar_idx_signed < 0 {
710 return;
711 }
712 #[allow(clippy::cast_sign_loss)]
713 let radar_idx = radar_idx_signed as usize;
714 if radar_idx + SCPITCH > self.radar.len() {
715 return;
716 }
717 if x < 0 || x >= vsid || y < 0 || y >= vsid {
718 self.radar[radar_idx] = 0;
719 self.radar[radar_idx + 1] = MAXZDIM;
720 return;
721 }
722 let idx = (y as usize) * (vsid as usize) + (x as usize);
723 let slab = self.vxl.column_data(idx);
724 expandrle(slab, &mut self.radar[radar_idx..radar_idx + SCPITCH]);
725 }
726
727 #[allow(clippy::too_many_lines)]
730 fn scum2_line(&mut self) {
731 let vsid = self.vxl.vsid as i32;
732
733 let x0 = (self.scox0 - 1).min(self.scx0).min(self.scoox0);
735 self.scoox0 = self.scox0;
736 self.scox0 = self.scx0;
737 let x1 = (self.scox1 + 1).max(self.scx1).max(self.scoox1);
738 self.scoox1 = self.scox1;
739 self.scox1 = self.scx1;
740
741 let uptr = wrap_radar(self.scoym3 + SCPITCH);
742 let mptr = wrap_radar(uptr + SCPITCH);
743
744 let scoy_2 = self.scoy - 2;
746 if x1 < self.sceox0 || x0 > self.sceox1 {
747 for x in x0..=x1 {
748 self.expand_column_into_row(x, scoy_2, uptr);
749 }
750 } else {
751 for x in x0..self.sceox0 {
752 self.expand_column_into_row(x, scoy_2, uptr);
753 }
754 let mut x = x1;
755 while x > self.sceox1 {
756 self.expand_column_into_row(x, scoy_2, uptr);
757 x -= 1;
758 }
759 }
760
761 let scoy_1 = self.scoy - 1;
763 if (self.scex1 | x1) >= 0 {
764 for x in (x1 + 2)..self.scex0 {
765 self.expand_column_into_row(x, scoy_1, mptr);
766 }
767 let mut x = x0 - 2;
768 while x > self.scex1 {
769 self.expand_column_into_row(x, scoy_1, mptr);
770 x -= 1;
771 }
772 }
773 if x1 + 1 < self.scex0 || x0 - 1 > self.scex1 {
774 for x in (x0 - 1)..=(x1 + 1) {
775 self.expand_column_into_row(x, scoy_1, mptr);
776 }
777 } else {
778 for x in (x0 - 1)..self.scex0 {
779 self.expand_column_into_row(x, scoy_1, mptr);
780 }
781 let mut x = x1 + 1;
782 while x > self.scex1 {
783 self.expand_column_into_row(x, scoy_1, mptr);
784 x -= 1;
785 }
786 }
787 self.sceox0 = (x0 - 1).min(self.scex0);
788 self.sceox1 = (x1 + 1).max(self.scex1);
789
790 let scoy_0 = self.scoy;
792 let scoym3 = self.scoym3;
793 if x1 < self.scx0 || x0 > self.scx1 {
794 for x in x0..=x1 {
795 self.expand_column_into_row(x, scoy_0, scoym3);
796 }
797 } else {
798 for x in x0..self.scx0 {
799 self.expand_column_into_row(x, scoy_0, scoym3);
800 }
801 let mut x = x1;
802 while x > self.scx1 {
803 self.expand_column_into_row(x, scoy_0, scoym3);
804 x -= 1;
805 }
806 }
807 self.scex0 = x0;
808 self.scex1 = x1;
809
810 let y = self.scoy - 1;
813 if !(0..vsid).contains(&y) {
814 return;
815 }
816 let x0_clamped = x0.max(0);
817 let x1_clamped = x1.min(vsid - 1);
818
819 for x in x0_clamped..=x1_clamped {
820 self.flush_column(x, y, mptr, uptr, scoym3);
821 }
822 }
823
824 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
828 fn flush_column(&mut self, x: i32, y: i32, mptr: usize, uptr: usize, scoym3: usize) {
829 let vsid = self.vxl.vsid as usize;
830 let k = (x as usize) * SCPITCH * 3;
831 let n0_pos = mptr + k;
832 let n1_pos_signed = (mptr as isize) + (k as isize) - (SCPITCH as isize) * 3;
833 let n2_pos = mptr + k + SCPITCH * 3;
834 let n3_pos = uptr + k;
835 let n4_pos = scoym3 + k;
836
837 if n1_pos_signed < 0 {
840 return;
841 }
842 let n1_pos = n1_pos_signed as usize;
843
844 let idx = (y as usize) * vsid + (x as usize);
845
846 let original_bytes: Vec<u8> = self.vxl.column_data(idx).to_vec();
849
850 let written = {
851 let radar = &self.radar;
852 let n0 = &radar[n0_pos..n0_pos + SCPITCH];
853 let n1 = &radar[n1_pos..n1_pos + SCPITCH];
854 let n2 = &radar[n2_pos..n2_pos + SCPITCH];
855 let n3 = &radar[n3_pos..n3_pos + SCPITCH];
856 let n4 = &radar[n4_pos..n4_pos + SCPITCH];
857 compilerle(
858 n0,
859 n1,
860 n2,
861 n3,
862 n4,
863 &mut self.cbuf,
864 &original_bytes,
865 x,
866 y,
867 &mut *self.colfunc,
868 )
869 };
870
871 let old_offset = self.vxl.column_offset[idx];
872 self.vxl.voxdealloc(old_offset);
873 let new_offset = self.vxl.voxalloc(written as u32);
874 self.vxl.data[new_offset as usize..new_offset as usize + written]
875 .copy_from_slice(&self.cbuf[..written]);
876 self.vxl.column_offset[idx] = new_offset;
877 }
878}
879
880fn wrap_radar(off: usize) -> usize {
883 if off == SCOYM3_WRAP {
884 SCOYM3_INITIAL
885 } else {
886 off
887 }
888}
889
890#[derive(Debug, Clone, Copy, PartialEq, Eq)]
905pub struct Vspan {
906 pub x: u32,
907 pub y: u32,
908 pub z0: u8,
909 pub z1: u8,
910}
911
912#[derive(Debug, Clone, Copy, PartialEq, Eq)]
922pub enum SpanOp {
923 Carve,
924 Insert,
925}
926
927#[allow(clippy::cast_possible_wrap)]
950pub fn set_spans_with_colfunc<F>(world: &mut Vxl, spans: &[Vspan], op: SpanOp, colfunc: F)
951where
952 F: FnMut(i32, i32, i32) -> i32,
953{
954 if spans.is_empty() {
955 return;
956 }
957 let inserting = op == SpanOp::Insert;
958 let mut ctx = ScumCtx::new(world);
959 ctx.set_colfunc(colfunc);
960 for span in spans {
961 let x = span.x as i32;
962 let y = span.y as i32;
963 let z0 = i32::from(span.z0);
964 let z1 = i32::from(span.z1) + 1; ctx.with_column(x, y, |b2| {
966 if inserting {
967 insslab(b2, z0, z1);
968 } else {
969 delslab(b2, z0, z1);
970 }
971 });
972 }
973 ctx.finish();
974}
975
976pub fn set_spans(world: &mut Vxl, spans: &[Vspan], color: Option<u32>) {
990 let op = if color.is_some() {
991 SpanOp::Insert
992 } else {
993 SpanOp::Carve
994 };
995 #[allow(clippy::cast_possible_wrap)]
996 let c_i32 = color.unwrap_or(0) as i32;
997 set_spans_with_colfunc(world, spans, op, move |_, _, _| c_i32);
998}
999
1000pub fn set_cube(world: &mut Vxl, x: i32, y: i32, z: i32, color: Option<u32>) {
1020 let op = if color.is_some() {
1021 SpanOp::Insert
1022 } else {
1023 SpanOp::Carve
1024 };
1025 #[allow(clippy::cast_possible_wrap)]
1026 let c_i32 = color.unwrap_or(0) as i32;
1027 set_cube_with_colfunc(world, x, y, z, op, move |_, _, _| c_i32);
1028}
1029
1030#[allow(
1032 clippy::cast_possible_truncation,
1033 clippy::cast_possible_wrap,
1034 clippy::cast_sign_loss
1035)]
1036pub fn set_cube_with_colfunc<F>(world: &mut Vxl, x: i32, y: i32, z: i32, op: SpanOp, colfunc: F)
1037where
1038 F: FnMut(i32, i32, i32) -> i32,
1039{
1040 let vsid = world.vsid as i32;
1041 if x < 0 || x >= vsid || y < 0 || y >= vsid || !(0..MAXZDIM).contains(&z) {
1042 return;
1043 }
1044 let span = Vspan {
1045 x: x as u32,
1046 y: y as u32,
1047 z0: z as u8,
1048 z1: z as u8,
1049 };
1050 set_spans_with_colfunc(world, &[span], op, colfunc);
1051}
1052
1053pub fn set_rect(world: &mut Vxl, lo: [i32; 3], hi: [i32; 3], color: Option<u32>) {
1066 let op = if color.is_some() {
1067 SpanOp::Insert
1068 } else {
1069 SpanOp::Carve
1070 };
1071 #[allow(clippy::cast_possible_wrap)]
1072 let c_i32 = color.unwrap_or(0) as i32;
1073 set_rect_with_colfunc(world, lo, hi, op, move |_, _, _| c_i32);
1074}
1075
1076#[allow(
1078 clippy::cast_possible_truncation,
1079 clippy::cast_possible_wrap,
1080 clippy::cast_sign_loss
1081)]
1082pub fn set_rect_with_colfunc<F>(world: &mut Vxl, lo: [i32; 3], hi: [i32; 3], op: SpanOp, colfunc: F)
1083where
1084 F: FnMut(i32, i32, i32) -> i32,
1085{
1086 let vsid = world.vsid as i32;
1087 let xs = lo[0].min(hi[0]).max(0);
1088 let xe = lo[0].max(hi[0]).min(vsid - 1);
1089 let ys = lo[1].min(hi[1]).max(0);
1090 let ye = lo[1].max(hi[1]).min(vsid - 1);
1091 let zs = lo[2].min(hi[2]).max(0);
1092 let ze = lo[2].max(hi[2]).min(MAXZDIM - 1);
1093 if xs > xe || ys > ye || zs > ze {
1094 return;
1095 }
1096 let inserting = op == SpanOp::Insert;
1097 let mut ctx = ScumCtx::new(world);
1098 ctx.set_colfunc(colfunc);
1099 for y in ys..=ye {
1100 for x in xs..=xe {
1101 ctx.with_column(x, y, |b2| {
1102 if inserting {
1103 insslab(b2, zs, ze + 1);
1104 } else {
1105 delslab(b2, zs, ze + 1);
1106 }
1107 });
1108 }
1109 }
1110 ctx.finish();
1111}
1112
1113pub fn set_sphere(world: &mut Vxl, center: [i32; 3], radius: u32, color: Option<u32>) {
1131 let op = if color.is_some() {
1132 SpanOp::Insert
1133 } else {
1134 SpanOp::Carve
1135 };
1136 #[allow(clippy::cast_possible_wrap)]
1137 let c_i32 = color.unwrap_or(0) as i32;
1138 set_sphere_with_colfunc(world, center, radius, op, move |_, _, _| c_i32);
1139}
1140
1141#[allow(
1143 clippy::cast_possible_truncation,
1144 clippy::cast_possible_wrap,
1145 clippy::cast_sign_loss,
1146 clippy::cast_precision_loss,
1147 clippy::similar_names
1148)]
1149pub fn set_sphere_with_colfunc<F>(
1150 world: &mut Vxl,
1151 center: [i32; 3],
1152 radius: u32,
1153 op: SpanOp,
1154 colfunc: F,
1155) where
1156 F: FnMut(i32, i32, i32) -> i32,
1157{
1158 let vsid = world.vsid as i32;
1159 let cx = center[0];
1160 let cy = center[1];
1161 let cz = center[2];
1162 let r = radius as i32;
1163 let xs = (cx - r).max(0);
1164 let xe = (cx + r).min(vsid - 1);
1165 let ys = (cy - r).max(0);
1166 let ye = (cy + r).min(vsid - 1);
1167 let zs = (cz - r).max(0);
1168 let ze = (cz + r).min(MAXZDIM - 1);
1169 if xs > xe || ys > ye || zs > ze {
1170 return;
1171 }
1172 let r_sq = r * r;
1173 let inserting = op == SpanOp::Insert;
1174 let mut ctx = ScumCtx::new(world);
1175 ctx.set_colfunc(colfunc);
1176 for y in ys..=ye {
1177 let dy = y - cy;
1178 let dy_sq = dy * dy;
1179 if dy_sq > r_sq {
1180 continue;
1181 }
1182 for x in xs..=xe {
1183 let dx = x - cx;
1184 let dx_sq = dx * dx;
1185 let xy_sq = dx_sq + dy_sq;
1186 if xy_sq > r_sq {
1187 continue;
1188 }
1189 let dz_max_sq = r_sq - xy_sq;
1192 let dz_max = (dz_max_sq as f32).sqrt() as i32;
1193 let z_lo = (cz - dz_max).max(zs);
1194 let z_hi = (cz + dz_max).min(ze);
1195 if z_lo > z_hi {
1196 continue;
1197 }
1198 ctx.with_column(x, y, |b2| {
1199 if inserting {
1200 insslab(b2, z_lo, z_hi + 1);
1201 } else {
1202 delslab(b2, z_lo, z_hi + 1);
1203 }
1204 });
1205 }
1206 }
1207 ctx.finish();
1208}
1209
1210#[cfg(test)]
1211#[allow(
1212 clippy::cast_possible_truncation,
1213 clippy::cast_possible_wrap,
1214 clippy::cast_sign_loss,
1215 clippy::items_after_statements
1216)]
1217mod tests {
1218 use super::*;
1219
1220 fn build_b2(slabs: &[(i32, i32)]) -> Vec<i32> {
1224 let mut buf: Vec<i32> = Vec::new();
1225 for &(top, bot) in slabs {
1226 assert!(top < bot, "slab top must be < bot");
1227 assert!(bot < MAXZDIM, "slab bot must fit below MAXZDIM");
1228 buf.push(top);
1229 buf.push(bot);
1230 }
1231 buf.push(MAXZDIM);
1234 buf.push(MAXZDIM);
1235 buf.resize(buf.len() + 32, 0);
1237 buf
1238 }
1239
1240 fn read_slabs(b2: &[i32]) -> Vec<(i32, i32)> {
1242 let mut out = Vec::new();
1243 let mut i = 0;
1244 while b2[i + 1] < MAXZDIM {
1245 out.push((b2[i], b2[i + 1]));
1246 i += 2;
1247 }
1248 out
1249 }
1250
1251 #[test]
1254 fn delslab_noop_y0_ge_y1() {
1255 let mut b2 = build_b2(&[(10, 20)]);
1256 delslab(&mut b2, 15, 15);
1257 assert_eq!(read_slabs(&b2), [(10, 20)]);
1258 delslab(&mut b2, 20, 10);
1259 assert_eq!(read_slabs(&b2), [(10, 20)]);
1260 }
1261
1262 #[test]
1263 fn delslab_split_inside_one_slab() {
1264 let mut b2 = build_b2(&[(10, 30)]);
1265 delslab(&mut b2, 15, 20);
1266 assert_eq!(read_slabs(&b2), [(10, 15), (20, 30)]);
1267 }
1268
1269 #[test]
1270 fn delslab_shrink_bot_of_slab() {
1271 let mut b2 = build_b2(&[(10, 30)]);
1272 delslab(&mut b2, 20, 30);
1273 assert_eq!(read_slabs(&b2), [(10, 20)]);
1274 }
1275
1276 #[test]
1277 fn delslab_shrink_top_of_slab() {
1278 let mut b2 = build_b2(&[(10, 30)]);
1279 delslab(&mut b2, 5, 15);
1280 assert_eq!(read_slabs(&b2), [(15, 30)]);
1281 }
1282
1283 #[test]
1284 fn delslab_carve_full_slab() {
1285 let mut b2 = build_b2(&[(10, 30)]);
1286 delslab(&mut b2, 5, 35);
1287 assert_eq!(read_slabs(&b2), Vec::<(i32, i32)>::new());
1288 }
1289
1290 #[test]
1291 fn delslab_in_air_noop() {
1292 let mut b2 = build_b2(&[(10, 30)]);
1293 delslab(&mut b2, 0, 8);
1294 assert_eq!(read_slabs(&b2), [(10, 30)]);
1295 delslab(&mut b2, 35, 50);
1296 assert_eq!(read_slabs(&b2), [(10, 30)]);
1297 }
1298
1299 #[test]
1300 fn delslab_span_two_slabs_carve_middle() {
1301 let mut b2 = build_b2(&[(10, 30), (50, 70)]);
1302 delslab(&mut b2, 20, 60);
1303 assert_eq!(read_slabs(&b2), [(10, 20), (60, 70)]);
1304 }
1305
1306 #[test]
1307 fn delslab_carve_two_full_slabs_keep_third() {
1308 let mut b2 = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1309 delslab(&mut b2, 5, 45);
1310 assert_eq!(read_slabs(&b2), [(50, 60)]);
1311 }
1312
1313 #[test]
1314 fn delslab_y1_clamped_to_maxzdim_minus_1() {
1315 let mut b2 = build_b2(&[(10, 200)]);
1316 delslab(&mut b2, 100, MAXZDIM);
1317 assert_eq!(read_slabs(&b2), [(10, 100)]);
1318 }
1319
1320 #[test]
1321 fn delslab_carve_top_edge_of_slab() {
1322 let mut b2 = build_b2(&[(10, 30)]);
1325 delslab(&mut b2, 5, 10);
1326 assert_eq!(read_slabs(&b2), [(10, 30)]);
1327 }
1328
1329 #[test]
1330 fn delslab_carve_bot_edge_of_slab() {
1331 let mut b2 = build_b2(&[(10, 30)]);
1333 delslab(&mut b2, 30, 35);
1334 assert_eq!(read_slabs(&b2), [(10, 30)]);
1335 }
1336
1337 #[test]
1338 fn delslab_carve_exact_full_slab_keeps_neighbors() {
1339 let mut b2 = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1340 delslab(&mut b2, 30, 40);
1341 assert_eq!(read_slabs(&b2), [(10, 20), (50, 60)]);
1342 }
1343
1344 #[test]
1347 fn insslab_noop_y0_ge_y1() {
1348 let mut b2 = build_b2(&[(10, 20)]);
1349 insslab(&mut b2, 15, 15);
1350 assert_eq!(read_slabs(&b2), [(10, 20)]);
1351 insslab(&mut b2, 20, 10);
1352 assert_eq!(read_slabs(&b2), [(10, 20)]);
1353 }
1354
1355 #[test]
1356 fn insslab_into_pure_air() {
1357 let mut b2 = build_b2(&[]);
1358 insslab(&mut b2, 10, 30);
1359 assert_eq!(read_slabs(&b2), [(10, 30)]);
1360 }
1361
1362 #[test]
1363 fn insslab_into_air_gap_above_slab() {
1364 let mut b2 = build_b2(&[(50, 70)]);
1365 insslab(&mut b2, 10, 30);
1366 assert_eq!(read_slabs(&b2), [(10, 30), (50, 70)]);
1367 }
1368
1369 #[test]
1370 fn insslab_into_air_gap_between_slabs() {
1371 let mut b2 = build_b2(&[(10, 20), (60, 70)]);
1372 insslab(&mut b2, 30, 50);
1373 assert_eq!(read_slabs(&b2), [(10, 20), (30, 50), (60, 70)]);
1374 }
1375
1376 #[test]
1377 fn insslab_into_air_gap_below_all_slabs() {
1378 let mut b2 = build_b2(&[(10, 20)]);
1379 insslab(&mut b2, 30, 50);
1380 assert_eq!(read_slabs(&b2), [(10, 20), (30, 50)]);
1381 }
1382
1383 #[test]
1384 fn insslab_extend_top_of_slab() {
1385 let mut b2 = build_b2(&[(50, 70)]);
1386 insslab(&mut b2, 30, 60);
1387 assert_eq!(read_slabs(&b2), [(30, 70)]);
1388 }
1389
1390 #[test]
1391 fn insslab_extend_bot_of_slab() {
1392 let mut b2 = build_b2(&[(50, 70)]);
1393 insslab(&mut b2, 60, 80);
1394 assert_eq!(read_slabs(&b2), [(50, 80)]);
1395 }
1396
1397 #[test]
1398 fn insslab_touch_top_merges() {
1399 let mut b2 = build_b2(&[(50, 70)]);
1401 insslab(&mut b2, 30, 50);
1402 assert_eq!(read_slabs(&b2), [(30, 70)]);
1403 }
1404
1405 #[test]
1406 fn insslab_touch_bot_merges() {
1407 let mut b2 = build_b2(&[(50, 70)]);
1409 insslab(&mut b2, 70, 80);
1410 assert_eq!(read_slabs(&b2), [(50, 80)]);
1411 }
1412
1413 #[test]
1414 fn insslab_merge_two_slabs() {
1415 let mut b2 = build_b2(&[(10, 30), (50, 70)]);
1416 insslab(&mut b2, 20, 60);
1417 assert_eq!(read_slabs(&b2), [(10, 70)]);
1418 }
1419
1420 #[test]
1421 fn insslab_engulf_inner_slabs() {
1422 let mut b2 = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1423 insslab(&mut b2, 5, 70);
1424 assert_eq!(read_slabs(&b2), [(5, 70)]);
1425 }
1426
1427 #[test]
1428 fn insslab_engulf_then_keep_lower() {
1429 let mut b2 = build_b2(&[(10, 20), (30, 40), (60, 80)]);
1430 insslab(&mut b2, 5, 50);
1431 assert_eq!(read_slabs(&b2), [(5, 50), (60, 80)]);
1432 }
1433
1434 #[test]
1435 fn insslab_engulf_then_merge_lower() {
1436 let mut b2 = build_b2(&[(10, 20), (30, 40), (60, 80)]);
1437 insslab(&mut b2, 5, 60);
1438 assert_eq!(read_slabs(&b2), [(5, 80)]);
1439 }
1440
1441 #[test]
1442 fn insslab_chain_of_touching_inserts() {
1443 let mut b2 = build_b2(&[]);
1444 insslab(&mut b2, 10, 20);
1445 insslab(&mut b2, 20, 30);
1446 insslab(&mut b2, 30, 40);
1447 assert_eq!(read_slabs(&b2), [(10, 40)]);
1448 }
1449
1450 #[test]
1451 fn insslab_carve_then_insert_round_trip() {
1452 let original = [(10, 50)];
1455 let mut b2 = build_b2(&original);
1456 delslab(&mut b2, 20, 30);
1457 assert_eq!(read_slabs(&b2), [(10, 20), (30, 50)]);
1458 insslab(&mut b2, 20, 30);
1459 assert_eq!(read_slabs(&b2), original);
1460 }
1461
1462 #[test]
1463 fn insslab_into_sentinel_only_buffer_with_z_advance() {
1464 let mut b2 = build_b2(&[(10, 20)]);
1466 insslab(&mut b2, 100, 150);
1467 assert_eq!(read_slabs(&b2), [(10, 20), (100, 150)]);
1468 }
1469
1470 fn read_uind(uind: &[i32]) -> Vec<(i32, i32)> {
1476 let mut out = Vec::new();
1477 let mut i = 0;
1478 while uind[i + 1] < MAXZDIM {
1479 out.push((uind[i], uind[i + 1]));
1480 i += 2;
1481 }
1482 out.push((uind[i], uind[i + 1]));
1484 out
1485 }
1486
1487 #[test]
1488 fn expandrle_single_slab_fully_solid_column() {
1489 let z1c = u8::try_from(MAXZDIM - 1).expect("MAXZDIM-1 fits in u8");
1493 let mut slab = vec![0u8, 0, z1c, 0];
1494 slab.extend(std::iter::repeat_n(0u8, (MAXZDIM as usize) * 4));
1495 let mut uind = vec![0i32; 16];
1496 expandrle(&slab, &mut uind);
1497 assert_eq!(uind[0], 0);
1499 assert_eq!(uind[1], MAXZDIM);
1500 }
1501
1502 #[test]
1503 fn expandrle_single_slab_partial_floor() {
1504 let slab = [0u8, 64, 66, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0];
1508 let mut uind = vec![0i32; 16];
1509 expandrle(&slab, &mut uind);
1510 assert_eq!(uind[0], 64);
1511 assert_eq!(uind[1], MAXZDIM);
1512 }
1513
1514 #[test]
1515 fn expandrle_two_slabs_with_cave() {
1516 let slab = [
1535 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 50, 52, 30, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0, 0, ];
1542 let mut uind = vec![0i32; 16];
1543 expandrle(&slab, &mut uind);
1544 assert_eq!(uind[0], 10);
1545 assert_eq!(uind[1], 30);
1546 assert_eq!(uind[2], 50);
1547 assert_eq!(uind[3], MAXZDIM);
1548 }
1549
1550 #[test]
1551 fn expandrle_skips_degenerate_slab_with_no_ceiling_gap() {
1552 let slab = [
1560 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 20, 22, 20, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0, 0, ];
1565 let mut uind = vec![0i32; 16];
1566 expandrle(&slab, &mut uind);
1567 assert_eq!(uind[0], 10);
1569 assert_eq!(uind[1], MAXZDIM);
1570 }
1571
1572 #[test]
1573 fn expandrle_round_trips_through_b2_helpers() {
1574 let slab = [
1578 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 50, 52, 30, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0,
1579 0,
1580 ];
1581 let mut uind = vec![0i32; 16];
1582 expandrle(&slab, &mut uind);
1583 let runs = read_uind(&uind[..4]);
1584 assert_eq!(runs, [(10, 30), (50, MAXZDIM)]);
1585 }
1586
1587 fn all_air_neighbor() -> Vec<i32> {
1593 let mut buf = vec![MAXZDIM, MAXZDIM];
1595 buf.resize(buf.len() + MAXZDIM as usize, MAXZDIM);
1596 buf
1597 }
1598
1599 fn b2_from_runs(runs: &[(i32, i32)]) -> Vec<i32> {
1603 let mut buf = Vec::new();
1604 for &(top, bot) in runs {
1605 buf.push(top);
1606 buf.push(bot);
1607 }
1608 buf.push(MAXZDIM);
1609 buf.push(MAXZDIM);
1610 buf.resize(buf.len() + MAXZDIM as usize, MAXZDIM);
1611 buf
1612 }
1613
1614 #[test]
1615 fn build_color_table_single_slab_one_floor_voxel() {
1616 let slab = [0u8, 10, 10, 0, 0xa1, 0xa2, 0xa3, 0xa4];
1618 let table = build_color_table(&slab);
1619 assert_eq!(table.len(), 2);
1620 assert_eq!(table[0].z_start, 10);
1621 assert_eq!(table[0].z_end, 11);
1622 assert_eq!(table[0].colors, &[0xa1, 0xa2, 0xa3, 0xa4]);
1623 assert_eq!(table[1].z_start, MAXZDIM);
1625 assert_eq!(table[1].z_end, MAXZDIM);
1626 }
1627
1628 #[test]
1629 fn build_color_table_two_slabs_with_ceiling() {
1630 let slab = [
1635 4u8, 10, 10, 0, 0xf0, 0xf0, 0xf0, 0xf0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc1, 0u8, 50, 52, 30, 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, ];
1644 let table = build_color_table(&slab);
1645 assert_eq!(table.len(), 4);
1646 assert_eq!(table[0].z_start, 10);
1648 assert_eq!(table[0].z_end, 11);
1649 assert_eq!(table[0].colors.len(), 4);
1650 assert_eq!(table[1].z_start, 28);
1652 assert_eq!(table[1].z_end, 30);
1653 assert_eq!(
1654 table[1].colors,
1655 &[0xc0, 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc1]
1656 );
1657 assert_eq!(table[2].z_start, 50);
1659 assert_eq!(table[2].z_end, 53);
1660 assert_eq!(table[2].colors.len(), 12);
1661 assert_eq!(table[3].z_start, MAXZDIM);
1663 }
1664
1665 #[test]
1666 fn compilerle_round_trip_single_slab_solid_to_maxzdim() {
1667 let mut slab = vec![0u8, 10, (MAXZDIM - 1) as u8, 0];
1671 for z in 10..MAXZDIM {
1672 slab.extend_from_slice(&[z as u8, (z + 1) as u8, (z + 2) as u8, 0]);
1674 }
1675
1676 let mut b2 = vec![0i32; (MAXZDIM as usize) + 4];
1678 expandrle(&slab, &mut b2);
1679 assert_eq!(b2[0], 10);
1680 assert_eq!(b2[1], MAXZDIM);
1681
1682 let n_air = all_air_neighbor();
1684 let mut cbuf = vec![0u8; 1028];
1685 let mut colfunc_called = 0;
1686 let mut colfunc = |_x: i32, _y: i32, _z: i32| -> i32 {
1687 colfunc_called += 1;
1688 0
1689 };
1690 let written = compilerle(
1691 &b2,
1692 &n_air,
1693 &n_air,
1694 &n_air,
1695 &n_air,
1696 &mut cbuf,
1697 &slab,
1698 0,
1699 0,
1700 &mut colfunc,
1701 );
1702 assert_eq!(colfunc_called, 0, "all colors should come from tbuf2");
1703
1704 assert_eq!(written, slab.len());
1707 assert_eq!(&cbuf[..written], &slab[..]);
1708
1709 let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
1711 expandrle(&cbuf[..written], &mut b2_round);
1712 assert_eq!(b2_round[0], 10);
1713 assert_eq!(b2_round[1], MAXZDIM);
1714 }
1715
1716 #[test]
1717 fn compilerle_round_trip_two_solid_runs_with_cave() {
1718 let dummy = vec![0u8, 0, (MAXZDIM - 1) as u8, 0];
1728 let mut dummy_full = dummy;
1729 dummy_full.extend(std::iter::repeat_n(0u8, (MAXZDIM as usize) * 4));
1730
1731 let n_air = all_air_neighbor();
1732 let b2 = b2_from_runs(&[(10, 30), (50, MAXZDIM)]);
1733 let mut seed = vec![0u8; 1028];
1734 let mut colfunc = |_x: i32, _y: i32, z: i32| -> i32 { z };
1735 let seed_len = compilerle(
1736 &b2,
1737 &n_air,
1738 &n_air,
1739 &n_air,
1740 &n_air,
1741 &mut seed,
1742 &dummy_full,
1743 0,
1744 0,
1745 &mut colfunc,
1746 );
1747 seed.truncate(seed_len);
1748
1749 let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
1751 expandrle(&seed, &mut b2_round);
1752 assert_eq!(b2_round[0], 10);
1754 assert_eq!(b2_round[1], 30);
1755 assert_eq!(b2_round[2], 50);
1756 assert_eq!(b2_round[3], MAXZDIM);
1757
1758 let mut cbuf = vec![0u8; 1028];
1762 let mut never_called = 0;
1763 let mut colfunc2 = |_x: i32, _y: i32, _z: i32| -> i32 {
1764 never_called += 1;
1765 0
1766 };
1767 let written = compilerle(
1768 &b2,
1769 &n_air,
1770 &n_air,
1771 &n_air,
1772 &n_air,
1773 &mut cbuf,
1774 &seed,
1775 0,
1776 0,
1777 &mut colfunc2,
1778 );
1779 assert_eq!(never_called, 0, "second pass needs no colfunc");
1780 assert_eq!(written, seed_len);
1781 assert_eq!(&cbuf[..written], &seed[..]);
1782 }
1783
1784 #[test]
1785 fn compilerle_buried_voxel_optimization_with_all_solid_neighbors() {
1786 let b2 = b2_from_runs(&[(10, MAXZDIM)]);
1794 let n_solid = b2_from_runs(&[(0, MAXZDIM)]);
1795 let mut slab = vec![0u8, 10, (MAXZDIM - 1) as u8, 0];
1799 for z in 10..MAXZDIM {
1800 slab.extend_from_slice(&[z as u8, 0, 0, 0]);
1801 }
1802 let mut cbuf = vec![0u8; 1028];
1803 let mut colfunc_called = 0;
1804 let mut colfunc = |_x: i32, _y: i32, _z: i32| -> i32 {
1805 colfunc_called += 1;
1806 0
1807 };
1808 let written = compilerle(
1809 &b2,
1810 &n_solid,
1811 &n_solid,
1812 &n_solid,
1813 &n_solid,
1814 &mut cbuf,
1815 &slab,
1816 0,
1817 0,
1818 &mut colfunc,
1819 );
1820 assert_eq!(colfunc_called, 0, "tbuf2 should cover every voxel");
1821 assert_eq!(written, 8);
1824 assert_eq!(cbuf[0], 0); assert_eq!(cbuf[1], 10); assert_eq!(cbuf[2], 10); assert_eq!(cbuf[3], 0); let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
1831 expandrle(&cbuf[..written], &mut b2_round);
1832 assert_eq!(b2_round[0], 10);
1833 assert_eq!(b2_round[1], MAXZDIM);
1834 }
1835
1836 fn build_1x1_min_solid_vxl() -> Vxl {
1841 let column = vec![0u8, 0, 0, 0, 0xff, 0x80, 0x40, 0x20];
1842 let column_offset = vec![0u32, column.len() as u32].into_boxed_slice();
1843 Vxl {
1844 vsid: 1,
1845 ipo: [0.0; 3],
1846 ist: [1.0, 0.0, 0.0],
1847 ihe: [0.0, 0.0, 1.0],
1848 ifo: [0.0, 1.0, 0.0],
1849 data: column.into_boxed_slice(),
1850 column_offset,
1851 mip_base_offsets: Box::new([0, 2]),
1852 vbit: Box::new([]),
1853 vbiti: 0,
1854 }
1855 }
1856
1857 #[test]
1858 fn scum2_no_edit_round_trip_1x1_minimal_column() {
1859 let mut vxl = build_1x1_min_solid_vxl();
1863 vxl.reserve_edit_capacity(4096);
1864
1865 let mut ctx = ScumCtx::new(&mut vxl);
1866 let _b2 = ctx.scum2(0, 0).expect("column 0,0 in bounds");
1867 ctx.finish();
1868
1869 let column = vxl.column_data(0);
1870 let mut b2_after = vec![0i32; SCPITCH];
1871 expandrle(column, &mut b2_after);
1872 assert_eq!(b2_after[0], 0);
1873 assert_eq!(b2_after[1], MAXZDIM);
1874 }
1875
1876 #[test]
1877 fn scum2_carve_edit_1x1_creates_air_gap() {
1878 let mut vxl = build_1x1_min_solid_vxl();
1881 vxl.reserve_edit_capacity(4096);
1882
1883 let mut ctx = ScumCtx::new(&mut vxl);
1884 ctx.set_colfunc(|_x, _y, _z| 0x80_60_40_20u32 as i32);
1885 {
1886 let b2 = ctx.scum2(0, 0).expect("column 0,0 in bounds");
1887 delslab(b2, 50, 100);
1889 }
1890 ctx.finish();
1891
1892 let column = vxl.column_data(0);
1893 let mut b2_after = vec![0i32; SCPITCH];
1894 expandrle(column, &mut b2_after);
1895 assert_eq!(b2_after[0], 0);
1897 assert_eq!(b2_after[1], 50);
1898 assert_eq!(b2_after[2], 100);
1899 assert_eq!(b2_after[3], MAXZDIM);
1900 }
1901
1902 fn build_4x4_min_solid_vxl() -> Vxl {
1905 const COL: [u8; 8] = [0, 0, 0, 0, 0xff, 0x80, 0x40, 0x20];
1906 let mut data = Vec::with_capacity(16 * 8);
1907 let mut offsets = Vec::with_capacity(17);
1908 for i in 0..16 {
1909 offsets.push((i * 8) as u32);
1910 data.extend_from_slice(&COL);
1911 }
1912 offsets.push((16 * 8) as u32);
1913 Vxl {
1914 vsid: 4,
1915 ipo: [0.0; 3],
1916 ist: [1.0, 0.0, 0.0],
1917 ihe: [0.0, 0.0, 1.0],
1918 ifo: [0.0, 1.0, 0.0],
1919 data: data.into_boxed_slice(),
1920 column_offset: offsets.into_boxed_slice(),
1921 mip_base_offsets: Box::new([0, 17]),
1922 vbit: Box::new([]),
1923 vbiti: 0,
1924 }
1925 }
1926
1927 #[test]
1928 fn scum2_batch_edits_multiple_columns_same_row() {
1929 let mut vxl = build_4x4_min_solid_vxl();
1932 vxl.reserve_edit_capacity(8192);
1933
1934 let mut ctx = ScumCtx::new(&mut vxl);
1935 ctx.set_colfunc(|_x, _y, _z| 0);
1936 {
1937 let b2 = ctx.scum2(1, 2).unwrap();
1938 delslab(b2, 50, 100);
1939 }
1940 {
1941 let b2 = ctx.scum2(2, 2).unwrap();
1942 delslab(b2, 50, 100);
1943 }
1944 ctx.finish();
1945
1946 for x in [1, 2] {
1947 let idx = 2 * 4 + x;
1948 let mut b2_after = vec![0i32; SCPITCH];
1949 expandrle(vxl.column_data(idx), &mut b2_after);
1950 assert_eq!(b2_after[0], 0);
1951 assert_eq!(b2_after[1], 50);
1952 assert_eq!(b2_after[2], 100);
1953 assert_eq!(b2_after[3], MAXZDIM);
1954 }
1955 for x in [0, 3] {
1957 let idx = 2 * 4 + x;
1958 let mut b2_after = vec![0i32; SCPITCH];
1959 expandrle(vxl.column_data(idx), &mut b2_after);
1960 assert_eq!(b2_after[0], 0);
1961 assert_eq!(b2_after[1], MAXZDIM);
1962 }
1963 }
1964
1965 #[test]
1966 fn scum2_batch_edits_across_rows() {
1967 let mut vxl = build_4x4_min_solid_vxl();
1970 vxl.reserve_edit_capacity(8192);
1971
1972 let mut ctx = ScumCtx::new(&mut vxl);
1973 ctx.set_colfunc(|_x, _y, _z| 0);
1974 {
1975 let b2 = ctx.scum2(1, 1).unwrap();
1976 delslab(b2, 60, 80);
1977 }
1978 {
1979 let b2 = ctx.scum2(1, 2).unwrap();
1980 delslab(b2, 60, 80);
1981 }
1982 ctx.finish();
1983
1984 for y in [1, 2] {
1985 let idx = y * 4 + 1;
1986 let mut b2_after = vec![0i32; SCPITCH];
1987 expandrle(vxl.column_data(idx), &mut b2_after);
1988 assert_eq!(b2_after[0], 0);
1989 assert_eq!(b2_after[1], 60);
1990 assert_eq!(b2_after[2], 80);
1991 assert_eq!(b2_after[3], MAXZDIM);
1992 }
1993 }
1994
1995 #[test]
1996 fn scum2_finish_without_any_edit_is_noop() {
1997 let mut vxl = build_1x1_min_solid_vxl();
1999 vxl.reserve_edit_capacity(4096);
2000 let original = vxl.column_data(0).to_vec();
2001 let ctx = ScumCtx::new(&mut vxl);
2002 ctx.finish();
2003 assert_eq!(vxl.column_data(0), &original[..]);
2004 }
2005
2006 #[test]
2007 fn scum2_returns_none_for_out_of_bounds() {
2008 let mut vxl = build_1x1_min_solid_vxl();
2009 vxl.reserve_edit_capacity(4096);
2010 let mut ctx = ScumCtx::new(&mut vxl);
2011 assert!(ctx.scum2(-1, 0).is_none());
2012 assert!(ctx.scum2(0, -1).is_none());
2013 assert!(ctx.scum2(1, 0).is_none());
2014 assert!(ctx.scum2(0, 1).is_none());
2015 ctx.finish();
2016 }
2017
2018 #[test]
2021 fn set_spans_empty_is_noop() {
2022 let mut vxl = build_1x1_min_solid_vxl();
2023 let original = vxl.column_data(0).to_vec();
2024 set_spans(&mut vxl, &[], None);
2025 assert_eq!(vxl.column_data(0), &original[..]);
2029 }
2030
2031 #[test]
2032 fn set_spans_single_carve_creates_air_gap() {
2033 let mut vxl = build_1x1_min_solid_vxl();
2034 vxl.reserve_edit_capacity(4096);
2035 set_spans(
2036 &mut vxl,
2037 &[Vspan {
2038 x: 0,
2039 y: 0,
2040 z0: 50,
2041 z1: 99,
2042 }],
2043 None,
2044 );
2045 let mut b2 = vec![0i32; SCPITCH];
2046 expandrle(vxl.column_data(0), &mut b2);
2047 assert_eq!(b2[0], 0);
2049 assert_eq!(b2[1], 50);
2050 assert_eq!(b2[2], 100);
2051 assert_eq!(b2[3], MAXZDIM);
2052 }
2053
2054 #[test]
2055 fn set_spans_multi_span_same_column_accumulates() {
2056 let mut vxl = build_1x1_min_solid_vxl();
2060 vxl.reserve_edit_capacity(4096);
2061 set_spans(
2062 &mut vxl,
2063 &[
2064 Vspan {
2065 x: 0,
2066 y: 0,
2067 z0: 30,
2068 z1: 49,
2069 },
2070 Vspan {
2071 x: 0,
2072 y: 0,
2073 z0: 100,
2074 z1: 119,
2075 },
2076 ],
2077 None,
2078 );
2079 let mut b2 = vec![0i32; SCPITCH];
2080 expandrle(vxl.column_data(0), &mut b2);
2081 assert_eq!(b2[0], 0);
2083 assert_eq!(b2[1], 30);
2084 assert_eq!(b2[2], 50);
2085 assert_eq!(b2[3], 100);
2086 assert_eq!(b2[4], 120);
2087 assert_eq!(b2[5], MAXZDIM);
2088 }
2089
2090 #[test]
2091 fn set_spans_insert_color_fills_air() {
2092 let mut vxl = build_1x1_min_solid_vxl();
2095 vxl.reserve_edit_capacity(4096);
2096 set_spans(
2098 &mut vxl,
2099 &[Vspan {
2100 x: 0,
2101 y: 0,
2102 z0: 50,
2103 z1: 99,
2104 }],
2105 None,
2106 );
2107 const FILL: u32 = 0x80_aa_bb_cc;
2109 set_spans(
2110 &mut vxl,
2111 &[Vspan {
2112 x: 0,
2113 y: 0,
2114 z0: 60,
2115 z1: 79,
2116 }],
2117 Some(FILL),
2118 );
2119 let mut b2 = vec![0i32; SCPITCH];
2120 expandrle(vxl.column_data(0), &mut b2);
2121 assert_eq!(b2[0], 0);
2123 assert_eq!(b2[1], 50);
2124 assert_eq!(b2[2], 60);
2125 assert_eq!(b2[3], 80);
2126 assert_eq!(b2[4], 100);
2127 assert_eq!(b2[5], MAXZDIM);
2128 }
2129
2130 #[test]
2131 fn set_spans_skips_out_of_bounds_silently() {
2132 let mut vxl = build_1x1_min_solid_vxl();
2133 vxl.reserve_edit_capacity(4096);
2134 set_spans(
2135 &mut vxl,
2136 &[Vspan {
2137 x: 7,
2138 y: 9,
2139 z0: 50,
2140 z1: 99,
2141 }],
2142 None,
2143 );
2144 let mut b2 = vec![0i32; SCPITCH];
2146 expandrle(vxl.column_data(0), &mut b2);
2147 assert_eq!(b2[0], 0);
2148 assert_eq!(b2[1], MAXZDIM);
2149 }
2150
2151 #[test]
2152 fn set_spans_with_colfunc_z_dependent_colour() {
2153 let mut vxl = build_4x4_min_solid_vxl();
2160 vxl.reserve_edit_capacity(8192);
2161 let carve_spans: Vec<Vspan> = (0..4)
2164 .flat_map(|y| {
2165 (0..4).map(move |x| Vspan {
2166 x,
2167 y,
2168 z0: 50,
2169 z1: 99,
2170 })
2171 })
2172 .collect();
2173 set_spans(&mut vxl, &carve_spans, None);
2174 set_spans_with_colfunc(
2177 &mut vxl,
2178 &[Vspan {
2179 x: 1,
2180 y: 1,
2181 z0: 60,
2182 z1: 79,
2183 }],
2184 SpanOp::Insert,
2185 |_x, _y, z| (0x80ff_ff00u32 as i32) | z,
2186 );
2187 let idx = 4 + 1; let column = vxl.column_data(idx);
2191 let mut v = 0usize;
2192 let mut found = false;
2193 loop {
2194 let nextptr = column[v];
2195 let z1 = column[v + 1];
2196 if z1 == 60 {
2197 let z1c = column[v + 2];
2198 assert_eq!(z1c, 79, "z1c");
2199 let n_voxels = usize::from(z1c) - usize::from(z1) + 1;
2200 for i in 0..n_voxels {
2201 let off = v + 4 + i * 4;
2202 let c = u32::from_le_bytes([
2203 column[off],
2204 column[off + 1],
2205 column[off + 2],
2206 column[off + 3],
2207 ]);
2208 let z = u32::from(z1) + (i as u32);
2209 assert_eq!(
2210 c,
2211 0x80ff_ff00 | z,
2212 "z={z}: expected colour {:#010x}, got {:#010x}",
2213 0x80ff_ff00 | z,
2214 c
2215 );
2216 }
2217 found = true;
2218 break;
2219 }
2220 if nextptr == 0 {
2221 break;
2222 }
2223 v += usize::from(nextptr) * 4;
2224 }
2225 assert!(found, "did not find a slab with z1=60");
2226 }
2227
2228 #[test]
2231 fn set_cube_carves_single_voxel() {
2232 let mut vxl = build_4x4_min_solid_vxl();
2233 vxl.reserve_edit_capacity(4096);
2234 set_cube(&mut vxl, 1, 1, 100, None);
2235 let mut b2 = vec![0i32; SCPITCH];
2236 expandrle(vxl.column_data(4 + 1), &mut b2);
2237 assert_eq!(b2[0], 0);
2239 assert_eq!(b2[1], 100);
2240 assert_eq!(b2[2], 101);
2241 assert_eq!(b2[3], MAXZDIM);
2242 }
2243
2244 #[test]
2245 fn set_cube_skips_oob() {
2246 let mut vxl = build_4x4_min_solid_vxl();
2247 vxl.reserve_edit_capacity(4096);
2248 set_cube(&mut vxl, -1, 1, 100, None);
2250 set_cube(&mut vxl, 5, 1, 100, None);
2251 set_cube(&mut vxl, 1, 1, 256, None);
2252 let mut b2 = vec![0i32; SCPITCH];
2254 expandrle(vxl.column_data(4 + 1), &mut b2);
2255 assert_eq!(b2[0], 0);
2256 assert_eq!(b2[1], MAXZDIM);
2257 }
2258
2259 #[test]
2260 fn set_rect_carves_aabb() {
2261 let mut vxl = build_4x4_min_solid_vxl();
2262 vxl.reserve_edit_capacity(8192);
2263 set_rect(&mut vxl, [1, 1, 50], [2, 2, 99], None);
2265 for y in 1..=2 {
2266 for x in 1..=2 {
2267 let idx = (y * 4 + x) as usize;
2268 let mut b2 = vec![0i32; SCPITCH];
2269 expandrle(vxl.column_data(idx), &mut b2);
2270 assert_eq!(b2[0], 0, "col ({x},{y})");
2271 assert_eq!(b2[1], 50, "col ({x},{y})");
2272 assert_eq!(b2[2], 100, "col ({x},{y})");
2273 assert_eq!(b2[3], MAXZDIM, "col ({x},{y})");
2274 }
2275 }
2276 for &(x, y) in &[(0, 0), (3, 3)] {
2278 let idx = (y * 4 + x) as usize;
2279 let mut b2 = vec![0i32; SCPITCH];
2280 expandrle(vxl.column_data(idx), &mut b2);
2281 assert_eq!(b2[0], 0);
2282 assert_eq!(b2[1], MAXZDIM);
2283 }
2284 }
2285
2286 #[test]
2287 fn set_rect_clamps_to_world() {
2288 let mut vxl = build_4x4_min_solid_vxl();
2289 vxl.reserve_edit_capacity(8192);
2290 set_rect(&mut vxl, [-10, -10, -10], [100, 100, 1000], None);
2293 for idx in 0..16 {
2295 let mut b2 = vec![0i32; SCPITCH];
2296 expandrle(vxl.column_data(idx), &mut b2);
2297 assert_eq!(b2[0], 255, "col {idx}");
2301 assert_eq!(b2[1], MAXZDIM, "col {idx}");
2302 }
2303 }
2304
2305 #[test]
2306 fn set_sphere_carves_centred_sphere() {
2307 let mut vxl = build_4x4_min_solid_vxl();
2310 vxl.reserve_edit_capacity(8192);
2311 set_sphere(&mut vxl, [1, 1, 128], 1, None);
2312 let mut b2 = vec![0i32; SCPITCH];
2316 expandrle(vxl.column_data(4 + 1), &mut b2);
2317 assert_eq!(b2[0], 0);
2318 assert_eq!(b2[1], 127);
2319 assert_eq!(b2[2], 130);
2320 assert_eq!(b2[3], MAXZDIM);
2321 let mut b2 = vec![0i32; SCPITCH];
2323 expandrle(vxl.column_data(4), &mut b2);
2324 assert_eq!(b2[0], 0);
2325 assert_eq!(b2[1], 128);
2326 assert_eq!(b2[2], 129);
2327 assert_eq!(b2[3], MAXZDIM);
2328 }
2329
2330 #[test]
2331 fn set_sphere_radius_zero_is_single_voxel() {
2332 let mut vxl = build_4x4_min_solid_vxl();
2333 vxl.reserve_edit_capacity(4096);
2334 set_sphere(&mut vxl, [1, 1, 100], 0, None);
2335 let mut b2 = vec![0i32; SCPITCH];
2337 expandrle(vxl.column_data(4 + 1), &mut b2);
2338 assert_eq!(b2[0], 0);
2339 assert_eq!(b2[1], 100);
2340 assert_eq!(b2[2], 101);
2341 assert_eq!(b2[3], MAXZDIM);
2342 }
2343
2344 #[test]
2345 fn set_sphere_with_colfunc_position_dependent_color() {
2346 let mut vxl = build_4x4_min_solid_vxl();
2358 vxl.reserve_edit_capacity(8192);
2359 set_rect(&mut vxl, [0, 0, 50], [3, 3, 199], None);
2361 set_sphere_with_colfunc(&mut vxl, [1, 1, 128], 2, SpanOp::Insert, |_, _, z| {
2364 (0x80ff_ff00u32 as i32) | z
2365 });
2366 let mut b2 = vec![0i32; SCPITCH];
2368 expandrle(vxl.column_data(4 + 1), &mut b2);
2369 assert_eq!(b2[0], 0, "b2 first run top");
2370 assert_eq!(b2[1], 50, "b2 first run bot");
2371 assert_eq!(b2[2], 126, "b2 sphere run top");
2372 assert_eq!(b2[3], 131, "b2 sphere run bot");
2373 assert_eq!(b2[4], 200, "b2 third run top");
2374 assert_eq!(b2[5], MAXZDIM, "b2 third run bot");
2375
2376 let column = vxl.column_data(4 + 1).to_vec();
2380 let mut v = 0usize;
2381 let mut top_color = None;
2382 loop {
2383 let nextptr = column[v];
2384 let z1 = column[v + 1];
2385 if z1 == 126 {
2386 let off = v + 4;
2387 top_color = Some(u32::from_le_bytes([
2388 column[off],
2389 column[off + 1],
2390 column[off + 2],
2391 column[off + 3],
2392 ]));
2393 break;
2394 }
2395 if nextptr == 0 {
2396 break;
2397 }
2398 v += usize::from(nextptr) * 4;
2399 }
2400 assert_eq!(
2401 top_color,
2402 Some(0x80ff_ff7e),
2403 "exposed voxel at z=126 should have colfunc-derived colour"
2404 );
2405 }
2406
2407 #[test]
2408 fn set_spans_4x4_batch_carves_each_listed_column() {
2409 let mut vxl = build_4x4_min_solid_vxl();
2411 vxl.reserve_edit_capacity(8192);
2412 let spans: Vec<Vspan> = (0..4)
2413 .flat_map(|y| {
2414 (0..4).map(move |x| Vspan {
2415 x,
2416 y,
2417 z0: 50,
2418 z1: 99,
2419 })
2420 })
2421 .collect();
2422 set_spans(&mut vxl, &spans, None);
2423 for idx in 0..16 {
2425 let mut b2 = vec![0i32; SCPITCH];
2426 expandrle(vxl.column_data(idx), &mut b2);
2427 assert_eq!(b2[0], 0, "col {idx}");
2428 assert_eq!(b2[1], 50, "col {idx}");
2429 assert_eq!(b2[2], 100, "col {idx}");
2430 assert_eq!(b2[3], MAXZDIM, "col {idx}");
2431 }
2432 }
2433}