use super::curve::*;
use super::basis::*;
use crate::geo::*;
const MAX_ITERATIONS: usize = 4;
const FIT_ATTEMPT_RATIO: f64 = 4.0;
const MAX_POINTS_TO_FIT: usize = 200;
#[inline]
fn max_points_to_fit(num_points: usize) -> usize {
if num_points < MAX_POINTS_TO_FIT {
MAX_POINTS_TO_FIT
} else {
let min_points_to_fit = MAX_POINTS_TO_FIT / 4;
let mut max_points_to_fit = MAX_POINTS_TO_FIT;
while max_points_to_fit > min_points_to_fit && (num_points % max_points_to_fit) < min_points_to_fit {
max_points_to_fit -= 1;
}
max_points_to_fit
}
}
pub fn fit_curve<Curve>(points: &[Curve::Point], max_error: f64) -> Option<Vec<Curve>>
where
Curve: BezierCurveFactory + BezierCurve,
{
let max_points_to_fit = max_points_to_fit(points.len());
if points.len() < 2 {
None
} else {
let mut curves = vec![];
let num_blocks = ((points.len()-1) / max_points_to_fit)+1;
for point_block in 0..num_blocks {
let start_point = point_block * max_points_to_fit;
let mut num_points = max_points_to_fit;
if start_point+num_points > points.len() {
num_points = points.len() - start_point;
}
if num_points < 2 { continue; }
let block_points = &points[start_point..start_point+num_points];
let start_tangent = start_tangent(block_points);
let end_tangent = if start_point + num_points + 1 < points.len() {
end_tangent(&points[start_point..start_point+num_points+1])
} else {
end_tangent(block_points)
};
let fit = fit_curve_cubic(block_points, &start_tangent, &end_tangent, max_error);
for curve in fit {
curves.push(curve);
}
}
Some(curves)
}
}
pub fn fit_curve_loop<Curve>(points: &[Curve::Point], max_error: f64) -> Option<Vec<Curve>>
where
Curve: BezierCurveFactory + BezierCurve,
{
let max_points_to_fit = max_points_to_fit(points.len());
if points.len() < 2 {
None
} else {
let mut curves = vec![];
let num_blocks = ((points.len()-1) / max_points_to_fit)+1;
for point_block in 0..num_blocks {
let start_point = point_block * max_points_to_fit;
let mut num_points = max_points_to_fit;
if start_point+num_points > points.len() {
num_points = points.len() - start_point;
}
if num_points < 2 { continue; }
let block_points = &points[start_point..start_point+num_points];
let start_tangent = if start_point <= 1 {
start_tangent(&points[(points.len()-2)..=(points.len()-1)])
} else {
start_tangent(&points[(start_point-2)..=(start_point-1)])
};
let end_tangent = if start_point + num_points + 1 < points.len() {
end_tangent(&points[(start_point+num_points)..=(start_point+num_points+1)])
} else {
end_tangent(&points[0..=1])
};
let fit = fit_curve_cubic(block_points, &start_tangent, &end_tangent, max_error);
for curve in fit {
curves.push(curve);
}
}
Some(curves)
}
}
pub fn fit_curve_cubic<Curve: BezierCurveFactory+BezierCurve>(points: &[Curve::Point], start_tangent: &Curve::Point, end_tangent: &Curve::Point, max_error: f64) -> Vec<Curve> {
if points.len() <= 2 {
fit_line(&points[0], &points[1])
} else {
let mut chords = chords_for_points(points);
let mut curve = generate_bezier(points, &chords, start_tangent, end_tangent);
chords = reparameterize(points, &chords, &curve);
curve = generate_bezier(points, &chords, start_tangent, end_tangent);
let (mut error, mut split_pos) = max_error_for_curve(points, &chords, &curve);
if error > max_error && error < max_error*FIT_ATTEMPT_RATIO {
for _iteration in 1..MAX_ITERATIONS {
chords = reparameterize(points, &chords, &curve);
curve = generate_bezier(points, &chords, start_tangent, end_tangent);
let (new_error, new_split_pos) = max_error_for_curve(points, &chords, &curve);
error = new_error;
split_pos = new_split_pos;
if error <= max_error {
break;
}
}
}
if error <= max_error {
vec![curve]
} else {
let center_tangent = tangent_between(&points[split_pos-1], &points[split_pos], &points[split_pos+1]);
let lhs = fit_curve_cubic(&points[0..split_pos+1], start_tangent, ¢er_tangent, max_error);
let rhs = fit_curve_cubic(&points[split_pos..points.len()], &(center_tangent*-1.0), end_tangent, max_error);
lhs.into_iter().chain(rhs.into_iter()).collect()
}
}
}
fn fit_line<Curve: BezierCurveFactory>(p1: &Curve::Point, p2: &Curve::Point) -> Vec<Curve> {
let direction = *p2 - *p1;
let cp1 = *p1 + (direction * 0.33);
let cp2 = *p1 + (direction * 0.66);
vec![Curve::from_points(*p1, (cp1, cp2), *p2)]
}
fn chords_for_points<Point: Coordinate>(points: &[Point]) -> Vec<f64> {
let mut distances = vec![];
let mut total_distance = 0.0;
distances.push(total_distance);
for p in 1..points.len() {
total_distance += points[p-1].distance_to(&points[p]);
distances.push(total_distance);
}
for distance in distances.iter_mut() {
*distance /= total_distance;
}
distances
}
fn generate_bezier<Curve: BezierCurveFactory>(points: &[Curve::Point], chords: &[f64], start_tangent: &Curve::Point, end_tangent: &Curve::Point) -> Curve {
let a: Vec<_> = chords.iter().map(|chord| {
let inverse_chord = 1.0 - chord;
let b1 = 3.0 * chord * (inverse_chord*inverse_chord);
let b2 = 3.0 * chord * chord * inverse_chord;
(*start_tangent*b1, *end_tangent*b2)
}).collect();
let mut c = [[ 0.0, 0.0 ], [ 0.0, 0.0 ]];
let mut x = [0.0, 0.0];
let last_point = points[points.len()-1];
for point in 0..points.len() {
c[0][0] += a[point].0.dot(&a[point].0);
c[0][1] += a[point].0.dot(&a[point].1);
c[1][0] = c[0][1];
c[1][1] += a[point].1.dot(&a[point].1);
let chord = chords[point];
let inverse_chord = 1.0 - chord;
let b0 = inverse_chord*inverse_chord*inverse_chord;
let b1 = 3.0 * chord * (inverse_chord*inverse_chord);
let b2 = 3.0 * chord * chord * inverse_chord;
let b3 = chord*chord*chord;
let tmp = points[point] -
((points[0] * b0) + (points[0] * b1) + (last_point*b2) + (last_point*b3));
x[0] += a[point].0.dot(&tmp);
x[1] += a[point].1.dot(&tmp);
}
let det_c0_c1 = c[0][0]*c[1][1] - c[1][0]*c[0][1];
let det_c0_x = c[0][0]*x[1] - c[1][0]*x[0];
let det_x_c1 = x[0]*c[1][1] - x[1]*c[0][1];
let alpha_l = if f64::abs(det_c0_c1)<1.0e-4 { 0.0 } else { det_x_c1/det_c0_c1 };
let alpha_r = if f64::abs(det_c0_c1)<1.0e-4 { 0.0 } else { det_c0_x/det_c0_c1 };
let seg_length = points[0].distance_to(&last_point);
let epsilon = 1.0e-6*seg_length;
if alpha_l < epsilon || alpha_r < epsilon {
let dist = seg_length/3.0;
Curve::from_points(points[0], (points[0]+(*start_tangent*dist), last_point+(*end_tangent*dist)), last_point)
} else {
Curve::from_points(points[0], (points[0]+(*start_tangent*alpha_l), last_point+(*end_tangent*alpha_r)), last_point)
}
}
fn max_error_for_curve<Curve: BezierCurveFactory>(points: &[Curve::Point], chords: &[f64], curve: &Curve) -> (f64, usize) {
let errors = points.iter().zip(chords.iter())
.map(|(point, chord)| {
let actual = curve.point_at_pos(*chord);
let offset = *point - actual;
offset.dot(&offset)
});
let mut biggest_error_squared = 0.0;
let mut biggest_error_offset = 0;
for (current_point, error_squared) in errors.enumerate() {
if error_squared > biggest_error_squared {
biggest_error_squared = error_squared;
biggest_error_offset = current_point;
}
}
(f64::sqrt(biggest_error_squared), biggest_error_offset)
}
#[inline]
fn start_tangent<Point: Coordinate>(points: &[Point]) -> Point {
(points[1]-points[0]).to_unit_vector()
}
#[inline]
fn end_tangent<Point: Coordinate>(points: &[Point]) -> Point {
(points[points.len()-2]-points[points.len()-1]).to_unit_vector()
}
fn tangent_between<Point: Coordinate>(p1: &Point, p2: &Point, p3: &Point) -> Point {
let v1 = *p1 - *p2;
let v2 = *p2 - *p3;
((v1+v2)*0.5).to_unit_vector()
}
fn reparameterize<Curve: BezierCurve>(points: &[Curve::Point], chords: &[f64], curve: &Curve) -> Vec<f64> {
points.iter().zip(chords.iter())
.map(|(point, chord)| newton_raphson_root_find(curve, point, *chord))
.collect()
}
fn newton_raphson_root_find<Curve: BezierCurve>(curve: &Curve, point: &Curve::Point, estimated_t: f64) -> f64 {
let start = curve.start_point();
let end = curve.end_point();
let (cp1, cp2) = curve.control_points();
let qt = curve.point_at_pos(estimated_t);
let qn1 = (cp1-start)*3.0;
let qn2 = (cp2-cp1)*3.0;
let qn3 = (end-cp2)*3.0;
let qnn1 = (qn2-qn1)*2.0;
let qnn2 = (qn3-qn2)*2.0;
let qnt = de_casteljau3(estimated_t, qn1, qn2, qn3);
let qnnt = de_casteljau2(estimated_t, qnn1, qnn2);
let numerator = (qt-*point).dot(&qnt);
let denominator = qnt.dot(&qnt) + (qt-*point).dot(&qnnt);
if denominator == 0.0 {
estimated_t
} else {
estimated_t - (numerator/denominator)
}
}