microcad_core/geo3d/
bounds.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use cgmath::ElementWise;
5use derive_more::Deref;
6
7use crate::*;
8
9/// Bounds3D type alias.
10pub type Bounds3D = Bounds<Vec3>;
11
12/// Corners iterator struct.
13pub struct Bounds3DCorners {
14    bounds: Bounds3D,
15    index: u8, // Only goes from 0 to 7
16}
17
18impl Iterator for Bounds3DCorners {
19    type Item = Vec3;
20
21    fn next(&mut self) -> Option<Self::Item> {
22        if self.index >= 8 {
23            return None;
24        }
25
26        let i = self.index;
27        self.index += 1;
28
29        let x = if i & 1 == 0 {
30            self.bounds.min.x
31        } else {
32            self.bounds.max.x
33        };
34        let y = if i & 2 == 0 {
35            self.bounds.min.y
36        } else {
37            self.bounds.max.y
38        };
39        let z = if i & 4 == 0 {
40            self.bounds.min.z
41        } else {
42            self.bounds.max.z
43        };
44
45        Some(Vec3 { x, y, z })
46    }
47}
48
49impl Bounds3D {
50    /// Minimum and maximum corner.
51    pub fn min_max(&self) -> (Vec3, Vec3) {
52        (self.min, self.max)
53    }
54
55    /// Calculate extended bounds.
56    pub fn extend(self, other: Bounds3D) -> Self {
57        match (self.is_valid(), other.is_valid()) {
58            (false, false) => Self::default(),
59            (false, true) => other,
60            (true, false) => self,
61            (true, true) => Self::new(
62                Vec3::new(
63                    self.min.x.min(other.min.x),
64                    self.min.y.min(other.min.y),
65                    self.min.z.min(other.min.z),
66                ),
67                Vec3::new(
68                    self.max.x.max(other.max.x),
69                    self.max.y.max(other.max.y),
70                    self.max.z.max(other.max.z),
71                ),
72            ),
73        }
74    }
75
76    /// Check if bounds are valid
77    pub fn is_valid(&self) -> bool {
78        self.min.x <= self.max.x && self.min.y <= self.max.y && self.min.z <= self.max.z
79    }
80
81    /// Extend these bounds by point.
82    pub fn extend_by_point(&mut self, p: Vec3) {
83        self.min.x = p.x.min(self.min.x);
84        self.min.y = p.y.min(self.min.y);
85        self.min.z = p.z.min(self.min.z);
86        self.max.x = p.x.max(self.max.x);
87        self.max.y = p.y.max(self.max.y);
88        self.max.z = p.z.max(self.max.z);
89    }
90
91    /// Corner iterator.
92    pub fn corners(&self) -> Bounds3DCorners {
93        Bounds3DCorners {
94            bounds: self.clone(),
95            index: 0,
96        }
97    }
98
99    /// Maps a vec3 to bounds.
100    ///
101    /// The resulting `Vec3` is normalized between (0,0,0) = min  and (1,1,1) = max.
102    pub fn map_vec3(&self, v: Vec3) -> Vec3 {
103        (v - self.min).div_element_wise(self.max - self.min)
104    }
105}
106
107impl Default for Bounds3D {
108    fn default() -> Self {
109        // Bounds are invalid by default.
110        let min = Scalar::MAX;
111        let max = Scalar::MIN;
112        Self::new(Vec3::new(min, min, min), Vec3::new(max, max, max))
113    }
114}
115
116impl FromIterator<Vec3> for Bounds3D {
117    fn from_iter<I: IntoIterator<Item = Vec3>>(iter: I) -> Self {
118        let mut iter = iter.into_iter();
119        let first_point = match iter.next() {
120            Some(point) => point,
121            None => return Bounds3D::default(),
122        };
123
124        let mut min = first_point;
125        let mut max = first_point;
126
127        iter.for_each(|p| {
128            min.x = min.x.min(p.x);
129            min.y = min.y.min(p.y);
130            min.z = min.z.min(p.z);
131
132            max.x = max.x.max(p.x);
133            max.y = max.y.max(p.y);
134            max.z = max.z.max(p.z);
135        });
136
137        Bounds3D::new(min, max)
138    }
139}
140
141impl Transformed3D for Bounds3D {
142    fn transformed_3d(&self, mat: &Mat4) -> Self {
143        let mut bounds = Bounds3D::default();
144        self.corners()
145            .for_each(|corner| bounds.extend_by_point((mat * corner.extend(1.0)).truncate()));
146
147        bounds
148    }
149}
150
151/// Trait to calculate a bounding box of 3D geometry.
152pub trait CalcBounds3D {
153    /// Fetch bounds.
154    fn calc_bounds_3d(&self) -> Bounds3D;
155}
156
157/// Transformed version of a 3D geometry.
158pub trait Transformed3D<T = Self> {
159    /// Transform from matrix.
160    fn transformed_3d(&self, mat: &Mat4) -> T;
161}
162
163/// Holds bounds for a 3D object.
164#[derive(Clone, Default, Debug, Deref)]
165pub struct WithBounds3D<T: CalcBounds3D + Transformed3D> {
166    /// Bounds.
167    pub bounds: Bounds3D,
168    /// The inner object.
169    #[deref]
170    pub inner: T,
171}
172
173impl<T: CalcBounds3D + Transformed3D> WithBounds3D<T> {
174    /// Create a new object with bounds.
175    pub fn new(inner: T, bounds: Bounds3D) -> Self {
176        Self { bounds, inner }
177    }
178
179    /// Update the bounds.
180    pub fn update_bounds(&mut self) {
181        self.bounds = self.inner.calc_bounds_3d()
182    }
183}
184
185impl<T: CalcBounds3D + Transformed3D> Transformed3D for WithBounds3D<T> {
186    fn transformed_3d(&self, mat: &Mat4) -> Self {
187        let inner = self.inner.transformed_3d(mat);
188        let bounds = inner.calc_bounds_3d();
189        Self { inner, bounds }
190    }
191}
192
193impl From<Geometry3D> for WithBounds3D<Geometry3D> {
194    fn from(geo: Geometry3D) -> Self {
195        let bounds = geo.calc_bounds_3d();
196        Self::new(geo, bounds)
197    }
198}