use crate::curves::{Circle2D, Line2D};
use crate::{Point2D, Vector2D};
use std::f64::consts::TAU;
pub trait ParametricCurve2D {
fn eval_point(&self, u: f64) -> Point2D;
fn eval_derivative(&self, u: f64, order: u32) -> Vector2D;
fn bounds(&self) -> (f64, f64);
fn period(&self) -> Option<f64>;
fn eval_points(&self, us: &[f64]) -> Vec<Point2D> {
us.iter().map(|&u| self.eval_point(u)).collect()
}
fn is_periodic(&self) -> bool {
self.period().is_some()
}
}
impl ParametricCurve2D for Line2D {
fn eval_point(&self, u: f64) -> Point2D {
Line2D::eval_point(self, u)
}
fn eval_derivative(&self, u: f64, order: u32) -> Vector2D {
Line2D::eval_derivative(self, u, order)
}
fn bounds(&self) -> (f64, f64) {
(f64::NEG_INFINITY, f64::INFINITY)
}
fn period(&self) -> Option<f64> {
None
}
fn eval_points(&self, us: &[f64]) -> Vec<Point2D> {
Line2D::eval_points(self, us)
}
}
impl ParametricCurve2D for Circle2D {
fn eval_point(&self, u: f64) -> Point2D {
Circle2D::eval_point(self, u)
}
fn eval_derivative(&self, u: f64, order: u32) -> Vector2D {
Circle2D::eval_derivative(self, u, order)
}
fn bounds(&self) -> (f64, f64) {
(0.0, TAU)
}
fn period(&self) -> Option<f64> {
Some(TAU)
}
fn eval_points(&self, us: &[f64]) -> Vec<Point2D> {
Circle2D::eval_points(self, us)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum Curve2D {
Line(Line2D),
Circle(Circle2D),
}
impl ParametricCurve2D for Curve2D {
fn eval_point(&self, u: f64) -> Point2D {
match self {
Curve2D::Line(c) => c.eval_point(u),
Curve2D::Circle(c) => c.eval_point(u),
}
}
fn eval_derivative(&self, u: f64, order: u32) -> Vector2D {
match self {
Curve2D::Line(c) => c.eval_derivative(u, order),
Curve2D::Circle(c) => c.eval_derivative(u, order),
}
}
fn bounds(&self) -> (f64, f64) {
match self {
Curve2D::Line(c) => c.bounds(),
Curve2D::Circle(c) => c.bounds(),
}
}
fn period(&self) -> Option<f64> {
match self {
Curve2D::Line(c) => c.period(),
Curve2D::Circle(c) => c.period(),
}
}
fn eval_points(&self, us: &[f64]) -> Vec<Point2D> {
match self {
Curve2D::Line(c) => c.eval_points(us),
Curve2D::Circle(c) => c.eval_points(us),
}
}
}
impl From<Line2D> for Curve2D {
fn from(curve: Line2D) -> Curve2D {
Curve2D::Line(curve)
}
}
impl From<Circle2D> for Curve2D {
fn from(curve: Circle2D) -> Curve2D {
Curve2D::Circle(curve)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample<C: ParametricCurve2D>(c: &C) -> Point2D {
c.eval_point(0.5)
}
fn line() -> Line2D {
Line2D::new(Point2D::ORIGIN, Vector2D::X).unwrap()
}
fn circle() -> Circle2D {
Circle2D::new(Point2D::ORIGIN, 2.0).unwrap()
}
#[test]
fn test_sample_line_agrees_with_inherent() {
let l = line();
assert_eq!(sample(&l), l.eval_point(0.5));
}
#[test]
fn test_sample_circle_agrees_with_inherent() {
let c = circle();
assert_eq!(sample(&c), c.eval_point(0.5));
}
#[test]
fn test_sample_curve2d_enum_agrees_with_inherent() {
let l = line();
let wrapped: Curve2D = l.into();
assert_eq!(sample(&wrapped), l.eval_point(0.5));
}
#[test]
fn test_heterogeneous_vec_eval() {
let l = line();
let c = circle();
let curves: Vec<Curve2D> = vec![l.into(), c.into()];
let points: Vec<Point2D> = curves.iter().map(|curve| curve.eval_point(0.25)).collect();
assert_eq!(points[0], l.eval_point(0.25));
assert_eq!(points[1], c.eval_point(0.25));
}
#[test]
fn test_from_line_round_trips() {
let l = line();
match Curve2D::from(l) {
Curve2D::Line(inner) => assert_eq!(inner, l),
_ => panic!("expected Curve2D::Line"),
}
}
#[test]
fn test_from_circle_round_trips() {
let c = circle();
match Curve2D::from(c) {
Curve2D::Circle(inner) => assert_eq!(inner, c),
_ => panic!("expected Curve2D::Circle"),
}
}
#[test]
fn test_line_bounds_and_period() {
let l = line();
assert_eq!(
ParametricCurve2D::bounds(&l),
(f64::NEG_INFINITY, f64::INFINITY)
);
assert_eq!(ParametricCurve2D::period(&l), None);
assert!(!ParametricCurve2D::is_periodic(&l));
let wrapped: Curve2D = l.into();
assert_eq!(wrapped.bounds(), (f64::NEG_INFINITY, f64::INFINITY));
assert_eq!(wrapped.period(), None);
assert!(!wrapped.is_periodic());
}
#[test]
fn test_circle_bounds_and_period() {
let c = circle();
assert_eq!(ParametricCurve2D::bounds(&c), (0.0, TAU));
assert_eq!(ParametricCurve2D::period(&c), Some(TAU));
assert!(ParametricCurve2D::is_periodic(&c));
let wrapped: Curve2D = c.into();
assert_eq!(wrapped.bounds(), (0.0, TAU));
assert_eq!(wrapped.period(), Some(TAU));
assert!(wrapped.is_periodic());
}
#[test]
fn test_default_eval_points_matches_mapped_eval_point_for_each_type() {
let us = [0.0, 0.5, 1.0, -2.0];
let l = line();
let expected: Vec<Point2D> = us.iter().map(|&u| l.eval_point(u)).collect();
assert_eq!(ParametricCurve2D::eval_points(&l, &us), expected);
let c = circle();
let expected: Vec<Point2D> = us.iter().map(|&u| c.eval_point(u)).collect();
assert_eq!(ParametricCurve2D::eval_points(&c, &us), expected);
}
#[test]
fn test_curve2d_enum_eval_points_matches_mapped_eval_point() {
let us = [0.0, 0.5, 1.0, -2.0];
let curves: Vec<Curve2D> = vec![line().into(), circle().into()];
for curve in &curves {
let expected: Vec<Point2D> = us.iter().map(|&u| curve.eval_point(u)).collect();
assert_eq!(curve.eval_points(&us), expected);
}
}
#[test]
fn test_eval_derivative_delegates_to_inherent() {
let l = line();
assert_eq!(
ParametricCurve2D::eval_derivative(&l, 0.5, 1),
l.eval_derivative(0.5, 1)
);
let c = circle();
assert_eq!(
ParametricCurve2D::eval_derivative(&c, 0.5, 1),
c.eval_derivative(0.5, 1)
);
}
}