zeno/
path_builder.rs

1//! Path builder.
2
3#![allow(clippy::excessive_precision)]
4
5use super::command::Command;
6use super::geometry::{Angle, BoundsBuilder, Point, Transform};
7#[allow(unused)]
8use super::F32Ext;
9
10use crate::lib::Vec;
11use core::f32;
12
13/// Describes the size of an arc.
14#[derive(Copy, Clone, PartialEq)]
15pub enum ArcSize {
16    /// An arc of <= 180 degrees will be drawn.
17    Small,
18    /// An arc of >= 180 degrees will be drawn.
19    Large,
20}
21
22/// Describes the sweep direction for an arc.
23#[derive(Copy, Clone, PartialEq)]
24pub enum ArcSweep {
25    /// The arc is drawn in a positive angle direction.
26    Positive,
27    /// The arc is drawn in a negative angle direction.
28    Negative,
29}
30
31/// Trait for types that accept path commands.
32pub trait PathBuilder: Sized {
33    /// Returns the current point of the path.
34    fn current_point(&self) -> Point;
35
36    /// Moves to the specified point, beginning a new subpath.
37    fn move_to(&mut self, to: impl Into<Point>) -> &mut Self;
38
39    /// Moves to the specified point, relative to the current point,
40    /// beginning a new subpath.
41    fn rel_move_to(&mut self, to: impl Into<Point>) -> &mut Self {
42        self.move_to(to.into() + self.current_point())
43    }
44
45    /// Adds a line to the specified point. This will begin a new subpath
46    /// if the path is empty or the previous subpath was closed.
47    fn line_to(&mut self, to: impl Into<Point>) -> &mut Self;
48
49    /// Adds a line to the specified point, relative to the current point. This
50    /// will begin a new subpath if the path is empty or the previous subpath
51    /// was closed.
52    fn rel_line_to(&mut self, to: impl Into<Point>) -> &mut Self {
53        self.line_to(to.into() + self.current_point())
54    }
55
56    /// Adds a cubic bezier curve from the current point through the specified
57    /// control points to the final point. This will begin a new subpath if the
58    /// path is empty or the previous subpath was closed.
59    fn curve_to(
60        &mut self,
61        control1: impl Into<Point>,
62        control2: impl Into<Point>,
63        to: impl Into<Point>,
64    ) -> &mut Self;
65
66    /// Adds a cubic bezier curve from the current point through the specified
67    /// control points to the final point. All points are considered relative to the
68    /// current point. This will begin a new subpath if the path is empty or the
69    /// previous subpath was closed.
70    fn rel_curve_to(
71        &mut self,
72        control1: impl Into<Point>,
73        control2: impl Into<Point>,
74        to: impl Into<Point>,
75    ) -> &mut Self {
76        let r = self.current_point();
77        self.curve_to(control1.into() + r, control2.into() + r, to.into() + r)
78    }
79
80    /// Adds a quadratic bezier curve from the current point through the specified
81    /// control point to the final point. This will begin a new subpath if the
82    /// path is empty or the previous subpath was closed.   
83    fn quad_to(&mut self, control1: impl Into<Point>, to: impl Into<Point>) -> &mut Self;
84
85    /// Adds a quadratic bezier curve from the current point through the specified
86    /// control point to the final point. All points are considered relative to the
87    /// current point. This will begin a new subpath if the path is empty or the
88    /// previous subpath was closed.    
89    fn rel_quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
90        let r = self.current_point();
91        self.quad_to(control.into() + r, to.into() + r)
92    }
93
94    /// Adds an arc with the specified x- and y-radius, rotation angle, arc size,
95    /// and arc sweep from the current point to the specified end point. The center
96    /// point of the arc will be computed from the parameters. This will begin a
97    /// new subpath if the path is empty or the previous subpath was closed.
98    fn arc_to(
99        &mut self,
100        rx: f32,
101        ry: f32,
102        angle: Angle,
103        size: ArcSize,
104        sweep: ArcSweep,
105        to: impl Into<Point>,
106    ) -> &mut Self {
107        let from = self.current_point();
108        arc(
109            self,
110            from,
111            rx,
112            ry,
113            angle.to_radians(),
114            size,
115            sweep,
116            to.into(),
117        );
118        self
119    }
120
121    /// Adds an arc with the specified x- and y-radius, rotation angle, arc size,
122    /// and arc sweep from the current point to the specified end point. The end
123    /// point is considered relative to the current point. The center point of the
124    /// arc will be computed from the parameters. This will begin a new subpath if
125    /// the path is empty or the previous subpath was closed.
126    fn rel_arc_to(
127        &mut self,
128        rx: f32,
129        ry: f32,
130        angle: Angle,
131        size: ArcSize,
132        sweep: ArcSweep,
133        to: impl Into<Point>,
134    ) -> &mut Self {
135        self.arc_to(rx, ry, angle, size, sweep, to.into() + self.current_point())
136    }
137
138    /// Closes the current subpath.
139    fn close(&mut self) -> &mut Self;
140
141    /// Adds a rectangle with the specified position and size to the path. This
142    /// will create a new closed subpath.
143    fn add_rect(&mut self, xy: impl Into<Point>, w: f32, h: f32) -> &mut Self {
144        let p = xy.into();
145        let (l, t, r, b) = (p.x, p.y, p.x + w, p.y + h);
146        self.move_to(p);
147        self.line_to((r, t));
148        self.line_to((r, b));
149        self.line_to((l, b));
150        self.close()
151    }
152
153    /// Adds a rounded rectangle with the specified position, size and radii to
154    /// the path. This will create a new closed subpath.
155    fn add_round_rect(
156        &mut self,
157        xy: impl Into<Point>,
158        w: f32,
159        h: f32,
160        rx: f32,
161        ry: f32,
162    ) -> &mut Self {
163        let p = xy.into();
164        let size = ArcSize::Small;
165        let sweep = ArcSweep::Positive;
166        let a = Angle::from_radians(0.);
167        let hw = w * 0.5;
168        let rx = rx.max(0.).min(hw);
169        let hh = h * 0.5;
170        let ry = ry.max(0.).min(hh);
171        self.move_to((p.x + rx, p.y));
172        self.line_to((p.x + w - rx, p.y));
173        self.arc_to(rx, ry, a, size, sweep, (p.x + w, p.y + ry));
174        self.line_to((p.x + w, p.y + h - ry));
175        self.arc_to(rx, ry, a, size, sweep, (p.x + w - rx, p.y + h));
176        self.line_to((p.x + rx, p.y + h));
177        self.arc_to(rx, ry, a, size, sweep, (p.x, p.y + h - ry));
178        self.line_to((p.x, p.y + ry));
179        self.arc_to(rx, ry, a, size, sweep, (p.x + rx, p.y));
180        self.close()
181    }
182
183    /// Adds an ellipse with the specified center and radii to the path. This
184    /// will create a new closed subpath.
185    fn add_ellipse(&mut self, center: impl Into<Point>, rx: f32, ry: f32) -> &mut Self {
186        let center = center.into();
187        let cx = center.x;
188        let cy = center.y;
189        let a = 0.551915024494;
190        let arx = a * rx;
191        let ary = a * ry;
192        self.move_to((cx + rx, cy));
193        self.curve_to((cx + rx, cy + ary), (cx + arx, cy + ry), (cx, cy + ry));
194        self.curve_to((cx - arx, cy + ry), (cx - rx, cy + ary), (cx - rx, cy));
195        self.curve_to((cx - rx, cy - ary), (cx - arx, cy - ry), (cx, cy - ry));
196        self.curve_to((cx + arx, cy - ry), (cx + rx, cy - ary), (cx + rx, cy));
197        self.close()
198    }
199
200    /// Adds a circle with the specified center and radius to the path. This
201    /// will create a new closed subpath.
202    fn add_circle(&mut self, center: impl Into<Point>, r: f32) -> &mut Self {
203        self.add_ellipse(center, r, r)
204    }
205}
206
207impl PathBuilder for Vec<Command> {
208    fn current_point(&self) -> Point {
209        match self.last() {
210            None => Point::ZERO,
211            Some(cmd) => match cmd {
212                Command::MoveTo(p)
213                | Command::LineTo(p)
214                | Command::QuadTo(_, p)
215                | Command::CurveTo(_, _, p) => *p,
216                Command::Close => {
217                    for cmd in self.iter().rev().skip(1) {
218                        if let Command::MoveTo(p) = cmd {
219                            return *p;
220                        }
221                    }
222                    Point::ZERO
223                }
224            },
225        }
226    }
227
228    fn move_to(&mut self, to: impl Into<Point>) -> &mut Self {
229        self.push(Command::MoveTo(to.into()));
230        self
231    }
232
233    fn line_to(&mut self, to: impl Into<Point>) -> &mut Self {
234        self.push(Command::LineTo(to.into()));
235        self
236    }
237
238    fn quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
239        self.push(Command::QuadTo(control.into(), to.into()));
240        self
241    }
242
243    fn curve_to(
244        &mut self,
245        control1: impl Into<Point>,
246        control2: impl Into<Point>,
247        to: impl Into<Point>,
248    ) -> &mut Self {
249        self.push(Command::CurveTo(
250            control1.into(),
251            control2.into(),
252            to.into(),
253        ));
254        self
255    }
256
257    fn close(&mut self) -> &mut Self {
258        self.push(Command::Close);
259        self
260    }
261}
262
263pub struct TransformSink<'a, S> {
264    pub sink: &'a mut S,
265    pub transform: Transform,
266}
267
268impl<S: PathBuilder> PathBuilder for TransformSink<'_, S> {
269    fn current_point(&self) -> Point {
270        self.sink.current_point()
271    }
272
273    fn move_to(&mut self, to: impl Into<Point>) -> &mut Self {
274        self.sink.move_to(self.transform.transform_point(to.into()));
275        self
276    }
277
278    fn line_to(&mut self, to: impl Into<Point>) -> &mut Self {
279        self.sink.line_to(self.transform.transform_point(to.into()));
280        self
281    }
282
283    fn quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
284        self.sink.quad_to(
285            self.transform.transform_point(control.into()),
286            self.transform.transform_point(to.into()),
287        );
288        self
289    }
290
291    fn curve_to(
292        &mut self,
293        control1: impl Into<Point>,
294        control2: impl Into<Point>,
295        to: impl Into<Point>,
296    ) -> &mut Self {
297        self.sink.curve_to(
298            self.transform.transform_point(control1.into()),
299            self.transform.transform_point(control2.into()),
300            self.transform.transform_point(to.into()),
301        );
302        self
303    }
304
305    fn close(&mut self) -> &mut Self {
306        self.sink.close();
307        self
308    }
309}
310
311impl PathBuilder for BoundsBuilder {
312    fn current_point(&self) -> Point {
313        self.current
314    }
315
316    fn move_to(&mut self, to: impl Into<Point>) -> &mut Self {
317        let p = to.into();
318        self.add(p);
319        self.current = p;
320        self
321    }
322
323    fn line_to(&mut self, to: impl Into<Point>) -> &mut Self {
324        let p = to.into();
325        self.add(p);
326        self.current = p;
327        self
328    }
329
330    fn quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
331        self.add(control.into());
332        let p = to.into();
333        self.add(p);
334        self.current = p;
335        self
336    }
337
338    fn curve_to(
339        &mut self,
340        control1: impl Into<Point>,
341        control2: impl Into<Point>,
342        to: impl Into<Point>,
343    ) -> &mut Self {
344        self.add(control1.into());
345        self.add(control2.into());
346        let p = to.into();
347        self.add(p);
348        self.current = p;
349        self
350    }
351
352    fn close(&mut self) -> &mut Self {
353        self
354    }
355}
356
357/// An iterator that generates cubic bezier curves for an arc.
358#[derive(Copy, Clone, Default)]
359pub struct Arc {
360    count: usize,
361    center: (f32, f32),
362    radii: (f32, f32),
363    cosphi: f32,
364    sinphi: f32,
365    ang1: f32,
366    ang2: f32,
367    a: f32,
368}
369
370impl Arc {
371    pub fn new(
372        from: impl Into<[f32; 2]>,
373        rx: f32,
374        ry: f32,
375        angle: f32,
376        size: ArcSize,
377        sweep: ArcSweep,
378        to: impl Into<[f32; 2]>,
379    ) -> Self {
380        let from = from.into();
381        let to = to.into();
382        let (px, py) = (from[0], from[1]);
383        const TAU: f32 = 3.141579 * 2.;
384        let (sinphi, cosphi) = angle.sin_cos();
385        let pxp = cosphi * (px - to[0]) / 2. + sinphi * (py - to[1]) / 2.;
386        let pyp = -sinphi * (px - to[0]) / 2. + cosphi * (py - to[1]) / 2.;
387        if pxp == 0. && pyp == 0. {
388            return Self::default();
389        }
390        let mut rx = rx.abs();
391        let mut ry = ry.abs();
392        let lambda = pxp.powi(2) / rx.powi(2) + pyp.powi(2) / ry.powi(2);
393        if lambda > 1. {
394            let s = lambda.sqrt();
395            rx *= s;
396            ry *= s;
397        }
398        let large_arc = size == ArcSize::Large;
399        let sweep = sweep == ArcSweep::Positive;
400        let (cx, cy, ang1, mut ang2) = {
401            fn vec_angle(ux: f32, uy: f32, vx: f32, vy: f32) -> f32 {
402                let sign = if (ux * vy - uy * vx) < 0. { -1. } else { 1. };
403                let dot = (ux * vx + uy * vy).clamp(-1., 1.);
404                sign * dot.acos()
405            }
406            let rxsq = rx * rx;
407            let rysq = ry * ry;
408            let pxpsq = pxp * pxp;
409            let pypsq = pyp * pyp;
410            let mut radicant = (rxsq * rysq) - (rxsq * pypsq) - (rysq * pxpsq);
411            if radicant < 0. {
412                radicant = 0.;
413            }
414            radicant /= (rxsq * pypsq) + (rysq * pxpsq);
415            radicant = radicant.sqrt() * if large_arc == sweep { -1. } else { 1. };
416            let cxp = radicant * rx / ry * pyp;
417            let cyp = radicant * -ry / rx * pxp;
418            let cx = cosphi * cxp - sinphi * cyp + (px + to[0]) / 2.;
419            let cy = sinphi * cxp + cosphi * cyp + (py + to[1]) / 2.;
420            let vx1 = (pxp - cxp) / rx;
421            let vy1 = (pyp - cyp) / ry;
422            let vx2 = (-pxp - cxp) / rx;
423            let vy2 = (-pyp - cyp) / ry;
424            let ang1 = vec_angle(1., 0., vx1, vy1);
425            let mut ang2 = vec_angle(vx1, vy1, vx2, vy2);
426            if !sweep && ang2 > 0. {
427                ang2 -= TAU;
428            }
429            if sweep && ang2 < 0. {
430                ang2 += TAU;
431            }
432            (cx, cy, ang1, ang2)
433        };
434        let mut ratio = ang2.abs() / (TAU / 4.);
435        if (1. - ratio).abs() < 0.0000001 {
436            ratio = 1.
437        }
438        let segments = ratio.ceil().max(1.);
439        ang2 /= segments;
440        let a = if ang2 == f32::consts::FRAC_PI_2 {
441            0.551915024494
442        } else if ang2 == -f32::consts::FRAC_PI_2 {
443            -0.551915024494
444        } else {
445            4. / 3. * (ang2 / 4.).tan()
446        };
447        Self {
448            count: segments as usize,
449            center: (cx, cy),
450            radii: (rx, ry),
451            sinphi,
452            cosphi,
453            ang1,
454            ang2,
455            a,
456        }
457    }
458}
459
460impl Iterator for Arc {
461    type Item = Command;
462
463    fn next(&mut self) -> Option<Self::Item> {
464        if self.count == 0 {
465            return None;
466        }
467        self.count -= 1;
468        let (y1, x1) = self.ang1.sin_cos();
469        let (y2, x2) = (self.ang1 + self.ang2).sin_cos();
470        let a = self.a;
471        let (cx, cy) = self.center;
472        let (rx, ry) = self.radii;
473        let sinphi = self.sinphi;
474        let cosphi = self.cosphi;
475        let c1 = Point::new((x1 - y1 * a) * rx, (y1 + x1 * a) * ry);
476        let c1 = Point::new(
477            cx + (cosphi * c1.x - sinphi * c1.y),
478            cy + (sinphi * c1.x + cosphi * c1.y),
479        );
480        let c2 = Point::new((x2 + y2 * a) * rx, (y2 - x2 * a) * ry);
481        let c2 = Point::new(
482            cx + (cosphi * c2.x - sinphi * c2.y),
483            cy + (sinphi * c2.x + cosphi * c2.y),
484        );
485        let p = Point::new(x2 * rx, y2 * ry);
486        let p = Point::new(
487            cx + (cosphi * p.x - sinphi * p.y),
488            cy + (sinphi * p.x + cosphi * p.y),
489        );
490        self.ang1 += self.ang2;
491        Some(Command::CurveTo(c1, c2, p))
492    }
493}
494
495#[allow(clippy::too_many_arguments)]
496pub fn arc(
497    sink: &mut impl PathBuilder,
498    from: Point,
499    rx: f32,
500    ry: f32,
501    angle: f32,
502    size: ArcSize,
503    sweep: ArcSweep,
504    to: Point,
505) {
506    let p = from;
507    let (px, py) = (p.x, p.y);
508    const TAU: f32 = core::f32::consts::PI * 2.;
509    let (sinphi, cosphi) = angle.sin_cos();
510    let pxp = cosphi * (px - to.x) / 2. + sinphi * (py - to.y) / 2.;
511    let pyp = -sinphi * (px - to.x) / 2. + cosphi * (py - to.y) / 2.;
512    if pxp == 0. && pyp == 0. {
513        return;
514    }
515    let mut rx = rx.abs();
516    let mut ry = ry.abs();
517    let lambda = pxp.powi(2) / rx.powi(2) + pyp.powi(2) / ry.powi(2);
518    if lambda > 1. {
519        let s = lambda.sqrt();
520        rx *= s;
521        ry *= s;
522    }
523    let large_arc = size == ArcSize::Large;
524    let sweep = sweep == ArcSweep::Positive;
525    let (cx, cy, mut ang1, mut ang2) = {
526        fn vec_angle(ux: f32, uy: f32, vx: f32, vy: f32) -> f32 {
527            let sign = if (ux * vy - uy * vx) < 0. { -1. } else { 1. };
528            let dot = (ux * vx + uy * vy).clamp(-1., 1.);
529            sign * dot.acos()
530        }
531        let rxsq = rx * rx;
532        let rysq = ry * ry;
533        let pxpsq = pxp * pxp;
534        let pypsq = pyp * pyp;
535        let mut radicant = (rxsq * rysq) - (rxsq * pypsq) - (rysq * pxpsq);
536        if radicant < 0. {
537            radicant = 0.;
538        }
539        radicant /= (rxsq * pypsq) + (rysq * pxpsq);
540        radicant = radicant.sqrt() * if large_arc == sweep { -1. } else { 1. };
541        let cxp = radicant * rx / ry * pyp;
542        let cyp = radicant * -ry / rx * pxp;
543        let cx = cosphi * cxp - sinphi * cyp + (px + to.x) / 2.;
544        let cy = sinphi * cxp + cosphi * cyp + (py + to.y) / 2.;
545        let vx1 = (pxp - cxp) / rx;
546        let vy1 = (pyp - cyp) / ry;
547        let vx2 = (-pxp - cxp) / rx;
548        let vy2 = (-pyp - cyp) / ry;
549        let ang1 = vec_angle(1., 0., vx1, vy1);
550        let mut ang2 = vec_angle(vx1, vy1, vx2, vy2);
551        if !sweep && ang2 > 0. {
552            ang2 -= TAU;
553        }
554        if sweep && ang2 < 0. {
555            ang2 += TAU;
556        }
557        (cx, cy, ang1, ang2)
558    };
559    let mut ratio = ang2.abs() / (TAU / 4.);
560    if (1. - ratio).abs() < 0.0000001 {
561        ratio = 1.
562    }
563    let segments = ratio.ceil().max(1.);
564    ang2 /= segments;
565    let a = if ang2 == f32::consts::FRAC_PI_2 {
566        0.551915024494
567    } else if ang2 == -f32::consts::FRAC_PI_2 {
568        -0.551915024494
569    } else {
570        4. / 3. * (ang2 / 4.).tan()
571    };
572    for _ in 0..segments as usize {
573        let (y1, x1) = ang1.sin_cos();
574        let (y2, x2) = (ang1 + ang2).sin_cos();
575        let c1 = Point::new((x1 - y1 * a) * rx, (y1 + x1 * a) * ry);
576        let c1 = Point::new(
577            cx + (cosphi * c1.x - sinphi * c1.y),
578            cy + (sinphi * c1.x + cosphi * c1.y),
579        );
580        let c2 = Point::new((x2 + y2 * a) * rx, (y2 - x2 * a) * ry);
581        let c2 = Point::new(
582            cx + (cosphi * c2.x - sinphi * c2.y),
583            cy + (sinphi * c2.x + cosphi * c2.y),
584        );
585        let p = Point::new(x2 * rx, y2 * ry);
586        let p = Point::new(
587            cx + (cosphi * p.x - sinphi * p.y),
588            cy + (sinphi * p.x + cosphi * p.y),
589        );
590        sink.curve_to(c1, c2, p);
591        ang1 += ang2;
592    }
593}