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 crate::{Mat4, RenderResolution, Vec3};
5
6/// Inner struct for bounds.
7#[derive(Debug, Clone)]
8struct BoundsInner {
9    /// Minimum corner.
10    min: Vec3,
11    /// Maximum corner.
12    max: Vec3,
13}
14
15/// Corners iterator struct.
16pub struct Bounds3DCorners {
17    bounds: BoundsInner,
18    index: u8, // Only goes from 0 to 7
19}
20
21impl Iterator for Bounds3DCorners {
22    type Item = Vec3;
23
24    fn next(&mut self) -> Option<Self::Item> {
25        if self.index >= 8 {
26            return None;
27        }
28
29        let i = self.index;
30        self.index += 1;
31
32        let x = if i & 1 == 0 {
33            self.bounds.min.x
34        } else {
35            self.bounds.max.x
36        };
37        let y = if i & 2 == 0 {
38            self.bounds.min.y
39        } else {
40            self.bounds.max.y
41        };
42        let z = if i & 4 == 0 {
43            self.bounds.min.z
44        } else {
45            self.bounds.max.z
46        };
47
48        Some(Vec3 { x, y, z })
49    }
50}
51
52/// A 3D bounds is a 3D bounding box with a minimum and maximum corner.
53#[derive(Debug, Clone, Default)]
54pub struct Bounds3D(Option<BoundsInner>);
55
56impl Bounds3D {
57    /// Create new 3D bounds.
58    pub fn new(min: Vec3, max: Vec3) -> Self {
59        Self(Some(BoundsInner { min, max }))
60    }
61
62    /// Minimum corner.
63    pub fn min(&self) -> Option<Vec3> {
64        self.0.as_ref().map(|s| s.min)
65    }
66
67    /// Maximum corner.
68    pub fn max(&self) -> Option<Vec3> {
69        self.0.as_ref().map(|s| s.max)
70    }
71
72    /// Minimum and maximum corner.
73    pub fn min_max(&self) -> Option<(Vec3, Vec3)> {
74        self.0.as_ref().map(|s| (s.min, s.max))
75    }
76
77    /// Calculate extended bounds.
78    pub fn extend(self, other: Bounds3D) -> Self {
79        match (self.0, other.0) {
80            (None, None) => Self(None),
81            (None, Some(b)) | (Some(b), None) => Self(Some(b)),
82            (Some(b1), Some(b2)) => Self::new(
83                Vec3::new(
84                    b1.min.x.min(b2.min.x),
85                    b1.min.y.min(b2.min.y),
86                    b1.min.z.min(b2.min.z),
87                ),
88                Vec3::new(
89                    b1.max.x.max(b2.max.x),
90                    b1.max.y.max(b2.max.y),
91                    b1.max.z.max(b2.max.z),
92                ),
93            ),
94        }
95    }
96
97    /// Extend these bounds by point.
98    pub fn extend_by_point(&mut self, p: Vec3) {
99        match &mut self.0 {
100            Some(bounds) => {
101                *bounds = BoundsInner {
102                    min: Vec3::new(
103                        bounds.min.x.min(p.x),
104                        bounds.min.y.min(p.y),
105                        bounds.min.z.min(p.z),
106                    ),
107                    max: Vec3::new(
108                        bounds.max.x.max(p.x),
109                        bounds.max.y.max(p.y),
110                        bounds.max.z.max(p.z),
111                    ),
112                }
113            }
114            None => *self = Self::new(p, p),
115        }
116    }
117
118    /// Corner iterator.
119    pub fn corners(&self) -> Bounds3DCorners {
120        Bounds3DCorners {
121            bounds: self.0.clone().expect("Bounds"),
122            index: 0,
123        }
124    }
125}
126
127impl FromIterator<Vec3> for Bounds3D {
128    fn from_iter<I: IntoIterator<Item = Vec3>>(iter: I) -> Self {
129        let mut iter = iter.into_iter();
130        let first_point = match iter.next() {
131            Some(point) => point,
132            None => return Bounds3D(None),
133        };
134
135        let mut min = first_point;
136        let mut max = first_point;
137
138        iter.for_each(|p| {
139            min.x = min.x.min(p.x);
140            min.y = min.y.min(p.y);
141            min.z = min.z.min(p.z);
142
143            max.x = max.x.max(p.x);
144            max.y = max.y.max(p.y);
145            max.z = max.z.max(p.z);
146        });
147
148        Bounds3D::new(min, max)
149    }
150}
151
152impl Transformed3D for Bounds3D {
153    fn transformed_3d(&self, _: &crate::RenderResolution, mat: &crate::Mat4) -> Self {
154        let mut bounds = Bounds3D::default();
155        self.corners()
156            .for_each(|corner| bounds.extend_by_point((mat * corner.extend(1.0)).truncate()));
157
158        bounds
159    }
160}
161
162/// Trait to return a bounding box of 3D geometry.
163pub trait FetchBounds3D {
164    /// Fetch bounds.
165    fn fetch_bounds_3d(&self) -> Bounds3D;
166}
167
168/// Transformed version of a 3D geometry.
169pub trait Transformed3D<T = Self> {
170    /// Transform from matrix.
171    fn transformed_3d(&self, render_resolution: &RenderResolution, mat: &Mat4) -> T;
172}