las/
bounds.rs

1use crate::{transform::RoundingMode, Point, Result, Transform, Vector};
2use std::f64;
3
4/// Minimum and maximum bounds in three dimensions.
5#[derive(Clone, Copy, Debug, PartialEq)]
6pub struct Bounds {
7    /// The minimum values.
8    pub min: Vector<f64>,
9
10    /// The maximum values.
11    pub max: Vector<f64>,
12}
13
14impl Bounds {
15    /// Grows the bounds to encompass this point in xyz space.
16    ///
17    /// # Examples
18    ///
19    /// ```
20    /// # use las::{Bounds, Point};
21    /// let point = Point { x: 1., y: 2., z: 3., ..Default::default() };
22    /// let mut bounds = Bounds { ..Default::default() };
23    /// bounds.grow(&point);
24    /// assert_eq!(1., bounds.min.x);
25    /// ```
26    pub fn grow(&mut self, point: &Point) {
27        if point.x < self.min.x {
28            self.min.x = point.x;
29        }
30        if point.y < self.min.y {
31            self.min.y = point.y;
32        }
33        if point.z < self.min.z {
34            self.min.z = point.z;
35        }
36        if point.x > self.max.x {
37            self.max.x = point.x;
38        }
39        if point.y > self.max.y {
40            self.max.y = point.y;
41        }
42        if point.z > self.max.z {
43            self.max.z = point.z;
44        }
45    }
46
47    /// Transform the bounds to be compatible with the chosen transform. Otherwise, points may lay outside of the bounding box due to floating-point issues.
48    ///
49    /// # Example
50    ///
51    /// ```
52    /// use las::{Bounds, Transform, Vector};
53    ///
54    /// let bounds = Bounds {
55    ///     min: Vector {
56    ///         x: -2.7868618965148926,
57    ///         y: -0.9322229027748108,
58    ///         z: -5.8063459396362305,
59    ///     },
60    ///     max: Vector {
61    ///         x: 0.6091402173042297,
62    ///         y: 1.5428568124771118,
63    ///         z: -0.09441471844911575,
64    ///     },
65    /// };
66    ///
67    /// // Currently, the default scale is 0.001.
68    /// let new_bounds = bounds.adapt(&Default::default()).unwrap();
69    /// assert_eq!(new_bounds.max.z, -0.094);
70    /// ```
71    pub fn adapt(&self, transform: &Vector<Transform>) -> Result<Self> {
72        use RoundingMode::*;
73
74        fn inner_convert(value: f64, transform: &Transform, rounding: RoundingMode) -> Result<f64> {
75            // During saving, an instance with +-inf is saved. We must consider for this corner case.
76            if value.is_infinite() {
77                return Ok(value);
78            }
79            Ok(transform.direct(transform.inverse_with_rounding_mode(value, rounding)?))
80        }
81
82        Ok(Self {
83            min: Vector {
84                x: inner_convert(self.min.x, &transform.x, Floor)?,
85                y: inner_convert(self.min.y, &transform.y, Floor)?,
86                z: inner_convert(self.min.z, &transform.z, Floor)?,
87            },
88            max: Vector {
89                x: inner_convert(self.max.x, &transform.x, Ceil)?,
90                y: inner_convert(self.max.y, &transform.y, Ceil)?,
91                z: inner_convert(self.max.z, &transform.z, Ceil)?,
92            },
93        })
94    }
95}
96
97impl Default for Bounds {
98    fn default() -> Bounds {
99        Bounds {
100            min: Vector {
101                x: f64::INFINITY,
102                y: f64::INFINITY,
103                z: f64::INFINITY,
104            },
105            max: Vector {
106                x: f64::NEG_INFINITY,
107                y: f64::NEG_INFINITY,
108                z: f64::NEG_INFINITY,
109            },
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116
117    use super::*;
118    use crate::Point;
119
120    #[test]
121    fn grow() {
122        let mut bounds = Bounds {
123            ..Default::default()
124        };
125        bounds.grow(&Point {
126            x: 1.,
127            y: 2.,
128            z: 3.,
129            ..Default::default()
130        });
131        assert_eq!(1., bounds.min.x);
132        assert_eq!(1., bounds.max.x);
133        assert_eq!(2., bounds.min.y);
134        assert_eq!(2., bounds.max.y);
135        assert_eq!(3., bounds.min.z);
136        assert_eq!(3., bounds.max.z);
137        bounds.grow(&Point {
138            x: 0.,
139            y: 1.,
140            z: 2.,
141            ..Default::default()
142        });
143        assert_eq!(0., bounds.min.x);
144        assert_eq!(1., bounds.max.x);
145        assert_eq!(1., bounds.min.y);
146        assert_eq!(2., bounds.max.y);
147        assert_eq!(2., bounds.min.z);
148        assert_eq!(3., bounds.max.z);
149        bounds.grow(&Point {
150            x: 2.,
151            y: 3.,
152            z: 4.,
153            ..Default::default()
154        });
155        assert_eq!(0., bounds.min.x);
156        assert_eq!(2., bounds.max.x);
157        assert_eq!(1., bounds.min.y);
158        assert_eq!(3., bounds.max.y);
159        assert_eq!(2., bounds.min.z);
160        assert_eq!(4., bounds.max.z);
161    }
162
163    const EPSILON: f64 = 0.00000001;
164
165    #[test]
166    fn bounds_adapt_neg_tick_above() {
167        assert_bounds_adapt_to_value(-1.0 + EPSILON);
168    }
169
170    #[test]
171    fn bounds_adapt_pos_tick_above() {
172        assert_bounds_adapt_to_value(1.0 + EPSILON);
173    }
174
175    #[test]
176    fn bounds_adapt_neg_tick_below() {
177        assert_bounds_adapt_to_value(-1.0 - EPSILON);
178    }
179
180    #[test]
181    fn bounds_adapt_pos_tick_below() {
182        assert_bounds_adapt_to_value(1.0 - EPSILON);
183    }
184
185    fn assert_bounds_adapt_to_value(n: f64) {
186        let mut bounds = Bounds {
187            ..Default::default()
188        };
189
190        let point = Point {
191            x: n,
192            y: n,
193            z: n,
194            ..Default::default()
195        };
196
197        bounds.grow(&point);
198
199        let rounded_box = bounds.adapt(&Default::default()).unwrap();
200
201        assert_point_inside_bounds(&point, &rounded_box);
202    }
203
204    fn assert_point_inside_bounds(p: &Point, b: &Bounds) {
205        let x_inside = b.max.x >= p.x && b.min.x <= p.x;
206        let y_inside = b.max.y >= p.y && b.min.y <= p.y;
207        let z_inside = b.max.z >= p.z && b.min.z <= p.z;
208
209        let inside = x_inside && y_inside && z_inside;
210
211        if !inside {
212            let bounds = b;
213            let point = Vector::<f64> {
214                x: p.x,
215                y: p.y,
216                z: p.z,
217            };
218
219            panic!(
220                "Point was outside the calculated bounds:\n{:#?}\n\n",
221                (point, bounds)
222            );
223        }
224    }
225}