3D Cubic Spline Trajectory Intersections
How to algorithmically check for collisions of 3D trajectories with non-convex optimization.
Setup
This is a followup to this post on 3D trajectories with cubic splines.
Let’s now assume that we have two different rigid bodies moving through 3D space, and each rigid body has a trajectory given with 3D cubic splines:
defined for each segment .
3D Spline Intersections
As trajectories in 3D will never intersect exactly, it is not enough to just solve an equation. What we need to do instead is look at the distance between the trajectories, and do a search for the minimum value(s). This can be formulated as an optimization problem.
Begin with looking at the distance between two trajectories over a single spline segment and . For now, let’s also assume that the spline segments start and end at the same timepoints. Define the trajectory difference as , which is another polynomial of the same degree. The distance function at spline segment can then be expressed as:
for , and using the norm for the induced distance metric.
If and are vectors made up of cubic polynomials, then is now a single polynomial of degree . Finding the minimum distance over the segment interval is then equivalent minimizing this function over the interval:
This optimization problem is non-convex in the general case, as most polynomials will swing up and down multiple times. But for a polynomial, the minimum will always be at either an extremal point or at the interval boundary. Evaluating the polynomial at the boundary is trivial, so then it remains to find the extremal points.
Extremal points are located at the same points where the derivative of the function is zero. As it is trivial to differentiate a polynomial, this means that the minimum points can be found where the derivative is equal to zero, reducing the whole problem to finding polynomial roots.
def derivative(coeffs):
n = len(coeffs) - 1
d_coeffs = [0 for _ in range(n)]
for i in range(n):
d_coeffs[i] = coeffs[i] * (n - i)
return d_coeffs def derivative(coeffs):
n = coeffs.shape[0] - 1
return coeffs[:-1] * np.arange(n, 0, -1) Polynomial Root Finding (Tangent)
The fundamental theorem of algebra states that a polynomial of degree will always have complex-valued roots. As this is about solving a minimization problem, we only care about the real roots. And in a given bounded interval, a polynomial can have anywhere between and real roots, including roots with higher multiplicities.
The standard way to find polynomial roots is to rely on some linear algebra. For a square matrix , the characteristic polynomial of the matrix is defined as:
and is a polynomial of degree in . It also has the property that the roots of the polynomial is equal to the eigenvalues of the matrix. When finding eigenvalues of and matrices by hand, this is a common method to use.
Doing this process in reverse, and relying on existing eigenvalue solvers, is then a way to find polynomial roots. Start with an degree polynomial of the form:
Such that the leading coefficient is . If it is different from , divide all the coefficients by this leading coeficient, as the resulting polynomial will have the same roots.
The companion matrix is defined like this:
and is deliberately defined to have the original polynomial as its characteristic polynomial. Finding the roots of this polynomial is then equivalent to finding the eigenvalues of the companion matrix. One option here is to use the QR algorithm, which finds all the eigenvalues simultaneously. One way to do this is with an iterative algorithm that computes a QR decomposition every iteration, but there are ways to make the algorithm more efficient, giving a time complexity of as most linear algebra algorithms have.
Low Degree Polynomial Root Finding
The companion matrix / QR algorithm approach is probably the best method for higher degree polynomials / larger matrices. But for lower degrees, there are more efficient algorithms. The following is mostly an explanation of [Yuksel 2022]. It works as a recursive algorithm, so let’s start with defining the base cases.
A degree zero polynomial is not really well defined, so let’s here assume that it means a function of the form , meaning a constant. A degree zero polynomial will then have zero roots. For a degree polynomial on the form: , there is one real root found at . For a quadractic polynomial there is the familiar ABC formula:
or alternatively the more numerically stable version:
giving either zero, one or two real roots. Continuing like this, there are explicit symbolic formulas for degree and polynomials, but those are too complicated to write out and use. Fifth degree and higher polynomials are famously unsolvable with symbolic expressions in the general case.
One thing to realize is that every polynomial root lies in between two extremal points of the polynomial. So one way to find roots is to first find the extremal points, and then do a local search inside the interval of the extremal points. And extremal points can be found by differentiating the polynomial and finding the roots of the derivative, leading to the recursive step.
If we go back to a degree polynomial, there are at most two extremal points. Differentiating it gives a degree polynomial, whose roots can be found easily with the formulas above. If we find two distinct extremal points, and these extremal points evaluate to different signs (one positive and one negative), then there must be a root in between. This is a convex optimization problem that can be solved with for example Newton’s method or just a ternary search.
So to find the roots of a degree polynomial, differentiate it and find the roots of the degree polynomial recursively. Then iterate over the intervals and solve the local convex optimization problem for each.
This method has a worse time complexity compared to the QR algorithm, but ends up being more performant anyways when the degree is small enough, as time complexity is only measuring the asymptotic performance. For a polynomial of degree 5, which is what we end up solving for with cubic splines, this is definitely low enough to be worth it.
Cauchy Bound
In addition to searching between extremal points, it is also necessary to search from to the first extremal point, and from the last extremal point and to . To limit the search a little, it is enough to search inside what is called the Cauchy bound of the polynomial.
More specifically, all the complex-valued roots of a complex-valued polynomial:
is guaranteed to lie within the following disk centered at the origin:
with the Cauchy bound defined as:
So when searching for roots, it is enough to limit the search to inside .
def cauchy_bound(coeffs):
n = len(coeffs)
alpha_max = 0.0
for i in range(1, n):
alpha = abs(coeffs[i] / coeffs[0])
alpha_max = max(alpha_max, alpha)
return 1.0 + alpha_max def cauchy_bound(coeffs):
return 1.0 + np.max(np.abs(coeffs[1:] / coeffs[0])) Intersecting Splines at Different Sampling Times
So far, the assumption has been that spline segments for the two trajectories has been at the same time interval. But one of the original motivations of using splines is because it works with irregularly sampled timepoints, so let’s correct this now.
Let’s explain with an example, and say that we have these timepoints: , and that . Then we’ll say that trajectory is given at , with spline segments: at , at and at . Trajectory is given at with spline segments at and at . When searching for the minimum distance, we need to split the intervals up with corresponding spline segments:
here assuming that the splines just continue with the first and last segment outside the first and last timepoint of the whole spline. This basically means taking the union of the two sets of timepoints, but restricting it to the intersection instead could also work if that is desirable.
Then solve the minimization problem for each of these intervals, and get the global minimum by taking the minimum of all of these again.
Putting it all together
- Inputs: timepoints, positions and velocities
- Trajectory 1 with states:
- Trajectory 2 with states:
- Compute cubic spline segments:
- Take the union of all the timepoints of both trajectories, and sort them in ascending order
- For each pair of timepoints, find the two spline segments that is defined in this interval
- Compute the spline segment difference and solve the minimization problem with the polynomial root finding method
- Find the global minimum over all the results from the minimization problems
Future work: Non-Spherical Hitboxes
todo