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::GeometryError;
8use crate::geometry::primitives::Point;
9use crate::geometry::primitives::Rect;
10use anyhow::Result;
11use anyhow::ensure;
12use std::cmp::Ordering;
13use std::f32::consts::PI;
14
15#[derive(Clone, Debug, PartialEq, Copy)]
17pub struct Circle {
18 pub center: Point,
19 pub radius: f32,
20}
21
22impl Circle {
23 pub fn try_new(center: Point, radius: f32) -> Result<Self> {
24 ensure!(
25 radius.is_finite() && radius >= 0.0 && center.0.is_finite() && center.1.is_finite(),
26 GeometryError::InvalidCircle { center, radius }
27 );
28
29 Ok(Self { center, radius })
30 }
31
32 pub fn bounding_circle<'a>(circles: impl IntoIterator<Item = &'a Circle>) -> Circle {
34 let mut circles = circles.into_iter();
35 let mut bounding_circle = *circles.next().expect("no circles provided");
36
37 for circle in circles {
38 let distance_between_centers = bounding_circle.center.distance_to(&circle.center);
39 if bounding_circle.radius < distance_between_centers + circle.radius {
40 let diameter = Edge {
42 start: bounding_circle.center,
43 end: circle.center,
44 }
45 .extend_at_front(bounding_circle.radius)
46 .extend_at_back(circle.radius);
47
48 bounding_circle = Circle {
49 center: diameter.centroid(),
50 radius: diameter.length() / 2.0,
51 }
52 }
53 }
54 bounding_circle
55 }
56
57 #[must_use]
58 pub fn area(&self) -> f32 {
59 self.radius * self.radius * PI
60 }
61
62 #[must_use]
63 pub fn bbox(&self) -> Rect {
64 let (r, x, y) = (self.radius, self.center.0, self.center.1);
65 Rect {
66 x_min: x - r,
67 y_min: y - r,
68 x_max: x + r,
69 y_max: y + r,
70 }
71 }
72
73 #[must_use]
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 let Point(c_x, c_y) = self.center;
121
122 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 } else {
144 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}