Skip to main content

steel_utils/
geometry.rs

1//! Geometry primitives shared by registry data, physics, and world queries.
2
3use std::marker::PhantomData;
4use std::ops::{Add, Div, Neg, Sub};
5
6use glam::{DVec3, IVec3};
7
8use crate::{BlockPos, axis::Axis};
9
10const fn ordered_pair(a: f64, b: f64) -> (f64, f64) {
11    if a <= b { (a, b) } else { (b, a) }
12}
13
14const fn ordered_pair_i32(a: i32, b: i32) -> (i32, i32) {
15    if a <= b { (a, b) } else { (b, a) }
16}
17
18/// Encodes the edge semantics of a coordinate space.
19pub trait Space {
20    /// Whether `min == max` on an axis means the box has zero extent.
21    const ZERO_SPAN_IS_EMPTY: bool;
22}
23
24/// Marker type for block-local AABBs.
25#[derive(Debug, Clone, Copy, PartialEq)]
26pub struct BlockLocal;
27
28impl Space for BlockLocal {
29    const ZERO_SPAN_IS_EMPTY: bool = true;
30}
31
32/// Marker type for world-space AABBs.
33#[derive(Debug, Clone, Copy, PartialEq)]
34pub struct World;
35
36impl Space for World {
37    const ZERO_SPAN_IS_EMPTY: bool = true;
38}
39
40/// Marker type for integer bounding boxes (structure pieces).
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub struct Structure;
43
44impl Space for Structure {
45    const ZERO_SPAN_IS_EMPTY: bool = false;
46}
47
48/// Generic axis-aligned bounding box.
49///
50/// `T` is the vector type (e.g. [`DVec3`] or [`IVec3`]) and `I` is a marker
51/// that differentiates coordinate spaces (block-local, world, structure).
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53pub struct Aabb<T, I> {
54    /// Minimum corner of the box.
55    min: T,
56    /// Maximum corner of the box.
57    max: T,
58    p: PhantomData<I>,
59}
60
61/// Block-local axis-aligned box used by voxel shapes.
62pub type BlockLocalAabb = Aabb<DVec3, BlockLocal>;
63
64/// World-space axis-aligned box used by entity and collision physics.
65pub type WorldAabb = Aabb<DVec3, World>;
66
67/// Integer axis-aligned bounding box for structure pieces.
68pub type BoundingBox = Aabb<IVec3, Structure>;
69
70/// Vector operations used by generic AABB helpers.
71pub trait AabbVector: Copy + Add<Output = Self> + Sub<Output = Self> {
72    /// Scalar component type.
73    type Scalar: Copy
74        + PartialOrd
75        + Add<Output = Self::Scalar>
76        + Sub<Output = Self::Scalar>
77        + Div<Output = Self::Scalar>
78        + Neg<Output = Self::Scalar>
79        + From<u8>;
80
81    /// Creates a vector from individual components.
82    fn new(x: Self::Scalar, y: Self::Scalar, z: Self::Scalar) -> Self;
83
84    /// X component.
85    fn x(self) -> Self::Scalar;
86
87    /// Y component.
88    fn y(self) -> Self::Scalar;
89
90    /// Z component.
91    fn z(self) -> Self::Scalar;
92
93    /// Per-component minimum.
94    #[must_use]
95    fn min(self, other: Self) -> Self;
96
97    /// Per-component maximum.
98    #[must_use]
99    fn max(self, other: Self) -> Self;
100}
101
102impl AabbVector for DVec3 {
103    type Scalar = f64;
104
105    fn new(x: Self::Scalar, y: Self::Scalar, z: Self::Scalar) -> Self {
106        DVec3::new(x, y, z)
107    }
108
109    fn x(self) -> Self::Scalar {
110        self.x
111    }
112
113    fn y(self) -> Self::Scalar {
114        self.y
115    }
116
117    fn z(self) -> Self::Scalar {
118        self.z
119    }
120
121    fn min(self, other: Self) -> Self {
122        self.min(other)
123    }
124
125    fn max(self, other: Self) -> Self {
126        self.max(other)
127    }
128}
129
130impl AabbVector for IVec3 {
131    type Scalar = i32;
132
133    fn new(x: Self::Scalar, y: Self::Scalar, z: Self::Scalar) -> Self {
134        IVec3::new(x, y, z)
135    }
136
137    fn x(self) -> Self::Scalar {
138        self.x
139    }
140
141    fn y(self) -> Self::Scalar {
142        self.y
143    }
144
145    fn z(self) -> Self::Scalar {
146        self.z
147    }
148
149    fn min(self, other: Self) -> Self {
150        self.min(other)
151    }
152
153    fn max(self, other: Self) -> Self {
154        self.max(other)
155    }
156}
157
158impl<T: AabbVector, I> Aabb<T, I> {
159    /// Returns the minimum coordinate on `axis`.
160    #[must_use]
161    pub fn min(&self, axis: Axis) -> T::Scalar {
162        match axis {
163            Axis::X => self.min.x(),
164            Axis::Y => self.min.y(),
165            Axis::Z => self.min.z(),
166        }
167    }
168
169    /// Returns the maximum coordinate on `axis`.
170    #[must_use]
171    pub fn max(&self, axis: Axis) -> T::Scalar {
172        match axis {
173            Axis::X => self.max.x(),
174            Axis::Y => self.max.y(),
175            Axis::Z => self.max.z(),
176        }
177    }
178
179    /// Creates an AABB ensuring min <= max on every axis.
180    #[must_use]
181    pub fn from_min_max(min: T, max: T) -> Self {
182        Self {
183            min: min.min(max),
184            max: min.max(max),
185            p: PhantomData,
186        }
187    }
188
189    /// Translates the box by a vector.
190    #[must_use]
191    pub fn translate(self, delta: T) -> Self {
192        Self {
193            min: self.min + delta,
194            max: self.max + delta,
195            p: PhantomData,
196        }
197    }
198
199    /// Expands the box in every direction by `amount`.
200    #[must_use]
201    pub fn inflate(self, amount: T::Scalar) -> Self {
202        self.inflate_xyz(amount, amount, amount)
203    }
204
205    /// Expands the box independently on each axis.
206    #[must_use]
207    pub fn inflate_xyz(self, x: T::Scalar, y: T::Scalar, z: T::Scalar) -> Self {
208        let delta = T::new(x, y, z);
209        Self::from_min_max(self.min - delta, self.max + delta)
210    }
211
212    /// Returns the smallest AABB that contains both `a` and `b`.
213    #[must_use]
214    pub fn encapsulating(a: &Self, b: &Self) -> Self {
215        Self {
216            min: a.min.min(b.min),
217            max: a.max.max(b.max),
218            p: PhantomData,
219        }
220    }
221}
222
223impl<I> Aabb<DVec3, I> {
224    /// Returns the minimum corner.
225    #[must_use]
226    pub const fn min_corner(&self) -> DVec3 {
227        self.min
228    }
229
230    /// Returns the maximum corner.
231    #[must_use]
232    pub const fn max_corner(&self) -> DVec3 {
233        self.max
234    }
235
236    /// Returns the minimum X coordinate.
237    #[must_use]
238    pub const fn min_x(&self) -> f64 {
239        self.min.x
240    }
241
242    /// Returns the minimum Y coordinate.
243    #[must_use]
244    pub const fn min_y(&self) -> f64 {
245        self.min.y
246    }
247
248    /// Returns the minimum Z coordinate.
249    #[must_use]
250    pub const fn min_z(&self) -> f64 {
251        self.min.z
252    }
253
254    /// Returns the maximum X coordinate.
255    #[must_use]
256    pub const fn max_x(&self) -> f64 {
257        self.max.x
258    }
259
260    /// Returns the maximum Y coordinate.
261    #[must_use]
262    pub const fn max_y(&self) -> f64 {
263        self.max.y
264    }
265
266    /// Returns the maximum Z coordinate.
267    #[must_use]
268    pub const fn max_z(&self) -> f64 {
269        self.max.z
270    }
271
272    /// Returns the squared distance from `point` to this box.
273    ///
274    /// Mirrors vanilla `AABB.distanceToSqr`.
275    #[must_use]
276    pub fn distance_to_sqr(self, point: DVec3) -> f64 {
277        let dx = f64::max(f64::max(self.min.x - point.x, point.x - self.max.x), 0.0);
278        let dy = f64::max(f64::max(self.min.y - point.y, point.y - self.max.y), 0.0);
279        let dz = f64::max(f64::max(self.min.z - point.z, point.z - self.max.z), 0.0);
280        dx * dx + dy * dy + dz * dz
281    }
282
283    /// Returns the closest point inside this box to `point`.
284    ///
285    /// Mirrors the per-box clamp used by vanilla `VoxelShape.closestPointTo`.
286    #[must_use]
287    pub const fn closest_point_to(self, point: DVec3) -> DVec3 {
288        DVec3::new(
289            point.x.clamp(self.min.x, self.max.x),
290            point.y.clamp(self.min.y, self.max.y),
291            point.z.clamp(self.min.z, self.max.z),
292        )
293    }
294}
295
296impl<I> Aabb<IVec3, I> {
297    /// Returns the minimum corner.
298    #[must_use]
299    #[inline]
300    pub const fn min_corner(&self) -> IVec3 {
301        self.min
302    }
303
304    /// Returns the maximum corner.
305    #[must_use]
306    #[inline]
307    pub const fn max_corner(&self) -> IVec3 {
308        self.max
309    }
310
311    /// Returns the minimum X coordinate.
312    #[must_use]
313    #[inline]
314    pub const fn min_x(&self) -> i32 {
315        self.min.x
316    }
317
318    /// Returns the minimum Y coordinate.
319    #[must_use]
320    #[inline]
321    pub const fn min_y(&self) -> i32 {
322        self.min.y
323    }
324
325    /// Returns the minimum Z coordinate.
326    #[must_use]
327    #[inline]
328    pub const fn min_z(&self) -> i32 {
329        self.min.z
330    }
331
332    /// Returns the maximum X coordinate.
333    #[must_use]
334    #[inline]
335    pub const fn max_x(&self) -> i32 {
336        self.max.x
337    }
338
339    /// Returns the maximum Y coordinate.
340    #[must_use]
341    #[inline]
342    pub const fn max_y(&self) -> i32 {
343        self.max.y
344    }
345
346    /// Returns the maximum Z coordinate.
347    #[must_use]
348    #[inline]
349    pub const fn max_z(&self) -> i32 {
350        self.max.z
351    }
352}
353
354impl<T: AabbVector, I> Aabb<T, I> {
355    /// Shrinks the box by `amount` in every direction.
356    #[must_use]
357    pub fn deflate(self, amount: T::Scalar) -> Self {
358        self.inflate(-amount)
359    }
360}
361
362impl<T: AabbVector, I: Space> Aabb<T, I> {
363    #[inline]
364    fn axis_overlaps(min1: T::Scalar, max1: T::Scalar, min2: T::Scalar, max2: T::Scalar) -> bool {
365        if I::ZERO_SPAN_IS_EMPTY {
366            min1 < max2 && max1 > min2
367        } else {
368            min1 <= max2 && max1 >= min2
369        }
370    }
371
372    #[inline]
373    fn axis_contains(min: T::Scalar, max: T::Scalar, v: T::Scalar) -> bool {
374        if I::ZERO_SPAN_IS_EMPTY {
375            v >= min && v < max
376        } else {
377            v >= min && v <= max
378        }
379    }
380
381    /// Returns `true` when this box has no positive volume on at least one axis.
382    pub fn is_empty(&self) -> bool {
383        if I::ZERO_SPAN_IS_EMPTY {
384            self.min.x() >= self.max.x()
385                || self.min.y() >= self.max.y()
386                || self.min.z() >= self.max.z()
387        } else {
388            self.min.x() > self.max.x()
389                || self.min.y() > self.max.y()
390                || self.min.z() > self.max.z()
391        }
392    }
393
394    /// Returns whether this bounding box intersects another.
395    #[must_use]
396    pub fn intersects(self, other: Self) -> bool {
397        self.intersects_bounds(other.min, other.max)
398    }
399
400    /// Returns `true` if this box intersects the given bounds.
401    #[must_use]
402    pub fn intersects_bounds(self, min: T, max: T) -> bool {
403        Self::axis_overlaps(self.min.x(), self.max.x(), min.x(), max.x())
404            && Self::axis_overlaps(self.min.y(), self.max.y(), min.y(), max.y())
405            && Self::axis_overlaps(self.min.z(), self.max.z(), min.z(), max.z())
406    }
407
408    /// Returns whether this bounding box intersects the given XZ range.
409    #[must_use]
410    pub fn intersects_xz(
411        self,
412        min_x: T::Scalar,
413        min_z: T::Scalar,
414        max_x: T::Scalar,
415        max_z: T::Scalar,
416    ) -> bool {
417        Self::axis_overlaps(self.min.x(), self.max.x(), min_x, max_x)
418            && Self::axis_overlaps(self.min.z(), self.max.z(), min_z, max_z)
419    }
420
421    /// Returns whether the given coordinates are inside this bounding box.
422    #[must_use]
423    pub fn contains(self, pos: T) -> bool {
424        self.contains_xyz(pos.x(), pos.y(), pos.z())
425    }
426
427    /// Returns whether the given coordinates are inside this bounding box.
428    #[must_use]
429    pub fn contains_xyz(self, x: T::Scalar, y: T::Scalar, z: T::Scalar) -> bool {
430        Self::axis_contains(self.min.x(), self.max.x(), x)
431            && Self::axis_contains(self.min.y(), self.max.y(), y)
432            && Self::axis_contains(self.min.z(), self.max.z(), z)
433    }
434}
435
436impl<T: AabbVector, I: Space> Aabb<T, I> {
437    #[inline]
438    fn span(raw: T::Scalar) -> T::Scalar {
439        if I::ZERO_SPAN_IS_EMPTY {
440            raw
441        } else {
442            raw + T::Scalar::from(1u8)
443        }
444    }
445
446    /// Get the width of bounding box (X Span)
447    #[must_use]
448    pub fn width(&self) -> T::Scalar {
449        Self::span(self.max.x() - self.min.x())
450    }
451
452    /// Get the height of bounding box (Y Span)
453    #[must_use]
454    pub fn height(&self) -> T::Scalar {
455        Self::span(self.max.y() - self.min.y())
456    }
457
458    /// Get the depth of bounding box (Z Span)
459    #[must_use]
460    pub fn depth(&self) -> T::Scalar {
461        Self::span(self.max.z() - self.min.z())
462    }
463
464    /// Returns the center point of the bounding box.
465    #[must_use]
466    pub fn center(&self) -> T {
467        let two = T::Scalar::from(2u8);
468        T::new(
469            self.min.x() + Self::span(self.max.x() - self.min.x()) / two,
470            self.min.y() + Self::span(self.max.y() - self.min.y()) / two,
471            self.min.z() + Self::span(self.max.z() - self.min.z()) / two,
472        )
473    }
474}
475
476impl<I: Space> Aabb<IVec3, I> {
477    /// Returns whether the given coordinates are inside this bounding box.
478    #[must_use]
479    pub fn contains_blockpos(self, pos: BlockPos) -> bool {
480        self.contains(pos.0)
481    }
482}
483
484impl<I: Space> Aabb<DVec3, I> {
485    /// A full block from `(0, 0, 0)` to `(1, 1, 1)`.
486    pub const FULL_BLOCK: Self = Self::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
487
488    /// A zero-volume box.
489    pub const EMPTY: Self = Self::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
490
491    /// Creates an AABB and normalizes endpoint order like vanilla `AABB`.
492    #[must_use]
493    pub const fn new(
494        min_x: f64,
495        min_y: f64,
496        min_z: f64,
497        max_x: f64,
498        max_y: f64,
499        max_z: f64,
500    ) -> Self {
501        let (min_x, max_x) = ordered_pair(min_x, max_x);
502        let (min_y, max_y) = ordered_pair(min_y, max_y);
503        let (min_z, max_z) = ordered_pair(min_z, max_z);
504        Self {
505            min: DVec3::new(min_x, min_y, min_z),
506            max: DVec3::new(max_x, max_y, max_z),
507            p: PhantomData,
508        }
509    }
510
511    /// Creates a box centered at `center` with the supplied side lengths.
512    ///
513    /// Vanilla equivalent: `AABB.ofSize(center, sizeX, sizeY, sizeZ)`.
514    #[must_use]
515    pub fn of_size(center: DVec3, size_x: f64, size_y: f64, size_z: f64) -> Self {
516        let half_x = size_x / 2.0;
517        let half_y = size_y / 2.0;
518        let half_z = size_z / 2.0;
519        Self::new(
520            center.x - half_x,
521            center.y - half_y,
522            center.z - half_z,
523            center.x + half_x,
524            center.y + half_y,
525            center.z + half_z,
526        )
527    }
528
529    /// Vanilla equivalent: `AABB.getSize()`.
530    #[must_use]
531    pub fn size(self) -> f64 {
532        (self.width() + self.height() + self.depth()) / 3.0
533    }
534}
535
536impl Aabb<DVec3, BlockLocal> {
537    /// Converts this block-local box to a world-space box at `pos`.
538    #[must_use]
539    pub fn at_block(self, pos: BlockPos) -> Aabb<DVec3, World> {
540        let offset = DVec3::new(f64::from(pos.x()), f64::from(pos.y()), f64::from(pos.z()));
541        Aabb {
542            min: self.min + offset,
543            max: self.max + offset,
544            p: PhantomData,
545        }
546    }
547}
548
549impl Aabb<DVec3, World> {
550    /// Creates an entity bounding box centered on X/Z and using `y` as feet.
551    #[must_use]
552    pub fn entity_box(x: f64, y: f64, z: f64, half_width: f64, height: f64) -> Self {
553        Self::new(
554            x - half_width,
555            y,
556            z - half_width,
557            x + half_width,
558            y + height,
559            z + half_width,
560        )
561    }
562
563    /// Expands the box only in the direction of `delta`.
564    #[must_use]
565    pub fn expand_towards(self, delta: DVec3) -> Self {
566        Self {
567            min: self.min + delta.min(DVec3::ZERO),
568            max: self.max + delta.max(DVec3::ZERO),
569            p: PhantomData,
570        }
571    }
572
573    /// Returns `true` if this box intersects the full block at `pos`.
574    #[must_use]
575    pub fn intersects_block(self, pos: BlockPos) -> bool {
576        let min = DVec3::new(f64::from(pos.x()), f64::from(pos.y()), f64::from(pos.z()));
577        let max = min + DVec3::ONE;
578        self.intersects_bounds(min, max)
579    }
580}
581
582impl Aabb<IVec3, Structure> {
583    /// Creates a new bounding box, normalizing so min <= max on each axis.
584    #[must_use]
585    pub const fn new(pos1: IVec3, pos2: IVec3) -> Self {
586        let (min_x, max_x) = ordered_pair_i32(pos1.x, pos2.x);
587        let (min_y, max_y) = ordered_pair_i32(pos1.y, pos2.y);
588        let (min_z, max_z) = ordered_pair_i32(pos1.z, pos2.z);
589        Self {
590            min: IVec3::new(min_x, min_y, min_z),
591            max: IVec3::new(max_x, max_y, max_z),
592            p: PhantomData,
593        }
594    }
595
596    /// Creates a bounding box from two corner block positions.
597    #[must_use]
598    pub const fn from_corners(a: BlockPos, b: BlockPos) -> Self {
599        Self::new(a.0, b.0)
600    }
601
602    /// Returns the squared distance from `point` to this box.
603    ///
604    /// Mirrors vanilla `AABB.distanceToSqr`.
605    #[must_use]
606    pub fn distance_to_sqr(self, point: DVec3) -> f64 {
607        let min_x = f64::from(self.min_x());
608        let min_y = f64::from(self.min_y());
609        let min_z = f64::from(self.min_z());
610        let max_x = f64::from(self.max_x());
611        let max_y = f64::from(self.max_y());
612        let max_z = f64::from(self.max_z());
613
614        let dx = f64::max(f64::max(min_x - point.x, point.x - max_x), 0.0);
615        let dy = f64::max(f64::max(min_y - point.y, point.y - max_y), 0.0);
616        let dz = f64::max(f64::max(min_z - point.z, point.z - max_z), 0.0);
617        dx * dx + dy * dy + dz * dz
618    }
619}
620
621#[cfg(test)]
622#[expect(
623    clippy::float_cmp,
624    reason = "geometry constructors use exact test values"
625)]
626mod tests {
627    use super::*;
628
629    #[test]
630    fn constructors_normalize_endpoints_like_vanilla() {
631        let aabb = WorldAabb::new(3.0, 4.0, 5.0, 1.0, 2.0, 0.0);
632        assert_eq!(aabb.min_x(), 1.0);
633        assert_eq!(aabb.min_y(), 2.0);
634        assert_eq!(aabb.min_z(), 0.0);
635        assert_eq!(aabb.max_x(), 3.0);
636        assert_eq!(aabb.max_y(), 4.0);
637        assert_eq!(aabb.max_z(), 5.0);
638    }
639
640    #[test]
641    fn inflate_and_deflate_normalize_inverted_bounds() {
642        let aabb = WorldAabb::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0).deflate(0.75);
643        assert_eq!(aabb.min_corner(), DVec3::splat(0.25));
644        assert_eq!(aabb.max_corner(), DVec3::splat(0.75));
645
646        let bbox = BoundingBox::new(IVec3::ZERO, IVec3::splat(5)).inflate(-4);
647        assert_eq!(bbox.min_corner(), IVec3::splat(1));
648        assert_eq!(bbox.max_corner(), IVec3::splat(4));
649    }
650
651    #[test]
652    fn of_size_builds_vanilla_centered_box() {
653        let aabb = WorldAabb::of_size(DVec3::new(10.0, 64.0, -2.0), 2.0, 4.0, 6.0);
654
655        assert_eq!(aabb.min_corner(), DVec3::new(9.0, 62.0, -5.0));
656        assert_eq!(aabb.max_corner(), DVec3::new(11.0, 66.0, 1.0));
657    }
658
659    #[test]
660    fn block_local_aabb_translates_to_world_space() {
661        let local = BlockLocalAabb::new(0.0, 0.25, 0.0, 1.0, 0.75, 1.0);
662        let world = local.at_block(BlockPos::new(10, 64, -5));
663
664        assert_eq!(world.min_x(), 10.0);
665        assert_eq!(world.min_y(), 64.25);
666        assert_eq!(world.min_z(), -5.0);
667        assert_eq!(world.max_x(), 11.0);
668        assert_eq!(world.max_y(), 64.75);
669        assert_eq!(world.max_z(), -4.0);
670    }
671
672    #[test]
673    fn contains_uses_vanilla_exclusive_max_edge() {
674        let aabb = WorldAabb::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
675
676        assert!(aabb.contains_xyz(0.0, 0.5, 0.5));
677        assert!(aabb.contains_xyz(0.999, 0.5, 0.5));
678        assert!(!aabb.contains_xyz(1.0, 0.5, 0.5));
679    }
680
681    #[test]
682    fn world_aabb_distance_to_sqr_uses_nearest_surface_point() {
683        let aabb = WorldAabb::new(1.0, 2.0, 3.0, 4.0, 6.0, 8.0);
684
685        assert_eq!(aabb.distance_to_sqr(DVec3::new(2.0, 3.0, 4.0)), 0.0);
686        assert_eq!(aabb.distance_to_sqr(DVec3::new(0.0, 1.0, 1.0)), 6.0);
687        assert_eq!(aabb.distance_to_sqr(DVec3::new(5.0, 7.0, 9.0)), 3.0);
688    }
689
690    #[test]
691    fn closest_point_to_clamps_to_box_bounds() {
692        let aabb = WorldAabb::new(1.0, 2.0, 3.0, 4.0, 6.0, 8.0);
693
694        assert_eq!(
695            aabb.closest_point_to(DVec3::new(0.0, 4.0, 10.0)),
696            DVec3::new(1.0, 4.0, 8.0)
697        );
698        assert_eq!(
699            aabb.closest_point_to(DVec3::new(2.0, 3.0, 4.0)),
700            DVec3::new(2.0, 3.0, 4.0)
701        );
702    }
703
704    #[test]
705    fn expand_towards_covers_start_and_end() {
706        let aabb = WorldAabb::new(1.0, 1.0, 1.0, 2.0, 2.0, 2.0);
707        let swept = aabb.expand_towards(DVec3::new(-0.5, 1.5, 0.0));
708
709        assert_eq!(swept.min_x(), 0.5);
710        assert_eq!(swept.min_y(), 1.0);
711        assert_eq!(swept.min_z(), 1.0);
712        assert_eq!(swept.max_x(), 2.0);
713        assert_eq!(swept.max_y(), 3.5);
714        assert_eq!(swept.max_z(), 2.0);
715    }
716}