ruisa_path/
path_geometry.rs

1// Copyright 2006 The Android Open Source Project
2// Copyright 2020 Yevhenii Reizner
3//
4// Use of this source code is governed by a BSD-style license that can be
5// found in the LICENSE file.
6
7//! A collection of functions to work with Bezier paths.
8//!
9//! Mainly for internal use. Do not rely on it!
10
11#![allow(missing_docs)]
12
13use crate::{Point, Transform};
14
15use crate::f32x2_t::f32x2;
16use crate::floating_point::FLOAT_PI;
17use crate::scalar::{Scalar, SCALAR_NEARLY_ZERO, SCALAR_ROOT_2_OVER_2};
18
19use crate::floating_point::{NormalizedF32, NormalizedF32Exclusive};
20use crate::path_builder::PathDirection;
21
22#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
23use crate::NoStdFloat;
24
25// use for : eval(t) == A * t^2 + B * t + C
26#[derive(Clone, Copy, Default, Debug)]
27pub struct QuadCoeff {
28    pub a: f32x2,
29    pub b: f32x2,
30    pub c: f32x2,
31}
32
33impl QuadCoeff {
34    pub fn from_points(points: &[Point; 3]) -> Self {
35        let c = points[0].to_f32x2();
36        let p1 = points[1].to_f32x2();
37        let p2 = points[2].to_f32x2();
38        let b = times_2(p1 - c);
39        let a = p2 - times_2(p1) + c;
40
41        QuadCoeff { a, b, c }
42    }
43
44    pub fn eval(&self, t: f32x2) -> f32x2 {
45        (self.a * t + self.b) * t + self.c
46    }
47}
48
49#[derive(Clone, Copy, Default, Debug)]
50pub struct CubicCoeff {
51    pub a: f32x2,
52    pub b: f32x2,
53    pub c: f32x2,
54    pub d: f32x2,
55}
56
57impl CubicCoeff {
58    pub fn from_points(points: &[Point; 4]) -> Self {
59        let p0 = points[0].to_f32x2();
60        let p1 = points[1].to_f32x2();
61        let p2 = points[2].to_f32x2();
62        let p3 = points[3].to_f32x2();
63        let three = f32x2::splat(3.0);
64
65        CubicCoeff {
66            a: p3 + three * (p1 - p2) - p0,
67            b: three * (p2 - times_2(p1) + p0),
68            c: three * (p1 - p0),
69            d: p0,
70        }
71    }
72
73    pub fn eval(&self, t: f32x2) -> f32x2 {
74        ((self.a * t + self.b) * t + self.c) * t + self.d
75    }
76}
77
78// TODO: to a custom type?
79pub fn new_t_values() -> [NormalizedF32Exclusive; 3] {
80    [
81        NormalizedF32Exclusive::ANY,
82        NormalizedF32Exclusive::ANY,
83        NormalizedF32Exclusive::ANY,
84    ]
85}
86
87pub fn chop_quad_at(src: &[Point], t: NormalizedF32Exclusive, dst: &mut [Point; 5]) {
88    let p0 = src[0].to_f32x2();
89    let p1 = src[1].to_f32x2();
90    let p2 = src[2].to_f32x2();
91    let tt = f32x2::splat(t.get());
92
93    let p01 = interp(p0, p1, tt);
94    let p12 = interp(p1, p2, tt);
95
96    dst[0] = Point::from_f32x2(p0);
97    dst[1] = Point::from_f32x2(p01);
98    dst[2] = Point::from_f32x2(interp(p01, p12, tt));
99    dst[3] = Point::from_f32x2(p12);
100    dst[4] = Point::from_f32x2(p2);
101}
102
103// From Numerical Recipes in C.
104//
105// Q = -1/2 (B + sign(B) sqrt[B*B - 4*A*C])
106// x1 = Q / A
107// x2 = C / Q
108pub fn find_unit_quad_roots(
109    a: f32,
110    b: f32,
111    c: f32,
112    roots: &mut [NormalizedF32Exclusive; 3],
113) -> usize {
114    if a == 0.0 {
115        if let Some(r) = valid_unit_divide(-c, b) {
116            roots[0] = r;
117            return 1;
118        } else {
119            return 0;
120        }
121    }
122
123    // use doubles so we don't overflow temporarily trying to compute R
124    let mut dr = f64::from(b) * f64::from(b) - 4.0 * f64::from(a) * f64::from(c);
125    if dr < 0.0 {
126        return 0;
127    }
128    dr = dr.sqrt();
129    let r = dr as f32;
130    if !r.is_finite() {
131        return 0;
132    }
133
134    let q = if b < 0.0 {
135        -(b - r) / 2.0
136    } else {
137        -(b + r) / 2.0
138    };
139
140    let mut roots_offset = 0;
141    if let Some(r) = valid_unit_divide(q, a) {
142        roots[roots_offset] = r;
143        roots_offset += 1;
144    }
145
146    if let Some(r) = valid_unit_divide(c, q) {
147        roots[roots_offset] = r;
148        roots_offset += 1;
149    }
150
151    if roots_offset == 2 {
152        if roots[0].get() > roots[1].get() {
153            roots.swap(0, 1);
154        } else if roots[0] == roots[1] {
155            // nearly-equal?
156            roots_offset -= 1; // skip the double root
157        }
158    }
159
160    roots_offset
161}
162
163pub fn chop_cubic_at2(src: &[Point; 4], t: NormalizedF32Exclusive, dst: &mut [Point]) {
164    let p0 = src[0].to_f32x2();
165    let p1 = src[1].to_f32x2();
166    let p2 = src[2].to_f32x2();
167    let p3 = src[3].to_f32x2();
168    let tt = f32x2::splat(t.get());
169
170    let ab = interp(p0, p1, tt);
171    let bc = interp(p1, p2, tt);
172    let cd = interp(p2, p3, tt);
173    let abc = interp(ab, bc, tt);
174    let bcd = interp(bc, cd, tt);
175    let abcd = interp(abc, bcd, tt);
176
177    dst[0] = Point::from_f32x2(p0);
178    dst[1] = Point::from_f32x2(ab);
179    dst[2] = Point::from_f32x2(abc);
180    dst[3] = Point::from_f32x2(abcd);
181    dst[4] = Point::from_f32x2(bcd);
182    dst[5] = Point::from_f32x2(cd);
183    dst[6] = Point::from_f32x2(p3);
184}
185
186pub fn valid_unit_divide(mut numer: f32, mut denom: f32) -> Option<NormalizedF32Exclusive> {
187    if numer < 0.0 {
188        numer = -numer;
189        denom = -denom;
190    }
191
192    if denom == 0.0 || numer == 0.0 || numer >= denom {
193        return None;
194    }
195
196    let r = numer / denom;
197    NormalizedF32Exclusive::new(r)
198}
199
200fn interp(v0: f32x2, v1: f32x2, t: f32x2) -> f32x2 {
201    v0 + (v1 - v0) * t
202}
203
204fn times_2(value: f32x2) -> f32x2 {
205    value + value
206}
207
208// F(t)    = a (1 - t) ^ 2 + 2 b t (1 - t) + c t ^ 2
209// F'(t)   = 2 (b - a) + 2 (a - 2b + c) t
210// F''(t)  = 2 (a - 2b + c)
211//
212// A = 2 (b - a)
213// B = 2 (a - 2b + c)
214//
215// Maximum curvature for a quadratic means solving
216// Fx' Fx'' + Fy' Fy'' = 0
217//
218// t = - (Ax Bx + Ay By) / (Bx ^ 2 + By ^ 2)
219pub(crate) fn find_quad_max_curvature(src: &[Point; 3]) -> NormalizedF32 {
220    let ax = src[1].x - src[0].x;
221    let ay = src[1].y - src[0].y;
222    let bx = src[0].x - src[1].x - src[1].x + src[2].x;
223    let by = src[0].y - src[1].y - src[1].y + src[2].y;
224
225    let mut numer = -(ax * bx + ay * by);
226    let mut denom = bx * bx + by * by;
227    if denom < 0.0 {
228        numer = -numer;
229        denom = -denom;
230    }
231
232    if numer <= 0.0 {
233        return NormalizedF32::ZERO;
234    }
235
236    if numer >= denom {
237        // Also catches denom=0
238        return NormalizedF32::ONE;
239    }
240
241    let t = numer / denom;
242    NormalizedF32::new(t).unwrap()
243}
244
245pub(crate) fn eval_quad_at(src: &[Point; 3], t: NormalizedF32) -> Point {
246    Point::from_f32x2(QuadCoeff::from_points(src).eval(f32x2::splat(t.get())))
247}
248
249pub(crate) fn eval_quad_tangent_at(src: &[Point; 3], tol: NormalizedF32) -> Point {
250    // The derivative equation is 2(b - a +(a - 2b +c)t). This returns a
251    // zero tangent vector when t is 0 or 1, and the control point is equal
252    // to the end point. In this case, use the quad end points to compute the tangent.
253    if (tol == NormalizedF32::ZERO && src[0] == src[1])
254        || (tol == NormalizedF32::ONE && src[1] == src[2])
255    {
256        return src[2] - src[0];
257    }
258
259    let p0 = src[0].to_f32x2();
260    let p1 = src[1].to_f32x2();
261    let p2 = src[2].to_f32x2();
262
263    let b = p1 - p0;
264    let a = p2 - p1 - b;
265    let t = a * f32x2::splat(tol.get()) + b;
266
267    Point::from_f32x2(t + t)
268}
269
270// Looking for F' dot F'' == 0
271//
272// A = b - a
273// B = c - 2b + a
274// C = d - 3c + 3b - a
275//
276// F' = 3Ct^2 + 6Bt + 3A
277// F'' = 6Ct + 6B
278//
279// F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
280pub fn find_cubic_max_curvature<'a>(
281    src: &[Point; 4],
282    t_values: &'a mut [NormalizedF32; 3],
283) -> &'a [NormalizedF32] {
284    let mut coeff_x = formulate_f1_dot_f2(&[src[0].x, src[1].x, src[2].x, src[3].x]);
285    let coeff_y = formulate_f1_dot_f2(&[src[0].y, src[1].y, src[2].y, src[3].y]);
286
287    for i in 0..4 {
288        coeff_x[i] += coeff_y[i];
289    }
290
291    let len = solve_cubic_poly(&coeff_x, t_values);
292    &t_values[0..len]
293}
294
295// Looking for F' dot F'' == 0
296//
297// A = b - a
298// B = c - 2b + a
299// C = d - 3c + 3b - a
300//
301// F' = 3Ct^2 + 6Bt + 3A
302// F'' = 6Ct + 6B
303//
304// F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
305fn formulate_f1_dot_f2(src: &[f32; 4]) -> [f32; 4] {
306    let a = src[1] - src[0];
307    let b = src[2] - 2.0 * src[1] + src[0];
308    let c = src[3] + 3.0 * (src[1] - src[2]) - src[0];
309
310    [c * c, 3.0 * b * c, 2.0 * b * b + c * a, a * b]
311}
312
313/// Solve coeff(t) == 0, returning the number of roots that lie withing 0 < t < 1.
314/// coeff[0]t^3 + coeff[1]t^2 + coeff[2]t + coeff[3]
315///
316/// Eliminates repeated roots (so that all t_values are distinct, and are always
317/// in increasing order.
318fn solve_cubic_poly(coeff: &[f32; 4], t_values: &mut [NormalizedF32; 3]) -> usize {
319    if coeff[0].is_nearly_zero() {
320        // we're just a quadratic
321        let mut tmp_t = new_t_values();
322        let count = find_unit_quad_roots(coeff[1], coeff[2], coeff[3], &mut tmp_t);
323        for i in 0..count {
324            t_values[i] = tmp_t[i].to_normalized();
325        }
326
327        return count;
328    }
329
330    debug_assert!(coeff[0] != 0.0);
331
332    let inva = coeff[0].invert();
333    let a = coeff[1] * inva;
334    let b = coeff[2] * inva;
335    let c = coeff[3] * inva;
336
337    let q = (a * a - b * 3.0) / 9.0;
338    let r = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0;
339
340    let q3 = q * q * q;
341    let r2_minus_q3 = r * r - q3;
342    let adiv3 = a / 3.0;
343
344    if r2_minus_q3 < 0.0 {
345        // we have 3 real roots
346        // the divide/root can, due to finite precisions, be slightly outside of -1...1
347        let theta = (r / q3.sqrt()).bound(-1.0, 1.0).acos();
348        let neg2_root_q = -2.0 * q.sqrt();
349
350        t_values[0] = NormalizedF32::new_clamped(neg2_root_q * (theta / 3.0).cos() - adiv3);
351        t_values[1] = NormalizedF32::new_clamped(
352            neg2_root_q * ((theta + 2.0 * FLOAT_PI) / 3.0).cos() - adiv3,
353        );
354        t_values[2] = NormalizedF32::new_clamped(
355            neg2_root_q * ((theta - 2.0 * FLOAT_PI) / 3.0).cos() - adiv3,
356        );
357
358        // now sort the roots
359        sort_array3(t_values);
360        collapse_duplicates3(t_values)
361    } else {
362        // we have 1 real root
363        let mut a = r.abs() + r2_minus_q3.sqrt();
364        a = scalar_cube_root(a);
365        if r > 0.0 {
366            a = -a;
367        }
368
369        if a != 0.0 {
370            a += q / a;
371        }
372
373        t_values[0] = NormalizedF32::new_clamped(a - adiv3);
374        1
375    }
376}
377
378fn sort_array3(array: &mut [NormalizedF32; 3]) {
379    if array[0] > array[1] {
380        array.swap(0, 1);
381    }
382
383    if array[1] > array[2] {
384        array.swap(1, 2);
385    }
386
387    if array[0] > array[1] {
388        array.swap(0, 1);
389    }
390}
391
392fn collapse_duplicates3(array: &mut [NormalizedF32; 3]) -> usize {
393    let mut len = 3;
394
395    if array[1] == array[2] {
396        len = 2;
397    }
398
399    if array[0] == array[1] {
400        len = 1;
401    }
402
403    len
404}
405
406fn scalar_cube_root(x: f32) -> f32 {
407    x.powf(0.3333333)
408}
409
410// This is SkEvalCubicAt split into three functions.
411pub(crate) fn eval_cubic_pos_at(src: &[Point; 4], t: NormalizedF32) -> Point {
412    Point::from_f32x2(CubicCoeff::from_points(src).eval(f32x2::splat(t.get())))
413}
414
415// This is SkEvalCubicAt split into three functions.
416pub(crate) fn eval_cubic_tangent_at(src: &[Point; 4], t: NormalizedF32) -> Point {
417    // The derivative equation returns a zero tangent vector when t is 0 or 1, and the
418    // adjacent control point is equal to the end point. In this case, use the
419    // next control point or the end points to compute the tangent.
420    if (t.get() == 0.0 && src[0] == src[1]) || (t.get() == 1.0 && src[2] == src[3]) {
421        let mut tangent = if t.get() == 0.0 {
422            src[2] - src[0]
423        } else {
424            src[3] - src[1]
425        };
426
427        if tangent.x == 0.0 && tangent.y == 0.0 {
428            tangent = src[3] - src[0];
429        }
430
431        tangent
432    } else {
433        eval_cubic_derivative(src, t)
434    }
435}
436
437fn eval_cubic_derivative(src: &[Point; 4], t: NormalizedF32) -> Point {
438    let p0 = src[0].to_f32x2();
439    let p1 = src[1].to_f32x2();
440    let p2 = src[2].to_f32x2();
441    let p3 = src[3].to_f32x2();
442
443    let coeff = QuadCoeff {
444        a: p3 + f32x2::splat(3.0) * (p1 - p2) - p0,
445        b: times_2(p2 - times_2(p1) + p0),
446        c: p1 - p0,
447    };
448
449    Point::from_f32x2(coeff.eval(f32x2::splat(t.get())))
450}
451
452// http://www.faculty.idc.ac.il/arik/quality/appendixA.html
453//
454// Inflection means that curvature is zero.
455// Curvature is [F' x F''] / [F'^3]
456// So we solve F'x X F''y - F'y X F''y == 0
457// After some canceling of the cubic term, we get
458// A = b - a
459// B = c - 2b + a
460// C = d - 3c + 3b - a
461// (BxCy - ByCx)t^2 + (AxCy - AyCx)t + AxBy - AyBx == 0
462pub(crate) fn find_cubic_inflections<'a>(
463    src: &[Point; 4],
464    t_values: &'a mut [NormalizedF32Exclusive; 3],
465) -> &'a [NormalizedF32Exclusive] {
466    let ax = src[1].x - src[0].x;
467    let ay = src[1].y - src[0].y;
468    let bx = src[2].x - 2.0 * src[1].x + src[0].x;
469    let by = src[2].y - 2.0 * src[1].y + src[0].y;
470    let cx = src[3].x + 3.0 * (src[1].x - src[2].x) - src[0].x;
471    let cy = src[3].y + 3.0 * (src[1].y - src[2].y) - src[0].y;
472
473    let len = find_unit_quad_roots(
474        bx * cy - by * cx,
475        ax * cy - ay * cx,
476        ax * by - ay * bx,
477        t_values,
478    );
479
480    &t_values[0..len]
481}
482
483// Return location (in t) of cubic cusp, if there is one.
484// Note that classify cubic code does not reliably return all cusp'd cubics, so
485// it is not called here.
486pub(crate) fn find_cubic_cusp(src: &[Point; 4]) -> Option<NormalizedF32Exclusive> {
487    // When the adjacent control point matches the end point, it behaves as if
488    // the cubic has a cusp: there's a point of max curvature where the derivative
489    // goes to zero. Ideally, this would be where t is zero or one, but math
490    // error makes not so. It is not uncommon to create cubics this way; skip them.
491    if src[0] == src[1] {
492        return None;
493    }
494
495    if src[2] == src[3] {
496        return None;
497    }
498
499    // Cubics only have a cusp if the line segments formed by the control and end points cross.
500    // Detect crossing if line ends are on opposite sides of plane formed by the other line.
501    if on_same_side(src, 0, 2) || on_same_side(src, 2, 0) {
502        return None;
503    }
504
505    // Cubics may have multiple points of maximum curvature, although at most only
506    // one is a cusp.
507    let mut t_values = [NormalizedF32::ZERO; 3];
508    let max_curvature = find_cubic_max_curvature(src, &mut t_values);
509    for test_t in max_curvature {
510        if 0.0 >= test_t.get() || test_t.get() >= 1.0 {
511            // no need to consider max curvature on the end
512            continue;
513        }
514
515        // A cusp is at the max curvature, and also has a derivative close to zero.
516        // Choose the 'close to zero' meaning by comparing the derivative length
517        // with the overall cubic size.
518        let d_pt = eval_cubic_derivative(src, *test_t);
519        let d_pt_magnitude = d_pt.length_sqd();
520        let precision = calc_cubic_precision(src);
521        if d_pt_magnitude < precision {
522            // All three max curvature t values may be close to the cusp;
523            // return the first one.
524            return Some(NormalizedF32Exclusive::new_bounded(test_t.get()));
525        }
526    }
527
528    None
529}
530
531// Returns true if both points src[testIndex], src[testIndex+1] are in the same half plane defined
532// by the line segment src[lineIndex], src[lineIndex+1].
533fn on_same_side(src: &[Point; 4], test_index: usize, line_index: usize) -> bool {
534    let origin = src[line_index];
535    let line = src[line_index + 1] - origin;
536    let mut crosses = [0.0, 0.0];
537    for index in 0..2 {
538        let test_line = src[test_index + index] - origin;
539        crosses[index] = line.cross(test_line);
540    }
541
542    crosses[0] * crosses[1] >= 0.0
543}
544
545// Returns a constant proportional to the dimensions of the cubic.
546// Constant found through experimentation -- maybe there's a better way....
547fn calc_cubic_precision(src: &[Point; 4]) -> f32 {
548    (src[1].distance_to_sqd(src[0])
549        + src[2].distance_to_sqd(src[1])
550        + src[3].distance_to_sqd(src[2]))
551        * 1e-8
552}
553
554#[derive(Copy, Clone, Default, Debug)]
555pub(crate) struct Conic {
556    pub points: [Point; 3],
557    pub weight: f32,
558}
559
560impl Conic {
561    pub fn new(pt0: Point, pt1: Point, pt2: Point, weight: f32) -> Self {
562        Conic {
563            points: [pt0, pt1, pt2],
564            weight,
565        }
566    }
567
568    pub fn from_points(points: &[Point], weight: f32) -> Self {
569        Conic {
570            points: [points[0], points[1], points[2]],
571            weight,
572        }
573    }
574
575    fn compute_quad_pow2(&self, tolerance: f32) -> Option<u8> {
576        if tolerance < 0.0 || !tolerance.is_finite() {
577            return None;
578        }
579
580        if !self.points[0].is_finite() || !self.points[1].is_finite() || !self.points[2].is_finite()
581        {
582            return None;
583        }
584
585        // Limit the number of suggested quads to approximate a conic
586        const MAX_CONIC_TO_QUAD_POW2: usize = 4;
587
588        // "High order approximation of conic sections by quadratic splines"
589        // by Michael Floater, 1993
590        let a = self.weight - 1.0;
591        let k = a / (4.0 * (2.0 + a));
592        let x = k * (self.points[0].x - 2.0 * self.points[1].x + self.points[2].x);
593        let y = k * (self.points[0].y - 2.0 * self.points[1].y + self.points[2].y);
594
595        let mut error = (x * x + y * y).sqrt();
596        let mut pow2 = 0;
597        for _ in 0..MAX_CONIC_TO_QUAD_POW2 {
598            if error <= tolerance {
599                break;
600            }
601
602            error *= 0.25;
603            pow2 += 1;
604        }
605
606        // Unlike Skia, we always expect `pow2` to be at least 1.
607        // Otherwise it produces ugly results.
608        Some(pow2.max(1))
609    }
610
611    // Chop this conic into N quads, stored continuously in pts[], where
612    // N = 1 << pow2. The amount of storage needed is (1 + 2 * N)
613    pub fn chop_into_quads_pow2(&self, pow2: u8, points: &mut [Point]) -> u8 {
614        debug_assert!(pow2 < 5);
615
616        points[0] = self.points[0];
617        subdivide(self, &mut points[1..], pow2);
618
619        let quad_count = 1 << pow2;
620        let pt_count = 2 * quad_count + 1;
621        if points.iter().take(pt_count).any(|n| !n.is_finite()) {
622            // if we generated a non-finite, pin ourselves to the middle of the hull,
623            // as our first and last are already on the first/last pts of the hull.
624            for p in points.iter_mut().take(pt_count - 1).skip(1) {
625                *p = self.points[1];
626            }
627        }
628
629        1 << pow2
630    }
631
632    fn chop(&self) -> (Conic, Conic) {
633        let scale = f32x2::splat((1.0 + self.weight).invert());
634        let new_w = subdivide_weight_value(self.weight);
635
636        let p0 = self.points[0].to_f32x2();
637        let p1 = self.points[1].to_f32x2();
638        let p2 = self.points[2].to_f32x2();
639        let ww = f32x2::splat(self.weight);
640
641        let wp1 = ww * p1;
642        let m = (p0 + times_2(wp1) + p2) * scale * f32x2::splat(0.5);
643        let mut m_pt = Point::from_f32x2(m);
644        if !m_pt.is_finite() {
645            let w_d = self.weight as f64;
646            let w_2 = w_d * 2.0;
647            let scale_half = 1.0 / (1.0 + w_d) * 0.5;
648            m_pt.x = ((self.points[0].x as f64
649                + w_2 * self.points[1].x as f64
650                + self.points[2].x as f64)
651                * scale_half) as f32;
652
653            m_pt.y = ((self.points[0].y as f64
654                + w_2 * self.points[1].y as f64
655                + self.points[2].y as f64)
656                * scale_half) as f32;
657        }
658
659        (
660            Conic {
661                points: [self.points[0], Point::from_f32x2((p0 + wp1) * scale), m_pt],
662                weight: new_w,
663            },
664            Conic {
665                points: [m_pt, Point::from_f32x2((wp1 + p2) * scale), self.points[2]],
666                weight: new_w,
667            },
668        )
669    }
670
671    pub fn build_unit_arc(
672        u_start: Point,
673        u_stop: Point,
674        dir: PathDirection,
675        user_transform: Transform,
676        dst: &mut [Conic; 5],
677    ) -> Option<&[Conic]> {
678        // rotate by x,y so that u_start is (1.0)
679        let x = u_start.dot(u_stop);
680        let mut y = u_start.cross(u_stop);
681
682        let abs_y = y.abs();
683
684        // check for (effectively) coincident vectors
685        // this can happen if our angle is nearly 0 or nearly 180 (y == 0)
686        // ... we use the dot-prod to distinguish between 0 and 180 (x > 0)
687        if abs_y <= SCALAR_NEARLY_ZERO
688            && x > 0.0
689            && ((y >= 0.0 && dir == PathDirection::CW) || (y <= 0.0 && dir == PathDirection::CCW))
690        {
691            return None;
692        }
693
694        if dir == PathDirection::CCW {
695            y = -y;
696        }
697
698        // We decide to use 1-conic per quadrant of a circle. What quadrant does [xy] lie in?
699        //      0 == [0  .. 90)
700        //      1 == [90 ..180)
701        //      2 == [180..270)
702        //      3 == [270..360)
703        //
704        let mut quadrant = 0;
705        if y == 0.0 {
706            quadrant = 2; // 180
707            debug_assert!((x + 1.0) <= SCALAR_NEARLY_ZERO);
708        } else if x == 0.0 {
709            debug_assert!(abs_y - 1.0 <= SCALAR_NEARLY_ZERO);
710            quadrant = if y > 0.0 { 1 } else { 3 }; // 90 / 270
711        } else {
712            if y < 0.0 {
713                quadrant += 2;
714            }
715
716            if (x < 0.0) != (y < 0.0) {
717                quadrant += 1;
718            }
719        }
720
721        let quadrant_points = [
722            Point::from_xy(1.0, 0.0),
723            Point::from_xy(1.0, 1.0),
724            Point::from_xy(0.0, 1.0),
725            Point::from_xy(-1.0, 1.0),
726            Point::from_xy(-1.0, 0.0),
727            Point::from_xy(-1.0, -1.0),
728            Point::from_xy(0.0, -1.0),
729            Point::from_xy(1.0, -1.0),
730        ];
731
732        const QUADRANT_WEIGHT: f32 = SCALAR_ROOT_2_OVER_2;
733
734        let mut conic_count = quadrant;
735        for i in 0..conic_count {
736            dst[i] = Conic::from_points(&quadrant_points[i * 2..], QUADRANT_WEIGHT);
737        }
738
739        // Now compute any remaing (sub-90-degree) arc for the last conic
740        let final_pt = Point::from_xy(x, y);
741        let last_q = quadrant_points[quadrant * 2]; // will already be a unit-vector
742        let dot = last_q.dot(final_pt);
743        debug_assert!(0.0 <= dot && dot <= 1.0 + SCALAR_NEARLY_ZERO);
744
745        if dot < 1.0 {
746            let mut off_curve = Point::from_xy(last_q.x + x, last_q.y + y);
747            // compute the bisector vector, and then rescale to be the off-curve point.
748            // we compute its length from cos(theta/2) = length / 1, using half-angle identity we get
749            // length = sqrt(2 / (1 + cos(theta)). We already have cos() when to computed the dot.
750            // This is nice, since our computed weight is cos(theta/2) as well!
751            let cos_theta_over_2 = ((1.0 + dot) / 2.0).sqrt();
752            off_curve.set_length(cos_theta_over_2.invert());
753            if !last_q.almost_equal(off_curve) {
754                dst[conic_count] = Conic::new(last_q, off_curve, final_pt, cos_theta_over_2);
755                conic_count += 1;
756            }
757        }
758
759        // now handle counter-clockwise and the initial unitStart rotation
760        let mut transform = Transform::from_sin_cos(u_start.y, u_start.x);
761        if dir == PathDirection::CCW {
762            transform = transform.pre_scale(1.0, -1.0);
763        }
764
765        transform = transform.post_concat(user_transform);
766
767        for conic in dst.iter_mut().take(conic_count) {
768            transform.map_points(&mut conic.points);
769        }
770
771        if conic_count == 0 {
772            None
773        } else {
774            Some(&dst[0..conic_count])
775        }
776    }
777}
778
779fn subdivide_weight_value(w: f32) -> f32 {
780    (0.5 + w * 0.5).sqrt()
781}
782
783fn subdivide<'a>(src: &Conic, mut points: &'a mut [Point], mut level: u8) -> &'a mut [Point] {
784    if level == 0 {
785        points[0] = src.points[1];
786        points[1] = src.points[2];
787        &mut points[2..]
788    } else {
789        let mut dst = src.chop();
790
791        let start_y = src.points[0].y;
792        let end_y = src.points[2].y;
793        if between(start_y, src.points[1].y, end_y) {
794            // If the input is monotonic and the output is not, the scan converter hangs.
795            // Ensure that the chopped conics maintain their y-order.
796            let mid_y = dst.0.points[2].y;
797            if !between(start_y, mid_y, end_y) {
798                // If the computed midpoint is outside the ends, move it to the closer one.
799                let closer_y = if (mid_y - start_y).abs() < (mid_y - end_y).abs() {
800                    start_y
801                } else {
802                    end_y
803                };
804                dst.0.points[2].y = closer_y;
805                dst.1.points[0].y = closer_y;
806            }
807
808            if !between(start_y, dst.0.points[1].y, dst.0.points[2].y) {
809                // If the 1st control is not between the start and end, put it at the start.
810                // This also reduces the quad to a line.
811                dst.0.points[1].y = start_y;
812            }
813
814            if !between(dst.1.points[0].y, dst.1.points[1].y, end_y) {
815                // If the 2nd control is not between the start and end, put it at the end.
816                // This also reduces the quad to a line.
817                dst.1.points[1].y = end_y;
818            }
819
820            // Verify that all five points are in order.
821            debug_assert!(between(start_y, dst.0.points[1].y, dst.0.points[2].y));
822            debug_assert!(between(
823                dst.0.points[1].y,
824                dst.0.points[2].y,
825                dst.1.points[1].y
826            ));
827            debug_assert!(between(dst.0.points[2].y, dst.1.points[1].y, end_y));
828        }
829
830        level -= 1;
831        points = subdivide(&dst.0, points, level);
832        subdivide(&dst.1, points, level)
833    }
834}
835
836// This was originally developed and tested for pathops: see SkOpTypes.h
837// returns true if (a <= b <= c) || (a >= b >= c)
838fn between(a: f32, b: f32, c: f32) -> bool {
839    (a - b) * (c - b) <= 0.0
840}
841
842pub(crate) struct AutoConicToQuads {
843    pub points: [Point; 64],
844    pub len: u8, // the number of quads
845}
846
847impl AutoConicToQuads {
848    pub fn compute(pt0: Point, pt1: Point, pt2: Point, weight: f32) -> Option<Self> {
849        let conic = Conic::new(pt0, pt1, pt2, weight);
850        let pow2 = conic.compute_quad_pow2(0.25)?;
851        let mut points = [Point::zero(); 64];
852        let len = conic.chop_into_quads_pow2(pow2, &mut points);
853        Some(AutoConicToQuads { points, len })
854    }
855}
856
857#[cfg(test)]
858mod tests {
859    use super::*;
860
861    #[test]
862    fn eval_cubic_at_1() {
863        let src = [
864            Point::from_xy(30.0, 40.0),
865            Point::from_xy(30.0, 40.0),
866            Point::from_xy(171.0, 45.0),
867            Point::from_xy(180.0, 155.0),
868        ];
869
870        assert_eq!(
871            eval_cubic_pos_at(&src, NormalizedF32::ZERO),
872            Point::from_xy(30.0, 40.0)
873        );
874        assert_eq!(
875            eval_cubic_tangent_at(&src, NormalizedF32::ZERO),
876            Point::from_xy(141.0, 5.0)
877        );
878    }
879
880    #[test]
881    fn find_cubic_max_curvature_1() {
882        let src = [
883            Point::from_xy(20.0, 160.0),
884            Point::from_xy(20.0001, 160.0),
885            Point::from_xy(160.0, 20.0),
886            Point::from_xy(160.0001, 20.0),
887        ];
888
889        let mut t_values = [NormalizedF32::ZERO; 3];
890        let t_values = find_cubic_max_curvature(&src, &mut t_values);
891
892        assert_eq!(
893            &t_values,
894            &[
895                NormalizedF32::ZERO,
896                NormalizedF32::new_clamped(0.5),
897                NormalizedF32::ONE,
898            ]
899        );
900    }
901}