Skip to main content

gpui_kit_material/shapes/
polygon.rs

1// Copyright 2022-2024 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy
5// at http://www.apache.org/licenses/LICENSE-2.0 (see LICENSE-androidx next to
6// this file). Unless required by applicable law or agreed to in writing,
7// software distributed under the License is distributed on an "AS IS" BASIS,
8// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9//
10// Ported to Rust from androidx.graphics:graphics-shapes (RoundedPolygon.kt,
11// Cubic.kt, CornerRounding.kt, Shapes.kt, Utils.kt). Exceptions become `None`
12// or a degenerate shape, so that no input panics.
13//! Rounded polygons: closed outlines of cubic curves, with each vertex's
14//! rounding and the corners the morph matches. All math stays in `f32`, in
15//! the source's operation order, so results match the JVM reference.
16
17/// Below this, two points are the same (the source's `DistanceEpsilon`).
18pub(super) const DISTANCE_EPSILON: f32 = 1e-4;
19/// The source's `AngleEpsilon`.
20pub(super) const ANGLE_EPSILON: f32 = 1e-6;
21const PI: f32 = std::f32::consts::PI;
22
23// Kotlin's `cos(Float)` and its kin evaluate in double precision. Doing the
24// same keeps results independent of the platform's single-precision libm.
25pub(super) fn cos(x: f32) -> f32 {
26    f64::from(x).cos() as f32
27}
28pub(super) fn sin(x: f32) -> f32 {
29    f64::from(x).sin() as f32
30}
31pub(super) fn atan2(y: f32, x: f32) -> f32 {
32    f64::from(y).atan2(f64::from(x)) as f32
33}
34
35pub(super) fn interpolate(start: f32, stop: f32, fraction: f32) -> f32 {
36    (1. - fraction) * start + fraction * stop
37}
38
39pub(super) fn positive_modulo(num: f32, modulus: f32) -> f32 {
40    (num % modulus + modulus) % modulus
41}
42
43/// A point in a shape's own coordinates.
44#[derive(Clone, Copy, Debug, Default, PartialEq)]
45pub(super) struct Pt {
46    pub x: f32,
47    pub y: f32,
48}
49
50pub(super) const fn pt(x: f32, y: f32) -> Pt {
51    Pt { x, y }
52}
53
54impl Pt {
55    pub fn distance(self) -> f32 {
56        (self.x * self.x + self.y * self.y).sqrt()
57    }
58    pub fn distance_squared(self) -> f32 {
59        self.x * self.x + self.y * self.y
60    }
61    fn dot(self, other: Self) -> f32 {
62        self.x * other.x + self.y * other.y
63    }
64    fn dot_xy(self, x: f32, y: f32) -> f32 {
65        self.x * x + self.y * y
66    }
67    fn clockwise(self, other: Self) -> bool {
68        self.x * other.y - self.y * other.x > 0.
69    }
70    /// The unit vector, or zero for a zero vector (the source throws).
71    fn direction(self) -> Self {
72        let distance = self.distance();
73        if distance > 0. { self / distance } else { self }
74    }
75    fn rotate90(self) -> Self {
76        pt(-self.y, self.x)
77    }
78}
79
80impl std::ops::Add for Pt {
81    type Output = Self;
82    fn add(self, other: Self) -> Self {
83        pt(self.x + other.x, self.y + other.y)
84    }
85}
86impl std::ops::Sub for Pt {
87    type Output = Self;
88    fn sub(self, other: Self) -> Self {
89        pt(self.x - other.x, self.y - other.y)
90    }
91}
92impl std::ops::Mul<f32> for Pt {
93    type Output = Self;
94    fn mul(self, operand: f32) -> Self {
95        pt(self.x * operand, self.y * operand)
96    }
97}
98impl std::ops::Div<f32> for Pt {
99    type Output = Self;
100    fn div(self, operand: f32) -> Self {
101        pt(self.x / operand, self.y / operand)
102    }
103}
104
105fn direction_of(x: f32, y: f32) -> Pt {
106    pt(x, y).direction()
107}
108
109fn lerp_pt(start: Pt, stop: Pt, fraction: f32) -> Pt {
110    pt(
111        interpolate(start.x, stop.x, fraction),
112        interpolate(start.y, stop.y, fraction),
113    )
114}
115
116/// One cubic Bézier curve, as anchor 0, control 0, control 1 and anchor 1,
117/// each `x, y`.
118#[derive(Clone, Copy, Debug, Default, PartialEq)]
119pub struct Cubic {
120    /// The curve's eight coordinates, in the order above.
121    pub points: [f32; 8],
122}
123
124impl Cubic {
125    pub(super) const fn new(points: [f32; 8]) -> Self {
126        Self { points }
127    }
128
129    fn from_points(anchor0: Pt, control0: Pt, control1: Pt, anchor1: Pt) -> Self {
130        Self::new([
131            anchor0.x, anchor0.y, control0.x, control0.y, control1.x, control1.y, anchor1.x,
132            anchor1.y,
133        ])
134    }
135
136    /// The point the curve starts at.
137    pub fn anchor0(&self) -> (f32, f32) {
138        (self.points[0], self.points[1])
139    }
140    /// The control point that leaves [`Self::anchor0`].
141    pub fn control0(&self) -> (f32, f32) {
142        (self.points[2], self.points[3])
143    }
144    /// The control point that arrives at [`Self::anchor1`].
145    pub fn control1(&self) -> (f32, f32) {
146        (self.points[4], self.points[5])
147    }
148    /// The point the curve ends at.
149    pub fn anchor1(&self) -> (f32, f32) {
150        (self.points[6], self.points[7])
151    }
152
153    /// The point at parameter `t` (0 at anchor 0, 1 at anchor 1).
154    pub fn point_at(&self, t: f32) -> (f32, f32) {
155        let p = self.point_on_curve(t);
156        (p.x, p.y)
157    }
158
159    pub(super) fn point_on_curve(&self, t: f32) -> Pt {
160        let u = 1. - t;
161        let [a0x, a0y, c0x, c0y, c1x, c1y, a1x, a1y] = self.points;
162        pt(
163            a0x * (u * u * u) + c0x * (3. * t * u * u) + c1x * (3. * t * t * u) + a1x * (t * t * t),
164            a0y * (u * u * u) + c0y * (3. * t * u * u) + c1y * (3. * t * t * u) + a1y * (t * t * t),
165        )
166    }
167
168    fn zero_length(&self) -> bool {
169        (self.points[0] - self.points[6]).abs() < DISTANCE_EPSILON
170            && (self.points[1] - self.points[7]).abs() < DISTANCE_EPSILON
171    }
172
173    /// The curve's bounds as `[left, top, right, bottom]`: tight, or, when
174    /// `approximate`, the box of its anchors and control points.
175    pub(super) fn bounds(&self, approximate: bool) -> [f32; 4] {
176        let [a0x, a0y, c0x, c0y, c1x, c1y, a1x, a1y] = self.points;
177        if self.zero_length() {
178            return [a0x, a0y, a0x, a0y];
179        }
180        let (mut min_x, mut max_x) = (a0x.min(a1x), a0x.max(a1x));
181        let (mut min_y, mut max_y) = (a0y.min(a1y), a0y.max(a1y));
182        if approximate {
183            return [
184                min_x.min(c0x.min(c1x)),
185                min_y.min(c0y.min(c1y)),
186                max_x.max(c0x.max(c1x)),
187                max_y.max(c0y.max(c1y)),
188            ];
189        }
190        // The derivative is a quadratic; its roots are the extrema.
191        let extrema = |a0: f32, c0: f32, c1: f32, a1: f32, each: &mut dyn FnMut(f32)| {
192            let a = -a0 + 3. * c0 - 3. * c1 + a1;
193            let b = 2. * a0 - 4. * c0 + 2. * c1;
194            let c = -a0 + c0;
195            if a.abs() < DISTANCE_EPSILON {
196                if b != 0. {
197                    each(2. * c / (-2. * b));
198                }
199            } else {
200                let s = b * b - 4. * a * c;
201                if s >= 0. {
202                    each((-b + s.sqrt()) / (2. * a));
203                    each((-b - s.sqrt()) / (2. * a));
204                }
205            }
206        };
207        extrema(a0x, c0x, c1x, a1x, &mut |t| {
208            if (0.0..=1.0).contains(&t) {
209                let x = self.point_on_curve(t).x;
210                min_x = min_x.min(x);
211                max_x = max_x.max(x);
212            }
213        });
214        extrema(a0y, c0y, c1y, a1y, &mut |t| {
215            if (0.0..=1.0).contains(&t) {
216                let y = self.point_on_curve(t).y;
217                min_y = min_y.min(y);
218                max_y = max_y.max(y);
219            }
220        });
221        [min_x, min_y, max_x, max_y]
222    }
223
224    pub(super) fn split(&self, t: f32) -> (Self, Self) {
225        let u = 1. - t;
226        let at = self.point_on_curve(t);
227        let [a0x, a0y, c0x, c0y, c1x, c1y, a1x, a1y] = self.points;
228        (
229            Self::new([
230                a0x,
231                a0y,
232                a0x * u + c0x * t,
233                a0y * u + c0y * t,
234                a0x * (u * u) + c0x * (2. * u * t) + c1x * (t * t),
235                a0y * (u * u) + c0y * (2. * u * t) + c1y * (t * t),
236                at.x,
237                at.y,
238            ]),
239            Self::new([
240                at.x,
241                at.y,
242                c0x * (u * u) + c1x * (2. * u * t) + a1x * (t * t),
243                c0y * (u * u) + c1y * (2. * u * t) + a1y * (t * t),
244                c1x * u + a1x * t,
245                c1y * u + a1y * t,
246                a1x,
247                a1y,
248            ]),
249        )
250    }
251
252    fn reversed(&self) -> Self {
253        let [a0x, a0y, c0x, c0y, c1x, c1y, a1x, a1y] = self.points;
254        Self::new([a1x, a1y, c1x, c1y, c0x, c0y, a0x, a0y])
255    }
256
257    fn straight_line(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
258        Self::new([
259            x0,
260            y0,
261            interpolate(x0, x1, 1. / 3.),
262            interpolate(y0, y1, 1. / 3.),
263            interpolate(x0, x1, 2. / 3.),
264            interpolate(y0, y1, 2. / 3.),
265            x1,
266            y1,
267        ])
268    }
269
270    fn circular_arc(center: Pt, x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
271        let p0d = direction_of(x0 - center.x, y0 - center.y);
272        let p1d = direction_of(x1 - center.x, y1 - center.y);
273        let rotated_p0 = p0d.rotate90();
274        let rotated_p1 = p1d.rotate90();
275        let clockwise = rotated_p0.dot_xy(x1 - center.x, y1 - center.y) >= 0.;
276        let cosa = p0d.dot(p1d);
277        if cosa > 0.999 {
278            return Self::straight_line(x0, y0, x1, y1);
279        }
280        let k = pt(x0 - center.x, y0 - center.y).distance() * 4. / 3.
281            * ((2. * (1. - cosa)).max(0.).sqrt() - (1. - cosa * cosa).max(0.).sqrt())
282            / (1. - cosa)
283            * if clockwise { 1. } else { -1. };
284        Self::new([
285            x0,
286            y0,
287            x0 + rotated_p0.x * k,
288            y0 + rotated_p0.y * k,
289            x1 - rotated_p1.x * k,
290            y1 - rotated_p1.y * k,
291            x1,
292            y1,
293        ])
294    }
295
296    fn transformed(&self, f: &impl Fn(f32, f32) -> (f32, f32)) -> Self {
297        let mut points = self.points;
298        for [x, y] in points.as_chunks_mut::<2>().0 {
299            (*x, *y) = f(*x, *y);
300        }
301        Self::new(points)
302    }
303}
304
305/// How a polygon's vertex is rounded.
306#[derive(Clone, Copy, Debug, Default, PartialEq)]
307pub struct CornerRounding {
308    /// The rounding circle's radius, in the polygon's coordinates. 0 is sharp.
309    pub radius: f32,
310    /// 0 rounds with a circular arc alone; up to 1 stretches flanking curves
311    /// from the arc along the edges.
312    pub smoothing: f32,
313}
314
315impl CornerRounding {
316    /// A sharp corner.
317    pub const UNROUNDED: Self = Self::new(0.);
318
319    /// A corner rounded by a circular arc of `radius`.
320    pub const fn new(radius: f32) -> Self {
321        Self {
322            radius,
323            smoothing: 0.,
324        }
325    }
326
327    /// A corner rounded with `radius` and `smoothing`.
328    pub const fn smoothed(radius: f32, smoothing: f32) -> Self {
329        Self { radius, smoothing }
330    }
331}
332
333/// A run of a polygon's outline: a corner (with whether it is convex) or an
334/// edge. The morph maps corners onto corners of the same convexity.
335#[derive(Clone, Debug)]
336pub(super) struct Feature {
337    pub cubics: Vec<Cubic>,
338    /// `Some(convex)` for a corner; `None` for an edge.
339    pub corner: Option<bool>,
340}
341
342/// A closed outline of cubic curves built from vertices with optional
343/// rounding. See `androidx.graphics.shapes.RoundedPolygon`.
344#[derive(Clone, Debug)]
345pub struct RoundedPolygon {
346    pub(super) features: Vec<Feature>,
347    center: Pt,
348    cubics: Vec<Cubic>,
349}
350
351impl RoundedPolygon {
352    fn from_features(features: Vec<Feature>, center: Pt) -> Self {
353        let cubics = flatten(&features, center);
354        Self {
355            features,
356            center,
357            cubics,
358        }
359    }
360
361    /// A polygon through `vertices`, each rounded by `rounding` or, when
362    /// given, by its entry in `per_vertex`. `center` defaults to the
363    /// vertices' average.
364    ///
365    /// `None` with fewer than three vertices, a non-finite coordinate, or a
366    /// `per_vertex` of the wrong length.
367    pub fn from_vertices(
368        vertices: &[(f32, f32)],
369        rounding: CornerRounding,
370        per_vertex: Option<&[CornerRounding]>,
371        center: Option<(f32, f32)>,
372    ) -> Option<Self> {
373        let n = vertices.len();
374        if n < 3
375            || vertices
376                .iter()
377                .any(|v| !v.0.is_finite() || !v.1.is_finite())
378            || per_vertex.is_some_and(|rounds| rounds.len() != n)
379        {
380            return None;
381        }
382        let at = |i: usize| pt(vertices[i].0, vertices[i].1);
383        let corners: Vec<RoundedCorner> = (0..n)
384            .map(|i| {
385                RoundedCorner::new(
386                    at((i + n - 1) % n),
387                    at(i),
388                    at((i + 1) % n),
389                    per_vertex.map_or(rounding, |rounds| rounds[i]),
390                )
391            })
392            .collect();
393
394        // For each side, how much of its corners' rounding and then smoothing
395        // fits: rounding is served first.
396        let cut_adjusts: Vec<(f32, f32)> = (0..n)
397            .map(|i| {
398                let (corner, next) = (&corners[i], &corners[(i + 1) % n]);
399                let expected_round_cut = corner.expected_round_cut + next.expected_round_cut;
400                let expected_cut = corner.expected_cut() + next.expected_cut();
401                let side = (at(i) - at((i + 1) % n)).distance();
402                if expected_round_cut > side {
403                    (side / expected_round_cut, 0.)
404                } else if expected_cut > side {
405                    (
406                        1.,
407                        (side - expected_round_cut) / (expected_cut - expected_round_cut),
408                    )
409                } else {
410                    (1., 1.)
411                }
412            })
413            .collect();
414
415        let corner_cubics: Vec<Vec<Cubic>> = (0..n)
416            .map(|i| {
417                let allowed = |delta: usize| {
418                    let (round_ratio, cut_ratio) = cut_adjusts[(i + n - 1 + delta) % n];
419                    corners[i].expected_round_cut * round_ratio
420                        + (corners[i].expected_cut() - corners[i].expected_round_cut) * cut_ratio
421                };
422                corners[i].cubics(allowed(0), allowed(1))
423            })
424            .collect();
425
426        let mut features = Vec::with_capacity(n * 2);
427        for i in 0..n {
428            let (previous, current, next) = (at((i + n - 1) % n), at(i), at((i + 1) % n));
429            features.push(Feature {
430                cubics: corner_cubics[i].clone(),
431                corner: Some((current - previous).clockwise(next - current)),
432            });
433            let (end, start) = (
434                corner_cubics[i].last().copied().unwrap_or_default(),
435                corner_cubics[(i + 1) % n]
436                    .first()
437                    .copied()
438                    .unwrap_or_default(),
439            );
440            features.push(Feature {
441                cubics: vec![Cubic::straight_line(
442                    end.points[6],
443                    end.points[7],
444                    start.points[0],
445                    start.points[1],
446                )],
447                corner: None,
448            });
449        }
450        let center = center.map_or_else(
451            || {
452                let (sum_x, sum_y) = vertices
453                    .iter()
454                    .fold((0_f32, 0_f32), |(x, y), v| (x + v.0, y + v.1));
455                pt(sum_x / n as f32, sum_y / n as f32)
456            },
457            |(x, y)| pt(x, y),
458        );
459        Some(Self::from_features(features, center))
460    }
461
462    /// A regular polygon of `sides` (at least 3) vertices on a circle of
463    /// `radius`, the first at three o'clock.
464    pub fn regular(
465        sides: usize,
466        radius: f32,
467        center: (f32, f32),
468        rounding: CornerRounding,
469        per_vertex: Option<&[CornerRounding]>,
470    ) -> Option<Self> {
471        let vertices: Vec<(f32, f32)> = (0..sides)
472            .map(|i| {
473                let angle = PI / sides as f32 * 2. * i as f32;
474                (
475                    cos(angle) * radius + center.0,
476                    sin(angle) * radius + center.1,
477                )
478            })
479            .collect();
480        Self::from_vertices(&vertices, rounding, per_vertex, Some(center))
481    }
482
483    /// A circle of `radius`, made by fully rounding `sides` (at least 3)
484    /// vertices.
485    pub fn circle(sides: usize, radius: f32, center: (f32, f32)) -> Option<Self> {
486        if sides < 3 {
487            return None;
488        }
489        let polygon_radius = radius / cos(PI / sides as f32);
490        Self::regular(
491            sides,
492            polygon_radius,
493            center,
494            CornerRounding::new(radius),
495            None,
496        )
497    }
498
499    /// A `width` by `height` rectangle. Vertices run bottom right, bottom
500    /// left, top left, top right.
501    pub fn rectangle(
502        width: f32,
503        height: f32,
504        rounding: CornerRounding,
505        per_vertex: Option<&[CornerRounding]>,
506        center: (f32, f32),
507    ) -> Option<Self> {
508        let (left, top) = (center.0 - width / 2., center.1 - height / 2.);
509        let (right, bottom) = (center.0 + width / 2., center.1 + height / 2.);
510        Self::from_vertices(
511            &[(right, bottom), (left, bottom), (left, top), (right, top)],
512            rounding,
513            per_vertex,
514            Some(center),
515        )
516    }
517
518    /// A star of `points` (at least 2) alternating between `radius` and
519    /// `inner_radius`, which must be positive and smaller. `inner_rounding`
520    /// defaults to `rounding`.
521    pub fn star(
522        points: usize,
523        radius: f32,
524        inner_radius: f32,
525        rounding: CornerRounding,
526        inner_rounding: Option<CornerRounding>,
527        center: (f32, f32),
528    ) -> Option<Self> {
529        if radius.is_nan() || inner_radius.is_nan() {
530            return None;
531        }
532        if radius <= 0. || inner_radius <= 0. || inner_radius >= radius {
533            return None;
534        }
535        let mut vertices = Vec::with_capacity(points * 2);
536        for i in 0..points {
537            let angle = PI / points as f32 * 2. * i as f32;
538            vertices.push((
539                cos(angle) * radius + center.0,
540                sin(angle) * radius + center.1,
541            ));
542            let angle = PI / points as f32 * (2 * i + 1) as f32;
543            vertices.push((
544                cos(angle) * inner_radius + center.0,
545                sin(angle) * inner_radius + center.1,
546            ));
547        }
548        let per_vertex: Option<Vec<CornerRounding>> =
549            inner_rounding.map(|inner| (0..points).flat_map(|_| [rounding, inner]).collect());
550        Self::from_vertices(&vertices, rounding, per_vertex.as_deref(), Some(center))
551    }
552
553    /// A capsule: a `width` by `height` rectangle whose short ends round
554    /// fully, with `smoothing` on the corners.
555    pub fn pill(width: f32, height: f32, smoothing: f32, center: (f32, f32)) -> Option<Self> {
556        if width.is_nan() || height.is_nan() || width <= 0. || height <= 0. {
557            return None;
558        }
559        let (half_w, half_h) = (width / 2., height / 2.);
560        Self::from_vertices(
561            &[
562                (half_w + center.0, half_h + center.1),
563                (-half_w + center.0, half_h + center.1),
564                (-half_w + center.0, -half_h + center.1),
565                (half_w + center.0, -half_h + center.1),
566            ],
567            CornerRounding::smoothed(half_w.min(half_h), smoothing),
568            None,
569            Some(center),
570        )
571    }
572
573    /// The outline, as contiguous curves.
574    pub fn cubics(&self) -> &[Cubic] {
575        &self.cubics
576    }
577
578    /// The center the polygon was built around.
579    pub fn center(&self) -> (f32, f32) {
580        (self.center.x, self.center.y)
581    }
582
583    /// This polygon with `f` applied to every point. Corners keep their
584    /// convexity.
585    pub fn transformed(&self, f: impl Fn(f32, f32) -> (f32, f32)) -> Self {
586        let (x, y) = f(self.center.x, self.center.y);
587        let features = self
588            .features
589            .iter()
590            .map(|feature| Feature {
591                cubics: feature.cubics.iter().map(|c| c.transformed(&f)).collect(),
592                corner: feature.corner,
593            })
594            .collect();
595        Self::from_features(features, pt(x, y))
596    }
597
598    /// This polygon turned `degrees` about the origin.
599    pub fn rotated(&self, degrees: f32) -> Self {
600        let radians = f64::from(degrees) * (std::f64::consts::PI / 180.);
601        let (s, c) = (radians.sin() as f32, radians.cos() as f32);
602        self.transformed(move |x, y| (c * x + -s * y, s * x + c * y))
603    }
604
605    /// This polygon moved and scaled so its [`Self::bounds`] are centered in
606    /// a 0-1 box, which the longer side fills.
607    pub fn normalized(&self) -> Self {
608        let [left, top, right, bottom] = self.bounds(true);
609        let (width, height) = (right - left, bottom - top);
610        let side = width.max(height);
611        if side.is_nan() || side <= 0. {
612            return self.clone();
613        }
614        let offset_x = (side - width) / 2. - left;
615        let offset_y = (side - height) / 2. - top;
616        self.transformed(move |x, y| ((x + offset_x) / side, (y + offset_y) / side))
617    }
618
619    /// The outline's `[left, top, right, bottom]`: tight, or, when
620    /// `approximate`, the box of all anchors and control points.
621    ///
622    /// As in the source, the result's right and bottom start from the
623    /// smallest positive float, so a polygon wholly in negative coordinates
624    /// reports them just above zero.
625    pub fn bounds(&self, approximate: bool) -> [f32; 4] {
626        let mut result = [f32::MAX, f32::MAX, f32::from_bits(1), f32::from_bits(1)];
627        for cubic in &self.cubics {
628            let bounds = cubic.bounds(approximate);
629            result[0] = result[0].min(bounds[0]);
630            result[1] = result[1].min(bounds[1]);
631            result[2] = result[2].max(bounds[2]);
632            result[3] = result[3].max(bounds[3]);
633        }
634        result
635    }
636
637    /// The square around [`Self::center`] that holds the outline at any
638    /// rotation, as `[left, top, right, bottom]`.
639    pub fn max_bounds(&self) -> [f32; 4] {
640        let mut max_squared = 0_f32;
641        for cubic in &self.cubics {
642            let anchor = (pt(cubic.points[0], cubic.points[1]) - self.center).distance_squared();
643            let middle = (cubic.point_on_curve(0.5) - self.center).distance_squared();
644            max_squared = max_squared.max(anchor.max(middle));
645        }
646        let distance = max_squared.sqrt();
647        [
648            self.center.x - distance,
649            self.center.y - distance,
650            self.center.x + distance,
651            self.center.y + distance,
652        ]
653    }
654}
655
656/// The features' curves, starting mid-way through the first corner, without
657/// zero-length curves, and closed exactly on the first anchor.
658fn flatten(features: &[Feature], center: Pt) -> Vec<Cubic> {
659    let mut out = Vec::new();
660    let mut first: Option<Cubic> = None;
661    let mut last: Option<Cubic> = None;
662    let split = features
663        .first()
664        .filter(|feature| feature.cubics.len() == 3)
665        .map(|feature| {
666            let (start, end) = feature.cubics[1].split(0.5);
667            ([feature.cubics[0], start], [end, feature.cubics[2]])
668        });
669    for i in 0..=features.len() {
670        let run: &[Cubic] = match &split {
671            Some(split) if i == 0 => &split.1,
672            Some(split) if i == features.len() => &split.0,
673            None if i == features.len() => break,
674            _ => &features[i].cubics,
675        };
676        for cubic in run {
677            if !cubic.zero_length() {
678                if let Some(last) = last {
679                    out.push(last);
680                }
681                last = Some(*cubic);
682                first.get_or_insert(*cubic);
683            } else if let Some(last) = &mut last {
684                // Dropping several tiny curves in a row must not leave a gap.
685                last.points[6] = cubic.points[6];
686                last.points[7] = cubic.points[7];
687            }
688        }
689    }
690    match (first, last) {
691        (Some(first), Some(mut last)) => {
692            last.points[6] = first.points[0];
693            last.points[7] = first.points[1];
694            out.push(last);
695        }
696        // A zero-sized polygon.
697        _ => out.push(Cubic::new([
698            center.x, center.y, center.x, center.y, center.x, center.y, center.x, center.y,
699        ])),
700    }
701    out
702}
703
704struct RoundedCorner {
705    p0: Pt,
706    p1: Pt,
707    p2: Pt,
708    d1: Pt,
709    d2: Pt,
710    radius: f32,
711    smoothing: f32,
712    expected_round_cut: f32,
713}
714
715impl RoundedCorner {
716    fn new(p0: Pt, p1: Pt, p2: Pt, rounding: CornerRounding) -> Self {
717        let (v01, v21) = (p0 - p1, p2 - p1);
718        let (d01, d21) = (v01.distance(), v21.distance());
719        if d01 > 0. && d21 > 0. {
720            let (d1, d2) = (v01 / d01, v21 / d21);
721            let cos_angle = d1.dot(d2);
722            let sin_angle = (1. - cos_angle * cos_angle).max(0.).sqrt();
723            let expected_round_cut = if f64::from(sin_angle) > 1e-3 {
724                rounding.radius * (cos_angle + 1.) / sin_angle
725            } else {
726                0.
727            };
728            Self {
729                p0,
730                p1,
731                p2,
732                d1,
733                d2,
734                radius: rounding.radius,
735                smoothing: rounding.smoothing,
736                expected_round_cut,
737            }
738        } else {
739            Self {
740                p0,
741                p1,
742                p2,
743                d1: Pt::default(),
744                d2: Pt::default(),
745                radius: 0.,
746                smoothing: 0.,
747                expected_round_cut: 0.,
748            }
749        }
750    }
751
752    fn expected_cut(&self) -> f32 {
753        (1. + self.smoothing) * self.expected_round_cut
754    }
755
756    fn cubics(&self, allowed_cut0: f32, allowed_cut1: f32) -> Vec<Cubic> {
757        let allowed_cut = allowed_cut0.min(allowed_cut1);
758        if self.expected_round_cut < DISTANCE_EPSILON
759            || allowed_cut < DISTANCE_EPSILON
760            || self.radius < DISTANCE_EPSILON
761        {
762            let p = self.p1;
763            return vec![Cubic::straight_line(p.x, p.y, p.x, p.y)];
764        }
765        let round_cut = allowed_cut.min(self.expected_round_cut);
766        let smoothing0 = self.actual_smoothing(allowed_cut0);
767        let smoothing1 = self.actual_smoothing(allowed_cut1);
768        let radius = self.radius * round_cut / self.expected_round_cut;
769        let center_distance = (radius * radius + round_cut * round_cut).sqrt();
770        let center = self.p1 + ((self.d1 + self.d2) / 2.).direction() * center_distance;
771        let intersection0 = self.p1 + self.d1 * round_cut;
772        let intersection2 = self.p1 + self.d2 * round_cut;
773        let flanking0 = flanking_curve(
774            round_cut,
775            smoothing0,
776            self.p1,
777            self.p0,
778            intersection0,
779            intersection2,
780            center,
781            radius,
782        );
783        let flanking2 = flanking_curve(
784            round_cut,
785            smoothing1,
786            self.p1,
787            self.p2,
788            intersection2,
789            intersection0,
790            center,
791            radius,
792        )
793        .reversed();
794        let arc = Cubic::circular_arc(
795            center,
796            flanking0.points[6],
797            flanking0.points[7],
798            flanking2.points[0],
799            flanking2.points[1],
800        );
801        vec![flanking0, arc, flanking2]
802    }
803
804    fn actual_smoothing(&self, allowed_cut: f32) -> f32 {
805        if allowed_cut > self.expected_cut() {
806            self.smoothing
807        } else if allowed_cut > self.expected_round_cut {
808            self.smoothing * (allowed_cut - self.expected_round_cut)
809                / (self.expected_cut() - self.expected_round_cut)
810        } else {
811            0.
812        }
813    }
814}
815
816#[allow(clippy::too_many_arguments)] // Mirrors the source's computeFlankingCurve.
817fn flanking_curve(
818    round_cut: f32,
819    smoothing: f32,
820    corner: Pt,
821    side_start: Pt,
822    intersection: Pt,
823    other_intersection: Pt,
824    center: Pt,
825    radius: f32,
826) -> Cubic {
827    let side_direction = (side_start - corner).direction();
828    let curve_start = corner + side_direction * round_cut * (1. + smoothing);
829    let p = lerp_pt(
830        intersection,
831        (intersection + other_intersection) / 2.,
832        smoothing,
833    );
834    let curve_end = center + direction_of(p.x - center.x, p.y - center.y) * radius;
835    let tangent = (curve_end - center).rotate90();
836    let anchor_end =
837        line_intersection(side_start, side_direction, curve_end, tangent).unwrap_or(intersection);
838    // Two thirds, as the source: it "seems to come from design tools".
839    let anchor_start = (curve_start + anchor_end * 2.) / 3.;
840    Cubic::from_points(curve_start, anchor_start, anchor_end, curve_end)
841}
842
843fn line_intersection(p0: Pt, d0: Pt, p1: Pt, d1: Pt) -> Option<Pt> {
844    let rotated = d1.rotate90();
845    let den = d0.dot(rotated);
846    if den.abs() < DISTANCE_EPSILON {
847        return None;
848    }
849    let num = (p1 - p0).dot(rotated);
850    if den.abs() < DISTANCE_EPSILON * num.abs() {
851        return None;
852    }
853    Some(p0 + d0 * (num / den))
854}
855
856/// The angle of a vector, in degrees.
857pub(super) fn angle_degrees(p: Pt) -> f32 {
858    atan2(p.y, p.x) * 180. / PI
859}
860
861#[cfg(test)]
862mod tests {
863    use super::*;
864    use core::prelude::v1::test;
865
866    #[test]
867    fn bad_input_is_refused_instead_of_panicking() {
868        let none = CornerRounding::UNROUNDED;
869        assert!(RoundedPolygon::from_vertices(&[(0., 0.), (1., 0.)], none, None, None).is_none());
870        let nan = [(0., 0.), (1., f32::NAN), (1., 1.)];
871        assert!(RoundedPolygon::from_vertices(&nan, none, None, None).is_none());
872        assert!(RoundedPolygon::circle(2, 1., (0., 0.)).is_none());
873        assert!(RoundedPolygon::regular(0, 1., (0., 0.), none, None).is_none());
874        assert!(RoundedPolygon::star(5, 1., 2., none, None, (0., 0.)).is_none());
875        assert!(RoundedPolygon::star(5, f32::NAN, 0.5, none, None, (0., 0.)).is_none());
876        assert!(RoundedPolygon::pill(0., 1., 0., (0., 0.)).is_none());
877        let short = [CornerRounding::new(0.1)];
878        let triangle = [(0., 0.), (1., 0.), (0., 1.)];
879        assert!(RoundedPolygon::from_vertices(&triangle, none, Some(&short), None).is_none());
880    }
881
882    #[test]
883    fn a_zero_sized_polygon_is_a_single_point_curve() {
884        let point = [(1., 1.); 4];
885        let polygon = RoundedPolygon::from_vertices(&point, CornerRounding::new(0.2), None, None)
886            .expect("finite");
887        assert_eq!(polygon.cubics().len(), 1);
888        assert_eq!(polygon.cubics()[0].anchor0(), (1., 1.));
889        // Normalizing has no side to divide by; it must not produce NaN.
890        assert_eq!(polygon.normalized().cubics()[0].anchor0(), (1., 1.));
891    }
892
893    #[test]
894    fn the_outline_closes_exactly() {
895        let polygon = RoundedPolygon::star(
896            6,
897            1.,
898            0.5,
899            CornerRounding::smoothed(0.1, 0.5),
900            None,
901            (0., 0.),
902        )
903        .expect("valid");
904        let cubics = polygon.cubics();
905        for pair in cubics.windows(2) {
906            let (a, b) = (pair[0].anchor1(), pair[1].anchor0());
907            assert!((a.0 - b.0).abs() < DISTANCE_EPSILON && (a.1 - b.1).abs() < DISTANCE_EPSILON);
908        }
909        assert_eq!(
910            cubics.last().map(Cubic::anchor1),
911            cubics.first().map(Cubic::anchor0)
912        );
913    }
914}