use path_builder::PrimitiveBuilder;
use vodk_math::{ Vector2D, Unit, Untyped };
use std::mem::swap;
pub fn sample_quadratic_bezier<U: Unit>(
from: Vector2D<U>,
ctrl: Vector2D<U>,
to: Vector2D<U>,
t: f32
) -> Vector2D<U> {
let t2 = t*t;
let one_t = 1.0 - t;
let one_t2 = one_t * one_t;
return from * one_t2
+ ctrl * 2.0*one_t*t
+ to * t2;
}
pub fn sample_cubic_bezier<U: Unit>(
from: Vector2D<U>,
ctrl1: Vector2D<U>,
ctrl2: Vector2D<U>,
to: Vector2D<U>,
t: f32
) -> Vector2D<U> {
let t2 = t*t;
let t3 = t2*t;
let one_t = 1.0 - t;
let one_t2 = one_t*one_t;
let one_t3 = one_t2*one_t;
return from * one_t3
+ ctrl1 * 3.0*one_t2*t
+ ctrl2 * 3.0*one_t*t2
+ to * t3
}
#[derive(Debug)]
pub struct CubicBezierSegment<U: Unit> {
pub from: Vector2D<U>,
pub cp1: Vector2D<U>,
pub cp2: Vector2D<U>,
pub to: Vector2D<U>,
}
impl<U: Unit> Copy for CubicBezierSegment<U> {}
impl<U: Unit> Clone for CubicBezierSegment<U> {
fn clone(&self) -> CubicBezierSegment<U> { *self }
}
#[derive(Debug)]
pub struct QuadraticBezierSegment<U: Unit> {
pub from: Vector2D<U>,
pub cp: Vector2D<U>,
pub to: Vector2D<U>,
}
impl<U: Unit> Copy for QuadraticBezierSegment<U> {}
impl<U: Unit> Clone for QuadraticBezierSegment<U> {
fn clone(&self) -> QuadraticBezierSegment<U> { *self }
}
impl<U: Unit> QuadraticBezierSegment<U> {
pub fn to_cubic(&self) -> CubicBezierSegment<U> {
CubicBezierSegment {
from: self.from,
cp1: (self.from + self.cp * 2.0) / 3.0,
cp2: (self.to + self.cp * 2.0) / 3.0,
to: self.to,
}
}
}
impl<U: Unit> CubicBezierSegment<U> {
pub fn split_in_place(&mut self, t: f32) -> CubicBezierSegment<U> {
let cp1a = self.from + (self.cp1 - self.from) * t;
let cp2a = self.cp1 + (self.cp2 - self.cp1) * t;
let cp1aa = cp1a + (cp2a - cp1a) * t;
let cp3a = self.cp2 + (self.to - self.cp2) * t;
let cp2aa = cp2a + (cp3a - cp2a) * t;
let cp1aaa = cp1aa + (cp2aa - cp1aa) * t;
let to = self.to;
self.cp1 = cp1a;
self.cp2 = cp1aa;
self.to = cp1aaa;
return CubicBezierSegment {
from: cp1aaa,
cp1: cp2aa,
cp2: cp3a,
to: to,
};
}
pub fn split(&self, t: f32) -> (CubicBezierSegment<U>, CubicBezierSegment<U>) {
let mut a = *self;
let b = a.split_in_place(t);
return (a, b);
}
pub fn sample(&self, t: f32) -> Vector2D<U> {
return sample_cubic_bezier(self.from, self.cp1, self.cp2, self.to, t);
}
}
pub fn split_cubic_bezier<U: Unit>(
bezier: &CubicBezierSegment<U>,
t: f32,
out_first_segment: Option<&mut CubicBezierSegment<U>>,
out_second_segment: Option<&mut CubicBezierSegment<U>>
) {
let cp1a = bezier.from + (bezier.cp1 - bezier.from) * t;
let cp2a = bezier.cp1 + (bezier.cp2 - bezier.cp1) * t;
let cp1aa = cp1a + (cp2a - cp1a) * t;
let cp3a = bezier.cp2 + (bezier.to - bezier.cp2) * t;
let cp2aa = cp2a + (cp3a - cp2a) * t;
let cp1aaa = cp1aa + (cp2aa - cp1aa) * t;
if let Some(first) = out_first_segment {
first.from = bezier.from;
first.cp1 = cp1a;
first.cp2 = cp1aa;
first.to = cp1aaa;
}
if let Some(second) = out_second_segment {
second.from = cp1aaa;
second.cp1 = cp2aa;
second.cp2 = cp3a;
second.to = bezier.to;
}
}
fn find_cubic_bezier_inflection_points<U: Unit>(
bezier: &CubicBezierSegment<U>,
) -> (Option<f32>, Option<f32>) {
let pa = bezier.cp1 - bezier.from;
let pb = bezier.cp2 - (bezier.cp1 * 2.0) + bezier.from;
let pc = bezier.to - (bezier.cp2 * 3.0) + (bezier.cp1 * 3.0) - bezier.from;
let a = pb.x * pc.y - pb.y * pc.x;
let b = pa.x * pc.y - pa.y * pc.x;
let c = pa.x * pb.y - pa.y * pb.x;
if a == 0.0 {
if b == 0.0 {
if c == 0.0 {
return (Some(0.0), None);
}
return (None, None);
}
return (Some(-c / b), None);
}
let discriminant = b * b - 4.0 * a * c;
if discriminant < 0.0 {
return (None, None);
}
if discriminant == 0.0 {
return (Some(-b / (2.0 * a)), None);
}
let discriminant_sqrt = discriminant.sqrt();
let q = if b < 0.0 { b - discriminant_sqrt } else { b + discriminant_sqrt } * -0.5;
let mut t1 = q / a;
let mut t2 = c / q;
if t1 > t2 {
swap(&mut t1, &mut t2);
}
return (Some(t1), Some(t2));
}
pub fn cubic_root(val: f32) -> f32 {
if val < 0.0 {
return -cubic_root(-val);
}
return val.powf(1.0 / 3.0);
}
fn find_cubic_bezier_inflection_approximation_range<U: Unit>(
bezier_segment: &CubicBezierSegment<U>,
t: f32, tolerance: f32,
min: &mut f32, max: &mut f32
) {
let mut bezier = *bezier_segment;
bezier = bezier.split_in_place(t);
let cp21 = bezier.cp1 - bezier.from;
let cp41 = bezier.to - bezier.from;
if cp21.x == 0.0 && cp21.y == 0.0 {
*min = t - cubic_root((tolerance / (cp41.x - cp41.y)).abs());
*max = t + cubic_root((tolerance / (cp41.x - cp41.y)).abs());
return;
}
let s3 = (cp41.x * cp21.y - cp41.y * cp21.x) / cp21.x.hypot(cp21.y);
if s3 == 0.0 {
*min = -1.0;
*max = 2.0;
return;
}
let tf = cubic_root((tolerance / s3).abs());
*min = t - tf * (1.0 - t);
*max = t + tf * (1.0 - t);
}
pub fn flatten_cubic_bezier<Builder: PrimitiveBuilder>(
bezier: CubicBezierSegment<Untyped>,
tolerance: f32,
path: &mut Builder
) {
let (t1, t2) = find_cubic_bezier_inflection_points(&bezier);
let count = if t1.is_none() { 0 } else if t2.is_none() { 1 } else { 2 };
let t1 = if let Some(t) = t1 { t } else { -1.0 };
let t2 = if let Some(t) = t2 { t } else { -1.0 };
if count == 0 || ((t1 <= 0.0 || t1 >= 1.0) && (count == 1 || (t2 <= 0.0 || t2 >= 1.0))) {
return flatten_cubic_bezier_segment(bezier, tolerance, path);
}
let mut t1min = t1;
let mut t1max = t1;
let mut t2min = t2;
let mut t2max = t2;
let mut remaining_cp = bezier;
if count > 0 && t1 >= 0.0 && t1 < 1.0 {
find_cubic_bezier_inflection_approximation_range(&bezier, t1, tolerance, &mut t1min, &mut t1max);
}
if count > 1 && t2 >= 0.0 && t2 < 1.0 {
find_cubic_bezier_inflection_approximation_range(&bezier, t2, tolerance, &mut t2min, &mut t2max);
}
let mut next_bezier = bezier;
let mut prev_bezier = bezier;
if count == 1 && t1min <= 0.0 && t1max >= 1.0 {
path.line_to(bezier.to);
return;
}
if t1min > 0.0 {
split_cubic_bezier(&bezier, t1min, Some(&mut prev_bezier), Some(&mut remaining_cp));
flatten_cubic_bezier_segment(prev_bezier, tolerance, path);
}
if t1max >= 0.0 && t1max < 1.0 && (count == 1 || t2min > t1max) {
split_cubic_bezier(&bezier, t1max, None, Some(&mut next_bezier));
path.line_to(next_bezier.from);
if count == 1 || (count > 1 && t2min >= 1.0) {
flatten_cubic_bezier_segment(next_bezier, tolerance, path);
return;
}
} else if count > 1 && t2min > 1.0 {
path.line_to(bezier.to);
return;
}
if count > 1 && t2min < 1.0 && t2max > 0.0 {
if t2min > 0.0 && t2min < t1max {
split_cubic_bezier(&bezier, t1max, None, Some(&mut next_bezier));
path.line_to(next_bezier.from);
} else if t2min > 0.0 && t1max > 0.0 {
split_cubic_bezier(&bezier, t1max, None, Some(&mut next_bezier));
let t2mina = (t2min - t1max) / (1.0 - t1max);
let tmp = next_bezier;
split_cubic_bezier(&tmp, t2mina, Some(&mut prev_bezier), Some(&mut next_bezier));
flatten_cubic_bezier_segment(prev_bezier, tolerance, path);
} else if t2min > 0.0 {
split_cubic_bezier(&bezier, t2min, Some(&mut prev_bezier), Some(&mut next_bezier));
flatten_cubic_bezier_segment(prev_bezier, tolerance, path);
}
if t2max < 1.0 {
split_cubic_bezier(&bezier, t2max, None, Some(&mut next_bezier));
path.line_to(next_bezier.from);
flatten_cubic_bezier_segment(next_bezier, tolerance, path);
} else {
path.line_to(bezier.to);
}
}
}
fn flatten_cubic_bezier_segment<Builder: PrimitiveBuilder>(
mut bezier: CubicBezierSegment<Untyped>,
tolerance: f32,
path: &mut Builder
) {
let end = bezier.to;
let mut t = 0.0;
while t < 1.0 {
let v1 = bezier.cp1 - bezier.from;
let v2 = bezier.cp2 - bezier.from;
let v1xv2 = v2.x * v1.y - v2.y * v1.x;
let h = v1.x.hypot(v1.y);
if v1xv2 * h == 0.0 {
break;
}
let s2inv = h / v1xv2;
t = 2.0 * (tolerance * s2inv.abs() / 3.0).sqrt();
if t >= 0.999 {
break;
}
bezier = bezier.split_in_place(t as f32);
path.line_to(bezier.from);
}
path.line_to(end);
}
#[test]
fn test_cubic_inflection_extremity() {
use vodk_math::vec2;
use path_builder::flattened_path_builder;
let mut builder = flattened_path_builder();
builder.move_to(vec2(141.0, 135.0));
builder.cubic_bezier_to(vec2(141.0, 130.0), vec2(140.0, 130.0),vec2(131.0, 130.0));
builder.close();
let path = builder.build();
assert!(path.num_vertices() > 2);
}