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#[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 pub radius: f32,
26}
27impl Primitive2d for Circle {}
28
29impl Default for Circle {
30 fn default() -> Self {
32 Self { radius: 0.5 }
33 }
34}
35
36impl Circle {
37 #[inline(always)]
39 pub const fn new(radius: f32) -> Self {
40 Self { radius }
41 }
42
43 #[inline(always)]
45 pub fn diameter(&self) -> f32 {
46 2.0 * self.radius
47 }
48
49 #[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 point
60 } else {
61 let dir_to_point = point / distance_squared.sqrt();
64 self.radius * dir_to_point
65 }
66 }
67}
68
69impl Measured2d for Circle {
70 #[inline(always)]
72 fn area(&self) -> f32 {
73 PI * self.radius.powi(2)
74 }
75
76 #[inline(always)]
78 #[doc(alias = "circumference")]
79 fn perimeter(&self) -> f32 {
80 2.0 * PI * self.radius
81 }
82}
83
84#[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 pub radius: f32,
113 pub half_angle: f32,
115}
116impl Primitive2d for Arc2d {}
117
118impl Default for Arc2d {
119 fn default() -> Self {
121 Self {
122 radius: 0.5,
123 half_angle: 2.0 * FRAC_PI_3,
124 }
125 }
126}
127
128impl Arc2d {
129 #[inline(always)]
131 pub fn new(radius: f32, half_angle: f32) -> Self {
132 Self { radius, half_angle }
133 }
134
135 #[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 #[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 #[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 #[inline(always)]
166 pub fn angle(&self) -> f32 {
167 self.half_angle * 2.0
168 }
169
170 #[inline(always)]
172 pub fn length(&self) -> f32 {
173 self.angle() * self.radius
174 }
175
176 #[inline(always)]
178 pub fn right_endpoint(&self) -> Vec2 {
179 self.radius * Vec2::from_angle(FRAC_PI_2 - self.half_angle)
180 }
181
182 #[inline(always)]
184 pub fn left_endpoint(&self) -> Vec2 {
185 self.radius * Vec2::from_angle(FRAC_PI_2 + self.half_angle)
186 }
187
188 #[inline(always)]
190 pub fn endpoints(&self) -> [Vec2; 2] {
191 [self.left_endpoint(), self.right_endpoint()]
192 }
193
194 #[inline]
196 pub fn midpoint(&self) -> Vec2 {
197 self.radius * Vec2::Y
198 }
199
200 #[inline(always)]
202 pub fn half_chord_length(&self) -> f32 {
203 self.radius * f32::sin(self.half_angle)
204 }
205
206 #[inline(always)]
208 pub fn chord_length(&self) -> f32 {
209 2.0 * self.half_chord_length()
210 }
211
212 #[inline(always)]
214 pub fn chord_midpoint(&self) -> Vec2 {
215 self.apothem() * Vec2::Y
216 }
217
218 #[inline(always)]
224 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 pub fn sagitta(&self) -> f32 {
238 self.radius - self.apothem()
239 }
240
241 #[inline(always)]
245 pub fn is_minor(&self) -> bool {
246 self.half_angle <= FRAC_PI_2
247 }
248
249 #[inline(always)]
253 pub fn is_major(&self) -> bool {
254 self.half_angle >= FRAC_PI_2
255 }
256}
257
258#[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 #[cfg_attr(feature = "serialize", serde(flatten))]
280 pub arc: Arc2d,
281}
282impl Primitive2d for CircularSector {}
283
284impl Default for CircularSector {
285 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 #[inline(always)]
300 pub fn new(radius: f32, angle: f32) -> Self {
301 Self::from(Arc2d::new(radius, angle))
302 }
303
304 #[inline(always)]
306 pub fn from_radians(radius: f32, angle: f32) -> Self {
307 Self::from(Arc2d::from_radians(radius, angle))
308 }
309
310 #[inline(always)]
312 pub fn from_degrees(radius: f32, angle: f32) -> Self {
313 Self::from(Arc2d::from_degrees(radius, angle))
314 }
315
316 #[inline(always)]
320 pub fn from_turns(radius: f32, fraction: f32) -> Self {
321 Self::from(Arc2d::from_turns(radius, fraction))
322 }
323
324 #[inline(always)]
326 pub fn half_angle(&self) -> f32 {
327 self.arc.half_angle
328 }
329
330 #[inline(always)]
332 pub fn angle(&self) -> f32 {
333 self.arc.angle()
334 }
335
336 #[inline(always)]
338 pub fn radius(&self) -> f32 {
339 self.arc.radius
340 }
341
342 #[inline(always)]
344 pub fn arc_length(&self) -> f32 {
345 self.arc.length()
346 }
347
348 #[inline(always)]
352 pub fn half_chord_length(&self) -> f32 {
353 self.arc.half_chord_length()
354 }
355
356 #[inline(always)]
360 pub fn chord_length(&self) -> f32 {
361 self.arc.chord_length()
362 }
363
364 #[inline(always)]
368 pub fn chord_midpoint(&self) -> Vec2 {
369 self.arc.chord_midpoint()
370 }
371
372 #[inline(always)]
376 pub fn apothem(&self) -> f32 {
377 self.arc.apothem()
378 }
379
380 #[inline(always)]
384 pub fn sagitta(&self) -> f32 {
385 self.arc.sagitta()
386 }
387
388 #[inline(always)]
390 pub fn area(&self) -> f32 {
391 self.arc.radius.powi(2) * self.arc.half_angle
392 }
393}
394
395#[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 #[cfg_attr(feature = "serialize", serde(flatten))]
419 pub arc: Arc2d,
420}
421impl Primitive2d for CircularSegment {}
422
423impl Default for CircularSegment {
424 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 #[inline(always)]
439 pub fn new(radius: f32, angle: f32) -> Self {
440 Self::from(Arc2d::new(radius, angle))
441 }
442
443 #[inline(always)]
445 pub fn from_radians(radius: f32, angle: f32) -> Self {
446 Self::from(Arc2d::from_radians(radius, angle))
447 }
448
449 #[inline(always)]
451 pub fn from_degrees(radius: f32, angle: f32) -> Self {
452 Self::from(Arc2d::from_degrees(radius, angle))
453 }
454
455 #[inline(always)]
459 pub fn from_turns(radius: f32, fraction: f32) -> Self {
460 Self::from(Arc2d::from_turns(radius, fraction))
461 }
462
463 #[inline(always)]
465 pub fn half_angle(&self) -> f32 {
466 self.arc.half_angle
467 }
468
469 #[inline(always)]
471 pub fn angle(&self) -> f32 {
472 self.arc.angle()
473 }
474
475 #[inline(always)]
477 pub fn radius(&self) -> f32 {
478 self.arc.radius
479 }
480
481 #[inline(always)]
483 pub fn arc_length(&self) -> f32 {
484 self.arc.length()
485 }
486
487 #[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 #[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 #[inline(always)]
504 #[doc(alias = "base_midpoint")]
505 pub fn chord_midpoint(&self) -> Vec2 {
506 self.arc.chord_midpoint()
507 }
508
509 #[inline(always)]
514 pub fn apothem(&self) -> f32 {
515 self.arc.apothem()
516 }
517
518 #[inline(always)]
522 #[doc(alias = "height")]
523 pub fn sagitta(&self) -> f32 {
524 self.arc.sagitta()
525 }
526
527 #[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#[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 pub half_size: Vec2,
761}
762impl Primitive2d for Ellipse {}
763
764impl Default for Ellipse {
765 fn default() -> Self {
767 Self {
768 half_size: Vec2::new(1.0, 0.5),
769 }
770 }
771}
772
773impl Ellipse {
774 #[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 #[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 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 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 #[inline(always)]
819 pub fn semi_major(&self) -> f32 {
820 self.half_size.max_element()
821 }
822
823 #[inline(always)]
825 pub fn semi_minor(&self) -> f32 {
826 self.half_size.min_element()
827 }
828}
829
830impl Measured2d for Ellipse {
831 #[inline(always)]
833 fn area(&self) -> f32 {
834 PI * self.half_size.x * self.half_size.y
835 }
836
837 #[inline(always)]
838 fn perimeter(&self) -> f32 {
842 let a = self.semi_major();
843 let b = self.semi_minor();
844
845 if a / b - 1. < 1e-5 {
847 return PI * (a + b);
848 };
849
850 if a / b > 1e4 {
852 return 4. * a;
853 };
854
855 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 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#[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 pub inner_circle: Circle,
910 pub outer_circle: Circle,
912}
913impl Primitive2d for Annulus {}
914
915impl Default for Annulus {
916 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 #[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 #[inline(always)]
937 pub fn diameter(&self) -> f32 {
938 self.outer_circle.diameter()
939 }
940
941 #[inline(always)]
943 pub fn thickness(&self) -> f32 {
944 self.outer_circle.radius - self.inner_circle.radius
945 }
946
947 #[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 point
960 } else {
961 let dir_to_point = point / distance_squared.sqrt();
964 self.outer_circle.radius * dir_to_point
965 }
966 } else {
967 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 #[inline(always)]
978 fn area(&self) -> f32 {
979 PI * (self.outer_circle.radius.powi(2) - self.inner_circle.radius.powi(2))
980 }
981
982 #[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#[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 pub half_diagonals: Vec2,
1007}
1008impl Primitive2d for Rhombus {}
1009
1010impl Default for Rhombus {
1011 fn default() -> Self {
1013 Self {
1014 half_diagonals: Vec2::splat(0.5),
1015 }
1016 }
1017}
1018
1019impl Rhombus {
1020 #[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 #[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 #[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 #[inline(always)]
1047 pub fn side(&self) -> f32 {
1048 self.half_diagonals.length()
1049 }
1050
1051 #[inline(always)]
1054 pub fn circumradius(&self) -> f32 {
1055 self.half_diagonals.x.max(self.half_diagonals.y)
1056 }
1057
1058 #[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 #[inline(always)]
1076 pub fn closest_point(&self, point: Vec2) -> Vec2 {
1077 let point_abs = point.abs();
1079 let half_diagonals = self.half_diagonals.abs(); 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; }
1087
1088 let distance_unnormalised = normal.dot(point_abs) - half_diagonals.x * half_diagonals.y;
1090
1091 if distance_unnormalised <= 0.0 {
1093 return point;
1094 }
1095
1096 let mut result = point_abs - normal * distance_unnormalised / normal_magnitude_squared;
1098
1099 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 result.copysign(point)
1109 }
1110}
1111
1112impl Measured2d for Rhombus {
1113 #[inline(always)]
1115 fn area(&self) -> f32 {
1116 2.0 * self.half_diagonals.x * self.half_diagonals.y
1117 }
1118
1119 #[inline(always)]
1121 fn perimeter(&self) -> f32 {
1122 4.0 * self.side()
1123 }
1124}
1125
1126#[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 pub normal: Dir2,
1142}
1143impl Primitive2d for Plane2d {}
1144
1145impl Default for Plane2d {
1146 fn default() -> Self {
1148 Self { normal: Dir2::Y }
1149 }
1150}
1151
1152impl Plane2d {
1153 #[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#[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 pub direction: Dir2,
1180}
1181impl Primitive2d for Line2d {}
1182
1183#[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 pub direction: Dir2,
1195 pub half_length: f32,
1198}
1199impl Primitive2d for Segment2d {}
1200
1201impl Segment2d {
1202 #[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 #[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 Self::new(Dir2::new_unchecked(diff / length), length),
1224 (point1 + point2) / 2.,
1225 )
1226 }
1227
1228 #[inline(always)]
1230 pub fn point1(&self) -> Vec2 {
1231 *self.direction * -self.half_length
1232 }
1233
1234 #[inline(always)]
1236 pub fn point2(&self) -> Vec2 {
1237 *self.direction * self.half_length
1238 }
1239}
1240
1241#[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 #[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 pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
1272 Self::from_iter(vertices)
1273 }
1274}
1275
1276#[derive(Clone, Debug, PartialEq)]
1281#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1282pub struct BoxedPolyline2d {
1283 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 pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
1300 Self::from_iter(vertices)
1301 }
1302}
1303
1304#[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 pub vertices: [Vec2; 3],
1319}
1320impl Primitive2d for Triangle2d {}
1321
1322impl Default for Triangle2d {
1323 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 #[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 #[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 pub fn circumcircle(&self) -> (Circle, Vec2) {
1358 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 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 let center = u + a;
1385 let radius = u.length();
1386
1387 (Circle { radius }, center)
1388 }
1389
1390 #[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 #[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 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 #[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 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 #[inline(always)]
1441 pub fn reverse(&mut self) {
1442 self.vertices.swap(0, 2);
1443 }
1444
1445 #[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 #[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 #[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#[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 pub half_size: Vec2,
1491}
1492impl Primitive2d for Rectangle {}
1493
1494impl Default for Rectangle {
1495 fn default() -> Self {
1497 Self {
1498 half_size: Vec2::splat(0.5),
1499 }
1500 }
1501}
1502
1503impl Rectangle {
1504 #[inline(always)]
1506 pub fn new(width: f32, height: f32) -> Self {
1507 Self::from_size(Vec2::new(width, height))
1508 }
1509
1510 #[inline(always)]
1512 pub fn from_size(size: Vec2) -> Self {
1513 Self {
1514 half_size: size / 2.0,
1515 }
1516 }
1517
1518 #[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 #[inline(always)]
1529 pub fn from_length(length: f32) -> Self {
1530 Self {
1531 half_size: Vec2::splat(length / 2.0),
1532 }
1533 }
1534
1535 #[inline(always)]
1537 pub fn size(&self) -> Vec2 {
1538 2.0 * self.half_size
1539 }
1540
1541 #[inline(always)]
1546 pub fn closest_point(&self, point: Vec2) -> Vec2 {
1547 point.clamp(-self.half_size, self.half_size)
1549 }
1550}
1551
1552impl Measured2d for Rectangle {
1553 #[inline(always)]
1555 fn area(&self) -> f32 {
1556 4.0 * self.half_size.x * self.half_size.y
1557 }
1558
1559 #[inline(always)]
1561 fn perimeter(&self) -> f32 {
1562 4.0 * (self.half_size.x + self.half_size.y)
1563 }
1564}
1565
1566#[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 #[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 pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
1597 Self::from_iter(vertices)
1598 }
1599}
1600
1601#[derive(Clone, Debug, PartialEq)]
1606#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1607pub struct BoxedPolygon {
1608 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 pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
1625 Self::from_iter(vertices)
1626 }
1627}
1628
1629#[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 pub circumcircle: Circle,
1644 pub sides: usize,
1646}
1647impl Primitive2d for RegularPolygon {}
1648
1649impl Default for RegularPolygon {
1650 fn default() -> Self {
1652 Self {
1653 circumcircle: Circle { radius: 0.5 },
1654 sides: 6,
1655 }
1656 }
1657}
1658
1659impl RegularPolygon {
1660 #[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 #[inline(always)]
1685 pub fn circumradius(&self) -> f32 {
1686 self.circumcircle.radius
1687 }
1688
1689 #[inline(always)]
1693 #[doc(alias = "apothem")]
1694 pub fn inradius(&self) -> f32 {
1695 self.circumradius() * (PI / self.sides as f32).cos()
1696 }
1697
1698 #[inline(always)]
1700 pub fn side_length(&self) -> f32 {
1701 2.0 * self.circumradius() * (PI / self.sides as f32).sin()
1702 }
1703
1704 #[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 #[inline(always)]
1718 pub fn internal_angle_radians(&self) -> f32 {
1719 (self.sides - 2) as f32 * PI / self.sides as f32
1720 }
1721
1722 #[inline(always)]
1727 pub fn external_angle_degrees(&self) -> f32 {
1728 360.0 / self.sides as f32
1729 }
1730
1731 #[inline(always)]
1736 pub fn external_angle_radians(&self) -> f32 {
1737 2.0 * PI / self.sides as f32
1738 }
1739
1740 pub fn vertices(self, rotation: f32) -> impl IntoIterator<Item = Vec2> {
1745 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 #[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 #[inline(always)]
1768 fn perimeter(&self) -> f32 {
1769 self.sides as f32 * self.side_length()
1770 }
1771}
1772
1773#[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 pub radius: f32,
1791 pub half_length: f32,
1793}
1794impl Primitive2d for Capsule2d {}
1795
1796impl Default for Capsule2d {
1797 fn default() -> Self {
1800 Self {
1801 radius: 0.5,
1802 half_length: 0.5,
1803 }
1804 }
1805}
1806
1807impl Capsule2d {
1808 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 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 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 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 let mut vertices = polygon.vertices(0.0).into_iter();
2064 assert!((vertices.next().unwrap() - Vec2::Y).length() < 1e-7);
2065
2066 let mut rotated_vertices = polygon.vertices(std::f32::consts::FRAC_PI_4).into_iter();
2068
2069 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}