jagua_rs/geometry/primitives/
circle.rs

1use crate::geometry::Transformation;
2use crate::geometry::geo_enums::GeoPosition;
3use crate::geometry::geo_traits::{
4    CollidesWith, DistanceTo, SeparationDistance, Transformable, TransformableFrom,
5};
6use crate::geometry::primitives::Edge;
7use crate::geometry::primitives::Point;
8use crate::geometry::primitives::Rect;
9use anyhow::Result;
10use anyhow::ensure;
11use std::cmp::Ordering;
12use std::f32::consts::PI;
13
14/// Circle
15#[derive(Clone, Debug, PartialEq, Copy)]
16pub struct Circle {
17    pub center: Point,
18    pub radius: f32,
19}
20
21impl Circle {
22    pub fn try_new(center: Point, radius: f32) -> Result<Self> {
23        ensure!(
24            radius.is_finite() && radius >= 0.0,
25            "invalid circle radius: {radius}",
26        );
27        ensure!(
28            center.0.is_finite() && center.1.is_finite(),
29            "invalid circle center: {center:?}",
30        );
31
32        Ok(Self { center, radius })
33    }
34
35    /// Returns the smallest possible circle that fully contains all ```circles```
36    pub fn bounding_circle<'a>(circles: impl IntoIterator<Item = &'a Circle>) -> Circle {
37        let mut circles = circles.into_iter();
38        let mut bounding_circle = *circles.next().expect("no circles provided");
39
40        for circle in circles {
41            let distance_between_centers = bounding_circle.center.distance_to(&circle.center);
42            if bounding_circle.radius < distance_between_centers + circle.radius {
43                // circle not contained in bounding circle, expand
44                let diameter = Edge {
45                    start: bounding_circle.center,
46                    end: circle.center,
47                }
48                .extend_at_front(bounding_circle.radius)
49                .extend_at_back(circle.radius);
50
51                bounding_circle = Circle {
52                    center: diameter.centroid(),
53                    radius: diameter.length() / 2.0,
54                }
55            }
56        }
57        bounding_circle
58    }
59
60    pub fn area(&self) -> f32 {
61        self.radius * self.radius * PI
62    }
63
64    pub fn bbox(&self) -> Rect {
65        let (r, x, y) = (self.radius, self.center.0, self.center.1);
66        Rect {
67            x_min: x - r,
68            y_min: y - r,
69            x_max: x + r,
70            y_max: y + r,
71        }
72    }
73
74    pub fn diameter(&self) -> f32 {
75        self.radius * 2.0
76    }
77}
78
79impl Transformable for Circle {
80    fn transform(&mut self, t: &Transformation) -> &mut Self {
81        let Circle { center, radius: _ } = self;
82        center.transform(t);
83        self
84    }
85}
86
87impl TransformableFrom for Circle {
88    fn transform_from(&mut self, reference: &Self, t: &Transformation) -> &mut Self {
89        let Circle { center, radius: _ } = self;
90        center.transform_from(&reference.center, t);
91        self
92    }
93}
94
95impl CollidesWith<Circle> for Circle {
96    fn collides_with(&self, other: &Circle) -> bool {
97        let (cx1, cx2) = (self.center.0, other.center.0);
98        let (cy1, cy2) = (self.center.1, other.center.1);
99        let (r1, r2) = (self.radius, other.radius);
100
101        let dx = cx1 - cx2;
102        let dy = cy1 - cy2;
103        let sq_d = dx * dx + dy * dy;
104
105        sq_d <= (r1 + r2) * (r1 + r2)
106    }
107}
108
109impl CollidesWith<Edge> for Circle {
110    fn collides_with(&self, edge: &Edge) -> bool {
111        edge.sq_distance_to(&self.center) <= self.radius.powi(2)
112    }
113}
114
115impl CollidesWith<Rect> for Circle {
116    #[inline(always)]
117    fn collides_with(&self, rect: &Rect) -> bool {
118        //Based on: https://yal.cc/rectangle-circle-intersection-test/
119
120        let Point(c_x, c_y) = self.center;
121
122        //x and y coordinates inside the rectangle, closest to the circle center
123        let nearest_x = f32::max(rect.x_min, f32::min(c_x, rect.x_max));
124        let nearest_y = f32::max(rect.y_min, f32::min(c_y, rect.y_max));
125
126        (nearest_x - c_x).powi(2) + (nearest_y - c_y).powi(2) <= self.radius.powi(2)
127    }
128}
129
130impl CollidesWith<Point> for Circle {
131    fn collides_with(&self, point: &Point) -> bool {
132        point.sq_distance_to(&self.center) <= self.radius.powi(2)
133    }
134}
135
136impl DistanceTo<Point> for Circle {
137    fn distance_to(&self, point: &Point) -> f32 {
138        let Point(x, y) = point;
139        let Point(cx, cy) = self.center;
140        let sq_d = (x - cx).powi(2) + (y - cy).powi(2);
141        if sq_d < self.radius.powi(2) {
142            0.0 //point is inside circle
143        } else {
144            //point is outside circle
145            f32::sqrt(sq_d) - self.radius
146        }
147    }
148
149    fn sq_distance_to(&self, other: &Point) -> f32 {
150        self.distance_to(other).powi(2)
151    }
152}
153
154impl SeparationDistance<Point> for Circle {
155    fn separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
156        let Point(x, y) = point;
157        let Point(cx, cy) = self.center;
158        let d_center = f32::sqrt((x - cx).powi(2) + (y - cy).powi(2));
159        match d_center.partial_cmp(&self.radius).unwrap() {
160            Ordering::Less | Ordering::Equal => (GeoPosition::Interior, self.radius - d_center),
161            Ordering::Greater => (GeoPosition::Exterior, d_center - self.radius),
162        }
163    }
164
165    fn sq_separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
166        let (pos, distance) = self.separation_distance(point);
167        (pos, distance.powi(2))
168    }
169}
170
171impl DistanceTo<Circle> for Circle {
172    fn distance_to(&self, other: &Circle) -> f32 {
173        match self.separation_distance(other) {
174            (GeoPosition::Interior, _) => 0.0,
175            (GeoPosition::Exterior, d) => d,
176        }
177    }
178
179    fn sq_distance_to(&self, other: &Circle) -> f32 {
180        self.distance_to(other).powi(2)
181    }
182}
183
184impl SeparationDistance<Circle> for Circle {
185    fn separation_distance(&self, other: &Circle) -> (GeoPosition, f32) {
186        let sq_center_dist = self.center.sq_distance_to(&other.center);
187        let sq_radii_sum = (self.radius + other.radius).powi(2);
188        if sq_center_dist < sq_radii_sum {
189            let dist = sq_radii_sum.sqrt() - sq_center_dist.sqrt();
190            (GeoPosition::Interior, dist)
191        } else {
192            let dist = sq_center_dist.sqrt() - sq_radii_sum.sqrt();
193            (GeoPosition::Exterior, dist)
194        }
195    }
196
197    fn sq_separation_distance(&self, other: &Circle) -> (GeoPosition, f32) {
198        let (pos, distance) = self.separation_distance(other);
199        (pos, distance.powi(2))
200    }
201}
202
203impl DistanceTo<Edge> for Circle {
204    fn distance_to(&self, e: &Edge) -> f32 {
205        match self.separation_distance(e) {
206            (GeoPosition::Interior, _) => 0.0,
207            (GeoPosition::Exterior, d) => d,
208        }
209    }
210
211    fn sq_distance_to(&self, e: &Edge) -> f32 {
212        self.distance_to(e).powi(2)
213    }
214}
215
216impl SeparationDistance<Edge> for Circle {
217    fn separation_distance(&self, e: &Edge) -> (GeoPosition, f32) {
218        let distance_to_center = e.distance_to(&self.center);
219        if distance_to_center < self.radius {
220            (GeoPosition::Interior, self.radius - distance_to_center)
221        } else {
222            (GeoPosition::Exterior, distance_to_center - self.radius)
223        }
224    }
225
226    fn sq_separation_distance(&self, e: &Edge) -> (GeoPosition, f32) {
227        let (pos, distance) = self.separation_distance(e);
228        (pos, distance.powi(2))
229    }
230}