bevy_math/primitives/
dim2.rs

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