use super::PathPoint;
use crate::types::MAX_CURVE_SPLITS;
#[inline]
fn to_idx(v: i32) -> usize {
usize::try_from(v).expect("curve stack index must be non-negative")
}
pub struct CurveData {
pub cx: Box<[f64]>,
pub cy: Box<[f64]>,
pub c_next: Box<[i32]>,
}
impl CurveData {
#[must_use]
pub fn new() -> Box<Self> {
let n = usize::try_from(MAX_CURVE_SPLITS)
.expect("MAX_CURVE_SPLITS must be non-negative (it is the constant 1024)")
+ 1;
Box::new(Self {
cx: vec![0.0f64; n * 3].into_boxed_slice(),
cy: vec![0.0f64; n * 3].into_boxed_slice(),
c_next: vec![0i32; n].into_boxed_slice(),
})
}
}
pub fn flatten_curve(
p0: PathPoint,
p1: PathPoint,
p2: PathPoint,
p3: PathPoint,
flatness_sq: f64,
out: &mut Vec<PathPoint>,
curve_data: &mut Option<Box<CurveData>>,
) {
let data = curve_data.get_or_insert_with(CurveData::new);
let max = MAX_CURVE_SPLITS;
let max_u = usize::try_from(max)
.expect("MAX_CURVE_SPLITS must be non-negative (it is the constant 1024)");
let i0 = 0usize;
let i2 = max_u;
data.cx[0] = p0.x;
data.cy[0] = p0.y;
data.cx[1] = p1.x;
data.cy[1] = p1.y;
data.cx[2] = p2.x;
data.cy[2] = p2.y;
data.cx[i2 * 3] = p3.x;
data.cy[i2 * 3] = p3.y;
data.c_next[i0] = max;
let mut pp1 = 0i32;
while pp1 < max {
let pp1u = to_idx(pp1);
let pp2 = data.c_next[pp1u];
let pp2u = to_idx(pp2);
let xl0 = data.cx[pp1u * 3];
let yl0 = data.cy[pp1u * 3];
let xx1 = data.cx[pp1u * 3 + 1];
let yy1 = data.cy[pp1u * 3 + 1];
let xx2 = data.cx[pp1u * 3 + 2];
let yy2 = data.cy[pp1u * 3 + 2];
let xr3 = data.cx[pp2u * 3];
let yr3 = data.cy[pp2u * 3];
let mx = xl0.midpoint(xr3);
let my = yl0.midpoint(yr3);
let dx1 = xx1 - mx;
let dy1 = yy1 - my;
let dx2 = xx2 - mx;
let dy2 = yy2 - my;
let d1 = dx1.mul_add(dx1, dy1 * dy1);
let d2 = dx2.mul_add(dx2, dy2 * dy2);
if pp2 - pp1 == 1 || (d1 <= flatness_sq && d2 <= flatness_sq) {
out.push(PathPoint::new(xr3, yr3));
pp1 = pp2;
} else {
let xl1 = xl0.midpoint(xx1);
let yl1 = yl0.midpoint(yy1);
let xh = xx1.midpoint(xx2);
let yh = yy1.midpoint(yy2);
let xl2 = xl1.midpoint(xh);
let yl2 = yl1.midpoint(yh);
let xr2 = xx2.midpoint(xr3);
let yr2 = yy2.midpoint(yr3);
let xr1 = xh.midpoint(xr2);
let yr1 = yh.midpoint(yr2);
let xr0 = xl2.midpoint(xr1);
let yr0 = yl2.midpoint(yr1);
let pp3 = i32::midpoint(pp1, pp2);
let pp3u = to_idx(pp3);
data.cx[pp1u * 3 + 1] = xl1;
data.cy[pp1u * 3 + 1] = yl1;
data.cx[pp1u * 3 + 2] = xl2;
data.cy[pp1u * 3 + 2] = yl2;
data.c_next[pp1u] = pp3;
data.cx[pp3u * 3] = xr0;
data.cy[pp3u * 3] = yr0;
data.cx[pp3u * 3 + 1] = xr1;
data.cy[pp3u * 3 + 1] = yr1;
data.cx[pp3u * 3 + 2] = xr2;
data.cy[pp3u * 3 + 2] = yr2;
data.c_next[pp3u] = pp2;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pt(x: f64, y: f64) -> PathPoint {
PathPoint::new(x, y)
}
#[test]
fn flat_curve_emits_one_segment() {
let mut out = Vec::new();
let mut data = None;
flatten_curve(
pt(0.0, 0.0),
pt(1.0, 0.0),
pt(2.0, 0.0),
pt(3.0, 0.0),
0.1,
&mut out,
&mut data,
);
assert!(!out.is_empty());
for p in &out {
assert!(p.y.abs() < 1e-10, "y={} should be 0", p.y);
}
let last = out.last().unwrap();
assert!((last.x - 3.0).abs() < 1e-10);
assert!(last.y.abs() < 1e-10);
}
#[test]
fn endpoints_match_cubic() {
let p0 = pt(0.0, 0.0);
let p3 = pt(1.0, 1.0);
let k = crate::types::BEZIER_CIRCLE;
let p1 = pt(0.0, k);
let p2 = pt(1.0 - k, 1.0);
let mut out = Vec::new();
let mut data = None;
flatten_curve(p0, p1, p2, p3, 0.01 * 0.01, &mut out, &mut data);
let last = out.last().unwrap();
assert!((last.x - p3.x).abs() < 1e-10, "last x = {}", last.x);
assert!((last.y - p3.y).abs() < 1e-10, "last y = {}", last.y);
}
#[test]
fn does_not_exceed_max_curve_splits() {
let p0 = pt(0.0, 0.0);
let p1 = pt(1e8, -1e8);
let p2 = pt(-1e8, 1e8);
let p3 = pt(0.0, 0.0);
let mut out = Vec::new();
let mut data = None;
flatten_curve(p0, p1, p2, p3, 0.5 * 0.5, &mut out, &mut data);
assert!(out.len() <= usize::try_from(MAX_CURVE_SPLITS).expect("non-negative"));
}
#[test]
fn curve_to_line_is_two_points() {
let origin = pt(5.0, 7.0);
let mut out = Vec::new();
let mut data = None;
flatten_curve(
origin,
origin,
origin,
origin,
f64::EPSILON,
&mut out,
&mut data,
);
assert_eq!(out.len(), 1, "degenerate curve must emit exactly one point");
assert_eq!(out[0], origin, "emitted point must equal p3");
}
}