Skip to content

Commit

Permalink
Add Parametric trait
Browse files Browse the repository at this point in the history
Represents a single-variable parametric curve.
  • Loading branch information
jdahlstrom committed Jan 11, 2025
1 parent 8dbf3ec commit a2286cf
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 20 deletions.
13 changes: 11 additions & 2 deletions core/src/geom.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Basic geometric primitives.
use crate::math::{Point2, Point3, Vec2, Vec3};
use crate::math::{Affine, Linear, Parametric, Point2, Point3, Vec2, Vec3};
use crate::render::Model;

pub use mesh::Mesh;
Expand Down Expand Up @@ -30,6 +30,8 @@ pub struct Tri<V>(pub [V; 3]);
#[repr(transparent)]
pub struct Plane<V>(pub(crate) V);

pub struct Ray<Orig, Dir>(pub Orig, pub Dir);

/// A surface normal.
// TODO Use distinct type rather than alias
pub type Normal3 = Vec3;
Expand All @@ -39,4 +41,11 @@ pub const fn vertex<P, A>(pos: P, attrib: A) -> Vertex<P, A> {
Vertex { pos, attrib }
}

pub struct Ray<Orig, Dir>(pub Orig, pub Dir);
impl<T> Parametric<T> for Ray<T, T::Diff>
where
T: Affine<Diff: Linear<Scalar = f32>>,
{
fn eval(&self, t: f32) -> T {
self.0.add(&self.1.mul(t))
}
}
35 changes: 30 additions & 5 deletions core/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub use {
orthographic, perspective, scale, scale3, translate, translate3,
viewport, Mat3x3, Mat4x4, Matrix,
},
param::Parametric,
point::{pt2, pt3, Point, Point2, Point2u, Point3},
space::{Affine, Linear},
spline::{smootherstep, smoothstep, BezierSpline, CubicBezier},
Expand All @@ -44,6 +45,7 @@ pub mod approx;
pub mod color;
pub mod float;
pub mod mat;
pub mod param;
pub mod point;
pub mod rand;
pub mod space;
Expand All @@ -52,8 +54,32 @@ pub mod vary;
pub mod vec;

