use crate::surfaces::{BSplineSurface, Cone, Cylinder, Plane, Sphere, Torus};
use crate::{Point3D, Vector3D};
use std::f64::consts::{FRAC_PI_2, TAU};
pub trait ParametricSurface {
fn eval_point(&self, u: f64, v: f64) -> Point3D;
fn eval_derivative(&self, u: f64, v: f64, du: u32, dv: u32) -> Vector3D;
fn u_bounds(&self) -> (f64, f64);
fn v_bounds(&self) -> (f64, f64);
fn u_period(&self) -> Option<f64>;
fn v_period(&self) -> Option<f64>;
fn eval_points(&self, uvs: &[(f64, f64)]) -> Vec<Point3D> {
uvs.iter().map(|&(u, v)| self.eval_point(u, v)).collect()
}
}
impl ParametricSurface for Plane {
fn eval_point(&self, u: f64, v: f64) -> Point3D {
Plane::eval_point(self, u, v)
}
fn eval_derivative(&self, u: f64, v: f64, du: u32, dv: u32) -> Vector3D {
Plane::eval_derivative(self, u, v, du, dv)
}
fn u_bounds(&self) -> (f64, f64) {
(f64::NEG_INFINITY, f64::INFINITY)
}
fn v_bounds(&self) -> (f64, f64) {
(f64::NEG_INFINITY, f64::INFINITY)
}
fn u_period(&self) -> Option<f64> {
None
}
fn v_period(&self) -> Option<f64> {
None
}
fn eval_points(&self, uvs: &[(f64, f64)]) -> Vec<Point3D> {
Plane::eval_points(self, uvs)
}
}
impl ParametricSurface for Cylinder {
fn eval_point(&self, u: f64, v: f64) -> Point3D {
Cylinder::eval_point(self, u, v)
}
fn eval_derivative(&self, u: f64, v: f64, du: u32, dv: u32) -> Vector3D {
Cylinder::eval_derivative(self, u, v, du, dv)
}
fn u_bounds(&self) -> (f64, f64) {
(0.0, TAU)
}
fn v_bounds(&self) -> (f64, f64) {
(f64::NEG_INFINITY, f64::INFINITY)
}
fn u_period(&self) -> Option<f64> {
Some(TAU)
}
fn v_period(&self) -> Option<f64> {
None
}
fn eval_points(&self, uvs: &[(f64, f64)]) -> Vec<Point3D> {
Cylinder::eval_points(self, uvs)
}
}
impl ParametricSurface for Cone {
fn eval_point(&self, u: f64, v: f64) -> Point3D {
Cone::eval_point(self, u, v)
}
fn eval_derivative(&self, u: f64, v: f64, du: u32, dv: u32) -> Vector3D {
Cone::eval_derivative(self, u, v, du, dv)
}
fn u_bounds(&self) -> (f64, f64) {
(0.0, TAU)
}
fn v_bounds(&self) -> (f64, f64) {
(f64::NEG_INFINITY, f64::INFINITY)
}
fn u_period(&self) -> Option<f64> {
Some(TAU)
}
fn v_period(&self) -> Option<f64> {
None
}
fn eval_points(&self, uvs: &[(f64, f64)]) -> Vec<Point3D> {
Cone::eval_points(self, uvs)
}
}
impl ParametricSurface for Sphere {
fn eval_point(&self, u: f64, v: f64) -> Point3D {
Sphere::eval_point(self, u, v)
}
fn eval_derivative(&self, u: f64, v: f64, du: u32, dv: u32) -> Vector3D {
Sphere::eval_derivative(self, u, v, du, dv)
}
fn u_bounds(&self) -> (f64, f64) {
(0.0, TAU)
}
fn v_bounds(&self) -> (f64, f64) {
(-FRAC_PI_2, FRAC_PI_2)
}
fn u_period(&self) -> Option<f64> {
Some(TAU)
}
fn v_period(&self) -> Option<f64> {
None
}
fn eval_points(&self, uvs: &[(f64, f64)]) -> Vec<Point3D> {
Sphere::eval_points(self, uvs)
}
}
impl ParametricSurface for Torus {
fn eval_point(&self, u: f64, v: f64) -> Point3D {
Torus::eval_point(self, u, v)
}
fn eval_derivative(&self, u: f64, v: f64, du: u32, dv: u32) -> Vector3D {
Torus::eval_derivative(self, u, v, du, dv)
}
fn u_bounds(&self) -> (f64, f64) {
(0.0, TAU)
}
fn v_bounds(&self) -> (f64, f64) {
(0.0, TAU)
}
fn u_period(&self) -> Option<f64> {
Some(TAU)
}
fn v_period(&self) -> Option<f64> {
Some(TAU)
}
fn eval_points(&self, uvs: &[(f64, f64)]) -> Vec<Point3D> {
Torus::eval_points(self, uvs)
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Surface {
Plane(Plane),
Cylinder(Cylinder),
Cone(Cone),
Sphere(Sphere),
Torus(Torus),
BSpline(BSplineSurface),
}
impl ParametricSurface for Surface {
fn eval_point(&self, u: f64, v: f64) -> Point3D {
match self {
Surface::Plane(s) => s.eval_point(u, v),
Surface::Cylinder(s) => s.eval_point(u, v),
Surface::Cone(s) => s.eval_point(u, v),
Surface::Sphere(s) => s.eval_point(u, v),
Surface::Torus(s) => s.eval_point(u, v),
Surface::BSpline(s) => s.eval_point(u, v),
}
}
fn eval_derivative(&self, u: f64, v: f64, du: u32, dv: u32) -> Vector3D {
match self {
Surface::Plane(s) => s.eval_derivative(u, v, du, dv),
Surface::Cylinder(s) => s.eval_derivative(u, v, du, dv),
Surface::Cone(s) => s.eval_derivative(u, v, du, dv),
Surface::Sphere(s) => s.eval_derivative(u, v, du, dv),
Surface::Torus(s) => s.eval_derivative(u, v, du, dv),
Surface::BSpline(s) => s.eval_derivative(u, v, du, dv),
}
}
fn u_bounds(&self) -> (f64, f64) {
match self {
Surface::Plane(s) => s.u_bounds(),
Surface::Cylinder(s) => s.u_bounds(),
Surface::Cone(s) => s.u_bounds(),
Surface::Sphere(s) => s.u_bounds(),
Surface::Torus(s) => s.u_bounds(),
Surface::BSpline(s) => s.u_bounds(),
}
}
fn v_bounds(&self) -> (f64, f64) {
match self {
Surface::Plane(s) => s.v_bounds(),
Surface::Cylinder(s) => s.v_bounds(),
Surface::Cone(s) => s.v_bounds(),
Surface::Sphere(s) => s.v_bounds(),
Surface::Torus(s) => s.v_bounds(),
Surface::BSpline(s) => s.v_bounds(),
}
}
fn u_period(&self) -> Option<f64> {
match self {
Surface::Plane(s) => s.u_period(),
Surface::Cylinder(s) => s.u_period(),
Surface::Cone(s) => s.u_period(),
Surface::Sphere(s) => s.u_period(),
Surface::Torus(s) => s.u_period(),
Surface::BSpline(s) => s.u_period(),
}
}
fn v_period(&self) -> Option<f64> {
match self {
Surface::Plane(s) => s.v_period(),
Surface::Cylinder(s) => s.v_period(),
Surface::Cone(s) => s.v_period(),
Surface::Sphere(s) => s.v_period(),
Surface::Torus(s) => s.v_period(),
Surface::BSpline(s) => s.v_period(),
}
}
fn eval_points(&self, uvs: &[(f64, f64)]) -> Vec<Point3D> {
match self {
Surface::Plane(s) => s.eval_points(uvs),
Surface::Cylinder(s) => s.eval_points(uvs),
Surface::Cone(s) => s.eval_points(uvs),
Surface::Sphere(s) => s.eval_points(uvs),
Surface::Torus(s) => s.eval_points(uvs),
Surface::BSpline(s) => s.eval_points(uvs),
}
}
}
impl From<Plane> for Surface {
fn from(surface: Plane) -> Surface {
Surface::Plane(surface)
}
}
impl From<&Plane> for Surface {
fn from(surface: &Plane) -> Surface {
Surface::Plane(*surface)
}
}
impl From<Cylinder> for Surface {
fn from(surface: Cylinder) -> Surface {
Surface::Cylinder(surface)
}
}
impl From<&Cylinder> for Surface {
fn from(surface: &Cylinder) -> Surface {
Surface::Cylinder(*surface)
}
}
impl From<Cone> for Surface {
fn from(surface: Cone) -> Surface {
Surface::Cone(surface)
}
}
impl From<&Cone> for Surface {
fn from(surface: &Cone) -> Surface {
Surface::Cone(*surface)
}
}
impl From<Sphere> for Surface {
fn from(surface: Sphere) -> Surface {
Surface::Sphere(surface)
}
}
impl From<&Sphere> for Surface {
fn from(surface: &Sphere) -> Surface {
Surface::Sphere(*surface)
}
}
impl From<Torus> for Surface {
fn from(surface: Torus) -> Surface {
Surface::Torus(surface)
}
}
impl From<&Torus> for Surface {
fn from(surface: &Torus) -> Surface {
Surface::Torus(*surface)
}
}
impl From<BSplineSurface> for Surface {
fn from(surface: BSplineSurface) -> Surface {
Surface::BSpline(surface)
}
}
impl From<&BSplineSurface> for Surface {
fn from(surface: &BSplineSurface) -> Surface {
Surface::BSpline(surface.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Point3D;
fn sample<S: ParametricSurface>(s: &S) -> Point3D {
s.eval_point(0.3, 0.4)
}
fn plane() -> Plane {
Plane::new(Point3D::ORIGIN, Vector3D::Z).unwrap()
}
fn cylinder() -> Cylinder {
Cylinder::new(Point3D::ORIGIN, Vector3D::Z, 2.0).unwrap()
}
fn cone() -> Cone {
Cone::new(Point3D::ORIGIN, Vector3D::Z, 0.4, 2.0).unwrap()
}
fn sphere() -> Sphere {
Sphere::new(Point3D::ORIGIN, 3.0).unwrap()
}
fn torus() -> Torus {
Torus::new(Point3D::ORIGIN, Vector3D::Z, 5.0, 1.5).unwrap()
}
fn bspline() -> BSplineSurface {
let poles = vec![
vec![Point3D::new(0.0, 0.0, 0.0), Point3D::new(0.0, 1.0, 0.0)],
vec![Point3D::new(1.0, 0.0, 0.0), Point3D::new(1.0, 1.0, 1.0)],
];
BSplineSurface::new(
1,
1,
poles,
vec![0.0, 1.0],
vec![2, 2],
vec![0.0, 1.0],
vec![2, 2],
false,
false,
)
.unwrap()
}
#[test]
fn test_sample_plane_agrees_with_inherent() {
let p = plane();
assert_eq!(sample(&p), p.eval_point(0.3, 0.4));
}
#[test]
fn test_sample_cylinder_agrees_with_inherent() {
let c = cylinder();
assert_eq!(sample(&c), c.eval_point(0.3, 0.4));
}
#[test]
fn test_sample_cone_agrees_with_inherent() {
let c = cone();
assert_eq!(sample(&c), c.eval_point(0.3, 0.4));
}
#[test]
fn test_sample_sphere_agrees_with_inherent() {
let s = sphere();
assert_eq!(sample(&s), s.eval_point(0.3, 0.4));
}
#[test]
fn test_sample_torus_agrees_with_inherent() {
let t = torus();
assert_eq!(sample(&t), t.eval_point(0.3, 0.4));
}
#[test]
fn test_sample_bspline_agrees_with_inherent() {
let b = bspline();
assert_eq!(sample(&b), b.eval_point(0.3, 0.4));
}
#[test]
fn test_sample_surface_enum_agrees_with_inherent() {
let p = plane();
let wrapped: Surface = p.into();
assert_eq!(sample(&wrapped), p.eval_point(0.3, 0.4));
}
#[test]
fn test_heterogeneous_vec_eval() {
let p = plane();
let c = cylinder();
let cn = cone();
let s = sphere();
let t = torus();
let surfaces: Vec<Surface> = vec![p.into(), c.into(), cn.into(), s.into(), t.into()];
let points: Vec<Point3D> = surfaces
.iter()
.map(|surf| surf.eval_point(0.2, 0.1))
.collect();
assert_eq!(points[0], p.eval_point(0.2, 0.1));
assert_eq!(points[1], c.eval_point(0.2, 0.1));
assert_eq!(points[2], cn.eval_point(0.2, 0.1));
assert_eq!(points[3], s.eval_point(0.2, 0.1));
assert_eq!(points[4], t.eval_point(0.2, 0.1));
}
#[test]
fn test_from_plane_round_trips() {
let p = plane();
match Surface::from(p) {
Surface::Plane(inner) => assert_eq!(inner, p),
_ => panic!("expected Surface::Plane"),
}
}
#[test]
fn test_from_plane_ref_round_trips() {
let p = plane();
match Surface::from(&p) {
Surface::Plane(inner) => assert_eq!(inner, p),
_ => panic!("expected Surface::Plane"),
}
}
#[test]
fn test_from_cylinder_round_trips() {
let c = cylinder();
match Surface::from(c) {
Surface::Cylinder(inner) => assert_eq!(inner, c),
_ => panic!("expected Surface::Cylinder"),
}
}
#[test]
fn test_from_cylinder_ref_round_trips() {
let c = cylinder();
match Surface::from(&c) {
Surface::Cylinder(inner) => assert_eq!(inner, c),
_ => panic!("expected Surface::Cylinder"),
}
}
#[test]
fn test_from_cone_round_trips() {
let c = cone();
match Surface::from(c) {
Surface::Cone(inner) => assert_eq!(inner, c),
_ => panic!("expected Surface::Cone"),
}
}
#[test]
fn test_from_cone_ref_round_trips() {
let c = cone();
match Surface::from(&c) {
Surface::Cone(inner) => assert_eq!(inner, c),
_ => panic!("expected Surface::Cone"),
}
}
#[test]
fn test_from_sphere_round_trips() {
let s = sphere();
match Surface::from(s) {
Surface::Sphere(inner) => assert_eq!(inner, s),
_ => panic!("expected Surface::Sphere"),
}
}
#[test]
fn test_from_sphere_ref_round_trips() {
let s = sphere();
match Surface::from(&s) {
Surface::Sphere(inner) => assert_eq!(inner, s),
_ => panic!("expected Surface::Sphere"),
}
}
#[test]
fn test_from_torus_round_trips() {
let t = torus();
match Surface::from(t) {
Surface::Torus(inner) => assert_eq!(inner, t),
_ => panic!("expected Surface::Torus"),
}
}
#[test]
fn test_from_torus_ref_round_trips() {
let t = torus();
match Surface::from(&t) {
Surface::Torus(inner) => assert_eq!(inner, t),
_ => panic!("expected Surface::Torus"),
}
}
#[test]
fn test_from_bspline_round_trips() {
let b = bspline();
match Surface::from(b.clone()) {
Surface::BSpline(inner) => assert_eq!(inner, b),
_ => panic!("expected Surface::BSpline"),
}
}
#[test]
fn test_from_bspline_ref_clones() {
let b = bspline();
match Surface::from(&b) {
Surface::BSpline(inner) => assert_eq!(inner, b),
_ => panic!("expected Surface::BSpline"),
}
}
#[test]
#[allow(clippy::needless_borrows_for_generic_args)]
fn test_into_from_reference_supports_parametrize_on_style_call() {
fn accepts(surface: impl Into<Surface>) -> Surface {
surface.into()
}
let c = cylinder();
match accepts(&c) {
Surface::Cylinder(inner) => assert_eq!(inner, c),
_ => panic!("expected Surface::Cylinder"),
}
}
#[test]
fn test_plane_bounds_and_periods() {
let p = plane();
assert_eq!(
ParametricSurface::u_bounds(&p),
(f64::NEG_INFINITY, f64::INFINITY)
);
assert_eq!(
ParametricSurface::v_bounds(&p),
(f64::NEG_INFINITY, f64::INFINITY)
);
assert_eq!(ParametricSurface::u_period(&p), None);
assert_eq!(ParametricSurface::v_period(&p), None);
let wrapped: Surface = p.into();
assert_eq!(wrapped.u_bounds(), (f64::NEG_INFINITY, f64::INFINITY));
assert_eq!(wrapped.v_bounds(), (f64::NEG_INFINITY, f64::INFINITY));
assert_eq!(wrapped.u_period(), None);
assert_eq!(wrapped.v_period(), None);
}
#[test]
fn test_cylinder_bounds_and_periods() {
let c = cylinder();
assert_eq!(ParametricSurface::u_bounds(&c), (0.0, TAU));
assert_eq!(
ParametricSurface::v_bounds(&c),
(f64::NEG_INFINITY, f64::INFINITY)
);
assert_eq!(ParametricSurface::u_period(&c), Some(TAU));
assert_eq!(ParametricSurface::v_period(&c), None);
let wrapped: Surface = c.into();
assert_eq!(wrapped.u_bounds(), (0.0, TAU));
assert_eq!(wrapped.v_bounds(), (f64::NEG_INFINITY, f64::INFINITY));
assert_eq!(wrapped.u_period(), Some(TAU));
assert_eq!(wrapped.v_period(), None);
}
#[test]
fn test_cone_bounds_and_periods() {
let c = cone();
assert_eq!(ParametricSurface::u_bounds(&c), (0.0, TAU));
assert_eq!(
ParametricSurface::v_bounds(&c),
(f64::NEG_INFINITY, f64::INFINITY)
);
assert_eq!(ParametricSurface::u_period(&c), Some(TAU));
assert_eq!(ParametricSurface::v_period(&c), None);
let wrapped: Surface = c.into();
assert_eq!(wrapped.u_bounds(), (0.0, TAU));
assert_eq!(wrapped.v_bounds(), (f64::NEG_INFINITY, f64::INFINITY));
assert_eq!(wrapped.u_period(), Some(TAU));
assert_eq!(wrapped.v_period(), None);
}
#[test]
fn test_sphere_bounds_and_periods() {
let s = sphere();
assert_eq!(ParametricSurface::u_bounds(&s), (0.0, TAU));
assert_eq!(ParametricSurface::v_bounds(&s), (-FRAC_PI_2, FRAC_PI_2));
assert_eq!(ParametricSurface::u_period(&s), Some(TAU));
assert_eq!(ParametricSurface::v_period(&s), None);
let wrapped: Surface = s.into();
assert_eq!(wrapped.u_bounds(), (0.0, TAU));
assert_eq!(wrapped.v_bounds(), (-FRAC_PI_2, FRAC_PI_2));
assert_eq!(wrapped.u_period(), Some(TAU));
assert_eq!(wrapped.v_period(), None);
}
#[test]
fn test_torus_bounds_and_periods() {
let t = torus();
assert_eq!(ParametricSurface::u_bounds(&t), (0.0, TAU));
assert_eq!(ParametricSurface::v_bounds(&t), (0.0, TAU));
assert_eq!(ParametricSurface::u_period(&t), Some(TAU));
assert_eq!(ParametricSurface::v_period(&t), Some(TAU));
let wrapped: Surface = t.into();
assert_eq!(wrapped.u_bounds(), (0.0, TAU));
assert_eq!(wrapped.v_bounds(), (0.0, TAU));
assert_eq!(wrapped.u_period(), Some(TAU));
assert_eq!(wrapped.v_period(), Some(TAU));
}
#[test]
fn test_bspline_bounds_and_periods() {
let b = bspline();
assert_eq!(ParametricSurface::u_bounds(&b), (0.0, 1.0));
assert_eq!(ParametricSurface::v_bounds(&b), (0.0, 1.0));
assert_eq!(ParametricSurface::u_period(&b), None);
assert_eq!(ParametricSurface::v_period(&b), None);
let wrapped: Surface = b.into();
assert_eq!(wrapped.u_bounds(), (0.0, 1.0));
assert_eq!(wrapped.v_bounds(), (0.0, 1.0));
assert_eq!(wrapped.u_period(), None);
assert_eq!(wrapped.v_period(), None);
}
#[test]
fn test_bspline_enum_dispatch_agrees_with_inherent() {
let b = bspline();
let wrapped: Surface = b.clone().into();
assert_eq!(wrapped.eval_point(0.3, 0.4), b.eval_point(0.3, 0.4));
assert_eq!(
wrapped.eval_derivative(0.3, 0.4, 1, 0),
b.eval_derivative(0.3, 0.4, 1, 0)
);
assert_eq!(
wrapped.eval_derivative(0.3, 0.4, 0, 1),
b.eval_derivative(0.3, 0.4, 0, 1)
);
}
#[test]
fn test_default_eval_points_matches_mapped_eval_point_for_each_type() {
let uvs = [(0.0, 0.0), (0.5, 0.2), (1.0, -0.3), (-2.0, 1.1)];
let p = plane();
let expected: Vec<Point3D> = uvs.iter().map(|&(u, v)| p.eval_point(u, v)).collect();
assert_eq!(ParametricSurface::eval_points(&p, &uvs), expected);
let c = cylinder();
let expected: Vec<Point3D> = uvs.iter().map(|&(u, v)| c.eval_point(u, v)).collect();
assert_eq!(ParametricSurface::eval_points(&c, &uvs), expected);
let cn = cone();
let expected: Vec<Point3D> = uvs.iter().map(|&(u, v)| cn.eval_point(u, v)).collect();
assert_eq!(ParametricSurface::eval_points(&cn, &uvs), expected);
let s = sphere();
let expected: Vec<Point3D> = uvs.iter().map(|&(u, v)| s.eval_point(u, v)).collect();
assert_eq!(ParametricSurface::eval_points(&s, &uvs), expected);
let t = torus();
let expected: Vec<Point3D> = uvs.iter().map(|&(u, v)| t.eval_point(u, v)).collect();
assert_eq!(ParametricSurface::eval_points(&t, &uvs), expected);
}
#[test]
fn test_surface_enum_eval_points_matches_mapped_eval_point() {
let uvs = [(0.0, 0.0), (0.5, 0.2), (1.0, -0.3), (-2.0, 1.1)];
let surfaces: Vec<Surface> = vec![
plane().into(),
cylinder().into(),
cone().into(),
sphere().into(),
torus().into(),
];
for surface in &surfaces {
let expected: Vec<Point3D> =
uvs.iter().map(|&(u, v)| surface.eval_point(u, v)).collect();
assert_eq!(surface.eval_points(&uvs), expected);
}
}
#[test]
fn test_eval_derivative_delegates_to_inherent() {
let p = plane();
assert_eq!(
ParametricSurface::eval_derivative(&p, 0.3, 0.4, 1, 0),
p.eval_derivative(0.3, 0.4, 1, 0)
);
let s = sphere();
assert_eq!(
ParametricSurface::eval_derivative(&s, 0.3, 0.4, 1, 0),
s.eval_derivative(0.3, 0.4, 1, 0)
);
let wrapped: Surface = torus().into();
assert_eq!(
wrapped.eval_derivative(0.3, 0.4, 0, 1),
torus().eval_derivative(0.3, 0.4, 0, 1)
);
}
}