Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smoothness for (cubic) curves / cubic spline integration #14306

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from

Conversation

mweatherley
Copy link
Contributor

This is another prototype as part of the Curve project. Once again, I have ignored NURBS for the time being.

Objective

Provide a general framework for expressing smoothness for curves (up to C2) and integrate it with bevy_math's existing cubic spline constructions.

Solution

Continuous/differentiable curves

There are a few layers here. At the most basic level, we have a trait-enshrined way of expressing tangent spaces at the level of types:

pub trait HasTangent {
    type Tangent;
}

The point of this is that it is used to define types WithDerivative<T> and WithTwoDerivatives<T> that pair a point with derivatives at that point (elements of the tangent space). For example:

/// A point with a tangent.
pub struct WithDerivative<T>
where
    T: HasTangent,
{
    /// The underlying point.
    pub point: T,
    /// The derivative at `point`.
    pub derivative: T::Tangent,
}

Now, the central idea connecting this to curves is that a differentiable curve is a curve that returns WithDerivative<T> when sampled, so that the point component provides the position and derivative provides the velocity. We have introduced some new traits that work this way: they are just marker traits on top of Curve<...>:

pub trait ContinuousCurve<T>: Curve<T> {}

pub trait DifferentiableCurve<T>: Curve<WithDerivative<T>>
where
    T: HasTangent,
{
}

//...

Of course, with these traits, it's worth keeping in mind that they will not be preserved by the abstract Curve interface methods. For example, applying Curve::map to a differentiable curve, the result need not generally be differentiable. The same is true for many operations, including simple ones like compose (which joins two curves end-to-end).

On the other hand, the result of calling such functions may actually be continuous, differentiable, and so on. To bridge this gap in expressivity, we have also introduced a wrapper struct Blessed and a method bless that produces it so that the user can explicitly elevate to the level of these marker traits when necessary:

/// Bless this curve, allowing it to be treated as continuous, differentiable, and so on.
fn bless(self) -> Blessed<Self> {
    Blessed(self)
}

Cubic segments and curves

The interoperation of cubic curves with this new technology comes in a few parts. Firstly, CubicCurve has an additional marker type parameter S: Smoothness which indicates the global guarantees of smoothness that issue from the curve's construction. This parameter has four levels: Nothing, C0, C1, and C2. CubicCurve<P, S> is itself a Curve<P>, sampling from its position. If its Smoothness parameter is at least C0, then it is a ContinuousCurve. CubicSegment<P> is similar, but it is always a ContinuousCurve (it has no Smoothness parameter).

To access higher derivative information through the Curve interface, there are a pair of new methods on both CubicSegment and CubicCurve:

/// Returns a [curve] whose samples include both the position and velocity from this curve. If
/// this curve is at least C1, then the output is a [`DifferentiableCurve`].
///
/// [curve]: Curve
#[inline]
pub fn with_velocity(&self) -> SegmentDerivative<&Self> {
    SegmentDerivative(&self)
}

/// Returns a [curve] whose samples include the position, velocity, and acceleration from this
/// curve. If this curve is at least C2, then the output is a [`TwiceDifferentiableCurve`].
///
/// [curve]: Curve
#[inline]
pub fn with_velocity_accel(&self) -> SegmentTwoDerivatives<&Self> {
    SegmentTwoDerivatives(&self)
}

CubicCurve is similar. The wrapper types SegmentDerivative and SegmentTwoDerivatives (truly inspired nomenclature) implement DifferentiableCurve and TwiceDifferentiableCurve respectively. Similarly, the wrapper types for CubicCurveCurveDerivative and CurveTwoDerivatives — implement DifferentiableCurve and TwiceDifferentiableCurve if the underlying Smoothness parameter is at least C1 or C2 respectively.

Putting it together

For starters, we can use the Curve interface pretty seamlessly in conjunction with cubic splines:

// This is a `Curve<P>` where `P` is determined by the control data:
let my_curve = CubicBSpline::new(control_data).to_curve();
// so we can do all the usual curve operations on it:
let new_curve = my_curve.reparametrize(|t| f32::cbrt(t));

For something more complex, let's imagine we have some geometric operation which wants a differentiable curve as input and produces some output:

pub fn do_geometry(curve: impl DifferentiableCurve<Vec3>) -> Output { //... }

For a cubic curve, we could do something like this:

let my_curve = CubicHermite::new(points, tangents).to_curve().with_velocity();
let output = do_geometry(my_curve);

On the other hand, if I specified my inputs with CubicBezier but I made sure it's differentiable using the folds of my brain (or am willing to accept the consequences of doing something weird), I can use the bless interface to work with it:

let my_curve = CubicBezier::new(control_data).to_curve().with_velocity();
let output = do_geometry(my_curve.bless());

Migration Guide

Some existing uses of the CubicCurve type will need to be annotated with a Smoothness parameter in order for the program to compile. This is because CubicCurve now tracks its global continuity and differentiability guarantees. For similar reasons, existing uses of CubicCurve::push_segment or Extend::extend will require first calling CubicCurve::without_smoothness in order to destroy the smoothness guarantees from the curve's construction, since arbitrary concatenations of curve segments cannot be known by the compiler to maintain these guarantees.

@alice-i-cecile alice-i-cecile added C-Enhancement A new feature A-Math Fundamental domain-agnostic mathematical operations C-Needs-Release-Note Work that should be called out in the blog due to impact X-Contentious There are nontrivial implications that should be thought through S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged D-Complex Quite challenging from either a design or technical perspective. Ask for help! labels Jul 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Math Fundamental domain-agnostic mathematical operations C-Enhancement A new feature C-Needs-Release-Note Work that should be called out in the blog due to impact D-Complex Quite challenging from either a design or technical perspective. Ask for help! S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants