1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
use core::fmt;

/// Acts as polyfill for float methods
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use num_traits::float::FloatCore as _;

use crate::math::{
    Aab, FreeCoordinate, FreePoint, FreeVector, GridAab, GridCoordinate, GridPoint, GridVector,
};
use crate::util::ConciseDebug;

/// “A cube”, in this documentation, is a unit cube whose corners' coordinates are integers.
/// This type identifies such a cube by the coordinates of its most negative corner.
///
/// The valid coordinate range is that of [`GridCoordinate`].
/// Note, however, that in most applications, cubes with lower corner coordinates equal to
/// [`GridCoordinate::MAX`] will not be valid, because their other corners are out of
/// range. The [`Cube`] type does not enforce this, because it would be unergonomic to
/// require fallible conversions there. Instead, the conversion from [`Cube`] to its
/// bounding [`GridAab`] may panic. Generally, this should be avoided by checking
/// the cube with [`GridAab::contains_cube()`] on some existing [`GridAab`].
///
/// Considered in continuous space (real, or floating-point, coordinates), the ranges of
/// coordinates a cube contains are half-open intervals: lower inclusive and upper exclusive.
///
/// # Representation
///
/// This struct is guaranteed to be three `i32` without padding, and so may be reinterpreted
/// as any type of identical layout such as `[i32; 3]`.
///
/// # Why have a dedicated type for this?
///
/// * Primarily, to avoid confusion between points (zero size) and cubes (nonzero size)
///   that causes off-by-one errors when rotating objects.
/// * To provide convenient methods for operations on cubes that aren't natural operations
///   on points.
/// * To reduce our dependence on external math libraries as part of our API.
#[derive(Clone, Copy, Eq, Hash, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[allow(missing_docs, clippy::exhaustive_structs)]
#[repr(C)]
pub struct Cube {
    pub x: i32,
    pub y: i32,
    pub z: i32,
}

impl Cube {
    /// Equal to `Cube::new(0, 0, 0)`.
    ///
    /// Note that this is not a box _centered_ on the coordinate origin.
    pub const ORIGIN: Self = Self::new(0, 0, 0);

    /// Construct `Cube { x, y, z }` from the given coordinates.
    #[inline]
    pub const fn new(x: GridCoordinate, y: GridCoordinate, z: GridCoordinate) -> Self {
        Self { x, y, z }
    }

    /// Convert a point in space to the unit cube that encloses it.
    ///
    /// Such cubes are defined to be half-open intervals on each axis; that is,
    /// an integer coordinate is counted as part of the cube extending positively
    /// from that coordinate.
    ///
    /// If the point coordinates are outside of the numeric range of [`GridCoordinate`],
    /// returns [`None`].
    ///
    /// ```
    /// # extern crate all_is_cubes_base as all_is_cubes;
    /// use all_is_cubes::math::{FreePoint, Cube};
    ///
    /// assert_eq!(Cube::containing(FreePoint::new(1.0, 1.5, -2.5)), Some(Cube::new(1, 1, -3)));
    /// ```
    #[inline]
    pub fn containing(point: FreePoint) -> Option<Self> {
        const RANGE: core::ops::Range<FreeCoordinate> =
            (GridCoordinate::MIN as FreeCoordinate)..(GridCoordinate::MAX as FreeCoordinate + 1.0);

        if RANGE.contains(&point.x) && RANGE.contains(&point.y) && RANGE.contains(&point.z) {
            Some(Self::from(
                point.map(|component| component.floor() as GridCoordinate),
            ))
        } else {
            None
        }
    }

    /// Returns the corner of this cube with the most negative coordinates.
    #[inline] // trivial arithmetic
    pub fn lower_bounds(self) -> GridPoint {
        self.into()
    }

    /// Returns the corner of this cube with the most positive coordinates.
    ///
    /// Panics if `self` has any coordinates equal to [`GridCoordinate::MAX`].
    /// Generally, that should be avoided by checking the cube with
    /// [`GridAab::contains_cube()`] on some existing [`GridAab`] before calling this
    /// method.
    #[inline]
    #[track_caller]
    pub fn upper_bounds(self) -> GridPoint {
        self.checked_add(GridVector::new(1, 1, 1))
            .expect("Cube::upper_bounds() overflowed")
            .lower_bounds()
    }

