1use alloc::boxed::Box;
2use alloc::sync::Arc;
3use core::fmt;
4use core::ops::{Deref, DerefMut};
5
6#[cfg_attr(
7 not(doc),
8 allow(
9 unused,
10 reason = "because this module’s items is hidden and reexported, we need unconditional imports for working doc links"
11 )
12)]
13use alloc::vec::Vec;
14
15use euclid::{Point3D, Size3D, vec3};
16use manyfmt::Refmt as _;
17
18use crate::math::{Axis, Cube, GridAab, GridCoordinate, GridIter, GridPoint, GridVector};
19
20#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
31#[expect(clippy::exhaustive_structs)]
32pub struct ZMaj;
33
34#[derive(Clone, Copy, Eq, Hash, PartialEq)]
50pub struct Vol<C, O = ZMaj> {
51 bounds: GridAab,
53 ordering: O,
54 contents: C,
56}
57
58impl<O> Vol<(), O> {
62 pub(crate) fn new_dataless(bounds: GridAab, ordering: O) -> Result<Self, VolLengthError> {
64 if bounds.volume().is_none() {
65 Err(VolLengthError {
66 input_length: None,
67 bounds,
68 })
69 } else {
70 Ok(Self {
71 bounds,
72 ordering,
73 contents: (),
74 })
75 }
76 }
77
78 #[inline]
83 pub const fn tiny(
84 lower_bounds: Point3D<i8, Cube>,
85 size: Size3D<u8, Cube>,
86 ordering: O,
87 ) -> Self {
88 Self {
89 bounds: GridAab::tiny(lower_bounds, size),
90 ordering,
91 contents: (),
92 }
93 }
94
95 #[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
100 pub fn with_elements<C, V>(self, elements: C) -> Result<Vol<C, O>, VolLengthError>
101 where
102 C: Deref<Target = [V]>,
103 {
104 if elements.len() == self.volume() {
105 Ok(Vol {
106 bounds: self.bounds,
107 ordering: self.ordering,
108 contents: elements,
109 })
110 } else {
111 Err(VolLengthError {
112 input_length: Some(elements.len()),
113 bounds: self.bounds(),
114 })
115 }
116 }
117}
118
119impl Vol<(), ZMaj> {
120 #[allow(clippy::missing_inline_in_public_items)]
129 pub fn subdivide(self) -> Result<[Self; 2], Self> {
130 match find_zmaj_subdivision(self) {
131 Some((lower_half, upper_half, _)) => Ok([lower_half, upper_half]),
132 _ => Err(self),
133 }
134 }
135}
136
137impl<C, O: Default, V> Vol<C, O>
139where
140 C: Deref<Target = [V]>,
141{
142 #[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
150 pub fn from_elements(bounds: GridAab, elements: impl Into<C>) -> Result<Self, VolLengthError> {
151 let elements = elements.into();
152 if Some(elements.len()) == bounds.volume() {
153 Ok(Vol {
154 bounds,
155 ordering: O::default(),
156 contents: elements,
157 })
158 } else {
159 Err(VolLengthError {
160 input_length: Some(elements.len()),
161 bounds,
162 })
163 }
164 }
165}
166
167impl<'a, O, V> Vol<&'a [V], O> {
168 #[inline]
176 pub const fn from_tiny_elements(
177 lower_bounds: Point3D<i8, Cube>,
178 size: Size3D<u8, Cube>,
179 ordering: O,
180 contents: &'a [V],
181 ) -> Self {
182 let bounds = GridAab::tiny(lower_bounds, size);
183 assert!(
184 bounds.volume().unwrap() == contents.len(),
185 "element count does not match AAB volume"
186 );
187 Self {
188 bounds,
189 ordering,
190 contents,
191 }
192 }
193}
194
195impl<'a, V> Vol<&'a [V], ZMaj> {
196 #[inline]
203 pub const fn from_element_ref(element: &'a V) -> Self {
204 Self {
205 bounds: GridAab::ORIGIN_CUBE,
206 ordering: ZMaj,
207 contents: core::slice::from_ref(element),
208 }
209 }
210}
211
212impl<'a, V> Vol<&'a mut [V], ZMaj> {
213 #[inline]
220 pub const fn from_element_mut(element: &'a mut V) -> Self {
221 Self {
222 bounds: GridAab::ORIGIN_CUBE,
223 ordering: ZMaj,
224 contents: core::slice::from_mut(element),
225 }
226 }
227}
228
229#[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
233impl<C, V> Vol<C, ZMaj>
234where
235 C: Deref<Target = [V]> + FromIterator<V>,
237{
238 #[inline]
244 #[track_caller]
245 pub fn from_fn<F>(bounds: GridAab, f: F) -> Self
246 where
247 F: FnMut(Cube) -> V,
248 {
249 match bounds.to_vol::<ZMaj>() {
250 Ok(bounds) => bounds.with_elements(bounds.iter_cubes().map(f).collect()).unwrap(),
251 Err(length_error) => panic!("{length_error}"),
252 }
253 }
254
255 #[inline]
257 pub fn repeat(bounds: GridAab, value: V) -> Self
258 where
259 V: Clone,
260 {
261 Self::from_fn(bounds, |_| value.clone())
262 }
263
264 #[inline]
270 pub fn from_element(value: V) -> Self {
271 Self::from_elements(GridAab::ORIGIN_CUBE, core::iter::once(value).collect::<C>()).unwrap()
272 }
273
274 #[doc(hidden)] #[expect(clippy::needless_pass_by_value)]
285 pub fn from_y_flipped_array<const DX: usize, const DY: usize, const DZ: usize>(
286 array: [[[V; DX]; DY]; DZ],
287 ) -> Self
288 where
289 V: Clone,
290 {
291 Self::from_fn(
292 GridAab::from_lower_size([0, 0, 0], vec3(DX, DY, DZ).to_u32()),
293 |p| array[p.z as usize][(DY - 1) - (p.y as usize)][p.x as usize].clone(),
294 )
295 }
296}
297
298impl<C, O> Vol<C, O> {
299 #[inline]
301 pub fn bounds(&self) -> GridAab {
302 self.bounds
303 }
304
305 #[inline]
307 pub fn volume(&self) -> usize {
308 let size = self.bounds.size();
313 size.width as usize * size.height as usize * size.depth as usize
315 }
316
317 #[inline]
319 pub fn into_elements(self) -> C {
320 self.contents
321 }
322
323 #[inline]
327 pub fn without_elements(&self) -> Vol<(), O>
328 where
329 O: Clone,
330 {
331 Vol {
332 bounds: self.bounds,
333 ordering: self.ordering.clone(),
334 contents: (),
335 }
336 }
337
338 #[must_use]
344 #[track_caller]
345 #[inline]
346 pub fn translate(self, offset: impl Into<GridVector>) -> Self {
347 self.translate_impl(offset.into())
348 }
349
350 #[track_caller]
351 #[inline]
352 fn translate_impl(mut self, offset: GridVector) -> Self {
353 let new_bounds = self.bounds.translate(offset);
354 if new_bounds.size() != self.bounds.size() {
355 panic!("Vol::translate() offset caused numeric overflow");
358 }
359 self.bounds = new_bounds;
360 self
361 }
362
363 #[doc(hidden)] #[inline]
366 pub fn map_container<C2, V2, F>(self, f: F) -> Vol<C2, O>
367 where
368 F: FnOnce(C) -> C2,
369 C2: Deref<Target = [V2]>,
370 {
371 let bounds = self.bounds;
372 let volume = self.volume();
373 let contents = f(self.contents);
374 if contents.len() != volume {
375 panic!(
376 "{}",
377 VolLengthError {
378 input_length: Some(contents.len()),
379 bounds: self.bounds,
380 }
381 )
382 }
383 Vol {
384 bounds,
385 ordering: self.ordering,
386 contents,
387 }
388 }
389}
390
391impl<C> Vol<C, ZMaj> {
392 #[inline]
395 pub fn iter_cubes(&self) -> GridIter {
396 GridIter::new(self.bounds)
397 }
398
399 #[inline(always)] pub fn index(&self, cube: Cube) -> Option<usize> {
420 index_into_aab_zmaj(self.bounds, cube)
421 }
422}
423
424#[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
426impl<C, O, V> Vol<C, O>
427where
428 C: Deref<Target = [V]>,
429 O: Copy,
430{
431 pub fn as_ref(&self) -> Vol<&[V], O> {
433 Vol {
434 bounds: self.bounds,
435 ordering: self.ordering,
436 contents: self.as_linear(),
437 }
438 }
439
440 pub fn as_mut(&mut self) -> Vol<&mut [V], O>
442 where
443 C: DerefMut,
444 {
445 Vol {
446 bounds: self.bounds,
447 ordering: self.ordering,
448 contents: self.as_linear_mut(),
449 }
450 }
451
452 pub fn as_linear(&self) -> &[V] {
454 let s = &*self.contents;
455 debug_assert_eq!(s.len(), self.volume());
456 s
457 }
458
459 pub fn as_linear_mut(&mut self) -> &mut [V]
461 where
462 C: DerefMut,
463 {
464 let s = &mut *self.contents;
465 debug_assert_eq!(s.len(), self.bounds.volume().unwrap());
466 s
467 }
468}
469
470impl<'a, V> Vol<&'a [V], ZMaj> {
471 #[inline]
477 pub fn get_ref(&self, position: impl Into<Cube>) -> Option<&'a V> {
478 let index = self.index(position.into())?;
479 Some(&self.contents[index])
480 }
481
482 #[allow(clippy::missing_inline_in_public_items)]
491 pub fn subdivide(self) -> Result<[Self; 2], Self> {
492 match find_zmaj_subdivision(self.without_elements()) {
493 Some((lower_half, upper_half, lower_half_len)) => {
494 let (lower_contents, upper_contents) = self.contents.split_at(lower_half_len);
495
496 Ok([
497 lower_half.with_elements(lower_contents).unwrap_or_else(unreachable_wrong_size),
498 upper_half.with_elements(upper_contents).unwrap_or_else(unreachable_wrong_size),
499 ])
500 }
501 _ => Err(self),
502 }
503 }
504}
505
506impl<V> Vol<&mut [V], ZMaj> {
507 #[allow(clippy::missing_inline_in_public_items)]
524 pub fn subdivide(self, filter: impl FnOnce([Vol<()>; 2]) -> bool) -> Result<[Self; 2], Self> {
525 match find_zmaj_subdivision(self.without_elements()) {
526 Some((lower_half, upper_half, lower_half_len)) if filter([lower_half, upper_half]) => {
527 let (lower_contents, upper_contents) = self.contents.split_at_mut(lower_half_len);
528
529 Ok([
530 lower_half.with_elements(lower_contents).unwrap_or_else(unreachable_wrong_size),
531 upper_half.with_elements(upper_contents).unwrap_or_else(unreachable_wrong_size),
532 ])
533 }
534 _ => Err(self),
535 }
536 }
537}
538
539impl<V: Clone, O> Vol<Arc<[V]>, O> {
540 #[doc(hidden)] #[allow(clippy::missing_inline_in_public_items)]
543 pub fn make_linear_mut(&mut self) -> &mut [V] {
544 let slice: &mut [V] = Arc::make_mut(&mut self.contents);
545 debug_assert_eq!(slice.len(), self.bounds.volume().unwrap());
546 slice
547 }
548}
549
550#[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
552impl<C, V> Vol<C, ZMaj>
553where
554 C: Deref<Target = [V]>,
555{
556 #[inline]
559 pub fn get(&self, position: impl Into<Cube>) -> Option<&V> {
560 let index = self.index(position.into())?;
561 Some(&self.as_linear()[index])
562 }
563
564 #[inline]
567 pub fn get_mut(&mut self, position: impl Into<Cube>) -> Option<&mut V>
568 where
569 C: DerefMut,
570 {
571 let index = self.index(position.into())?;
572 Some(&mut self.as_linear_mut()[index])
573 }
574
575 pub fn iter<'s>(&'s self) -> impl Iterator<Item = (Cube, &'s V)> + Clone
578 where
579 V: 's,
580 {
581 self.iter_cubes().zip(self.as_linear().iter())
582 }
583
584 pub fn iter_mut<'s>(&'s mut self) -> impl Iterator<Item = (Cube, &'s mut V)>
587 where
588 C: DerefMut,
589 V: 's,
590 {
591 self.iter_cubes().zip(self.as_linear_mut().iter_mut())
592 }
593}
594
595#[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
596impl<V, O> Vol<Box<[V]>, O> {
597 pub fn map<T, F>(self, f: F) -> Vol<Box<[T]>, O>
599 where
600 F: FnMut(V) -> T,
601 {
602 Vol {
603 bounds: self.bounds,
604 ordering: self.ordering,
605 contents: <Box<[V]> as IntoIterator>::into_iter(self.contents).map(f).collect(),
607 }
608 }
609}
610
611#[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
616impl<C: fmt::Debug, O: fmt::Debug> fmt::Debug for Vol<C, O> {
617 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618 let Self {
623 bounds,
624 ordering,
625 contents,
626 } = self;
627
628 let mut ds = f.debug_struct(&format!(
629 "Vol<{contents_type}, {ordering:?}>",
630 contents_type = core::any::type_name::<C>(),
631 ));
632 ds.field("bounds", &bounds);
633 let volume = self.volume();
634 if core::any::type_name::<C>() == core::any::type_name::<()>() {
635 } else if volume > 32 {
637 ds.field(
638 "contents",
639 &format!("[...{volume} elements]").refmt(&manyfmt::formats::Unquote),
640 );
641 } else {
642 ds.field("contents", &contents);
643 }
644 ds.finish()
645 }
646}
647
648impl<C: Deref<Target = [V]>, V> core::ops::Index<Cube> for Vol<C, ZMaj> {
649 type Output = V;
650
651 #[inline(always)] fn index(&self, position: Cube) -> &Self::Output {
657 if let Some(index) = self.index(position) {
658 &self.contents[index]
659 } else {
660 panic!(
661 "position {:?} out of Vol bounds {:?}",
662 position, self.bounds
663 )
664 }
665 }
666}
667impl<C: DerefMut<Target = [V]>, V> core::ops::IndexMut<Cube> for Vol<C, ZMaj> {
668 #[inline(always)]
671 fn index_mut(&mut self, position: Cube) -> &mut Self::Output {
672 if let Some(index) = self.index(position) {
673 &mut self.contents[index]
674 } else {
675 panic!(
676 "position {:?} out of Vol bounds {:?}",
677 position, self.bounds
678 )
679 }
680 }
681}
682
683impl<C: Deref<Target = [V]>, V> core::ops::Index<[GridCoordinate; 3]> for Vol<C, ZMaj> {
684 type Output = V;
685
686 #[inline(always)]
693 fn index(&self, position: [GridCoordinate; 3]) -> &Self::Output {
694 &self[Cube::from(position)]
695 }
696}
697
698impl<C: DerefMut<Target = [V]>, V> core::ops::IndexMut<[GridCoordinate; 3]> for Vol<C, ZMaj> {
699 #[inline(always)]
706 fn index_mut(&mut self, position: [GridCoordinate; 3]) -> &mut Self::Output {
707 &mut self[Cube::from(position)]
708 }
709}
710
711mod aab_compat {
712 use super::*;
713
714 impl<O> PartialEq<GridAab> for Vol<(), O> {
715 #[inline]
716 fn eq(&self, other: &GridAab) -> bool {
717 self.bounds() == *other
718 }
719 }
720
721 impl<O> PartialEq<Vol<(), O>> for GridAab {
722 #[inline]
723 fn eq(&self, other: &Vol<(), O>) -> bool {
724 *self == other.bounds()
725 }
726 }
727}
728
729#[cfg(feature = "arbitrary")]
730#[allow(clippy::missing_inline_in_public_items)]
731pub(crate) mod vol_arb {
732 use super::*;
733 use arbitrary::Arbitrary;
734
735 const MAX_VOLUME: usize = 2_usize.pow(16);
738
739 pub(crate) const ARBITRARY_BOUNDS_SIZE_HINT: (usize, Option<usize>) = {
741 let gc = size_of::<GridCoordinate>();
746 (3 + 1, Some(gc * 6 + 1))
747 };
748
749 impl<O: Default> Vol<(), O> {
750 #[cfg(feature = "arbitrary")]
751 #[doc(hidden)]
752 pub fn arbitrary_with_max_volume(
753 u: &mut arbitrary::Unstructured<'_>,
754 volume: usize,
755 ) -> arbitrary::Result<Self> {
756 let mut limit: u32 = volume.try_into().unwrap_or(u32::MAX);
758 let size_1 = u.int_in_range(0..=limit)?;
759 limit /= size_1.max(1);
760 let size_2 = u.int_in_range(0..=limit)?;
761 limit /= size_2.max(1);
762 let size_3 = u.int_in_range(0..=limit)?;
763
764 let sizes = *u.choose(&[
766 vec3(size_1, size_2, size_3),
767 vec3(size_1, size_3, size_2),
768 vec3(size_2, size_1, size_3),
769 vec3(size_2, size_3, size_1),
770 vec3(size_3, size_1, size_2),
771 vec3(size_3, size_2, size_1),
772 ])?;
773
774 let possible_lower_bounds = sizes.map(|coord| {
776 GridCoordinate::MIN..=GridCoordinate::MAX.saturating_sub_unsigned(coord)
777 });
778 let lower_bounds = GridPoint::new(
779 u.int_in_range(possible_lower_bounds.x)?,
780 u.int_in_range(possible_lower_bounds.y)?,
781 u.int_in_range(possible_lower_bounds.z)?,
782 );
783
784 Ok(GridAab::from_lower_size(lower_bounds, sizes)
785 .to_vol()
786 .expect("GridAab as Arbitrary failed to compute valid bounds"))
787 }
788 }
789
790 impl<'a, V: Arbitrary<'a>, C> Arbitrary<'a> for Vol<C, ZMaj>
791 where
792 C: FromIterator<V> + Deref<Target = [V]>,
793 {
794 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
795 let bounds = Vol::<()>::arbitrary_with_max_volume(u, MAX_VOLUME)?;
796 let contents: C =
797 u.arbitrary_iter()?.take(bounds.volume()).collect::<Result<C, _>>()?;
798 bounds.with_elements(contents).map_err(|_| arbitrary::Error::NotEnoughData)
799 }
800
801 fn size_hint(depth: usize) -> (usize, Option<usize>) {
802 Self::try_size_hint(depth).unwrap_or_default()
804 }
805
806 fn try_size_hint(
807 depth: usize,
808 ) -> Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
809 arbitrary::size_hint::try_recursion_guard(depth, |depth| {
810 let (lower, upper) = V::try_size_hint(depth)?;
811 Ok(arbitrary::size_hint::and(
812 ARBITRARY_BOUNDS_SIZE_HINT,
813 (
814 lower.saturating_mul(MAX_VOLUME),
815 upper.map(|u| u.saturating_mul(MAX_VOLUME)),
816 ),
817 ))
818 })
819 }
820 }
821}
822
823#[derive(Clone, Copy, Debug, Eq, PartialEq)]
826pub struct VolLengthError {
827 input_length: Option<usize>,
829 bounds: GridAab,
831}
832
833impl core::error::Error for VolLengthError {}
834
835impl fmt::Display for VolLengthError {
836 #[allow(clippy::missing_inline_in_public_items)]
837 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
838 let Self {
839 input_length,
840 bounds,
841 } = self;
842 match (input_length, bounds.volume()) {
843 (Some(input_length), Some(volume)) => write!(
844 f,
845 "data of length {input_length} cannot fill volume {volume} of {bounds:?}",
846 ),
847
848 (Some(input_length), None) => write!(
849 f,
850 "data of length {input_length} cannot fill {bounds:?}, \
851 which is too large to be linearized at all",
852 ),
853
854 (None, None) => write!(
855 f,
856 "{bounds:?} has a volume of {volume_u128}, \
857 which is too large to be linearized",
858 volume_u128 = bounds.size().cast::<u128>().volume(),
860 ),
861
862 (None, Some(_)) => write!(f, "<malformed error {self:?}>"),
863 }
864 }
865}
866
867#[inline(always)] fn index_into_aab_zmaj(bounds: GridAab, cube: Cube) -> Option<usize> {
877 let sizes = bounds.size();
878
879 let deoffsetted: GridPoint = GridPoint::from(cube)
884 .zip(bounds.lower_bounds(), GridCoordinate::wrapping_sub)
885 .to_point();
886
887 if (deoffsetted.x as u32 >= sizes.width)
889 | (deoffsetted.y as u32 >= sizes.height)
890 | (deoffsetted.z as u32 >= sizes.depth)
891 {
892 return None;
893 }
894
895 let ixvec: Point3D<usize, _> = deoffsetted.map(|s| s as usize);
903
904 Some(
908 (ixvec.x.wrapping_mul(sizes.height as usize).wrapping_add(ixvec.y))
909 .wrapping_mul(sizes.depth as usize)
910 .wrapping_add(ixvec.z),
911 )
912}
913
914fn find_zmaj_subdivision(vol: Vol<()>) -> Option<(Vol<()>, Vol<()>, usize)> {
918 let bounds = vol.bounds();
921 for axis in [Axis::X, Axis::Y, Axis::Z] {
922 let axis_range = bounds.axis_range(axis);
923 let size: u32 = bounds.size()[axis];
924 if size >= 2 {
925 let split_coordinate = axis_range.start + (size / 2).cast_signed();
926
927 let mut lower_half_ub = bounds.upper_bounds();
928 lower_half_ub[axis] = split_coordinate;
929 let lower_half = GridAab::from_lower_upper(bounds.lower_bounds(), lower_half_ub)
930 .to_vol()
931 .unwrap_or_else(unreachable_wrong_size);
932
933 let mut upper_half_lb = bounds.lower_bounds();
934 upper_half_lb[axis] = split_coordinate;
935 let upper_half = GridAab::from_lower_upper(upper_half_lb, bounds.upper_bounds())
936 .to_vol()
937 .unwrap_or_else(unreachable_wrong_size);
938
939 let lower_half_volume = lower_half.volume();
940 debug_assert_eq!(lower_half_volume + upper_half.volume(), vol.volume());
941 return Some((lower_half, upper_half, lower_half_volume));
942 }
943 }
944 None
945}
946
947#[cold]
951fn unreachable_wrong_size<T>(error: VolLengthError) -> T {
952 panic!("impossible size mismatch: {error}")
953}
954
955#[cfg(test)]
958mod tests {
959 use super::*;
960 use euclid::{point3, size3};
961 use pretty_assertions::assert_eq;
962
963 type VolBox<T> = Vol<Box<[T]>>;
964
965 fn cube(x: GridCoordinate, y: GridCoordinate, z: GridCoordinate) -> Cube {
966 Cube::new(x, y, z)
967 }
968
969 #[test]
970 fn debug_no_elements() {
971 let vol = GridAab::from_lower_size([10, 0, 0], [4, 1, 1]).to_vol::<ZMaj>().unwrap();
972 assert_eq!(
973 format!("{vol:#?}"),
974 indoc::indoc! {"
975 Vol<(), ZMaj> {
976 bounds: GridAab(
977 10..14 (4),
978 0..1 (1),
979 0..1 (1),
980 ),
981 }\
982 "}
983 )
984 }
985
986 #[test]
987 fn debug_with_contents() {
988 let vol = VolBox::from_fn(GridAab::from_lower_size([10, 0, 0], [4, 1, 1]), |p| p.x);
989 assert_eq!(
990 format!("{vol:#?}"),
991 indoc::indoc! {"
992 Vol<alloc::boxed::Box<[i32]>, ZMaj> {
993 bounds: GridAab(
994 10..14 (4),
995 0..1 (1),
996 0..1 (1),
997 ),
998 contents: [
999 10,
1000 11,
1001 12,
1002 13,
1003 ],
1004 }\
1005 "}
1006 )
1007 }
1008
1009 #[test]
1010 fn debug_without_contents() {
1011 let vol = VolBox::from_fn(GridAab::from_lower_size([0, 0, 0], [64, 1, 1]), |p| p.x);
1012 assert_eq!(
1013 format!("{vol:#?}"),
1014 indoc::indoc! {"
1015 Vol<alloc::boxed::Box<[i32]>, ZMaj> {
1016 bounds: GridAab(
1017 0..64 (64),
1018 0..1 (1),
1019 0..1 (1),
1020 ),
1021 contents: [...64 elements],
1022 }\
1023 "}
1024 )
1025 }
1026
1027 #[test]
1028 fn tiny_is_sufficiently_tiny() {
1029 let maximum_size = num_traits::Bounded::max_value();
1031
1032 let tiny_vol = Vol::tiny(point3(0, 0, 0), Size3D::splat(maximum_size), ZMaj);
1033 assert_eq!(tiny_vol.volume(), usize::from(maximum_size).pow(3));
1034
1035 let tiny_vol_data = Vol::from_tiny_elements(
1037 point3(0, 0, 0),
1038 size3(2, 2, 2),
1039 ZMaj,
1040 &[0, 1, 2, 3, 4, 5, 6, 7],
1041 );
1042 assert_eq!(tiny_vol_data.volume(), 8);
1043 assert_eq!(tiny_vol_data[[1, 1, 0]], 6);
1044 }
1045
1046 #[test]
1047 #[should_panic = "element count does not match AAB volume"]
1048 fn tiny_error() {
1049 _ = Vol::from_tiny_elements(point3(0, 0, 0), size3(2, 2, 2), ZMaj, &[3, 3, 3]);
1050 }
1051
1052 #[test]
1053 fn from_elements() {
1054 let bounds = GridAab::from_lower_size([10, 0, 0], [4, 1, 1]);
1055 assert_eq!(
1056 VolBox::from_fn(bounds, |p| p.x),
1057 VolBox::from_elements(bounds, vec![10i32, 11, 12, 13]).unwrap(),
1058 );
1059 }
1060
1061 #[test]
1062 fn from_elements_error() {
1063 let bounds = GridAab::from_lower_size([10, 0, 0], [4, 1, 1]);
1064 assert_eq!(
1065 VolBox::from_elements(bounds, vec![10i32, 11, 12]),
1066 Err(VolLengthError {
1067 input_length: Some(3),
1068 bounds,
1069 })
1070 );
1071 }
1072
1073 #[test]
1074 fn repeat() {
1075 let bounds = GridAab::from_lower_size([10, 0, 0], [2, 2, 1]);
1076 assert_eq!(
1077 VolBox::repeat(bounds, 9),
1078 VolBox::from_elements(bounds, vec![9, 9, 9, 9]).unwrap(),
1079 );
1080 }
1081
1082 #[test]
1083 fn from_element() {
1084 #[derive(Debug, PartialEq)]
1085 struct Foo;
1086
1087 assert_eq!(
1088 VolBox::from_element(Foo),
1089 VolBox::from_elements(GridAab::ORIGIN_CUBE, [Foo]).unwrap(),
1090 );
1091 assert_eq!(
1092 Vol::from_element_ref(&Foo),
1093 Vol::from_elements(GridAab::ORIGIN_CUBE, [Foo].as_slice()).unwrap(),
1094 );
1095 assert_eq!(
1096 Vol::from_element_mut(&mut Foo),
1097 Vol::from_elements(GridAab::ORIGIN_CUBE, [Foo].as_mut_slice()).unwrap(),
1098 );
1099 }
1100
1101 #[test]
1102 fn from_y_flipped() {
1103 let array = VolBox::from_y_flipped_array([
1104 [*b"abcd", *b"efgh", *b"ijkl"],
1105 [*b"mnop", *b"qrst", *b"uvwx"],
1106 ]);
1107 assert_eq!(
1108 array,
1109 Vol::from_elements(
1110 GridAab::from_lower_size([0, 0, 0], [4, 3, 2]),
1111 *b"iueqamjvfrbnkwgscolxhtdp"
1112 )
1113 .unwrap()
1114 );
1115 }
1116
1117 #[test]
1118 fn index_overflow_low() {
1119 let low = GridAab::from_lower_size([GridCoordinate::MAX - 1, 0, 0], [1, 1, 1])
1122 .to_vol::<ZMaj>()
1123 .unwrap();
1124 assert_eq!(low.index(cube(0, 0, 0)), None);
1125 assert_eq!(low.index(cube(-1, 0, 0)), None);
1126 assert_eq!(low.index(cube(-2, 0, 0)), None);
1127 assert_eq!(low.index(cube(GridCoordinate::MIN, 0, 0)), None);
1128 assert_eq!(low.index(cube(GridCoordinate::MAX - 1, 0, 0)), Some(0));
1130 }
1131
1132 #[test]
1133 fn index_overflow_high() {
1134 let high = GridAab::from_lower_size([GridCoordinate::MAX - 1, 0, 0], [1, 1, 1])
1135 .to_vol::<ZMaj>()
1136 .unwrap();
1137 assert_eq!(high.index(cube(0, 0, 0)), None);
1138 assert_eq!(high.index(cube(1, 0, 0)), None);
1139 assert_eq!(high.index(cube(2, 0, 0)), None);
1140 assert_eq!(high.index(cube(GridCoordinate::MAX - 1, 0, 0)), Some(0));
1141 }
1142
1143 #[test]
1144 fn index_not_overflow_large_volume() {
1145 let vol = GridAab::from_lower_size([0, 0, 0], [2000, 2000, 2000])
1146 .to_vol::<ZMaj>()
1147 .unwrap();
1148 assert_eq!(
1151 vol.index(cube(1500, 1500, 1500)),
1152 Some(((1500 * 2000) + 1500) * 2000 + 1500)
1153 );
1154 }
1155
1156 #[inline(never)]
1158 fn check_subdivide_case(vol: Vol<&mut [Cube]>) {
1159 fn filter(_: [Vol<()>; 2]) -> bool {
1160 true
1161 }
1162
1163 eprintln!("Checking {:?}", vol.bounds());
1164
1165 for (cube, &value) in vol.iter() {
1167 assert_eq!(cube, value);
1168 }
1169
1170 if vol.volume() < 2 {
1171 assert!(vol.without_elements().subdivide().is_err()); assert!(vol.as_ref().subdivide().is_err()); assert!(vol.subdivide(filter).is_err()); } else {
1176 let Ok([a, b]) = vol.without_elements().subdivide() else {
1177 panic!("{vol:?} failed to subdivide");
1178 };
1179 assert_ne!(a.volume(), 0);
1180 assert_ne!(b.volume(), 0);
1181
1182 let [aref, bref] = vol.as_ref().subdivide().unwrap();
1184 assert_eq!((a, b), (aref.without_elements(), bref.without_elements()));
1185
1186 let [amut, bmut] = vol.subdivide(filter).unwrap();
1188 assert_eq!((a, b), (amut.without_elements(), bmut.without_elements()));
1189
1190 check_subdivide_case(amut);
1192 check_subdivide_case(bmut);
1193 }
1194 }
1195 fn check_subdivide(bounds: GridAab) {
1196 check_subdivide_case(Vol::<Box<[Cube]>>::from_fn(bounds, std::convert::identity).as_mut());
1197 }
1198
1199 #[test]
1200 fn subdivide_test() {
1201 check_subdivide(GridAab::ORIGIN_CUBE);
1202 check_subdivide(GridAab::ORIGIN_EMPTY);
1203 check_subdivide(GridAab::from_lower_upper([0, 0, 0], [2, 4, 5]));
1204 }
1205
1206 #[cfg(feature = "arbitrary")]
1207 #[test]
1208 fn arbitrary_bounds_size_hint() {
1209 use arbitrary::{Arbitrary, Unstructured};
1210 let hint = vol_arb::ARBITRARY_BOUNDS_SIZE_HINT;
1211 let most_bytes_used = (0..=255)
1212 .map(|byte| {
1213 let data = [byte; 1000];
1215 let mut u = Unstructured::new(&data);
1216 GridAab::arbitrary(&mut u).unwrap();
1217 let bytes_used = 1000 - u.len();
1218 assert!(
1219 bytes_used >= hint.0,
1220 "used {}, less than {}",
1221 bytes_used,
1222 hint.0
1223 );
1224 bytes_used
1225 })
1226 .max();
1227 assert_eq!(most_bytes_used, hint.1);
1228
1229 }
1231
1232 #[cfg(feature = "arbitrary")]
1233 #[test]
1234 fn arbitrary_bounds_volume() {
1235 use arbitrary::Unstructured;
1236 use itertools::Itertools as _;
1237 let max_volume = 100;
1238 let minmax = (0..=255)
1239 .map(|byte| {
1240 let data = [byte; 25];
1242 let mut u = Unstructured::new(&data);
1243 Vol::<()>::arbitrary_with_max_volume(&mut u, max_volume).unwrap().volume()
1244 })
1245 .minmax()
1246 .into_option();
1247 assert_eq!(minmax, Some((0, max_volume)));
1248 }
1249}