1use core::{
7 f64::consts::{FRAC_PI_2, PI},
8 iter,
9 ops::{Add, Mul, Sub},
10};
11
12use crate::{Affine, Arc, ArcAppendIter, Ellipse, PathEl, Point, Rect, Shape, Vec2};
13
14#[cfg(not(feature = "std"))]
15use crate::common::FloatFuncs;
16
17#[derive(Clone, Copy, Default, Debug, PartialEq)]
19#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct Circle {
22 pub center: Point,
24 pub radius: f64,
26}
27
28impl Circle {
29 #[inline(always)]
31 pub fn new(center: impl Into<Point>, radius: f64) -> Circle {
32 Circle {
33 center: center.into(),
34 radius,
35 }
36 }
37
38 #[inline(always)]
40 pub fn segment(self, inner_radius: f64, start_angle: f64, sweep_angle: f64) -> CircleSegment {
41 CircleSegment {
42 center: self.center,
43 outer_radius: self.radius,
44 inner_radius,
45 start_angle,
46 sweep_angle,
47 }
48 }
49
50 #[inline]
54 pub fn is_finite(&self) -> bool {
55 self.center.is_finite() && self.radius.is_finite()
56 }
57
58 #[inline]
62 pub fn is_nan(&self) -> bool {
63 self.center.is_nan() || self.radius.is_nan()
64 }
65}
66
67impl Add<Vec2> for Circle {
68 type Output = Circle;
69
70 #[inline]
71 fn add(self, v: Vec2) -> Circle {
72 Circle {
73 center: self.center + v,
74 radius: self.radius,
75 }
76 }
77}
78
79impl Sub<Vec2> for Circle {
80 type Output = Circle;
81
82 #[inline]
83 fn sub(self, v: Vec2) -> Circle {
84 Circle {
85 center: self.center - v,
86 radius: self.radius,
87 }
88 }
89}
90
91impl Mul<Circle> for Affine {
92 type Output = Ellipse;
93 fn mul(self, other: Circle) -> Self::Output {
94 self * Ellipse::from(other)
95 }
96}
97
98#[doc(hidden)]
99pub struct CirclePathIter {
100 circle: Circle,
101 delta_th: f64,
102 arm_len: f64,
103 ix: usize,
104 n: usize,
105}
106
107impl Shape for Circle {
108 type PathElementsIter<'iter> = CirclePathIter;
109
110 fn path_elements(&self, tolerance: f64) -> CirclePathIter {
111 let scaled_err = self.radius.abs() / tolerance;
112 let (n, arm_len) = if scaled_err < 1.0 / 1.9608e-4 {
113 (4, 0.551915024494)
115 } else {
116 let n = (1.1163 * scaled_err).powf(1.0 / 6.0).ceil() as usize;
118 let arm_len = (4.0 / 3.0) * (FRAC_PI_2 / (n as f64)).tan();
121 (n, arm_len)
122 };
123 CirclePathIter {
124 circle: *self,
125 delta_th: 2.0 * PI / (n as f64),
126 arm_len,
127 ix: 0,
128 n,
129 }
130 }
131
132 #[inline]
133 fn area(&self) -> f64 {
134 PI * self.radius.powi(2)
135 }
136
137 #[inline]
138 fn perimeter(&self, _accuracy: f64) -> f64 {
139 (2.0 * PI * self.radius).abs()
140 }
141
142 fn winding(&self, pt: Point) -> i32 {
143 if (pt - self.center).hypot2() < self.radius.powi(2) {
144 1
145 } else {
146 0
147 }
148 }
149
150 #[inline]
151 fn bounding_box(&self) -> Rect {
152 let r = self.radius.abs();
153 let (x, y) = self.center.into();
154 Rect::new(x - r, y - r, x + r, y + r)
155 }
156
157 #[inline(always)]
158 fn as_circle(&self) -> Option<Circle> {
159 Some(*self)
160 }
161}
162
163impl Iterator for CirclePathIter {
164 type Item = PathEl;
165
166 fn next(&mut self) -> Option<PathEl> {
167 let a = self.arm_len;
168 let r = self.circle.radius;
169 let (x, y) = self.circle.center.into();
170 let ix = self.ix;
171 self.ix += 1;
172 if ix == 0 {
173 Some(PathEl::MoveTo(Point::new(x + r, y)))
174 } else if ix <= self.n {
175 let th1 = self.delta_th * (ix as f64);
176 let th0 = th1 - self.delta_th;
177 let (s0, c0) = th0.sin_cos();
178 let (s1, c1) = if ix == self.n {
179 (0.0, 1.0)
180 } else {
181 th1.sin_cos()
182 };
183 Some(PathEl::CurveTo(
184 Point::new(x + r * (c0 - a * s0), y + r * (s0 + a * c0)),
185 Point::new(x + r * (c1 + a * s1), y + r * (s1 - a * c1)),
186 Point::new(x + r * c1, y + r * s1),
187 ))
188 } else if ix == self.n + 1 {
189 Some(PathEl::ClosePath)
190 } else {
191 None
192 }
193 }
194}
195
196#[derive(Clone, Copy, Debug, PartialEq)]
200#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
201#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
202pub struct CircleSegment {
203 pub center: Point,
205 pub outer_radius: f64,
207 pub inner_radius: f64,
209 pub start_angle: f64,
211 pub sweep_angle: f64,
213}
214
215impl CircleSegment {
216 #[inline(always)]
218 pub fn new(
219 center: impl Into<Point>,
220 outer_radius: f64,
221 inner_radius: f64,
222 start_angle: f64,
223 sweep_angle: f64,
224 ) -> Self {
225 CircleSegment {
226 center: center.into(),
227 outer_radius,
228 inner_radius,
229 start_angle,
230 sweep_angle,
231 }
232 }
233
234 #[must_use]
236 #[inline(always)]
237 pub fn outer_arc(&self) -> Arc {
238 Arc {
239 center: self.center,
240 radii: Vec2::new(self.outer_radius, self.outer_radius),
241 start_angle: self.start_angle,
242 sweep_angle: self.sweep_angle,
243 x_rotation: 0.0,
244 }
245 }
246
247 #[must_use]
255 #[inline]
256 pub fn inner_arc(&self) -> Arc {
257 Arc {
258 center: self.center,
259 radii: Vec2::new(self.inner_radius, self.inner_radius),
260 start_angle: self.start_angle + self.sweep_angle,
261 sweep_angle: -self.sweep_angle,
262 x_rotation: 0.0,
263 }
264 }
265
266 #[inline]
270 pub fn is_finite(&self) -> bool {
271 self.center.is_finite()
272 && self.outer_radius.is_finite()
273 && self.inner_radius.is_finite()
274 && self.start_angle.is_finite()
275 && self.sweep_angle.is_finite()
276 }
277
278 #[inline]
282 pub fn is_nan(&self) -> bool {
283 self.center.is_nan()
284 || self.outer_radius.is_nan()
285 || self.inner_radius.is_nan()
286 || self.start_angle.is_nan()
287 || self.sweep_angle.is_nan()
288 }
289}
290
291impl Add<Vec2> for CircleSegment {
292 type Output = CircleSegment;
293
294 #[inline]
295 fn add(self, v: Vec2) -> Self {
296 Self {
297 center: self.center + v,
298 ..self
299 }
300 }
301}
302
303impl Sub<Vec2> for CircleSegment {
304 type Output = CircleSegment;
305
306 #[inline]
307 fn sub(self, v: Vec2) -> Self {
308 Self {
309 center: self.center - v,
310 ..self
311 }
312 }
313}
314
315type CircleSegmentPathIter = iter::Chain<
316 iter::Chain<
317 iter::Chain<iter::Chain<iter::Once<PathEl>, iter::Once<PathEl>>, ArcAppendIter>,
318 iter::Once<PathEl>,
319 >,
320 ArcAppendIter,
321>;
322
323impl Shape for CircleSegment {
324 type PathElementsIter<'iter> = CircleSegmentPathIter;
325
326 fn path_elements(&self, tolerance: f64) -> CircleSegmentPathIter {
327 iter::once(PathEl::MoveTo(point_on_circle(
328 self.center,
329 self.inner_radius,
330 self.start_angle,
331 )))
332 .chain(iter::once(PathEl::LineTo(point_on_circle(
334 self.center,
335 self.outer_radius,
336 self.start_angle,
337 ))))
338 .chain(self.outer_arc().append_iter(tolerance))
340 .chain(iter::once(PathEl::LineTo(point_on_circle(
342 self.center,
343 self.inner_radius,
344 self.start_angle + self.sweep_angle,
345 ))))
346 .chain(self.inner_arc().append_iter(tolerance))
348 }
349
350 #[inline]
351 fn area(&self) -> f64 {
352 0.5 * (self.outer_radius.powi(2) - self.inner_radius.powi(2)).abs() * self.sweep_angle
353 }
354
355 #[inline]
356 fn perimeter(&self, _accuracy: f64) -> f64 {
357 2.0 * (self.outer_radius - self.inner_radius).abs()
358 + self.sweep_angle * (self.inner_radius + self.outer_radius)
359 }
360
361 fn winding(&self, pt: Point) -> i32 {
362 let angle = (pt - self.center).atan2();
363 if angle < self.start_angle || angle > self.start_angle + self.sweep_angle {
364 return 0;
365 }
366 let dist2 = (pt - self.center).hypot2();
367 if (dist2 < self.outer_radius.powi(2) && dist2 > self.inner_radius.powi(2)) ||
368 (dist2 < self.inner_radius.powi(2) && dist2 > self.outer_radius.powi(2))
370 {
371 1
372 } else {
373 0
374 }
375 }
376
377 #[inline]
378 fn bounding_box(&self) -> Rect {
379 let r = self.inner_radius.max(self.outer_radius);
381 let (x, y) = self.center.into();
382 Rect::new(x - r, y - r, x + r, y + r)
383 }
384}
385
386#[inline]
387fn point_on_circle(center: Point, radius: f64, angle: f64) -> Point {
388 let (angle_sin, angle_cos) = angle.sin_cos();
389 center
390 + Vec2 {
391 x: angle_cos * radius,
392 y: angle_sin * radius,
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use crate::{Circle, Point, Shape};
399 use std::f64::consts::PI;
400
401 fn assert_approx_eq(x: f64, y: f64) {
402 assert!((x - y).abs() < 1e-7, "{x} != {y}");
405 }
406
407 #[test]
408 fn area_sign() {
409 let center = Point::new(5.0, 5.0);
410 let c = Circle::new(center, 5.0);
411 assert_approx_eq(c.area(), 25.0 * PI);
412
413 assert_eq!(c.winding(center), 1);
414
415 let p = c.to_path(1e-9);
416 assert_approx_eq(c.area(), p.area());
417 assert_eq!(c.winding(center), p.winding(center));
418
419 let c_neg_radius = Circle::new(center, -5.0);
420 assert_approx_eq(c_neg_radius.area(), 25.0 * PI);
421
422 assert_eq!(c_neg_radius.winding(center), 1);
423
424 let p_neg_radius = c_neg_radius.to_path(1e-9);
425 assert_approx_eq(c_neg_radius.area(), p_neg_radius.area());
426 assert_eq!(c_neg_radius.winding(center), p_neg_radius.winding(center));
427 }
428}