// Copyright 2012 Google Inc. // Copyright 2020 Yevhenii Reizner // // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use super::point64::{Point64, SearchAxis}; use super::quad64; use super::Scalar64; #[cfg(all(not(feature = "std"), feature = "no-std-float"))] use tiny_skia_path::NoStdFloat; pub const POINT_COUNT: usize = 4; const PI: f64 = 3.141592653589793; pub struct Cubic64Pair { pub points: [Point64; 7], } pub struct Cubic64 { pub points: [Point64; POINT_COUNT], } impl Cubic64 { pub fn new(points: [Point64; POINT_COUNT]) -> Self { Cubic64 { points } } pub fn as_f64_slice(&self) -> [f64; POINT_COUNT * 2] { [ self.points[0].x, self.points[0].y, self.points[1].x, self.points[1].y, self.points[2].x, self.points[2].y, self.points[3].x, self.points[3].y, ] } pub fn point_at_t(&self, t: f64) -> Point64 { if t == 0.0 { return self.points[0]; } if t == 1.0 { return self.points[3]; } let one_t = 1.0 - t; let one_t2 = one_t * one_t; let a = one_t2 * one_t; let b = 3.0 * one_t2 * t; let t2 = t * t; let c = 3.0 * one_t * t2; let d = t2 * t; Point64::from_xy( a * self.points[0].x + b * self.points[1].x + c * self.points[2].x + d * self.points[3].x, a * self.points[0].y + b * self.points[1].y + c * self.points[2].y + d * self.points[3].y, ) } pub fn search_roots( &self, mut extrema: usize, axis_intercept: f64, x_axis: SearchAxis, extreme_ts: &mut [f64; 6], valid_roots: &mut [f64], ) -> usize { extrema += self.find_inflections(&mut extreme_ts[extrema..]); extreme_ts[extrema] = 0.0; extrema += 1; extreme_ts[extrema] = 1.0; debug_assert!(extrema < 6); extreme_ts[0..extrema].sort_by(cmp_f64); let mut valid_count = 0; let mut index = 0; while index < extrema { let min = extreme_ts[index]; index += 1; let max = extreme_ts[index]; if min == max { continue; } let new_t = self.binary_search(min, max, axis_intercept, x_axis); if new_t >= 0.0 { if valid_count >= 3 { return 0; } valid_roots[valid_count] = new_t; valid_count += 1; } } valid_count } fn find_inflections(&self, t_values: &mut [f64]) -> usize { let ax = self.points[1].x - self.points[0].x; let ay = self.points[1].y - self.points[0].y; let bx = self.points[2].x - 2.0 * self.points[1].x + self.points[0].x; let by = self.points[2].y - 2.0 * self.points[1].y + self.points[0].y; let cx = self.points[3].x + 3.0 * (self.points[1].x - self.points[2].x) - self.points[0].x; let cy = self.points[3].y + 3.0 * (self.points[1].y - self.points[2].y) - self.points[0].y; quad64::roots_valid_t( bx * cy - by * cx, ax * cy - ay * cx, ax * by - ay * bx, t_values, ) } // give up when changing t no longer moves point // also, copy point rather than recompute it when it does change fn binary_search(&self, min: f64, max: f64, axis_intercept: f64, x_axis: SearchAxis) -> f64 { let mut t = (min + max) / 2.0; let mut step = (t - min) / 2.0; let mut cubic_at_t = self.point_at_t(t); let mut calc_pos = cubic_at_t.axis_coord(x_axis); let mut calc_dist = calc_pos - axis_intercept; loop { let prior_t = min.max(t - step); let less_pt = self.point_at_t(prior_t); if less_pt.x.approximately_equal_half(cubic_at_t.x) && less_pt.y.approximately_equal_half(cubic_at_t.y) { return -1.0; // binary search found no point at this axis intercept } let less_dist = less_pt.axis_coord(x_axis) - axis_intercept; let last_step = step; step /= 2.0; let ok = if calc_dist > 0.0 { calc_dist > less_dist } else { calc_dist < less_dist }; if ok { t = prior_t; } else { let next_t = t + last_step; if next_t > max { return -1.0; } let more_pt = self.point_at_t(next_t); if more_pt.x.approximately_equal_half(cubic_at_t.x) && more_pt.y.approximately_equal_half(cubic_at_t.y) { return -1.0; // binary search found no point at this axis intercept } let more_dist = more_pt.axis_coord(x_axis) - axis_intercept; let ok = if calc_dist > 0.0 { calc_dist <= more_dist } else { calc_dist >= more_dist }; if ok { continue; } t = next_t; } let test_at_t = self.point_at_t(t); cubic_at_t = test_at_t; calc_pos = cubic_at_t.axis_coord(x_axis); calc_dist = calc_pos - axis_intercept; if calc_pos.approximately_equal(axis_intercept) { break; } } t } pub fn chop_at(&self, t: f64) -> Cubic64Pair { let mut dst = [Point64::zero(); 7]; if t == 0.5 { dst[0] = self.points[0]; dst[1].x = (self.points[0].x + self.points[1].x) / 2.0; dst[1].y = (self.points[0].y + self.points[1].y) / 2.0; dst[2].x = (self.points[0].x + 2.0 * self.points[1].x + self.points[2].x) / 4.0; dst[2].y = (self.points[0].y + 2.0 * self.points[1].y + self.points[2].y) / 4.0; dst[3].x = (self.points[0].x + 3.0 * (self.points[1].x + self.points[2].x) + self.points[3].x) / 8.0; dst[3].y = (self.points[0].y + 3.0 * (self.points[1].y + self.points[2].y) + self.points[3].y) / 8.0; dst[4].x = (self.points[1].x + 2.0 * self.points[2].x + self.points[3].x) / 4.0; dst[4].y = (self.points[1].y + 2.0 * self.points[2].y + self.points[3].y) / 4.0; dst[5].x = (self.points[2].x + self.points[3].x) / 2.0; dst[5].y = (self.points[2].y + self.points[3].y) / 2.0; dst[6] = self.points[3]; Cubic64Pair { points: dst } } else { interp_cubic_coords_x(&self.points, t, &mut dst); interp_cubic_coords_y(&self.points, t, &mut dst); Cubic64Pair { points: dst } } } } pub fn coefficients(src: &[f64]) -> (f64, f64, f64, f64) { let mut a = src[6]; // d let mut b = src[4] * 3.0; // 3*c let mut c = src[2] * 3.0; // 3*b let d = src[0]; // a a -= d - c + b; // A = -a + 3*b - 3*c + d b += 3.0 * d - 2.0 * c; // B = 3*a - 6*b + 3*c c -= 3.0 * d; // C = -3*a + 3*b (a, b, c, d) } // from SkGeometry.cpp (and Numeric Solutions, 5.6) pub fn roots_valid_t(a: f64, b: f64, c: f64, d: f64, t: &mut [f64; 3]) -> usize { let mut s = [0.0; 3]; let real_roots = roots_real(a, b, c, d, &mut s); let mut found_roots = quad64::push_valid_ts(&s, real_roots, t); 'outer: for index in 0..real_roots { let t_value = s[index]; if !t_value.approximately_one_or_less() && t_value.between(1.0, 1.00005) { for idx2 in 0..found_roots { if t[idx2].approximately_equal(1.0) { continue 'outer; } } debug_assert!(found_roots < 3); t[found_roots] = 1.0; found_roots += 1; } else if !t_value.approximately_zero_or_more() && t_value.between(-0.00005, 0.0) { for idx2 in 0..found_roots { if t[idx2].approximately_equal(0.0) { continue 'outer; } } debug_assert!(found_roots < 3); t[found_roots] = 0.0; found_roots += 1; } } found_roots } fn roots_real(a: f64, b: f64, c: f64, d: f64, s: &mut [f64; 3]) -> usize { if a.approximately_zero() && a.approximately_zero_when_compared_to(b) && a.approximately_zero_when_compared_to(c) && a.approximately_zero_when_compared_to(d) { // we're just a quadratic return quad64::roots_real(b, c, d, s); } if d.approximately_zero_when_compared_to(a) && d.approximately_zero_when_compared_to(b) && d.approximately_zero_when_compared_to(c) { // 0 is one root let mut num = quad64::roots_real(a, b, c, s); for i in 0..num { if s[i].approximately_zero() { return num; } } s[num] = 0.0; num += 1; return num; } if (a + b + c + d).approximately_zero() { // 1 is one root let mut num = quad64::roots_real(a, a + b, -d, s); for i in 0..num { if s[i].almost_dequal_ulps(1.0) { return num; } } s[num] = 1.0; num += 1; return num; } let (a, b, c) = { let inv_a = 1.0 / a; let a = b * inv_a; let b = c * inv_a; let c = d * inv_a; (a, b, c) }; let a2 = a * a; let q = (a2 - b * 3.0) / 9.0; let r = (2.0 * a2 * a - 9.0 * a * b + 27.0 * c) / 54.0; let r2 = r * r; let q3 = q * q * q; let r2_minus_q3 = r2 - q3; let adiv3 = a / 3.0; let mut offset = 0; if r2_minus_q3 < 0.0 { // we have 3 real roots // the divide/root can, due to finite precisions, be slightly outside of -1...1 let theta = (r / q3.sqrt()).bound(-1.0, 1.0).acos(); let neg2_root_q = -2.0 * q.sqrt(); let mut rr = neg2_root_q * (theta / 3.0).cos() - adiv3; s[offset] = rr; offset += 1; rr = neg2_root_q * ((theta + 2.0 * PI) / 3.0).cos() - adiv3; if !s[0].almost_dequal_ulps(rr) { s[offset] = rr; offset += 1; } rr = neg2_root_q * ((theta - 2.0 * PI) / 3.0).cos() - adiv3; if !s[0].almost_dequal_ulps(rr) && (offset == 1 || !s[1].almost_dequal_ulps(rr)) { s[offset] = rr; offset += 1; } } else { // we have 1 real root let sqrt_r2_minus_q3 = r2_minus_q3.sqrt(); let mut a = r.abs() + sqrt_r2_minus_q3; a = super::cube_root(a); if r > 0.0 { a = -a; } if a != 0.0 { a += q / a; } let mut r2 = a - adiv3; s[offset] = r2; offset += 1; if r2.almost_dequal_ulps(q3) { r2 = -a / 2.0 - adiv3; if !s[0].almost_dequal_ulps(r2) { s[offset] = r2; offset += 1; } } } offset } // Cubic64'(t) = At^2 + Bt + C, where // A = 3(-a + 3(b - c) + d) // B = 6(a - 2b + c) // C = 3(b - a) // Solve for t, keeping only those that fit between 0 < t < 1 pub fn find_extrema(src: &[f64], t_values: &mut [f64]) -> usize { // we divide A,B,C by 3 to simplify let a = src[0]; let b = src[2]; let c = src[4]; let d = src[6]; let a2 = d - a + 3.0 * (b - c); let b2 = 2.0 * (a - b - b + c); let c2 = b - a; quad64::roots_valid_t(a2, b2, c2, t_values) } // Skia doesn't seems to care about NaN/inf during sorting, so we don't too. fn cmp_f64(a: &f64, b: &f64) -> core::cmp::Ordering { if a < b { core::cmp::Ordering::Less } else if a > b { core::cmp::Ordering::Greater } else { core::cmp::Ordering::Equal } } // classic one t subdivision fn interp_cubic_coords_x(src: &[Point64; 4], t: f64, dst: &mut [Point64; 7]) { use super::interp; let ab = interp(src[0].x, src[1].x, t); let bc = interp(src[1].x, src[2].x, t); let cd = interp(src[2].x, src[3].x, t); let abc = interp(ab, bc, t); let bcd = interp(bc, cd, t); let abcd = interp(abc, bcd, t); dst[0].x = src[0].x; dst[1].x = ab; dst[2].x = abc; dst[3].x = abcd; dst[4].x = bcd; dst[5].x = cd; dst[6].x = src[3].x; } fn interp_cubic_coords_y(src: &[Point64; 4], t: f64, dst: &mut [Point64; 7]) { use super::interp; let ab = interp(src[0].y, src[1].y, t); let bc = interp(src[1].y, src[2].y, t); let cd = interp(src[2].y, src[3].y, t); let abc = interp(ab, bc, t); let bcd = interp(bc, cd, t); let abcd = interp(abc, bcd, t); dst[0].y = src[0].y; dst[1].y = ab; dst[2].y = abc; dst[3].y = abcd; dst[4].y = bcd; dst[5].y = cd; dst[6].y = src[3].y; }