use super::curve::*;
use super::normal::*;
use super::characteristics::*;
use crate::geo::*;
use crate::line::*;
use crate::bezier::{CurveSection};
use smallvec::*;
use itertools::*;
use std::cmp::*;
pub fn offset_scaling<Curve>(curve: &Curve, initial_offset: f64, final_offset: f64) -> Vec<Curve>
where
Curve: BezierCurveFactory+NormalCurve,
Curve::Point: Normalize+Coordinate2D,
{
let sections: SmallVec<[_; 4]> = match features_for_curve(curve, 0.01) {
CurveFeatures::DoubleInflectionPoint(t1, t2) => {
let t1 = if t1 > 0.9999 { 1.0 } else if t1 < 0.0001 { 0.0 } else { t1 };
let t2 = if t2 > 0.9999 { 1.0 } else if t2 < 0.0001 { 0.0 } else { t2 };
if t2 > t1 {
smallvec![(0.0, t1), (t1, t2), (t2, 1.0)]
} else {
smallvec![(0.0, t2), (t2, t1), (t1, 1.0)]
}
}
CurveFeatures::Loop(t1, t3) => {
let t1 = if t1 > 0.9999 { 1.0 } else if t1 < 0.0001 { 0.0 } else { t1 };
let t3 = if t3 > 0.9999 { 1.0 } else if t3 < 0.0001 { 0.0 } else { t3 };
let t2 = (t1+t3)/2.0;
if t3 > t1 {
smallvec![(0.0, t1), (t1, t2), (t2, t3), (t3, 1.0)]
} else {
smallvec![(0.0, t3), (t3, t2), (t2, t1), (t1, 1.0)]
}
}
CurveFeatures::SingleInflectionPoint(t) => {
if t > 0.0001 && t < 0.9999 {
smallvec![(0.0, t), (t, 1.0)]
} else {
smallvec![(0.0, 1.0)]
}
}
_ => { smallvec![(0.0, 1.0)] }
};
let sections = sections.into_iter()
.filter(|(t1, t2)| t1 != t2)
.map(|(t1, t2)| curve.section(t1, t2))
.collect::<SmallVec<[_; 8]>>();
let offset_distance = final_offset-initial_offset;
sections.into_iter()
.flat_map(|section| {
let (t1, t2) = section.original_curve_t_values();
let (offset1, offset2) = (t1*offset_distance+initial_offset, t2*offset_distance+initial_offset);
subdivide_offset(§ion, offset1, offset2, 0)
})
.collect()
}
fn subdivide_offset<CurveIn, CurveOut>(curve: &CurveSection<'_, CurveIn>, initial_offset: f64, final_offset: f64, depth: usize) -> SmallVec<[CurveOut; 2]>
where
CurveIn: NormalCurve+BezierCurve,
CurveOut: BezierCurveFactory<Point=CurveIn::Point>,
CurveIn::Point: Coordinate2D+Normalize,
{
const MAX_DEPTH: usize = 5;
let start = curve.start_point();
let end = curve.end_point();
let normal_start = curve.normal_at_pos(0.0);
let normal_end = curve.normal_at_pos(1.0);
let normal_start = normal_start.to_unit_vector();
let normal_end = normal_end.to_unit_vector();
let intersect_point = ray_intersects_ray(&(start, start+normal_start), &(end, end+normal_end));
if intersect_point.is_none() {
if characterize_curve(curve) != CurveCategory::Linear && depth < MAX_DEPTH {
let divide_point = 0.5;
let mid_offset = initial_offset + (final_offset - initial_offset) * divide_point;
let left_curve = curve.subsection(0.0, divide_point);
let right_curve = curve.subsection(divide_point, 1.0);
let left_offset = subdivide_offset(&left_curve, initial_offset, mid_offset, depth+1);
let right_offset = subdivide_offset(&right_curve, mid_offset, final_offset, depth+1);
return left_offset.into_iter()
.chain(right_offset)
.collect();
}
}
if let Some(intersect_point) = intersect_point {
let start_distance = intersect_point.distance_to(&start);
let end_distance = intersect_point.distance_to(&end);
let distance_ratio = start_distance.min(end_distance) / start_distance.max(end_distance);
if distance_ratio < 0.995 && depth < MAX_DEPTH {
let mut extremities = curve.find_extremities();
extremities.retain(|item| item > &0.01 && item < &0.99);
if extremities.is_empty() {
let divide_point = 0.5;
let mid_offset = initial_offset + (final_offset - initial_offset) * divide_point;
let left_curve = curve.subsection(0.0, divide_point);
let right_curve = curve.subsection(divide_point, 1.0);
let left_offset = subdivide_offset(&left_curve, initial_offset, mid_offset, depth+1);
let right_offset = subdivide_offset(&right_curve, mid_offset, final_offset, depth+1);
left_offset.into_iter()
.chain(right_offset)
.collect()
} else {
let mut extremities = extremities;
extremities.insert(0, 0.0);
extremities.push(1.0);
extremities.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
extremities
.into_iter()
.tuple_windows()
.flat_map(|(t1, t2)| {
let subsection = curve.subsection(t1, t2);
let offset1 = initial_offset + (final_offset - initial_offset) * t1;
let offset2 = initial_offset + (final_offset - initial_offset) * t2;
let res = subdivide_offset(&subsection, offset1, offset2, depth+1);
res
})
.collect()
}
} else {
smallvec![offset_by_scaling(curve, initial_offset, final_offset, intersect_point, normal_start, normal_end)]
}
} else {
smallvec![offset_by_moving(curve, initial_offset, final_offset, normal_start, normal_end)]
}
}
#[inline]
fn offset_by_scaling<CurveIn, CurveOut>(curve: &CurveIn, initial_offset: f64, final_offset: f64, intersect_point: CurveIn::Point, unit_normal_start: CurveIn::Point, unit_normal_end: CurveIn::Point) -> CurveOut
where
CurveIn: NormalCurve+BezierCurve,
CurveOut: BezierCurveFactory<Point=CurveIn::Point>,
CurveIn::Point: Coordinate2D+Normalize,
{
let start = curve.start_point();
let end = curve.end_point();
let (cp1, cp2) = curve.control_points();
let new_start = start + (unit_normal_start * initial_offset);
let new_end = end + (unit_normal_end * final_offset);
let start_scale = (intersect_point.distance_to(&new_start))/(intersect_point.distance_to(&start));
let end_scale = (intersect_point.distance_to(&new_end))/(intersect_point.distance_to(&end));
let cp1_scale = (end_scale - start_scale) * (1.0/3.0) + start_scale;
let cp2_scale = (end_scale - start_scale) * (2.0/3.0) + start_scale;
let new_cp1 = ((cp1-intersect_point) * cp1_scale) + intersect_point;
let new_cp2 = ((cp2-intersect_point) * cp2_scale) + intersect_point;
CurveOut::from_points(new_start, (new_cp1, new_cp2), new_end)
}
#[inline]
fn offset_by_moving<CurveIn, CurveOut>(curve: &CurveIn, initial_offset: f64, final_offset: f64, unit_normal_start: CurveIn::Point, unit_normal_end: CurveIn::Point) -> CurveOut
where
CurveIn: NormalCurve+BezierCurve,
CurveOut: BezierCurveFactory<Point=CurveIn::Point>,
CurveIn::Point: Coordinate2D+Normalize,
{
let start = curve.start_point();
let end = curve.end_point();
let (cp1, cp2) = curve.control_points();
let new_start = start + (unit_normal_start * initial_offset);
let new_cp1 = cp1 + (unit_normal_start * initial_offset);
let new_cp2 = cp2 + (unit_normal_end * final_offset);
let new_end = end + (unit_normal_end * final_offset);
CurveOut::from_points(new_start, (new_cp1, new_cp2), new_end)
}