use crate::api::path::{Path, PathCmd, Point};
const FLATNESS_TOLERANCE: f64 = 0.25;
const MAX_RECURSION_DEPTH: u32 = 16;
pub struct EdgeBuilder;
impl EdgeBuilder {
pub fn flatten<F>(path: &Path, mut f: F)
where
F: FnMut(f64, f64, f64, f64),
{
let cmds = path.cmds();
let points = path.points();
let conic_w = path.conic_weights();
let n_conic_cmds = cmds.iter().filter(|&&c| c == PathCmd::ConicTo).count();
debug_assert_eq!(
n_conic_cmds,
conic_w.len(),
"conic_weights length must match PathCmd::ConicTo count"
);
let mut pt_idx = 0usize;
let mut conic_idx = 0usize;
let mut start = Point::new(0.0, 0.0);
let mut cur = Point::new(0.0, 0.0);
for &cmd in cmds {
match cmd {
PathCmd::MoveTo => {
let p = points[pt_idx];
pt_idx += 1;
start = p;
cur = p;
}
PathCmd::LineTo => {
let p = points[pt_idx];
pt_idx += 1;
f(cur.x, cur.y, p.x, p.y);
cur = p;
}
PathCmd::CubicTo => {
let cp1 = points[pt_idx];
let cp2 = points[pt_idx + 1];
let end = points[pt_idx + 2];
pt_idx += 3;
flatten_cubic(&mut f, cur, cp1, cp2, end, 0);
cur = end;
}
PathCmd::QuadTo => {
let cp = points[pt_idx];
let end = points[pt_idx + 1];
pt_idx += 2;
flatten_quadratic(&mut f, cur, cp, end, 0);
cur = end;
}
PathCmd::ConicTo => {
let cp = points[pt_idx];
let end = points[pt_idx + 1];
pt_idx += 2;
let w = conic_w[conic_idx];
conic_idx += 1;
flatten_conic(&mut f, cur, cp, end, w);
cur = end;
}
PathCmd::Close => {
if cur.x != start.x || cur.y != start.y {
f(cur.x, cur.y, start.x, start.y);
}
cur = start;
}
}
}
}
}
fn flatten_conic<F>(f: &mut F, p0: Point, p1: Point, p2: Point, w: f64)
where
F: FnMut(f64, f64, f64, f64),
{
const N: usize = 24;
let mut prev = eval_conic(p0, p1, p2, w, 0.0);
for i in 1..=N {
let t = i as f64 / N as f64;
let next = eval_conic(p0, p1, p2, w, t);
f(prev.x, prev.y, next.x, next.y);
prev = next;
}
}
fn eval_conic(p0: Point, p1: Point, p2: Point, w: f64, t: f64) -> Point {
let u = 1.0 - t;
let denom = u * u + 2.0 * w * u * t + t * t;
let x = (u * u * p0.x + 2.0 * w * u * t * p1.x + t * t * p2.x) / denom;
let y = (u * u * p0.y + 2.0 * w * u * t * p1.y + t * t * p2.y) / denom;
Point::new(x, y)
}
fn flatten_cubic<F>(f: &mut F, p0: Point, p1: Point, p2: Point, p3: Point, depth: u32)
where
F: FnMut(f64, f64, f64, f64),
{
if depth >= MAX_RECURSION_DEPTH || is_flat(p0, p1, p2, p3) {
f(p0.x, p0.y, p3.x, p3.y);
return;
}
let m01 = mid(p0, p1);
let m12 = mid(p1, p2);
let m23 = mid(p2, p3);
let m012 = mid(m01, m12);
let m123 = mid(m12, m23);
let m0123 = mid(m012, m123);
flatten_cubic(f, p0, m01, m012, m0123, depth + 1);
flatten_cubic(f, m0123, m123, m23, p3, depth + 1);
}
fn is_flat(p0: Point, p1: Point, p2: Point, p3: Point) -> bool {
let dx = p3.x - p0.x;
let dy = p3.y - p0.y;
let len_sq = dx * dx + dy * dy;
if len_sq < 1e-12 {
let d1_sq = (p1.x - p0.x).powi(2) + (p1.y - p0.y).powi(2);
let d2_sq = (p2.x - p0.x).powi(2) + (p2.y - p0.y).powi(2);
let tol_sq = FLATNESS_TOLERANCE * FLATNESS_TOLERANCE;
return d1_sq <= tol_sq && d2_sq <= tol_sq;
}
let cross1 = (p1.x - p0.x) * dy - (p1.y - p0.y) * dx;
let cross2 = (p2.x - p0.x) * dy - (p2.y - p0.y) * dx;
let tol_sq = FLATNESS_TOLERANCE * FLATNESS_TOLERANCE * len_sq;
cross1 * cross1 <= tol_sq && cross2 * cross2 <= tol_sq
}
fn flatten_quadratic<F>(f: &mut F, p0: Point, p1: Point, p2: Point, depth: u32)
where
F: FnMut(f64, f64, f64, f64),
{
if depth >= MAX_RECURSION_DEPTH || is_flat_quad(p0, p1, p2) {
f(p0.x, p0.y, p2.x, p2.y);
return;
}
let m01 = mid(p0, p1);
let m12 = mid(p1, p2);
let m012 = mid(m01, m12);
flatten_quadratic(f, p0, m01, m012, depth + 1);
flatten_quadratic(f, m012, m12, p2, depth + 1);
}
fn is_flat_quad(p0: Point, p1: Point, p2: Point) -> bool {
let dx = p2.x - p0.x;
let dy = p2.y - p0.y;
let len_sq = dx * dx + dy * dy;
if len_sq < 1e-12 {
let d_sq = (p1.x - p0.x).powi(2) + (p1.y - p0.y).powi(2);
return d_sq <= FLATNESS_TOLERANCE * FLATNESS_TOLERANCE;
}
let cross = (p1.x - p0.x) * dy - (p1.y - p0.y) * dx;
let tol_sq = FLATNESS_TOLERANCE * FLATNESS_TOLERANCE * len_sq;
cross * cross <= tol_sq
}
fn mid(a: Point, b: Point) -> Point {
Point::new((a.x + b.x) * 0.5, (a.y + b.y) * 0.5)
}