makepad_vector/geometry/
arc.rs

1use std::f64::consts::{PI, TAU};
2
3use crate::geometry::{Point, Transform, Transformation};
4use crate::internal_iter::InternalIterator;
5
6#[derive(Clone, Copy, Debug, Default, PartialEq)]
7#[repr(C)]
8pub struct Arc {
9    pub from: Point,
10    pub to: Point,
11    pub radius: Point,
12    pub x_axis_rotation: f64,
13    pub large_arc: bool,
14    pub sweep: bool,
15
16    center: Point,
17    transformed_point: Point,
18    transformed_center: Point,
19    start_angle: f64,
20    sweep_angle: f64,
21    end_angle: f64,
22    rx: f64,
23    ry: f64,
24}
25
26impl Arc {
27    // See http://www.w3.org/TR/SVG/implnote.html#ArcParameterizationAlternatives
28    // for how we calculate center point, start and end angle
29    pub fn new(
30        from: Point,
31        to: Point,
32        radius: Point,
33        x_axis_rotation: f64,
34        large_arc: bool,
35        sweep: bool,
36    ) -> Arc {
37        // If the endpoints are identical, then this is equivalent to omitting the elliptical arc
38        // segment entirely.
39        if from.x == to.x && from.y == to.y {
40            return Arc {
41                from,
42                to,
43                radius,
44                x_axis_rotation,
45                large_arc,
46                sweep,
47                ..Default::default()
48            };
49        }
50
51        let mut rx = radius.x.abs();
52        let mut ry = radius.y.abs();
53        if rx == 0.0 || ry == 0.0 {
54            // Effectively a line
55            return Arc {
56                from,
57                to,
58                radius,
59                x_axis_rotation,
60                large_arc,
61                sweep,
62                ..Default::default()
63            };
64        }
65
66        let x_axis_rotation = x_axis_rotation % 360.0;
67        let x_axis_rotation_radians = x_axis_rotation * (PI / 180.0);
68
69        // Step #1: Compute transformedPoint
70        let dx = (from.x - to.x) / 2.0;
71        let dy = (from.y - to.y) / 2.0;
72        let transformed_point = Point {
73            x: x_axis_rotation_radians.cos() * dx + x_axis_rotation_radians.sin() * dy,
74            y: -x_axis_rotation_radians.sin() * dx + x_axis_rotation_radians.cos() * dy,
75        };
76
77        // Ensure radii are large enough
78        let radii_check =
79            transformed_point.x.powi(2) / rx.powi(2) + transformed_point.y.powi(2) / ry.powi(2);
80        if radii_check > 1.0 {
81            rx *= radii_check.sqrt();
82            ry *= radii_check.sqrt();
83        }
84
85        // Step #2: Compute transformedCenter
86        let c_square_numerator = rx.powi(2) * ry.powi(2)
87            - rx.powi(2) * transformed_point.y.powi(2)
88            - ry.powi(2) * transformed_point.x.powi(2);
89        let c_square_denominator =
90            rx.powi(2) * transformed_point.y.powi(2)
91            + ry.powi(2) * transformed_point.x.powi(2);
92        let c_radicand = (c_square_numerator / c_square_denominator).max(0.0);
93        let c_coefficient = if sweep != large_arc {
94            1.0
95        } else {
96            -1.0
97        } * c_radicand.sqrt();
98        let transformed_center = Point {
99            x: c_coefficient * ((rx * transformed_point.y) / ry),
100            y: c_coefficient * (-(ry * transformed_point.x) / rx),
101        };
102
103        let center = Point {
104            x: x_axis_rotation_radians.cos() * transformed_center.x
105                - x_axis_rotation_radians.sin() * transformed_center.y
106                + ((from.x + to.x) / 2.0),
107            y: x_axis_rotation_radians.sin() * transformed_center.x
108                + x_axis_rotation_radians.cos() * transformed_center.y
109                + ((from.y + to.y) / 2.0),
110        };
111
112        // Step #4: Compute start/sweep angles
113        // Start angle of the elliptical arc prior to the stretch and rotate operations.
114        // Difference between the start and end angles
115        let start_vector = Point {
116            x: (transformed_point.x - transformed_center.x) / rx,
117            y: (transformed_point.y - transformed_center.y) / ry,
118        };
119        let start_angle = angle_between(Point { x: 1.0, y: 0.0 }, start_vector);
120        debug_assert!(!start_angle.is_nan());
121
122        let end_vector = Point {
123            x: (-transformed_point.x - transformed_center.x) / rx,
124            y: (-transformed_point.y - transformed_center.y) / ry,
125        };
126        let mut sweep_angle = angle_between(start_vector, end_vector);
127
128        if !sweep && sweep_angle > 0.0 {
129            sweep_angle -= TAU;
130        } else if sweep && sweep_angle < 0.0 {
131            sweep_angle += TAU;
132        }
133        sweep_angle %= TAU;
134        let end_angle = start_angle + sweep_angle;
135
136        Arc {
137            from,
138            to,
139            radius,
140            x_axis_rotation,
141            large_arc,
142            sweep,
143
144            center,
145            transformed_point,
146            transformed_center,
147            start_angle,
148            sweep_angle,
149            end_angle,
150            rx,
151            ry,
152        }
153    }
154
155    /// Returns true if `self` is approximately linear with tolerance `epsilon`.
156    pub fn is_approximately_linear(&self, epsilon: f64) -> bool {
157        let center = self.from.lerp(self.to, 0.5);
158        let midpoint = self.midpoint();
159
160        let dx = midpoint.x - center.x;
161        let dy = midpoint.y - center.y;
162        let sagitta = (dx.powi(2) + dy.powi(2)).sqrt();
163
164        sagitta < epsilon
165    }
166
167    pub fn point_on_curve(&self, t: f64) -> Point {
168        debug_assert!(t >= 0.0 && t <= 1.0);
169
170        let angle = self.start_angle + self.sweep_angle * t;
171        let ellipse_component_x = self.rx * angle.cos();
172        let ellipse_component_y = self.ry * angle.sin();
173        let x_axis_rotation_radians = self.x_axis_rotation * (PI / 180.0);
174        Point {
175            x: x_axis_rotation_radians.cos() * ellipse_component_x
176                - x_axis_rotation_radians.sin() * ellipse_component_y
177                + self.center.x,
178            y: x_axis_rotation_radians.sin() * ellipse_component_x
179                + x_axis_rotation_radians.cos() * ellipse_component_y
180                + self.center.y,
181        }
182    }
183
184    pub fn midpoint(&self) -> Point {
185        self.point_on_curve(0.5)
186    }
187
188    // Function to split the arc at parameter `t`
189    pub fn split(&self, t: f64) -> (Arc, Arc) {
190        let split_at = self.point_on_curve(t);
191        let angle = self.start_angle + self.sweep_angle * t;
192
193        (Arc::new(
194            self.from,
195            split_at,
196            self.radius,
197            self.x_axis_rotation,
198            (angle - self.start_angle) > PI,
199            self.sweep,
200        ),
201        Arc::new(
202            split_at,
203            self.to,
204            self.radius,
205            self.x_axis_rotation,
206            (self.end_angle - angle) > PI,
207            self.sweep,
208        ))
209    }
210
211    pub fn linearize(self, epsilon: f64) -> Linearize {
212        Linearize {
213            segment: self,
214            epsilon,
215        }
216    }
217}
218
219fn angle_between(v0: Point, v1: Point) -> f64 {
220    let adjacent = v0.x * v1.x + v0.y * v1.y;
221    let hypotenuse = (
222        (v0.x.powi(2) + v0.y.powi(2)) * (v1.x.powi(2) + v1.y.powi(2))
223    ).sqrt();
224    let sign = if v0.x * v1.y - v0.y * v1.x < 0.0 {
225        -1.0
226    } else {
227        1.0
228    };
229    sign * (adjacent / hypotenuse).clamp(-1.0, 1.0).acos()
230}
231
232impl Transform for Arc {
233    fn transform<T>(self, t: &T) -> Arc
234    where
235        T: Transformation,
236    {
237        Arc::new(
238            self.from.transform(t),
239            self.to.transform(t),
240            self.radius,
241            self.x_axis_rotation,
242            self.large_arc,
243            self.sweep,
244        )
245    }
246
247    fn transform_mut<T>(&mut self, t: &T)
248    where
249        T: Transformation,
250    {
251        *self = self.transform(t);
252    }
253}
254
255#[derive(Clone, Copy)]
256pub struct Linearize {
257    segment: Arc,
258    epsilon: f64,
259}
260
261impl InternalIterator for Linearize {
262    type Item = Point;
263
264    fn for_each<F>(self, f: &mut F) -> bool
265    where
266        F: FnMut(Point) -> bool,
267    {
268        if self.segment.is_approximately_linear(self.epsilon) {
269            return f(self.segment.to);
270        }
271        let (segment_0, segment_1) = self.segment.split(0.5);
272        if !segment_0.linearize(self.epsilon).for_each(f) {
273            return false;
274        }
275        segment_1.linearize(self.epsilon).for_each(f)
276    }
277}