3D Cubic Splines from Irregularly Sampled Timepoints
Interpolating trajectories with cubic splines.
Motivation
Imagine a rigid body moving in 3D space, subject to forces. Then imagine integrating this system with an ODE solver and getting the output states as a list of timepoints, positions and velocities. Optionally also orientations and angular velocities, but we can ignore those for now.
Adaptive ODE solvers are relatively common, which could result in states at irregularly sampled timepoints. This means the deltatime between timepoints isn’t constant, which is again often problematic when doing signal processing or other forms of data analysis, typically being solved with various interpolation schemes.
Let’s say we now have two rigid bodies, and we want to check if they collide when simulating the systems. Also assume that we are interested in checking if the two objects are going to collide at some future timepoint, and then finding the time of collision. One option is to have a low enough step size, and just check every iteration, as game engines often do. Here is an alternative approach relying on spline interpolations for irregularly sampled timepoints.
Let’s first construct a continuous representation of the trajectories using splines.
Splines
Given a set of states:
here assumed to be 3-dimensional, find a function that perfectly hits each of these states:
for .
As an intermediary step, let’s start by assuming that the states are actually 1-dimensional. Using a polynomial to interpolate points is relatively easy. Given points, a polynomial of degree can be used to perfectly fit each of these points. But higher-degree polynomials have a lot of problems (poor generalizability outside the points, numerical instability from high exponents, high variance to noise in the dataset). A spline is defined as a set of piecewise polynomials, consisting of many polynomials with a lower degree instead of one single high degree polynomial. Splines will often fix a lot of the mentioned issues, and can be considered a much more robust way to interpolate.
For states, a spline would then be represented as different polynomials defined on each interval: such that:
This means that the spline segments are continuous on the knot points . If the velocity constraints are added in:
then the splines also have a continuous first derivative on the knot points.
Cubic Splines
There are a lot of different types of splines, and ways to construct them.
Many physical systems modeled with Newton’s second law will typically have a continuous state and first derivative. The second derivative (like acceleration) will not necessarily be continuous, as that depends on the active forces.
This then motivates the use of spline segments with cubic polynomials:
which means that acceleration will be continuous within each spline segment, but can be discontinuous between them.
A cubic polynomial has 4 coefficients. Given positions and velocities at 2 knot points, this results in equations:
and unknowns. Rewrite as a linear system:
which is solvable as long as , and can be solved symbolically to get explicit expressions for the coefficients of each spline segment. Alternatively with numerical solvers.
Cubic Hermite Splines (Tangent 1)
Right now the timepoints are assumed to be any value, as long as they are sorted in ascending order. Sometimes the timepoints can get relatively large, which can lead to some numerical errors. Something that would simplify some calculations and numerical problems is to normalize the time between intervals to always go from 0 to 1.
Define a normalized time as:
for . Then define the new spline segment polynomial:
such that:
Can then define:
for each spline segment. This leads to a simpler form:
This approach turns out be to equivalent to a cubic Hermite spline, which uses a set of basis functions to define the polynomial:
Expanding the above expression and collecting the terms for the coefficients can be shown to lead to the same result as solving the linear system.
Cubic Hermite splines are a relatively standard way of doing it, so mentioned here for completeness. It is often convenient to not have to rescale the timepoints, so the initial approach is going to be the one used forwards.
3D Splines
Let’s now go back to the original 3-dimensional trajectories. Extending splines to work in 3-dimensions is as easy as using a separate spline for each coordinate, and concatenating in a vector. Each spline segment is found by independently estimating the coefficients for that segment and coordinate. For cubic splines this becomes:
Another way to interpret 3D splines is as follows:
meaning one cubic spline with the coefficients as vectors. This is of course the same mathematically, but can be important to consider when implementing it in code. Basically vector of splines vs spline of vectors.
Horner’s Method for Evaluating Polynomials (Tangent 2)
Let’s use a standard cubic polynomial as an example:
To evaluate this polynomial naively, an algorithm can look like this:
def f_naive(t, a, b, c, d):
t2 = t * t
t3 = t2 * t
return a * t3 + b * t2 + c * t + d requiring a total of 5 multiplications and 3 additions. As floating point multiplications are a lot more expensive than additions, a more efficient implementation looks like this:
def f_horner(t, a, b, c, d):
return (((a * t) + b) * t + c) * t + d called Horner’s method, and only requires 3 multiplications and 3 additions. For a general polynomial of degree n, the implementation could look like this:
def f_horner(t, coeffs):
n = len(coeffs)
f = coeffs[0]
for i in range(1, n):
f = f * t + coeffs[i]
return f
End
Now that we have a way to represent trajectories as continuous functions, the followup post goes into how to use these to check for collisions between two different trajectories.