bevy_math/primitives/
dim2.rs

1use core::f32::consts::{FRAC_1_SQRT_2, FRAC_PI_2, FRAC_PI_3, PI};
2use derive_more::derive::{Display, Error, From};
3
4use super::{Measured2d, Primitive2d, WindingOrder};
5use crate::{
6    ops::{self, FloatPow},
7    Dir2, Vec2,
8};
9
10#[cfg(feature = "bevy_reflect")]
11use bevy_reflect::{std_traits::ReflectDefault, Reflect};
12#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
13use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
14
15/// A circle primitive, representing the set of points some distance from the origin
16#[derive(Clone, Copy, Debug, PartialEq)]
17#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
18#[cfg_attr(
19    feature = "bevy_reflect",
20    derive(Reflect),
21    reflect(Debug, PartialEq, Default)
22)]
23#[cfg_attr(
24    all(feature = "serialize", feature = "bevy_reflect"),
25    reflect(Serialize, Deserialize)
26)]
27pub struct Circle {
28    /// The radius of the circle
29    pub radius: f32,
30}
31impl Primitive2d for Circle {}
32
33impl Default for Circle {
34    /// Returns the default [`Circle`] with a radius of `0.5`.
35    fn default() -> Self {
36        Self { radius: 0.5 }
37    }
38}
39
40impl Circle {
41    /// Create a new [`Circle`] from a `radius`
42    #[inline(always)]
43    pub const fn new(radius: f32) -> Self {
44        Self { radius }
45    }
46
47    /// Get the diameter of the circle
48    #[inline(always)]
49    pub fn diameter(&self) -> f32 {
50        2.0 * self.radius
51    }
52
53    /// Finds the point on the circle that is closest to the given `point`.
54    ///
55    /// If the point is outside the circle, the returned point will be on the perimeter of the circle.
56    /// Otherwise, it will be inside the circle and returned as is.
57    #[inline(always)]
58    pub fn closest_point(&self, point: Vec2) -> Vec2 {
59        let distance_squared = point.length_squared();
60
61        if distance_squared <= self.radius.squared() {
62            // The point is inside the circle.
63            point
64        } else {
65            // The point is outside the circle.
66            // Find the closest point on the perimeter of the circle.
67            let dir_to_point = point / distance_squared.sqrt();
68            self.radius * dir_to_point
69        }
70    }
71}
72
73impl Measured2d for Circle {
74    /// Get the area of the circle
75    #[inline(always)]
76    fn area(&self) -> f32 {
77        PI * self.radius.squared()
78    }
79
80    /// Get the perimeter or circumference of the circle
81    #[inline(always)]
82    #[doc(alias = "circumference")]
83    fn perimeter(&self) -> f32 {
84        2.0 * PI * self.radius
85    }
86}
87
88/// A primitive representing an arc between two points on a circle.
89///
90/// An arc has no area.
91/// If you want to include the portion of a circle's area swept out by the arc,
92/// use the pie-shaped [`CircularSector`].
93/// If you want to include only the space inside the convex hull of the arc,
94/// use the bowl-shaped [`CircularSegment`].
95///
96/// The arc is drawn starting from [`Vec2::Y`], extending by `half_angle` radians on
97/// either side. The center of the circle is the origin [`Vec2::ZERO`]. Note that this
98/// means that the origin may not be within the `Arc2d`'s convex hull.
99///
100/// **Warning:** Arcs with negative angle or radius, or with angle greater than an entire circle, are not officially supported.
101/// It is recommended to normalize arcs to have an angle in [0, 2π].
102#[derive(Clone, Copy, Debug, PartialEq)]
103#[doc(alias("CircularArc", "CircleArc"))]
104#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
105#[cfg_attr(
106    feature = "bevy_reflect",
107    derive(Reflect),
108    reflect(Debug, PartialEq, Default)
109)]
110#[cfg_attr(
111    all(feature = "serialize", feature = "bevy_reflect"),
112    reflect(Serialize, Deserialize)
113)]
114pub struct Arc2d {
115    /// The radius of the circle
116    pub radius: f32,
117    /// Half the angle defining the arc
118    pub half_angle: f32,
119}
120impl Primitive2d for Arc2d {}
121
122impl Default for Arc2d {
123    /// Returns the default [`Arc2d`] with radius `0.5`, covering one third of a circle
124    fn default() -> Self {
125        Self {
126            radius: 0.5,
127            half_angle: 2.0 * FRAC_PI_3,
128        }
129    }
130}
131
132impl Arc2d {
133    /// Create a new [`Arc2d`] from a `radius` and a `half_angle`
134    #[inline(always)]
135    pub fn new(radius: f32, half_angle: f32) -> Self {
136        Self { radius, half_angle }
137    }
138
139    /// Create a new [`Arc2d`] from a `radius` and an `angle` in radians
140    #[inline(always)]
141    pub fn from_radians(radius: f32, angle: f32) -> Self {
142        Self {
143            radius,
144            half_angle: angle / 2.0,
145        }
146    }
147
148    /// Create a new [`Arc2d`] from a `radius` and an `angle` in degrees.
149    #[inline(always)]
150    pub fn from_degrees(radius: f32, angle: f32) -> Self {
151        Self {
152            radius,
153            half_angle: angle.to_radians() / 2.0,
154        }
155    }
156
157    /// Create a new [`Arc2d`] from a `radius` and a `fraction` of a single turn.
158    ///
159    /// For instance, `0.5` turns is a semicircle.
160    #[inline(always)]
161    pub fn from_turns(radius: f32, fraction: f32) -> Self {
162        Self {
163            radius,
164            half_angle: fraction * PI,
165        }
166    }
167
168    /// Get the angle of the arc
169    #[inline(always)]
170    pub fn angle(&self) -> f32 {
171        self.half_angle * 2.0
172    }
173
174    /// Get the length of the arc
175    #[inline(always)]
176    pub fn length(&self) -> f32 {
177        self.angle() * self.radius
178    }
179
180    /// Get the right-hand end point of the arc
181    #[inline(always)]
182    pub fn right_endpoint(&self) -> Vec2 {
183        self.radius * Vec2::from_angle(FRAC_PI_2 - self.half_angle)
184    }
185
186    /// Get the left-hand end point of the arc
187    #[inline(always)]
188    pub fn left_endpoint(&self) -> Vec2 {
189        self.radius * Vec2::from_angle(FRAC_PI_2 + self.half_angle)
190    }
191
192    /// Get the endpoints of the arc
193    #[inline(always)]
194    pub fn endpoints(&self) -> [Vec2; 2] {
195        [self.left_endpoint(), self.right_endpoint()]
196    }
197
198    /// Get the midpoint of the arc
199    #[inline]
200    pub fn midpoint(&self) -> Vec2 {
201        self.radius * Vec2::Y
202    }
203
204    /// Get half the distance between the endpoints (half the length of the chord)
205    #[inline(always)]
206    pub fn half_chord_length(&self) -> f32 {
207        self.radius * ops::sin(self.half_angle)
208    }
209
210    /// Get the distance between the endpoints (the length of the chord)
211    #[inline(always)]
212    pub fn chord_length(&self) -> f32 {
213        2.0 * self.half_chord_length()
214    }
215
216    /// Get the midpoint of the two endpoints (the midpoint of the chord)
217    #[inline(always)]
218    pub fn chord_midpoint(&self) -> Vec2 {
219        self.apothem() * Vec2::Y
220    }
221
222    /// Get the length of the apothem of this arc, that is,
223    /// the distance from the center of the circle to the midpoint of the chord, in the direction of the midpoint of the arc.
224    /// Equivalently, the [`radius`](Self::radius) minus the [`sagitta`](Self::sagitta).
225    ///
226    /// Note that for a [`major`](Self::is_major) arc, the apothem will be negative.
227    #[inline(always)]
228    // Naming note: Various sources are inconsistent as to whether the apothem is the segment between the center and the
229    // midpoint of a chord, or the length of that segment. Given this confusion, we've opted for the definition
230    // used by Wolfram MathWorld, which is the distance rather than the segment.
231    pub fn apothem(&self) -> f32 {
232        let sign = if self.is_minor() { 1.0 } else { -1.0 };
233        sign * f32::sqrt(self.radius.squared() - self.half_chord_length().squared())
234    }
235
236    /// Get the length of the sagitta of this arc, that is,
237    /// the length of the line between the midpoints of the arc and its chord.
238    /// Equivalently, the height of the triangle whose base is the chord and whose apex is the midpoint of the arc.
239    ///
240    /// The sagitta is also the sum of the [`radius`](Self::radius) and the [`apothem`](Self::apothem).
241    pub fn sagitta(&self) -> f32 {
242        self.radius - self.apothem()
243    }
244
245    /// Produces true if the arc is at most half a circle.
246    ///
247    /// **Note:** This is not the negation of [`is_major`](Self::is_major): an exact semicircle is both major and minor.
248    #[inline(always)]
249    pub fn is_minor(&self) -> bool {
250        self.half_angle <= FRAC_PI_2
251    }
252
253    /// Produces true if the arc is at least half a circle.
254    ///
255    /// **Note:** This is not the negation of [`is_minor`](Self::is_minor): an exact semicircle is both major and minor.
256    #[inline(always)]
257    pub fn is_major(&self) -> bool {
258        self.half_angle >= FRAC_PI_2
259    }
260}
261
262/// A primitive representing a circular sector: a pie slice of a circle.
263///
264/// The segment is positioned so that it always includes [`Vec2::Y`] and is vertically symmetrical.
265/// To orient the sector differently, apply a rotation.
266/// The sector is drawn with the center of its circle at the origin [`Vec2::ZERO`].
267///
268/// **Warning:** Circular sectors with negative angle or radius, or with angle greater than an entire circle, are not officially supported.
269/// We recommend normalizing circular sectors to have an angle in [0, 2π].
270#[derive(Clone, Copy, Debug, PartialEq, From)]
271#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
272#[cfg_attr(
273    feature = "bevy_reflect",
274    derive(Reflect),
275    reflect(Debug, PartialEq, Default)
276)]
277#[cfg_attr(
278    all(feature = "serialize", feature = "bevy_reflect"),
279    reflect(Serialize, Deserialize)
280)]
281pub struct CircularSector {
282    /// The arc defining the sector
283    #[cfg_attr(feature = "serialize", serde(flatten))]
284    pub arc: Arc2d,
285}
286impl Primitive2d for CircularSector {}
287
288impl Default for CircularSector {
289    /// Returns the default [`CircularSector`] with radius `0.5` and covering a third of a circle
290    fn default() -> Self {
291        Self::from(Arc2d::default())
292    }
293}
294
295impl Measured2d for CircularSector {
296    #[inline(always)]
297    fn area(&self) -> f32 {
298        self.arc.radius.squared() * self.arc.half_angle
299    }
300
301    #[inline(always)]
302    fn perimeter(&self) -> f32 {
303        if self.half_angle() >= PI {
304            self.arc.radius * 2.0 * PI
305        } else {
306            2.0 * self.radius() + self.arc_length()
307        }
308    }
309}
310
311impl CircularSector {
312    /// Create a new [`CircularSector`] from a `radius` and an `angle`
313    #[inline(always)]
314    pub fn new(radius: f32, angle: f32) -> Self {
315        Self::from(Arc2d::new(radius, angle))
316    }
317
318    /// Create a new [`CircularSector`] from a `radius` and an `angle` in radians.
319    #[inline(always)]
320    pub fn from_radians(radius: f32, angle: f32) -> Self {
321        Self::from(Arc2d::from_radians(radius, angle))
322    }
323
324    /// Create a new [`CircularSector`] from a `radius` and an `angle` in degrees.
325    #[inline(always)]
326    pub fn from_degrees(radius: f32, angle: f32) -> Self {
327        Self::from(Arc2d::from_degrees(radius, angle))
328    }
329
330    /// Create a new [`CircularSector`] from a `radius` and a number of `turns` of a circle.
331    ///
332    /// For instance, `0.5` turns is a semicircle.
333    #[inline(always)]
334    pub fn from_turns(radius: f32, fraction: f32) -> Self {
335        Self::from(Arc2d::from_turns(radius, fraction))
336    }
337
338    /// Get half the angle of the sector
339    #[inline(always)]
340    pub fn half_angle(&self) -> f32 {
341        self.arc.half_angle
342    }
343
344    /// Get the angle of the sector
345    #[inline(always)]
346    pub fn angle(&self) -> f32 {
347        self.arc.angle()
348    }
349
350    /// Get the radius of the sector
351    #[inline(always)]
352    pub fn radius(&self) -> f32 {
353        self.arc.radius
354    }
355
356    /// Get the length of the arc defining the sector
357    #[inline(always)]
358    pub fn arc_length(&self) -> f32 {
359        self.arc.length()
360    }
361
362    /// Get half the length of the chord defined by the sector
363    ///
364    /// See [`Arc2d::half_chord_length`]
365    #[inline(always)]
366    pub fn half_chord_length(&self) -> f32 {
367        self.arc.half_chord_length()
368    }
369
370    /// Get the length of the chord defined by the sector
371    ///
372    /// See [`Arc2d::chord_length`]
373    #[inline(always)]
374    pub fn chord_length(&self) -> f32 {
375        self.arc.chord_length()
376    }
377
378    /// Get the midpoint of the chord defined by the sector
379    ///
380    /// See [`Arc2d::chord_midpoint`]
381    #[inline(always)]
382    pub fn chord_midpoint(&self) -> Vec2 {
383        self.arc.chord_midpoint()
384    }
385
386    /// Get the length of the apothem of this sector
387    ///
388    /// See [`Arc2d::apothem`]
389    #[inline(always)]
390    pub fn apothem(&self) -> f32 {
391        self.arc.apothem()
392    }
393
394    /// Get the length of the sagitta of this sector
395    ///
396    /// See [`Arc2d::sagitta`]
397    #[inline(always)]
398    pub fn sagitta(&self) -> f32 {
399        self.arc.sagitta()
400    }
401}
402
403/// A primitive representing a circular segment:
404/// the area enclosed by the arc of a circle and its chord (the line between its endpoints).
405///
406/// The segment is drawn starting from [`Vec2::Y`], extending equally on either side.
407/// To orient the segment differently, apply a rotation.
408/// The segment is drawn with the center of its circle at the origin [`Vec2::ZERO`].
409/// When positioning a segment, the [`apothem`](Self::apothem) function may be particularly useful.
410///
411/// **Warning:** Circular segments with negative angle or radius, or with angle greater than an entire circle, are not officially supported.
412/// We recommend normalizing circular segments to have an angle in [0, 2π].
413#[derive(Clone, Copy, Debug, PartialEq, From)]
414#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
415#[cfg_attr(
416    feature = "bevy_reflect",
417    derive(Reflect),
418    reflect(Debug, PartialEq, Default)
419)]
420#[cfg_attr(
421    all(feature = "serialize", feature = "bevy_reflect"),
422    reflect(Serialize, Deserialize)
423)]
424pub struct CircularSegment {
425    /// The arc defining the segment
426    #[cfg_attr(feature = "serialize", serde(flatten))]
427    pub arc: Arc2d,
428}
429impl Primitive2d for CircularSegment {}
430
431impl Default for CircularSegment {
432    /// Returns the default [`CircularSegment`] with radius `0.5` and covering a third of a circle
433    fn default() -> Self {
434        Self::from(Arc2d::default())
435    }
436}
437
438impl Measured2d for CircularSegment {
439    #[inline(always)]
440    fn area(&self) -> f32 {
441        0.5 * self.arc.radius.squared() * (self.arc.angle() - ops::sin(self.arc.angle()))
442    }
443
444    #[inline(always)]
445    fn perimeter(&self) -> f32 {
446        self.chord_length() + self.arc_length()
447    }
448}
449impl CircularSegment {
450    /// Create a new [`CircularSegment`] from a `radius`, and an `angle`
451    #[inline(always)]
452    pub fn new(radius: f32, angle: f32) -> Self {
453        Self::from(Arc2d::new(radius, angle))
454    }
455
456    /// Create a new [`CircularSegment`] from a `radius` and an `angle` in radians.
457    #[inline(always)]
458    pub fn from_radians(radius: f32, angle: f32) -> Self {
459        Self::from(Arc2d::from_radians(radius, angle))
460    }
461
462    /// Create a new [`CircularSegment`] from a `radius` and an `angle` in degrees.
463    #[inline(always)]
464    pub fn from_degrees(radius: f32, angle: f32) -> Self {
465        Self::from(Arc2d::from_degrees(radius, angle))
466    }
467
468    /// Create a new [`CircularSegment`] from a `radius` and a number of `turns` of a circle.
469    ///
470    /// For instance, `0.5` turns is a semicircle.
471    #[inline(always)]
472    pub fn from_turns(radius: f32, fraction: f32) -> Self {
473        Self::from(Arc2d::from_turns(radius, fraction))
474    }
475
476    /// Get the half-angle of the segment
477    #[inline(always)]
478    pub fn half_angle(&self) -> f32 {
479        self.arc.half_angle
480    }
481
482    /// Get the angle of the segment
483    #[inline(always)]
484    pub fn angle(&self) -> f32 {
485        self.arc.angle()
486    }
487
488    /// Get the radius of the segment
489    #[inline(always)]
490    pub fn radius(&self) -> f32 {
491        self.arc.radius
492    }
493
494    /// Get the length of the arc defining the segment
495    #[inline(always)]
496    pub fn arc_length(&self) -> f32 {
497        self.arc.length()
498    }
499
500    /// Get half the length of the segment's base, also known as its chord
501    #[inline(always)]
502    #[doc(alias = "half_base_length")]
503    pub fn half_chord_length(&self) -> f32 {
504        self.arc.half_chord_length()
505    }
506
507    /// Get the length of the segment's base, also known as its chord
508    #[inline(always)]
509    #[doc(alias = "base_length")]
510    #[doc(alias = "base")]
511    pub fn chord_length(&self) -> f32 {
512        self.arc.chord_length()
513    }
514
515    /// Get the midpoint of the segment's base, also known as its chord
516    #[inline(always)]
517    #[doc(alias = "base_midpoint")]
518    pub fn chord_midpoint(&self) -> Vec2 {
519        self.arc.chord_midpoint()
520    }
521
522    /// Get the length of the apothem of this segment,
523    /// which is the signed distance between the segment and the center of its circle
524    ///
525    /// See [`Arc2d::apothem`]
526    #[inline(always)]
527    pub fn apothem(&self) -> f32 {
528        self.arc.apothem()
529    }
530
531    /// Get the length of the sagitta of this segment, also known as its height
532    ///
533    /// See [`Arc2d::sagitta`]
534    #[inline(always)]
535    #[doc(alias = "height")]
536    pub fn sagitta(&self) -> f32 {
537        self.arc.sagitta()
538    }
539}
540
541#[cfg(test)]
542mod arc_tests {
543    use core::f32::consts::FRAC_PI_4;
544    use core::f32::consts::SQRT_2;
545
546    // use approx::assert_abs_diff_eq;
547
548    use super::*;
549
550    struct ArcTestCase {
551        radius: f32,
552        half_angle: f32,
553        angle: f32,
554        length: f32,
555        right_endpoint: Vec2,
556        left_endpoint: Vec2,
557        endpoints: [Vec2; 2],
558        midpoint: Vec2,
559        half_chord_length: f32,
560        chord_length: f32,
561        chord_midpoint: Vec2,
562        apothem: f32,
563        sagitta: f32,
564        is_minor: bool,
565        is_major: bool,
566        sector_area: f32,
567        sector_perimeter: f32,
568        segment_area: f32,
569        segment_perimeter: f32,
570    }
571
572    impl ArcTestCase {
573        fn check_arc(&self, arc: Arc2d) {
574            // assert_abs_diff_eq!(self.radius, arc.radius);
575            // assert_abs_diff_eq!(self.half_angle, arc.half_angle);
576            // assert_abs_diff_eq!(self.angle, arc.angle());
577            // assert_abs_diff_eq!(self.length, arc.length());
578            // assert_abs_diff_eq!(self.right_endpoint, arc.right_endpoint());
579            // assert_abs_diff_eq!(self.left_endpoint, arc.left_endpoint());
580            // assert_abs_diff_eq!(self.endpoints[0], arc.endpoints()[0]);
581            // assert_abs_diff_eq!(self.endpoints[1], arc.endpoints()[1]);
582            // assert_abs_diff_eq!(self.midpoint, arc.midpoint());
583            // assert_abs_diff_eq!(self.half_chord_length, arc.half_chord_length());
584            // assert_abs_diff_eq!(self.chord_length, arc.chord_length(), epsilon = 0.00001);
585            // assert_abs_diff_eq!(self.chord_midpoint, arc.chord_midpoint());
586            // assert_abs_diff_eq!(self.apothem, arc.apothem());
587            // assert_abs_diff_eq!(self.sagitta, arc.sagitta());
588            assert_eq!(self.is_minor, arc.is_minor());
589            assert_eq!(self.is_major, arc.is_major());
590        }
591
592        fn check_sector(&self, sector: CircularSector) {
593            // assert_abs_diff_eq!(self.radius, sector.radius());
594            // assert_abs_diff_eq!(self.half_angle, sector.half_angle());
595            // assert_abs_diff_eq!(self.angle, sector.angle());
596            // assert_abs_diff_eq!(self.half_chord_length, sector.half_chord_length());
597            // assert_abs_diff_eq!(self.chord_length, sector.chord_length(), epsilon = 0.00001);
598            // assert_abs_diff_eq!(self.chord_midpoint, sector.chord_midpoint());
599            // assert_abs_diff_eq!(self.apothem, sector.apothem());
600            // assert_abs_diff_eq!(self.sagitta, sector.sagitta());
601            // assert_abs_diff_eq!(self.sector_area, sector.area());
602            // assert_abs_diff_eq!(self.sector_perimeter, sector.perimeter());
603        }
604
605        fn check_segment(&self, segment: CircularSegment) {
606            // assert_abs_diff_eq!(self.radius, segment.radius());
607            // assert_abs_diff_eq!(self.half_angle, segment.half_angle());
608            // assert_abs_diff_eq!(self.angle, segment.angle());
609            // assert_abs_diff_eq!(self.half_chord_length, segment.half_chord_length());
610            // assert_abs_diff_eq!(self.chord_length, segment.chord_length(), epsilon = 0.00001);
611            // assert_abs_diff_eq!(self.chord_midpoint, segment.chord_midpoint());
612            // assert_abs_diff_eq!(self.apothem, segment.apothem());
613            // assert_abs_diff_eq!(self.sagitta, segment.sagitta());
614            // assert_abs_diff_eq!(self.segment_area, segment.area());
615            // assert_abs_diff_eq!(self.segment_perimeter, segment.perimeter());
616        }
617    }
618
619    #[test]
620    fn zero_angle() {
621        let tests = ArcTestCase {
622            radius: 1.0,
623            half_angle: 0.0,
624            angle: 0.0,
625            length: 0.0,
626            left_endpoint: Vec2::Y,
627            right_endpoint: Vec2::Y,
628            endpoints: [Vec2::Y, Vec2::Y],
629            midpoint: Vec2::Y,
630            half_chord_length: 0.0,
631            chord_length: 0.0,
632            chord_midpoint: Vec2::Y,
633            apothem: 1.0,
634            sagitta: 0.0,
635            is_minor: true,
636            is_major: false,
637            sector_area: 0.0,
638            sector_perimeter: 2.0,
639            segment_area: 0.0,
640            segment_perimeter: 0.0,
641        };
642
643        tests.check_arc(Arc2d::new(1.0, 0.0));
644        tests.check_sector(CircularSector::new(1.0, 0.0));
645        tests.check_segment(CircularSegment::new(1.0, 0.0));
646    }
647
648    #[test]
649    fn zero_radius() {
650        let tests = ArcTestCase {
651            radius: 0.0,
652            half_angle: FRAC_PI_4,
653            angle: FRAC_PI_2,
654            length: 0.0,
655            left_endpoint: Vec2::ZERO,
656            right_endpoint: Vec2::ZERO,
657            endpoints: [Vec2::ZERO, Vec2::ZERO],
658            midpoint: Vec2::ZERO,
659            half_chord_length: 0.0,
660            chord_length: 0.0,
661            chord_midpoint: Vec2::ZERO,
662            apothem: 0.0,
663            sagitta: 0.0,
664            is_minor: true,
665            is_major: false,
666            sector_area: 0.0,
667            sector_perimeter: 0.0,
668            segment_area: 0.0,
669            segment_perimeter: 0.0,
670        };
671
672        tests.check_arc(Arc2d::new(0.0, FRAC_PI_4));
673        tests.check_sector(CircularSector::new(0.0, FRAC_PI_4));
674        tests.check_segment(CircularSegment::new(0.0, FRAC_PI_4));
675    }
676
677    #[test]
678    fn quarter_circle() {
679        let sqrt_half: f32 = f32::sqrt(0.5);
680        let tests = ArcTestCase {
681            radius: 1.0,
682            half_angle: FRAC_PI_4,
683            angle: FRAC_PI_2,
684            length: FRAC_PI_2,
685            left_endpoint: Vec2::new(-sqrt_half, sqrt_half),
686            right_endpoint: Vec2::splat(sqrt_half),
687            endpoints: [Vec2::new(-sqrt_half, sqrt_half), Vec2::splat(sqrt_half)],
688            midpoint: Vec2::Y,
689            half_chord_length: sqrt_half,
690            chord_length: f32::sqrt(2.0),
691            chord_midpoint: Vec2::new(0.0, sqrt_half),
692            apothem: sqrt_half,
693            sagitta: 1.0 - sqrt_half,
694            is_minor: true,
695            is_major: false,
696            sector_area: FRAC_PI_4,
697            sector_perimeter: FRAC_PI_2 + 2.0,
698            segment_area: FRAC_PI_4 - 0.5,
699            segment_perimeter: FRAC_PI_2 + SQRT_2,
700        };
701
702        tests.check_arc(Arc2d::from_turns(1.0, 0.25));
703        tests.check_sector(CircularSector::from_turns(1.0, 0.25));
704        tests.check_segment(CircularSegment::from_turns(1.0, 0.25));
705    }
706
707    #[test]
708    fn half_circle() {
709        let tests = ArcTestCase {
710            radius: 1.0,
711            half_angle: FRAC_PI_2,
712            angle: PI,
713            length: PI,
714            left_endpoint: Vec2::NEG_X,
715            right_endpoint: Vec2::X,
716            endpoints: [Vec2::NEG_X, Vec2::X],
717            midpoint: Vec2::Y,
718            half_chord_length: 1.0,
719            chord_length: 2.0,
720            chord_midpoint: Vec2::ZERO,
721            apothem: 0.0,
722            sagitta: 1.0,
723            is_minor: true,
724            is_major: true,
725            sector_area: FRAC_PI_2,
726            sector_perimeter: PI + 2.0,
727            segment_area: FRAC_PI_2,
728            segment_perimeter: PI + 2.0,
729        };
730
731        tests.check_arc(Arc2d::from_radians(1.0, PI));
732        tests.check_sector(CircularSector::from_radians(1.0, PI));
733        tests.check_segment(CircularSegment::from_radians(1.0, PI));
734    }
735
736    #[test]
737    fn full_circle() {
738        let tests = ArcTestCase {
739            radius: 1.0,
740            half_angle: PI,
741            angle: 2.0 * PI,
742            length: 2.0 * PI,
743            left_endpoint: Vec2::NEG_Y,
744            right_endpoint: Vec2::NEG_Y,
745            endpoints: [Vec2::NEG_Y, Vec2::NEG_Y],
746            midpoint: Vec2::Y,
747            half_chord_length: 0.0,
748            chord_length: 0.0,
749            chord_midpoint: Vec2::NEG_Y,
750            apothem: -1.0,
751            sagitta: 2.0,
752            is_minor: false,
753            is_major: true,
754            sector_area: PI,
755            sector_perimeter: 2.0 * PI,
756            segment_area: PI,
757            segment_perimeter: 2.0 * PI,
758        };
759
760        tests.check_arc(Arc2d::from_degrees(1.0, 360.0));
761        tests.check_sector(CircularSector::from_degrees(1.0, 360.0));
762        tests.check_segment(CircularSegment::from_degrees(1.0, 360.0));
763    }
764}
765
766/// An ellipse primitive, which is like a circle, but the width and height can be different
767#[derive(Clone, Copy, Debug, PartialEq)]
768#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
769#[cfg_attr(
770    feature = "bevy_reflect",
771    derive(Reflect),
772    reflect(Debug, PartialEq, Default)
773)]
774#[cfg_attr(
775    all(feature = "serialize", feature = "bevy_reflect"),
776    reflect(Serialize, Deserialize)
777)]
778pub struct Ellipse {
779    /// Half of the width and height of the ellipse.
780    ///
781    /// This corresponds to the two perpendicular radii defining the ellipse.
782    pub half_size: Vec2,
783}
784impl Primitive2d for Ellipse {}
785
786impl Default for Ellipse {
787    /// Returns the default [`Ellipse`] with a half-width of `1.0` and a half-height of `0.5`.
788    fn default() -> Self {
789        Self {
790            half_size: Vec2::new(1.0, 0.5),
791        }
792    }
793}
794
795impl Ellipse {
796    /// Create a new `Ellipse` from half of its width and height.
797    ///
798    /// This corresponds to the two perpendicular radii defining the ellipse.
799    #[inline(always)]
800    pub const fn new(half_width: f32, half_height: f32) -> Self {
801        Self {
802            half_size: Vec2::new(half_width, half_height),
803        }
804    }
805
806    /// Create a new `Ellipse` from a given full size.
807    ///
808    /// `size.x` is the diameter along the X axis, and `size.y` is the diameter along the Y axis.
809    #[inline(always)]
810    pub fn from_size(size: Vec2) -> Self {
811        Self {
812            half_size: size / 2.0,
813        }
814    }
815
816    #[inline(always)]
817    /// Returns the [eccentricity](https://en.wikipedia.org/wiki/Eccentricity_(mathematics)) of the ellipse.
818    /// It can be thought of as a measure of how "stretched" or elongated the ellipse is.
819    ///
820    /// The value should be in the range [0, 1), where 0 represents a circle, and 1 represents a parabola.
821    pub fn eccentricity(&self) -> f32 {
822        let a = self.semi_major();
823        let b = self.semi_minor();
824
825        (a * a - b * b).sqrt() / a
826    }
827
828    #[inline(always)]
829    /// Get the focal length of the ellipse. This corresponds to the distance between one of the foci and the center of the ellipse.
830    ///
831    /// The focal length of an ellipse is related to its eccentricity by `eccentricity = focal_length / semi_major`
832    pub fn focal_length(&self) -> f32 {
833        let a = self.semi_major();
834        let b = self.semi_minor();
835
836        (a * a - b * b).sqrt()
837    }
838
839    /// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse.
840    #[inline(always)]
841    pub fn semi_major(&self) -> f32 {
842        self.half_size.max_element()
843    }
844
845    /// Returns the length of the semi-minor axis. This corresponds to the shortest radius of the ellipse.
846    #[inline(always)]
847    pub fn semi_minor(&self) -> f32 {
848        self.half_size.min_element()
849    }
850}
851
852impl Measured2d for Ellipse {
853    /// Get the area of the ellipse
854    #[inline(always)]
855    fn area(&self) -> f32 {
856        PI * self.half_size.x * self.half_size.y
857    }
858
859    #[inline(always)]
860    /// Get an approximation for the perimeter or circumference of the ellipse.
861    ///
862    /// The approximation is reasonably precise with a relative error less than 0.007%, getting more precise as the eccentricity of the ellipse decreases.
863    fn perimeter(&self) -> f32 {
864        let a = self.semi_major();
865        let b = self.semi_minor();
866
867        // In the case that `a == b`, the ellipse is a circle
868        if a / b - 1. < 1e-5 {
869            return PI * (a + b);
870        };
871
872        // In the case that `a` is much larger than `b`, the ellipse is a line
873        if a / b > 1e4 {
874            return 4. * a;
875        };
876
877        // These values are  the result of (0.5 choose n)^2 where n is the index in the array
878        // They could be calculated on the fly but hardcoding them yields more accurate and faster results
879        // because the actual calculation for these values involves factorials and numbers > 10^23
880        const BINOMIAL_COEFFICIENTS: [f32; 21] = [
881            1.,
882            0.25,
883            0.015625,
884            0.00390625,
885            0.0015258789,
886            0.00074768066,
887            0.00042057037,
888            0.00025963783,
889            0.00017140154,
890            0.000119028846,
891            0.00008599834,
892            0.00006414339,
893            0.000049109784,
894            0.000038430585,
895            0.000030636627,
896            0.000024815668,
897            0.000020380836,
898            0.000016942893,
899            0.000014236736,
900            0.000012077564,
901            0.000010333865,
902        ];
903
904        // The algorithm used here is the Gauss-Kummer infinite series expansion of the elliptic integral expression for the perimeter of ellipses
905        // For more information see https://www.wolframalpha.com/input/?i=gauss-kummer+series
906        // We only use the terms up to `i == 20` for this approximation
907        let h = ((a - b) / (a + b)).squared();
908
909        PI * (a + b)
910            * (0..=20)
911                .map(|i| BINOMIAL_COEFFICIENTS[i] * ops::powf(h, i as f32))
912                .sum::<f32>()
913    }
914}
915
916/// A primitive shape formed by the region between two circles, also known as a ring.
917#[derive(Clone, Copy, Debug, PartialEq)]
918#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
919#[cfg_attr(
920    feature = "bevy_reflect",
921    derive(Reflect),
922    reflect(Debug, PartialEq, Default)
923)]
924#[cfg_attr(
925    all(feature = "serialize", feature = "bevy_reflect"),
926    reflect(Serialize, Deserialize)
927)]
928#[doc(alias = "Ring")]
929pub struct Annulus {
930    /// The inner circle of the annulus
931    pub inner_circle: Circle,
932    /// The outer circle of the annulus
933    pub outer_circle: Circle,
934}
935impl Primitive2d for Annulus {}
936
937impl Default for Annulus {
938    /// Returns the default [`Annulus`] with radii of `0.5` and `1.0`.
939    fn default() -> Self {
940        Self {
941            inner_circle: Circle::new(0.5),
942            outer_circle: Circle::new(1.0),
943        }
944    }
945}
946
947impl Annulus {
948    /// Create a new [`Annulus`] from the radii of the inner and outer circle
949    #[inline(always)]
950    pub const fn new(inner_radius: f32, outer_radius: f32) -> Self {
951        Self {
952            inner_circle: Circle::new(inner_radius),
953            outer_circle: Circle::new(outer_radius),
954        }
955    }
956
957    /// Get the diameter of the annulus
958    #[inline(always)]
959    pub fn diameter(&self) -> f32 {
960        self.outer_circle.diameter()
961    }
962
963    /// Get the thickness of the annulus
964    #[inline(always)]
965    pub fn thickness(&self) -> f32 {
966        self.outer_circle.radius - self.inner_circle.radius
967    }
968
969    /// Finds the point on the annulus that is closest to the given `point`:
970    ///
971    /// - If the point is outside of the annulus completely, the returned point will be on the outer perimeter.
972    /// - If the point is inside of the inner circle (hole) of the annulus, the returned point will be on the inner perimeter.
973    /// - Otherwise, the returned point is overlapping the annulus and returned as is.
974    #[inline(always)]
975    pub fn closest_point(&self, point: Vec2) -> Vec2 {
976        let distance_squared = point.length_squared();
977
978        if self.inner_circle.radius.squared() <= distance_squared {
979            if distance_squared <= self.outer_circle.radius.squared() {
980                // The point is inside the annulus.
981                point
982            } else {
983                // The point is outside the annulus and closer to the outer perimeter.
984                // Find the closest point on the perimeter of the annulus.
985                let dir_to_point = point / distance_squared.sqrt();
986                self.outer_circle.radius * dir_to_point
987            }
988        } else {
989            // The point is outside the annulus and closer to the inner perimeter.
990            // Find the closest point on the perimeter of the annulus.
991            let dir_to_point = point / distance_squared.sqrt();
992            self.inner_circle.radius * dir_to_point
993        }
994    }
995}
996
997impl Measured2d for Annulus {
998    /// Get the area of the annulus
999    #[inline(always)]
1000    fn area(&self) -> f32 {
1001        PI * (self.outer_circle.radius.squared() - self.inner_circle.radius.squared())
1002    }
1003
1004    /// Get the perimeter or circumference of the annulus,
1005    /// which is the sum of the perimeters of the inner and outer circles.
1006    #[inline(always)]
1007    #[doc(alias = "circumference")]
1008    fn perimeter(&self) -> f32 {
1009        2.0 * PI * (self.outer_circle.radius + self.inner_circle.radius)
1010    }
1011}
1012
1013/// A rhombus primitive, also known as a diamond shape.
1014/// A four sided polygon, centered on the origin, where opposite sides are parallel but without
1015/// requiring right angles.
1016#[derive(Clone, Copy, Debug, PartialEq)]
1017#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1018#[cfg_attr(
1019    feature = "bevy_reflect",
1020    derive(Reflect),
1021    reflect(Debug, PartialEq, Default)
1022)]
1023#[cfg_attr(
1024    all(feature = "serialize", feature = "bevy_reflect"),
1025    reflect(Serialize, Deserialize)
1026)]
1027#[doc(alias = "Diamond")]
1028pub struct Rhombus {
1029    /// Size of the horizontal and vertical diagonals of the rhombus
1030    pub half_diagonals: Vec2,
1031}
1032impl Primitive2d for Rhombus {}
1033
1034impl Default for Rhombus {
1035    /// Returns the default [`Rhombus`] with a half-horizontal and half-vertical diagonal of `0.5`.
1036    fn default() -> Self {
1037        Self {
1038            half_diagonals: Vec2::splat(0.5),
1039        }
1040    }
1041}
1042
1043impl Rhombus {
1044    /// Create a new `Rhombus` from a vertical and horizontal diagonal sizes.
1045    #[inline(always)]
1046    pub fn new(horizontal_diagonal: f32, vertical_diagonal: f32) -> Self {
1047        Self {
1048            half_diagonals: Vec2::new(horizontal_diagonal / 2.0, vertical_diagonal / 2.0),
1049        }
1050    }
1051
1052    /// Create a new `Rhombus` from a side length with all inner angles equal.
1053    #[inline(always)]
1054    pub fn from_side(side: f32) -> Self {
1055        Self {
1056            half_diagonals: Vec2::splat(side * FRAC_1_SQRT_2),
1057        }
1058    }
1059
1060    /// Create a new `Rhombus` from a given inradius with all inner angles equal.
1061    #[inline(always)]
1062    pub fn from_inradius(inradius: f32) -> Self {
1063        let half_diagonal = inradius * 2.0 / core::f32::consts::SQRT_2;
1064        Self {
1065            half_diagonals: Vec2::new(half_diagonal, half_diagonal),
1066        }
1067    }
1068
1069    /// Get the length of each side of the rhombus
1070    #[inline(always)]
1071    pub fn side(&self) -> f32 {
1072        self.half_diagonals.length()
1073    }
1074
1075    /// Get the radius of the circumcircle on which all vertices
1076    /// of the rhombus lie
1077    #[inline(always)]
1078    pub fn circumradius(&self) -> f32 {
1079        self.half_diagonals.x.max(self.half_diagonals.y)
1080    }
1081
1082    /// Get the radius of the largest circle that can
1083    /// be drawn within the rhombus
1084    #[inline(always)]
1085    #[doc(alias = "apothem")]
1086    pub fn inradius(&self) -> f32 {
1087        let side = self.side();
1088        if side == 0.0 {
1089            0.0
1090        } else {
1091            (self.half_diagonals.x * self.half_diagonals.y) / side
1092        }
1093    }
1094
1095    /// Finds the point on the rhombus that is closest to the given `point`.
1096    ///
1097    /// If the point is outside the rhombus, the returned point will be on the perimeter of the rhombus.
1098    /// Otherwise, it will be inside the rhombus and returned as is.
1099    #[inline(always)]
1100    pub fn closest_point(&self, point: Vec2) -> Vec2 {
1101        // Fold the problem into the positive quadrant
1102        let point_abs = point.abs();
1103        let half_diagonals = self.half_diagonals.abs(); // to ensure correct sign
1104
1105        // The unnormalised normal vector perpendicular to the side of the rhombus
1106        let normal = Vec2::new(half_diagonals.y, half_diagonals.x);
1107        let normal_magnitude_squared = normal.length_squared();
1108        if normal_magnitude_squared == 0.0 {
1109            return Vec2::ZERO; // A null Rhombus has only one point anyway.
1110        }
1111
1112        // The last term corresponds to normal.dot(rhombus_vertex)
1113        let distance_unnormalised = normal.dot(point_abs) - half_diagonals.x * half_diagonals.y;
1114
1115        // The point is already inside so we simply return it.
1116        if distance_unnormalised <= 0.0 {
1117            return point;
1118        }
1119
1120        // Clamp the point to the edge
1121        let mut result = point_abs - normal * distance_unnormalised / normal_magnitude_squared;
1122
1123        // Clamp the point back to the positive quadrant
1124        // if it's outside, it needs to be clamped to either vertex
1125        if result.x <= 0.0 {
1126            result = Vec2::new(0.0, half_diagonals.y);
1127        } else if result.y <= 0.0 {
1128            result = Vec2::new(half_diagonals.x, 0.0);
1129        }
1130
1131        // Finally, we restore the signs of the original vector
1132        result.copysign(point)
1133    }
1134}
1135
1136impl Measured2d for Rhombus {
1137    /// Get the area of the rhombus
1138    #[inline(always)]
1139    fn area(&self) -> f32 {
1140        2.0 * self.half_diagonals.x * self.half_diagonals.y
1141    }
1142
1143    /// Get the perimeter of the rhombus
1144    #[inline(always)]
1145    fn perimeter(&self) -> f32 {
1146        4.0 * self.side()
1147    }
1148}
1149
1150/// An unbounded plane in 2D space. It forms a separating surface through the origin,
1151/// stretching infinitely far
1152#[derive(Clone, Copy, Debug, PartialEq)]
1153#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1154#[cfg_attr(
1155    feature = "bevy_reflect",
1156    derive(Reflect),
1157    reflect(Debug, PartialEq, Default)
1158)]
1159#[cfg_attr(
1160    all(feature = "serialize", feature = "bevy_reflect"),
1161    reflect(Serialize, Deserialize)
1162)]
1163pub struct Plane2d {
1164    /// The normal of the plane. The plane will be placed perpendicular to this direction
1165    pub normal: Dir2,
1166}
1167impl Primitive2d for Plane2d {}
1168
1169impl Default for Plane2d {
1170    /// Returns the default [`Plane2d`] with a normal pointing in the `+Y` direction.
1171    fn default() -> Self {
1172        Self { normal: Dir2::Y }
1173    }
1174}
1175
1176impl Plane2d {
1177    /// Create a new `Plane2d` from a normal
1178    ///
1179    /// # Panics
1180    ///
1181    /// Panics if the given `normal` is zero (or very close to zero), or non-finite.
1182    #[inline(always)]
1183    pub fn new(normal: Vec2) -> Self {
1184        Self {
1185            normal: Dir2::new(normal).expect("normal must be nonzero and finite"),
1186        }
1187    }
1188}
1189
1190/// An infinite line going through the origin along a direction in 2D space.
1191///
1192/// For a finite line: [`Segment2d`]
1193#[derive(Clone, Copy, Debug, PartialEq)]
1194#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1195#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
1196#[cfg_attr(
1197    all(feature = "serialize", feature = "bevy_reflect"),
1198    reflect(Serialize, Deserialize)
1199)]
1200pub struct Line2d {
1201    /// The direction of the line. The line extends infinitely in both the given direction
1202    /// and its opposite direction
1203    pub direction: Dir2,
1204}
1205impl Primitive2d for Line2d {}
1206
1207/// A segment of a line going through the origin along a direction in 2D space.
1208#[derive(Clone, Copy, Debug, PartialEq)]
1209#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1210#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
1211#[cfg_attr(
1212    all(feature = "serialize", feature = "bevy_reflect"),
1213    reflect(Serialize, Deserialize)
1214)]
1215#[doc(alias = "LineSegment2d")]
1216pub struct Segment2d {
1217    /// The direction of the line segment
1218    pub direction: Dir2,
1219    /// Half the length of the line segment. The segment extends by this amount in both
1220    /// the given direction and its opposite direction
1221    pub half_length: f32,
1222}
1223impl Primitive2d for Segment2d {}
1224
1225impl Segment2d {
1226    /// Create a new `Segment2d` from a direction and full length of the segment
1227    #[inline(always)]
1228    pub fn new(direction: Dir2, length: f32) -> Self {
1229        Self {
1230            direction,
1231            half_length: length / 2.0,
1232        }
1233    }
1234
1235    /// Create a new `Segment2d` from its endpoints and compute its geometric center
1236    ///
1237    /// # Panics
1238    ///
1239    /// Panics if `point1 == point2`
1240    #[inline(always)]
1241    pub fn from_points(point1: Vec2, point2: Vec2) -> (Self, Vec2) {
1242        let diff = point2 - point1;
1243        let length = diff.length();
1244
1245        (
1246            // We are dividing by the length here, so the vector is normalized.
1247            Self::new(Dir2::new_unchecked(diff / length), length),
1248            (point1 + point2) / 2.,
1249        )
1250    }
1251
1252    /// Get the position of the first point on the line segment
1253    #[inline(always)]
1254    pub fn point1(&self) -> Vec2 {
1255        *self.direction * -self.half_length
1256    }
1257
1258    /// Get the position of the second point on the line segment
1259    #[inline(always)]
1260    pub fn point2(&self) -> Vec2 {
1261        *self.direction * self.half_length
1262    }
1263}
1264
1265/// A series of connected line segments in 2D space.
1266///
1267/// For a version without generics: [`BoxedPolyline2d`]
1268#[derive(Clone, Debug, PartialEq)]
1269#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1270#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
1271#[cfg_attr(
1272    all(feature = "serialize", feature = "bevy_reflect"),
1273    reflect(Serialize, Deserialize)
1274)]
1275pub struct Polyline2d<const N: usize> {
1276    /// The vertices of the polyline
1277    #[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
1278    pub vertices: [Vec2; N],
1279}
1280impl<const N: usize> Primitive2d for Polyline2d<N> {}
1281
1282impl<const N: usize> FromIterator<Vec2> for Polyline2d<N> {
1283    fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {
1284        let mut vertices: [Vec2; N] = [Vec2::ZERO; N];
1285
1286        for (index, i) in iter.into_iter().take(N).enumerate() {
1287            vertices[index] = i;
1288        }
1289        Self { vertices }
1290    }
1291}
1292
1293impl<const N: usize> Polyline2d<N> {
1294    /// Create a new `Polyline2d` from its vertices
1295    pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
1296        Self::from_iter(vertices)
1297    }
1298}
1299
1300/// A series of connected line segments in 2D space, allocated on the heap
1301/// in a `Box<[Vec2]>`.
1302///
1303/// For a version without alloc: [`Polyline2d`]
1304#[derive(Clone, Debug, PartialEq)]
1305#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1306pub struct BoxedPolyline2d {
1307    /// The vertices of the polyline
1308    pub vertices: Box<[Vec2]>,
1309}
1310impl Primitive2d for BoxedPolyline2d {}
1311
1312impl FromIterator<Vec2> for BoxedPolyline2d {
1313    fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {
1314        let vertices: Vec<Vec2> = iter.into_iter().collect();
1315        Self {
1316            vertices: vertices.into_boxed_slice(),
1317        }
1318    }
1319}
1320
1321impl BoxedPolyline2d {
1322    /// Create a new `BoxedPolyline2d` from its vertices
1323    pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
1324        Self::from_iter(vertices)
1325    }
1326}
1327
1328/// A triangle in 2D space
1329#[derive(Clone, Copy, Debug, PartialEq)]
1330#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1331#[cfg_attr(
1332    feature = "bevy_reflect",
1333    derive(Reflect),
1334    reflect(Debug, PartialEq, Default)
1335)]
1336#[cfg_attr(
1337    all(feature = "serialize", feature = "bevy_reflect"),
1338    reflect(Serialize, Deserialize)
1339)]
1340pub struct Triangle2d {
1341    /// The vertices of the triangle
1342    pub vertices: [Vec2; 3],
1343}
1344impl Primitive2d for Triangle2d {}
1345
1346impl Default for Triangle2d {
1347    /// Returns the default [`Triangle2d`] with the vertices `[0.0, 0.5]`, `[-0.5, -0.5]`, and `[0.5, -0.5]`.
1348    fn default() -> Self {
1349        Self {
1350            vertices: [Vec2::Y * 0.5, Vec2::new(-0.5, -0.5), Vec2::new(0.5, -0.5)],
1351        }
1352    }
1353}
1354
1355impl Triangle2d {
1356    /// Create a new `Triangle2d` from points `a`, `b`, and `c`
1357    #[inline(always)]
1358    pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {
1359        Self {
1360            vertices: [a, b, c],
1361        }
1362    }
1363
1364    /// Get the [`WindingOrder`] of the triangle
1365    #[inline(always)]
1366    #[doc(alias = "orientation")]
1367    pub fn winding_order(&self) -> WindingOrder {
1368        let [a, b, c] = self.vertices;
1369        let area = (b - a).perp_dot(c - a);
1370        if area > f32::EPSILON {
1371            WindingOrder::CounterClockwise
1372        } else if area < -f32::EPSILON {
1373            WindingOrder::Clockwise
1374        } else {
1375            WindingOrder::Invalid
1376        }
1377    }
1378
1379    /// Compute the circle passing through all three vertices of the triangle.
1380    /// The vector in the returned tuple is the circumcenter.
1381    pub fn circumcircle(&self) -> (Circle, Vec2) {
1382        // We treat the triangle as translated so that vertex A is at the origin. This simplifies calculations.
1383        //
1384        //     A = (0, 0)
1385        //        *
1386        //       / \
1387        //      /   \
1388        //     /     \
1389        //    /       \
1390        //   /    U    \
1391        //  /           \
1392        // *-------------*
1393        // B             C
1394
1395        let a = self.vertices[0];
1396        let (b, c) = (self.vertices[1] - a, self.vertices[2] - a);
1397        let b_length_sq = b.length_squared();
1398        let c_length_sq = c.length_squared();
1399
1400        // Reference: https://en.wikipedia.org/wiki/Circumcircle#Cartesian_coordinates_2
1401        let inv_d = (2.0 * (b.x * c.y - b.y * c.x)).recip();
1402        let ux = inv_d * (c.y * b_length_sq - b.y * c_length_sq);
1403        let uy = inv_d * (b.x * c_length_sq - c.x * b_length_sq);
1404        let u = Vec2::new(ux, uy);
1405
1406        // Compute true circumcenter and circumradius, adding the tip coordinate so that
1407        // A is translated back to its actual coordinate.
1408        let center = u + a;
1409        let radius = u.length();
1410
1411        (Circle { radius }, center)
1412    }
1413
1414    /// Checks if the triangle is degenerate, meaning it has zero area.
1415    ///
1416    /// A triangle is degenerate if the cross product of the vectors `ab` and `ac` has a length less than `10e-7`.
1417    /// This indicates that the three vertices are collinear or nearly collinear.
1418    #[inline(always)]
1419    pub fn is_degenerate(&self) -> bool {
1420        let [a, b, c] = self.vertices;
1421        let ab = (b - a).extend(0.);
1422        let ac = (c - a).extend(0.);
1423        ab.cross(ac).length() < 10e-7
1424    }
1425
1426    /// Checks if the triangle is acute, meaning all angles are less than 90 degrees
1427    #[inline(always)]
1428    pub fn is_acute(&self) -> bool {
1429        let [a, b, c] = self.vertices;
1430        let ab = b - a;
1431        let bc = c - b;
1432        let ca = a - c;
1433
1434        // a^2 + b^2 < c^2 for an acute triangle
1435        let mut side_lengths = [
1436            ab.length_squared(),
1437            bc.length_squared(),
1438            ca.length_squared(),
1439        ];
1440        side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
1441        side_lengths[0] + side_lengths[1] > side_lengths[2]
1442    }
1443
1444    /// Checks if the triangle is obtuse, meaning one angle is greater than 90 degrees
1445    #[inline(always)]
1446    pub fn is_obtuse(&self) -> bool {
1447        let [a, b, c] = self.vertices;
1448        let ab = b - a;
1449        let bc = c - b;
1450        let ca = a - c;
1451
1452        // a^2 + b^2 > c^2 for an obtuse triangle
1453        let mut side_lengths = [
1454            ab.length_squared(),
1455            bc.length_squared(),
1456            ca.length_squared(),
1457        ];
1458        side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
1459        side_lengths[0] + side_lengths[1] < side_lengths[2]
1460    }
1461
1462    /// Reverse the [`WindingOrder`] of the triangle
1463    /// by swapping the first and last vertices.
1464    #[inline(always)]
1465    pub fn reverse(&mut self) {
1466        self.vertices.swap(0, 2);
1467    }
1468
1469    /// This triangle but reversed.
1470    #[inline(always)]
1471    #[must_use]
1472    pub fn reversed(mut self) -> Self {
1473        self.reverse();
1474        self
1475    }
1476}
1477
1478impl Measured2d for Triangle2d {
1479    /// Get the area of the triangle
1480    #[inline(always)]
1481    fn area(&self) -> f32 {
1482        let [a, b, c] = self.vertices;
1483        (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)).abs() / 2.0
1484    }
1485
1486    /// Get the perimeter of the triangle
1487    #[inline(always)]
1488    fn perimeter(&self) -> f32 {
1489        let [a, b, c] = self.vertices;
1490
1491        let ab = a.distance(b);
1492        let bc = b.distance(c);
1493        let ca = c.distance(a);
1494
1495        ab + bc + ca
1496    }
1497}
1498
1499/// A rectangle primitive, which is like a square, except that the width and height can be different
1500#[derive(Clone, Copy, Debug, PartialEq)]
1501#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1502#[cfg_attr(
1503    feature = "bevy_reflect",
1504    derive(Reflect),
1505    reflect(Debug, PartialEq, Default)
1506)]
1507#[cfg_attr(
1508    all(feature = "serialize", feature = "bevy_reflect"),
1509    reflect(Serialize, Deserialize)
1510)]
1511#[doc(alias = "Quad")]
1512pub struct Rectangle {
1513    /// Half of the width and height of the rectangle
1514    pub half_size: Vec2,
1515}
1516impl Primitive2d for Rectangle {}
1517
1518impl Default for Rectangle {
1519    /// Returns the default [`Rectangle`] with a half-width and half-height of `0.5`.
1520    fn default() -> Self {
1521        Self {
1522            half_size: Vec2::splat(0.5),
1523        }
1524    }
1525}
1526
1527impl Rectangle {
1528    /// Create a new `Rectangle` from a full width and height
1529    #[inline(always)]
1530    pub fn new(width: f32, height: f32) -> Self {
1531        Self::from_size(Vec2::new(width, height))
1532    }
1533
1534    /// Create a new `Rectangle` from a given full size
1535    #[inline(always)]
1536    pub fn from_size(size: Vec2) -> Self {
1537        Self {
1538            half_size: size / 2.0,
1539        }
1540    }
1541
1542    /// Create a new `Rectangle` from two corner points
1543    #[inline(always)]
1544    pub fn from_corners(point1: Vec2, point2: Vec2) -> Self {
1545        Self {
1546            half_size: (point2 - point1).abs() / 2.0,
1547        }
1548    }
1549
1550    /// Create a `Rectangle` from a single length.
1551    /// The resulting `Rectangle` will be the same size in every direction.
1552    #[inline(always)]
1553    pub fn from_length(length: f32) -> Self {
1554        Self {
1555            half_size: Vec2::splat(length / 2.0),
1556        }
1557    }
1558
1559    /// Get the size of the rectangle
1560    #[inline(always)]
1561    pub fn size(&self) -> Vec2 {
1562        2.0 * self.half_size
1563    }
1564
1565    /// Finds the point on the rectangle that is closest to the given `point`.
1566    ///
1567    /// If the point is outside the rectangle, the returned point will be on the perimeter of the rectangle.
1568    /// Otherwise, it will be inside the rectangle and returned as is.
1569    #[inline(always)]
1570    pub fn closest_point(&self, point: Vec2) -> Vec2 {
1571        // Clamp point coordinates to the rectangle
1572        point.clamp(-self.half_size, self.half_size)
1573    }
1574}
1575
1576impl Measured2d for Rectangle {
1577    /// Get the area of the rectangle
1578    #[inline(always)]
1579    fn area(&self) -> f32 {
1580        4.0 * self.half_size.x * self.half_size.y
1581    }
1582
1583    /// Get the perimeter of the rectangle
1584    #[inline(always)]
1585    fn perimeter(&self) -> f32 {
1586        4.0 * (self.half_size.x + self.half_size.y)
1587    }
1588}
1589
1590/// A polygon with N vertices.
1591///
1592/// For a version without generics: [`BoxedPolygon`]
1593#[derive(Clone, Debug, PartialEq)]
1594#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1595#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
1596#[cfg_attr(
1597    all(feature = "serialize", feature = "bevy_reflect"),
1598    reflect(Serialize, Deserialize)
1599)]
1600pub struct Polygon<const N: usize> {
1601    /// The vertices of the `Polygon`
1602    #[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
1603    pub vertices: [Vec2; N],
1604}
1605impl<const N: usize> Primitive2d for Polygon<N> {}
1606
1607impl<const N: usize> FromIterator<Vec2> for Polygon<N> {
1608    fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {
1609        let mut vertices: [Vec2; N] = [Vec2::ZERO; N];
1610
1611        for (index, i) in iter.into_iter().take(N).enumerate() {
1612            vertices[index] = i;
1613        }
1614        Self { vertices }
1615    }
1616}
1617
1618impl<const N: usize> Polygon<N> {
1619    /// Create a new `Polygon` from its vertices
1620    pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
1621        Self::from_iter(vertices)
1622    }
1623}
1624
1625impl<const N: usize> From<ConvexPolygon<N>> for Polygon<N> {
1626    fn from(val: ConvexPolygon<N>) -> Self {
1627        Polygon {
1628            vertices: val.vertices,
1629        }
1630    }
1631}
1632
1633/// A convex polygon with `N` vertices.
1634#[derive(Clone, Debug, PartialEq)]
1635#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1636#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
1637#[cfg_attr(
1638    all(feature = "serialize", feature = "bevy_reflect"),
1639    reflect(Serialize, Deserialize)
1640)]
1641pub struct ConvexPolygon<const N: usize> {
1642    /// The vertices of the [`ConvexPolygon`].
1643    #[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
1644    vertices: [Vec2; N],
1645}
1646impl<const N: usize> Primitive2d for ConvexPolygon<N> {}
1647
1648/// An error that happens when creating a [`ConvexPolygon`].
1649#[derive(Error, Display, Debug, Clone)]
1650pub enum ConvexPolygonError {
1651    /// The created polygon is not convex.
1652    #[display("The created polygon is not convex")]
1653    Concave,
1654}
1655
1656impl<const N: usize> ConvexPolygon<N> {
1657    fn triangle_winding_order(
1658        &self,
1659        a_index: usize,
1660        b_index: usize,
1661        c_index: usize,
1662    ) -> WindingOrder {
1663        let a = self.vertices[a_index];
1664        let b = self.vertices[b_index];
1665        let c = self.vertices[c_index];
1666        Triangle2d::new(a, b, c).winding_order()
1667    }
1668
1669    /// Create a [`ConvexPolygon`] from its `vertices`.
1670    ///
1671    /// # Errors
1672    ///
1673    /// Returns [`ConvexPolygonError::Concave`] if the `vertices` do not form a convex polygon.
1674    pub fn new(vertices: [Vec2; N]) -> Result<Self, ConvexPolygonError> {
1675        let polygon = Self::new_unchecked(vertices);
1676        let ref_winding_order = polygon.triangle_winding_order(N - 1, 0, 1);
1677        for i in 1..N {
1678            let winding_order = polygon.triangle_winding_order(i - 1, i, (i + 1) % N);
1679            if winding_order != ref_winding_order {
1680                return Err(ConvexPolygonError::Concave);
1681            }
1682        }
1683        Ok(polygon)
1684    }
1685
1686    /// Create a [`ConvexPolygon`] from its `vertices`, without checks.
1687    /// Use this version only if you know that the `vertices` make up a convex polygon.
1688    #[inline(always)]
1689    pub fn new_unchecked(vertices: [Vec2; N]) -> Self {
1690        Self { vertices }
1691    }
1692
1693    /// Get the vertices of this polygon
1694    #[inline(always)]
1695    pub fn vertices(&self) -> &[Vec2; N] {
1696        &self.vertices
1697    }
1698}
1699
1700impl<const N: usize> TryFrom<Polygon<N>> for ConvexPolygon<N> {
1701    type Error = ConvexPolygonError;
1702
1703    fn try_from(val: Polygon<N>) -> Result<Self, Self::Error> {
1704        ConvexPolygon::new(val.vertices)
1705    }
1706}
1707
1708/// A polygon with a variable number of vertices, allocated on the heap
1709/// in a `Box<[Vec2]>`.
1710///
1711/// For a version without alloc: [`Polygon`]
1712#[derive(Clone, Debug, PartialEq)]
1713#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1714pub struct BoxedPolygon {
1715    /// The vertices of the `BoxedPolygon`
1716    pub vertices: Box<[Vec2]>,
1717}
1718impl Primitive2d for BoxedPolygon {}
1719
1720impl FromIterator<Vec2> for BoxedPolygon {
1721    fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {
1722        let vertices: Vec<Vec2> = iter.into_iter().collect();
1723        Self {
1724            vertices: vertices.into_boxed_slice(),
1725        }
1726    }
1727}
1728
1729impl BoxedPolygon {
1730    /// Create a new `BoxedPolygon` from its vertices
1731    pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
1732        Self::from_iter(vertices)
1733    }
1734}
1735
1736/// A polygon centered on the origin where all vertices lie on a circle, equally far apart.
1737#[derive(Clone, Copy, Debug, PartialEq)]
1738#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1739#[cfg_attr(
1740    feature = "bevy_reflect",
1741    derive(Reflect),
1742    reflect(Debug, PartialEq, Default)
1743)]
1744#[cfg_attr(
1745    all(feature = "serialize", feature = "bevy_reflect"),
1746    reflect(Serialize, Deserialize)
1747)]
1748pub struct RegularPolygon {
1749    /// The circumcircle on which all vertices lie
1750    pub circumcircle: Circle,
1751    /// The number of sides
1752    pub sides: u32,
1753}
1754impl Primitive2d for RegularPolygon {}
1755
1756impl Default for RegularPolygon {
1757    /// Returns the default [`RegularPolygon`] with six sides (a hexagon) and a circumradius of `0.5`.
1758    fn default() -> Self {
1759        Self {
1760            circumcircle: Circle { radius: 0.5 },
1761            sides: 6,
1762        }
1763    }
1764}
1765
1766impl RegularPolygon {
1767    /// Create a new `RegularPolygon`
1768    /// from the radius of the circumcircle and a number of sides
1769    ///
1770    /// # Panics
1771    ///
1772    /// Panics if `circumradius` is negative
1773    #[inline(always)]
1774    pub fn new(circumradius: f32, sides: u32) -> Self {
1775        assert!(
1776            circumradius.is_sign_positive(),
1777            "polygon has a negative radius"
1778        );
1779        assert!(sides > 2, "polygon has less than 3 sides");
1780
1781        Self {
1782            circumcircle: Circle {
1783                radius: circumradius,
1784            },
1785            sides,
1786        }
1787    }
1788
1789    /// Get the radius of the circumcircle on which all vertices
1790    /// of the regular polygon lie
1791    #[inline(always)]
1792    pub fn circumradius(&self) -> f32 {
1793        self.circumcircle.radius
1794    }
1795
1796    /// Get the inradius or apothem of the regular polygon.
1797    /// This is the radius of the largest circle that can
1798    /// be drawn within the polygon
1799    #[inline(always)]
1800    #[doc(alias = "apothem")]
1801    pub fn inradius(&self) -> f32 {
1802        self.circumradius() * ops::cos(PI / self.sides as f32)
1803    }
1804
1805    /// Get the length of one side of the regular polygon
1806    #[inline(always)]
1807    pub fn side_length(&self) -> f32 {
1808        2.0 * self.circumradius() * ops::sin(PI / self.sides as f32)
1809    }
1810
1811    /// Get the internal angle of the regular polygon in degrees.
1812    ///
1813    /// This is the angle formed by two adjacent sides with points
1814    /// within the angle being in the interior of the polygon
1815    #[inline(always)]
1816    pub fn internal_angle_degrees(&self) -> f32 {
1817        (self.sides - 2) as f32 / self.sides as f32 * 180.0
1818    }
1819
1820    /// Get the internal angle of the regular polygon in radians.
1821    ///
1822    /// This is the angle formed by two adjacent sides with points
1823    /// within the angle being in the interior of the polygon
1824    #[inline(always)]
1825    pub fn internal_angle_radians(&self) -> f32 {
1826        (self.sides - 2) as f32 * PI / self.sides as f32
1827    }
1828
1829    /// Get the external angle of the regular polygon in degrees.
1830    ///
1831    /// This is the angle formed by two adjacent sides with points
1832    /// within the angle being in the exterior of the polygon
1833    #[inline(always)]
1834    pub fn external_angle_degrees(&self) -> f32 {
1835        360.0 / self.sides as f32
1836    }
1837
1838    /// Get the external angle of the regular polygon in radians.
1839    ///
1840    /// This is the angle formed by two adjacent sides with points
1841    /// within the angle being in the exterior of the polygon
1842    #[inline(always)]
1843    pub fn external_angle_radians(&self) -> f32 {
1844        2.0 * PI / self.sides as f32
1845    }
1846
1847    /// Returns an iterator over the vertices of the regular polygon,
1848    /// rotated counterclockwise by the given angle in radians.
1849    ///
1850    /// With a rotation of 0, a vertex will be placed at the top `(0.0, circumradius)`.
1851    pub fn vertices(self, rotation: f32) -> impl IntoIterator<Item = Vec2> {
1852        // Add pi/2 so that the polygon has a vertex at the top (sin is 1.0 and cos is 0.0)
1853        let start_angle = rotation + FRAC_PI_2;
1854        let step = core::f32::consts::TAU / self.sides as f32;
1855
1856        (0..self.sides).map(move |i| {
1857            let theta = start_angle + i as f32 * step;
1858            let (sin, cos) = ops::sin_cos(theta);
1859            Vec2::new(cos, sin) * self.circumcircle.radius
1860        })
1861    }
1862}
1863
1864impl Measured2d for RegularPolygon {
1865    /// Get the area of the regular polygon
1866    #[inline(always)]
1867    fn area(&self) -> f32 {
1868        let angle: f32 = 2.0 * PI / (self.sides as f32);
1869        (self.sides as f32) * self.circumradius().squared() * ops::sin(angle) / 2.0
1870    }
1871
1872    /// Get the perimeter of the regular polygon.
1873    /// This is the sum of its sides
1874    #[inline(always)]
1875    fn perimeter(&self) -> f32 {
1876        self.sides as f32 * self.side_length()
1877    }
1878}
1879
1880/// A 2D capsule primitive, also known as a stadium or pill shape.
1881///
1882/// A two-dimensional capsule is defined as a neighborhood of points at a distance (radius) from a line
1883#[derive(Clone, Copy, Debug, PartialEq)]
1884#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1885#[cfg_attr(
1886    feature = "bevy_reflect",
1887    derive(Reflect),
1888    reflect(Debug, PartialEq, Default)
1889)]
1890#[cfg_attr(
1891    all(feature = "serialize", feature = "bevy_reflect"),
1892    reflect(Serialize, Deserialize)
1893)]
1894#[doc(alias = "stadium", alias = "pill")]
1895pub struct Capsule2d {
1896    /// The radius of the capsule
1897    pub radius: f32,
1898    /// Half the height of the capsule, excluding the hemicircles
1899    pub half_length: f32,
1900}
1901impl Primitive2d for Capsule2d {}
1902
1903impl Default for Capsule2d {
1904    /// Returns the default [`Capsule2d`] with a radius of `0.5` and a half-height of `0.5`,
1905    /// excluding the hemicircles.
1906    fn default() -> Self {
1907        Self {
1908            radius: 0.5,
1909            half_length: 0.5,
1910        }
1911    }
1912}
1913
1914impl Capsule2d {
1915    /// Create a new `Capsule2d` from a radius and length
1916    pub fn new(radius: f32, length: f32) -> Self {
1917        Self {
1918            radius,
1919            half_length: length / 2.0,
1920        }
1921    }
1922
1923    /// Get the part connecting the semicircular ends of the capsule as a [`Rectangle`]
1924    #[inline]
1925    pub fn to_inner_rectangle(&self) -> Rectangle {
1926        Rectangle::new(self.radius * 2.0, self.half_length * 2.0)
1927    }
1928}
1929
1930impl Measured2d for Capsule2d {
1931    /// Get the area of the capsule
1932    #[inline]
1933    fn area(&self) -> f32 {
1934        // pi*r^2 + (2r)*l
1935        PI * self.radius.squared() + self.to_inner_rectangle().area()
1936    }
1937
1938    /// Get the perimeter of the capsule
1939    #[inline]
1940    fn perimeter(&self) -> f32 {
1941        // 2pi*r + 2l
1942        2.0 * PI * self.radius + 4.0 * self.half_length
1943    }
1944}
1945
1946#[cfg(test)]
1947mod tests {
1948    // Reference values were computed by hand and/or with external tools
1949
1950    use super::*;
1951    // use approx::{assert_abs_diff_eq, assert_relative_eq};
1952
1953    #[test]
1954    fn rectangle_closest_point() {
1955        let rectangle = Rectangle::new(2.0, 2.0);
1956        assert_eq!(rectangle.closest_point(Vec2::X * 10.0), Vec2::X);
1957        assert_eq!(rectangle.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);
1958        assert_eq!(
1959            rectangle.closest_point(Vec2::new(0.25, 0.1)),
1960            Vec2::new(0.25, 0.1)
1961        );
1962    }
1963
1964    #[test]
1965    fn circle_closest_point() {
1966        let circle = Circle { radius: 1.0 };
1967        assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);
1968        assert_eq!(
1969            circle.closest_point(Vec2::NEG_ONE * 10.0),
1970            Vec2::NEG_ONE.normalize()
1971        );
1972        assert_eq!(
1973            circle.closest_point(Vec2::new(0.25, 0.1)),
1974            Vec2::new(0.25, 0.1)
1975        );
1976    }
1977
1978    #[test]
1979    fn annulus_closest_point() {
1980        let annulus = Annulus::new(1.5, 2.0);
1981        assert_eq!(annulus.closest_point(Vec2::X * 10.0), Vec2::X * 2.0);
1982        assert_eq!(
1983            annulus.closest_point(Vec2::NEG_ONE),
1984            Vec2::NEG_ONE.normalize() * 1.5
1985        );
1986        assert_eq!(
1987            annulus.closest_point(Vec2::new(1.55, 0.85)),
1988            Vec2::new(1.55, 0.85)
1989        );
1990    }
1991
1992    #[test]
1993    fn rhombus_closest_point() {
1994        let rhombus = Rhombus::new(2.0, 1.0);
1995        assert_eq!(rhombus.closest_point(Vec2::X * 10.0), Vec2::X);
1996        assert_eq!(
1997            rhombus.closest_point(Vec2::NEG_ONE * 0.2),
1998            Vec2::NEG_ONE * 0.2
1999        );
2000        assert_eq!(
2001            rhombus.closest_point(Vec2::new(-0.55, 0.35)),
2002            Vec2::new(-0.5, 0.25)
2003        );
2004
2005        let rhombus = Rhombus::new(0.0, 0.0);
2006        assert_eq!(rhombus.closest_point(Vec2::X * 10.0), Vec2::ZERO);
2007        assert_eq!(rhombus.closest_point(Vec2::NEG_ONE * 0.2), Vec2::ZERO);
2008        assert_eq!(rhombus.closest_point(Vec2::new(-0.55, 0.35)), Vec2::ZERO);
2009    }
2010
2011    #[test]
2012    fn circle_math() {
2013        let circle = Circle { radius: 3.0 };
2014        assert_eq!(circle.diameter(), 6.0, "incorrect diameter");
2015        assert_eq!(circle.area(), 28.274334, "incorrect area");
2016        assert_eq!(circle.perimeter(), 18.849556, "incorrect perimeter");
2017    }
2018
2019    #[test]
2020    fn capsule_math() {
2021        let capsule = Capsule2d::new(2.0, 9.0);
2022        assert_eq!(
2023            capsule.to_inner_rectangle(),
2024            Rectangle::new(4.0, 9.0),
2025            "rectangle wasn't created correctly from a capsule"
2026        );
2027        assert_eq!(capsule.area(), 48.566371, "incorrect area");
2028        assert_eq!(capsule.perimeter(), 30.566371, "incorrect perimeter");
2029    }
2030
2031    #[test]
2032    fn annulus_math() {
2033        let annulus = Annulus::new(2.5, 3.5);
2034        assert_eq!(annulus.diameter(), 7.0, "incorrect diameter");
2035        assert_eq!(annulus.thickness(), 1.0, "incorrect thickness");
2036        assert_eq!(annulus.area(), 18.849556, "incorrect area");
2037        assert_eq!(annulus.perimeter(), 37.699112, "incorrect perimeter");
2038    }
2039
2040    #[test]
2041    fn rhombus_math() {
2042        let rhombus = Rhombus::new(3.0, 4.0);
2043        assert_eq!(rhombus.area(), 6.0, "incorrect area");
2044        assert_eq!(rhombus.perimeter(), 10.0, "incorrect perimeter");
2045        assert_eq!(rhombus.side(), 2.5, "incorrect side");
2046        assert_eq!(rhombus.inradius(), 1.2, "incorrect inradius");
2047        assert_eq!(rhombus.circumradius(), 2.0, "incorrect circumradius");
2048        let rhombus = Rhombus::new(0.0, 0.0);
2049        assert_eq!(rhombus.area(), 0.0, "incorrect area");
2050        assert_eq!(rhombus.perimeter(), 0.0, "incorrect perimeter");
2051        assert_eq!(rhombus.side(), 0.0, "incorrect side");
2052        assert_eq!(rhombus.inradius(), 0.0, "incorrect inradius");
2053        assert_eq!(rhombus.circumradius(), 0.0, "incorrect circumradius");
2054        let rhombus = Rhombus::from_side(core::f32::consts::SQRT_2);
2055        // assert_abs_diff_eq!(rhombus.half_diagonals, Vec2::new(1.0, 1.0));
2056        // assert_abs_diff_eq!(
2057        //     rhombus.half_diagonals,
2058        //     Rhombus::from_inradius(FRAC_1_SQRT_2).half_diagonals
2059        // );
2060    }
2061
2062    #[test]
2063    fn ellipse_math() {
2064        let ellipse = Ellipse::new(3.0, 1.0);
2065        assert_eq!(ellipse.area(), 9.424778, "incorrect area");
2066
2067        assert_eq!(ellipse.eccentricity(), 0.94280905, "incorrect eccentricity");
2068
2069        let line = Ellipse::new(1., 0.);
2070        assert_eq!(line.eccentricity(), 1., "incorrect line eccentricity");
2071
2072        let circle = Ellipse::new(2., 2.);
2073        assert_eq!(circle.eccentricity(), 0., "incorrect circle eccentricity");
2074    }
2075
2076    #[test]
2077    fn ellipse_perimeter() {
2078        let circle = Ellipse::new(1., 1.);
2079        // assert_relative_eq!(circle.perimeter(), 6.2831855);
2080
2081        let line = Ellipse::new(75_000., 0.5);
2082        // assert_relative_eq!(line.perimeter(), 300_000.);
2083
2084        let ellipse = Ellipse::new(0.5, 2.);
2085        // assert_relative_eq!(ellipse.perimeter(), 8.578423);
2086
2087        let ellipse = Ellipse::new(5., 3.);
2088        // assert_relative_eq!(ellipse.perimeter(), 25.526999);
2089    }
2090
2091    #[test]
2092    fn triangle_math() {
2093        let triangle = Triangle2d::new(
2094            Vec2::new(-2.0, -1.0),
2095            Vec2::new(1.0, 4.0),
2096            Vec2::new(7.0, 0.0),
2097        );
2098        assert_eq!(triangle.area(), 21.0, "incorrect area");
2099        assert_eq!(triangle.perimeter(), 22.097439, "incorrect perimeter");
2100
2101        let degenerate_triangle =
2102            Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(0., 0.), Vec2::new(1., 0.));
2103        assert!(degenerate_triangle.is_degenerate());
2104
2105        let acute_triangle =
2106            Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 5.));
2107        let obtuse_triangle =
2108            Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 0.5));
2109
2110        assert!(acute_triangle.is_acute());
2111        assert!(!acute_triangle.is_obtuse());
2112        assert!(!obtuse_triangle.is_acute());
2113        assert!(obtuse_triangle.is_obtuse());
2114    }
2115
2116    #[test]
2117    fn triangle_winding_order() {
2118        let mut cw_triangle = Triangle2d::new(
2119            Vec2::new(0.0, 2.0),
2120            Vec2::new(-0.5, -1.2),
2121            Vec2::new(-1.0, -1.0),
2122        );
2123        assert_eq!(cw_triangle.winding_order(), WindingOrder::Clockwise);
2124
2125        let ccw_triangle = Triangle2d::new(
2126            Vec2::new(-1.0, -1.0),
2127            Vec2::new(-0.5, -1.2),
2128            Vec2::new(0.0, 2.0),
2129        );
2130        assert_eq!(ccw_triangle.winding_order(), WindingOrder::CounterClockwise);
2131
2132        // The clockwise triangle should be the same as the counterclockwise
2133        // triangle when reversed
2134        cw_triangle.reverse();
2135        assert_eq!(cw_triangle, ccw_triangle);
2136
2137        let invalid_triangle = Triangle2d::new(
2138            Vec2::new(0.0, 2.0),
2139            Vec2::new(0.0, -1.0),
2140            Vec2::new(0.0, -1.2),
2141        );
2142        assert_eq!(invalid_triangle.winding_order(), WindingOrder::Invalid);
2143    }
2144
2145    #[test]
2146    fn rectangle_math() {
2147        let rectangle = Rectangle::new(3.0, 7.0);
2148        assert_eq!(
2149            rectangle,
2150            Rectangle::from_corners(Vec2::new(-1.5, -3.5), Vec2::new(1.5, 3.5))
2151        );
2152        assert_eq!(rectangle.area(), 21.0, "incorrect area");
2153        assert_eq!(rectangle.perimeter(), 20.0, "incorrect perimeter");
2154    }
2155
2156    #[test]
2157    fn regular_polygon_math() {
2158        let polygon = RegularPolygon::new(3.0, 6);
2159        assert_eq!(polygon.inradius(), 2.598076, "incorrect inradius");
2160        assert_eq!(polygon.side_length(), 3.0, "incorrect side length");
2161        // assert_relative_eq!(polygon.area(), 23.38268, epsilon = 0.00001);
2162        assert_eq!(polygon.perimeter(), 18.0, "incorrect perimeter");
2163        assert_eq!(
2164            polygon.internal_angle_degrees(),
2165            120.0,
2166            "incorrect internal angle"
2167        );
2168        assert_eq!(
2169            polygon.internal_angle_radians(),
2170            120_f32.to_radians(),
2171            "incorrect internal angle"
2172        );
2173        assert_eq!(
2174            polygon.external_angle_degrees(),
2175            60.0,
2176            "incorrect external angle"
2177        );
2178        assert_eq!(
2179            polygon.external_angle_radians(),
2180            60_f32.to_radians(),
2181            "incorrect external angle"
2182        );
2183    }
2184
2185    #[test]
2186    fn triangle_circumcenter() {
2187        let triangle = Triangle2d::new(
2188            Vec2::new(10.0, 2.0),
2189            Vec2::new(-5.0, -3.0),
2190            Vec2::new(2.0, -1.0),
2191        );
2192        let (Circle { radius }, circumcenter) = triangle.circumcircle();
2193
2194        // Calculated with external calculator
2195        assert_eq!(radius, 98.34887);
2196        assert_eq!(circumcenter, Vec2::new(-28.5, 92.5));
2197    }
2198
2199    #[test]
2200    fn regular_polygon_vertices() {
2201        let polygon = RegularPolygon::new(1.0, 4);
2202
2203        // Regular polygons have a vertex at the top by default
2204        let mut vertices = polygon.vertices(0.0).into_iter();
2205        assert!((vertices.next().unwrap() - Vec2::Y).length() < 1e-7);
2206
2207        // Rotate by 45 degrees, forming an axis-aligned square
2208        let mut rotated_vertices = polygon.vertices(core::f32::consts::FRAC_PI_4).into_iter();
2209
2210        // Distance from the origin to the middle of a side, derived using Pythagorean theorem
2211        let side_sistance = FRAC_1_SQRT_2;
2212        assert!(
2213            (rotated_vertices.next().unwrap() - Vec2::new(-side_sistance, side_sistance)).length()
2214                < 1e-7,
2215        );
2216    }
2217}