index/objects/geometry/
arc.rs1use wasm_bindgen::prelude::*;
2
3use crate::{objects::vector_object::VectorObjectBuilder, utils::{bezier::AnchorsAndHandles, point2d::{Path2D, Point2D}}};
4
5use super::tipable::Tipable;
6
7#[wasm_bindgen]
9#[derive(Clone, Debug)]
10pub struct Arc {
11 center: Point2D,
13 radius: f32,
15 start_angle: f32,
17 end_angle: f32,
19}
20
21#[wasm_bindgen]
22impl Arc {
23 #[wasm_bindgen(constructor, return_description = "An arc.")]
25 pub fn new(
26 #[wasm_bindgen(param_description = "The center point of the arc as a Point2D.")]
27 center: Point2D,
28 #[wasm_bindgen(param_description = "The radius of the arc.")]
29 radius: f32,
30 #[wasm_bindgen(param_description = "The start angle of the arc in radians.")]
31 start_angle: f32,
32 #[wasm_bindgen(param_description = "The end angle of the arc in radians.")]
33 end_angle: f32,
34 ) -> Arc {
35 Arc {
36 center,
37 radius,
38 start_angle,
39 end_angle,
40 }
41 }
42
43 #[wasm_bindgen(return_description = "A VectorObjectBuilder representing the arc.")]
45 pub fn vector_object_builder(&self, samples: Option<usize>) -> VectorObjectBuilder {
46 let samples = samples.unwrap_or(15);
47 let anchors = (0..samples)
48 .map(|i| self.start_angle + (self.end_angle - self.start_angle) * i as f32 / (samples - 1) as f32)
49 .map(|angle| Point2D::new(angle.cos(), angle.sin()))
50 .collect::<Vec<Point2D>>();
51 let dtheta = (self.end_angle - self.start_angle) / (samples - 1) as f32;
52 let tangent_vectors = anchors.iter().map(|point| {
53 Point2D::new(-point.y, point.x)
54 }).collect::<Vec<Point2D>>();
55 let factor = 4.0 / 3.0 * (dtheta / 4.0).tan();
56 let handles1 = anchors[0..samples - 1].iter().zip(tangent_vectors[0..samples - 1].iter()).map(|(point, tangent)| {
57 *point + *tangent * factor
58 }).collect::<Vec<Point2D>>();
59 let handles2 = anchors[1..samples].iter().zip(tangent_vectors[1..samples].iter()).map(|(point, tangent)| {
60 *point - *tangent * factor
61 }).collect::<Vec<Point2D>>();
62 let path = Path2D::from_anchors_and_handles(&AnchorsAndHandles::new(
63 anchors[0..samples - 1].to_vec(),
64 handles1,
65 handles2,
66 anchors[1..samples].to_vec(),
67 ).unwrap());
68 VectorObjectBuilder::default()
69 .set_path(path)
70 .scale(self.radius, self.radius, None, None)
71 .shift(self.center.x, self.center.y, None)
72 .actual_path_as_path(None, None)
73 }
74
75 #[wasm_bindgen(getter, return_description = "The center point of the arc as a Point2D.")]
77 pub fn center(&self) -> Point2D {
78 self.center
79 }
80
81 #[wasm_bindgen(getter, return_description = "The radius of the arc.")]
83 pub fn radius(&self) -> f32 {
84 self.radius
85 }
86
87 #[wasm_bindgen(getter, return_description = "The start angle of the arc in radians.")]
89 pub fn start_angle(&self) -> f32 {
90 self.start_angle
91 }
92
93 #[wasm_bindgen(getter, return_description = "The end angle of the arc in radians.")]
95 pub fn end_angle(&self) -> f32 {
96 self.end_angle
97 }
98
99 #[wasm_bindgen(return_description = "A VectorObjectBuilder representing the arc and a tip at the start of the arc.")]
101 pub fn start_tip_vector_object_builder(
102 &self,
103 #[wasm_bindgen(param_description = "The tip shape as a VectorObjectBuilder to add to the start of the arc. It must be pointing to the right and centered at (0, 0). This function will rotate and move it to the correct angle.")]
104 tip_shape: VectorObjectBuilder,
105 #[wasm_bindgen(param_description = "The number of samples to use to create the arc, by default 15.")]
106 samples: Option<usize>
107 ) -> VectorObjectBuilder {
108 let mut builder = self.vector_object_builder(samples);
109 builder = builder.add_child(self.tip_at_start(tip_shape));
110 builder
111 }
112
113 #[wasm_bindgen(return_description = "A VectorObjectBuilder representing the arc and a tip at the end of the arc.")]
115 pub fn end_tip_vector_object_builder(
116 &self,
117 #[wasm_bindgen(param_description = "The tip shape as a VectorObjectBuilder to add to the end of the arc. It must be pointing to the right and centered at (0, 0). This function will rotate and move it to the correct angle.")]
118 tip_shape: VectorObjectBuilder,
119 #[wasm_bindgen(param_description = "The number of samples to use to create the arc.")]
120 samples: Option<usize>
121 ) -> VectorObjectBuilder {
122 let mut builder = self.vector_object_builder(samples);
123 builder = builder.add_child(self.tip_at_end(tip_shape));
124 builder
125 }
126
127 #[wasm_bindgen(return_description = "A VectorObjectBuilder representing the arc and tips at both ends of the arc.")]
129 pub fn both_tips_vector_object_builder(
130 &self,
131 #[wasm_bindgen(param_description = "The tip shape as a VectorObjectBuilder to add to the start of the arc. It must be pointing to the right and centered at (0, 0). This function will rotate and move it to the correct angle.")]
132 tip_shape: VectorObjectBuilder,
133 #[wasm_bindgen(param_description = "The number of samples to use to create the arc.")]
134 samples: Option<usize>
135 ) -> VectorObjectBuilder {
136 let mut builder = self.vector_object_builder(samples);
137 builder = builder.add_child(self.tip_at_start(tip_shape.clone()));
138 builder = builder.add_child(self.tip_at_end(tip_shape));
139 builder
140 }
141
142 #[wasm_bindgen(return_description = "The Point2D on the arc at the given t value.")]
144 pub fn point_at(
145 &self,
146 #[wasm_bindgen(param_description = "The t value to evaluate the polynomial at. A number between 0 and 1.")]
147 t: f32,
148 ) -> Point2D {
149 let angle = self.start_angle + (self.end_angle - self.start_angle) * t;
150 let x = self.center.x + self.radius * angle.cos();
151 let y = self.center.y + self.radius * angle.sin();
152 Point2D::new(x, y)
153 }
154
155 #[wasm_bindgen(return_description = "The length of the arc.")]
157 pub fn length(&self) -> f32 {
158 let angle = self.end_angle - self.start_angle;
159 angle.abs() * self.radius
160 }
161}
162
163impl Tipable for Arc {
164 fn start(&self) -> Point2D {
165 self.point_at(0.0)
166 }
167
168 fn end(&self) -> Point2D {
169 self.point_at(1.0)
170 }
171
172 fn angle_at_end(&self) -> f32 {
173 self.end_angle + std::f32::consts::PI / 2.0
174 }
175
176 fn angle_at_start(&self) -> f32 {
177 self.start_angle - std::f32::consts::PI / 2.0
178 }
179}
180
181#[wasm_bindgen]
183#[derive(Clone, Debug)]
184pub struct Circle {
185 center: Point2D,
187 radius: f32,
189}
190
191#[wasm_bindgen]
192impl Circle {
193 #[wasm_bindgen(constructor, return_description = "A circle.")]
195 pub fn new(
196 #[wasm_bindgen(param_description = "The center point of the circle as a Point2D.")]
197 center: Point2D,
198 #[wasm_bindgen(param_description = "The radius of the circle.")]
199 radius: f32,
200 ) -> Circle {
201 Circle { center, radius }
202 }
203 #[wasm_bindgen(getter, return_description = "An Arc representing the circle.")]
205 pub fn arc(&self) -> Arc {
206 Arc::new(self.center, self.radius, 0.0, 2.0 * std::f32::consts::PI)
207 }
208 #[wasm_bindgen(return_description = "A VectorObjectBuilder representing the circle.")]
210 pub fn vector_object_builder(
211 &self,
212 #[wasm_bindgen(param_description = "The number of samples to use to create the circle, by default 15.")]
213 samples: Option<usize>
214 ) -> VectorObjectBuilder {
215 self.arc().vector_object_builder(samples).close()
216 }
217 #[wasm_bindgen(getter, return_description = "The center point of the circle.")]
219 pub fn center(&self) -> Point2D {
220 self.center
221 }
222 #[wasm_bindgen(getter, return_description = "The radius of the circle.")]
224 pub fn radius(&self) -> f32 {
225 self.radius
226 }
227 #[wasm_bindgen(return_description = "The circumference of the circle.")]
229 pub fn circumference(&self) -> f32 {
230 2.0 * std::f32::consts::PI * self.radius
231 }
232 #[wasm_bindgen(return_description = "The area of the circle.")]
234 pub fn area(&self) -> f32 {
235 std::f32::consts::PI * self.radius.powi(2)
236 }
237}