1#![allow(dead_code)]
23
24pub const MAXZDIM: i32 = 256;
26
27pub fn delslab(spans: &mut [i32], y0: i32, mut y1: i32) {
38 if y1 >= MAXZDIM {
39 y1 = MAXZDIM - 1;
40 }
41 if y0 >= y1 || spans.is_empty() {
42 return;
43 }
44 let mut z = 0usize;
45 while y0 >= spans[z + 1] {
46 z += 2;
47 }
48 if y0 > spans[z] {
49 if y1 < spans[z + 1] {
50 let mut i = z;
54 while spans[i + 1] < MAXZDIM {
55 i += 2;
56 }
57 while i > z {
58 spans[i + 3] = spans[i + 1];
59 spans[i + 2] = spans[i];
60 i -= 2;
61 }
62 spans[z + 3] = spans[z + 1];
63 spans[z + 1] = y0;
64 spans[z + 2] = y1;
65 return;
66 }
67 spans[z + 1] = y0;
70 z += 2;
71 }
72 if y1 >= spans[z + 1] {
73 let mut i = z + 2;
77 while y1 >= spans[i + 1] {
78 i += 2;
79 }
80 let delta = i - z;
81 spans[z] = spans[i];
82 spans[z + 1] = spans[i + 1];
83 while spans[i + 1] < MAXZDIM {
84 i += 2;
85 spans[i - delta] = spans[i];
86 spans[i - delta + 1] = spans[i + 1];
87 }
88 }
89 if y1 > spans[z] {
90 spans[z] = y1;
92 }
93}
94
95pub fn insslab(spans: &mut [i32], y0: i32, y1: i32) {
110 if y0 >= y1 || spans.is_empty() {
111 return;
112 }
113 let mut z = 0usize;
114 while y0 > spans[z + 1] {
115 z += 2;
116 }
117 if y1 < spans[z] {
118 let mut i = z;
122 while spans[i + 1] < MAXZDIM {
123 i += 2;
124 }
125 loop {
126 spans[i + 3] = spans[i + 1];
127 spans[i + 2] = spans[i];
128 if i == z {
129 break;
130 }
131 i -= 2;
132 }
133 spans[z + 1] = y1;
134 spans[z] = y0;
135 return;
136 }
137 if y0 < spans[z] {
138 spans[z] = y0;
140 }
141 if y1 >= spans[z + 2] && spans[z + 1] < MAXZDIM {
142 let mut i = z + 2;
146 while y1 >= spans[i + 2] && spans[i + 1] < MAXZDIM {
147 i += 2;
148 }
149 let delta = i - z;
150 spans[z + 1] = spans[i + 1];
151 while spans[i + 1] < MAXZDIM {
152 i += 2;
153 spans[i - delta] = spans[i];
154 spans[i - delta + 1] = spans[i + 1];
155 }
156 spans[i + 2 - delta] = MAXZDIM;
168 spans[i + 3 - delta] = MAXZDIM;
169 }
170 if y1 > spans[z + 1] {
171 spans[z + 1] = y1;
173 }
174}
175
176pub fn expandrle(slab: &[u8], uind: &mut [i32]) {
189 uind[0] = i32::from(slab[1]);
190 let mut i = 2usize;
191 let mut v = 0usize;
192 while slab[v] != 0 {
193 v += usize::from(slab[v]) * 4;
194 if slab[v + 3] >= slab[v + 1] {
195 continue;
196 }
197 uind[i - 1] = i32::from(slab[v + 3]);
198 uind[i] = i32::from(slab[v + 1]);
199 i += 2;
200 }
201 uind[i - 1] = MAXZDIM;
202}
203
204#[derive(Debug)]
208struct ColorRange<'s> {
209 z_start: i32,
210 z_end: i32,
211 colors: &'s [u8],
214}
215
216fn build_color_table(slab: &[u8]) -> Vec<ColorRange<'_>> {
221 let mut ranges = Vec::new();
222 let mut v = 0usize;
223 loop {
224 let z_start = i32::from(slab[v + 1]);
225 let z1c = i32::from(slab[v + 2]);
226 let z_end = z1c + 1;
227 let n_voxels = usize::try_from((z_end - z_start).max(0)).expect("voxel count >= 0");
228 let off = v + 4;
229 ranges.push(ColorRange {
230 z_start,
231 z_end,
232 colors: &slab[off..off + n_voxels * 4],
233 });
234
235 let nextptr = slab[v];
236 if nextptr == 0 {
237 break;
238 }
239 let prev_v = v;
240 v += usize::from(nextptr) * 4;
241 let ze = i32::from(slab[v + 3]);
242 let prev_z1 = i32::from(slab[prev_v + 1]);
249 let prev_z1c = i32::from(slab[prev_v + 2]);
250 let prev_nextptr = i32::from(slab[prev_v]);
251 let ceil_z_start = ze + prev_z1c - prev_z1 - prev_nextptr + 2;
252 let ceil_z_end = ze;
253 let ceil_n =
254 usize::try_from((ceil_z_end - ceil_z_start).max(0)).expect("ceiling voxel count >= 0");
255 let ceil_start = v - ceil_n * 4;
257 ranges.push(ColorRange {
258 z_start: ceil_z_start,
259 z_end: ceil_z_end,
260 colors: &slab[ceil_start..v],
261 });
262 }
263 ranges.push(ColorRange {
264 z_start: MAXZDIM,
265 z_end: MAXZDIM,
266 colors: &[],
267 });
268 ranges
269}
270
271#[allow(
291 clippy::too_many_arguments,
292 clippy::too_many_lines,
293 clippy::missing_panics_doc
294)]
295pub(crate) fn compilerle(
296 n0: &[i32],
297 n1: &[i32],
298 n2: &[i32],
299 n3: &[i32],
300 n4: &[i32],
301 cbuf: &mut [u8],
302 original_column: &[u8],
303 px: i32,
304 py: i32,
305 colfunc: &mut dyn FnMut(i32, i32, i32) -> i32,
306) -> usize {
307 let tbuf2 = build_color_table(original_column);
308
309 let mut p_z: i32 = n0[0];
310 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
315 let to_u8 = |v: i32| (v & 0xff) as u8;
316
317 cbuf[1] = to_u8(p_z);
318 let mut ze: i32 = n0[1];
319 cbuf[2] = to_u8(ze - 1);
320 cbuf[3] = 0;
321
322 let mut i = 0usize;
323 let mut onext = 0usize;
324 let mut ic = 0usize;
325 let mut ia: i32 = 15;
326 let mut n = 4usize;
327 let mut zend = if ze == MAXZDIM { -1 } else { ze - 1 };
328
329 let mut n1_idx = 0usize;
330 let mut n2_idx = 0usize;
331 let mut n3_idx = 0usize;
332 let mut n4_idx = 0usize;
333
334 'outer: loop {
335 let mut dacnt = 0;
336 'middle: loop {
337 let exit_to_rlendit2 = loop {
339 while p_z >= tbuf2[ic].z_end {
340 ic += 1;
341 }
342 let color: i32 = if p_z >= tbuf2[ic].z_start {
343 let off =
344 usize::try_from((p_z - tbuf2[ic].z_start) * 4).expect("color offset >= 0");
345 let bytes = &tbuf2[ic].colors[off..off + 4];
346 i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
347 } else {
348 colfunc(px, py, p_z)
349 };
350 cbuf[n..n + 4].copy_from_slice(&color.to_le_bytes());
351 n += 4;
352 p_z += 1;
353 if p_z >= ze {
354 break true; }
356 while p_z >= n1[n1_idx] {
357 n1_idx += 1;
358 ia ^= 1;
359 }
360 while p_z >= n2[n2_idx] {
361 n2_idx += 1;
362 ia ^= 2;
363 }
364 while p_z >= n3[n3_idx] {
365 n3_idx += 1;
366 ia ^= 4;
367 }
368 while p_z >= n4[n4_idx] {
369 n4_idx += 1;
370 ia ^= 8;
371 }
372 if !(ia != 0 || p_z == zend) {
373 break false; }
375 };
376
377 if exit_to_rlendit2 {
378 if ze >= MAXZDIM {
379 break 'outer;
380 }
381 i += 2;
382 cbuf[onext] = u8::try_from((n - onext) >> 2).expect("slab dword count fits in u8");
383 onext = n;
384 p_z = n0[i];
385 cbuf[n + 1] = to_u8(p_z);
386 cbuf[n + 3] = to_u8(ze);
387 ze = n0[i + 1];
388 cbuf[n + 2] = to_u8(ze - 1);
389 n += 4;
390 zend = if ze == MAXZDIM { -1 } else { ze - 1 };
391 break 'middle; }
393
394 if dacnt == 0 {
397 cbuf[onext + 2] = to_u8(p_z - 1);
398 dacnt = 1;
399 } else {
400 cbuf[onext] = u8::try_from((n - onext) >> 2).expect("slab dword count fits in u8");
401 onext = n;
402 cbuf[n + 1] = to_u8(p_z);
403 cbuf[n + 2] = to_u8(p_z - 1);
404 cbuf[n + 3] = to_u8(p_z);
405 n += 4;
406 }
407
408 let n1_v = n1[n1_idx];
410 let n2_v = n2[n2_idx];
411 let n3_v = n3[n3_idx];
412 let n4_v = n4[n4_idx];
413 if n1_v < n2_v && n1_v < n3_v && n1_v < n4_v {
414 if n1_v >= ze {
415 p_z = ze - 1;
416 } else {
417 p_z = n1_v;
418 n1_idx += 1;
419 ia ^= 1;
420 }
421 } else if n2_v < n3_v && n2_v < n4_v {
422 if n2_v >= ze {
423 p_z = ze - 1;
424 } else {
425 p_z = n2_v;
426 n2_idx += 1;
427 ia ^= 2;
428 }
429 } else if n3_v < n4_v {
430 if n3_v >= ze {
431 p_z = ze - 1;
432 } else {
433 p_z = n3_v;
434 n3_idx += 1;
435 ia ^= 4;
436 }
437 } else if n4_v >= ze {
438 p_z = ze - 1;
439 } else {
440 p_z = n4_v;
441 n4_idx += 1;
442 ia ^= 8;
443 }
444
445 if p_z == MAXZDIM - 1 {
446 break 'outer;
447 }
448 }
450 }
451
452 cbuf[onext] = 0;
453 n
454}
455
456use crate::color::VoxColor;
485use crate::vxl::Vxl;
486
487pub(crate) const SPAN_STRIDE: usize = 256;
492
493pub(crate) const MAXCSIZ: usize = 1028;
496
497const SCOY_NONE: i32 = i32::MIN;
500
501const ROW_BASE_INITIAL: usize = SPAN_STRIDE * 6;
503
504const ROW_BASE_WRAP: usize = SPAN_STRIDE * 9;
507
508pub struct ScumCtx<'v> {
520 vxl: &'v mut Vxl,
521 row_cache: Vec<i32>,
523 cbuf: Vec<u8>,
525 colfunc: Box<dyn FnMut(i32, i32, i32) -> i32 + 'v>,
528
529 scoy: i32,
531 cur_row_base: usize,
532 scx0: i32,
533 scx1: i32,
534 scox0: i32,
535 scox1: i32,
536 scoox0: i32,
537 scoox1: i32,
538 scex0: i32,
539 scex1: i32,
540 sceox0: i32,
541 sceox1: i32,
542
543 last_scum2: Option<(i32, i32)>,
550}
551
552#[allow(
553 clippy::cast_possible_truncation,
554 clippy::cast_possible_wrap,
555 clippy::cast_sign_loss,
556 clippy::if_not_else,
557 clippy::similar_names
558)]
559impl<'v> ScumCtx<'v> {
560 pub fn new(vxl: &'v mut Vxl) -> Self {
568 assert!(
569 !vxl.vbit.is_empty(),
570 "ScumCtx::new requires Vxl::reserve_edit_capacity to be called first"
571 );
572 let radar_size = (vxl.vsid as usize + 4) * 3 * SPAN_STRIDE;
573 Self {
574 vxl,
575 row_cache: vec![0i32; radar_size],
576 cbuf: vec![0u8; MAXCSIZ],
577 colfunc: Box::new(|_, _, _| 0),
578 scoy: SCOY_NONE,
579 cur_row_base: ROW_BASE_INITIAL,
580 scx0: 0,
581 scx1: 0,
582 scox0: 0,
583 scox1: 0,
584 scoox0: 0,
585 scoox1: 0,
586 scex0: 0,
587 scex1: 0,
588 sceox0: 0,
589 sceox1: 0,
590 last_scum2: None,
591 }
592 }
593
594 pub fn set_colfunc<F>(&mut self, mut f: F)
598 where
599 F: FnMut(i32, i32, i32) -> VoxColor + 'v,
600 {
601 #[allow(clippy::cast_possible_wrap)]
602 {
603 self.colfunc = Box::new(move |x, y, z| f(x, y, z).0 as i32);
604 }
605 }
606
607 pub fn scum2(&mut self, x: i32, y: i32) -> Option<&mut [i32]> {
613 let vsid = self.vxl.vsid as i32;
614 if x < 0 || x >= vsid || y < 0 || y >= vsid {
615 return None;
616 }
617
618 if y != self.scoy {
619 if self.scoy != SCOY_NONE {
620 self.scum2_line();
621 while self.scoy < y - 1 {
622 self.scx0 = i32::MAX;
623 self.scx1 = i32::MIN;
624 self.advance_row();
625 self.scum2_line();
626 }
627 self.advance_row();
628 } else {
629 self.scoox0 = i32::MAX;
630 self.scox0 = i32::MAX;
631 self.sceox0 = x + 1;
632 self.scex0 = x + 1;
633 self.sceox1 = x;
634 self.scex1 = x;
635 self.scoy = y;
636 self.cur_row_base = ROW_BASE_INITIAL;
637 }
638 self.scx0 = x;
639 } else {
640 while self.scx1 < x - 1 {
643 self.scx1 += 1;
644 let scx1 = self.scx1;
645 self.expand_column_into_row(scx1, y, self.cur_row_base);
646 }
647 }
648
649 let radar_idx = self.cur_row_base + (x as usize) * SPAN_STRIDE * 3;
650 self.scx1 = x;
651 self.expand_column_into_row(x, y, self.cur_row_base);
652 self.last_scum2 = Some((x, y));
653 Some(&mut self.row_cache[radar_idx..radar_idx + SPAN_STRIDE])
654 }
655
656 pub fn with_column<F>(&mut self, x: i32, y: i32, f: F) -> bool
667 where
668 F: FnOnce(&mut [i32]),
669 {
670 if self.last_scum2 != Some((x, y)) && self.scum2(x, y).is_none() {
671 return false;
672 }
673 let radar_idx = self.cur_row_base + (x as usize) * SPAN_STRIDE * 3;
676 let spans = &mut self.row_cache[radar_idx..radar_idx + SPAN_STRIDE];
677 f(spans);
678 true
679 }
680
681 pub fn finish(mut self) {
684 if self.scoy == SCOY_NONE {
685 return;
686 }
687 for _ in 0..2 {
688 self.scum2_line();
689 self.scx0 = i32::MAX;
690 self.scx1 = i32::MIN;
691 self.advance_row();
692 }
693 self.scum2_line();
694 self.scoy = SCOY_NONE;
695 }
696
697 fn advance_row(&mut self) {
703 self.scoy += 1;
704 self.cur_row_base += SPAN_STRIDE;
705 if self.cur_row_base == ROW_BASE_WRAP {
706 self.cur_row_base = ROW_BASE_INITIAL;
707 }
708 self.last_scum2 = None;
709 }
710
711 fn expand_column_into_row(&mut self, x: i32, y: i32, row_base: usize) {
716 let vsid = self.vxl.vsid as i32;
717 let radar_idx_signed = (row_base as isize) + (x as isize) * (SPAN_STRIDE as isize) * 3;
719 if radar_idx_signed < 0 {
720 return;
721 }
722 #[allow(clippy::cast_sign_loss)]
723 let radar_idx = radar_idx_signed as usize;
724 if radar_idx + SPAN_STRIDE > self.row_cache.len() {
725 return;
726 }
727 if x < 0 || x >= vsid || y < 0 || y >= vsid {
728 self.row_cache[radar_idx] = 0;
729 self.row_cache[radar_idx + 1] = MAXZDIM;
730 return;
731 }
732 let idx = (y as usize) * (vsid as usize) + (x as usize);
733 let slab = self.vxl.column_data(idx);
734 expandrle(
735 slab,
736 &mut self.row_cache[radar_idx..radar_idx + SPAN_STRIDE],
737 );
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.cur_row_base + SPAN_STRIDE);
755 let mptr = wrap_radar(uptr + SPAN_STRIDE);
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 cur_row_base = self.cur_row_base;
806 if x1 < self.scx0 || x0 > self.scx1 {
807 for x in x0..=x1 {
808 self.expand_column_into_row(x, scoy_0, cur_row_base);
809 }
810 } else {
811 for x in x0..self.scx0 {
812 self.expand_column_into_row(x, scoy_0, cur_row_base);
813 }
814 let mut x = x1;
815 while x > self.scx1 {
816 self.expand_column_into_row(x, scoy_0, cur_row_base);
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, cur_row_base);
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, cur_row_base: usize) {
842 let vsid = self.vxl.vsid as usize;
843 let k = (x as usize) * SPAN_STRIDE * 3;
844 let n0_pos = mptr + k;
845 let n1_pos_signed = (mptr as isize) + (k as isize) - (SPAN_STRIDE as isize) * 3;
846 let n2_pos = mptr + k + SPAN_STRIDE * 3;
847 let n3_pos = uptr + k;
848 let n4_pos = cur_row_base + 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 row_cache = &self.row_cache;
865 let n0 = &row_cache[n0_pos..n0_pos + SPAN_STRIDE];
866 let n1 = &row_cache[n1_pos..n1_pos + SPAN_STRIDE];
867 let n2 = &row_cache[n2_pos..n2_pos + SPAN_STRIDE];
868 let n3 = &row_cache[n3_pos..n3_pos + SPAN_STRIDE];
869 let n4 = &row_cache[n4_pos..n4_pos + SPAN_STRIDE];
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 == ROW_BASE_WRAP {
897 ROW_BASE_INITIAL
898 } else {
899 off
900 }
901}
902
903#[derive(Debug, Clone, Copy, PartialEq, Eq)]
917pub struct Vspan {
918 pub x: u32,
921 pub y: u32,
923 pub z0: u8,
926 pub z1: u8,
929}
930
931#[derive(Debug, Clone, Copy, PartialEq, Eq)]
941pub enum SpanOp {
942 Carve,
944 Insert,
946}
947
948#[allow(clippy::cast_possible_wrap)]
969pub fn set_spans_with_colfunc<F>(world: &mut Vxl, spans: &[Vspan], op: SpanOp, colfunc: F)
970where
971 F: FnMut(i32, i32, i32) -> VoxColor,
972{
973 if spans.is_empty() {
974 return;
975 }
976 let inserting = op == SpanOp::Insert;
977 let mut ctx = ScumCtx::new(world);
978 ctx.set_colfunc(colfunc);
979 for span in spans {
980 let x = span.x as i32;
981 let y = span.y as i32;
982 let z0 = i32::from(span.z0);
983 let z1 = i32::from(span.z1) + 1; ctx.with_column(x, y, |spans| {
985 if inserting {
986 insslab(spans, z0, z1);
987 } else {
988 delslab(spans, z0, z1);
989 }
990 });
991 }
992 ctx.finish();
993}
994
995pub fn set_spans(world: &mut Vxl, spans: &[Vspan], color: Option<VoxColor>) {
1009 let op = if color.is_some() {
1010 SpanOp::Insert
1011 } else {
1012 SpanOp::Carve
1013 };
1014 #[allow(clippy::cast_possible_wrap)]
1015 let c = color.unwrap_or(VoxColor(0));
1016 set_spans_with_colfunc(world, spans, op, move |_, _, _| c);
1017}
1018
1019pub fn set_cube(world: &mut Vxl, x: i32, y: i32, z: i32, color: Option<VoxColor>) {
1039 let op = if color.is_some() {
1040 SpanOp::Insert
1041 } else {
1042 SpanOp::Carve
1043 };
1044 #[allow(clippy::cast_possible_wrap)]
1045 let c = color.unwrap_or(VoxColor(0));
1046 set_cube_with_colfunc(world, x, y, z, op, move |_, _, _| c);
1047}
1048
1049#[allow(
1051 clippy::cast_possible_truncation,
1052 clippy::cast_possible_wrap,
1053 clippy::cast_sign_loss
1054)]
1055pub fn set_cube_with_colfunc<F>(world: &mut Vxl, x: i32, y: i32, z: i32, op: SpanOp, colfunc: F)
1056where
1057 F: FnMut(i32, i32, i32) -> VoxColor,
1058{
1059 let vsid = world.vsid as i32;
1060 if x < 0 || x >= vsid || y < 0 || y >= vsid || !(0..MAXZDIM).contains(&z) {
1061 return;
1062 }
1063 let span = Vspan {
1064 x: x as u32,
1065 y: y as u32,
1066 z0: z as u8,
1067 z1: z as u8,
1068 };
1069 set_spans_with_colfunc(world, &[span], op, colfunc);
1070}
1071
1072pub fn set_rect(world: &mut Vxl, lo: [i32; 3], hi: [i32; 3], color: Option<VoxColor>) {
1085 let op = if color.is_some() {
1086 SpanOp::Insert
1087 } else {
1088 SpanOp::Carve
1089 };
1090 #[allow(clippy::cast_possible_wrap)]
1091 let c = color.unwrap_or(VoxColor(0));
1092 set_rect_with_colfunc(world, lo, hi, op, move |_, _, _| c);
1093}
1094
1095#[allow(
1097 clippy::cast_possible_truncation,
1098 clippy::cast_possible_wrap,
1099 clippy::cast_sign_loss
1100)]
1101pub fn set_rect_with_colfunc<F>(world: &mut Vxl, lo: [i32; 3], hi: [i32; 3], op: SpanOp, colfunc: F)
1102where
1103 F: FnMut(i32, i32, i32) -> VoxColor,
1104{
1105 let vsid = world.vsid as i32;
1106 let xs = lo[0].min(hi[0]).max(0);
1107 let xe = lo[0].max(hi[0]).min(vsid - 1);
1108 let ys = lo[1].min(hi[1]).max(0);
1109 let ye = lo[1].max(hi[1]).min(vsid - 1);
1110 let zs = lo[2].min(hi[2]).max(0);
1111 let ze = lo[2].max(hi[2]).min(MAXZDIM - 1);
1112 if xs > xe || ys > ye || zs > ze {
1113 return;
1114 }
1115 let inserting = op == SpanOp::Insert;
1116 let mut ctx = ScumCtx::new(world);
1117 ctx.set_colfunc(colfunc);
1118 for y in ys..=ye {
1119 for x in xs..=xe {
1120 ctx.with_column(x, y, |spans| {
1121 if inserting {
1122 insslab(spans, zs, ze + 1);
1123 } else {
1124 delslab(spans, zs, ze + 1);
1125 }
1126 });
1127 }
1128 }
1129 ctx.finish();
1130}
1131
1132pub fn set_sphere(world: &mut Vxl, center: [i32; 3], radius: u32, color: Option<VoxColor>) {
1150 let op = if color.is_some() {
1151 SpanOp::Insert
1152 } else {
1153 SpanOp::Carve
1154 };
1155 #[allow(clippy::cast_possible_wrap)]
1156 let c = color.unwrap_or(VoxColor(0));
1157 set_sphere_with_colfunc(world, center, radius, op, move |_, _, _| c);
1158}
1159
1160#[allow(
1162 clippy::cast_possible_truncation,
1163 clippy::cast_possible_wrap,
1164 clippy::cast_sign_loss,
1165 clippy::cast_precision_loss,
1166 clippy::similar_names
1167)]
1168pub fn set_sphere_with_colfunc<F>(
1169 world: &mut Vxl,
1170 center: [i32; 3],
1171 radius: u32,
1172 op: SpanOp,
1173 colfunc: F,
1174) where
1175 F: FnMut(i32, i32, i32) -> VoxColor,
1176{
1177 let vsid = world.vsid as i32;
1178 let cx = center[0];
1179 let cy = center[1];
1180 let cz = center[2];
1181 let r = radius as i32;
1182 let xs = (cx - r).max(0);
1183 let xe = (cx + r).min(vsid - 1);
1184 let ys = (cy - r).max(0);
1185 let ye = (cy + r).min(vsid - 1);
1186 let zs = (cz - r).max(0);
1187 let ze = (cz + r).min(MAXZDIM - 1);
1188 if xs > xe || ys > ye || zs > ze {
1189 return;
1190 }
1191 let r_sq = r * r;
1192 let inserting = op == SpanOp::Insert;
1193 let mut ctx = ScumCtx::new(world);
1194 ctx.set_colfunc(colfunc);
1195 for y in ys..=ye {
1196 let dy = y - cy;
1197 let dy_sq = dy * dy;
1198 if dy_sq > r_sq {
1199 continue;
1200 }
1201 for x in xs..=xe {
1202 let dx = x - cx;
1203 let dx_sq = dx * dx;
1204 let xy_sq = dx_sq + dy_sq;
1205 if xy_sq > r_sq {
1206 continue;
1207 }
1208 let dz_max_sq = r_sq - xy_sq;
1211 let dz_max = (dz_max_sq as f32).sqrt() as i32;
1212 let z_lo = (cz - dz_max).max(zs);
1213 let z_hi = (cz + dz_max).min(ze);
1214 if z_lo > z_hi {
1215 continue;
1216 }
1217 ctx.with_column(x, y, |spans| {
1218 if inserting {
1219 insslab(spans, z_lo, z_hi + 1);
1220 } else {
1221 delslab(spans, z_lo, z_hi + 1);
1222 }
1223 });
1224 }
1225 }
1226 ctx.finish();
1227}
1228
1229#[cfg(test)]
1230#[allow(
1231 clippy::cast_possible_truncation,
1232 clippy::cast_possible_wrap,
1233 clippy::cast_sign_loss,
1234 clippy::items_after_statements
1235)]
1236mod tests {
1237 use super::*;
1238
1239 fn build_b2(slabs: &[(i32, i32)]) -> Vec<i32> {
1243 let mut buf: Vec<i32> = Vec::new();
1244 for &(top, bot) in slabs {
1245 assert!(top < bot, "slab top must be < bot");
1246 assert!(bot < MAXZDIM, "slab bot must fit below MAXZDIM");
1247 buf.push(top);
1248 buf.push(bot);
1249 }
1250 buf.push(MAXZDIM);
1253 buf.push(MAXZDIM);
1254 buf.resize(buf.len() + 32, 0);
1256 buf
1257 }
1258
1259 fn read_slabs(spans: &[i32]) -> Vec<(i32, i32)> {
1261 let mut out = Vec::new();
1262 let mut i = 0;
1263 while spans[i + 1] < MAXZDIM {
1264 out.push((spans[i], spans[i + 1]));
1265 i += 2;
1266 }
1267 out
1268 }
1269
1270 #[test]
1273 fn delslab_noop_y0_ge_y1() {
1274 let mut spans = build_b2(&[(10, 20)]);
1275 delslab(&mut spans, 15, 15);
1276 assert_eq!(read_slabs(&spans), [(10, 20)]);
1277 delslab(&mut spans, 20, 10);
1278 assert_eq!(read_slabs(&spans), [(10, 20)]);
1279 }
1280
1281 #[test]
1282 fn delslab_split_inside_one_slab() {
1283 let mut spans = build_b2(&[(10, 30)]);
1284 delslab(&mut spans, 15, 20);
1285 assert_eq!(read_slabs(&spans), [(10, 15), (20, 30)]);
1286 }
1287
1288 #[test]
1289 fn delslab_shrink_bot_of_slab() {
1290 let mut spans = build_b2(&[(10, 30)]);
1291 delslab(&mut spans, 20, 30);
1292 assert_eq!(read_slabs(&spans), [(10, 20)]);
1293 }
1294
1295 #[test]
1296 fn delslab_shrink_top_of_slab() {
1297 let mut spans = build_b2(&[(10, 30)]);
1298 delslab(&mut spans, 5, 15);
1299 assert_eq!(read_slabs(&spans), [(15, 30)]);
1300 }
1301
1302 #[test]
1303 fn delslab_carve_full_slab() {
1304 let mut spans = build_b2(&[(10, 30)]);
1305 delslab(&mut spans, 5, 35);
1306 assert_eq!(read_slabs(&spans), Vec::<(i32, i32)>::new());
1307 }
1308
1309 #[test]
1310 fn delslab_in_air_noop() {
1311 let mut spans = build_b2(&[(10, 30)]);
1312 delslab(&mut spans, 0, 8);
1313 assert_eq!(read_slabs(&spans), [(10, 30)]);
1314 delslab(&mut spans, 35, 50);
1315 assert_eq!(read_slabs(&spans), [(10, 30)]);
1316 }
1317
1318 #[test]
1319 fn delslab_span_two_slabs_carve_middle() {
1320 let mut spans = build_b2(&[(10, 30), (50, 70)]);
1321 delslab(&mut spans, 20, 60);
1322 assert_eq!(read_slabs(&spans), [(10, 20), (60, 70)]);
1323 }
1324
1325 #[test]
1326 fn delslab_carve_two_full_slabs_keep_third() {
1327 let mut spans = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1328 delslab(&mut spans, 5, 45);
1329 assert_eq!(read_slabs(&spans), [(50, 60)]);
1330 }
1331
1332 #[test]
1333 fn delslab_y1_clamped_to_maxzdim_minus_1() {
1334 let mut spans = build_b2(&[(10, 200)]);
1335 delslab(&mut spans, 100, MAXZDIM);
1336 assert_eq!(read_slabs(&spans), [(10, 100)]);
1337 }
1338
1339 #[test]
1340 fn delslab_carve_top_edge_of_slab() {
1341 let mut spans = build_b2(&[(10, 30)]);
1344 delslab(&mut spans, 5, 10);
1345 assert_eq!(read_slabs(&spans), [(10, 30)]);
1346 }
1347
1348 #[test]
1349 fn delslab_carve_bot_edge_of_slab() {
1350 let mut spans = build_b2(&[(10, 30)]);
1352 delslab(&mut spans, 30, 35);
1353 assert_eq!(read_slabs(&spans), [(10, 30)]);
1354 }
1355
1356 #[test]
1357 fn delslab_carve_exact_full_slab_keeps_neighbors() {
1358 let mut spans = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1359 delslab(&mut spans, 30, 40);
1360 assert_eq!(read_slabs(&spans), [(10, 20), (50, 60)]);
1361 }
1362
1363 #[test]
1366 fn insslab_noop_y0_ge_y1() {
1367 let mut spans = build_b2(&[(10, 20)]);
1368 insslab(&mut spans, 15, 15);
1369 assert_eq!(read_slabs(&spans), [(10, 20)]);
1370 insslab(&mut spans, 20, 10);
1371 assert_eq!(read_slabs(&spans), [(10, 20)]);
1372 }
1373
1374 #[test]
1375 fn insslab_into_pure_air() {
1376 let mut spans = build_b2(&[]);
1377 insslab(&mut spans, 10, 30);
1378 assert_eq!(read_slabs(&spans), [(10, 30)]);
1379 }
1380
1381 #[test]
1382 fn insslab_into_air_gap_above_slab() {
1383 let mut spans = build_b2(&[(50, 70)]);
1384 insslab(&mut spans, 10, 30);
1385 assert_eq!(read_slabs(&spans), [(10, 30), (50, 70)]);
1386 }
1387
1388 #[test]
1389 fn insslab_into_air_gap_between_slabs() {
1390 let mut spans = build_b2(&[(10, 20), (60, 70)]);
1391 insslab(&mut spans, 30, 50);
1392 assert_eq!(read_slabs(&spans), [(10, 20), (30, 50), (60, 70)]);
1393 }
1394
1395 #[test]
1396 fn insslab_into_air_gap_below_all_slabs() {
1397 let mut spans = build_b2(&[(10, 20)]);
1398 insslab(&mut spans, 30, 50);
1399 assert_eq!(read_slabs(&spans), [(10, 20), (30, 50)]);
1400 }
1401
1402 #[test]
1403 fn insslab_extend_top_of_slab() {
1404 let mut spans = build_b2(&[(50, 70)]);
1405 insslab(&mut spans, 30, 60);
1406 assert_eq!(read_slabs(&spans), [(30, 70)]);
1407 }
1408
1409 #[test]
1410 fn insslab_extend_bot_of_slab() {
1411 let mut spans = build_b2(&[(50, 70)]);
1412 insslab(&mut spans, 60, 80);
1413 assert_eq!(read_slabs(&spans), [(50, 80)]);
1414 }
1415
1416 #[test]
1417 fn insslab_merge_into_last_slab_writes_sentinel() {
1418 let mut spans: Vec<i32> = vec![100, 105, 255, MAXZDIM, MAXZDIM, MAXZDIM];
1429 spans.resize(spans.len() + 32, 0); insslab(&mut spans, 105, 255);
1431 assert_eq!(spans[0], 100);
1434 assert!(
1435 spans[1] >= MAXZDIM,
1436 "expected merged run to extend to MAXZDIM, got spans[1] = {}",
1437 spans[1]
1438 );
1439 assert!(
1442 spans[2] >= MAXZDIM,
1443 "spans[2] should be sentinel, got {} (pre-fix this was 255 from the un-shifted phantom slab)",
1444 spans[2]
1445 );
1446 }
1447
1448 #[test]
1449 fn insslab_touch_top_merges() {
1450 let mut spans = build_b2(&[(50, 70)]);
1452 insslab(&mut spans, 30, 50);
1453 assert_eq!(read_slabs(&spans), [(30, 70)]);
1454 }
1455
1456 #[test]
1457 fn insslab_touch_bot_merges() {
1458 let mut spans = build_b2(&[(50, 70)]);
1460 insslab(&mut spans, 70, 80);
1461 assert_eq!(read_slabs(&spans), [(50, 80)]);
1462 }
1463
1464 #[test]
1465 fn insslab_merge_two_slabs() {
1466 let mut spans = build_b2(&[(10, 30), (50, 70)]);
1467 insslab(&mut spans, 20, 60);
1468 assert_eq!(read_slabs(&spans), [(10, 70)]);
1469 }
1470
1471 #[test]
1472 fn insslab_engulf_inner_slabs() {
1473 let mut spans = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1474 insslab(&mut spans, 5, 70);
1475 assert_eq!(read_slabs(&spans), [(5, 70)]);
1476 }
1477
1478 #[test]
1479 fn insslab_engulf_then_keep_lower() {
1480 let mut spans = build_b2(&[(10, 20), (30, 40), (60, 80)]);
1481 insslab(&mut spans, 5, 50);
1482 assert_eq!(read_slabs(&spans), [(5, 50), (60, 80)]);
1483 }
1484
1485 #[test]
1486 fn insslab_engulf_then_merge_lower() {
1487 let mut spans = build_b2(&[(10, 20), (30, 40), (60, 80)]);
1488 insslab(&mut spans, 5, 60);
1489 assert_eq!(read_slabs(&spans), [(5, 80)]);
1490 }
1491
1492 #[test]
1493 fn insslab_chain_of_touching_inserts() {
1494 let mut spans = build_b2(&[]);
1495 insslab(&mut spans, 10, 20);
1496 insslab(&mut spans, 20, 30);
1497 insslab(&mut spans, 30, 40);
1498 assert_eq!(read_slabs(&spans), [(10, 40)]);
1499 }
1500
1501 #[test]
1502 fn insslab_carve_then_insert_round_trip() {
1503 let original = [(10, 50)];
1506 let mut spans = build_b2(&original);
1507 delslab(&mut spans, 20, 30);
1508 assert_eq!(read_slabs(&spans), [(10, 20), (30, 50)]);
1509 insslab(&mut spans, 20, 30);
1510 assert_eq!(read_slabs(&spans), original);
1511 }
1512
1513 #[test]
1514 fn insslab_into_sentinel_only_buffer_with_z_advance() {
1515 let mut spans = build_b2(&[(10, 20)]);
1517 insslab(&mut spans, 100, 150);
1518 assert_eq!(read_slabs(&spans), [(10, 20), (100, 150)]);
1519 }
1520
1521 fn read_uind(uind: &[i32]) -> Vec<(i32, i32)> {
1527 let mut out = Vec::new();
1528 let mut i = 0;
1529 while uind[i + 1] < MAXZDIM {
1530 out.push((uind[i], uind[i + 1]));
1531 i += 2;
1532 }
1533 out.push((uind[i], uind[i + 1]));
1535 out
1536 }
1537
1538 #[test]
1539 fn expandrle_single_slab_fully_solid_column() {
1540 let z1c = u8::try_from(MAXZDIM - 1).expect("MAXZDIM-1 fits in u8");
1544 let mut slab = vec![0u8, 0, z1c, 0];
1545 slab.extend(std::iter::repeat_n(0u8, (MAXZDIM as usize) * 4));
1546 let mut uind = vec![0i32; 16];
1547 expandrle(&slab, &mut uind);
1548 assert_eq!(uind[0], 0);
1550 assert_eq!(uind[1], MAXZDIM);
1551 }
1552
1553 #[test]
1554 fn expandrle_single_slab_partial_floor() {
1555 let slab = [0u8, 64, 66, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0];
1559 let mut uind = vec![0i32; 16];
1560 expandrle(&slab, &mut uind);
1561 assert_eq!(uind[0], 64);
1562 assert_eq!(uind[1], MAXZDIM);
1563 }
1564
1565 #[test]
1566 fn expandrle_two_slabs_with_cave() {
1567 let slab = [
1586 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 50, 52, 30, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0, 0, ];
1593 let mut uind = vec![0i32; 16];
1594 expandrle(&slab, &mut uind);
1595 assert_eq!(uind[0], 10);
1596 assert_eq!(uind[1], 30);
1597 assert_eq!(uind[2], 50);
1598 assert_eq!(uind[3], MAXZDIM);
1599 }
1600
1601 #[test]
1602 fn expandrle_skips_degenerate_slab_with_no_ceiling_gap() {
1603 let slab = [
1611 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 20, 22, 20, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0, 0, ];
1616 let mut uind = vec![0i32; 16];
1617 expandrle(&slab, &mut uind);
1618 assert_eq!(uind[0], 10);
1620 assert_eq!(uind[1], MAXZDIM);
1621 }
1622
1623 #[test]
1624 fn expandrle_round_trips_through_b2_helpers() {
1625 let slab = [
1629 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 50, 52, 30, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0,
1630 0,
1631 ];
1632 let mut uind = vec![0i32; 16];
1633 expandrle(&slab, &mut uind);
1634 let runs = read_uind(&uind[..4]);
1635 assert_eq!(runs, [(10, 30), (50, MAXZDIM)]);
1636 }
1637
1638 fn all_air_neighbor() -> Vec<i32> {
1644 let mut buf = vec![MAXZDIM, MAXZDIM];
1646 buf.resize(buf.len() + MAXZDIM as usize, MAXZDIM);
1647 buf
1648 }
1649
1650 fn b2_from_runs(runs: &[(i32, i32)]) -> Vec<i32> {
1654 let mut buf = Vec::new();
1655 for &(top, bot) in runs {
1656 buf.push(top);
1657 buf.push(bot);
1658 }
1659 buf.push(MAXZDIM);
1660 buf.push(MAXZDIM);
1661 buf.resize(buf.len() + MAXZDIM as usize, MAXZDIM);
1662 buf
1663 }
1664
1665 #[test]
1666 fn build_color_table_single_slab_one_floor_voxel() {
1667 let slab = [0u8, 10, 10, 0, 0xa1, 0xa2, 0xa3, 0xa4];
1669 let table = build_color_table(&slab);
1670 assert_eq!(table.len(), 2);
1671 assert_eq!(table[0].z_start, 10);
1672 assert_eq!(table[0].z_end, 11);
1673 assert_eq!(table[0].colors, &[0xa1, 0xa2, 0xa3, 0xa4]);
1674 assert_eq!(table[1].z_start, MAXZDIM);
1676 assert_eq!(table[1].z_end, MAXZDIM);
1677 }
1678
1679 #[test]
1680 fn build_color_table_two_slabs_with_ceiling() {
1681 let slab = [
1686 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, ];
1695 let table = build_color_table(&slab);
1696 assert_eq!(table.len(), 4);
1697 assert_eq!(table[0].z_start, 10);
1699 assert_eq!(table[0].z_end, 11);
1700 assert_eq!(table[0].colors.len(), 4);
1701 assert_eq!(table[1].z_start, 28);
1703 assert_eq!(table[1].z_end, 30);
1704 assert_eq!(
1705 table[1].colors,
1706 &[0xc0, 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc1]
1707 );
1708 assert_eq!(table[2].z_start, 50);
1710 assert_eq!(table[2].z_end, 53);
1711 assert_eq!(table[2].colors.len(), 12);
1712 assert_eq!(table[3].z_start, MAXZDIM);
1714 }
1715
1716 #[test]
1717 fn compilerle_round_trip_single_slab_solid_to_maxzdim() {
1718 let mut slab = vec![0u8, 10, (MAXZDIM - 1) as u8, 0];
1722 for z in 10..MAXZDIM {
1723 slab.extend_from_slice(&[z as u8, (z + 1) as u8, (z + 2) as u8, 0]);
1725 }
1726
1727 let mut spans = vec![0i32; (MAXZDIM as usize) + 4];
1729 expandrle(&slab, &mut spans);
1730 assert_eq!(spans[0], 10);
1731 assert_eq!(spans[1], MAXZDIM);
1732
1733 let n_air = all_air_neighbor();
1735 let mut cbuf = vec![0u8; 1028];
1736 let mut colfunc_called = 0;
1737 let mut colfunc = |_x: i32, _y: i32, _z: i32| -> i32 {
1738 colfunc_called += 1;
1739 0
1740 };
1741 let written = compilerle(
1742 &spans,
1743 &n_air,
1744 &n_air,
1745 &n_air,
1746 &n_air,
1747 &mut cbuf,
1748 &slab,
1749 0,
1750 0,
1751 &mut colfunc,
1752 );
1753 assert_eq!(colfunc_called, 0, "all colors should come from tbuf2");
1754
1755 assert_eq!(written, slab.len());
1758 assert_eq!(&cbuf[..written], &slab[..]);
1759
1760 let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
1762 expandrle(&cbuf[..written], &mut b2_round);
1763 assert_eq!(b2_round[0], 10);
1764 assert_eq!(b2_round[1], MAXZDIM);
1765 }
1766
1767 #[test]
1768 fn compilerle_round_trip_two_solid_runs_with_cave() {
1769 let dummy = vec![0u8, 0, (MAXZDIM - 1) as u8, 0];
1779 let mut dummy_full = dummy;
1780 dummy_full.extend(std::iter::repeat_n(0u8, (MAXZDIM as usize) * 4));
1781
1782 let n_air = all_air_neighbor();
1783 let spans = b2_from_runs(&[(10, 30), (50, MAXZDIM)]);
1784 let mut seed = vec![0u8; 1028];
1785 let mut colfunc = |_x: i32, _y: i32, z: i32| -> i32 { z };
1786 let seed_len = compilerle(
1787 &spans,
1788 &n_air,
1789 &n_air,
1790 &n_air,
1791 &n_air,
1792 &mut seed,
1793 &dummy_full,
1794 0,
1795 0,
1796 &mut colfunc,
1797 );
1798 seed.truncate(seed_len);
1799
1800 let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
1802 expandrle(&seed, &mut b2_round);
1803 assert_eq!(b2_round[0], 10);
1805 assert_eq!(b2_round[1], 30);
1806 assert_eq!(b2_round[2], 50);
1807 assert_eq!(b2_round[3], MAXZDIM);
1808
1809 let mut cbuf = vec![0u8; 1028];
1813 let mut never_called = 0;
1814 let mut colfunc2 = |_x: i32, _y: i32, _z: i32| -> i32 {
1815 never_called += 1;
1816 0
1817 };
1818 let written = compilerle(
1819 &spans,
1820 &n_air,
1821 &n_air,
1822 &n_air,
1823 &n_air,
1824 &mut cbuf,
1825 &seed,
1826 0,
1827 0,
1828 &mut colfunc2,
1829 );
1830 assert_eq!(never_called, 0, "second pass needs no colfunc");
1831 assert_eq!(written, seed_len);
1832 assert_eq!(&cbuf[..written], &seed[..]);
1833 }
1834
1835 #[test]
1836 fn compilerle_buried_voxel_optimization_with_all_solid_neighbors() {
1837 let spans = b2_from_runs(&[(10, MAXZDIM)]);
1845 let n_solid = b2_from_runs(&[(0, MAXZDIM)]);
1846 let mut slab = vec![0u8, 10, (MAXZDIM - 1) as u8, 0];
1850 for z in 10..MAXZDIM {
1851 slab.extend_from_slice(&[z as u8, 0, 0, 0]);
1852 }
1853 let mut cbuf = vec![0u8; 1028];
1854 let mut colfunc_called = 0;
1855 let mut colfunc = |_x: i32, _y: i32, _z: i32| -> i32 {
1856 colfunc_called += 1;
1857 0
1858 };
1859 let written = compilerle(
1860 &spans,
1861 &n_solid,
1862 &n_solid,
1863 &n_solid,
1864 &n_solid,
1865 &mut cbuf,
1866 &slab,
1867 0,
1868 0,
1869 &mut colfunc,
1870 );
1871 assert_eq!(colfunc_called, 0, "tbuf2 should cover every voxel");
1872 assert_eq!(written, 8);
1875 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];
1882 expandrle(&cbuf[..written], &mut b2_round);
1883 assert_eq!(b2_round[0], 10);
1884 assert_eq!(b2_round[1], MAXZDIM);
1885 }
1886
1887 fn build_1x1_min_solid_vxl() -> Vxl {
1892 let column = vec![0u8, 0, 0, 0, 0xff, 0x80, 0x40, 0x20];
1893 let column_offset = vec![0u32, column.len() as u32].into_boxed_slice();
1894 Vxl {
1895 vsid: 1,
1896 ipo: [0.0; 3],
1897 ist: [1.0, 0.0, 0.0],
1898 ihe: [0.0, 0.0, 1.0],
1899 ifo: [0.0, 1.0, 0.0],
1900 data: column.into_boxed_slice(),
1901 column_offset,
1902 mip_base_offsets: Box::new([0, 2]),
1903 vbit: Box::new([]),
1904 vbiti: 0,
1905 }
1906 }
1907
1908 #[test]
1909 fn scum2_no_edit_round_trip_1x1_minimal_column() {
1910 let mut vxl = build_1x1_min_solid_vxl();
1914 vxl.reserve_edit_capacity(4096);
1915
1916 let mut ctx = ScumCtx::new(&mut vxl);
1917 let _b2 = ctx.scum2(0, 0).expect("column 0,0 in bounds");
1918 ctx.finish();
1919
1920 let column = vxl.column_data(0);
1921 let mut b2_after = vec![0i32; SPAN_STRIDE];
1922 expandrle(column, &mut b2_after);
1923 assert_eq!(b2_after[0], 0);
1924 assert_eq!(b2_after[1], MAXZDIM);
1925 }
1926
1927 #[test]
1928 fn scum2_carve_edit_1x1_creates_air_gap() {
1929 let mut vxl = build_1x1_min_solid_vxl();
1932 vxl.reserve_edit_capacity(4096);
1933
1934 let mut ctx = ScumCtx::new(&mut vxl);
1935 ctx.set_colfunc(|_x, _y, _z| VoxColor(0x80_60_40_20));
1936 {
1937 let spans = ctx.scum2(0, 0).expect("column 0,0 in bounds");
1938 delslab(spans, 50, 100);
1940 }
1941 ctx.finish();
1942
1943 let column = vxl.column_data(0);
1944 let mut b2_after = vec![0i32; SPAN_STRIDE];
1945 expandrle(column, &mut b2_after);
1946 assert_eq!(b2_after[0], 0);
1948 assert_eq!(b2_after[1], 50);
1949 assert_eq!(b2_after[2], 100);
1950 assert_eq!(b2_after[3], MAXZDIM);
1951 }
1952
1953 fn build_4x4_min_solid_vxl() -> Vxl {
1956 const COL: [u8; 8] = [0, 0, 0, 0, 0xff, 0x80, 0x40, 0x20];
1957 let mut data = Vec::with_capacity(16 * 8);
1958 let mut offsets = Vec::with_capacity(17);
1959 for i in 0..16 {
1960 offsets.push((i * 8) as u32);
1961 data.extend_from_slice(&COL);
1962 }
1963 offsets.push((16 * 8) as u32);
1964 Vxl {
1965 vsid: 4,
1966 ipo: [0.0; 3],
1967 ist: [1.0, 0.0, 0.0],
1968 ihe: [0.0, 0.0, 1.0],
1969 ifo: [0.0, 1.0, 0.0],
1970 data: data.into_boxed_slice(),
1971 column_offset: offsets.into_boxed_slice(),
1972 mip_base_offsets: Box::new([0, 17]),
1973 vbit: Box::new([]),
1974 vbiti: 0,
1975 }
1976 }
1977
1978 #[test]
1979 fn scum2_batch_edits_multiple_columns_same_row() {
1980 let mut vxl = build_4x4_min_solid_vxl();
1983 vxl.reserve_edit_capacity(8192);
1984
1985 let mut ctx = ScumCtx::new(&mut vxl);
1986 ctx.set_colfunc(|_x, _y, _z| VoxColor(0));
1987 {
1988 let spans = ctx.scum2(1, 2).unwrap();
1989 delslab(spans, 50, 100);
1990 }
1991 {
1992 let spans = ctx.scum2(2, 2).unwrap();
1993 delslab(spans, 50, 100);
1994 }
1995 ctx.finish();
1996
1997 for x in [1, 2] {
1998 let idx = 2 * 4 + x;
1999 let mut b2_after = vec![0i32; SPAN_STRIDE];
2000 expandrle(vxl.column_data(idx), &mut b2_after);
2001 assert_eq!(b2_after[0], 0);
2002 assert_eq!(b2_after[1], 50);
2003 assert_eq!(b2_after[2], 100);
2004 assert_eq!(b2_after[3], MAXZDIM);
2005 }
2006 for x in [0, 3] {
2008 let idx = 2 * 4 + x;
2009 let mut b2_after = vec![0i32; SPAN_STRIDE];
2010 expandrle(vxl.column_data(idx), &mut b2_after);
2011 assert_eq!(b2_after[0], 0);
2012 assert_eq!(b2_after[1], MAXZDIM);
2013 }
2014 }
2015
2016 #[test]
2017 fn scum2_batch_edits_across_rows() {
2018 let mut vxl = build_4x4_min_solid_vxl();
2021 vxl.reserve_edit_capacity(8192);
2022
2023 let mut ctx = ScumCtx::new(&mut vxl);
2024 ctx.set_colfunc(|_x, _y, _z| VoxColor(0));
2025 {
2026 let spans = ctx.scum2(1, 1).unwrap();
2027 delslab(spans, 60, 80);
2028 }
2029 {
2030 let spans = ctx.scum2(1, 2).unwrap();
2031 delslab(spans, 60, 80);
2032 }
2033 ctx.finish();
2034
2035 for y in [1, 2] {
2036 let idx = y * 4 + 1;
2037 let mut b2_after = vec![0i32; SPAN_STRIDE];
2038 expandrle(vxl.column_data(idx), &mut b2_after);
2039 assert_eq!(b2_after[0], 0);
2040 assert_eq!(b2_after[1], 60);
2041 assert_eq!(b2_after[2], 80);
2042 assert_eq!(b2_after[3], MAXZDIM);
2043 }
2044 }
2045
2046 #[test]
2047 fn scum2_finish_without_any_edit_is_noop() {
2048 let mut vxl = build_1x1_min_solid_vxl();
2050 vxl.reserve_edit_capacity(4096);
2051 let original = vxl.column_data(0).to_vec();
2052 let ctx = ScumCtx::new(&mut vxl);
2053 ctx.finish();
2054 assert_eq!(vxl.column_data(0), &original[..]);
2055 }
2056
2057 #[test]
2058 fn scum2_returns_none_for_out_of_bounds() {
2059 let mut vxl = build_1x1_min_solid_vxl();
2060 vxl.reserve_edit_capacity(4096);
2061 let mut ctx = ScumCtx::new(&mut vxl);
2062 assert!(ctx.scum2(-1, 0).is_none());
2063 assert!(ctx.scum2(0, -1).is_none());
2064 assert!(ctx.scum2(1, 0).is_none());
2065 assert!(ctx.scum2(0, 1).is_none());
2066 ctx.finish();
2067 }
2068
2069 #[test]
2072 fn set_spans_empty_is_noop() {
2073 let mut vxl = build_1x1_min_solid_vxl();
2074 let original = vxl.column_data(0).to_vec();
2075 set_spans(&mut vxl, &[], None);
2076 assert_eq!(vxl.column_data(0), &original[..]);
2080 }
2081
2082 #[test]
2083 fn set_spans_single_carve_creates_air_gap() {
2084 let mut vxl = build_1x1_min_solid_vxl();
2085 vxl.reserve_edit_capacity(4096);
2086 set_spans(
2087 &mut vxl,
2088 &[Vspan {
2089 x: 0,
2090 y: 0,
2091 z0: 50,
2092 z1: 99,
2093 }],
2094 None,
2095 );
2096 let mut spans = vec![0i32; SPAN_STRIDE];
2097 expandrle(vxl.column_data(0), &mut spans);
2098 assert_eq!(spans[0], 0);
2100 assert_eq!(spans[1], 50);
2101 assert_eq!(spans[2], 100);
2102 assert_eq!(spans[3], MAXZDIM);
2103 }
2104
2105 #[test]
2106 fn set_spans_multi_span_same_column_accumulates() {
2107 let mut vxl = build_1x1_min_solid_vxl();
2111 vxl.reserve_edit_capacity(4096);
2112 set_spans(
2113 &mut vxl,
2114 &[
2115 Vspan {
2116 x: 0,
2117 y: 0,
2118 z0: 30,
2119 z1: 49,
2120 },
2121 Vspan {
2122 x: 0,
2123 y: 0,
2124 z0: 100,
2125 z1: 119,
2126 },
2127 ],
2128 None,
2129 );
2130 let mut spans = vec![0i32; SPAN_STRIDE];
2131 expandrle(vxl.column_data(0), &mut spans);
2132 assert_eq!(spans[0], 0);
2134 assert_eq!(spans[1], 30);
2135 assert_eq!(spans[2], 50);
2136 assert_eq!(spans[3], 100);
2137 assert_eq!(spans[4], 120);
2138 assert_eq!(spans[5], MAXZDIM);
2139 }
2140
2141 #[test]
2142 fn set_spans_insert_color_fills_air() {
2143 let mut vxl = build_1x1_min_solid_vxl();
2146 vxl.reserve_edit_capacity(4096);
2147 set_spans(
2149 &mut vxl,
2150 &[Vspan {
2151 x: 0,
2152 y: 0,
2153 z0: 50,
2154 z1: 99,
2155 }],
2156 None,
2157 );
2158 const FILL: VoxColor = VoxColor(0x80_aa_bb_cc);
2160 set_spans(
2161 &mut vxl,
2162 &[Vspan {
2163 x: 0,
2164 y: 0,
2165 z0: 60,
2166 z1: 79,
2167 }],
2168 Some(FILL),
2169 );
2170 let mut spans = vec![0i32; SPAN_STRIDE];
2171 expandrle(vxl.column_data(0), &mut spans);
2172 assert_eq!(spans[0], 0);
2174 assert_eq!(spans[1], 50);
2175 assert_eq!(spans[2], 60);
2176 assert_eq!(spans[3], 80);
2177 assert_eq!(spans[4], 100);
2178 assert_eq!(spans[5], MAXZDIM);
2179 }
2180
2181 #[test]
2182 fn set_spans_skips_out_of_bounds_silently() {
2183 let mut vxl = build_1x1_min_solid_vxl();
2184 vxl.reserve_edit_capacity(4096);
2185 set_spans(
2186 &mut vxl,
2187 &[Vspan {
2188 x: 7,
2189 y: 9,
2190 z0: 50,
2191 z1: 99,
2192 }],
2193 None,
2194 );
2195 let mut spans = vec![0i32; SPAN_STRIDE];
2197 expandrle(vxl.column_data(0), &mut spans);
2198 assert_eq!(spans[0], 0);
2199 assert_eq!(spans[1], MAXZDIM);
2200 }
2201
2202 #[test]
2203 fn set_spans_with_colfunc_z_dependent_colour() {
2204 let mut vxl = build_4x4_min_solid_vxl();
2211 vxl.reserve_edit_capacity(8192);
2212 let carve_spans: Vec<Vspan> = (0..4)
2215 .flat_map(|y| {
2216 (0..4).map(move |x| Vspan {
2217 x,
2218 y,
2219 z0: 50,
2220 z1: 99,
2221 })
2222 })
2223 .collect();
2224 set_spans(&mut vxl, &carve_spans, None);
2225 set_spans_with_colfunc(
2228 &mut vxl,
2229 &[Vspan {
2230 x: 1,
2231 y: 1,
2232 z0: 60,
2233 z1: 79,
2234 }],
2235 SpanOp::Insert,
2236 |_x, _y, z| VoxColor(0x80ff_ff00 | z as u32),
2237 );
2238 let idx = 4 + 1; let column = vxl.column_data(idx);
2242 let mut v = 0usize;
2243 let mut found = false;
2244 loop {
2245 let nextptr = column[v];
2246 let z1 = column[v + 1];
2247 if z1 == 60 {
2248 let z1c = column[v + 2];
2249 assert_eq!(z1c, 79, "z1c");
2250 let n_voxels = usize::from(z1c) - usize::from(z1) + 1;
2251 for i in 0..n_voxels {
2252 let off = v + 4 + i * 4;
2253 let c = u32::from_le_bytes([
2254 column[off],
2255 column[off + 1],
2256 column[off + 2],
2257 column[off + 3],
2258 ]);
2259 let z = u32::from(z1) + (i as u32);
2260 assert_eq!(
2261 c,
2262 0x80ff_ff00 | z,
2263 "z={z}: expected colour {:#010x}, got {:#010x}",
2264 0x80ff_ff00 | z,
2265 c
2266 );
2267 }
2268 found = true;
2269 break;
2270 }
2271 if nextptr == 0 {
2272 break;
2273 }
2274 v += usize::from(nextptr) * 4;
2275 }
2276 assert!(found, "did not find a slab with z1=60");
2277 }
2278
2279 #[test]
2282 fn set_cube_carves_single_voxel() {
2283 let mut vxl = build_4x4_min_solid_vxl();
2284 vxl.reserve_edit_capacity(4096);
2285 set_cube(&mut vxl, 1, 1, 100, None);
2286 let mut spans = vec![0i32; SPAN_STRIDE];
2287 expandrle(vxl.column_data(4 + 1), &mut spans);
2288 assert_eq!(spans[0], 0);
2290 assert_eq!(spans[1], 100);
2291 assert_eq!(spans[2], 101);
2292 assert_eq!(spans[3], MAXZDIM);
2293 }
2294
2295 #[test]
2296 fn set_cube_skips_oob() {
2297 let mut vxl = build_4x4_min_solid_vxl();
2298 vxl.reserve_edit_capacity(4096);
2299 set_cube(&mut vxl, -1, 1, 100, None);
2301 set_cube(&mut vxl, 5, 1, 100, None);
2302 set_cube(&mut vxl, 1, 1, 256, None);
2303 let mut spans = vec![0i32; SPAN_STRIDE];
2305 expandrle(vxl.column_data(4 + 1), &mut spans);
2306 assert_eq!(spans[0], 0);
2307 assert_eq!(spans[1], MAXZDIM);
2308 }
2309
2310 #[test]
2311 fn set_rect_carves_aabb() {
2312 let mut vxl = build_4x4_min_solid_vxl();
2313 vxl.reserve_edit_capacity(8192);
2314 set_rect(&mut vxl, [1, 1, 50], [2, 2, 99], None);
2316 for y in 1..=2 {
2317 for x in 1..=2 {
2318 let idx = (y * 4 + x) as usize;
2319 let mut spans = vec![0i32; SPAN_STRIDE];
2320 expandrle(vxl.column_data(idx), &mut spans);
2321 assert_eq!(spans[0], 0, "col ({x},{y})");
2322 assert_eq!(spans[1], 50, "col ({x},{y})");
2323 assert_eq!(spans[2], 100, "col ({x},{y})");
2324 assert_eq!(spans[3], MAXZDIM, "col ({x},{y})");
2325 }
2326 }
2327 for &(x, y) in &[(0, 0), (3, 3)] {
2329 let idx = (y * 4 + x) as usize;
2330 let mut spans = vec![0i32; SPAN_STRIDE];
2331 expandrle(vxl.column_data(idx), &mut spans);
2332 assert_eq!(spans[0], 0);
2333 assert_eq!(spans[1], MAXZDIM);
2334 }
2335 }
2336
2337 #[test]
2338 fn set_rect_clamps_to_world() {
2339 let mut vxl = build_4x4_min_solid_vxl();
2340 vxl.reserve_edit_capacity(8192);
2341 set_rect(&mut vxl, [-10, -10, -10], [100, 100, 1000], None);
2344 for idx in 0..16 {
2346 let mut spans = vec![0i32; SPAN_STRIDE];
2347 expandrle(vxl.column_data(idx), &mut spans);
2348 assert_eq!(spans[0], 255, "col {idx}");
2352 assert_eq!(spans[1], MAXZDIM, "col {idx}");
2353 }
2354 }
2355
2356 #[test]
2357 fn set_sphere_carves_centred_sphere() {
2358 let mut vxl = build_4x4_min_solid_vxl();
2361 vxl.reserve_edit_capacity(8192);
2362 set_sphere(&mut vxl, [1, 1, 128], 1, None);
2363 let mut spans = vec![0i32; SPAN_STRIDE];
2367 expandrle(vxl.column_data(4 + 1), &mut spans);
2368 assert_eq!(spans[0], 0);
2369 assert_eq!(spans[1], 127);
2370 assert_eq!(spans[2], 130);
2371 assert_eq!(spans[3], MAXZDIM);
2372 let mut spans = vec![0i32; SPAN_STRIDE];
2374 expandrle(vxl.column_data(4), &mut spans);
2375 assert_eq!(spans[0], 0);
2376 assert_eq!(spans[1], 128);
2377 assert_eq!(spans[2], 129);
2378 assert_eq!(spans[3], MAXZDIM);
2379 }
2380
2381 #[test]
2382 fn set_sphere_radius_zero_is_single_voxel() {
2383 let mut vxl = build_4x4_min_solid_vxl();
2384 vxl.reserve_edit_capacity(4096);
2385 set_sphere(&mut vxl, [1, 1, 100], 0, None);
2386 let mut spans = vec![0i32; SPAN_STRIDE];
2388 expandrle(vxl.column_data(4 + 1), &mut spans);
2389 assert_eq!(spans[0], 0);
2390 assert_eq!(spans[1], 100);
2391 assert_eq!(spans[2], 101);
2392 assert_eq!(spans[3], MAXZDIM);
2393 }
2394
2395 #[test]
2396 fn set_sphere_with_colfunc_position_dependent_color() {
2397 let mut vxl = build_4x4_min_solid_vxl();
2409 vxl.reserve_edit_capacity(8192);
2410 set_rect(&mut vxl, [0, 0, 50], [3, 3, 199], None);
2412 set_sphere_with_colfunc(&mut vxl, [1, 1, 128], 2, SpanOp::Insert, |_, _, z| {
2415 VoxColor(0x80ff_ff00 | z as u32)
2416 });
2417 let mut spans = vec![0i32; SPAN_STRIDE];
2419 expandrle(vxl.column_data(4 + 1), &mut spans);
2420 assert_eq!(spans[0], 0, "spans first run top");
2421 assert_eq!(spans[1], 50, "spans first run bot");
2422 assert_eq!(spans[2], 126, "spans sphere run top");
2423 assert_eq!(spans[3], 131, "spans sphere run bot");
2424 assert_eq!(spans[4], 200, "spans third run top");
2425 assert_eq!(spans[5], MAXZDIM, "spans third run bot");
2426
2427 let column = vxl.column_data(4 + 1).to_vec();
2431 let mut v = 0usize;
2432 let mut top_color = None;
2433 loop {
2434 let nextptr = column[v];
2435 let z1 = column[v + 1];
2436 if z1 == 126 {
2437 let off = v + 4;
2438 top_color = Some(u32::from_le_bytes([
2439 column[off],
2440 column[off + 1],
2441 column[off + 2],
2442 column[off + 3],
2443 ]));
2444 break;
2445 }
2446 if nextptr == 0 {
2447 break;
2448 }
2449 v += usize::from(nextptr) * 4;
2450 }
2451 assert_eq!(
2452 top_color,
2453 Some(0x80ff_ff7e),
2454 "exposed voxel at z=126 should have colfunc-derived colour"
2455 );
2456 }
2457
2458 #[test]
2459 fn set_spans_4x4_batch_carves_each_listed_column() {
2460 let mut vxl = build_4x4_min_solid_vxl();
2462 vxl.reserve_edit_capacity(8192);
2463 let spans: Vec<Vspan> = (0..4)
2464 .flat_map(|y| {
2465 (0..4).map(move |x| Vspan {
2466 x,
2467 y,
2468 z0: 50,
2469 z1: 99,
2470 })
2471 })
2472 .collect();
2473 set_spans(&mut vxl, &spans, None);
2474 for idx in 0..16 {
2476 let mut spans = vec![0i32; SPAN_STRIDE];
2477 expandrle(vxl.column_data(idx), &mut spans);
2478 assert_eq!(spans[0], 0, "col {idx}");
2479 assert_eq!(spans[1], 50, "col {idx}");
2480 assert_eq!(spans[2], 100, "col {idx}");
2481 assert_eq!(spans[3], MAXZDIM, "col {idx}");
2482 }
2483 }
2484}