1use std::rc::Rc;
2
3use wasm_bindgen::prelude::*;
4
5use super::point2d::{Path2D, Point2D};
6
7#[wasm_bindgen]
9#[derive(Clone, Debug)]
10pub struct AnchorsAndHandles {
11 start_anchors: Rc<Vec<Point2D>>,
13 first_controls: Rc<Vec<Point2D>>,
15 second_controls: Rc<Vec<Point2D>>,
17 end_anchors: Rc<Vec<Point2D>>,
19}
20
21#[wasm_bindgen]
22impl AnchorsAndHandles {
23 #[wasm_bindgen(constructor, return_description = "An object containing the start anchors, first control points, second control points, and end anchors of a path.")]
25 pub fn new(
26 #[wasm_bindgen(param_description = "The start anchors of the path.")]
27 start_anchors: Vec<Point2D>,
28 #[wasm_bindgen(param_description = "The first control points of the path.")]
29 first_controls: Vec<Point2D>,
30 #[wasm_bindgen(param_description = "The second control points of the path.")]
31 second_controls: Vec<Point2D>,
32 #[wasm_bindgen(param_description = "The end anchors of the path.")]
33 end_anchors: Vec<Point2D>
34 ) -> Result<AnchorsAndHandles, JsError> {
35 let lengths = vec![start_anchors.len(), first_controls.len(), second_controls.len(), end_anchors.len()];
36 let min_length = *lengths.iter().min().unwrap();
37 let max_length = *lengths.iter().max().unwrap();
38 if min_length != max_length {
39 return Err(JsError::new("The start anchors, first controls, second controls, and end anchors must have the same length."));
40 }
41 Ok(AnchorsAndHandles { start_anchors: Rc::new(start_anchors), first_controls: Rc::new(first_controls), second_controls: Rc::new(second_controls), end_anchors: Rc::new(end_anchors) })
42 }
43 #[wasm_bindgen(return_description = "An object containing the start anchors, first control points, second control points, and end anchors of a path.")]
45 pub fn from_path(
46 #[wasm_bindgen(param_description = "The path to extract the anchors and handles from.")]
47 path: Path2D
48 ) -> Result<AnchorsAndHandles, JsError> {
49 if path.len() % 4 != 0 {
50 return Err(JsError::new("The path length must be a multiple of 4."));
51 }
52 let mut start_anchors = Vec::with_capacity(path.len() / 4);
53 let mut first_controls = Vec::with_capacity(path.len() / 4);
54 let mut second_controls = Vec::with_capacity(path.len() / 4);
55 let mut end_anchors = Vec::with_capacity(path.len() / 4);
56 let cubic_beziers = path.cubic_bezier_tuples();
57 for cubic_bezier in cubic_beziers {
58 start_anchors.push(cubic_bezier.start_anchor);
59 first_controls.push(cubic_bezier.first_control);
60 second_controls.push(cubic_bezier.second_control);
61 end_anchors.push(cubic_bezier.end_anchor);
62 }
63 Ok(AnchorsAndHandles { start_anchors: Rc::new(start_anchors), first_controls: Rc::new(first_controls), second_controls: Rc::new(second_controls), end_anchors: Rc::new(end_anchors) })
64 }
65 #[wasm_bindgen(getter, return_description = "The start anchors of the path.")]
67 pub fn start_anchors(&self) -> Vec<Point2D> {
68 self.start_anchors.to_vec()
69 }
70 #[wasm_bindgen(getter, return_description = "The first control points of the path.")]
72 pub fn first_controls(&self) -> Vec<Point2D> {
73 self.first_controls.to_vec()
74 }
75 #[wasm_bindgen(getter, return_description = "The second control points of the path.")]
77 pub fn second_controls(&self) -> Vec<Point2D> {
78 self.second_controls.to_vec()
79 }
80 #[wasm_bindgen(getter, return_description = "The end anchors of the path.")]
82 pub fn end_anchors(&self) -> Vec<Point2D> {
83 self.end_anchors.to_vec()
84 }
85 #[wasm_bindgen(getter, return_description = "The number of cubic bezier curves in the path.")]
87 pub fn len(&self) -> usize {
88 self.start_anchors.len()
89 }
90 #[wasm_bindgen(js_name = clone)]
92 pub fn copy(&self) -> AnchorsAndHandles {
93 self.clone()
94 }
95}
96
97#[wasm_bindgen]
99#[derive(Clone, Copy, Debug, PartialEq)]
100pub struct CubicBezierTuple {
101 start_anchor: Point2D,
103 first_control: Point2D,
105 second_control: Point2D,
107 end_anchor: Point2D,
109}
110
111#[wasm_bindgen]
112impl CubicBezierTuple {
113 #[wasm_bindgen(constructor, return_description = "A cubic bezier curve.")]
115 pub fn new(
116 #[wasm_bindgen(param_description = "The start anchor of the cubic bezier.")]
117 start_anchor: Point2D,
118 #[wasm_bindgen(param_description = "The first control point of the cubic bezier.")]
119 first_control: Point2D,
120 #[wasm_bindgen(param_description = "The second control point of the cubic bezier.")]
121 second_control: Point2D,
122 #[wasm_bindgen(param_description = "The end anchor of the cubic bezier.")]
123 end_anchor: Point2D
124 ) -> CubicBezierTuple {
125 CubicBezierTuple { start_anchor, first_control, second_control, end_anchor }
126 }
127 #[wasm_bindgen(return_description = "The cubic bezier curve representing the line.")]
129 pub fn from_line(
130 #[wasm_bindgen(param_description = "The start point of the line.")]
131 p1: Point2D,
132 #[wasm_bindgen(param_description = "The end point of the line.")]
133 p2: Point2D
134 ) -> CubicBezierTuple {
135 let handle1 = Point2D::lerp(&p1, &p2, 1.0 / 3.0);
136 let handle2 = Point2D::lerp(&p1, &p2, 2.0 / 3.0);
137 CubicBezierTuple::new(p1, handle1, handle2, p2)
138 }
139 #[wasm_bindgen(return_description = "The cubic bezier curve representing the quadratic bezier.")]
141 pub fn from_quadratic(
142 #[wasm_bindgen(param_description = "The first anchor point of the quadratic bezier.")]
143 p1: Point2D,
144 #[wasm_bindgen(param_description = "The control point of the quadratic bezier.")]
145 p2: Point2D,
146 #[wasm_bindgen(param_description = "The second anchor point of the quadratic bezier.")]
147 p3: Point2D
148 ) -> CubicBezierTuple {
149 let handle1 = Point2D::lerp(&p1, &p2, 2.0 / 3.0);
150 let handle2 = Point2D::lerp(&p2, &p3, 1.0 / 3.0);
151 CubicBezierTuple::new(p1, handle1, handle2, p3)
152 }
153 #[wasm_bindgen(getter, return_description = "The start anchor of the cubic bezier.")]
155 pub fn start_anchor(&self) -> Point2D {
156 self.start_anchor
157 }
158 #[wasm_bindgen(getter, return_description = "The first control point of the cubic bezier.")]
160 pub fn first_control(&self) -> Point2D {
161 self.first_control
162 }
163 #[wasm_bindgen(getter, return_description = "The second control point of the cubic bezier.")]
165 pub fn second_control(&self) -> Point2D {
166 self.second_control
167 }
168 #[wasm_bindgen(getter, return_description = "The end anchor of the cubic bezier.")]
170 pub fn end_anchor(&self) -> Point2D {
171 self.end_anchor
172 }
173 #[wasm_bindgen(return_description = "The point on the cubic bezier curve when the polynomial is evaluated at the given t value.")]
175 pub fn point_at(
176 &self,
177 #[wasm_bindgen(param_description = "The t value to evaluate the polynomial at. A number between 0 and 1.")]
178 t: f32
179 ) -> Point2D {
180 let one_minus_t = 1.0 - t;
181 let one_minus_t_squared = one_minus_t * one_minus_t;
182 let one_minus_t_cubed = one_minus_t_squared * one_minus_t;
183 let t_squared = t * t;
184 let t_cubed = t_squared * t;
185 self.start_anchor * one_minus_t_cubed
186 + self.first_control * (3.0 * one_minus_t_squared * t)
187 + self.second_control * (3.0 * one_minus_t * t_squared)
188 + self.end_anchor * t_cubed
189 }
190 pub fn length(
192 &self,
193 #[wasm_bindgen(param_description = "The number of samples to take along the curve.")]
194 samples: Option<usize>,
195 #[wasm_bindgen(param_description = "An optional extra length to add to the approximation.")]
196 extra_length: Option<f32>
197 ) -> f32 {
198 let mut length = 0.0;
199 let mut last_point = self.start_anchor;
200 let samples = samples.unwrap_or(100);
201 let extra_length = extra_length.unwrap_or(0.0);
202 for i in 1..=samples {
203 let t = i as f32 / samples as f32;
204 let point = self.point_at(t);
205 length += last_point.distance(&point);
206 last_point = point;
207 }
208 length + extra_length
209 }
210 #[wasm_bindgen(js_name = clone)]
212 pub fn copy(&self) -> CubicBezierTuple {
213 *self
214 }
215}