    /// Returns the midpoint of this cube.
    #[inline] // trivial arithmetic
    pub fn midpoint(self) -> FreePoint {
        let Self { x, y, z } = self;
        FreePoint::new(
            FreeCoordinate::from(x) + 0.5,
            FreeCoordinate::from(y) + 0.5,
            FreeCoordinate::from(z) + 0.5,
        )
    }

    /// Constructs a [`GridAab`] with a volume of 1, containing this cube.
    ///
    /// Panics if `self` has any coordinates equal to [`GridCoordinate::MAX`].
    /// Generally, that should be avoided by checking the cube with
    /// [`GridAab::contains_cube()`] on some existing [`GridAab`] before calling this
    /// method.
    #[inline]
    pub fn grid_aab(self) -> GridAab {
        GridAab::from_lower_size(self.lower_bounds(), [1, 1, 1])
    }

    /// Returns the bounding box in floating-point coordinates containing this cube.
    ///
    /// ```
    /// # extern crate all_is_cubes_base as all_is_cubes;
    /// use all_is_cubes::math::{Aab, Cube};
    ///
    /// assert_eq!(
    ///     Cube::new(10, 20, -30).aab(),
    ///     Aab::new(10.0, 11.0, 20.0, 21.0, -30.0, -29.0)
    /// );
    /// ```
    #[inline]
    pub fn aab(self) -> Aab {
        // Note: this does not use `.upper_bounds()` so that it is non-panicking.
        let lower = GridPoint::from(self).map(FreeCoordinate::from);
        Aab::from_lower_upper(lower, lower + FreeVector::new(1.0, 1.0, 1.0))
    }

    /// Componentwise [`GridCoordinate::checked_add()`].
    #[must_use]
    #[inline]
    pub fn checked_add(self, v: GridVector) -> Option<Self> {
        Some(Self {
            x: self.x.checked_add(v.x)?,
            y: self.y.checked_add(v.y)?,
            z: self.z.checked_add(v.z)?,
        })
    }

    /// Apply a function to each coordinate independently.
    ///
    /// If a different return type is desired, use `.lower_bounds().map(f)` instead.
    #[allow(clippy::return_self_not_must_use)]
    #[inline]
    pub fn map(self, mut f: impl FnMut(GridCoordinate) -> GridCoordinate) -> Self {
        Self {
            x: f(self.x),
            y: f(self.y),
            z: f(self.z),
        }
    }
}

impl fmt::Debug for Cube {
    #[allow(clippy::missing_inline_in_public_items)]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let Self { x, y, z } = self;
        write!(f, "({x:+.3?}, {y:+.3?}, {z:+.3?})")
    }
}
impl manyfmt::Fmt<ConciseDebug> for Cube {
    #[allow(clippy::missing_inline_in_public_items)]
    fn fmt(&self, f: &mut fmt::Formatter<'_>, _: &ConciseDebug) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}

mod arithmetic {
    use super::*;
    use crate::math::Axis;
    use core::ops;

    impl ops::Add<GridVector> for Cube {
        type Output = Self;
        #[inline]
        fn add(self, rhs: GridVector) -> Self::Output {
            Self::from(self.lower_bounds() + rhs)
        }
    }
    impl ops::AddAssign<GridVector> for Cube {
        #[inline]
        fn add_assign(&mut self, rhs: GridVector) {
            *self = Self::from(self.lower_bounds() + rhs)
        }
    }

    impl ops::Sub<GridVector> for Cube {
        type Output = Self;
        #[inline]
        fn sub(self, rhs: GridVector) -> Self::Output {
            Self::from(self.lower_bounds() - rhs)
        }
    }
    impl ops::SubAssign<GridVector> for Cube {
        #[inline]
        fn sub_assign(&mut self, rhs: GridVector) {
            *self = Self::from(self.lower_bounds() - rhs)
        }
    }

    impl ops::Sub<Cube> for Cube {
        type Output = GridVector;
        #[inline]
        fn sub(self, rhs: Cube) -> Self::Output {
            self.lower_bounds() - rhs.lower_bounds()
        }
    }

    impl ops::Index<Axis> for Cube {
        type Output = GridCoordinate;
        #[inline]
        fn index(&self, index: Axis) -> &Self::Output {
            match index {
                Axis::X => &self.x,
                Axis::Y => &self.y,
                Axis::Z => &self.z,
            }
        }
    }
    impl ops::IndexMut<Axis> for Cube {
        #[inline]
        fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
            match index {
                Axis::X => &mut self.x,
                Axis::Y => &mut self.y,
                Axis::Z => &mut self.z,
            }
        }
    }
}

mod conversion {
    use super::*;

    impl AsRef<[GridCoordinate; 3]> for Cube {
        #[inline]
        fn as_ref(&self) -> &[GridCoordinate; 3] {
            bytemuck::cast_ref(self)
        }
    }
    impl AsMut<[GridCoordinate; 3]> for Cube {
        #[inline]
        fn as_mut(&mut self) -> &mut [GridCoordinate; 3] {
            bytemuck::cast_mut(self)
        }
    }
    impl core::borrow::Borrow<[GridCoordinate; 3]> for Cube {
        #[inline]
        fn borrow(&self) -> &[GridCoordinate; 3] {
            bytemuck::cast_ref(self)
        }
    }
    impl core::borrow::BorrowMut<[GridCoordinate; 3]> for Cube {
        #[inline]
        fn borrow_mut(&mut self) -> &mut [GridCoordinate; 3] {
            bytemuck::cast_mut(self)
        }
    }

    impl From<Cube> for [GridCoordinate; 3] {
        #[inline]
        fn from(Cube { x, y, z }: Cube) -> [GridCoordinate; 3] {
            [x, y, z]
        }
    }
    impl From<Cube> for GridPoint {
        #[inline]
        fn from(Cube { x, y, z }: Cube) -> GridPoint {
            GridPoint::new(x, y, z)
        }
    }

    impl From<[GridCoordinate; 3]> for Cube {
        #[inline]
        fn from([x, y, z]: [GridCoordinate; 3]) -> Self {
            Self { x, y, z }
        }
    }
    impl From<GridPoint> for Cube {
        #[inline]
        fn from(GridPoint { x, y, z, _unit }: GridPoint) -> Self {
            Self { x, y, z }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use euclid::point3;

    #[test]
    fn containing_simple() {
        assert_eq!(
            Cube::containing(point3(1.5, -2.0, -3.5)),
            Some(Cube::new(1, -2, -4))
        );
    }

    #[test]
    fn containing_inf() {
        assert_eq!(
            Cube::containing(point3(FreeCoordinate::INFINITY, 0., 0.)),
            None
        );
        assert_eq!(
            Cube::containing(point3(-FreeCoordinate::INFINITY, 0., 0.)),
            None
        );
    }

    #[test]
    fn containing_nan() {
        assert_eq!(Cube::containing(point3(0., 0., FreeCoordinate::NAN)), None);
    }

    #[test]
    fn containing_in_and_out_of_range() {
        let fmax = FreeCoordinate::from(GridCoordinate::MAX);
        let fmin = FreeCoordinate::from(GridCoordinate::MIN);

        // min Z
        assert_eq!(Cube::containing(point3(0., 0., fmin - 0.001)), None);
        assert_eq!(
            Cube::containing(point3(0., 0., fmin + 0.001,)),
            Some(Cube::new(0, 0, GridCoordinate::MIN))
        );

        // max Z
        assert_eq!(
            Cube::containing(point3(0., 0., fmax + 0.999,)),
            Some(Cube::new(0, 0, GridCoordinate::MAX))
        );
        assert_eq!(Cube::containing(point3(0., 0., fmax + 1.001)), None);

        // max Y (exercise more axes)
        assert_eq!(
            Cube::containing(point3(0., fmax + 0.999, 0.)),
            Some(Cube::new(0, GridCoordinate::MAX, 0))
        );
        assert_eq!(Cube::containing(point3(0., fmax + 1.001, 0.)), None);

        // max X
        assert_eq!(
            Cube::containing(point3(fmax + 0.999, 0., 0.)),
            Some(Cube::new(GridCoordinate::MAX, 0, 0))
        );
        assert_eq!(Cube::containing(point3(fmax + 1.001, 0., 0.)), None);
    }
}