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::vxl::Vxl;
485
486pub(crate) const SPAN_STRIDE: usize = 256;
491
492pub(crate) const MAXCSIZ: usize = 1028;
495
496const SCOY_NONE: i32 = i32::MIN;
499
500const ROW_BASE_INITIAL: usize = SPAN_STRIDE * 6;
502
503const ROW_BASE_WRAP: usize = SPAN_STRIDE * 9;
506
507pub struct ScumCtx<'v> {
519 vxl: &'v mut Vxl,
520 row_cache: Vec<i32>,
522 cbuf: Vec<u8>,
524 colfunc: Box<dyn FnMut(i32, i32, i32) -> i32 + 'v>,
527
528 scoy: i32,
530 cur_row_base: usize,
531 scx0: i32,
532 scx1: i32,
533 scox0: i32,
534 scox1: i32,
535 scoox0: i32,
536 scoox1: i32,
537 scex0: i32,
538 scex1: i32,
539 sceox0: i32,
540 sceox1: i32,
541
542 last_scum2: Option<(i32, i32)>,
549}
550
551#[allow(
552 clippy::cast_possible_truncation,
553 clippy::cast_possible_wrap,
554 clippy::cast_sign_loss,
555 clippy::if_not_else,
556 clippy::similar_names
557)]
558impl<'v> ScumCtx<'v> {
559 pub fn new(vxl: &'v mut Vxl) -> Self {
567 assert!(
568 !vxl.vbit.is_empty(),
569 "ScumCtx::new requires Vxl::reserve_edit_capacity to be called first"
570 );
571 let radar_size = (vxl.vsid as usize + 4) * 3 * SPAN_STRIDE;
572 Self {
573 vxl,
574 row_cache: vec![0i32; radar_size],
575 cbuf: vec![0u8; MAXCSIZ],
576 colfunc: Box::new(|_, _, _| 0),
577 scoy: SCOY_NONE,
578 cur_row_base: ROW_BASE_INITIAL,
579 scx0: 0,
580 scx1: 0,
581 scox0: 0,
582 scox1: 0,
583 scoox0: 0,
584 scoox1: 0,
585 scex0: 0,
586 scex1: 0,
587 sceox0: 0,
588 sceox1: 0,
589 last_scum2: None,
590 }
591 }
592
593 pub fn set_colfunc<F>(&mut self, f: F)
596 where
597 F: FnMut(i32, i32, i32) -> i32 + 'v,
598 {
599 self.colfunc = Box::new(f);
600 }
601
602 pub fn scum2(&mut self, x: i32, y: i32) -> Option<&mut [i32]> {
608 let vsid = self.vxl.vsid as i32;
609 if x < 0 || x >= vsid || y < 0 || y >= vsid {
610 return None;
611 }
612
613 if y != self.scoy {
614 if self.scoy != SCOY_NONE {
615 self.scum2_line();
616 while self.scoy < y - 1 {
617 self.scx0 = i32::MAX;
618 self.scx1 = i32::MIN;
619 self.advance_row();
620 self.scum2_line();
621 }
622 self.advance_row();
623 } else {
624 self.scoox0 = i32::MAX;
625 self.scox0 = i32::MAX;
626 self.sceox0 = x + 1;
627 self.scex0 = x + 1;
628 self.sceox1 = x;
629 self.scex1 = x;
630 self.scoy = y;
631 self.cur_row_base = ROW_BASE_INITIAL;
632 }
633 self.scx0 = x;
634 } else {
635 while self.scx1 < x - 1 {
638 self.scx1 += 1;
639 let scx1 = self.scx1;
640 self.expand_column_into_row(scx1, y, self.cur_row_base);
641 }
642 }
643
644 let radar_idx = self.cur_row_base + (x as usize) * SPAN_STRIDE * 3;
645 self.scx1 = x;
646 self.expand_column_into_row(x, y, self.cur_row_base);
647 self.last_scum2 = Some((x, y));
648 Some(&mut self.row_cache[radar_idx..radar_idx + SPAN_STRIDE])
649 }
650
651 pub fn with_column<F>(&mut self, x: i32, y: i32, f: F) -> bool
662 where
663 F: FnOnce(&mut [i32]),
664 {
665 if self.last_scum2 != Some((x, y)) && self.scum2(x, y).is_none() {
666 return false;
667 }
668 let radar_idx = self.cur_row_base + (x as usize) * SPAN_STRIDE * 3;
671 let spans = &mut self.row_cache[radar_idx..radar_idx + SPAN_STRIDE];
672 f(spans);
673 true
674 }
675
676 pub fn finish(mut self) {
679 if self.scoy == SCOY_NONE {
680 return;
681 }
682 for _ in 0..2 {
683 self.scum2_line();
684 self.scx0 = i32::MAX;
685 self.scx1 = i32::MIN;
686 self.advance_row();
687 }
688 self.scum2_line();
689 self.scoy = SCOY_NONE;
690 }
691
692 fn advance_row(&mut self) {
698 self.scoy += 1;
699 self.cur_row_base += SPAN_STRIDE;
700 if self.cur_row_base == ROW_BASE_WRAP {
701 self.cur_row_base = ROW_BASE_INITIAL;
702 }
703 self.last_scum2 = None;
704 }
705
706 fn expand_column_into_row(&mut self, x: i32, y: i32, row_base: usize) {
711 let vsid = self.vxl.vsid as i32;
712 let radar_idx_signed = (row_base as isize) + (x as isize) * (SPAN_STRIDE as isize) * 3;
714 if radar_idx_signed < 0 {
715 return;
716 }
717 #[allow(clippy::cast_sign_loss)]
718 let radar_idx = radar_idx_signed as usize;
719 if radar_idx + SPAN_STRIDE > self.row_cache.len() {
720 return;
721 }
722 if x < 0 || x >= vsid || y < 0 || y >= vsid {
723 self.row_cache[radar_idx] = 0;
724 self.row_cache[radar_idx + 1] = MAXZDIM;
725 return;
726 }
727 let idx = (y as usize) * (vsid as usize) + (x as usize);
728 let slab = self.vxl.column_data(idx);
729 expandrle(
730 slab,
731 &mut self.row_cache[radar_idx..radar_idx + SPAN_STRIDE],
732 );
733 }
734
735 #[allow(clippy::too_many_lines)]
738 fn scum2_line(&mut self) {
739 let vsid = self.vxl.vsid as i32;
740
741 let x0 = (self.scox0 - 1).min(self.scx0).min(self.scoox0);
743 self.scoox0 = self.scox0;
744 self.scox0 = self.scx0;
745 let x1 = (self.scox1 + 1).max(self.scx1).max(self.scoox1);
746 self.scoox1 = self.scox1;
747 self.scox1 = self.scx1;
748
749 let uptr = wrap_radar(self.cur_row_base + SPAN_STRIDE);
750 let mptr = wrap_radar(uptr + SPAN_STRIDE);
751
752 let scoy_2 = self.scoy - 2;
754 if x1 < self.sceox0 || x0 > self.sceox1 {
755 for x in x0..=x1 {
756 self.expand_column_into_row(x, scoy_2, uptr);
757 }
758 } else {
759 for x in x0..self.sceox0 {
760 self.expand_column_into_row(x, scoy_2, uptr);
761 }
762 let mut x = x1;
763 while x > self.sceox1 {
764 self.expand_column_into_row(x, scoy_2, uptr);
765 x -= 1;
766 }
767 }
768
769 let scoy_1 = self.scoy - 1;
771 if (self.scex1 | x1) >= 0 {
772 for x in (x1 + 2)..self.scex0 {
773 self.expand_column_into_row(x, scoy_1, mptr);
774 }
775 let mut x = x0 - 2;
776 while x > self.scex1 {
777 self.expand_column_into_row(x, scoy_1, mptr);
778 x -= 1;
779 }
780 }
781 if x1 + 1 < self.scex0 || x0 - 1 > self.scex1 {
782 for x in (x0 - 1)..=(x1 + 1) {
783 self.expand_column_into_row(x, scoy_1, mptr);
784 }
785 } else {
786 for x in (x0 - 1)..self.scex0 {
787 self.expand_column_into_row(x, scoy_1, mptr);
788 }
789 let mut x = x1 + 1;
790 while x > self.scex1 {
791 self.expand_column_into_row(x, scoy_1, mptr);
792 x -= 1;
793 }
794 }
795 self.sceox0 = (x0 - 1).min(self.scex0);
796 self.sceox1 = (x1 + 1).max(self.scex1);
797
798 let scoy_0 = self.scoy;
800 let cur_row_base = self.cur_row_base;
801 if x1 < self.scx0 || x0 > self.scx1 {
802 for x in x0..=x1 {
803 self.expand_column_into_row(x, scoy_0, cur_row_base);
804 }
805 } else {
806 for x in x0..self.scx0 {
807 self.expand_column_into_row(x, scoy_0, cur_row_base);
808 }
809 let mut x = x1;
810 while x > self.scx1 {
811 self.expand_column_into_row(x, scoy_0, cur_row_base);
812 x -= 1;
813 }
814 }
815 self.scex0 = x0;
816 self.scex1 = x1;
817
818 let y = self.scoy - 1;
821 if !(0..vsid).contains(&y) {
822 return;
823 }
824 let x0_clamped = x0.max(0);
825 let x1_clamped = x1.min(vsid - 1);
826
827 for x in x0_clamped..=x1_clamped {
828 self.flush_column(x, y, mptr, uptr, cur_row_base);
829 }
830 }
831
832 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
836 fn flush_column(&mut self, x: i32, y: i32, mptr: usize, uptr: usize, cur_row_base: usize) {
837 let vsid = self.vxl.vsid as usize;
838 let k = (x as usize) * SPAN_STRIDE * 3;
839 let n0_pos = mptr + k;
840 let n1_pos_signed = (mptr as isize) + (k as isize) - (SPAN_STRIDE as isize) * 3;
841 let n2_pos = mptr + k + SPAN_STRIDE * 3;
842 let n3_pos = uptr + k;
843 let n4_pos = cur_row_base + k;
844
845 if n1_pos_signed < 0 {
848 return;
849 }
850 let n1_pos = n1_pos_signed as usize;
851
852 let idx = (y as usize) * vsid + (x as usize);
853
854 let original_bytes: Vec<u8> = self.vxl.column_data(idx).to_vec();
857
858 let written = {
859 let row_cache = &self.row_cache;
860 let n0 = &row_cache[n0_pos..n0_pos + SPAN_STRIDE];
861 let n1 = &row_cache[n1_pos..n1_pos + SPAN_STRIDE];
862 let n2 = &row_cache[n2_pos..n2_pos + SPAN_STRIDE];
863 let n3 = &row_cache[n3_pos..n3_pos + SPAN_STRIDE];
864 let n4 = &row_cache[n4_pos..n4_pos + SPAN_STRIDE];
865 compilerle(
866 n0,
867 n1,
868 n2,
869 n3,
870 n4,
871 &mut self.cbuf,
872 &original_bytes,
873 x,
874 y,
875 &mut *self.colfunc,
876 )
877 };
878
879 let old_offset = self.vxl.column_offset[idx];
880 self.vxl.voxdealloc(old_offset);
881 let new_offset = self.vxl.voxalloc(written as u32);
882 self.vxl.data[new_offset as usize..new_offset as usize + written]
883 .copy_from_slice(&self.cbuf[..written]);
884 self.vxl.column_offset[idx] = new_offset;
885 }
886}
887
888fn wrap_radar(off: usize) -> usize {
891 if off == ROW_BASE_WRAP {
892 ROW_BASE_INITIAL
893 } else {
894 off
895 }
896}
897
898#[derive(Debug, Clone, Copy, PartialEq, Eq)]
912pub struct Vspan {
913 pub x: u32,
914 pub y: u32,
915 pub z0: u8,
916 pub z1: u8,
917}
918
919#[derive(Debug, Clone, Copy, PartialEq, Eq)]
929pub enum SpanOp {
930 Carve,
931 Insert,
932}
933
934#[allow(clippy::cast_possible_wrap)]
955pub fn set_spans_with_colfunc<F>(world: &mut Vxl, spans: &[Vspan], op: SpanOp, colfunc: F)
956where
957 F: FnMut(i32, i32, i32) -> i32,
958{
959 if spans.is_empty() {
960 return;
961 }
962 let inserting = op == SpanOp::Insert;
963 let mut ctx = ScumCtx::new(world);
964 ctx.set_colfunc(colfunc);
965 for span in spans {
966 let x = span.x as i32;
967 let y = span.y as i32;
968 let z0 = i32::from(span.z0);
969 let z1 = i32::from(span.z1) + 1; ctx.with_column(x, y, |spans| {
971 if inserting {
972 insslab(spans, z0, z1);
973 } else {
974 delslab(spans, z0, z1);
975 }
976 });
977 }
978 ctx.finish();
979}
980
981pub fn set_spans(world: &mut Vxl, spans: &[Vspan], color: Option<u32>) {
995 let op = if color.is_some() {
996 SpanOp::Insert
997 } else {
998 SpanOp::Carve
999 };
1000 #[allow(clippy::cast_possible_wrap)]
1001 let c_i32 = color.unwrap_or(0) as i32;
1002 set_spans_with_colfunc(world, spans, op, move |_, _, _| c_i32);
1003}
1004
1005pub fn set_cube(world: &mut Vxl, x: i32, y: i32, z: i32, color: Option<u32>) {
1025 let op = if color.is_some() {
1026 SpanOp::Insert
1027 } else {
1028 SpanOp::Carve
1029 };
1030 #[allow(clippy::cast_possible_wrap)]
1031 let c_i32 = color.unwrap_or(0) as i32;
1032 set_cube_with_colfunc(world, x, y, z, op, move |_, _, _| c_i32);
1033}
1034
1035#[allow(
1037 clippy::cast_possible_truncation,
1038 clippy::cast_possible_wrap,
1039 clippy::cast_sign_loss
1040)]
1041pub fn set_cube_with_colfunc<F>(world: &mut Vxl, x: i32, y: i32, z: i32, op: SpanOp, colfunc: F)
1042where
1043 F: FnMut(i32, i32, i32) -> i32,
1044{
1045 let vsid = world.vsid as i32;
1046 if x < 0 || x >= vsid || y < 0 || y >= vsid || !(0..MAXZDIM).contains(&z) {
1047 return;
1048 }
1049 let span = Vspan {
1050 x: x as u32,
1051 y: y as u32,
1052 z0: z as u8,
1053 z1: z as u8,
1054 };
1055 set_spans_with_colfunc(world, &[span], op, colfunc);
1056}
1057
1058pub fn set_rect(world: &mut Vxl, lo: [i32; 3], hi: [i32; 3], color: Option<u32>) {
1071 let op = if color.is_some() {
1072 SpanOp::Insert
1073 } else {
1074 SpanOp::Carve
1075 };
1076 #[allow(clippy::cast_possible_wrap)]
1077 let c_i32 = color.unwrap_or(0) as i32;
1078 set_rect_with_colfunc(world, lo, hi, op, move |_, _, _| c_i32);
1079}
1080
1081#[allow(
1083 clippy::cast_possible_truncation,
1084 clippy::cast_possible_wrap,
1085 clippy::cast_sign_loss
1086)]
1087pub fn set_rect_with_colfunc<F>(world: &mut Vxl, lo: [i32; 3], hi: [i32; 3], op: SpanOp, colfunc: F)
1088where
1089 F: FnMut(i32, i32, i32) -> i32,
1090{
1091 let vsid = world.vsid as i32;
1092 let xs = lo[0].min(hi[0]).max(0);
1093 let xe = lo[0].max(hi[0]).min(vsid - 1);
1094 let ys = lo[1].min(hi[1]).max(0);
1095 let ye = lo[1].max(hi[1]).min(vsid - 1);
1096 let zs = lo[2].min(hi[2]).max(0);
1097 let ze = lo[2].max(hi[2]).min(MAXZDIM - 1);
1098 if xs > xe || ys > ye || zs > ze {
1099 return;
1100 }
1101 let inserting = op == SpanOp::Insert;
1102 let mut ctx = ScumCtx::new(world);
1103 ctx.set_colfunc(colfunc);
1104 for y in ys..=ye {
1105 for x in xs..=xe {
1106 ctx.with_column(x, y, |spans| {
1107 if inserting {
1108 insslab(spans, zs, ze + 1);
1109 } else {
1110 delslab(spans, zs, ze + 1);
1111 }
1112 });
1113 }
1114 }
1115 ctx.finish();
1116}
1117
1118pub fn set_sphere(world: &mut Vxl, center: [i32; 3], radius: u32, color: Option<u32>) {
1136 let op = if color.is_some() {
1137 SpanOp::Insert
1138 } else {
1139 SpanOp::Carve
1140 };
1141 #[allow(clippy::cast_possible_wrap)]
1142 let c_i32 = color.unwrap_or(0) as i32;
1143 set_sphere_with_colfunc(world, center, radius, op, move |_, _, _| c_i32);
1144}
1145
1146#[allow(
1148 clippy::cast_possible_truncation,
1149 clippy::cast_possible_wrap,
1150 clippy::cast_sign_loss,
1151 clippy::cast_precision_loss,
1152 clippy::similar_names
1153)]
1154pub fn set_sphere_with_colfunc<F>(
1155 world: &mut Vxl,
1156 center: [i32; 3],
1157 radius: u32,
1158 op: SpanOp,
1159 colfunc: F,
1160) where
1161 F: FnMut(i32, i32, i32) -> i32,
1162{
1163 let vsid = world.vsid as i32;
1164 let cx = center[0];
1165 let cy = center[1];
1166 let cz = center[2];
1167 let r = radius as i32;
1168 let xs = (cx - r).max(0);
1169 let xe = (cx + r).min(vsid - 1);
1170 let ys = (cy - r).max(0);
1171 let ye = (cy + r).min(vsid - 1);
1172 let zs = (cz - r).max(0);
1173 let ze = (cz + r).min(MAXZDIM - 1);
1174 if xs > xe || ys > ye || zs > ze {
1175 return;
1176 }
1177 let r_sq = r * r;
1178 let inserting = op == SpanOp::Insert;
1179 let mut ctx = ScumCtx::new(world);
1180 ctx.set_colfunc(colfunc);
1181 for y in ys..=ye {
1182 let dy = y - cy;
1183 let dy_sq = dy * dy;
1184 if dy_sq > r_sq {
1185 continue;
1186 }
1187 for x in xs..=xe {
1188 let dx = x - cx;
1189 let dx_sq = dx * dx;
1190 let xy_sq = dx_sq + dy_sq;
1191 if xy_sq > r_sq {
1192 continue;
1193 }
1194 let dz_max_sq = r_sq - xy_sq;
1197 let dz_max = (dz_max_sq as f32).sqrt() as i32;
1198 let z_lo = (cz - dz_max).max(zs);
1199 let z_hi = (cz + dz_max).min(ze);
1200 if z_lo > z_hi {
1201 continue;
1202 }
1203 ctx.with_column(x, y, |spans| {
1204 if inserting {
1205 insslab(spans, z_lo, z_hi + 1);
1206 } else {
1207 delslab(spans, z_lo, z_hi + 1);
1208 }
1209 });
1210 }
1211 }
1212 ctx.finish();
1213}
1214
1215#[cfg(test)]
1216#[allow(
1217 clippy::cast_possible_truncation,
1218 clippy::cast_possible_wrap,
1219 clippy::cast_sign_loss,
1220 clippy::items_after_statements
1221)]
1222mod tests {
1223 use super::*;
1224
1225 fn build_b2(slabs: &[(i32, i32)]) -> Vec<i32> {
1229 let mut buf: Vec<i32> = Vec::new();
1230 for &(top, bot) in slabs {
1231 assert!(top < bot, "slab top must be < bot");
1232 assert!(bot < MAXZDIM, "slab bot must fit below MAXZDIM");
1233 buf.push(top);
1234 buf.push(bot);
1235 }
1236 buf.push(MAXZDIM);
1239 buf.push(MAXZDIM);
1240 buf.resize(buf.len() + 32, 0);
1242 buf
1243 }
1244
1245 fn read_slabs(spans: &[i32]) -> Vec<(i32, i32)> {
1247 let mut out = Vec::new();
1248 let mut i = 0;
1249 while spans[i + 1] < MAXZDIM {
1250 out.push((spans[i], spans[i + 1]));
1251 i += 2;
1252 }
1253 out
1254 }
1255
1256 #[test]
1259 fn delslab_noop_y0_ge_y1() {
1260 let mut spans = build_b2(&[(10, 20)]);
1261 delslab(&mut spans, 15, 15);
1262 assert_eq!(read_slabs(&spans), [(10, 20)]);
1263 delslab(&mut spans, 20, 10);
1264 assert_eq!(read_slabs(&spans), [(10, 20)]);
1265 }
1266
1267 #[test]
1268 fn delslab_split_inside_one_slab() {
1269 let mut spans = build_b2(&[(10, 30)]);
1270 delslab(&mut spans, 15, 20);
1271 assert_eq!(read_slabs(&spans), [(10, 15), (20, 30)]);
1272 }
1273
1274 #[test]
1275 fn delslab_shrink_bot_of_slab() {
1276 let mut spans = build_b2(&[(10, 30)]);
1277 delslab(&mut spans, 20, 30);
1278 assert_eq!(read_slabs(&spans), [(10, 20)]);
1279 }
1280
1281 #[test]
1282 fn delslab_shrink_top_of_slab() {
1283 let mut spans = build_b2(&[(10, 30)]);
1284 delslab(&mut spans, 5, 15);
1285 assert_eq!(read_slabs(&spans), [(15, 30)]);
1286 }
1287
1288 #[test]
1289 fn delslab_carve_full_slab() {
1290 let mut spans = build_b2(&[(10, 30)]);
1291 delslab(&mut spans, 5, 35);
1292 assert_eq!(read_slabs(&spans), Vec::<(i32, i32)>::new());
1293 }
1294
1295 #[test]
1296 fn delslab_in_air_noop() {
1297 let mut spans = build_b2(&[(10, 30)]);
1298 delslab(&mut spans, 0, 8);
1299 assert_eq!(read_slabs(&spans), [(10, 30)]);
1300 delslab(&mut spans, 35, 50);
1301 assert_eq!(read_slabs(&spans), [(10, 30)]);
1302 }
1303
1304 #[test]
1305 fn delslab_span_two_slabs_carve_middle() {
1306 let mut spans = build_b2(&[(10, 30), (50, 70)]);
1307 delslab(&mut spans, 20, 60);
1308 assert_eq!(read_slabs(&spans), [(10, 20), (60, 70)]);
1309 }
1310
1311 #[test]
1312 fn delslab_carve_two_full_slabs_keep_third() {
1313 let mut spans = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1314 delslab(&mut spans, 5, 45);
1315 assert_eq!(read_slabs(&spans), [(50, 60)]);
1316 }
1317
1318 #[test]
1319 fn delslab_y1_clamped_to_maxzdim_minus_1() {
1320 let mut spans = build_b2(&[(10, 200)]);
1321 delslab(&mut spans, 100, MAXZDIM);
1322 assert_eq!(read_slabs(&spans), [(10, 100)]);
1323 }
1324
1325 #[test]
1326 fn delslab_carve_top_edge_of_slab() {
1327 let mut spans = build_b2(&[(10, 30)]);
1330 delslab(&mut spans, 5, 10);
1331 assert_eq!(read_slabs(&spans), [(10, 30)]);
1332 }
1333
1334 #[test]
1335 fn delslab_carve_bot_edge_of_slab() {
1336 let mut spans = build_b2(&[(10, 30)]);
1338 delslab(&mut spans, 30, 35);
1339 assert_eq!(read_slabs(&spans), [(10, 30)]);
1340 }
1341
1342 #[test]
1343 fn delslab_carve_exact_full_slab_keeps_neighbors() {
1344 let mut spans = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1345 delslab(&mut spans, 30, 40);
1346 assert_eq!(read_slabs(&spans), [(10, 20), (50, 60)]);
1347 }
1348
1349 #[test]
1352 fn insslab_noop_y0_ge_y1() {
1353 let mut spans = build_b2(&[(10, 20)]);
1354 insslab(&mut spans, 15, 15);
1355 assert_eq!(read_slabs(&spans), [(10, 20)]);
1356 insslab(&mut spans, 20, 10);
1357 assert_eq!(read_slabs(&spans), [(10, 20)]);
1358 }
1359
1360 #[test]
1361 fn insslab_into_pure_air() {
1362 let mut spans = build_b2(&[]);
1363 insslab(&mut spans, 10, 30);
1364 assert_eq!(read_slabs(&spans), [(10, 30)]);
1365 }
1366
1367 #[test]
1368 fn insslab_into_air_gap_above_slab() {
1369 let mut spans = build_b2(&[(50, 70)]);
1370 insslab(&mut spans, 10, 30);
1371 assert_eq!(read_slabs(&spans), [(10, 30), (50, 70)]);
1372 }
1373
1374 #[test]
1375 fn insslab_into_air_gap_between_slabs() {
1376 let mut spans = build_b2(&[(10, 20), (60, 70)]);
1377 insslab(&mut spans, 30, 50);
1378 assert_eq!(read_slabs(&spans), [(10, 20), (30, 50), (60, 70)]);
1379 }
1380
1381 #[test]
1382 fn insslab_into_air_gap_below_all_slabs() {
1383 let mut spans = build_b2(&[(10, 20)]);
1384 insslab(&mut spans, 30, 50);
1385 assert_eq!(read_slabs(&spans), [(10, 20), (30, 50)]);
1386 }
1387
1388 #[test]
1389 fn insslab_extend_top_of_slab() {
1390 let mut spans = build_b2(&[(50, 70)]);
1391 insslab(&mut spans, 30, 60);
1392 assert_eq!(read_slabs(&spans), [(30, 70)]);
1393 }
1394
1395 #[test]
1396 fn insslab_extend_bot_of_slab() {
1397 let mut spans = build_b2(&[(50, 70)]);
1398 insslab(&mut spans, 60, 80);
1399 assert_eq!(read_slabs(&spans), [(50, 80)]);
1400 }
1401
1402 #[test]
1403 fn insslab_merge_into_last_slab_writes_sentinel() {
1404 let mut spans: Vec<i32> = vec![100, 105, 255, MAXZDIM, MAXZDIM, MAXZDIM];
1415 spans.resize(spans.len() + 32, 0); insslab(&mut spans, 105, 255);
1417 assert_eq!(spans[0], 100);
1420 assert!(
1421 spans[1] >= MAXZDIM,
1422 "expected merged run to extend to MAXZDIM, got spans[1] = {}",
1423 spans[1]
1424 );
1425 assert!(
1428 spans[2] >= MAXZDIM,
1429 "spans[2] should be sentinel, got {} (pre-fix this was 255 from the un-shifted phantom slab)",
1430 spans[2]
1431 );
1432 }
1433
1434 #[test]
1435 fn insslab_touch_top_merges() {
1436 let mut spans = build_b2(&[(50, 70)]);
1438 insslab(&mut spans, 30, 50);
1439 assert_eq!(read_slabs(&spans), [(30, 70)]);
1440 }
1441
1442 #[test]
1443 fn insslab_touch_bot_merges() {
1444 let mut spans = build_b2(&[(50, 70)]);
1446 insslab(&mut spans, 70, 80);
1447 assert_eq!(read_slabs(&spans), [(50, 80)]);
1448 }
1449
1450 #[test]
1451 fn insslab_merge_two_slabs() {
1452 let mut spans = build_b2(&[(10, 30), (50, 70)]);
1453 insslab(&mut spans, 20, 60);
1454 assert_eq!(read_slabs(&spans), [(10, 70)]);
1455 }
1456
1457 #[test]
1458 fn insslab_engulf_inner_slabs() {
1459 let mut spans = build_b2(&[(10, 20), (30, 40), (50, 60)]);
1460 insslab(&mut spans, 5, 70);
1461 assert_eq!(read_slabs(&spans), [(5, 70)]);
1462 }
1463
1464 #[test]
1465 fn insslab_engulf_then_keep_lower() {
1466 let mut spans = build_b2(&[(10, 20), (30, 40), (60, 80)]);
1467 insslab(&mut spans, 5, 50);
1468 assert_eq!(read_slabs(&spans), [(5, 50), (60, 80)]);
1469 }
1470
1471 #[test]
1472 fn insslab_engulf_then_merge_lower() {
1473 let mut spans = build_b2(&[(10, 20), (30, 40), (60, 80)]);
1474 insslab(&mut spans, 5, 60);
1475 assert_eq!(read_slabs(&spans), [(5, 80)]);
1476 }
1477
1478 #[test]
1479 fn insslab_chain_of_touching_inserts() {
1480 let mut spans = build_b2(&[]);
1481 insslab(&mut spans, 10, 20);
1482 insslab(&mut spans, 20, 30);
1483 insslab(&mut spans, 30, 40);
1484 assert_eq!(read_slabs(&spans), [(10, 40)]);
1485 }
1486
1487 #[test]
1488 fn insslab_carve_then_insert_round_trip() {
1489 let original = [(10, 50)];
1492 let mut spans = build_b2(&original);
1493 delslab(&mut spans, 20, 30);
1494 assert_eq!(read_slabs(&spans), [(10, 20), (30, 50)]);
1495 insslab(&mut spans, 20, 30);
1496 assert_eq!(read_slabs(&spans), original);
1497 }
1498
1499 #[test]
1500 fn insslab_into_sentinel_only_buffer_with_z_advance() {
1501 let mut spans = build_b2(&[(10, 20)]);
1503 insslab(&mut spans, 100, 150);
1504 assert_eq!(read_slabs(&spans), [(10, 20), (100, 150)]);
1505 }
1506
1507 fn read_uind(uind: &[i32]) -> Vec<(i32, i32)> {
1513 let mut out = Vec::new();
1514 let mut i = 0;
1515 while uind[i + 1] < MAXZDIM {
1516 out.push((uind[i], uind[i + 1]));
1517 i += 2;
1518 }
1519 out.push((uind[i], uind[i + 1]));
1521 out
1522 }
1523
1524 #[test]
1525 fn expandrle_single_slab_fully_solid_column() {
1526 let z1c = u8::try_from(MAXZDIM - 1).expect("MAXZDIM-1 fits in u8");
1530 let mut slab = vec![0u8, 0, z1c, 0];
1531 slab.extend(std::iter::repeat_n(0u8, (MAXZDIM as usize) * 4));
1532 let mut uind = vec![0i32; 16];
1533 expandrle(&slab, &mut uind);
1534 assert_eq!(uind[0], 0);
1536 assert_eq!(uind[1], MAXZDIM);
1537 }
1538
1539 #[test]
1540 fn expandrle_single_slab_partial_floor() {
1541 let slab = [0u8, 64, 66, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0];
1545 let mut uind = vec![0i32; 16];
1546 expandrle(&slab, &mut uind);
1547 assert_eq!(uind[0], 64);
1548 assert_eq!(uind[1], MAXZDIM);
1549 }
1550
1551 #[test]
1552 fn expandrle_two_slabs_with_cave() {
1553 let slab = [
1572 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 50, 52, 30, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0, 0, ];
1579 let mut uind = vec![0i32; 16];
1580 expandrle(&slab, &mut uind);
1581 assert_eq!(uind[0], 10);
1582 assert_eq!(uind[1], 30);
1583 assert_eq!(uind[2], 50);
1584 assert_eq!(uind[3], MAXZDIM);
1585 }
1586
1587 #[test]
1588 fn expandrle_skips_degenerate_slab_with_no_ceiling_gap() {
1589 let slab = [
1597 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 20, 22, 20, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0, 0, ];
1602 let mut uind = vec![0i32; 16];
1603 expandrle(&slab, &mut uind);
1604 assert_eq!(uind[0], 10);
1606 assert_eq!(uind[1], MAXZDIM);
1607 }
1608
1609 #[test]
1610 fn expandrle_round_trips_through_b2_helpers() {
1611 let slab = [
1615 2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 50, 52, 30, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0,
1616 0,
1617 ];
1618 let mut uind = vec![0i32; 16];
1619 expandrle(&slab, &mut uind);
1620 let runs = read_uind(&uind[..4]);
1621 assert_eq!(runs, [(10, 30), (50, MAXZDIM)]);
1622 }
1623
1624 fn all_air_neighbor() -> Vec<i32> {
1630 let mut buf = vec![MAXZDIM, MAXZDIM];
1632 buf.resize(buf.len() + MAXZDIM as usize, MAXZDIM);
1633 buf
1634 }
1635
1636 fn b2_from_runs(runs: &[(i32, i32)]) -> Vec<i32> {
1640 let mut buf = Vec::new();
1641 for &(top, bot) in runs {
1642 buf.push(top);
1643 buf.push(bot);
1644 }
1645 buf.push(MAXZDIM);
1646 buf.push(MAXZDIM);
1647 buf.resize(buf.len() + MAXZDIM as usize, MAXZDIM);
1648 buf
1649 }
1650
1651 #[test]
1652 fn build_color_table_single_slab_one_floor_voxel() {
1653 let slab = [0u8, 10, 10, 0, 0xa1, 0xa2, 0xa3, 0xa4];
1655 let table = build_color_table(&slab);
1656 assert_eq!(table.len(), 2);
1657 assert_eq!(table[0].z_start, 10);
1658 assert_eq!(table[0].z_end, 11);
1659 assert_eq!(table[0].colors, &[0xa1, 0xa2, 0xa3, 0xa4]);
1660 assert_eq!(table[1].z_start, MAXZDIM);
1662 assert_eq!(table[1].z_end, MAXZDIM);
1663 }
1664
1665 #[test]
1666 fn build_color_table_two_slabs_with_ceiling() {
1667 let slab = [
1672 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, ];
1681 let table = build_color_table(&slab);
1682 assert_eq!(table.len(), 4);
1683 assert_eq!(table[0].z_start, 10);
1685 assert_eq!(table[0].z_end, 11);
1686 assert_eq!(table[0].colors.len(), 4);
1687 assert_eq!(table[1].z_start, 28);
1689 assert_eq!(table[1].z_end, 30);
1690 assert_eq!(
1691 table[1].colors,
1692 &[0xc0, 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc1]
1693 );
1694 assert_eq!(table[2].z_start, 50);
1696 assert_eq!(table[2].z_end, 53);
1697 assert_eq!(table[2].colors.len(), 12);
1698 assert_eq!(table[3].z_start, MAXZDIM);
1700 }
1701
1702 #[test]
1703 fn compilerle_round_trip_single_slab_solid_to_maxzdim() {
1704 let mut slab = vec![0u8, 10, (MAXZDIM - 1) as u8, 0];
1708 for z in 10..MAXZDIM {
1709 slab.extend_from_slice(&[z as u8, (z + 1) as u8, (z + 2) as u8, 0]);
1711 }
1712
1713 let mut spans = vec![0i32; (MAXZDIM as usize) + 4];
1715 expandrle(&slab, &mut spans);
1716 assert_eq!(spans[0], 10);
1717 assert_eq!(spans[1], MAXZDIM);
1718
1719 let n_air = all_air_neighbor();
1721 let mut cbuf = vec![0u8; 1028];
1722 let mut colfunc_called = 0;
1723 let mut colfunc = |_x: i32, _y: i32, _z: i32| -> i32 {
1724 colfunc_called += 1;
1725 0
1726 };
1727 let written = compilerle(
1728 &spans,
1729 &n_air,
1730 &n_air,
1731 &n_air,
1732 &n_air,
1733 &mut cbuf,
1734 &slab,
1735 0,
1736 0,
1737 &mut colfunc,
1738 );
1739 assert_eq!(colfunc_called, 0, "all colors should come from tbuf2");
1740
1741 assert_eq!(written, slab.len());
1744 assert_eq!(&cbuf[..written], &slab[..]);
1745
1746 let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
1748 expandrle(&cbuf[..written], &mut b2_round);
1749 assert_eq!(b2_round[0], 10);
1750 assert_eq!(b2_round[1], MAXZDIM);
1751 }
1752
1753 #[test]
1754 fn compilerle_round_trip_two_solid_runs_with_cave() {
1755 let dummy = vec![0u8, 0, (MAXZDIM - 1) as u8, 0];
1765 let mut dummy_full = dummy;
1766 dummy_full.extend(std::iter::repeat_n(0u8, (MAXZDIM as usize) * 4));
1767
1768 let n_air = all_air_neighbor();
1769 let spans = b2_from_runs(&[(10, 30), (50, MAXZDIM)]);
1770 let mut seed = vec![0u8; 1028];
1771 let mut colfunc = |_x: i32, _y: i32, z: i32| -> i32 { z };
1772 let seed_len = compilerle(
1773 &spans,
1774 &n_air,
1775 &n_air,
1776 &n_air,
1777 &n_air,
1778 &mut seed,
1779 &dummy_full,
1780 0,
1781 0,
1782 &mut colfunc,
1783 );
1784 seed.truncate(seed_len);
1785
1786 let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
1788 expandrle(&seed, &mut b2_round);
1789 assert_eq!(b2_round[0], 10);
1791 assert_eq!(b2_round[1], 30);
1792 assert_eq!(b2_round[2], 50);
1793 assert_eq!(b2_round[3], MAXZDIM);
1794
1795 let mut cbuf = vec![0u8; 1028];
1799 let mut never_called = 0;
1800 let mut colfunc2 = |_x: i32, _y: i32, _z: i32| -> i32 {
1801 never_called += 1;
1802 0
1803 };
1804 let written = compilerle(
1805 &spans,
1806 &n_air,
1807 &n_air,
1808 &n_air,
1809 &n_air,
1810 &mut cbuf,
1811 &seed,
1812 0,
1813 0,
1814 &mut colfunc2,
1815 );
1816 assert_eq!(never_called, 0, "second pass needs no colfunc");
1817 assert_eq!(written, seed_len);
1818 assert_eq!(&cbuf[..written], &seed[..]);
1819 }
1820
1821 #[test]
1822 fn compilerle_buried_voxel_optimization_with_all_solid_neighbors() {
1823 let spans = b2_from_runs(&[(10, MAXZDIM)]);
1831 let n_solid = b2_from_runs(&[(0, MAXZDIM)]);
1832 let mut slab = vec![0u8, 10, (MAXZDIM - 1) as u8, 0];
1836 for z in 10..MAXZDIM {
1837 slab.extend_from_slice(&[z as u8, 0, 0, 0]);
1838 }
1839 let mut cbuf = vec![0u8; 1028];
1840 let mut colfunc_called = 0;
1841 let mut colfunc = |_x: i32, _y: i32, _z: i32| -> i32 {
1842 colfunc_called += 1;
1843 0
1844 };
1845 let written = compilerle(
1846 &spans,
1847 &n_solid,
1848 &n_solid,
1849 &n_solid,
1850 &n_solid,
1851 &mut cbuf,
1852 &slab,
1853 0,
1854 0,
1855 &mut colfunc,
1856 );
1857 assert_eq!(colfunc_called, 0, "tbuf2 should cover every voxel");
1858 assert_eq!(written, 8);
1861 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];
1868 expandrle(&cbuf[..written], &mut b2_round);
1869 assert_eq!(b2_round[0], 10);
1870 assert_eq!(b2_round[1], MAXZDIM);
1871 }
1872
1873 fn build_1x1_min_solid_vxl() -> Vxl {
1878 let column = vec![0u8, 0, 0, 0, 0xff, 0x80, 0x40, 0x20];
1879 let column_offset = vec![0u32, column.len() as u32].into_boxed_slice();
1880 Vxl {
1881 vsid: 1,
1882 ipo: [0.0; 3],
1883 ist: [1.0, 0.0, 0.0],
1884 ihe: [0.0, 0.0, 1.0],
1885 ifo: [0.0, 1.0, 0.0],
1886 data: column.into_boxed_slice(),
1887 column_offset,
1888 mip_base_offsets: Box::new([0, 2]),
1889 vbit: Box::new([]),
1890 vbiti: 0,
1891 }
1892 }
1893
1894 #[test]
1895 fn scum2_no_edit_round_trip_1x1_minimal_column() {
1896 let mut vxl = build_1x1_min_solid_vxl();
1900 vxl.reserve_edit_capacity(4096);
1901
1902 let mut ctx = ScumCtx::new(&mut vxl);
1903 let _b2 = ctx.scum2(0, 0).expect("column 0,0 in bounds");
1904 ctx.finish();
1905
1906 let column = vxl.column_data(0);
1907 let mut b2_after = vec![0i32; SPAN_STRIDE];
1908 expandrle(column, &mut b2_after);
1909 assert_eq!(b2_after[0], 0);
1910 assert_eq!(b2_after[1], MAXZDIM);
1911 }
1912
1913 #[test]
1914 fn scum2_carve_edit_1x1_creates_air_gap() {
1915 let mut vxl = build_1x1_min_solid_vxl();
1918 vxl.reserve_edit_capacity(4096);
1919
1920 let mut ctx = ScumCtx::new(&mut vxl);
1921 ctx.set_colfunc(|_x, _y, _z| 0x80_60_40_20u32 as i32);
1922 {
1923 let spans = ctx.scum2(0, 0).expect("column 0,0 in bounds");
1924 delslab(spans, 50, 100);
1926 }
1927 ctx.finish();
1928
1929 let column = vxl.column_data(0);
1930 let mut b2_after = vec![0i32; SPAN_STRIDE];
1931 expandrle(column, &mut b2_after);
1932 assert_eq!(b2_after[0], 0);
1934 assert_eq!(b2_after[1], 50);
1935 assert_eq!(b2_after[2], 100);
1936 assert_eq!(b2_after[3], MAXZDIM);
1937 }
1938
1939 fn build_4x4_min_solid_vxl() -> Vxl {
1942 const COL: [u8; 8] = [0, 0, 0, 0, 0xff, 0x80, 0x40, 0x20];
1943 let mut data = Vec::with_capacity(16 * 8);
1944 let mut offsets = Vec::with_capacity(17);
1945 for i in 0..16 {
1946 offsets.push((i * 8) as u32);
1947 data.extend_from_slice(&COL);
1948 }
1949 offsets.push((16 * 8) as u32);
1950 Vxl {
1951 vsid: 4,
1952 ipo: [0.0; 3],
1953 ist: [1.0, 0.0, 0.0],
1954 ihe: [0.0, 0.0, 1.0],
1955 ifo: [0.0, 1.0, 0.0],
1956 data: data.into_boxed_slice(),
1957 column_offset: offsets.into_boxed_slice(),
1958 mip_base_offsets: Box::new([0, 17]),
1959 vbit: Box::new([]),
1960 vbiti: 0,
1961 }
1962 }
1963
1964 #[test]
1965 fn scum2_batch_edits_multiple_columns_same_row() {
1966 let mut vxl = build_4x4_min_solid_vxl();
1969 vxl.reserve_edit_capacity(8192);
1970
1971 let mut ctx = ScumCtx::new(&mut vxl);
1972 ctx.set_colfunc(|_x, _y, _z| 0);
1973 {
1974 let spans = ctx.scum2(1, 2).unwrap();
1975 delslab(spans, 50, 100);
1976 }
1977 {
1978 let spans = ctx.scum2(2, 2).unwrap();
1979 delslab(spans, 50, 100);
1980 }
1981 ctx.finish();
1982
1983 for x in [1, 2] {
1984 let idx = 2 * 4 + x;
1985 let mut b2_after = vec![0i32; SPAN_STRIDE];
1986 expandrle(vxl.column_data(idx), &mut b2_after);
1987 assert_eq!(b2_after[0], 0);
1988 assert_eq!(b2_after[1], 50);
1989 assert_eq!(b2_after[2], 100);
1990 assert_eq!(b2_after[3], MAXZDIM);
1991 }
1992 for x in [0, 3] {
1994 let idx = 2 * 4 + x;
1995 let mut b2_after = vec![0i32; SPAN_STRIDE];
1996 expandrle(vxl.column_data(idx), &mut b2_after);
1997 assert_eq!(b2_after[0], 0);
1998 assert_eq!(b2_after[1], MAXZDIM);
1999 }
2000 }
2001
2002 #[test]
2003 fn scum2_batch_edits_across_rows() {
2004 let mut vxl = build_4x4_min_solid_vxl();
2007 vxl.reserve_edit_capacity(8192);
2008
2009 let mut ctx = ScumCtx::new(&mut vxl);
2010 ctx.set_colfunc(|_x, _y, _z| 0);
2011 {
2012 let spans = ctx.scum2(1, 1).unwrap();
2013 delslab(spans, 60, 80);
2014 }
2015 {
2016 let spans = ctx.scum2(1, 2).unwrap();
2017 delslab(spans, 60, 80);
2018 }
2019 ctx.finish();
2020
2021 for y in [1, 2] {
2022 let idx = y * 4 + 1;
2023 let mut b2_after = vec![0i32; SPAN_STRIDE];
2024 expandrle(vxl.column_data(idx), &mut b2_after);
2025 assert_eq!(b2_after[0], 0);
2026 assert_eq!(b2_after[1], 60);
2027 assert_eq!(b2_after[2], 80);
2028 assert_eq!(b2_after[3], MAXZDIM);
2029 }
2030 }
2031
2032 #[test]
2033 fn scum2_finish_without_any_edit_is_noop() {
2034 let mut vxl = build_1x1_min_solid_vxl();
2036 vxl.reserve_edit_capacity(4096);
2037 let original = vxl.column_data(0).to_vec();
2038 let ctx = ScumCtx::new(&mut vxl);
2039 ctx.finish();
2040 assert_eq!(vxl.column_data(0), &original[..]);
2041 }
2042
2043 #[test]
2044 fn scum2_returns_none_for_out_of_bounds() {
2045 let mut vxl = build_1x1_min_solid_vxl();
2046 vxl.reserve_edit_capacity(4096);
2047 let mut ctx = ScumCtx::new(&mut vxl);
2048 assert!(ctx.scum2(-1, 0).is_none());
2049 assert!(ctx.scum2(0, -1).is_none());
2050 assert!(ctx.scum2(1, 0).is_none());
2051 assert!(ctx.scum2(0, 1).is_none());
2052 ctx.finish();
2053 }
2054
2055 #[test]
2058 fn set_spans_empty_is_noop() {
2059 let mut vxl = build_1x1_min_solid_vxl();
2060 let original = vxl.column_data(0).to_vec();
2061 set_spans(&mut vxl, &[], None);
2062 assert_eq!(vxl.column_data(0), &original[..]);
2066 }
2067
2068 #[test]
2069 fn set_spans_single_carve_creates_air_gap() {
2070 let mut vxl = build_1x1_min_solid_vxl();
2071 vxl.reserve_edit_capacity(4096);
2072 set_spans(
2073 &mut vxl,
2074 &[Vspan {
2075 x: 0,
2076 y: 0,
2077 z0: 50,
2078 z1: 99,
2079 }],
2080 None,
2081 );
2082 let mut spans = vec![0i32; SPAN_STRIDE];
2083 expandrle(vxl.column_data(0), &mut spans);
2084 assert_eq!(spans[0], 0);
2086 assert_eq!(spans[1], 50);
2087 assert_eq!(spans[2], 100);
2088 assert_eq!(spans[3], MAXZDIM);
2089 }
2090
2091 #[test]
2092 fn set_spans_multi_span_same_column_accumulates() {
2093 let mut vxl = build_1x1_min_solid_vxl();
2097 vxl.reserve_edit_capacity(4096);
2098 set_spans(
2099 &mut vxl,
2100 &[
2101 Vspan {
2102 x: 0,
2103 y: 0,
2104 z0: 30,
2105 z1: 49,
2106 },
2107 Vspan {
2108 x: 0,
2109 y: 0,
2110 z0: 100,
2111 z1: 119,
2112 },
2113 ],
2114 None,
2115 );
2116 let mut spans = vec![0i32; SPAN_STRIDE];
2117 expandrle(vxl.column_data(0), &mut spans);
2118 assert_eq!(spans[0], 0);
2120 assert_eq!(spans[1], 30);
2121 assert_eq!(spans[2], 50);
2122 assert_eq!(spans[3], 100);
2123 assert_eq!(spans[4], 120);
2124 assert_eq!(spans[5], MAXZDIM);
2125 }
2126
2127 #[test]
2128 fn set_spans_insert_color_fills_air() {
2129 let mut vxl = build_1x1_min_solid_vxl();
2132 vxl.reserve_edit_capacity(4096);
2133 set_spans(
2135 &mut vxl,
2136 &[Vspan {
2137 x: 0,
2138 y: 0,
2139 z0: 50,
2140 z1: 99,
2141 }],
2142 None,
2143 );
2144 const FILL: u32 = 0x80_aa_bb_cc;
2146 set_spans(
2147 &mut vxl,
2148 &[Vspan {
2149 x: 0,
2150 y: 0,
2151 z0: 60,
2152 z1: 79,
2153 }],
2154 Some(FILL),
2155 );
2156 let mut spans = vec![0i32; SPAN_STRIDE];
2157 expandrle(vxl.column_data(0), &mut spans);
2158 assert_eq!(spans[0], 0);
2160 assert_eq!(spans[1], 50);
2161 assert_eq!(spans[2], 60);
2162 assert_eq!(spans[3], 80);
2163 assert_eq!(spans[4], 100);
2164 assert_eq!(spans[5], MAXZDIM);
2165 }
2166
2167 #[test]
2168 fn set_spans_skips_out_of_bounds_silently() {
2169 let mut vxl = build_1x1_min_solid_vxl();
2170 vxl.reserve_edit_capacity(4096);
2171 set_spans(
2172 &mut vxl,
2173 &[Vspan {
2174 x: 7,
2175 y: 9,
2176 z0: 50,
2177 z1: 99,
2178 }],
2179 None,
2180 );
2181 let mut spans = vec![0i32; SPAN_STRIDE];
2183 expandrle(vxl.column_data(0), &mut spans);
2184 assert_eq!(spans[0], 0);
2185 assert_eq!(spans[1], MAXZDIM);
2186 }
2187
2188 #[test]
2189 fn set_spans_with_colfunc_z_dependent_colour() {
2190 let mut vxl = build_4x4_min_solid_vxl();
2197 vxl.reserve_edit_capacity(8192);
2198 let carve_spans: Vec<Vspan> = (0..4)
2201 .flat_map(|y| {
2202 (0..4).map(move |x| Vspan {
2203 x,
2204 y,
2205 z0: 50,
2206 z1: 99,
2207 })
2208 })
2209 .collect();
2210 set_spans(&mut vxl, &carve_spans, None);
2211 set_spans_with_colfunc(
2214 &mut vxl,
2215 &[Vspan {
2216 x: 1,
2217 y: 1,
2218 z0: 60,
2219 z1: 79,
2220 }],
2221 SpanOp::Insert,
2222 |_x, _y, z| (0x80ff_ff00u32 as i32) | z,
2223 );
2224 let idx = 4 + 1; let column = vxl.column_data(idx);
2228 let mut v = 0usize;
2229 let mut found = false;
2230 loop {
2231 let nextptr = column[v];
2232 let z1 = column[v + 1];
2233 if z1 == 60 {
2234 let z1c = column[v + 2];
2235 assert_eq!(z1c, 79, "z1c");
2236 let n_voxels = usize::from(z1c) - usize::from(z1) + 1;
2237 for i in 0..n_voxels {
2238 let off = v + 4 + i * 4;
2239 let c = u32::from_le_bytes([
2240 column[off],
2241 column[off + 1],
2242 column[off + 2],
2243 column[off + 3],
2244 ]);
2245 let z = u32::from(z1) + (i as u32);
2246 assert_eq!(
2247 c,
2248 0x80ff_ff00 | z,
2249 "z={z}: expected colour {:#010x}, got {:#010x}",
2250 0x80ff_ff00 | z,
2251 c
2252 );
2253 }
2254 found = true;
2255 break;
2256 }
2257 if nextptr == 0 {
2258 break;
2259 }
2260 v += usize::from(nextptr) * 4;
2261 }
2262 assert!(found, "did not find a slab with z1=60");
2263 }
2264
2265 #[test]
2268 fn set_cube_carves_single_voxel() {
2269 let mut vxl = build_4x4_min_solid_vxl();
2270 vxl.reserve_edit_capacity(4096);
2271 set_cube(&mut vxl, 1, 1, 100, None);
2272 let mut spans = vec![0i32; SPAN_STRIDE];
2273 expandrle(vxl.column_data(4 + 1), &mut spans);
2274 assert_eq!(spans[0], 0);
2276 assert_eq!(spans[1], 100);
2277 assert_eq!(spans[2], 101);
2278 assert_eq!(spans[3], MAXZDIM);
2279 }
2280
2281 #[test]
2282 fn set_cube_skips_oob() {
2283 let mut vxl = build_4x4_min_solid_vxl();
2284 vxl.reserve_edit_capacity(4096);
2285 set_cube(&mut vxl, -1, 1, 100, None);
2287 set_cube(&mut vxl, 5, 1, 100, None);
2288 set_cube(&mut vxl, 1, 1, 256, None);
2289 let mut spans = vec![0i32; SPAN_STRIDE];
2291 expandrle(vxl.column_data(4 + 1), &mut spans);
2292 assert_eq!(spans[0], 0);
2293 assert_eq!(spans[1], MAXZDIM);
2294 }
2295
2296 #[test]
2297 fn set_rect_carves_aabb() {
2298 let mut vxl = build_4x4_min_solid_vxl();
2299 vxl.reserve_edit_capacity(8192);
2300 set_rect(&mut vxl, [1, 1, 50], [2, 2, 99], None);
2302 for y in 1..=2 {
2303 for x in 1..=2 {
2304 let idx = (y * 4 + x) as usize;
2305 let mut spans = vec![0i32; SPAN_STRIDE];
2306 expandrle(vxl.column_data(idx), &mut spans);
2307 assert_eq!(spans[0], 0, "col ({x},{y})");
2308 assert_eq!(spans[1], 50, "col ({x},{y})");
2309 assert_eq!(spans[2], 100, "col ({x},{y})");
2310 assert_eq!(spans[3], MAXZDIM, "col ({x},{y})");
2311 }
2312 }
2313 for &(x, y) in &[(0, 0), (3, 3)] {
2315 let idx = (y * 4 + x) as usize;
2316 let mut spans = vec![0i32; SPAN_STRIDE];
2317 expandrle(vxl.column_data(idx), &mut spans);
2318 assert_eq!(spans[0], 0);
2319 assert_eq!(spans[1], MAXZDIM);
2320 }
2321 }
2322
2323 #[test]
2324 fn set_rect_clamps_to_world() {
2325 let mut vxl = build_4x4_min_solid_vxl();
2326 vxl.reserve_edit_capacity(8192);
2327 set_rect(&mut vxl, [-10, -10, -10], [100, 100, 1000], None);
2330 for idx in 0..16 {
2332 let mut spans = vec![0i32; SPAN_STRIDE];
2333 expandrle(vxl.column_data(idx), &mut spans);
2334 assert_eq!(spans[0], 255, "col {idx}");
2338 assert_eq!(spans[1], MAXZDIM, "col {idx}");
2339 }
2340 }
2341
2342 #[test]
2343 fn set_sphere_carves_centred_sphere() {
2344 let mut vxl = build_4x4_min_solid_vxl();
2347 vxl.reserve_edit_capacity(8192);
2348 set_sphere(&mut vxl, [1, 1, 128], 1, None);
2349 let mut spans = vec![0i32; SPAN_STRIDE];
2353 expandrle(vxl.column_data(4 + 1), &mut spans);
2354 assert_eq!(spans[0], 0);
2355 assert_eq!(spans[1], 127);
2356 assert_eq!(spans[2], 130);
2357 assert_eq!(spans[3], MAXZDIM);
2358 let mut spans = vec![0i32; SPAN_STRIDE];
2360 expandrle(vxl.column_data(4), &mut spans);
2361 assert_eq!(spans[0], 0);
2362 assert_eq!(spans[1], 128);
2363 assert_eq!(spans[2], 129);
2364 assert_eq!(spans[3], MAXZDIM);
2365 }
2366
2367 #[test]
2368 fn set_sphere_radius_zero_is_single_voxel() {
2369 let mut vxl = build_4x4_min_solid_vxl();
2370 vxl.reserve_edit_capacity(4096);
2371 set_sphere(&mut vxl, [1, 1, 100], 0, None);
2372 let mut spans = vec![0i32; SPAN_STRIDE];
2374 expandrle(vxl.column_data(4 + 1), &mut spans);
2375 assert_eq!(spans[0], 0);
2376 assert_eq!(spans[1], 100);
2377 assert_eq!(spans[2], 101);
2378 assert_eq!(spans[3], MAXZDIM);
2379 }
2380
2381 #[test]
2382 fn set_sphere_with_colfunc_position_dependent_color() {
2383 let mut vxl = build_4x4_min_solid_vxl();
2395 vxl.reserve_edit_capacity(8192);
2396 set_rect(&mut vxl, [0, 0, 50], [3, 3, 199], None);
2398 set_sphere_with_colfunc(&mut vxl, [1, 1, 128], 2, SpanOp::Insert, |_, _, z| {
2401 (0x80ff_ff00u32 as i32) | z
2402 });
2403 let mut spans = vec![0i32; SPAN_STRIDE];
2405 expandrle(vxl.column_data(4 + 1), &mut spans);
2406 assert_eq!(spans[0], 0, "spans first run top");
2407 assert_eq!(spans[1], 50, "spans first run bot");
2408 assert_eq!(spans[2], 126, "spans sphere run top");
2409 assert_eq!(spans[3], 131, "spans sphere run bot");
2410 assert_eq!(spans[4], 200, "spans third run top");
2411 assert_eq!(spans[5], MAXZDIM, "spans third run bot");
2412
2413 let column = vxl.column_data(4 + 1).to_vec();
2417 let mut v = 0usize;
2418 let mut top_color = None;
2419 loop {
2420 let nextptr = column[v];
2421 let z1 = column[v + 1];
2422 if z1 == 126 {
2423 let off = v + 4;
2424 top_color = Some(u32::from_le_bytes([
2425 column[off],
2426 column[off + 1],
2427 column[off + 2],
2428 column[off + 3],
2429 ]));
2430 break;
2431 }
2432 if nextptr == 0 {
2433 break;
2434 }
2435 v += usize::from(nextptr) * 4;
2436 }
2437 assert_eq!(
2438 top_color,
2439 Some(0x80ff_ff7e),
2440 "exposed voxel at z=126 should have colfunc-derived colour"
2441 );
2442 }
2443
2444 #[test]
2445 fn set_spans_4x4_batch_carves_each_listed_column() {
2446 let mut vxl = build_4x4_min_solid_vxl();
2448 vxl.reserve_edit_capacity(8192);
2449 let spans: Vec<Vspan> = (0..4)
2450 .flat_map(|y| {
2451 (0..4).map(move |x| Vspan {
2452 x,
2453 y,
2454 z0: 50,
2455 z1: 99,
2456 })
2457 })
2458 .collect();
2459 set_spans(&mut vxl, &spans, None);
2460 for idx in 0..16 {
2462 let mut spans = vec![0i32; SPAN_STRIDE];
2463 expandrle(vxl.column_data(idx), &mut spans);
2464 assert_eq!(spans[0], 0, "col {idx}");
2465 assert_eq!(spans[1], 50, "col {idx}");
2466 assert_eq!(spans[2], 100, "col {idx}");
2467 assert_eq!(spans[3], MAXZDIM, "col {idx}");
2468 }
2469 }
2470}