index/utils/
bezier.rs

1use std::rc::Rc;
2
3use wasm_bindgen::prelude::*;
4
5use super::point2d::{Path2D, Point2D};
6
7/// Start anchors, first control points, second control points, and end anchors of a path.
8#[wasm_bindgen]
9#[derive(Clone, Debug)]
10pub struct AnchorsAndHandles {
11    /// The start anchors of the path.
12    start_anchors: Rc<Vec<Point2D>>,
13    /// The first control points of the path.
14    first_controls: Rc<Vec<Point2D>>,
15    /// The second control points of the path.
16    second_controls: Rc<Vec<Point2D>>,
17    /// The end anchors of the path.
18    end_anchors: Rc<Vec<Point2D>>,
19}
20
21#[wasm_bindgen]
22impl AnchorsAndHandles {
23    /// Creates a new AnchorsAndHandles object.
24    #[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    /// Creates a AnchorsAndHandles object from a Path2D object.
44    #[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    /// Returns the start anchors of the path.
66    #[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    /// Returns the first control points of the path.
71    #[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    /// Returns the second control points of the path.
76    #[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    /// Returns the end anchors of the path.
81    #[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    /// Returns the number of cubic bezier curves in the path.
86    #[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    /// Clones the AnchorsAndHandles object.
91    #[wasm_bindgen(js_name = clone)]
92    pub fn copy(&self) -> AnchorsAndHandles {
93        self.clone()
94    }
95}
96
97/// A cubic bezier curve represented by four points: a start anchor, a first control point, a second control point, and an end anchor.
98#[wasm_bindgen]
99#[derive(Clone, Copy, Debug, PartialEq)]
100pub struct CubicBezierTuple {
101    /// The start anchor of the cubic bezier.
102    start_anchor: Point2D,
103    /// The first control point of the cubic bezier.
104    first_control: Point2D,
105    /// The second control point of the cubic bezier.
106    second_control: Point2D,
107    /// The end anchor of the cubic bezier.
108    end_anchor: Point2D,
109}
110
111#[wasm_bindgen]
112impl CubicBezierTuple {
113    /// Creates a new cubic bezier curve.
114    #[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    /// Creates a new cubic bezier curve from a line.
128    #[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    /// Creates a new cubic bezier curve from a quadratic bezier.
140    #[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    /// Returns the start anchor of the cubic bezier.
154    #[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    /// Returns the first control point of the cubic bezier.
159    #[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    /// Returns the second control point of the cubic bezier.
164    #[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    /// Returns the end anchor of the cubic bezier.
169    #[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    /// Returns the point on the cubic bezier curve at a given t value.
174    #[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    /// Returns an approximation of the length of the cubic bezier curve, based on sampling points along the curve and an optional extra length to add.
191    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    /// Clones the CubicBezierTuple object.
211    #[wasm_bindgen(js_name = clone)]
212    pub fn copy(&self) -> CubicBezierTuple {
213        *self
214    }
215}