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 b2[i + 2 - delta] = MAXZDIM;
176 b2[i + 3 - delta] = MAXZDIM;
177 }
178 if y1 > b2[z + 1] {
179 b2[z + 1] = y1;
181 }
182}
183
184pub fn expandrle(slab: &[u8], uind: &mut [i32]) {
197 uind[0] = i32::from(slab[1]);
198 let mut i = 2usize;
199 let mut v = 0usize;
200 while slab[v] != 0 {
201 v += usize::from(slab[v]) * 4;
202 if slab[v + 3] >= slab[v + 1] {
203 continue;
204 }
205 uind[i - 1] = i32::from(slab[v + 3]);
206 uind[i] = i32::from(slab[v + 1]);
207 i += 2;
208 }
209 uind[i - 1] = MAXZDIM;
210}
211
212#[derive(Debug)]
216struct ColorRange<'s> {
217 z_start: i32,
218 z_end: i32,
219 colors: &'s [u8],
222}
223
224fn build_color_table(slab: &[u8]) -> Vec<ColorRange<'_>> {
229 let mut ranges = Vec::new();
230 let mut v = 0usize;
231 loop {
232 let z_start = i32::from(slab[v + 1]);
233 let z1c = i32::from(slab[v + 2]);
234 let z_end = z1c + 1;
235 let n_voxels = usize::try_from((z_end - z_start).max(0)).expect("voxel count >= 0");
236 let off = v + 4;
237 ranges.push(ColorRange {
238 z_start,
239 z_end,
240 colors: &slab[off..off + n_voxels * 4],
241 });
242
243 let nextptr = slab[v];
244 if nextptr == 0 {
245 break;
246 }
247 let prev_v = v;
248 v += usize::from(nextptr) * 4;
249 let ze = i32::from(slab[v + 3]);
250 let prev_z1 = i32::from(slab[prev_v + 1]);
257 let prev_z1c = i32::from(slab[prev_v + 2]);
258 let prev_nextptr = i32::from(slab[prev_v]);
259 let ceil_z_start = ze + prev_z1c - prev_z1 - prev_nextptr + 2;
260 let ceil_z_end = ze;
261 let ceil_n =
262 usize::try_from((ceil_z_end - ceil_z_start).max(0)).expect("ceiling voxel count >= 0");
263 let ceil_start = v - ceil_n * 4;
265 ranges.push(ColorRange {
266 z_start: ceil_z_start,
267 z_end: ceil_z_end,
268 colors: &slab[ceil_start..v],
269 });
270 }
271 ranges.push(ColorRange {
272 z_start: MAXZDIM,
273 z_end: MAXZDIM,
274 colors: &[],
275 });
276 ranges
277}
278
279#[allow(
299 clippy::too_many_arguments,
300 clippy::too_many_lines,
301 clippy::missing_panics_doc
302)]
303pub(crate) fn compilerle(
304 n0: &[i32],
305 n1: &[i32],
306 n2: &[i32],
307 n3: &[i32],
308 n4: &[i32],
309 cbuf: &mut [u8],
310 original_column: &[u8],
311 px: i32,
312 py: i32,
313 colfunc: &mut dyn FnMut(i32, i32, i32) -> i32,
314) -> usize {
315 let tbuf2 = build_color_table(original_column);
316
317 let mut p_z: i32 = n0[0];
318 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
323 let to_u8 = |v: i32| (v & 0xff) as u8;
324
325 cbuf[1] = to_u8(p_z);
326 let mut ze: i32 = n0[1];
327 cbuf[2] = to_u8(ze - 1);
328 cbuf[3] = 0;
329
330 let mut i = 0usize;
331 let mut onext = 0usize;
332 let mut ic = 0usize;
333 let mut ia: i32 = 15;
334 let mut n = 4usize;
335 let mut zend = if ze == MAXZDIM { -1 } else { ze - 1 };
336
337 let mut n1_idx = 0usize;
338 let mut n2_idx = 0usize;
339 let mut n3_idx = 0usize;
340 let mut n4_idx = 0usize;
341
342 'outer: loop {
343 let mut dacnt = 0;
344 'middle: loop {
345 let exit_to_rlendit2 = loop {
347 while p_z >= tbuf2[ic].z_end {
348 ic += 1;
349 }
350 let color: i32 = if p_z >= tbuf2[ic].z_start {
351 let off =
352 usize::try_from((p_z - tbuf2[ic].z_start) * 4).expect("color offset >= 0");
353 let bytes = &tbuf2[ic].colors[off..off + 4];
354 i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
355 } else {
356 colfunc(px, py, p_z)
357 };
358 cbuf[n..n + 4].copy_from_slice(&color.to_le_bytes());
359 n += 4;
360 p_z += 1;
361 if p_z >= ze {
362 break true; }
364 while p_z >= n1[n1_idx] {
365 n1_idx += 1;
366 ia ^= 1;
367 }
368 while p_z >= n2[n2_idx] {
369 n2_idx += 1;
370 ia ^= 2;
371 }
372 while p_z >= n3[n3_idx] {
373 n3_idx += 1;
374 ia ^= 4;
375 }
376 while p_z >= n4[n4_idx] {
377 n4_idx += 1;
378 ia ^= 8;
379 }
380 if !(ia != 0 || p_z == zend) {
381 break false; }
383 };
384
385 if exit_to_rlendit2 {
386 if ze >= MAXZDIM {
387 break 'outer;
388 }
389 i += 2;
390 cbuf[onext] = u8::try_from((n - onext) >> 2).expect("slab dword count fits in u8");
391 onext = n;
392 p_z = n0[i];
393 cbuf[n + 1] = to_u8(p_z);
394 cbuf[n + 3] = to_u8(ze);
395 ze = n0[i + 1];
396 cbuf[n + 2] = to_u8(ze - 1);
397 n += 4;
398 zend = if ze == MAXZDIM { -1 } else { ze - 1 };
399 break 'middle; }
401
402 if dacnt == 0 {
405 cbuf[onext + 2] = to_u8(p_z - 1);
406 dacnt = 1;
407 } else {
408 cbuf[onext] = u8::try_from((n - onext) >> 2).expect("slab dword count fits in u8");
409 onext = n;
410 cbuf[n + 1] = to_u8(p_z);
411 cbuf[n + 2] = to_u8(p_z - 1);
412 cbuf[n + 3] = to_u8(p_z);
413 n += 4;
414 }
415
416 let n1_v = n1[n1_idx];
418 let n2_v = n2[n2_idx];
419 let n3_v = n3[n3_idx];
420 let n4_v = n4[n4_idx];
421 if n1_v < n2_v && n1_v < n3_v && n1_v < n4_v {
422 if n1_v >= ze {
423 p_z = ze - 1;
424 } else {
425 p_z = n1_v;
426 n1_idx += 1;
427 ia ^= 1;
428 }
429 } else if n2_v < n3_v && n2_v < n4_v {
430 if n2_v >= ze {
431 p_z = ze - 1;
432 } else {
433 p_z = n2_v;
434 n2_idx += 1;
435 ia ^= 2;
436 }
437 } else if n3_v < n4_v {
438 if n3_v >= ze {
439 p_z = ze - 1;
440 } else {
441 p_z = n3_v;
442 n3_idx += 1;
443 ia ^= 4;
444 }
445 } else if n4_v >= ze {
446 p_z = ze - 1;
447 } else {
448 p_z = n4_v;
449 n4_idx += 1;
450 ia ^= 8;
451 }
452
453 if p_z == MAXZDIM - 1 {
454 break 'outer;
455 }
456 }
458 }
459
460 cbuf[onext] = 0;
461 n
462}
463
464use crate::vxl::Vxl;
493
494pub(crate) const SCPITCH: usize = 256;
499
500pub(crate) const MAXCSIZ: usize = 1028;
503
504const SCOY_NONE: i32 = i32::MIN;
507
508const SCOYM3_INITIAL: usize = SCPITCH * 6;
510
511const SCOYM3_WRAP: usize = SCPITCH * 9;
514
515pub struct ScumCtx<'v> {
527 vxl: &'v mut Vxl,
528 radar: Vec<i32>,
530 cbuf: Vec<u8>,
532 colfunc: Box<dyn FnMut(i32, i32, i32) -> i32 + 'v>,
535
536 scoy: i32,
538 scoym3: usize,
539 scx0: i32,
540 scx1: i32,
541 scox0: i32,
542 scox1: i32,
543 scoox0: i32,
544 scoox1: i32,
545 scex0: i32,
546 scex1: i32,
547 sceox0: i32,
548 sceox1: i32,
549
550 last_scum2: Option<(i32, i32)>,
557}
558
559#[allow(
560 clippy::cast_possible_truncation,
561 clippy::cast_possible_wrap,
562 clippy::cast_sign_loss,
563 clippy::if_not_else,
564 clippy::similar_names
565)]
566impl<'v> ScumCtx<'v> {
567 pub fn new(vxl: &'v mut Vxl) -> Self {
575 assert!(
576 !vxl.vbit.is_empty(),
577 "ScumCtx::new requires Vxl::reserve_edit_capacity to be called first"
578 );
579 let radar_size = (vxl.vsid as usize + 4) * 3 * SCPITCH;
580 Self {
581 vxl,
582 radar: vec![0i32; radar_size],
583 cbuf: vec![0u8; MAXCSIZ],
584 colfunc: Box::new(|_, _, _| 0),
585 scoy: SCOY_NONE,
586 scoym3: SCOYM3_INITIAL,
587 scx0: 0,
588 scx1: 0,
589 scox0: 0,
590 scox1: 0,
591 scoox0: 0,
592 scoox1: 0,
593 scex0: 0,
594 scex1: 0,
595 sceox0: 0,
596 sceox1: 0,
597 last_scum2: None,
598 }
599 }
600
601 pub fn set_colfunc<F>(&mut self, f: F)
604 where
605 F: FnMut(i32, i32, i32) -> i32 + 'v,
606 {
607 self.colfunc = Box::new(f);
608 }
609
610 pub fn scum2(&mut self, x: i32, y: i32) -> Option<&mut [i32]> {
616 let vsid = self.vxl.vsid as i32;
617 if x < 0 || x >= vsid || y < 0 || y >= vsid {
618 return None;
619 }
620
621 if y != self.scoy {
622 if self.scoy != SCOY_NONE {
623 self.scum2_line();
624 while self.scoy < y - 1 {
625 self.scx0 = i32::MAX;
626 self.scx1 = i32::MIN;
627 self.advance_row();
628 self.scum2_line();
629 }
630 self.advance_row();
631 } else {
632 self.scoox0 = i32::MAX;
633 self.scox0 = i32::MAX;
634 self.sceox0 = x + 1;
635 self.scex0 = x + 1;
636 self.sceox1 = x;
637 self.scex1 = x;
638 self.scoy = y;
639 self.scoym3 = SCOYM3_INITIAL;
640 }
641 self.scx0 = x;
642 } else {
643 while self.scx1 < x - 1 {
646 self.scx1 += 1;
647 let scx1 = self.scx1;
648 self.expand_column_into_row(scx1, y, self.scoym3);
649 }
650 }
651
652 let radar_idx = self.scoym3 + (x as usize) * SCPITCH * 3;
653 self.scx1 = x;
654 self.expand_column_into_row(x, y, self.scoym3);
655 self.last_scum2 = Some((x, y));
656 Some(&mut self.radar[radar_idx..radar_idx + SCPITCH])
657 }
658
659 pub fn with_column<F>(&mut self, x: i32, y: i32, f: F) -> bool
670 where
671 F: FnOnce(&mut [i32]),
672 {
673 if self.last_scum2 != Some((x, y)) && self.scum2(x, y).is_none() {
674 return false;
675 }
676 let radar_idx = self.scoym3 + (x as usize) * SCPITCH * 3;
679 let b2 = &mut self.radar[radar_idx..radar_idx + SCPITCH];
680 f(b2);
681 true
682 }
683
684 pub fn finish(mut self) {
687 if self.scoy == SCOY_NONE {
688 return;
689 }
690 for _ in 0..2 {
691 self.scum2_line();
692 self.scx0 = i32::MAX;
693 self.scx1 = i32::MIN;
694 self.advance_row();
695 }
696 self.scum2_line();
697 self.scoy = SCOY_NONE;
698 }
699
700 fn advance_row(&mut self) {
706 self.scoy += 1;
707 self.scoym3 += SCPITCH;
708 if self.scoym3 == SCOYM3_WRAP {
709 self.scoym3 = SCOYM3_INITIAL;
710 }
711 self.last_scum2 = None;
712 }
713
714 fn expand_column_into_row(&mut self, x: i32, y: i32, row_base: usize) {
719 let vsid = self.vxl.vsid as i32;
720 let radar_idx_signed = (row_base as isize) + (x as isize) * (SCPITCH as isize) * 3;
722 if radar_idx_signed < 0 {
723 return;
724 }
725 #[allow(clippy::cast_sign_loss)]
726 let radar_idx = radar_idx_signed as usize;
727 if radar_idx + SCPITCH > self.radar.len() {
728 return;
729 }
730 if x < 0 || x >= vsid || y < 0 || y >= vsid {
731 self.radar[radar_idx] = 0;
732 self.radar[radar_idx + 1] = MAXZDIM;
733 return;
734 }
735 let idx = (y as usize) * (vsid as usize) + (x as usize);
736 let slab = self.vxl.column_data(idx);
737 expandrle(slab, &mut self.radar[radar_idx..radar_idx + SCPITCH]);
738 }
739
740 #[allow(clippy::too_many_lines)]
743 fn scum2_line(&mut self) {
744 let vsid = self.vxl.vsid as i32;
745
746 let x0 = (self.scox0 - 1).min(self.scx0).min(self.scoox0);
748 self.scoox0 = self.scox0;
749 self.scox0 = self.scx0;
750 let x1 = (self.scox1 + 1).max(self.scx1).max(self.scoox1);
751 self.scoox1 = self.scox1;
752 self.scox1 = self.scx1;
753
754 let uptr = wrap_radar(self.scoym3 + SCPITCH);
755 let mptr = wrap_radar(uptr + SCPITCH);
756
757 let scoy_2 = self.scoy - 2;
759 if x1 < self.sceox0 || x0 > self.sceox1 {
760 for x in x0..=x1 {
761 self.expand_column_into_row(x, scoy_2, uptr);
762 }
763 } else {
764 for x in x0..self.sceox0 {
765 self.expand_column_into_row(x, scoy_2, uptr);
766 }
767 let mut x = x1;
768 while x > self.sceox1 {
769 self.expand_column_into_row(x, scoy_2, uptr);
770 x -= 1;
771 }
772 }
773
774 let scoy_1 = self.scoy - 1;
776 if (self.scex1 | x1) >= 0 {
777 for x in (x1 + 2)..self.scex0 {
778 self.expand_column_into_row(x, scoy_1, mptr);
779 }
780 let mut x = x0 - 2;
781 while x > self.scex1 {
782 self.expand_column_into_row(x, scoy_1, mptr);
783 x -= 1;
784 }
785 }
786 if x1 + 1 < self.scex0 || x0 - 1 > self.scex1 {
787 for x in (x0 - 1)..=(x1 + 1) {
788 self.expand_column_into_row(x, scoy_1, mptr);
789 }
790 } else {
791 for x in (x0 - 1)..self.scex0 {
792 self.expand_column_into_row(x, scoy_1, mptr);
793 }
794 let mut x = x1 + 1;
795 while x > self.scex1 {
796 self.expand_column_into_row(x, scoy_1, mptr);
797 x -= 1;
798 }
799 }
800 self.sceox0 = (x0 - 1).min(self.scex0);
801 self.sceox1 = (x1 + 1).max(self.scex1);
802
803 let scoy_0 = self.scoy;
805 let scoym3 = self.scoym3;
806 if x1 < self.scx0 || x0 > self.scx1 {
807 for x in x0..=x1 {
808 self.expand_column_into_row(x, scoy_0, scoym3);
809 }
810 } else {
811 for x in x0..self.scx0 {
812 self.expand_column_into_row(x, scoy_0, scoym3);
813 }
814 let mut x = x1;
815 while x > self.scx1 {
816 self.expand_column_into_row(x, scoy_0, scoym3);
817 x -= 1;
818 }
819 }
820 self.scex0 = x0;
821 self.scex1 = x1;
822
823 let y = self.scoy - 1;
826 if !(0..vsid).contains(&y) {
827 return;
828 }
829 let x0_clamped = x0.max(0);
830 let x1_clamped = x1.min(vsid - 1);
831
832 for x in x0_clamped..=x1_clamped {
833 self.flush_column(x, y, mptr, uptr, scoym3);
834 }
835 }
836
837 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
841 fn flush_column(&mut self, x: i32, y: i32, mptr: usize, uptr: usize, scoym3: usize) {
842 let vsid = self.vxl.vsid as usize;
843 let k = (x as usize) * SCPITCH * 3;
844 let n0_pos = mptr + k;
845 let n1_pos_signed = (mptr as isize) + (k as isize) - (SCPITCH as isize) * 3;
846 let n2_pos = mptr + k + SCPITCH * 3;
847 let n3_pos = uptr + k;
848 let n4_pos = scoym3 + k;
849
850 if n1_pos_signed < 0 {
853 return;
854 }
855 let n1_pos = n1_pos_signed as usize;
856
857 let idx = (y as usize) * vsid + (x as usize);
858
859 let original_bytes: Vec<u8> = self.vxl.column_data(idx).to_vec();
862
863 let written = {
864 let radar = &self.radar;
865 let n0 = &radar[n0_pos..n0_pos + SCPITCH];
866 let n1 = &radar[n1_pos..n1_pos + SCPITCH];
867 let n2 = &radar[n2_pos..n2_pos + SCPITCH];
868 let n3 = &radar[n3_pos..n3_pos + SCPITCH];
869 let n4 = &radar[n4_pos..n4_pos + SCPITCH];
870 compilerle(
871 n0,
872 n1,
873 n2,
874 n3,
875 n4,
876 &mut self.cbuf,
877 &original_bytes,
878 x,
879 y,
880 &mut *self.colfunc,
881 )
882 };
883
884 let old_offset = self.vxl.column_offset[idx];
885 self.vxl.voxdealloc(old_offset);
886 let new_offset = self.vxl.voxalloc(written as u32);
887 self.vxl.data[new_offset as usize..new_offset as usize + written]
888 .copy_from_slice(&self.cbuf[..written]);
889 self.vxl.column_offset[idx] = new_offset;
890 }
891}
892
893fn wrap_radar(off: usize) -> usize {
896 if off == SCOYM3_WRAP {
897 SCOYM3_INITIAL
898 } else {
899 off
900 }
901}
902
903#[derive(Debug, Clone, Copy, PartialEq, Eq)]
918pub struct Vspan {
919 pub x: u32,
920 pub y: u32,
921 pub z0: u8,
922 pub z1: u8,
923}
924
925#[derive(Debug, Clone, Copy, PartialEq, Eq)]
935pub enum SpanOp {
936 Carve,
937 Insert,
938}
939
940#[allow(clippy::cast_possible_wrap)]
963pub fn set_spans_with_colfunc<F>(world: &mut Vxl, spans: &[Vspan], op: SpanOp, colfunc: F)
964where
965 F: FnMut(i32, i32, i32) -> i32,
966{
967 if spans.is_empty() {
968 return;
969 }
970 let inserting = op == SpanOp::Insert;
971 let mut ctx = ScumCtx::new(world);
972 ctx.set_colfunc(colfunc);
973 for span in spans {
974 let x = span.x as i32;
975 let y = span.y as i32;
976 let z0 = i32::from(span.z0);
977 let z1 = i32::from(span.z1) + 1; ctx.with_column(x, y, |b2| {
979 if inserting {
980 insslab(b2, z0, z1);
981 } else {
982 delslab(b2, z0, z1);
983 }
984 });
985 }
986 ctx.finish();
987}
988
989pub fn set_spans(world: &mut Vxl, spans: &[Vspan], color: Option<u32>) {
1003 let op = if color.is_some() {
1004 SpanOp::Insert
1005 } else {
1006 SpanOp::Carve
1007 };
1008 #[allow(clippy::cast_possible_wrap)]
1009 let c_i32 = color.unwrap_or(0) as i32;
1010 set_spans_with_colfunc(world, spans, op, move |_, _, _| c_i32);
1011}
1012
1013pub fn set_cube(world: &mut Vxl, x: i32, y: i32, z: i32, color: Option<u32>) {
1033 let op = if color.is_some() {
1034 SpanOp::Insert
1035 } else {
1036 SpanOp::Carve
1037 };
1038 #[allow(clippy::cast_possible_wrap)]
1039 let c_i32 = color.unwrap_or(0) as i32;
1040 set_cube_with_colfunc(world, x, y, z, op, move |_, _, _| c_i32);
1041}
1042
1043#[allow(
1045 clippy::cast_possible_truncation,
1046 clippy::cast_possible_wrap,
1047 clippy::cast_sign_loss
1048)]
1049pub fn set_cube_with_colfunc<F>(world: &mut Vxl, x: i32, y: i32, z: i32, op: SpanOp, colfunc: F)
1050where
1051 F: FnMut(i32, i32, i32) -> i32,
1052{
1053 let vsid = world.vsid as i32;
1054 if x < 0 || x >= vsid || y < 0 || y >= vsid || !(0..MAXZDIM).contains(&z) {
1055 return;
1056 }
1057 let span = Vspan {
1058 x: x as u32,
1059 y: y as u32,
1060 z0: z as u8,
1061 z1: z as u8,
1062 };
1063 set_spans_with_colfunc(world, &[span], op, colfunc);
1064}
1065
1066pub fn set_rect(world: &mut Vxl, lo: [i32; 3], hi: [i32; 3], color: Option<u32>) {
1079 let op = if color.is_some() {
1080 SpanOp::Insert
1081 } else {
1082 SpanOp::Carve
1083 };
1084 #[allow(clippy::cast_possible_wrap)]
1085 let c_i32 = color.unwrap_or(0) as i32;
1086 set_rect_with_colfunc(world, lo, hi, op, move |_, _, _| c_i32);
1087}
1088
1089#[allow(
1091 clippy::cast_possible_truncation,
1092 clippy::cast_possible_wrap,
1093 clippy::cast_sign_loss
1094)]
1095pub fn set_rect_with_colfunc<F>(world: &mut Vxl, lo: [i32; 3], hi: [i32; 3], op: SpanOp, colfunc: F)
1096where
1097 F: FnMut(i32, i32, i32) -> i32,
1098{
1099 let vsid = world.vsid as i32;
1100 let xs = lo[0].min(hi[0]).max(0);
1101 let xe = lo[0].max(hi[0]).min(vsid - 1);
1102 let ys = lo[1].min(hi[1]).max(0);
1103 let ye = lo[1].max(hi[1]).min(vsid - 1);
1104 let zs = lo[2].min(hi[2]).max(0);
1105 let ze = lo[2].max(hi[2]).min(MAXZDIM - 1);
1106 if xs > xe || ys > ye || zs > ze {
1107 return;
1108 }
1109 let inserting = op == SpanOp::Insert;
1110 let mut ctx = ScumCtx::new(world);
1111 ctx.set_colfunc(colfunc);
1112 for y in ys..=ye {
1113 for x in xs..=xe {
1114 ctx.with_column(x, y, |b2| {
1115 if inserting {
1116 insslab(b2, zs, ze + 1);
1117 } else {
1118 delslab(b2, zs, ze + 1);
1119 }
1120 });
1121 }
1122 }
1123 ctx.finish();
1124}
1125
1126pub fn set_sphere(world: &mut Vxl, center: [i32; 3], radius: u32, color: Option<u32>) {
1144 let op = if color.is_some() {
1145 SpanOp::Insert
1146 } else {
1147 SpanOp::Carve
1148 };
1149 #[allow(clippy::cast_possible_wrap)]
1150 let c_i32 = color.unwrap_or(0) as i32;
1151 set_sphere_with_colfunc(world, center, radius, op, move |_, _, _| c_i32);
1152}
1153
1154#[allow(
1156 clippy::cast_possible_truncation,
1157 clippy::cast_possible_wrap,
1158 clippy::cast_sign_loss,
1159 clippy::cast_precision_loss,
1160 clippy::similar_names
1161)]
1162pub fn set_sphere_with_colfunc<F>(
1163 world: &mut Vxl,
1164 center: [i32; 3],
1165 radius: u32,
1166 op: SpanOp,
1167 colfunc: F,
1168) where
1169 F: FnMut(i32, i32, i32) -> i32,
1170{
1171 let vsid = world.vsid as i32;
1172 let cx = center[0];
1173 let cy = center[1];
1174 let cz = center[2];
1175 let r = radius as i32;
1176 let xs = (cx - r).max(0);
1177 let xe = (cx + r).min(vsid - 1);
1178 let ys = (cy - r).max(0);
1179 let ye = (cy + r).min(vsid - 1);
1180 let zs = (cz - r).max(0);
1181 let ze = (cz + r).min(MAXZDIM - 1);
1182 if xs > xe || ys > ye || zs > ze {
1183 return;
1184 }
1185 let r_sq = r * r;
1186 let inserting = op == SpanOp::Insert;
1187 let mut ctx = ScumCtx::new(world);
1188 ctx.set_colfunc(colfunc);
1189 for y in ys..=ye {
1190 let dy = y - cy;
1191 let dy_sq = dy * dy;
1192 if dy_sq > r_sq {
1193 continue;
1194 }
1195 for x in xs..=xe {
1196 let dx = x - cx;
1197 let dx_sq = dx * dx;
1198 let xy_sq = dx_sq + dy_sq;
1199 if xy_sq > r_sq {
1200 continue;
1201 }
1202 let dz_max_sq = r_sq - xy_sq;
1205 let dz_max = (dz_max_sq as f32).sqrt() as i32;
1206 let z_lo = (cz - dz_max).max(zs);
1207 let z_hi = (cz + dz_max).min(ze);
1208 if z_lo > z_hi {
1209 continue;
1210 }
1211 ctx.with_column(x, y, |b2| {
1212 if inserting {
1213 insslab(b2, z_lo, z_hi + 1);
1214 } else {
1215 delslab(b2, z_lo, z_hi + 1);
1216 }
1217 });
1218 }
1219 }
1220 ctx.finish();
1221}
1222
1223#[cfg(test)]
1224#[allow(
1225 clippy::cast_possible_truncation,
1226 clippy::cast_possible_wrap,
1227 clippy::cast_sign_loss,
1228 clippy::items_after_statements
1229)]
1230mod tests {
1231 use super::*;
1232
1233 fn build_b2(slabs: &[(i32, i32)]) -> Vec<i32> {
1237 let mut buf: Vec<i32> = Vec::new();
1238 for &(top, bot) in slabs {
1239 assert!(top < bot, "slab top must be < bot");
1240 assert!(bot < MAXZDIM, "slab bot must fit below MAXZDIM");
1241 buf.push(top);
1242 buf.push(bot);
1243 }
1244 buf.push(MAXZDIM);
1247 buf.push(MAXZDIM);
1248 buf.resize(buf.len() + 32, 0);
1250 buf
1251 }
1252
1253 fn read_slabs(b2: &[i32]) -> Vec<(i32, i32)> {
1255 let mut out = Vec::new();
1256 let mut i = 0;
1257 while b2[i + 1] < MAXZDIM {
1258 out.push((b2[i], b2[i + 1]));
1259 i += 2;
1260 }
1261 out
1262 }
1263
1264 #[test]
1267 fn delslab_noop_y0_ge_y1() {
1268 let mut b2 = build_b2(&[(10, 20)]);
1269 delslab(&mut b2, 15, 15);
1270 assert_eq!(read_slabs(&b2), [(10, 20)]);
1271 delslab(&mut b2, 20, 10);
1272 assert_eq!(read_slabs(&b2), [(10, 20)]);
1273 }
1274
1275 #[test]
1276 fn delslab_split_inside_one_slab() {
1277 let mut b2 = build_b2(&[(10, 30)]);
1278 delslab(&mut b2, 15, 20);
1279 assert_eq!(read_slabs(&b2), [(10, 15), (20, 30)]);
1280 }
1281
1282 #[test]
1283 fn delslab_shrink_bot_of_slab() {
1284 let mut b2 = build_b2(&[(10, 30)]);
1285 delslab(&mut b2, 20, 30);
1286 assert_eq!(read_slabs(&b2), [(10, 20)]);
1287 }
1288
1289 #[test]
1290 fn delslab_shrink_top_of_slab() {
1291 let mut b2 = build_b2(&[(10, 30)]);
1292 delslab(&mut b2, 5, 15);
1293 assert_eq!(read_slabs(&b2), [(15, 30)]);
1294 }
1295
1296 #[test]
1297 fn delslab_carve_full_slab() {
1298 let mut b2 = build_b2(&[(10, 30)]);
1299 delslab(&mut b2, 5, 35);
1300 assert_eq!(read_slabs(&b2), Vec::<(i32, i32)>::new());
1301 }
1302
1303 #[test]
1304 fn delslab_in_air_noop() {
1305 let mut b2 = build_b2(&[(10, 30)]);
1306 delslab(&mut b2, 0, 8);
1307 assert_eq!(read_slabs(&b2), [(10, 30)]);
1308 delslab(&mut b2, 35, 50);
1309 assert_eq!(read_slabs(&b2), [(10, 30)]);
1310 }
1311
1312 #[test]
1313 fn delslab_span_two_slabs_carve_middle() {
1314 let mut b2 = build_b2(&[(10, 30), (50, 70)]);
1315 delslab(&mut b2, 20, 60);
1316 assert_eq!(read_slabs(&b2), [(10, 20), (60, 70)]);
1317 }
1318
1319 #[test]
1320 fn delslab_carve_two_full_slabs_keep_third() {
1321 let mut b2 = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1322 delslab(&mut b2, 5, 45);
1323 assert_eq!(read_slabs(&b2), [(50, 60)]);
1324 }
1325
1326 #[test]
1327 fn delslab_y1_clamped_to_maxzdim_minus_1() {
1328 let mut b2 = build_b2(&[(10, 200)]);
1329 delslab(&mut b2, 100, MAXZDIM);
1330 assert_eq!(read_slabs(&b2), [(10, 100)]);
1331 }
1332
1333 #[test]
1334 fn delslab_carve_top_edge_of_slab() {
1335 let mut b2 = build_b2(&[(10, 30)]);
1338 delslab(&mut b2, 5, 10);
1339 assert_eq!(read_slabs(&b2), [(10, 30)]);
1340 }
1341
1342 #[test]
1343 fn delslab_carve_bot_edge_of_slab() {
1344 let mut b2 = build_b2(&[(10, 30)]);
1346 delslab(&mut b2, 30, 35);
1347 assert_eq!(read_slabs(&b2), [(10, 30)]);
1348 }
1349
1350 #[test]
1351 fn delslab_carve_exact_full_slab_keeps_neighbors() {
1352 let mut b2 = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1353 delslab(&mut b2, 30, 40);
1354 assert_eq!(read_slabs(&b2), [(10, 20), (50, 60)]);
1355 }
1356
1357 #[test]
1360 fn insslab_noop_y0_ge_y1() {
1361 let mut b2 = build_b2(&[(10, 20)]);
1362 insslab(&mut b2, 15, 15);
1363 assert_eq!(read_slabs(&b2), [(10, 20)]);
1364 insslab(&mut b2, 20, 10);
1365 assert_eq!(read_slabs(&b2), [(10, 20)]);
1366 }
1367
1368 #[test]
1369 fn insslab_into_pure_air() {
1370 let mut b2 = build_b2(&[]);
1371 insslab(&mut b2, 10, 30);
1372 assert_eq!(read_slabs(&b2), [(10, 30)]);
1373 }
1374
1375 #[test]
1376 fn insslab_into_air_gap_above_slab() {
1377 let mut b2 = build_b2(&[(50, 70)]);
1378 insslab(&mut b2, 10, 30);
1379 assert_eq!(read_slabs(&b2), [(10, 30), (50, 70)]);
1380 }
1381
1382 #[test]
1383 fn insslab_into_air_gap_between_slabs() {
1384 let mut b2 = build_b2(&[(10, 20), (60, 70)]);
1385 insslab(&mut b2, 30, 50);
1386 assert_eq!(read_slabs(&b2), [(10, 20), (30, 50), (60, 70)]);
1387 }
1388
1389 #[test]
1390 fn insslab_into_air_gap_below_all_slabs() {
1391 let mut b2 = build_b2(&[(10, 20)]);
1392 insslab(&mut b2, 30, 50);
1393 assert_eq!(read_slabs(&b2), [(10, 20), (30, 50)]);
1394 }
1395
1396 #[test]
1397 fn insslab_extend_top_of_slab() {
1398 let mut b2 = build_b2(&[(50, 70)]);
1399 insslab(&mut b2, 30, 60);
1400 assert_eq!(read_slabs(&b2), [(30, 70)]);
1401 }
1402
1403 #[test]
1404 fn insslab_extend_bot_of_slab() {
1405 let mut b2 = build_b2(&[(50, 70)]);
1406 insslab(&mut b2, 60, 80);
1407 assert_eq!(read_slabs(&b2), [(50, 80)]);
1408 }
1409
1410 #[test]
1411 fn insslab_merge_into_last_slab_writes_sentinel() {
1412 let mut b2: Vec<i32> = vec![100, 105, 255, MAXZDIM, MAXZDIM, MAXZDIM];
1423 b2.resize(b2.len() + 32, 0); insslab(&mut b2, 105, 255);
1425 assert_eq!(b2[0], 100);
1428 assert!(
1429 b2[1] >= MAXZDIM,
1430 "expected merged run to extend to MAXZDIM, got b2[1] = {}",
1431 b2[1]
1432 );
1433 assert!(
1436 b2[2] >= MAXZDIM,
1437 "b2[2] should be sentinel, got {} (pre-fix this was 255 from the un-shifted phantom slab)",
1438 b2[2]
1439 );
1440 }
1441
1442 #[test]
1443 fn insslab_touch_top_merges() {
1444 let mut b2 = build_b2(&[(50, 70)]);
1446 insslab(&mut b2, 30, 50);
1447 assert_eq!(read_slabs(&b2), [(30, 70)]);
1448 }
1449
1450 #[test]
1451 fn insslab_touch_bot_merges() {
1452 let mut b2 = build_b2(&[(50, 70)]);
1454 insslab(&mut b2, 70, 80);
1455 assert_eq!(read_slabs(&b2), [(50, 80)]);
1456 }
1457
1458 #[test]
1459 fn insslab_merge_two_slabs() {
1460 let mut b2 = build_b2(&[(10, 30), (50, 70)]);
1461 insslab(&mut b2, 20, 60);
1462 assert_eq!(read_slabs(&b2), [(10, 70)]);
1463 }
1464
1465 #[test]
1466 fn insslab_engulf_inner_slabs() {
1467 let mut b2 = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1468 insslab(&mut b2, 5, 70);
1469 assert_eq!(read_slabs(&b2), [(5, 70)]);
1470 }
1471
1472 #[test]
1473 fn insslab_engulf_then_keep_lower() {
1474 let mut b2 = build_b2(&[(10, 20), (30, 40), (60, 80)]);
1475 insslab(&mut b2, 5, 50);
1476 assert_eq!(read_slabs(&b2), [(5, 50), (60, 80)]);
1477 }
1478
1479 #[test]
1480 fn insslab_engulf_then_merge_lower() {
1481 let mut b2 = build_b2(&[(10, 20), (30, 40), (60, 80)]);
1482 insslab(&mut b2, 5, 60);
1483 assert_eq!(read_slabs(&b2), [(5, 80)]);
1484 }
1485
1486 #[test]
1487 fn insslab_chain_of_touching_inserts() {
1488 let mut b2 = build_b2(&[]);
1489 insslab(&mut b2, 10, 20);
1490 insslab(&mut b2, 20, 30);
1491 insslab(&mut b2, 30, 40);
1492 assert_eq!(read_slabs(&b2), [(10, 40)]);
1493 }
1494
1495 #[test]
1496 fn insslab_carve_then_insert_round_trip() {
1497 let original = [(10, 50)];
1500 let mut b2 = build_b2(&original);
1501 delslab(&mut b2, 20, 30);
1502 assert_eq!(read_slabs(&b2), [(10, 20), (30, 50)]);
1503 insslab(&mut b2, 20, 30);
1504 assert_eq!(read_slabs(&b2), original);
1505 }
1506
1507 #[test]
1508 fn insslab_into_sentinel_only_buffer_with_z_advance() {
1509 let mut b2 = build_b2(&[(10, 20)]);
1511 insslab(&mut b2, 100, 150);
1512 assert_eq!(read_slabs(&b2), [(10, 20), (100, 150)]);
1513 }
1514
1515 fn read_uind(uind: &[i32]) -> Vec<(i32, i32)> {
1521 let mut out = Vec::new();
1522 let mut i = 0;
1523 while uind[i + 1] < MAXZDIM {
1524 out.push((uind[i], uind[i + 1]));
1525 i += 2;
1526 }
1527 out.push((uind[i], uind[i + 1]));
1529 out
1530 }
1531
1532 #[test]
1533 fn expandrle_single_slab_fully_solid_column() {
1534 let z1c = u8::try_from(MAXZDIM - 1).expect("MAXZDIM-1 fits in u8");
1538 let mut slab = vec![0u8, 0, z1c, 0];
1539 slab.extend(std::iter::repeat_n(0u8, (MAXZDIM as usize) * 4));
1540 let mut uind = vec![0i32; 16];
1541 expandrle(&slab, &mut uind);
1542 assert_eq!(uind[0], 0);
1544 assert_eq!(uind[1], MAXZDIM);
1545 }
1546
1547 #[test]
1548 fn expandrle_single_slab_partial_floor() {
1549 let slab = [0u8, 64, 66, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0];
1553 let mut uind = vec![0i32; 16];
1554 expandrle(&slab, &mut uind);
1555 assert_eq!(uind[0], 64);
1556 assert_eq!(uind[1], MAXZDIM);
1557 }
1558
1559 #[test]
1560 fn expandrle_two_slabs_with_cave() {
1561 let slab = [
1580 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 50, 52, 30, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0, 0, ];
1587 let mut uind = vec![0i32; 16];
1588 expandrle(&slab, &mut uind);
1589 assert_eq!(uind[0], 10);
1590 assert_eq!(uind[1], 30);
1591 assert_eq!(uind[2], 50);
1592 assert_eq!(uind[3], MAXZDIM);
1593 }
1594
1595 #[test]
1596 fn expandrle_skips_degenerate_slab_with_no_ceiling_gap() {
1597 let slab = [
1605 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 20, 22, 20, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0, 0, ];
1610 let mut uind = vec![0i32; 16];
1611 expandrle(&slab, &mut uind);
1612 assert_eq!(uind[0], 10);
1614 assert_eq!(uind[1], MAXZDIM);
1615 }
1616
1617 #[test]
1618 fn expandrle_round_trips_through_b2_helpers() {
1619 let slab = [
1623 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 50, 52, 30, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0,
1624 0,
1625 ];
1626 let mut uind = vec![0i32; 16];
1627 expandrle(&slab, &mut uind);
1628 let runs = read_uind(&uind[..4]);
1629 assert_eq!(runs, [(10, 30), (50, MAXZDIM)]);
1630 }
1631
1632 fn all_air_neighbor() -> Vec<i32> {
1638 let mut buf = vec![MAXZDIM, MAXZDIM];
1640 buf.resize(buf.len() + MAXZDIM as usize, MAXZDIM);
1641 buf
1642 }
1643
1644 fn b2_from_runs(runs: &[(i32, i32)]) -> Vec<i32> {
1648 let mut buf = Vec::new();
1649 for &(top, bot) in runs {
1650 buf.push(top);
1651 buf.push(bot);
1652 }
1653 buf.push(MAXZDIM);
1654 buf.push(MAXZDIM);
1655 buf.resize(buf.len() + MAXZDIM as usize, MAXZDIM);
1656 buf
1657 }
1658
1659 #[test]
1660 fn build_color_table_single_slab_one_floor_voxel() {
1661 let slab = [0u8, 10, 10, 0, 0xa1, 0xa2, 0xa3, 0xa4];
1663 let table = build_color_table(&slab);
1664 assert_eq!(table.len(), 2);
1665 assert_eq!(table[0].z_start, 10);
1666 assert_eq!(table[0].z_end, 11);
1667 assert_eq!(table[0].colors, &[0xa1, 0xa2, 0xa3, 0xa4]);
1668 assert_eq!(table[1].z_start, MAXZDIM);
1670 assert_eq!(table[1].z_end, MAXZDIM);
1671 }
1672
1673 #[test]
1674 fn build_color_table_two_slabs_with_ceiling() {
1675 let slab = [
1680 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, ];
1689 let table = build_color_table(&slab);
1690 assert_eq!(table.len(), 4);
1691 assert_eq!(table[0].z_start, 10);
1693 assert_eq!(table[0].z_end, 11);
1694 assert_eq!(table[0].colors.len(), 4);
1695 assert_eq!(table[1].z_start, 28);
1697 assert_eq!(table[1].z_end, 30);
1698 assert_eq!(
1699 table[1].colors,
1700 &[0xc0, 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc1]
1701 );
1702 assert_eq!(table[2].z_start, 50);
1704 assert_eq!(table[2].z_end, 53);
1705 assert_eq!(table[2].colors.len(), 12);
1706 assert_eq!(table[3].z_start, MAXZDIM);
1708 }
1709
1710 #[test]
1711 fn compilerle_round_trip_single_slab_solid_to_maxzdim() {
1712 let mut slab = vec![0u8, 10, (MAXZDIM - 1) as u8, 0];
1716 for z in 10..MAXZDIM {
1717 slab.extend_from_slice(&[z as u8, (z + 1) as u8, (z + 2) as u8, 0]);
1719 }
1720
1721 let mut b2 = vec![0i32; (MAXZDIM as usize) + 4];
1723 expandrle(&slab, &mut b2);
1724 assert_eq!(b2[0], 10);
1725 assert_eq!(b2[1], MAXZDIM);
1726
1727 let n_air = all_air_neighbor();
1729 let mut cbuf = vec![0u8; 1028];
1730 let mut colfunc_called = 0;
1731 let mut colfunc = |_x: i32, _y: i32, _z: i32| -> i32 {
1732 colfunc_called += 1;
1733 0
1734 };
1735 let written = compilerle(
1736 &b2,
1737 &n_air,
1738 &n_air,
1739 &n_air,
1740 &n_air,
1741 &mut cbuf,
1742 &slab,
1743 0,
1744 0,
1745 &mut colfunc,
1746 );
1747 assert_eq!(colfunc_called, 0, "all colors should come from tbuf2");
1748
1749 assert_eq!(written, slab.len());
1752 assert_eq!(&cbuf[..written], &slab[..]);
1753
1754 let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
1756 expandrle(&cbuf[..written], &mut b2_round);
1757 assert_eq!(b2_round[0], 10);
1758 assert_eq!(b2_round[1], MAXZDIM);
1759 }
1760
1761 #[test]
1762 fn compilerle_round_trip_two_solid_runs_with_cave() {
1763 let dummy = vec![0u8, 0, (MAXZDIM - 1) as u8, 0];
1773 let mut dummy_full = dummy;
1774 dummy_full.extend(std::iter::repeat_n(0u8, (MAXZDIM as usize) * 4));
1775
1776 let n_air = all_air_neighbor();
1777 let b2 = b2_from_runs(&[(10, 30), (50, MAXZDIM)]);
1778 let mut seed = vec![0u8; 1028];
1779 let mut colfunc = |_x: i32, _y: i32, z: i32| -> i32 { z };
1780 let seed_len = compilerle(
1781 &b2,
1782 &n_air,
1783 &n_air,
1784 &n_air,
1785 &n_air,
1786 &mut seed,
1787 &dummy_full,
1788 0,
1789 0,
1790 &mut colfunc,
1791 );
1792 seed.truncate(seed_len);
1793
1794 let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
1796 expandrle(&seed, &mut b2_round);
1797 assert_eq!(b2_round[0], 10);
1799 assert_eq!(b2_round[1], 30);
1800 assert_eq!(b2_round[2], 50);
1801 assert_eq!(b2_round[3], MAXZDIM);
1802
1803 let mut cbuf = vec![0u8; 1028];
1807 let mut never_called = 0;
1808 let mut colfunc2 = |_x: i32, _y: i32, _z: i32| -> i32 {
1809 never_called += 1;
1810 0
1811 };
1812 let written = compilerle(
1813 &b2,
1814 &n_air,
1815 &n_air,
1816 &n_air,
1817 &n_air,
1818 &mut cbuf,
1819 &seed,
1820 0,
1821 0,
1822 &mut colfunc2,
1823 );
1824 assert_eq!(never_called, 0, "second pass needs no colfunc");
1825 assert_eq!(written, seed_len);
1826 assert_eq!(&cbuf[..written], &seed[..]);
1827 }
1828
1829 #[test]
1830 fn compilerle_buried_voxel_optimization_with_all_solid_neighbors() {
1831 let b2 = b2_from_runs(&[(10, MAXZDIM)]);
1839 let n_solid = b2_from_runs(&[(0, MAXZDIM)]);
1840 let mut slab = vec![0u8, 10, (MAXZDIM - 1) as u8, 0];
1844 for z in 10..MAXZDIM {
1845 slab.extend_from_slice(&[z as u8, 0, 0, 0]);
1846 }
1847 let mut cbuf = vec![0u8; 1028];
1848 let mut colfunc_called = 0;
1849 let mut colfunc = |_x: i32, _y: i32, _z: i32| -> i32 {
1850 colfunc_called += 1;
1851 0
1852 };
1853 let written = compilerle(
1854 &b2,
1855 &n_solid,
1856 &n_solid,
1857 &n_solid,
1858 &n_solid,
1859 &mut cbuf,
1860 &slab,
1861 0,
1862 0,
1863 &mut colfunc,
1864 );
1865 assert_eq!(colfunc_called, 0, "tbuf2 should cover every voxel");
1866 assert_eq!(written, 8);
1869 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];
1876 expandrle(&cbuf[..written], &mut b2_round);
1877 assert_eq!(b2_round[0], 10);
1878 assert_eq!(b2_round[1], MAXZDIM);
1879 }
1880
1881 fn build_1x1_min_solid_vxl() -> Vxl {
1886 let column = vec![0u8, 0, 0, 0, 0xff, 0x80, 0x40, 0x20];
1887 let column_offset = vec![0u32, column.len() as u32].into_boxed_slice();
1888 Vxl {
1889 vsid: 1,
1890 ipo: [0.0; 3],
1891 ist: [1.0, 0.0, 0.0],
1892 ihe: [0.0, 0.0, 1.0],
1893 ifo: [0.0, 1.0, 0.0],
1894 data: column.into_boxed_slice(),
1895 column_offset,
1896 mip_base_offsets: Box::new([0, 2]),
1897 vbit: Box::new([]),
1898 vbiti: 0,
1899 }
1900 }
1901
1902 #[test]
1903 fn scum2_no_edit_round_trip_1x1_minimal_column() {
1904 let mut vxl = build_1x1_min_solid_vxl();
1908 vxl.reserve_edit_capacity(4096);
1909
1910 let mut ctx = ScumCtx::new(&mut vxl);
1911 let _b2 = ctx.scum2(0, 0).expect("column 0,0 in bounds");
1912 ctx.finish();
1913
1914 let column = vxl.column_data(0);
1915 let mut b2_after = vec![0i32; SCPITCH];
1916 expandrle(column, &mut b2_after);
1917 assert_eq!(b2_after[0], 0);
1918 assert_eq!(b2_after[1], MAXZDIM);
1919 }
1920
1921 #[test]
1922 fn scum2_carve_edit_1x1_creates_air_gap() {
1923 let mut vxl = build_1x1_min_solid_vxl();
1926 vxl.reserve_edit_capacity(4096);
1927
1928 let mut ctx = ScumCtx::new(&mut vxl);
1929 ctx.set_colfunc(|_x, _y, _z| 0x80_60_40_20u32 as i32);
1930 {
1931 let b2 = ctx.scum2(0, 0).expect("column 0,0 in bounds");
1932 delslab(b2, 50, 100);
1934 }
1935 ctx.finish();
1936
1937 let column = vxl.column_data(0);
1938 let mut b2_after = vec![0i32; SCPITCH];
1939 expandrle(column, &mut b2_after);
1940 assert_eq!(b2_after[0], 0);
1942 assert_eq!(b2_after[1], 50);
1943 assert_eq!(b2_after[2], 100);
1944 assert_eq!(b2_after[3], MAXZDIM);
1945 }
1946
1947 fn build_4x4_min_solid_vxl() -> Vxl {
1950 const COL: [u8; 8] = [0, 0, 0, 0, 0xff, 0x80, 0x40, 0x20];
1951 let mut data = Vec::with_capacity(16 * 8);
1952 let mut offsets = Vec::with_capacity(17);
1953 for i in 0..16 {
1954 offsets.push((i * 8) as u32);
1955 data.extend_from_slice(&COL);
1956 }
1957 offsets.push((16 * 8) as u32);
1958 Vxl {
1959 vsid: 4,
1960 ipo: [0.0; 3],
1961 ist: [1.0, 0.0, 0.0],
1962 ihe: [0.0, 0.0, 1.0],
1963 ifo: [0.0, 1.0, 0.0],
1964 data: data.into_boxed_slice(),
1965 column_offset: offsets.into_boxed_slice(),
1966 mip_base_offsets: Box::new([0, 17]),
1967 vbit: Box::new([]),
1968 vbiti: 0,
1969 }
1970 }
1971
1972 #[test]
1973 fn scum2_batch_edits_multiple_columns_same_row() {
1974 let mut vxl = build_4x4_min_solid_vxl();
1977 vxl.reserve_edit_capacity(8192);
1978
1979 let mut ctx = ScumCtx::new(&mut vxl);
1980 ctx.set_colfunc(|_x, _y, _z| 0);
1981 {
1982 let b2 = ctx.scum2(1, 2).unwrap();
1983 delslab(b2, 50, 100);
1984 }
1985 {
1986 let b2 = ctx.scum2(2, 2).unwrap();
1987 delslab(b2, 50, 100);
1988 }
1989 ctx.finish();
1990
1991 for x in [1, 2] {
1992 let idx = 2 * 4 + x;
1993 let mut b2_after = vec![0i32; SCPITCH];
1994 expandrle(vxl.column_data(idx), &mut b2_after);
1995 assert_eq!(b2_after[0], 0);
1996 assert_eq!(b2_after[1], 50);
1997 assert_eq!(b2_after[2], 100);
1998 assert_eq!(b2_after[3], MAXZDIM);
1999 }
2000 for x in [0, 3] {
2002 let idx = 2 * 4 + x;
2003 let mut b2_after = vec![0i32; SCPITCH];
2004 expandrle(vxl.column_data(idx), &mut b2_after);
2005 assert_eq!(b2_after[0], 0);
2006 assert_eq!(b2_after[1], MAXZDIM);
2007 }
2008 }
2009
2010 #[test]
2011 fn scum2_batch_edits_across_rows() {
2012 let mut vxl = build_4x4_min_solid_vxl();
2015 vxl.reserve_edit_capacity(8192);
2016
2017 let mut ctx = ScumCtx::new(&mut vxl);
2018 ctx.set_colfunc(|_x, _y, _z| 0);
2019 {
2020 let b2 = ctx.scum2(1, 1).unwrap();
2021 delslab(b2, 60, 80);
2022 }
2023 {
2024 let b2 = ctx.scum2(1, 2).unwrap();
2025 delslab(b2, 60, 80);
2026 }
2027 ctx.finish();
2028
2029 for y in [1, 2] {
2030 let idx = y * 4 + 1;
2031 let mut b2_after = vec![0i32; SCPITCH];
2032 expandrle(vxl.column_data(idx), &mut b2_after);
2033 assert_eq!(b2_after[0], 0);
2034 assert_eq!(b2_after[1], 60);
2035 assert_eq!(b2_after[2], 80);
2036 assert_eq!(b2_after[3], MAXZDIM);
2037 }
2038 }
2039
2040 #[test]
2041 fn scum2_finish_without_any_edit_is_noop() {
2042 let mut vxl = build_1x1_min_solid_vxl();
2044 vxl.reserve_edit_capacity(4096);
2045 let original = vxl.column_data(0).to_vec();
2046 let ctx = ScumCtx::new(&mut vxl);
2047 ctx.finish();
2048 assert_eq!(vxl.column_data(0), &original[..]);
2049 }
2050
2051 #[test]
2052 fn scum2_returns_none_for_out_of_bounds() {
2053 let mut vxl = build_1x1_min_solid_vxl();
2054 vxl.reserve_edit_capacity(4096);
2055 let mut ctx = ScumCtx::new(&mut vxl);
2056 assert!(ctx.scum2(-1, 0).is_none());
2057 assert!(ctx.scum2(0, -1).is_none());
2058 assert!(ctx.scum2(1, 0).is_none());
2059 assert!(ctx.scum2(0, 1).is_none());
2060 ctx.finish();
2061 }
2062
2063 #[test]
2066 fn set_spans_empty_is_noop() {
2067 let mut vxl = build_1x1_min_solid_vxl();
2068 let original = vxl.column_data(0).to_vec();
2069 set_spans(&mut vxl, &[], None);
2070 assert_eq!(vxl.column_data(0), &original[..]);
2074 }
2075
2076 #[test]
2077 fn set_spans_single_carve_creates_air_gap() {
2078 let mut vxl = build_1x1_min_solid_vxl();
2079 vxl.reserve_edit_capacity(4096);
2080 set_spans(
2081 &mut vxl,
2082 &[Vspan {
2083 x: 0,
2084 y: 0,
2085 z0: 50,
2086 z1: 99,
2087 }],
2088 None,
2089 );
2090 let mut b2 = vec![0i32; SCPITCH];
2091 expandrle(vxl.column_data(0), &mut b2);
2092 assert_eq!(b2[0], 0);
2094 assert_eq!(b2[1], 50);
2095 assert_eq!(b2[2], 100);
2096 assert_eq!(b2[3], MAXZDIM);
2097 }
2098
2099 #[test]
2100 fn set_spans_multi_span_same_column_accumulates() {
2101 let mut vxl = build_1x1_min_solid_vxl();
2105 vxl.reserve_edit_capacity(4096);
2106 set_spans(
2107 &mut vxl,
2108 &[
2109 Vspan {
2110 x: 0,
2111 y: 0,
2112 z0: 30,
2113 z1: 49,
2114 },
2115 Vspan {
2116 x: 0,
2117 y: 0,
2118 z0: 100,
2119 z1: 119,
2120 },
2121 ],
2122 None,
2123 );
2124 let mut b2 = vec![0i32; SCPITCH];
2125 expandrle(vxl.column_data(0), &mut b2);
2126 assert_eq!(b2[0], 0);
2128 assert_eq!(b2[1], 30);
2129 assert_eq!(b2[2], 50);
2130 assert_eq!(b2[3], 100);
2131 assert_eq!(b2[4], 120);
2132 assert_eq!(b2[5], MAXZDIM);
2133 }
2134
2135 #[test]
2136 fn set_spans_insert_color_fills_air() {
2137 let mut vxl = build_1x1_min_solid_vxl();
2140 vxl.reserve_edit_capacity(4096);
2141 set_spans(
2143 &mut vxl,
2144 &[Vspan {
2145 x: 0,
2146 y: 0,
2147 z0: 50,
2148 z1: 99,
2149 }],
2150 None,
2151 );
2152 const FILL: u32 = 0x80_aa_bb_cc;
2154 set_spans(
2155 &mut vxl,
2156 &[Vspan {
2157 x: 0,
2158 y: 0,
2159 z0: 60,
2160 z1: 79,
2161 }],
2162 Some(FILL),
2163 );
2164 let mut b2 = vec![0i32; SCPITCH];
2165 expandrle(vxl.column_data(0), &mut b2);
2166 assert_eq!(b2[0], 0);
2168 assert_eq!(b2[1], 50);
2169 assert_eq!(b2[2], 60);
2170 assert_eq!(b2[3], 80);
2171 assert_eq!(b2[4], 100);
2172 assert_eq!(b2[5], MAXZDIM);
2173 }
2174
2175 #[test]
2176 fn set_spans_skips_out_of_bounds_silently() {
2177 let mut vxl = build_1x1_min_solid_vxl();
2178 vxl.reserve_edit_capacity(4096);
2179 set_spans(
2180 &mut vxl,
2181 &[Vspan {
2182 x: 7,
2183 y: 9,
2184 z0: 50,
2185 z1: 99,
2186 }],
2187 None,
2188 );
2189 let mut b2 = vec![0i32; SCPITCH];
2191 expandrle(vxl.column_data(0), &mut b2);
2192 assert_eq!(b2[0], 0);
2193 assert_eq!(b2[1], MAXZDIM);
2194 }
2195
2196 #[test]
2197 fn set_spans_with_colfunc_z_dependent_colour() {
2198 let mut vxl = build_4x4_min_solid_vxl();
2205 vxl.reserve_edit_capacity(8192);
2206 let carve_spans: Vec<Vspan> = (0..4)
2209 .flat_map(|y| {
2210 (0..4).map(move |x| Vspan {
2211 x,
2212 y,
2213 z0: 50,
2214 z1: 99,
2215 })
2216 })
2217 .collect();
2218 set_spans(&mut vxl, &carve_spans, None);
2219 set_spans_with_colfunc(
2222 &mut vxl,
2223 &[Vspan {
2224 x: 1,
2225 y: 1,
2226 z0: 60,
2227 z1: 79,
2228 }],
2229 SpanOp::Insert,
2230 |_x, _y, z| (0x80ff_ff00u32 as i32) | z,
2231 );
2232 let idx = 4 + 1; let column = vxl.column_data(idx);
2236 let mut v = 0usize;
2237 let mut found = false;
2238 loop {
2239 let nextptr = column[v];
2240 let z1 = column[v + 1];
2241 if z1 == 60 {
2242 let z1c = column[v + 2];
2243 assert_eq!(z1c, 79, "z1c");
2244 let n_voxels = usize::from(z1c) - usize::from(z1) + 1;
2245 for i in 0..n_voxels {
2246 let off = v + 4 + i * 4;
2247 let c = u32::from_le_bytes([
2248 column[off],
2249 column[off + 1],
2250 column[off + 2],
2251 column[off + 3],
2252 ]);
2253 let z = u32::from(z1) + (i as u32);
2254 assert_eq!(
2255 c,
2256 0x80ff_ff00 | z,
2257 "z={z}: expected colour {:#010x}, got {:#010x}",
2258 0x80ff_ff00 | z,
2259 c
2260 );
2261 }
2262 found = true;
2263 break;
2264 }
2265 if nextptr == 0 {
2266 break;
2267 }
2268 v += usize::from(nextptr) * 4;
2269 }
2270 assert!(found, "did not find a slab with z1=60");
2271 }
2272
2273 #[test]
2276 fn set_cube_carves_single_voxel() {
2277 let mut vxl = build_4x4_min_solid_vxl();
2278 vxl.reserve_edit_capacity(4096);
2279 set_cube(&mut vxl, 1, 1, 100, None);
2280 let mut b2 = vec![0i32; SCPITCH];
2281 expandrle(vxl.column_data(4 + 1), &mut b2);
2282 assert_eq!(b2[0], 0);
2284 assert_eq!(b2[1], 100);
2285 assert_eq!(b2[2], 101);
2286 assert_eq!(b2[3], MAXZDIM);
2287 }
2288
2289 #[test]
2290 fn set_cube_skips_oob() {
2291 let mut vxl = build_4x4_min_solid_vxl();
2292 vxl.reserve_edit_capacity(4096);
2293 set_cube(&mut vxl, -1, 1, 100, None);
2295 set_cube(&mut vxl, 5, 1, 100, None);
2296 set_cube(&mut vxl, 1, 1, 256, None);
2297 let mut b2 = vec![0i32; SCPITCH];
2299 expandrle(vxl.column_data(4 + 1), &mut b2);
2300 assert_eq!(b2[0], 0);
2301 assert_eq!(b2[1], MAXZDIM);
2302 }
2303
2304 #[test]
2305 fn set_rect_carves_aabb() {
2306 let mut vxl = build_4x4_min_solid_vxl();
2307 vxl.reserve_edit_capacity(8192);
2308 set_rect(&mut vxl, [1, 1, 50], [2, 2, 99], None);
2310 for y in 1..=2 {
2311 for x in 1..=2 {
2312 let idx = (y * 4 + x) as usize;
2313 let mut b2 = vec![0i32; SCPITCH];
2314 expandrle(vxl.column_data(idx), &mut b2);
2315 assert_eq!(b2[0], 0, "col ({x},{y})");
2316 assert_eq!(b2[1], 50, "col ({x},{y})");
2317 assert_eq!(b2[2], 100, "col ({x},{y})");
2318 assert_eq!(b2[3], MAXZDIM, "col ({x},{y})");
2319 }
2320 }
2321 for &(x, y) in &[(0, 0), (3, 3)] {
2323 let idx = (y * 4 + x) as usize;
2324 let mut b2 = vec![0i32; SCPITCH];
2325 expandrle(vxl.column_data(idx), &mut b2);
2326 assert_eq!(b2[0], 0);
2327 assert_eq!(b2[1], MAXZDIM);
2328 }
2329 }
2330
2331 #[test]
2332 fn set_rect_clamps_to_world() {
2333 let mut vxl = build_4x4_min_solid_vxl();
2334 vxl.reserve_edit_capacity(8192);
2335 set_rect(&mut vxl, [-10, -10, -10], [100, 100, 1000], None);
2338 for idx in 0..16 {
2340 let mut b2 = vec![0i32; SCPITCH];
2341 expandrle(vxl.column_data(idx), &mut b2);
2342 assert_eq!(b2[0], 255, "col {idx}");
2346 assert_eq!(b2[1], MAXZDIM, "col {idx}");
2347 }
2348 }
2349
2350 #[test]
2351 fn set_sphere_carves_centred_sphere() {
2352 let mut vxl = build_4x4_min_solid_vxl();
2355 vxl.reserve_edit_capacity(8192);
2356 set_sphere(&mut vxl, [1, 1, 128], 1, None);
2357 let mut b2 = vec![0i32; SCPITCH];
2361 expandrle(vxl.column_data(4 + 1), &mut b2);
2362 assert_eq!(b2[0], 0);
2363 assert_eq!(b2[1], 127);
2364 assert_eq!(b2[2], 130);
2365 assert_eq!(b2[3], MAXZDIM);
2366 let mut b2 = vec![0i32; SCPITCH];
2368 expandrle(vxl.column_data(4), &mut b2);
2369 assert_eq!(b2[0], 0);
2370 assert_eq!(b2[1], 128);
2371 assert_eq!(b2[2], 129);
2372 assert_eq!(b2[3], MAXZDIM);
2373 }
2374
2375 #[test]
2376 fn set_sphere_radius_zero_is_single_voxel() {
2377 let mut vxl = build_4x4_min_solid_vxl();
2378 vxl.reserve_edit_capacity(4096);
2379 set_sphere(&mut vxl, [1, 1, 100], 0, None);
2380 let mut b2 = vec![0i32; SCPITCH];
2382 expandrle(vxl.column_data(4 + 1), &mut b2);
2383 assert_eq!(b2[0], 0);
2384 assert_eq!(b2[1], 100);
2385 assert_eq!(b2[2], 101);
2386 assert_eq!(b2[3], MAXZDIM);
2387 }
2388
2389 #[test]
2390 fn set_sphere_with_colfunc_position_dependent_color() {
2391 let mut vxl = build_4x4_min_solid_vxl();
2403 vxl.reserve_edit_capacity(8192);
2404 set_rect(&mut vxl, [0, 0, 50], [3, 3, 199], None);
2406 set_sphere_with_colfunc(&mut vxl, [1, 1, 128], 2, SpanOp::Insert, |_, _, z| {
2409 (0x80ff_ff00u32 as i32) | z
2410 });
2411 let mut b2 = vec![0i32; SCPITCH];
2413 expandrle(vxl.column_data(4 + 1), &mut b2);
2414 assert_eq!(b2[0], 0, "b2 first run top");
2415 assert_eq!(b2[1], 50, "b2 first run bot");
2416 assert_eq!(b2[2], 126, "b2 sphere run top");
2417 assert_eq!(b2[3], 131, "b2 sphere run bot");
2418 assert_eq!(b2[4], 200, "b2 third run top");
2419 assert_eq!(b2[5], MAXZDIM, "b2 third run bot");
2420
2421 let column = vxl.column_data(4 + 1).to_vec();
2425 let mut v = 0usize;
2426 let mut top_color = None;
2427 loop {
2428 let nextptr = column[v];
2429 let z1 = column[v + 1];
2430 if z1 == 126 {
2431 let off = v + 4;
2432 top_color = Some(u32::from_le_bytes([
2433 column[off],
2434 column[off + 1],
2435 column[off + 2],
2436 column[off + 3],
2437 ]));
2438 break;
2439 }
2440 if nextptr == 0 {
2441 break;
2442 }
2443 v += usize::from(nextptr) * 4;
2444 }
2445 assert_eq!(
2446 top_color,
2447 Some(0x80ff_ff7e),
2448 "exposed voxel at z=126 should have colfunc-derived colour"
2449 );
2450 }
2451
2452 #[test]
2453 fn set_spans_4x4_batch_carves_each_listed_column() {
2454 let mut vxl = build_4x4_min_solid_vxl();
2456 vxl.reserve_edit_capacity(8192);
2457 let spans: Vec<Vspan> = (0..4)
2458 .flat_map(|y| {
2459 (0..4).map(move |x| Vspan {
2460 x,
2461 y,
2462 z0: 50,
2463 z1: 99,
2464 })
2465 })
2466 .collect();
2467 set_spans(&mut vxl, &spans, None);
2468 for idx in 0..16 {
2470 let mut b2 = vec![0i32; SCPITCH];
2471 expandrle(vxl.column_data(idx), &mut b2);
2472 assert_eq!(b2[0], 0, "col {idx}");
2473 assert_eq!(b2[1], 50, "col {idx}");
2474 assert_eq!(b2[2], 100, "col {idx}");
2475 assert_eq!(b2[3], MAXZDIM, "col {idx}");
2476 }
2477 }
2478}