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,
)
}
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; }
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; }
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]; let mut b = src[4] * 3.0; let mut c = src[2] * 3.0; let d = src[0]; a -= d - c + b; b += 3.0 * d - 2.0 * c; c -= 3.0 * d; (a, b, c, d)
}
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)
{
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)
{
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() {
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 {
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 {
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
}
pub fn find_extrema(src: &[f64], t_values: &mut [f64]) -> usize {
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)
}
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
}
}
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;
}