1use crate::{transform::RoundingMode, Point, Result, Transform, Vector};
2use std::f64;
3
4#[derive(Clone, Copy, Debug, PartialEq)]
6pub struct Bounds {
7 pub min: Vector<f64>,
9
10 pub max: Vector<f64>,
12}
13
14impl Bounds {
15 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 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 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}