use super::fit::*;
use super::curve::*;
use super::bounds::*;
use super::normal::*;
use crate::geo::*;
use std::iter;
use itertools::*;
#[derive(Copy, Clone)]
pub struct SubdivisionOffsetOptions {
initial_subdivisions: usize,
min_distance: f64,
max_distance: f64,
max_error: f64,
}
impl Default for SubdivisionOffsetOptions {
fn default() -> Self {
SubdivisionOffsetOptions {
initial_subdivisions: 3,
min_distance: 0.1,
max_distance: 30.0,
max_error: 1.0,
}
}
}
impl SubdivisionOffsetOptions {
#[inline]
pub fn with_initial_subdivisions(mut self, initial_subdivisions: usize) -> Self {
self.initial_subdivisions = initial_subdivisions;
self
}
#[inline]
pub fn with_min_distance(mut self, min_distance: f64) -> Self {
self.min_distance = min_distance;
self
}
#[inline]
pub fn with_max_distance(mut self, max_distance: f64) -> Self {
self.max_distance = max_distance;
self
}
#[inline]
pub fn with_max_error(mut self, max_error: f64) -> Self {
self.max_error = max_error;
self
}
}
#[inline]
fn calc_offset_point<Curve, NormalOffsetFn, TangentOffsetFn>(curve: &Curve, normal_offset_for_t: &NormalOffsetFn, tangent_offset_for_t: &TangentOffsetFn, t: f64) -> (Curve::Point, Curve::Point)
where
Curve: BezierCurveFactory+NormalCurve,
Curve::Point: Normalize+Coordinate2D,
NormalOffsetFn: Fn(f64) -> f64,
TangentOffsetFn: Fn(f64) -> f64,
{
let mut point = curve.point_at_pos(t);
let normal_offset = normal_offset_for_t(t);
let tangent_offset = tangent_offset_for_t(t);
let unit_tangent = curve.tangent_at_pos(t).to_unit_vector();
let unit_normal = Curve::Point::to_normal(&point, &unit_tangent);
let unit_normal = Curve::Point::from_components(&unit_normal);
point = point + (unit_normal * normal_offset) + (unit_tangent * tangent_offset);
(point, unit_tangent)
}
pub fn offset_lms_subdivisions<Curve, NormalOffsetFn, TangentOffsetFn>(curve: &Curve, normal_offset_for_t: NormalOffsetFn, tangent_offset_for_t: TangentOffsetFn, subdivision_options: &SubdivisionOffsetOptions) -> Option<Vec<Curve>>
where
Curve: BezierCurveFactory + NormalCurve,
Curve::Point: Normalize + Coordinate2D,
NormalOffsetFn: Fn(f64) -> f64,
TangentOffsetFn: Fn(f64) -> f64,
{
let (start_point, (cp1, cp2), end_point) = curve.all_points();
let mut extremities = find_extremities(start_point, cp1, cp2, end_point);
extremities.retain(|t| (0.0..=1.0).contains(t));
extremities.sort_unstable_by(|t1, t2| t1.total_cmp(t2));
if extremities.len() == 0 || extremities[0] != 0.0 {
extremities.insert(0, 0.0);
}
if extremities.last() != Some(&1.0) {
extremities.push(1.0);
}
let samples = extremities.into_iter()
.map(|t| (t, calc_offset_point(curve, &normal_offset_for_t, &tangent_offset_for_t, t)))
.collect::<Vec<_>>();
let last_sample = samples.last().copied();
let initial_subdivisions = subdivision_options.initial_subdivisions;
let mut samples = if initial_subdivisions > 0 {
let normal_offset_for_t = &normal_offset_for_t;
let tangent_offset_for_t = &tangent_offset_for_t;
samples.into_iter().tuple_windows()
.flat_map(|((t1, p1), (t2, _p2))| {
iter::once((t1, p1))
.chain((0..initial_subdivisions)
.map(move |div| {
let div = ((div+1) as f64) / ((initial_subdivisions+2) as f64);
let t = (t2-t1)*div + t1;
(t, calc_offset_point(curve, normal_offset_for_t, tangent_offset_for_t, t))
}))
})
.chain(last_sample)
.collect::<Vec<_>>()
} else {
samples
};
loop {
let mut subdivided = false;
let mut next_samples = vec![];
let mut idx = 0;
while idx < samples.len()-1 {
let (t1, (first_point, first_tangent)) = &samples[idx];
let (t2, (next_point, _next_tangent)) = &samples[idx+1];
next_samples.push((*t1, (*first_point, *first_tangent)));
let distance = first_point.distance_to(&next_point);
if distance > subdivision_options.max_distance {
let t3 = (t1+t2)/2.0;
next_samples.push((t3, calc_offset_point(curve, &normal_offset_for_t, &tangent_offset_for_t, t3)));
subdivided = true;
} else if distance > subdivision_options.min_distance && idx < samples.len()-1 {
let t3 = (t1+t2)/2.0;
let (mid_point, mid_tangent) = calc_offset_point(curve, &normal_offset_for_t, &tangent_offset_for_t, t3);
let estimate_point = (*first_point + *next_point) * 0.5;
if estimate_point.distance_to(&mid_point) > subdivision_options.max_error {
next_samples.push((t3, (mid_point, mid_tangent)));
subdivided = true;
}
}
idx += 1;
}
while idx < samples.len() {
next_samples.push(samples[idx]);
idx +=1;
}
samples = next_samples;
if !subdivided {
break;
}
}
let sample_points = samples.into_iter().map(|(_, (point, _))| point).collect::<Vec<_>>();
let start_tangent = curve.tangent_at_pos(0.0).to_unit_vector();
let end_tangent = curve.tangent_at_pos(1.0).to_unit_vector() * -1.0;
Some(fit_curve_cubic(&sample_points, &start_tangent, &end_tangent, subdivision_options.max_error))
}