rscad_core/objects/two_dimensional/
polygon.rs1use crate::prelude_::*;
2
3#[derive(Clone, Copy, Debug, PartialEq)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
8pub struct Polygon<const N: usize> {
9 pub radius: f64,
10}
11
12pub type Triangle = Polygon<3>;
13pub type SquarePolygon = Polygon<4>;
14pub type Pentagon = Polygon<5>;
15pub type Hexagon = Polygon<6>;
16pub type Heptagon = Polygon<7>;
17pub type Octagon = Polygon<8>;
18
19impl<const N: usize> Polygon<N> {
20 pub const fn with_radius(radius: f64) -> Self {
22 Self { radius }
23 }
24
25 pub const fn with_diameter(diameter: f64) -> Self {
27 let radius = diameter / 2.0;
28 Self::with_radius(radius)
29 }
30
31 pub fn with_side(length: f64) -> Self {
36 let radius = N as f64 / (2.0 * (core::f64::consts::PI / N as f64).sin()) * length;
37 Self::with_radius(radius)
38 }
39
40 pub fn with_apothem(apothem: f64) -> Self {
41 let radius = apothem / (core::f64::consts::PI / N as f64).cos();
42 Self::with_radius(radius)
43 }
44
45 pub fn side(&self) -> f64 {
46 2.0 * self.radius * (core::f64::consts::PI / N as f64).sin()
47 }
48
49 pub fn apothem(&self) -> f64 {
50 self.radius * (core::f64::consts::PI / N as f64).cos()
51 }
52
53 pub fn radius(&self) -> f64 {
54 self.radius
55 }
56
57 pub fn points(&self) -> core::array::IntoIter<DVec2, N> {
58 self.points_array().into_iter()
59 }
60
61 pub fn points_array(&self) -> [glam::DVec2; N] {
62 core::array::from_fn(|i| {
63 let angle = 2.0 * core::f64::consts::PI * i as f64 / N as f64;
64 glam::DVec2::new(self.radius * angle.cos(), self.radius * angle.sin())
65 })
66 }
67
68 #[allow(clippy::type_complexity)]
69 pub fn sides(
70 &self,
71 ) -> core::iter::Map<
72 itertools::CircularTupleWindows<core::array::IntoIter<DVec2, N>, (DVec2, DVec2)>,
73 impl FnMut((DVec2, DVec2)) -> [DVec2; 2],
74 > {
75 use itertools::Itertools;
76 self.points_array()
77 .into_iter()
78 .circular_tuple_windows()
79 .map(|(a, b)| [a, b])
80 }
81}
82
83impl<const N: usize> Object for Polygon<N> {}
84
85#[test]
86pub fn test_hexagon_side_and_apothem() {
87 let hexagon = Hexagon::with_radius(10.0);
88 assert_eq!(
89 hexagon.side(),
90 10.0 * (core::f64::consts::PI / 6.0).sin() * 2.0
91 );
92 assert_eq!(
93 hexagon.apothem(),
94 10.0 * (core::f64::consts::PI / 6.0).cos()
95 );
96 assert!(hexagon.apothem() - 10.0 / 2.0 * 3.0f64.sqrt() < 0.00000000000001);
97 assert!(hexagon.side() - hexagon.radius() < 0.00000000000001);
98}
99
100#[derive(Clone, Copy, Debug, PartialEq)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
103#[cfg(feature = "std")]
104pub struct PolygonDynamic {
105 pub radius: f64,
106 pub points: usize,
107}
108
109#[cfg(feature = "std")]
110impl Object for PolygonDynamic {}
111
112#[cfg(feature = "std")]
113impl PolygonDynamic {
114 pub fn new(radius: f64, points: usize) -> Self {
115 Self { radius, points }
116 }
117
118 pub fn apothem(&self) -> f64 {
119 self.radius * (core::f64::consts::PI / self.points as f64).cos()
120 }
121
122 pub fn radius(&self) -> f64 {
123 self.radius
124 }
125
126 pub fn points(&self) -> core::iter::Map<core::ops::Range<usize>, impl FnMut(usize) -> DVec2> {
127 (0..self.points).map(|i| {
128 let angle = 2.0 * core::f64::consts::PI * i as f64 / self.points as f64;
129 glam::DVec2::new(self.radius * angle.cos(), self.radius * angle.sin())
130 })
131 }
132
133 #[allow(clippy::type_complexity)]
134 pub fn sides(
135 &self,
136 ) -> core::iter::Map<
137 itertools::CircularTupleWindows<std::vec::IntoIter<DVec2>, (DVec2, DVec2)>,
138 impl FnMut((DVec2, DVec2)) -> [DVec2; 2],
139 > {
140 use itertools::Itertools;
141 self.points()
142 .collect_vec()
143 .into_iter()
144 .circular_tuple_windows()
145 .map(|(a, b)| [a, b])
146 }
147}
148
149#[cfg(feature = "std")]
150impl<const N: usize> From<Polygon<N>> for PolygonDynamic {
151 fn from(polygon: Polygon<N>) -> Self {
152 Self {
153 radius: polygon.radius,
154 points: N,
155 }
156 }
157}
158
159#[cfg(feature = "std")]
160impl<const N: usize> TryFrom<PolygonDynamic> for Polygon<N> {
161 type Error = Report<Error>;
162
163 fn try_from(value: PolygonDynamic) -> Result<Self, Self::Error> {
164 if value.points == N {
165 Ok(Polygon::with_radius(value.radius))
166 } else {
167 Err(Error)
168 .attach("Mismatched number of points")
169 .attach_with(|| format!("Expected {}, got {}", N, value.points))
170 }
171 }
172}