svg_nd/
bbox.rs

1/*a Copyright
2
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7  http://www.apache.org/licenses/LICENSE-2.0
8
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations under the License.
14
15@file    bbox.rs
16@brief   Part of SVG library
17 */
18//a Imports
19use geo_nd::vector;
20
21use crate::{Point, Range, Transform};
22
23//a BBox
24//tp BBox
25#[derive(Debug, Clone, Copy, Default, PartialEq)]
26/// [BBox] describes a region bounded by (x0,y0) and (x1,y1) It
27/// requires x0 <= x1 and y0 <= y1, and if either are equal then the
28/// region is deemed to be *none*
29pub struct BBox {
30    /// X range
31    pub x: Range,
32    /// Y range
33    pub y: Range,
34}
35
36//ti Display for BBox
37impl std::fmt::Display for BBox {
38    //mp fmt - format for a human
39    /// Display the BBox
40    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
41        write!(
42            f,
43            "[({},{}):({},{})]",
44            self.x[0], self.y[0], self.x[1], self.y[1]
45        )
46    }
47
48    //zz All done
49}
50
51//ti BBox
52impl BBox {
53    //mp none
54    /// Create a none bbox - where both ranges are none
55    pub fn none() -> Self {
56        Self {
57            x: Range::none(),
58            y: Range::none(),
59        }
60    }
61
62    //mp is_none
63    /// Return `true` if the rectangle describes a 'none' region
64    pub fn is_none(&self) -> bool {
65        self.x.is_none() || self.y.is_none()
66    }
67
68    //fp new
69    /// Make a rectangle using the coordinates supplied, ensuring that
70    /// the rectangle is correctly defined
71    pub fn new(x0: f64, y0: f64, x1: f64, y1: f64) -> Self {
72        let x = if x0 < x1 {
73            Range::new(x0, x1)
74        } else {
75            Range::new(x1, x0)
76        };
77        let y = if y0 < y1 {
78            Range::new(y0, y1)
79        } else {
80            Range::new(y1, y0)
81        };
82        Self { x, y }
83    }
84
85    //cp of_ranges
86    /// Create a [BBox] from two pRange]
87    pub fn of_ranges(x: Range, y: Range) -> Self {
88        Self { x, y }
89    }
90
91    //cp of_points
92    /// Make a new rectangle that is the bounding box of a vec of points
93    pub fn of_points(pts: &[Point]) -> Self {
94        let mut s = Self::none();
95        for p in pts.iter() {
96            s.x = s.x.include(p[0]);
97            s.y = s.y.include(p[1]);
98        }
99        s
100    }
101
102    //fp of_cwh
103    /// Generate a rectangle from a centre `Point` and a width/height.
104    pub fn of_cwh(centre: Point, width: f64, height: f64) -> Self {
105        Self::new(
106            centre[0] - width / 2.,
107            centre[1] - height / 2.,
108            centre[0] + width / 2.,
109            centre[1] + height / 2.,
110        )
111    }
112
113    //mp pt_within
114    /// Consume a point, and return a new point that whose X
115    /// coordinate indicate the fraction of this rectangles' width the
116    /// original point is along its width, and similarly for the Y
117    /// coordinate
118    ///
119    pub fn pt_within(&self, pt: Point) -> Point {
120        if self.is_none() {
121            pt
122        } else {
123            let (rx, ry) = (pt[0] - self.x[0], pt[1] - self.y[0]);
124            [rx / self.x.size(), ry / self.y.size()].into()
125        }
126    }
127
128    //mp add_as_points
129    /// Create a vector of four points that are the
130    /// anticlockwise-ordered corners of the rectangle starting at the
131    /// minumum (x,y)
132    pub fn add_as_points(&self, close: bool, mut v: Vec<Point>) -> Vec<Point> {
133        v.push([self.x[0], self.y[0]].into());
134        v.push([self.x[1], self.y[0]].into());
135        v.push([self.x[1], self.y[1]].into());
136        v.push([self.x[0], self.y[1]].into());
137        if close {
138            v.push([self.x[0], self.y[0]].into());
139        }
140        v
141    }
142
143    //mp get_wh
144    /// Return a point consisting of the width and height of the rectangle
145    pub fn get_wh(&self) -> (f64, f64) {
146        (self.x.size(), self.y.size())
147    }
148
149    //mp center
150    /// Return a point indicating the centre of the rectangle
151    pub fn center(&self) -> Point {
152        [self.x.center(), self.y.center()].into()
153    }
154
155    //mp width
156    /// Return the width of the rectangle (`x1` - `x0`)
157    pub fn width(&self) -> f64 {
158        self.x.size()
159    }
160
161    //mp height
162    /// Return the height of the rectangle (`y1` - `y0`)
163    ///
164    pub fn height(&self) -> f64 {
165        self.y.size()
166    }
167
168    //mp get_cwh
169    /// Get the centre, width and height of the rectangle
170    pub fn get_cwh(&self) -> (Point, f64, f64) {
171        (self.center(), self.width(), self.height())
172    }
173
174    //mp get_bounds
175    /// Get the bounds
176    pub fn get_bounds(&self) -> (f64, f64, f64, f64) {
177        (self.x[0], self.y[0], self.width(), self.height())
178    }
179
180    //cp enlarge
181    /// Consume the rectangle and return a new rectangle enlarge by a
182    /// fixed value
183    ///
184    #[must_use]
185    pub fn enlarge(mut self, value: f64) -> Self {
186        self.x = self.x.enlarge(value);
187        self.y = self.y.enlarge(value);
188        self
189    }
190
191    //cp reduce
192    /// Shrink the rectangle, keeping the same center, by a fixed value
193    ///
194    #[must_use]
195    pub fn reduce(mut self, value: f64) -> Self {
196        self.x = self.x.reduce(value);
197        self.y = self.y.reduce(value);
198        self
199    }
200
201    //cp expand
202    /// exand in-place by expansion scaled by 'scale'
203    ///
204    /// Was Float4 x0, x1, y0,, x1 now [x0, y0, x1, y1]
205    #[must_use]
206    pub fn expand(mut self, other: &[f64; 4], scale: f64) -> Self {
207        self.x = Range::new(self.x[0] - scale * other[0], self.x[1] + scale * other[2]);
208        self.y = Range::new(self.y[0] - scale * other[1], self.y[1] + scale * other[3]);
209        self
210    }
211
212    //cp shrink
213    /// shrink in-place by expansion scaled by 'scale'
214    #[must_use]
215    #[inline]
216    pub fn shrink(self, other: &[f64; 4], scale: f64) -> Self {
217        self.expand(other, -scale)
218    }
219
220    //cp include
221    /// Include a point into the BBox, exanding min or max if required
222    #[must_use]
223    #[inline]
224    pub fn include(mut self, p: Point) -> Self {
225        self.x = self.x.include(p[0]);
226        self.y = self.y.include(p[1]);
227        self
228    }
229
230    //cp union
231    /// union this with another; if either is_zero then just the other
232    #[must_use]
233    #[inline]
234    pub fn union(mut self, other: Self) -> Self {
235        if other.is_none() {
236            self
237        } else if self.is_none() {
238            other
239        } else {
240            self.x = self.x.union(&other.x);
241            self.y = self.y.union(&other.y);
242            self
243        }
244    }
245
246    //cp intersect
247    /// intersect this with another; if either is_zero then this will be zero
248    #[must_use]
249    #[inline]
250    pub fn intersect(mut self, other: Self) -> Self {
251        if other.is_none() {
252            other
253        } else if self.is_none() {
254            self
255        } else {
256            self.x = self.x.intersect(&other.x);
257            self.y = self.y.intersect(&other.y);
258            self
259        }
260    }
261
262    //cp new_rotated_around
263    /// Rotate the rectangle around a point by an angle,
264    /// generating a new rectangle that is the bounding box of that rotated rectangle
265    #[must_use]
266    pub fn new_rotated_around(&self, pt: &Point, degrees: f64) -> Self {
267        let radians = degrees.to_radians();
268        let p0 = vector::rotate_around([self.x[0], self.y[0]], pt.as_ref(), radians, 0, 1);
269        let p1 = vector::rotate_around([self.x[1], self.y[0]], pt.as_ref(), radians, 0, 1);
270        let p2 = vector::rotate_around([self.x[0], self.y[1]], pt.as_ref(), radians, 0, 1);
271        let p3 = vector::rotate_around([self.x[1], self.y[1]], pt.as_ref(), radians, 0, 1);
272        let mut x = Range::none();
273        let mut y = Range::none();
274        x = x
275            .include(p0[0])
276            .include(p1[0])
277            .include(p2[0])
278            .include(p3[0]);
279        y = y
280            .include(p0[1])
281            .include(p1[1])
282            .include(p2[1])
283            .include(p3[1]);
284        Self { x, y }
285    }
286
287    //mp transform
288    #[must_use]
289    #[inline]
290    pub fn transform(mut self, transform: &Transform) -> Self {
291        let corners: [Point; 4] = [
292            [self.x[0], self.y[0]].into(),
293            [self.x[1], self.y[0]].into(),
294            [self.x[0], self.y[1]].into(),
295            [self.x[1], self.y[1]].into(),
296        ];
297        self = Self::none();
298        for c in corners {
299            self = self.include(transform.apply(c));
300        }
301        self
302    }
303
304    //zz All done
305}
306
307//ip std::ops::Add<Point> for BBox
308impl std::ops::Add<Point> for BBox {
309    type Output = Self;
310    fn add(mut self, dxy: Point) -> Self {
311        self.x += dxy[0];
312        self.y += dxy[1];
313        self
314    }
315}
316//ip std::ops::AddAssign<Point> for BBox
317impl std::ops::AddAssign<Point> for BBox {
318    fn add_assign(&mut self, dxy: Point) {
319        self.x += dxy[0];
320        self.y += dxy[1];
321    }
322}
323
324//ip std::ops::Sub<Point> for BBox
325impl std::ops::Sub<Point> for BBox {
326    type Output = Self;
327    fn sub(mut self, dxy: Point) -> Self {
328        self.x -= dxy[0];
329        self.y -= dxy[1];
330        self
331    }
332}
333//ip std::ops::SubAssign<Point> for BBox
334impl std::ops::SubAssign<Point> for BBox {
335    fn sub_assign(&mut self, dxy: Point) {
336        self.x -= dxy[0];
337        self.y -= dxy[1];
338    }
339}
340
341//ip std::ops::Mul<f64> for BBox
342impl std::ops::Mul<f64> for BBox {
343    type Output = Self;
344    fn mul(mut self, scale: f64) -> Self {
345        self.x *= scale;
346        self.y *= scale;
347        self
348    }
349}
350
351//ip std::ops::MulAssign<f64> for BBox
352impl std::ops::MulAssign<f64> for BBox {
353    fn mul_assign(&mut self, scale: f64) {
354        self.x *= scale;
355        self.y *= scale;
356    }
357}
358
359//ip std::ops::Div<f64> for BBox
360impl std::ops::Div<f64> for BBox {
361    type Output = Self;
362    fn div(mut self, scale: f64) -> Self {
363        self.x /= scale;
364        self.y /= scale;
365        self
366    }
367}
368
369//ip std::ops::DivAssign<f64> for BBox
370impl std::ops::DivAssign<f64> for BBox {
371    fn div_assign(&mut self, scale: f64) {
372        self.x /= scale;
373        self.y /= scale;
374    }
375}
376
377//a Test
378#[cfg(test)]
379mod tests_polygon {
380    use super::*;
381    pub fn range_eq(pt: &Range, x: f64, y: f64) {
382        assert!(
383            (pt[0] - x).abs() < 1E-8,
384            "mismatch in x {:?} {} {}",
385            pt,
386            x,
387            y
388        );
389        assert!(
390            (pt[1] - y).abs() < 1E-8,
391            "mismatch in y {:?} {} {}",
392            pt,
393            x,
394            y
395        );
396    }
397    pub fn pt_eq(pt: &Point, x: f64, y: f64) {
398        assert!(
399            (pt[0] - x).abs() < 1E-8,
400            "mismatch in x {:?} {} {}",
401            pt,
402            x,
403            y
404        );
405        assert!(
406            (pt[1] - y).abs() < 1E-8,
407            "mismatch in y {:?} {} {}",
408            pt,
409            x,
410            y
411        );
412    }
413    pub fn pair_eq(pt: &(f64, f64), x: f64, y: f64) {
414        assert!(
415            (pt.0 - x).abs() < 1E-8,
416            "mismatch in x {:?} {} {}",
417            pt,
418            x,
419            y
420        );
421        assert!(
422            (pt.1 - y).abs() < 1E-8,
423            "mismatch in y {:?} {} {}",
424            pt,
425            x,
426            y
427        );
428    }
429    #[test]
430    fn test_zero() {
431        let x = BBox::none();
432        assert!(x.is_none());
433        dbg!("center {:?}", x.get_cwh());
434        assert_eq!(x.width(), 0.);
435        assert_eq!(x.height(), 0.);
436    }
437    #[test]
438    fn test_0() {
439        let x = BBox::new(-3., 1., 5., 7.);
440        pt_eq(&x.center(), 1., 4.);
441        assert_eq!(x.width(), 8.);
442        assert_eq!(x.height(), 6.);
443        pair_eq(&x.get_wh(), 8., 6.);
444        range_eq(&x.x, -3., 5.);
445        range_eq(&x.y, 1., 7.);
446        pt_eq(&x.get_cwh().0, 1., 4.);
447        assert_eq!(x.get_cwh().1, 8.);
448        assert_eq!(x.get_cwh().2, 6.);
449    }
450    #[test]
451    fn test_ops_0() {
452        let x = BBox::new(2., 1., 5., 7.);
453        let y = BBox::new(4., 0., 6., 3.);
454        let z = BBox::new(5., 1., 7., 4.);
455        let x_and_y = x.clone().intersect(y);
456        let x_or_y = x.clone().union(y);
457        let x_and_z = x.clone().intersect(z);
458        let x_or_z = x.clone().union(z);
459        println!("x_and_y:{}", x_and_y);
460        println!("x_or_y:{}", x_or_y);
461        println!("x_and_z:{}", x_and_z);
462        println!("x_or_z:{}", x_or_z);
463        range_eq(&x_and_y.x, 4., 5.);
464        range_eq(&x_and_y.y, 1., 3.);
465        range_eq(&x_or_y.x, 2., 6.);
466        range_eq(&x_or_y.y, 0., 7.);
467
468        assert!(!x_and_z.is_none()); // was none originally
469        dbg!(x_and_z.x);
470        dbg!(x_and_z.y);
471        dbg!(x_or_z.x);
472        dbg!(x_or_z.y);
473        range_eq(&x_and_z.x, 5., 5.);
474        range_eq(&x_and_z.y, 1., 4.);
475        range_eq(&x_or_z.x, 2., 7.);
476        range_eq(&x_or_z.y, 1., 7.);
477    }
478    #[test]
479    fn test_ops_1() {
480        let x = BBox::new(2., 1., 5., 7.);
481        let y = [0.1, 0.2, 0.3, 0.5];
482        let x_p_y = x.clone().expand(&y, 1.);
483        let x_p_2y = x.clone().expand(&y, 2.);
484        println!("x_p_y:{}", x_p_y);
485        println!("x_p_2y:{}", x_p_2y);
486        range_eq(&x_p_y.x, 1.9, 5.3);
487        range_eq(&x_p_y.y, 0.8, 7.5);
488        range_eq(&x_p_2y.x, 1.8, 5.6);
489        range_eq(&x_p_2y.y, 0.6, 8.);
490    }
491    #[test]
492    fn test_ops_2() {
493        let x = BBox::new(2., 1., 5., 7.);
494        let y = [0.1, 0.2, 0.3, 0.5];
495        let x_m_y = x.clone().shrink(&y, 1.);
496        let x_m_2y = x.clone().shrink(&y, 2.);
497        println!("x_m_y:{}", x_m_y);
498        println!("x_m_2y:{}", x_m_2y);
499        range_eq(&x_m_y.x, 2.1, 4.7);
500        range_eq(&x_m_y.y, 1.2, 6.5);
501        range_eq(&x_m_2y.x, 2.2, 4.4);
502        range_eq(&x_m_2y.y, 1.4, 6.);
503    }
504}