/// Trait for linear interpolation between two values.
pub trait Lerp {
pub trait Lerp: Sized {
/// Linearly interpolates between `self` and `other`.
///
/// if `t` = 0, returns `self`; if `t` = 1, returns `other`.
/// For 0 < `t` < 1, returns the weighted average of `self` and `other`
/// ```text
/// (1 - t) * self + t * other
/// ```
///
/// This method does not panic if `t < 0.0` or `t > 1.0`, or if `t`
/// is a `NaN`, but the return value in those cases is unspecified.
/// Individual implementations may offer stronger guarantees.
fn lerp(&self, other: &Self, t: f32) -> Self;

/// Returns the (unweighted) average of `self` and `other`.
fn midpoint(&self, other: &Self) -> Self {
self.lerp(other, 0.5)
}
}

/// Linearly interpolates between two values.
///
/// For more information, see [`Lerp::lerp`].
#[inline]
pub fn lerp<T: Lerp>(t: f32, from: T, to: T) -> T {
from.lerp(&to, t)
}

impl<T> Lerp for T
Expand All @@ -65,16 +91,15 @@ where
/// if `t` = 0, returns `self`; if `t` = 1, returns `other`.
/// For 0 < `t` < 1, returns the affine combination
/// ```text
/// self * (1 - t) + other * t
/// (1 - t) * self + t * other
/// ```
/// or rearranged:
/// ```text
/// self + t * (other - self)
/// ```
///
/// This method does not panic if `t < 0.0` or `t > 1.0`, or if `t`
/// is a `NaN`, but the return value in those cases is unspecified.
/// Individual implementations may offer stronger guarantees.
/// If `t < 0.0` or `t > 1.0`, returns the appropriate extrapolated value.
/// If `t` is a NaN, the result is unspecified.
///
/// # Examples
/// ```
Expand Down
45 changes: 45 additions & 0 deletions core/src/math/param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use core::ops::Range;

use super::Lerp;

/// Represents a single-variable parametric curve.
// TODO More documentation
pub trait Parametric<T> {
/// Returns the value of `self` at `t`.
///
/// The "canonical" domain of this function is `t` ∈ [0.0, 1.0],
/// but implementations should return "reasonable" values outside
/// the unit interval as well.
#[allow(unused)]
fn eval(&self, t: f32) -> T;
}

impl<F: Fn(f32) -> T, T> Parametric<T> for F {
/// Returns `self(t)`.
fn eval(&self, t: f32) -> T {
self(t)
}
}

impl<T: Lerp> Parametric<T> for Range<T> {
/// Linearly interpolates between `self.start` and `self.end`.
///
/// Equivalent to `<Self as Lerp>::lerp(&self.start, &self.end, t)`.
///
/// See also [`Lerp::lerp`].
///
/// # Examples
/// ```
/// use core::ops::Range;
/// use retrofire_core::math::{Parametric, pt2, Point2};
///
/// let range: Range<Point2> = pt2(-2.0, 1.0)..pt2(3.0, 2.0);
///
/// assert_eq!(range.eval(0.0), range.start);
/// assert_eq!(range.eval(0.5), pt2(0.5, 1.5));
/// assert_eq!(range.eval(1.0), range.end);
/// ```
fn eval(&self, t: f32) -> T {
self.start.lerp(&self.end, t)
}
}
45 changes: 34 additions & 11 deletions core/src/math/spline.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Bézier curves and splines.
use alloc::{vec, vec::Vec};
use alloc::vec::Vec;
use core::{array, fmt::Debug};

use crate::geom::Ray;
use crate::math::{Affine, Lerp, Linear};
use crate::math::{Affine, Lerp, Linear, Parametric};

/// A cubic Bézier curve, defined by four control points.
///
Expand Down Expand Up @@ -165,7 +165,7 @@ impl<T> BezierSpline<T>
where
T: Affine<Diff: Linear<Scalar = f32> + Clone> + Clone,
{
/// Creates a Bézier curve from the given control points. The number of
/// Creates a Bézier spline from the given control points. The number of
/// elements in `pts` must be 3n + 1 for some positive integer n.
///
/// Consecutive points in `pts` make up Bézier curves such that:
Expand All @@ -175,7 +175,7 @@ where
/// and so on.
///
/// # Panics
/// If `pts.len()` < 4 or if `pts.len()` mod 3 ≠ 1.
/// If `pts.len() < 4` or if `pts.len() % 3 != 1`.
pub fn new(pts: &[T]) -> Self {
assert!(
pts.len() >= 4 && pts.len() % 3 == 1,
Expand All @@ -185,29 +185,30 @@ where
Self(pts.to_vec())
}

/// Constructs a Bézier spline
pub fn from_rays<I>(rays: I) -> Self
where
I: IntoIterator<Item = Ray<T, T::Diff>>,
{
let mut rays = rays.into_iter().peekable();
let mut first = true;
let mut pts = vec![];
while let Some(Ray(p, v)) = rays.next() {
let mut pts = Vec::with_capacity(rays.size_hint().0);
while let Some(ray) = rays.next() {
if !first {
pts.push(p.add(&v.neg()));
pts.push(ray.eval(-1.0));
}
first = false;
pts.push(p.clone());
pts.push(ray.0.clone());
if rays.peek().is_some() {
pts.push(p.add(&v));
pts.push(ray.eval(1.0));
}
}
Self::new(&pts)
}

/// Evaluates `self` at position `t`.
///
/// Returns the first point if `t` < 0 and the last point if `t` > 1.
/// Returns the first point if `t < 0` and the last point if `t > 1`.
pub fn eval(&self, t: f32) -> T {
// invariant self.0.len() != 0 -> last always exists
step(t, &self.0[0], self.0.last().unwrap(), |t| {
Expand Down Expand Up @@ -301,12 +302,30 @@ where
}
}

impl<T> Parametric<T> for CubicBezier<T>
where
T: Affine<Diff: Linear<Scalar = f32> + Clone> + Clone,
{
fn eval(&self, t: f32) -> T {
self.fast_eval(t)
}
}

impl<T> Parametric<T> for BezierSpline<T>
where
T: Affine<Diff: Linear<Scalar = f32> + Clone> + Clone,
{
fn eval(&self, t: f32) -> T {
self.eval(t)
}
}

#[cfg(test)]
mod tests {
use alloc::vec;

use crate::assert_approx_eq;
use crate::math::{pt2, vec2, Point2, Vec2};
use crate::math::{pt2, vec2, Parametric, Point2, Vec2};

use super::*;

Expand All @@ -321,6 +340,8 @@ mod tests {

assert_eq!(1.0, smoothstep(1.0));
assert_eq!(1.0, smoothstep(10.0));

assert_eq!(0.15625, smoothstep.eval(0.25));
}

#[test]
Expand All @@ -334,6 +355,8 @@ mod tests {

assert_eq!(1.0, smootherstep(1.0));
assert_eq!(1.0, smootherstep(10.0));

assert_eq!(0.103515625, smootherstep.eval(0.25));
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion core/src/math/vary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use core::mem;

use crate::math::Lerp;
use super::Lerp;

pub trait ZDiv: Sized {
#[must_use]
Expand Down
1 change: 0 additions & 1 deletion demos/src/bin/bezier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use re::prelude::*;

use re::geom::Ray;
use re::math::rand::{Distrib, Uniform, VectorsOnUnitDisk, Xorshift64};

use re_front::{dims::SVGA_800_600, minifb::Window, Frame};

fn line([mut p0, mut p1]: [Point2; 2]) -> impl Iterator<Item = Point2u> {
Expand Down

0 comments on commit a2286cf

Please sign in to comment.