all_is_cubes_base/math/
vol.rs

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// -------------------------------------------------------------------------------------------------
21
22// #[derive(Clone, Copy, Debug)]
23// pub struct XMaj;
24
25/// Z-major ordering: linearly adjacent elements have adjacent Z coordinates.
26///
27/// `[0, 0, 0], [0, 0, 1], [0, 0, 2], ..., [0, 1, 0], [0, 1, 1], ...`
28///
29/// Use this type with [`Vol`] to store volume data in this order.
30#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
31#[expect(clippy::exhaustive_structs)]
32pub struct ZMaj;
33
34/// Type for volume data stored in a slice, or for generating linear indexing.
35///
36/// * `C` is some slice container type, e.g. `&[T]` or `Box<[T]>`.
37///   It may also be `()` to describe a linearization without actually storing data.
38/// * `O` specifies the choice of linearization.
39///   Currently, only one choice exists, [`ZMaj`].
40///
41/// In addition to the data, each [`Vol`] stores the [`GridAab`] defining its size;
42/// the container's length must be equal to the volume of that AAB. Whether that can be
43/// relied upon entirely depends on whether the specific container value produces
44/// the same length of slice every time it is [`Deref`]erenced without mutating it directly.
45/// For example, `Vec<T>` and `Box<[T]>` satisfy this criterion; the fact that [`Vec`] has
46/// length-mutating operations is irrelevant because no `&mut Vec<T>` is exposed.
47///
48/// A [`Vol`] whose volume exceeds [`usize::MAX`] cannot exist.
49#[derive(Clone, Copy, Eq, Hash, PartialEq)]
50pub struct Vol<C, O = ZMaj> {
51    /// Invariant: `bounds` has a volume that is at most [`usize::MAX`].
52    bounds: GridAab,
53    ordering: O,
54    /// Invariant: `contents.deref().len()`, if it exists, equals `bounds.volume()`.
55    contents: C,
56}
57
58// -------------------------------------------------------------------------------------------------
59// `impl Vol<...> {` with various bounds
60
61impl<O> Vol<(), O> {
62    /// Use `GridAab::to_vol()` to call this.
63    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    /// Constructs a [`Vol`] from 8-bit integers that cannot overflow.
79    ///
80    /// This constructor is limited so that it is `const` and infallible;
81    /// It is the [`Vol`] version of [`GridAab::tiny()`].
82    #[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    /// Attach some data to this dataless `Vol`.
96    ///
97    /// Returns a [`VolLengthError`] if the number of elements does not match
98    /// [`bounds.volume()`](GridAab::volume).
99    #[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    /// Divide `self` into two approximately equal-sized parts which, if they had elements, would
121    /// each be contiguous in the linear ordering.
122    ///
123    /// Returns [`None`] if `self` does not have at least two cubes.
124    ///
125    /// Note that this is one of several `subdivide()` methods for different container types;
126    /// it is also implemented for immutable and mutable references.
127    /// These are intended to be useful in executing parallel algorithms on volume data.
128    #[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
137/// Constructors from linear containers.
138impl<C, O: Default, V> Vol<C, O>
139where
140    C: Deref<Target = [V]>,
141{
142    /// Constructs a `Vol<C>` containing the provided elements, which must be in the
143    /// ordering specified by `O`.
144    ///
145    /// Returns a [`VolLengthError`] if the number of elements does not match
146    /// [`bounds.volume()`](GridAab::volume).
147    //---
148    // TODO: Remove this in favor of with_elements()?
149    #[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    /// Constructs a [`Vol`] containing the provided elements, of which there must be no more than
169    /// 255³.
170    ///
171    /// This constructor is limited so that it is `const` and infallible;
172    /// see also [`GridAab::tiny()`] and [`Vol::tiny()`].
173    ///
174    /// Panics if `data`’s length does not match the volume of `size`.
175    #[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    /// Constructs a shared [`Vol`] reference to a single element, with bounds
197    /// [`GridAab::ORIGIN_CUBE`].
198    ///
199    /// If the single element should be at a different location, you can call
200    /// [`.translate(offset)`](Self::translate), or use [`Vol::from_elements()`]
201    /// instead.
202    #[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    /// Constructs a mutable [`Vol`] reference to a single element, with bounds
214    /// [`GridAab::ORIGIN_CUBE`].
215    ///
216    /// If the single element should be at a different location, you can call
217    /// [`.translate(offset)`](Self::translate), or use [`Vol::from_elements()`]
218    /// instead.
219    #[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/// Constructors from elements not already stored linearly.
230//---
231// TODO: This should be `O: Ordering` instead of `ZMaj` once we have alternative orderings
232#[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
233impl<C, V> Vol<C, ZMaj>
234where
235    // Note that the Deref bound is necessary to give this a unique `V`.
236    C: Deref<Target = [V]> + FromIterator<V>,
237{
238    /// Constructs a `Vol<C>` by using the provided function to compute a value
239    /// for each point.
240    ///
241    /// Panics if `bounds` has a volume exceeding `usize::MAX`.
242    /// (But there will likely be a memory allocation failure well below that point.)
243    #[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    /// Constructs a `Vol<C>` by cloning the provided value for each point.
256    #[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    /// Constructs a `Vol<C>` with a single value, in bounds `ORIGIN_CUBE`.
265    ///
266    /// If the single element should be at a different location, you can call
267    /// [`.translate(offset)`](Self::translate), or use [`Vol::from_elements()`]
268    /// instead.
269    #[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    /// Constructs a [`Vol<Box<[V]>>`] from nested Rust arrays in \[Z\]\[Y\]\[X\] order
275    /// with the Y axis mirrored.
276    /// The result's bounds's lower bounds are zero.
277    ///
278    /// Note: The current implementation requires that `V` implement [`Clone`], and will
279    /// clone each element once, but this may be improved in the future.
280    // TODO: Decide if this is a good public interface.
281    // TODO: Reimplement this in terms of adopting the elements as a linear array.
282    // TODO: Test.
283    #[doc(hidden)] // used by all-is-cubes-content
284    #[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    /// Returns the [`GridAab`] specifying the bounds of this volume data.
300    #[inline]
301    pub fn bounds(&self) -> GridAab {
302        self.bounds
303    }
304
305    /// Returns the volume, also known as the number of elements.
306    #[inline]
307    pub fn volume(&self) -> usize {
308        // Ideally, we could specialize on C and return self.contents.len() if possible,
309        // as it doesn't require doing any multiplications, but that's not currently possible
310        // in Rust.
311
312        let size = self.bounds.size();
313        // This will not overflow, as an invariant of the `Vol` type.
314        size.width as usize * size.height as usize * size.depth as usize
315    }
316
317    /// Extracts the linear contents, discarding the bounds and ordering.
318    #[inline]
319    pub fn into_elements(self) -> C {
320        self.contents
321    }
322
323    /// Returns a `Vol` with the same bounds and ordering but no data.
324    ///
325    /// This is the inverse operation to [`Vol::with_elements()`].
326    #[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    /// Translates the volume without affecting its contents.
339    ///
340    /// Panics if this would cause numeric overflow.
341    ///
342    /// TODO: example
343    #[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            // We can't just continue like `GridAab::translate` does, because that would
356            // break the invariant that self.volume() == self.contents.len().
357            panic!("Vol::translate() offset caused numeric overflow");
358        }
359        self.bounds = new_bounds;
360        self
361    }
362
363    // TODO: reconcile this with from_elements() — should only be implemented once.
364    #[doc(hidden)] // TODO: good public api?
365    #[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    /// Iterate over all cubes that this contains, in the order of the linearization,
393    /// without including the stored data (if there is any).
394    #[inline]
395    pub fn iter_cubes(&self) -> GridIter {
396        GridIter::new(self.bounds)
397    }
398
399    /// Determines whether a unit cube lies within this volume and, if it does, returns the
400    /// linearized slice index into it.
401    ///
402    /// The linearized element order is defined by the `O` type.
403    ///
404    /// ```
405    /// # extern crate all_is_cubes_base as all_is_cubes;
406    /// use all_is_cubes::math::{Vol, GridAab};
407    ///
408    /// let vol = GridAab::from_lower_size([0, 0, 0], [10, 10, 10]).to_vol().unwrap();
409    ///
410    /// assert_eq!(vol.index([0, 0, 0].into()), Some(0));
411    /// assert_eq!(vol.index([1, 2, 3].into()), Some(123));
412    /// assert_eq!(vol.index([9, 9, 9].into()), Some(999));
413    /// assert_eq!(vol.index([0, 0, -1].into()), None);
414    /// assert_eq!(vol.index([0, 0, 10].into()), None);
415    /// ```
416    ///
417    /// TODO: more example, less unit-test
418    #[inline(always)] // very hot code
419    pub fn index(&self, cube: Cube) -> Option<usize> {
420        index_into_aab_zmaj(self.bounds, cube)
421    }
422}
423
424/// Linear data access.
425#[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    /// Return a [`Vol`] that borrows the contents of this one.
432    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    /// Return a [`Vol`] that mutably borrows the contents of this one.
441    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    /// Returns the linear contents viewed as a slice.
453    pub fn as_linear(&self) -> &[V] {
454        let s = &*self.contents;
455        debug_assert_eq!(s.len(), self.volume());
456        s
457    }
458
459    /// Returns the linear contents viewed as a mutable slice.
460    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    /// Returns the element at `position` of this volume data, or [`None`] if `position` is out
472    /// of bounds.
473    ///
474    /// This differs from [`Self::get()`] in that it inherits the lifetime of the container
475    /// reference, rather than reborrowing. It is therefore only available for `Vol<&'a [V]>`.
476    #[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    /// Divide `self` into two approximately equal-sized parts,
483    /// each of which refers to the appropriate sub-slice of elements.
484    ///
485    /// Returns [`None`] if `self` does not have at least two cubes.
486    ///
487    /// Note that this is one of several `subdivide()` methods for different container types;
488    /// it is also implemented for mutable references and `()`.
489    /// These are intended to be useful in executing parallel algorithms on volume data.
490    #[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    /// Divide `self` into two approximately equal-sized parts.
508    /// each of which refers to the appropriate sub-slice of elements.
509    ///
510    /// `filter` may be used to reject subdivisions that are too small or otherwise unsuitable;
511    /// it is passed the bounds of the two slices that would be returned.
512    ///
513    /// Returns [`Err`] containing `self` if `self` does not have at least two cubes,
514    /// or if `filter` returns `false`.
515    ///
516    /// Note that this is one of several `subdivide()` methods for different container types;
517    /// it is also implemented for immutable references and `()`.
518    /// These are intended to be useful in executing parallel algorithms on volume data.
519    //---
520    // Design note: This has the `filter` parameter, where other `subdivide()`s do not,
521    // because otherwise the caller may have trouble with conditional returns of mutable borrows
522    // (NLL Problem Case #3); the filter allows cancelling the split rather than discarding it.
523    #[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    /// Returns the linear contents viewed as a mutable slice, as if by [`Arc::make_mut()`].
541    #[doc(hidden)] // TODO: good public API?
542    #[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/// Element lookup operations by 3D coordinates.
551#[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    /// Returns the element at `position` of this volume data, or [`None`] if `position` is out
557    /// of bounds.
558    #[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    /// Returns a mutable reference to the element at `position` of this volume data,
565    /// or [`None`] if `position` is out of bounds.
566    #[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    /// Iterates over all the cubes and values in this volume data, in the ordering specified
576    /// by the `O` type parameter.
577    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    /// Iterates by mutable reference over all the cubes and values in this volume data,
585    /// in the ordering specified by the `O` type parameter.
586    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    /// Apply `f` to each element and collect the results into the same shape and ordering.
598    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            // When we switch to Rust 2024 edition, replace this qualified call with a method call.
606            contents: <Box<[V]> as IntoIterator>::into_iter(self.contents).map(f).collect(),
607        }
608    }
609}
610
611// end of `impl Vol<...> {`
612// -------------------------------------------------------------------------------------------------
613// `impl Trait for Vol<...>`
614
615#[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        // Note: If specialization was available we'd like to use it to print the elements under
619        // our own control if there are elements, but as it is, we'd rather preserve functionality
620        // for `Vol<()>`, which means we always print self.contents as a whole or not at all.
621
622        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            // don't print "contents: ()"
636        } 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    /// Returns the element at `position` of this volume data,
652    /// or panics if `position` is out of bounds.
653    ///
654    /// Use [`Vol::get()`] for a non-panicing alternative.
655    #[inline(always)] // measured faster on wasm32 in worldgen
656    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    /// Returns the element at `position` of this volume data,
669    /// or panics if `position` is out of bounds.
670    #[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    /// Returns the element at <code>[Cube]::from(position)</code> of this volume data,
687    /// or panics if `position` is out of bounds.
688    ///
689    /// This implementation is a convenience for writing `vol[[x, y, z]]`.
690    /// We recommend indexing using [`Cube`] values in all non-trivial cases,
691    /// to avoid confusion between cubes and their corner points.
692    #[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    /// Returns the element at <code>[Cube]::from(position)</code>of this volume data,
700    /// or panics if `position` is out of bounds.
701    ///
702    /// This implementation is a convenience for writing `vol[[x, y, z]]`.
703    /// We recommend indexing using [`Cube`] values in all non-trivial cases,
704    /// to avoid confusion between cubes and their corner points.
705    #[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    /// Let's not spend too much memory on generating arbitrary length vectors.
736    /// This does reduce coverage...
737    const MAX_VOLUME: usize = 2_usize.pow(16);
738
739    /// Size hint for [`Vol::arbitrary_with_max_volume()`].
740    pub(crate) const ARBITRARY_BOUNDS_SIZE_HINT: (usize, Option<usize>) = {
741        // 6 bounding coordinates plus one permutation selection.
742        // Depending on the volume, we could end up consuming as little as 1 byte each
743        // for the sizes, and none for the positions, because `int_in_range()` uses the range
744        // to decide how many bytes to consume.
745        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            // Pick sizes within the volume constraint.
757            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            // Shuffle the sizes to remove any bias.
765            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            // Compute lower bounds that are valid for the sizes.
775            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            // recommended impl from trait documentation
803            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/// Error from [`Vol::from_elements()`] being given the wrong length,
824/// or from constructing a [`Vol`] with a volume greater than [`usize::MAX`].
825#[derive(Clone, Copy, Debug, Eq, PartialEq)]
826pub struct VolLengthError {
827    /// The length of the linear data, or [`None`] if we're constructing a dataless [`Vol`].
828    input_length: Option<usize>,
829    /// The attempted bounds, whose volume is either unequal to `input_length` or overflowing.
830    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                // u128 is large enough to hold the multiplication of three u32s
859                volume_u128 = bounds.size().cast::<u128>().volume(),
860            ),
861
862            (None, Some(_)) => write!(f, "<malformed error {self:?}>"),
863        }
864    }
865}
866
867// -------------------------------------------------------------------------------------------------
868// Algorithms and helper functions not part of `Vol` impls
869
870/// Computes a linear index into a [`GridAab`], without checking whether it is big enough to
871/// overflow [`usize`].
872///
873/// This function can only be called through [`Vol`] which does check; it is the implementation
874/// of [`Vol::index()`].
875#[inline(always)] // very hot code
876fn index_into_aab_zmaj(bounds: GridAab, cube: Cube) -> Option<usize> {
877    let sizes = bounds.size();
878
879    // This might overflow and wrap, but if it does, the result will still be out
880    // of bounds, just in the other direction, because wrapping subtraction is an
881    // injective mapping of integers, and every in-bounds maps to in-bounds, so
882    // every out-of-bounds must also map to out-of-bounds.
883    let deoffsetted: GridPoint = GridPoint::from(cube)
884        .zip(bounds.lower_bounds(), GridCoordinate::wrapping_sub)
885        .to_point();
886
887    // Bounds check, expressed as a single unsigned comparison.
888    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    // Convert to usize for indexing.
896    // This cannot overflow because:
897    // * We just checked it is not negative
898    // * We just checked it is not greater than `self.sizes[i]`, which is an `i32`
899    // * We don't support platforms with `usize` smaller than 32 bits
900    // We use `as usize` rather than `deoffsetted.to_usize()` because the latter has an
901    // overflow check.
902    let ixvec: Point3D<usize, _> = deoffsetted.map(|s| s as usize);
903
904    // Compute index.
905    // Always use wrapping (rather than maybe-checked) arithmetic, because we
906    // checked the criteria for it to not overflow.
907    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
914/// Find a way to split the bounds of a `Vol` which results in two adjacent volumes
915/// whose linear elements are also adjacent.
916/// Returns the two boxes and the linear split point.
917fn find_zmaj_subdivision(vol: Vol<()>) -> Option<(Vol<()>, Vol<()>, usize)> {
918    // The order of these tests must reflect the ordering in use
919    // for the result to be valid.
920    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/// Function for `.unwrap_or_else()`s inside subdivision operations.
948/// The advantage of this over many `unwrap()`s is generating fewer distinct panic sites for
949/// these cases which are impossible.
950#[cold]
951fn unreachable_wrong_size<T>(error: VolLengthError) -> T {
952    panic!("impossible size mismatch: {error}")
953}
954
955// -------------------------------------------------------------------------------------------------
956
957#[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        // Obtain the maximum without specifying what we think the type is.
1030        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        // Try the constructor with elements
1036        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        // Indexing calculates (point - lower_bounds), so this would overflow in the negative direction if the overflow weren't checked.
1120        // Note that MAX - 1 is the highest allowed lower bound since the exclusive upper bound must be representable.
1121        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        // But, an actually in-bounds cube should still work.
1129        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        // This value fits in a 32-bit `usize` and is therefore a valid index,
1149        // but it does not fit in a `GridCoordinate` = `i32`.
1150        assert_eq!(
1151            vol.index(cube(1500, 1500, 1500)),
1152            Some(((1500 * 2000) + 1500) * 2000 + 1500)
1153        );
1154    }
1155
1156    /// Test the properties of the `subdivide()` operations, starting from this example.
1157    #[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        // Check the elements are as expected
1166        for (cube, &value) in vol.iter() {
1167            assert_eq!(cube, value);
1168        }
1169
1170        if vol.volume() < 2 {
1171            // Never subdivide a cube or empty.
1172            assert!(vol.without_elements().subdivide().is_err()); // () version
1173            assert!(vol.as_ref().subdivide().is_err()); // & version
1174            assert!(vol.subdivide(filter).is_err()); // &mut version
1175        } 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            // Compare immutable slice subdivide
1183            let [aref, bref] = vol.as_ref().subdivide().unwrap();
1184            assert_eq!((a, b), (aref.without_elements(), bref.without_elements()));
1185
1186            // Compare mutable slice subdivide
1187            let [amut, bmut] = vol.subdivide(filter).unwrap();
1188            assert_eq!((a, b), (amut.without_elements(), bmut.without_elements()));
1189
1190            // Recurse
1191            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                // TODO: sketchy coverage; would be better to generate some random/hashed data
1214                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        // TODO: Also look at the resulting Grids and see if they're good coverage.
1230    }
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                // TODO: sketchy coverage; would be better to generate some random/hashed data
1241                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}