use super::{CubicBezier, LineSegment, Point, PointDot, PointIndex, PointNorm, QuadraticBezier};
use num_traits::{Float, NumCast};
const DEFAULT_LENGTH_STEPS: usize = 64;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum BezierSegment<P: Point> {
Linear(LineSegment<P>),
Quadratic(QuadraticBezier<P>),
Cubic(CubicBezier<P>),
}
impl<P> BezierSegment<P>
where
P: Point,
{
pub fn eval(&self, t: P::Scalar) -> P {
match self {
BezierSegment::Linear(segment) => segment.eval(t),
BezierSegment::Quadratic(segment) => segment.eval(t),
BezierSegment::Cubic(segment) => segment.eval(t),
}
}
pub fn start(&self) -> P {
match self {
BezierSegment::Linear(segment) => segment.start,
BezierSegment::Quadratic(segment) => segment.start,
BezierSegment::Cubic(segment) => segment.start,
}
}
#[inline]
pub fn end(&self) -> P {
match self {
BezierSegment::Linear(segment) => segment.end,
BezierSegment::Quadratic(segment) => segment.end,
BezierSegment::Cubic(segment) => segment.end,
}
}
pub fn reverse(&self) -> Self {
match self {
BezierSegment::Linear(segment) => BezierSegment::Linear(segment.reverse()),
BezierSegment::Quadratic(segment) => BezierSegment::Quadratic(segment.reverse()),
BezierSegment::Cubic(segment) => BezierSegment::Cubic(segment.reverse()),
}
}
#[inline]
pub fn is_linear<F>(&self, tolerance: F) -> bool
where
P: PointNorm,
F: Float + NumCast,
{
let tolerance = <P::Scalar as NumCast>::from(tolerance).unwrap();
match self {
BezierSegment::Linear(..) => true,
BezierSegment::Quadratic(segment) => segment.is_linear(tolerance),
BezierSegment::Cubic(segment) => segment.is_linear(tolerance),
}
}
#[inline]
pub fn baseline(&self) -> LineSegment<P> {
match self {
BezierSegment::Linear(segment) => *segment,
BezierSegment::Quadratic(segment) => segment.baseline(),
BezierSegment::Cubic(segment) => segment.baseline(),
}
}
#[inline]
pub fn bounding_box(&self) -> [(P::Scalar, P::Scalar); P::DIM]
where
P: PointIndex,
[P::Scalar; 1]: tinyvec::Array<Item = P::Scalar>,
[P::Scalar; 2]: tinyvec::Array<Item = P::Scalar>,
[P::Scalar; 3]: tinyvec::Array<Item = P::Scalar>,
[P::Scalar; 4]: tinyvec::Array<Item = P::Scalar>,
{
match self {
BezierSegment::Linear(segment) => segment.bounding_box(),
BezierSegment::Quadratic(segment) => segment.bounding_box(),
BezierSegment::Cubic(segment) => segment.bounding_box(),
}
}
pub fn split<F>(&self, t: F) -> (BezierSegment<P>, BezierSegment<P>)
where
F: Float + NumCast,
{
let t = <P::Scalar as NumCast>::from(t).unwrap();
match self {
BezierSegment::Linear(segment) => {
let (a, b) = segment.split(t);
(BezierSegment::Linear(a), BezierSegment::Linear(b))
}
BezierSegment::Quadratic(segment) => {
let (a, b) = segment.split(t);
(BezierSegment::Quadratic(a), BezierSegment::Quadratic(b))
}
BezierSegment::Cubic(segment) => {
let (a, b) = segment.split(t);
(BezierSegment::Cubic(a), BezierSegment::Cubic(b))
}
}
}
pub fn tangent(&self, t: P::Scalar) -> P
where
P: PointNorm,
{
match self {
BezierSegment::Linear(segment) => segment.tangent(t),
BezierSegment::Quadratic(segment) => segment.tangent(t),
BezierSegment::Cubic(segment) => segment.tangent(t),
}
}
pub fn curvature(&self, t: P::Scalar) -> P::Scalar
where
P: PointNorm + PointDot,
{
match self {
BezierSegment::Linear(segment) => segment.curvature(t),
BezierSegment::Quadratic(segment) => segment.curvature(t),
BezierSegment::Cubic(segment) => segment.curvature(t),
}
}
pub fn normal(&self, t: P::Scalar) -> Option<P>
where
P: PointNorm + PointDot,
{
match self {
BezierSegment::Linear(segment) => segment.normal(t),
BezierSegment::Quadratic(segment) => segment.normal(t),
BezierSegment::Cubic(segment) => segment.normal(t),
}
}
pub fn t_at_length_approx(&self, s: P::Scalar, nsteps: usize) -> P::Scalar
where
P: PointNorm,
{
match self {
BezierSegment::Linear(segment) => segment.t_at_length_approx(s, nsteps),
BezierSegment::Quadratic(segment) => segment.t_at_length_approx(s, nsteps),
BezierSegment::Cubic(segment) => segment.t_at_length_approx(s, nsteps),
}
}
pub fn t_at_length(&self, s: P::Scalar) -> P::Scalar
where
P: PointNorm,
{
self.t_at_length_approx(s, DEFAULT_LENGTH_STEPS)
}
pub fn point_at_length_approx(&self, s: P::Scalar, nsteps: usize) -> P
where
P: PointNorm,
{
self.eval(self.t_at_length_approx(s, nsteps))
}
pub fn point_at_length(&self, s: P::Scalar) -> P
where
P: PointNorm,
{
self.point_at_length_approx(s, DEFAULT_LENGTH_STEPS)
}
}
impl<P> From<LineSegment<P>> for BezierSegment<P>
where
P: Point,
{
fn from(s: LineSegment<P>) -> Self {
BezierSegment::Linear(s)
}
}
impl<P> From<QuadraticBezier<P>> for BezierSegment<P>
where
P: Point,
{
fn from(s: QuadraticBezier<P>) -> Self {
BezierSegment::Quadratic(s)
}
}
impl<P> From<CubicBezier<P>> for BezierSegment<P>
where
P: Point,
{
fn from(s: CubicBezier<P>) -> Self {
BezierSegment::Cubic(s)
}
}
impl<P> Default for BezierSegment<P>
where
P: Point + Default,
{
fn default() -> Self {
BezierSegment::Linear(LineSegment::new(P::default(), P::default()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{EPSILON, PointN, PointNorm};
#[test]
fn bezier_segment_api_parity() {
let line = LineSegment::new(PointN::new([0f64, 0f64]), PointN::new([2f64, 0f64]));
let segment = BezierSegment::from(line);
assert_eq!(segment.start(), segment.eval(0.0));
assert_eq!(segment.end(), segment.eval(1.0));
let reversed = segment.reverse();
let p0 = segment.eval(0.25);
let p1 = reversed.eval(0.75);
assert!((p0 - p1).squared_norm() < EPSILON);
let tangent = segment.tangent(0.3);
assert!((tangent[0] - 1.0).abs() < EPSILON);
assert!(tangent[1].abs() < EPSILON);
let curvature = segment.curvature(0.3);
assert!(curvature.abs() < EPSILON);
assert!(segment.normal(0.3).is_none());
let t = segment.t_at_length(1.0);
assert!((t - 0.5).abs() < EPSILON);
let p = segment.point_at_length(1.0);
assert!((p - PointN::new([1.0, 0.0])).squared_norm() < EPSILON);
}
}