index/utils/
bounding_box.rs

1use wasm_bindgen::prelude::*;
2
3use super::point2d::{Path2D, Point2D};
4
5/// A bounding box is a rectangle that contains a set of points.
6#[wasm_bindgen]
7#[derive(Clone, Debug)]
8pub struct BoundingBox {
9    /// The minimum x-coordinate of the bounding box.
10    min_x: f32,
11    /// The minimum y-coordinate of the bounding box.
12    min_y: f32,
13    /// The width of the bounding box.
14    width: f32,
15    /// The height of the bounding box.
16    height: f32,
17}
18
19#[wasm_bindgen]
20impl BoundingBox {
21    /// Creates a new bounding box from a minimum x-coordinate, minimum y-coordinate, width, and height.
22    #[wasm_bindgen(constructor, return_description = "A bounding box with the given dimensions.")]
23    pub fn new(
24        #[wasm_bindgen(param_description = "The minimum x-coordinate of the bounding box.")]
25        min_x: f32,
26        #[wasm_bindgen(param_description = "The minimum y-coordinate of the bounding box.")]
27        min_y: f32,
28        #[wasm_bindgen(param_description = "The width of the bounding box.")]
29        width: f32,
30        #[wasm_bindgen(param_description = "The height of the bounding box.")]
31        height: f32,
32    ) -> Result<BoundingBox, JsError> {
33        if width < 0.0 || height < 0.0 {
34            return Err(JsError::new("The width and height must be non-negative."));
35        }
36        Ok(BoundingBox {
37            min_x,
38            min_y,
39            width,
40            height,
41        })
42    }
43    /// Clones the bounding box.
44    #[wasm_bindgen(js_name = clone)]
45    pub fn copy(&self) -> BoundingBox {
46        self.clone()
47    }
48    /// Creates the instance of a path's bounding box.
49    #[wasm_bindgen(return_description = "The bounding box of the path.")]
50    pub fn from_path(
51        #[wasm_bindgen(param_description = "The path to calculate the bounding box of.")]
52        path: &Path2D
53    ) -> Option<BoundingBox> {
54        if path.is_empty() {
55            return None;
56        }
57
58        let mut min_x = f32::INFINITY;
59        let mut min_y = f32::INFINITY;
60        let mut max_x = f32::NEG_INFINITY;
61        let mut max_y = f32::NEG_INFINITY;
62
63        for point in path.points().iter() {
64            min_x = min_x.min(point.x);
65            min_y = min_y.min(point.y);
66            max_x = max_x.max(point.x);
67            max_y = max_y.max(point.y);
68        }
69
70        Some(BoundingBox {
71            min_x,
72            min_y,
73            width: max_x - min_x,
74            height: max_y - min_y,
75        })
76    }
77
78    /// Gets the minimum x-coordinate of the bounding box.
79    #[wasm_bindgen(getter, return_description = "The minimum x-coordinate of the bounding box.")]
80    pub fn min_x(&self) -> f32 {
81        self.min_x
82    }
83
84    /// Gets the minimum y-coordinate of the bounding box.
85    #[wasm_bindgen(getter, return_description = "The minimum y-coordinate of the bounding box.")]
86    pub fn min_y(&self) -> f32 {
87        self.min_y
88    }
89
90    /// Gets the width of the bounding box.
91    #[wasm_bindgen(getter, return_description = "The width of the bounding box.")]
92    pub fn width(&self) -> f32 {
93        self.width
94    }
95
96    /// Gets the height of the bounding box.
97    #[wasm_bindgen(getter, return_description = "The height of the bounding box.")]
98    pub fn height(&self) -> f32 {
99        self.height
100    }
101
102    /// Checks if a point is contained within the bounding box.
103    #[wasm_bindgen(return_description = "A boolean indicating if the point is contained within the bounding box.")]
104    pub fn contains(
105        &self,
106        #[wasm_bindgen(param_description = "The point to check if it is contained within the bounding box.")]
107        point: Point2D,
108    ) -> bool {
109        let x = point.x;
110        let y = point.y;
111        x >= self.min_x && x <= self.min_x + self.width && y >= self.min_y && y <= self.min_y + self.height
112    }
113
114    /// Checks if the bounding box overlaps with another bounding box.
115    #[wasm_bindgen(return_description = "A boolean indicating if the bounding box overlaps with the other bounding box.")]
116    pub fn intersects(
117        &self,
118        #[wasm_bindgen(param_description = "The other bounding box to check for intersection.")]
119        other: &BoundingBox
120    ) -> bool {
121        self.min_x < other.min_x + other.width
122            && self.min_x + self.width > other.min_x
123            && self.min_y < other.min_y + other.height
124            && self.min_y + self.height > other.min_y
125    }
126
127    /// Returns the intersection of the bounding box with another bounding box, it means the area that is common to both bounding boxes.
128    #[wasm_bindgen(return_description = "The intersection of the bounding box with the other bounding box.")]
129    pub fn intersection(
130        #[wasm_bindgen(param_description = "The bounding box to intersect with.")]
131        this: Option<BoundingBox>,
132        #[wasm_bindgen(param_description = "The other bounding box to intersect with.")]
133        other: Option<BoundingBox>
134    ) -> Option<BoundingBox> {
135        if this.is_none() {
136            return None;
137        }
138        let this = this.unwrap();
139        if other.is_none() {
140            return None;
141        }
142        let other = other.unwrap();
143
144        if !this.intersects(&other) {
145            return None;
146        }
147
148        let min_x = this.min_x.max(other.min_x);
149        let min_y = this.min_y.max(other.min_y);
150        let max_x = (this.min_x + this.width).min(other.min_x + other.width);
151        let max_y = (this.min_y + this.height).min(other.min_y + other.height);
152
153        Some(BoundingBox {
154            min_x,
155            min_y,
156            width: max_x - min_x,
157            height: max_y - min_y,
158        })
159    }
160
161    /// Returns the union of the bounding box with another bounding box, it means the smallest bounding box that contains both bounding boxes.
162    #[wasm_bindgen(return_description = "The union of the bounding box with the other bounding box.")]
163    pub fn union(
164        #[wasm_bindgen(param_description = "The bounding box to union with.")]
165        this: Option<BoundingBox>,
166        #[wasm_bindgen(param_description = "The other bounding box to union with.")]
167        other: Option<BoundingBox>
168    ) -> Option<BoundingBox> {
169        if this.is_none() {
170            return other;
171        }
172        let this = this.unwrap();
173        if other.is_none() {
174            return Some(this);
175        }
176        let other = other.unwrap();
177
178        let min_x = this.min_x.min(other.min_x);
179        let min_y = this.min_y.min(other.min_y);
180        let max_x = (this.min_x + this.width).max(other.min_x + other.width);
181        let max_y = (this.min_y + this.height).max(other.min_y + other.height);
182
183        Some(BoundingBox {
184            min_x,
185            min_y,
186            width: max_x - min_x,
187            height: max_y - min_y,
188        })
189    }
190
191    /// Returns the center point of the bounding box.
192    #[wasm_bindgen(getter, return_description = "The center of the bounding box.")]
193    pub fn center(&self) -> Point2D {
194        Point2D {
195            x: self.min_x + self.width / 2.0,
196            y: self.min_y + self.height / 2.0,
197        }
198    }
199}