use skia_rs_core::{Point, Scalar};
const MAX_SUBDIVISION_DEPTH: u32 = 12;
pub fn flatten_quad_adaptive(
output: &mut Vec<Point>,
start: Point,
ctrl: Point,
end: Point,
tolerance: Scalar,
) {
flatten_quad_recursive(output, start, ctrl, end, tolerance, 0);
}
fn flatten_quad_recursive(
output: &mut Vec<Point>,
start: Point,
ctrl: Point,
end: Point,
tolerance: Scalar,
depth: u32,
) {
let chord_mid = Point::new((start.x + end.x) * 0.5, (start.y + end.y) * 0.5);
let dx = ctrl.x - chord_mid.x;
let dy = ctrl.y - chord_mid.y;
let dev_sq = dx * dx + dy * dy;
let tol_sq = tolerance * tolerance;
if depth >= MAX_SUBDIVISION_DEPTH || dev_sq < tol_sq * 4.0 {
output.push(end);
return;
}
let m1 = Point::new((start.x + ctrl.x) * 0.5, (start.y + ctrl.y) * 0.5);
let m2 = Point::new((ctrl.x + end.x) * 0.5, (ctrl.y + end.y) * 0.5);
let m12 = Point::new((m1.x + m2.x) * 0.5, (m1.y + m2.y) * 0.5);
flatten_quad_recursive(output, start, m1, m12, tolerance, depth + 1);
flatten_quad_recursive(output, m12, m2, end, tolerance, depth + 1);
}
pub fn flatten_cubic_adaptive(
output: &mut Vec<Point>,
start: Point,
ctrl1: Point,
ctrl2: Point,
end: Point,
tolerance: Scalar,
) {
flatten_cubic_recursive(output, start, ctrl1, ctrl2, end, tolerance, 0);
}
fn flatten_cubic_recursive(
output: &mut Vec<Point>,
start: Point,
ctrl1: Point,
ctrl2: Point,
end: Point,
tolerance: Scalar,
depth: u32,
) {
let dx = end.x - start.x;
let dy = end.y - start.y;
let chord_len_sq = dx * dx + dy * dy;
let dev_of = |c: Point| -> Scalar {
if chord_len_sq > 1e-9 {
let t = ((c.x - start.x) * dx + (c.y - start.y) * dy) / chord_len_sq;
let proj_x = start.x + t * dx;
let proj_y = start.y + t * dy;
let pdx = c.x - proj_x;
let pdy = c.y - proj_y;
pdx * pdx + pdy * pdy
} else {
let pdx = c.x - start.x;
let pdy = c.y - start.y;
pdx * pdx + pdy * pdy
}
};
let max_dev = dev_of(ctrl1).max(dev_of(ctrl2));
let tol_sq = tolerance * tolerance;
if depth >= MAX_SUBDIVISION_DEPTH || max_dev < tol_sq {
output.push(end);
return;
}
let m1 = Point::new((start.x + ctrl1.x) * 0.5, (start.y + ctrl1.y) * 0.5);
let m2 = Point::new((ctrl1.x + ctrl2.x) * 0.5, (ctrl1.y + ctrl2.y) * 0.5);
let m3 = Point::new((ctrl2.x + end.x) * 0.5, (ctrl2.y + end.y) * 0.5);
let m12 = Point::new((m1.x + m2.x) * 0.5, (m1.y + m2.y) * 0.5);
let m23 = Point::new((m2.x + m3.x) * 0.5, (m2.y + m3.y) * 0.5);
let m123 = Point::new((m12.x + m23.x) * 0.5, (m12.y + m23.y) * 0.5);
flatten_cubic_recursive(output, start, m1, m12, m123, tolerance, depth + 1);
flatten_cubic_recursive(output, m123, m23, m3, end, tolerance, depth + 1);
}
pub fn flatten_conic_adaptive(
output: &mut Vec<Point>,
start: Point,
ctrl: Point,
end: Point,
weight: Scalar,
tolerance: Scalar,
) {
flatten_conic_recursive(output, start, ctrl, end, weight, 0.0, 1.0, tolerance, 0);
}
fn eval_conic(start: Point, ctrl: Point, end: Point, w: Scalar, t: Scalar) -> Point {
let mt = 1.0 - t;
let denom = mt * mt + 2.0 * w * t * mt + t * t;
let inv_denom = 1.0 / denom;
Point::new(
(start.x * mt * mt + 2.0 * ctrl.x * w * t * mt + end.x * t * t) * inv_denom,
(start.y * mt * mt + 2.0 * ctrl.y * w * t * mt + end.y * t * t) * inv_denom,
)
}
fn flatten_conic_recursive(
output: &mut Vec<Point>,
start: Point,
ctrl: Point,
end: Point,
weight: Scalar,
t0: Scalar,
t1: Scalar,
tolerance: Scalar,
depth: u32,
) {
let p0 = eval_conic(start, ctrl, end, weight, t0);
let p1 = eval_conic(start, ctrl, end, weight, t1);
let tm = (t0 + t1) * 0.5;
let pm = eval_conic(start, ctrl, end, weight, tm);
let chord_mid = Point::new((p0.x + p1.x) * 0.5, (p0.y + p1.y) * 0.5);
let dx = pm.x - chord_mid.x;
let dy = pm.y - chord_mid.y;
let dev_sq = dx * dx + dy * dy;
let tol_sq = tolerance * tolerance;
if depth >= MAX_SUBDIVISION_DEPTH || dev_sq < tol_sq {
output.push(p1);
return;
}
flatten_conic_recursive(output, start, ctrl, end, weight, t0, tm, tolerance, depth + 1);
flatten_conic_recursive(output, start, ctrl, end, weight, tm, t1, tolerance, depth + 1);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flatten_quad_simple() {
let mut output = Vec::new();
flatten_quad_adaptive(
&mut output,
Point::new(0.0, 0.0),
Point::new(50.0, 50.0),
Point::new(100.0, 0.0),
0.5,
);
assert!(!output.is_empty());
assert_eq!(*output.last().unwrap(), Point::new(100.0, 0.0));
}
#[test]
fn test_flatten_quad_straight_line() {
let mut output = Vec::new();
flatten_quad_adaptive(
&mut output,
Point::new(0.0, 0.0),
Point::new(50.0, 0.0),
Point::new(100.0, 0.0),
0.5,
);
assert_eq!(output.len(), 1, "Straight line should produce only endpoint");
}
#[test]
fn test_flatten_cubic_simple() {
let mut output = Vec::new();
flatten_cubic_adaptive(
&mut output,
Point::new(0.0, 0.0),
Point::new(33.0, 50.0),
Point::new(66.0, 50.0),
Point::new(100.0, 0.0),
0.5,
);
assert!(!output.is_empty());
assert_eq!(*output.last().unwrap(), Point::new(100.0, 0.0));
}
#[test]
fn test_flatten_conic_quarter_circle() {
let mut output = Vec::new();
flatten_conic_adaptive(
&mut output,
Point::new(1.0, 0.0),
Point::new(1.0, 1.0),
Point::new(0.0, 1.0),
std::f32::consts::FRAC_1_SQRT_2,
0.01,
);
for p in &output {
let r = (p.x * p.x + p.y * p.y).sqrt();
assert!((r - 1.0).abs() < 0.05, "Point not on unit circle: ({}, {})", p.x, p.y);
}
assert!(output.len() >= 4, "Expected subdivision, got {} points", output.len());
}
}