jagua_rs/geometry/primitives/
circle.rs1use 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#[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 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 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 #[must_use]
61 pub fn area(&self) -> f32 {
62 self.radius * self.radius * PI
63 }
64
65 #[must_use]
66 pub fn bbox(&self) -> Rect {
67 let (r, x, y) = (self.radius, self.center.0, self.center.1);
68 Rect {
69 x_min: x - r,
70 y_min: y - r,
71 x_max: x + r,
72 y_max: y + r,
73 }
74 }
75
76 #[must_use]
77 pub fn diameter(&self) -> f32 {
78 self.radius * 2.0
79 }
80}
81
82impl Transformable for Circle {
83 fn transform(&mut self, t: &Transformation) -> &mut Self {
84 let Circle { center, radius: _ } = self;
85 center.transform(t);
86 self
87 }
88}
89
90impl TransformableFrom for Circle {
91 fn transform_from(&mut self, reference: &Self, t: &Transformation) -> &mut Self {
92 let Circle { center, radius: _ } = self;
93 center.transform_from(&reference.center, t);
94 self
95 }
96}
97
98impl CollidesWith<Circle> for Circle {
99 fn collides_with(&self, other: &Circle) -> bool {
100 let (cx1, cx2) = (self.center.0, other.center.0);
101 let (cy1, cy2) = (self.center.1, other.center.1);
102 let (r1, r2) = (self.radius, other.radius);
103
104 let dx = cx1 - cx2;
105 let dy = cy1 - cy2;
106 let sq_d = dx * dx + dy * dy;
107
108 sq_d <= (r1 + r2) * (r1 + r2)
109 }
110}
111
112impl CollidesWith<Edge> for Circle {
113 fn collides_with(&self, edge: &Edge) -> bool {
114 edge.sq_distance_to(&self.center) <= self.radius.powi(2)
115 }
116}
117
118impl CollidesWith<Rect> for Circle {
119 #[inline(always)]
120 fn collides_with(&self, rect: &Rect) -> bool {
121 let Point(c_x, c_y) = self.center;
124
125 let nearest_x = f32::max(rect.x_min, f32::min(c_x, rect.x_max));
127 let nearest_y = f32::max(rect.y_min, f32::min(c_y, rect.y_max));
128
129 (nearest_x - c_x).powi(2) + (nearest_y - c_y).powi(2) <= self.radius.powi(2)
130 }
131}
132
133impl CollidesWith<Point> for Circle {
134 fn collides_with(&self, point: &Point) -> bool {
135 point.sq_distance_to(&self.center) <= self.radius.powi(2)
136 }
137}
138
139impl DistanceTo<Point> for Circle {
140 fn distance_to(&self, point: &Point) -> f32 {
141 let Point(x, y) = point;
142 let Point(cx, cy) = self.center;
143 let sq_d = (x - cx).powi(2) + (y - cy).powi(2);
144 if sq_d < self.radius.powi(2) {
145 0.0 } else {
147 f32::sqrt(sq_d) - self.radius
149 }
150 }
151
152 fn sq_distance_to(&self, other: &Point) -> f32 {
153 self.distance_to(other).powi(2)
154 }
155}
156
157impl SeparationDistance<Point> for Circle {
158 fn separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
159 let Point(x, y) = point;
160 let Point(cx, cy) = self.center;
161 let d_center = f32::sqrt((x - cx).powi(2) + (y - cy).powi(2));
162 match d_center.partial_cmp(&self.radius).unwrap() {
163 Ordering::Less | Ordering::Equal => (GeoPosition::Interior, self.radius - d_center),
164 Ordering::Greater => (GeoPosition::Exterior, d_center - self.radius),
165 }
166 }
167
168 fn sq_separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
169 let (pos, distance) = self.separation_distance(point);
170 (pos, distance.powi(2))
171 }
172}
173
174impl DistanceTo<Circle> for Circle {
175 fn distance_to(&self, other: &Circle) -> f32 {
176 match self.separation_distance(other) {
177 (GeoPosition::Interior, _) => 0.0,
178 (GeoPosition::Exterior, d) => d,
179 }
180 }
181
182 fn sq_distance_to(&self, other: &Circle) -> f32 {
183 self.distance_to(other).powi(2)
184 }
185}
186
187impl SeparationDistance<Circle> for Circle {
188 fn separation_distance(&self, other: &Circle) -> (GeoPosition, f32) {
189 let sq_center_dist = self.center.sq_distance_to(&other.center);
190 let sq_radii_sum = (self.radius + other.radius).powi(2);
191 if sq_center_dist < sq_radii_sum {
192 let dist = sq_radii_sum.sqrt() - sq_center_dist.sqrt();
193 (GeoPosition::Interior, dist)
194 } else {
195 let dist = sq_center_dist.sqrt() - sq_radii_sum.sqrt();
196 (GeoPosition::Exterior, dist)
197 }
198 }
199
200 fn sq_separation_distance(&self, other: &Circle) -> (GeoPosition, f32) {
201 let (pos, distance) = self.separation_distance(other);
202 (pos, distance.powi(2))
203 }
204}
205
206impl DistanceTo<Edge> for Circle {
207 fn distance_to(&self, e: &Edge) -> f32 {
208 match self.separation_distance(e) {
209 (GeoPosition::Interior, _) => 0.0,
210 (GeoPosition::Exterior, d) => d,
211 }
212 }
213
214 fn sq_distance_to(&self, e: &Edge) -> f32 {
215 self.distance_to(e).powi(2)
216 }
217}
218
219impl SeparationDistance<Edge> for Circle {
220 fn separation_distance(&self, e: &Edge) -> (GeoPosition, f32) {
221 let distance_to_center = e.distance_to(&self.center);
222 if distance_to_center < self.radius {
223 (GeoPosition::Interior, self.radius - distance_to_center)
224 } else {
225 (GeoPosition::Exterior, distance_to_center - self.radius)
226 }
227 }
228
229 fn sq_separation_distance(&self, e: &Edge) -> (GeoPosition, f32) {
230 let (pos, distance) = self.separation_distance(e);
231 (pos, distance.powi(2))
232 }
233}