use core::ops::Range;
use arrayvec::ArrayVec;
use crate::{Point, Rect, common};
#[cfg(not(feature = "std"))]
use crate::common::FloatFuncs;
pub const DEFAULT_ACCURACY: f64 = 1e-6;
pub trait ParamCurve: Sized {
fn eval(&self, t: f64) -> Point;
fn subsegment(&self, range: Range<f64>) -> Self;
#[inline]
fn subdivide(&self) -> (Self, Self) {
(self.subsegment(0.0..0.5), self.subsegment(0.5..1.0))
}
fn start(&self) -> Point {
self.eval(0.0)
}
fn end(&self) -> Point {
self.eval(1.0)
}
}
pub trait ParamCurveDeriv {
type DerivResult: ParamCurve;
fn deriv(&self) -> Self::DerivResult;
#[inline]
fn gauss_arclen(&self, coeffs: &[(f64, f64)]) -> f64 {
let d = self.deriv();
coeffs
.iter()
.map(|(wi, xi)| wi * d.eval(0.5 * (xi + 1.0)).to_vec2().hypot())
.sum::<f64>()
* 0.5
}
}
pub trait ParamCurveArclen: ParamCurve {
fn arclen(&self, accuracy: f64) -> f64;
fn inv_arclen(&self, arclen: f64, accuracy: f64) -> f64 {
if arclen <= 0.0 {
return 0.0;
}
let total_arclen = self.arclen(accuracy);
if arclen >= total_arclen {
return 1.0;
}
let mut t_last = 0.0;
let mut arclen_last = 0.0;
let epsilon = accuracy / total_arclen;
let n = 1.0 - epsilon.log2().ceil().min(0.0);
let inner_accuracy = accuracy / n;
let f = |t: f64| {
let (range, dir) = if t > t_last {
(t_last..t, 1.0)
} else {
(t..t_last, -1.0)
};
let arc = self.subsegment(range).arclen(inner_accuracy);
arclen_last += arc * dir;
t_last = t;
arclen_last - arclen
};
common::solve_itp(f, 0.0, 1.0, epsilon, 1, 0.2, -arclen, total_arclen - arclen)
}
}
pub trait ParamCurveArea {
fn signed_area(&self) -> f64;
}
#[derive(Debug, Clone, Copy)]
pub struct Nearest {
pub distance_sq: f64,
pub t: f64,
}
pub trait ParamCurveNearest {
fn nearest(&self, p: Point, accuracy: f64) -> Nearest;
}
pub trait ParamCurveCurvature: ParamCurveDeriv
where
Self::DerivResult: ParamCurveDeriv,
{
#[inline]
fn curvature(&self, t: f64) -> f64 {
let deriv = self.deriv();
let deriv2 = deriv.deriv();
let d = deriv.eval(t).to_vec2();
let d2 = deriv2.eval(t).to_vec2();
d2.cross(d) * d.hypot2().powf(-1.5)
}
}
pub const MAX_EXTREMA: usize = 4;
pub trait ParamCurveExtrema: ParamCurve {
fn extrema(&self) -> ArrayVec<f64, MAX_EXTREMA>;
fn extrema_ranges(&self) -> ArrayVec<Range<f64>, { MAX_EXTREMA + 1 }> {
let mut result = ArrayVec::new();
let mut t0 = 0.0;
for t in self.extrema() {
result.push(t0..t);
t0 = t;
}
result.push(t0..1.0);
result
}
fn bounding_box(&self) -> Rect {
let mut bbox = Rect::from_points(self.start(), self.end());
for t in self.extrema() {
bbox = bbox.union_pt(self.eval(t));
}
bbox
}
}