Spline Interpolation
The mathematics and programming for interpolating three dimensional polynomials of degree 1, 3 and 5.
Introduction
A spline is a function which is defined piecewise by polynomials that interpolate through a set of points. The degree \(d\) of the polynomials determines the overall continuity \(C^{d-1}\) of the spline. When working in three dimensions, the set of points are three dimensional vectors. Each sub-function in the spline is a three dimensional vector function where each coordinate \(x\), \(y\) and \(z\) has a corresponding polynomial of degree \(d\).
A set of \(n\) points needs \(n-1\) sub-functions in order to create a spline which interpolates through all of the points. The degrees of splines which will be covered here are \(d \in \{1, 3, 5\}\). The main reason to why these three is simple. For \(d=1\) the spline is essentially drawing straight lines between each point which has its specific usecase. For \(d=3\) you get a sufficiently smooth curve that can be used as the default interpolation degree. You might in some cases want more smoothness which is why \(d=5\) is implemented, more than \(5\) and numerical instabilities might occur. All of the degrees are odd since even degrees would cause the amount of constraints to be unbalanced. This will become more as you read the post.
As usual, all code presented here is in ANSI C, this post will have a lot more hidden functions such as vector operations. Visit the github repository github.com/fullnitrous/arm for more details. All relevant code is found under /src/core/
.
Parameterization
The first step before calculating the coefficients for each polynomial for each sub-function, is to parameterize the points. Parameterization meaning to assign a value \(t_i\) to each point \(\vec{p_i}\). That way you have intervals \([t_i, t_{i+1}]\) which correspond to one sub-function.
For \(n\) three-dimensional points.
\[ P = \{\vec{p_0}, \space ... \space ,\vec{p_{n-1}}\} \]
Where.
\[ \vec{p_i} = \begin{bmatrix} p_{i,x} \\ p_{i,y} \\ p_{i,z} \end{bmatrix} \]
Then the parameter values.
\[ T = \{t_0, \space, ..., \space t_{n-1}\} \]
Are calculated with the normalized chord length between each point.
\[ \begin{aligned} S &= \sum_{i=0}^{n-1} | \vec{p_i} - \vec{p_{i-1}} | \\ t_0 &= 0 \\ t_{n-1} &= 1 \\ t_i &= \frac{|\vec{p_i} - \vec{p_{i-1}}|}{S} + t_{i-1}, \quad i = 1, \space ..., n-2 \end{aligned} \]
Note that this is not the only way to parameterize the points, but it is the method chosen. The parameterization step is required for all degrees.
Programming the parameterization function is simple. The function intrpl_parameterize
requires the n
points p
. The parameter outputs are stored in the array u
.
void intrpl_parameterize(vec3_t* p, int n, double* u) {
double sum;
int i;
sum = 0.0;
u[0] = 0;
u[n-1] = 1;
for(i = 1; i < n; i++) {
sum += vecmag(vecsub(p[i], p[i-1]));
}
for(i = 1; i < n-1; i++) {
u[i] = vecmag(vecsub(p[i], p[i-1])) / sum + u[i-1];
}
return;
}
Note just for clarity — the parameter values \(t_i\) always start at \(0\) and end at \(1\).
Linear Spline
The linear spline interpolation function, or in other words, a spline with polynomials of degree \(1\), looks like the following.
\[ L(t) = \begin{cases} l_0(t) & t \in [t_0, t_1] \\ \vdots & \vdots \\ l_{n-2}(t) & t \in (t_{n-2}, t_{n-1}] \end{cases} \]
The function requires that \(n \geq 2\), where each sub-function looks like the following.
\[ l_i(t) = \begin{bmatrix} a_{i,x} \\ a_{i,y} \\ a_{i,z} \\ \end{bmatrix}t + \begin{bmatrix} b_{i,x} \\ b_{i,y} \\ b_{i,z} \\ \end{bmatrix} \]
I decided to make one type ppoly_t
which covers all three types of splines. The struct has the array of coefficients c
whichs structure depends on what type of spline it is. The array u
is simply the parameter values and n
is the length of the arrays.
For each of the types of splines, there is an initialization function which returns the corresponding ppoly_t
;
ppoly_t eval_ppoly_init(vec3_t* mem0, double* mem1, int n) {
ppoly_t ppoly;
ppoly.c = mem0;
ppoly.u = mem1;
ppoly.n = n;
return ppoly;
}
The initialization functions do not call malloc
or anything that would require a syscall. Therefore there are macros that define how much memory each array requires. In the case of a linear spline there is only one.
For the coefficient array. The parameter array is always just an array of doubles with length n
.
Interpolation
The cofficients \(\vec{a_i}\) and \(\vec{b_i}\) are defined by solving the equation system that follows from the positional constraints for each sub-function.
\[ \begin{aligned} l_i(t_i) &= \vec{p_i} \\ l_i(t_{i+1}) &= \vec{p_{i+1}} \end{aligned} \quad i = 0, \space ... ,\space n-2 \]
Since the equations are not dependent on eachother, the coefficients can be calculated directly by.
\[ \begin{aligned} \vec{a_{i}} &= \frac{\vec{p_{i+1}} - \vec{p_{i}}}{t_{i+1}-t_i} \\ \vec{b_i} &= \vec{p_i} - \vec{a_i}t_i \end{aligned} \quad i = 0, \space ... \space, n-2 \]
int intrpl_ppoly1(vec3_t* p, int n, ppoly_t* ppoly) {
vec3_t* c;
double* u;
int i;
if(n < 2) { return 0; }
c = ppoly->c;
u = ppoly->u;
intrpl_parameterize(p, n, u);
for(i = 0; i < n-1; i++) {
c[2*i] = vecdiv(vecsub(p[i+1], p[i]), u[i+1]-u[i]);
c[2*i+1] = vecsub(p[i], vecmul(c[2*i], u[i]));
}
ppoly->n = n;
return 1;
}
Evaluation
The evaluation function eval_ppoly
takes in the function f
which in this case is a a ppoly_t
initialized as a linear spline. And of course the parameter t
.
eval_t eval_ppoly1(void* f_, double t) {
ppoly_t* f = (ppoly_t*)f_;
int k = eval_findinterval(f->u, t, f->n);
return __eval_ppoly1(f, k, t);
}
This is a wrapper function for further generalization which will be required later on. The function first calculates which index in the cofficient array to use k
via the eval_findinterval
function.
int eval_findinterval(double* u, double t, int n) {
int lo, hi, mi;
if(t >= u[n-1]) { return n - 2; }
if(t <= u[0]) { return 0; }
lo = 0;
hi = n-1;
mi = (lo + hi) >> 1;
while(t < u[mi] || t >= u[mi+1]) {
if(t < u[mi]) { hi = mi; }
else { lo = mi; }
mi = (lo + hi) >> 1;
}
return mi;
}
This function is essentially just binary search on the array u
with length n
searching for parameter t
. Binary search is possible since the parameter array is strictly increasing therefore ordered.
Once the index k
has been found, the eval_ppoly
returns the return value of the actual evaluation function __eval_poly
which requires the function f
index k
and parameter t
.
eval_t __eval_ppoly1(void* f_, int k, double t) {
vec3_t* c = ((ppoly_t*)f_)->c;
eval_t e;
int aidx = (k << 1);
int bidx = aidx + 1;
e.p.x = c[aidx].x*t + c[bidx].x;
e.p.y = c[aidx].y*t + c[bidx].y;
e.p.z = c[aidx].z*t + c[bidx].z;
e.v.x = c[aidx].x;
e.v.y = c[aidx].y;
e.v.z = c[aidx].z;
e.a = veczeros();
e.m = vecmag(e.v);
return e;
}
The return value of eval_ppoly1
and __eval_ppoly1
is not just the \((x, y, z)\) values but the first and second derivative as well. In addition to that, the magnitude of the first derivative.
These values will be used by other algorithms which I will cover in later posts.
However, there is a __eval_ppoly_fast
function.
vec3_t __eval_ppoly1_fast(void* f_, int k, double t) {
vec3_t* c = ((ppoly_t*)f_)->c;
vec3_t e;
int aidx = (k << 1);
int bidx = aidx + 1;
e.x = c[aidx].x*t + c[bidx].x;
e.y = c[aidx].y*t + c[bidx].y;
e.z = c[aidx].z*t + c[bidx].z;
return e;
}
Designed to execute as fast as possible, when only the position is required. The usecase for this is on the embedded side of things.
Cubic Spline
The cubic spline interpolation function looks like the following.
\[ C(t) = \begin{cases} c_0(t) & t \in [t_0, t_1] \\ \vdots & \vdots \\ c_{n-2}(t) & t \in (t_{n-2}, t_{n-1}] \end{cases} \]
The function requires that \(n \geq 2\), where each sub-function looks like the following.
\[ c_i(t) = \begin{bmatrix} a_{i,x} \\ a_{i,y} \\ a_{i,z} \\ \end{bmatrix}t^3 + \begin{bmatrix} b_{i,x} \\ b_{i,y} \\ b_{i,z} \\ \end{bmatrix}t^2 + \begin{bmatrix} c_{i,x} \\ c_{i,y} \\ c_{i,z} \\ \end{bmatrix}t + \begin{bmatrix} d_{i,x} \\ d_{i,y} \\ d_{i,z} \\ \end{bmatrix} \]
Interpolation
The coefficients \(\vec{a_i}\), \(\vec{b_i}\), \(\vec{c_i}\) and \(\vec{d_i}\) are calculated by solving the equation system that follows from the positional, first and second derivative equivalence constraints.
\[ \begin{aligned} c_i(t_i) &= \vec{p_i} \\ c_i(t_{i+1}) &= \vec{p_{i+1}} \end{aligned} \quad i = 0, \space ... \space n-2 \]
\[ \begin{aligned} c'_i(t_{i+1}) &= c'_{i+1}(t_{i+1}) \\ c''_i(t_{i+1}) &= c''_{i+1}(t_{i+1}) \end{aligned} \quad i = 0, \space ... \space n-3 \]
In addition to the positional, first and second derivative constraints, two more equations are required to form a linear system of equations. In other words, the spline still has some degrees of freedom after the previous constraints. What you wish to further constrain with is an arbitrary choice, however usually, you set the second derivative of the first and last sub-functions to zero in order for the spline to be considered a “natural spline”.
\[ \begin{aligned} c''_0(t_0) &= \vec{0} \\ c''_{n-2}(t_{n-1}) &= \vec{0} \end{aligned} \]
The constraints for interpolating a cubic spline are dependent on eachother since the derivatives of the sub-functions are the same where they connect. Therefore, one must solve a linear system of equations.
Since the splines will interpolate through thee-dimensional points, the equation system needs to be solved for three different right hand sides. If one were to use Gauss-Jordan elimination then three computations of complexity \(\mathcal{O}(n^3)\) would be required. However, with LU decomposition, one decomposition step of complexity \(\mathcal{O}(n^3)\) followed by three \(\mathcal{O}(n^2)\) for solving each right hand side would be required. Essentially, LU decomposition is faster when you solve the same equation system for multiple right hand sides.
The algorithm for LU decomposition is a modified version of the algorithm found in the article “An efficient implementation of LU decomposition in C”, the article can be found at ScienceDirect.
Firstly, a structure containing the context for the decomposition is required along side a helper macro APPROACHES_ZERO
.
The struct has size n
of the matrix, pivot diagonal p
of length n
, equation system matrix m
of size n*n
and right hand side b
of length n
.
#define APPROACHES_ZERO(x) (fabs(x) < 1e-6)
typedef struct lu_context {
int n, *p;
double *m, *b;
} luctx_t;
The function mat_lud
decomposes matrix m
and saves the decomposed matrix back to m
.
int mat_lud(luctx_t* ctx) {
int i, j, k, n, n_minus_1, *pivot;
double dtmp1, *dptr1, *dptr2, *matrix;
pivot = ctx->p;
matrix = ctx->m;
n = ctx->n;
n_minus_1 = n-1;
for(i = 0; i < n_minus_1; i++) {
*(pivot + i) = i;
}
*(pivot + n_minus_1) = 1;
for(i = 0; i < n_minus_1; i++) {
k = i;
dptr1 = dptr2 = matrix + i*(n+1);
for(j = i+1; j < n; j++) {
dptr2 += n;
if(fabs(*dptr2) > fabs(*dptr1)) {
dptr1 = dptr2;
k = j;
}
}
if(APPROACHES_ZERO(*dptr1)) { return 0; }
if(k != i) {
*(pivot + i) = k;
*(pivot + n_minus_1) = -*(pivot + n_minus_1);
dptr1 = matrix + i*n;
dptr2 = matrix + k*n;
for(j = 0; j < n; j++, dptr1++, dptr2++) {
dtmp1 = *dptr1;
*dptr1 = *dptr2;
*dptr2 = dtmp1;
}
}
for(j = i+1; j < n; j++) {
dtmp1 = *(matrix + j*n + i) / *(matrix + i*n + i);
dptr1 = matrix + j*n + i + 1;
dptr2 = matrix + i*n + i + 1;
for(k = i+1; k < n; k++, dptr1++, dptr2++) {
*dptr1 -= dtmp1 * *dptr2;
}
*(matrix + j*n + i) = dtmp1;
}
}
return 1;
}
Once the matrix has been decomposed, the function mat_lusolve
can be used by setting the b
right hand side in the context then calling the function to get the answers saved back into b
.
void mat_lusolve(luctx_t* ctx) {
int i, j, n, n_minus_1, *pivot;
double dtmp1, *dptr1, *matrix, *b;
pivot = ctx->p;
matrix = ctx->m;
b = ctx->b;
n = ctx->n;
n_minus_1 = n-1;
for(i = 0; i < n_minus_1; i++) {
j = *(pivot + i);
if(j != i) {
dtmp1 = *(b+i);
*(b+i) = *(b+j);
*(b+j) = dtmp1;
}
}
for(i = 0; i < n; i++) {
dtmp1 = *(b+i);
dptr1 = matrix + i*n;
for(j = 0; j < i; j++, dptr1++) {
dtmp1 -= *dptr1 * *(b+j);
}
*(b+i) = dtmp1;
}
for(i = n_minus_1; i >= 0; i--) {
dtmp1 = *(b+i);
dptr1 = matrix + i*n + n_minus_1;
for(j = n_minus_1; j > i; j--) {
dtmp1 -= *dptr1 * *(b+j);
dptr1--;
}
*(b+i) = dtmp1 / *dptr1;
}
return;
}
The following macros define the sizes for the coefficient array and the arrays in luctx_t
.
#define EVAL_PPOLY3_MEMSIZE(n) (4*n)
#define EVAL_PPOLY3_EQSYS_MEMSIZE(n) (4*(n-1)*4*(n-1))
#define EVAL_PPOLY3_PIVOT_MEMSIZE(n) (4*(n-1))
#define EVAL_PPOLY3_B_MEMSIZE(n) (4*(n-1))
These are used to allocate the required memory then initialize ppoly_t
and luctx_t
. The initialization for ppoly_t
is the same as for the linear spline, however luctx_t
has its own initialization function eval_ppoly_luctx
.
luctx_t eval_ppoly_luctx_init(int* pivotmem, double* matrixmem, double* bmem) {
luctx_t ctx;
ctx.n = 0;
ctx.p = pivotmem;
ctx.m = matrixmem;
ctx.b = bmem;
return ctx;
}
Once the structs are initialized, the function intrpl_ppoly3
is used to interpolate though the points p
of length n
. Optionally the initial and final velocity v0
and v1
can be set. For the spline to be “natural” these parameters are just set to NULL
. The function also requires the decomposition context ctx
and saves the spline to ppoly
.
int intrpl_ppoly3(vec3_t* p, int n, vec3_t* v0, vec3_t* v1, luctx_t* ctx, ppoly_t* ppoly) {
vec3_t* c;
double* u;
int i, component, sz;
if(n < 2) { return 0; }
c = ppoly->c;
u = ppoly->u;
intrpl_parameterize(p, n, u);
sz = intrpl_spline_eqsys(ctx->m, u, 3, n);
ctx->n = sz;
if(v0 == NULL) {
ctx->m[sz*(sz-2) + 0] = 6*u[0];
ctx->m[sz*(sz-2) + 1] = 2;
} else {
ctx->m[sz*(sz-2) + 0] = 3*u[0]*u[0];
ctx->m[sz*(sz-2) + 1] = 2*u[0];
ctx->m[sz*(sz-2) + 2] = 1;
}
if(v1 == NULL) {
ctx->m[sz*(sz-1) + sz-4] = 6*u[n-1];
ctx->m[sz*(sz-1) + sz-3] = 2;
} else {
ctx->m[sz*(sz-1) + sz-4] = 3*u[n-1]*u[n-1];
ctx->m[sz*(sz-1) + sz-3] = 2*u[n-1];
ctx->m[sz*(sz-1) + sz-2] = 1;
}
if(!mat_lud(ctx)) { return 0; }
for(component = 0; component < 3; component++) {
for(i = 0; i < sz; i++) { ctx->b[i] = 0.0; }
for(i = 0; i < n-1; i++) {
ctx->b[2*i + 0] = *VEC3GET(p+i+0, component);
ctx->b[2*i + 1] = *VEC3GET(p+i+1, component);
}
if(v0 != NULL) { ctx->b[sz-2] = *VEC3GET(v0, component); }
if(v1 != NULL) { ctx->b[sz-1] = *VEC3GET(v1, component); }
mat_lusolve(ctx);
for(i = 0; i < sz; i++) {
*VEC3GET(c+i, component) = ctx->b[i];
}
}
return 1;
}
The macro VEC3GET
gets the \((x, y, z)\) components in a vec3_t
based on index.
extern const int __vec3_member_offsets[3];
const int __vec3_member_offsets[] = {offsetof(struct vec3, x), offsetof(struct vec3, y), offsetof(struct vec3, z)};
#define VEC3GET(v, i) ((double*)((uint8_t*)(v) + __vec3_member_offsets[i]))
The intrpl_ppoly3
function depends on yet another function intrpl_spline_eqsys
which generates the equation system to be solved. This function needs the parameter array u
, degree d
, number of points n
and saves the equation system to matrix matrix
. The return value is the size of the matrix.
int intrpl_spline_eqsys(double* u, int d, int n, double* matrix) {
int uk, sz, k, i, j, z, q, derivs;
int deriv, coeff, lhs0, lhs1, lhs, rhs;
double t, e;
uk = d + 1;
sz = uk*(n-1);
k = 0;
e = 1;
for(i = 0; i < sz*sz; i++) {
matrix[i] = 0.0;
}
for(i = 0; i < n-1; i++) {
t = u[i];
e = 1;
lhs0 = 2*i*sz;
lhs1 = (2*i+1)*sz;
for(j = uk-1; j >= 0; j--) {
matrix[lhs0 + k+j] = e;
e *= t;
}
t = u[i+1];
e = 1;
for(j = uk-1; j >= 0; j--) {
matrix[lhs1 + k+j] = e;
e *= t;
}
k += uk;
}
derivs = d - 1;
k = 0;
for(i = 0; i < n-2; i++) {
t = u[i+1];
e = 1;
for(deriv = 0; deriv < derivs; deriv++) {
z = deriv+1;
for(j = uk-2-deriv; j >= 0; j--) {
coeff = z;
for(q = 0; q < deriv; q++) {
coeff *= z - q - 1;
}
lhs = (2*(n-1)+derivs*i+deriv)*sz;
rhs = lhs + uk;
matrix[lhs + k+j] = coeff*e;
matrix[rhs + k+j] = -coeff*e;
e *= t;
z++;
}
}
k += uk;
}
return sz;
}
Evaluation
The family of functions for evaluation is pretty much the same as for the linear spline, just with more coefficients.
eval_t __eval_ppoly3(void* f_, int k, double t) {
ppoly_t* f = (ppoly_t*)f_;
vec3_t* c = f->c;
eval_t e;
int aidx = (k << 2);
int bidx = aidx + 1;
int cidx = aidx + 2;
int didx = aidx + 3;
double t2 = t*t;
double t3 = t2*t;
e.p.x = c[aidx].x*t3 + c[bidx].x*t2 + c[cidx].x*t + c[didx].x;
e.p.y = c[aidx].y*t3 + c[bidx].y*t2 + c[cidx].y*t + c[didx].y;
e.p.z = c[aidx].z*t3 + c[bidx].z*t2 + c[cidx].z*t + c[didx].z;
e.v.x = 3.0*c[aidx].x*t2 + 2.0*c[bidx].x*t + c[cidx].x;
e.v.y = 3.0*c[aidx].y*t2 + 2.0*c[bidx].y*t + c[cidx].y;
e.v.z = 3.0*c[aidx].z*t2 + 2.0*c[bidx].z*t + c[cidx].z;
e.a.x = 6.0*c[aidx].x*t + 2.0*c[bidx].x;
e.a.y = 6.0*c[aidx].y*t + 2.0*c[bidx].y;
e.a.z = 6.0*c[aidx].z*t + 2.0*c[bidx].z;
e.m = vecmag(e.v);
return e;
}
vec3_t __eval_ppoly3_fast(void* f_, int k, double t) {
ppoly_t* f = (ppoly_t*)f_;
vec3_t* c = f->c;
vec3_t e;
int aidx = (k << 2);
int bidx = aidx + 1;
int cidx = aidx + 2;
int didx = aidx + 3;
double t2 = t*t;
double t3 = t2*t;
e.x = c[aidx].x*t3 + c[bidx].x*t2 + c[cidx].x*t + c[didx].x;
e.y = c[aidx].y*t3 + c[bidx].y*t2 + c[cidx].y*t + c[didx].y;
e.z = c[aidx].z*t3 + c[bidx].z*t2 + c[cidx].z*t + c[didx].z;
return e;
}
eval_t eval_ppoly5(void* f_, double t) {
ppoly_t* f = (ppoly_t*)f_;
int k = eval_findinterval(f->u, t, f->n);
return __eval_ppoly5(f, k, t);
}
Quintic Spline
The quintic spline interpolation function looks like the following.
\[ Q(t) = \begin{cases} q_0(t) & t \in [t_0, t_1] \\ \vdots & \vdots \\ q_{n-2}(t) & t \in (t_{n-2}, t_{n-1}] \end{cases} \]
The function requires that \(n \geq 2\), where each sub-function looks like the following.
\[ q_i(t) = \begin{bmatrix} a_{i,x} \\ a_{i,y} \\ a_{i,z} \\ \end{bmatrix}t^5 + \begin{bmatrix} b_{i,x} \\ b_{i,y} \\ b_{i,z} \\ \end{bmatrix}t^4 + \begin{bmatrix} c_{i,x} \\ c_{i,y} \\ c_{i,z} \\ \end{bmatrix}t^3 + \begin{bmatrix} d_{i,x} \\ d_{i,y} \\ d_{i,z} \\ \end{bmatrix}t^2 + \begin{bmatrix} e_{i,x} \\ e_{i,y} \\ e_{i,z} \\ \end{bmatrix}t + \begin{bmatrix} f_{i,x} \\ f_{i,y} \\ f_{i,z} \\ \end{bmatrix} \]
Interpolation
The cofficients \(\vec{a_i}\), \(\vec{b_i}\), \(\vec{c_i}\), \(\vec{d_i}\), \(\vec{e_i}\) and \(\vec{f_i}\) are calculated by solving the equation system that follows from the positional, first, second and third derivative equivalence constraints.
\[ \begin{aligned} q_i(t_i) &= \vec{p_i} \\ q_i(t_{i+1}) &= \vec{p_{i+1}} \end{aligned} \quad i = 0, \space ... \space , n-2 \]
\[ \begin{aligned} q'_i(t_{i+1}) &= q'_{i+1}(t_{i+1}) \\ q''_i(t_{i+1}) &= q''_{i+1}(t_{i+1}) \\ q'''_i(t_{i+1}) &= q''_{i+1}(t_{i+1}) \\ q''''_i(t_{i+1}) &= q''''_{i+1}(t_{i+1}) \\ \end{aligned} \quad i = 0, \space ... \space n-3 \]
As with cubic splines, a couple more equations are required to fully constrain everything such that a linear system of equation is formed. In the case of a quintic spline, four more equations are required. Unless something else, the second and third derivatives are set to zero.
\[ \begin{aligned} q''_0(t_0) &= \vec{0} \\ q'''_0(t_0) &= \vec{0} \\ q''_{n-2}(t_{n-1}) &= \vec{0} \\ q'''_{n-2}(t_{n-1}) &= \vec{0} \end{aligned} \]
In terms of code, interpolating a quintic spline is pretty much identical to the cubic spline. Just with the option to set the initial and final acceleration a0
and a1
due to it having more degrees of freedom. The lengths of the arrays for the coefficient array and luctx_t
are also different.
#define EVAL_PPOLY5_MEMSIZE(n) (6*n)
#define EVAL_PPOLY5_EQSYS_MEMSIZE(n) (6*(n-1)*6*(n-1))
#define EVAL_PPOLY5_PIVOT_MEMSIZE(n) (6*(n-1))
#define EVAL_PPOLY5_B_MEMSIZE(n) (6*(n-1))
int intrpl_ppoly5(vec3_t* p, int n, vec3_t* v0, vec3_t* v1, vec3_t* a0, vec3_t* a1, luctx_t* ctx, ppoly_t* ppoly) {
vec3_t* c;
double* u;
int i, sz, component;
if(n < 2) { return 0; }
c = ppoly->c;
u = ppoly->u;
intrpl_parameterize(p, n, u);
sz = intrpl_spline_eqsys(u, 5, n, ctx->m);
ctx->n = sz;
if(v0 == NULL) {
ctx->m[sz*(sz-4) + 0] = 60*u[0]*u[0];
ctx->m[sz*(sz-4) + 1] = 24*u[0];
ctx->m[sz*(sz-4) + 2] = 6;
} else {
ctx->m[sz*(sz-4) + 0] = 5*u[0]*u[0]*u[0]*u[0];
ctx->m[sz*(sz-4) + 1] = 4*u[0]*u[0]*u[0];
ctx->m[sz*(sz-4) + 2] = 3*u[0]*u[0];
ctx->m[sz*(sz-4) + 3] = 2*u[0];
ctx->m[sz*(sz-4) + 4] = 1;
}
ctx->m[sz*(sz-3) + 0] = 20*u[0]*u[0]*u[0];
ctx->m[sz*(sz-3) + 1] = 12*u[0]*u[0];
ctx->m[sz*(sz-3) + 2] = 6*u[0];
ctx->m[sz*(sz-3) + 3] = 2;
if(v1 == NULL) {
ctx->m[sz*(sz-2) + sz-6] = 60*u[n-1]*u[n-1];
ctx->m[sz*(sz-2) + sz-5] = 24*u[n-1];
ctx->m[sz*(sz-2) + sz-4] = 6;
} else {
ctx->m[sz*(sz-2) + sz-6] = 5*u[n-1]*u[n-1]*u[n-1]*u[n-1];
ctx->m[sz*(sz-2) + sz-5] = 4*u[n-1]*u[n-1]*u[n-1];
ctx->m[sz*(sz-2) + sz-4] = 3*u[n-1]*u[n-1];
ctx->m[sz*(sz-2) + sz-3] = 2*u[n-1];
ctx->m[sz*(sz-2) + sz-2] = 1;
}
ctx->m[sz*(sz-1) + sz-6] = 20*u[n-1]*u[n-1]*u[n-1];
ctx->m[sz*(sz-1) + sz-5] = 12*u[n-1]*u[n-1];
ctx->m[sz*(sz-1) + sz-4] = 6*u[n-1];
ctx->m[sz*(sz-1) + sz-3] = 2;
if(!mat_lud(ctx)) { return 0; }
for(component = 0; component < 3; component++) {
for(i = 0; i < sz; i++) { ctx->b[i] = 0.0; }
for(i = 0; i < n-1; i++) {
ctx->b[2*i + 0] = *VEC3GET(p+i+0, component);
ctx->b[2*i + 1] = *VEC3GET(p+i+1, component);
}
if(v0 != NULL) { ctx->b[sz-4] = *VEC3GET(v0, component); }
if(a0 != NULL) { ctx->b[sz-3] = *VEC3GET(a0, component); }
if(v1 != NULL) { ctx->b[sz-2] = *VEC3GET(v1, component); }
if(a1 != NULL) { ctx->b[sz-1] = *VEC3GET(a1, component); }
mat_lusolve(ctx);
for(i = 0; i < sz; i++) {
*VEC3GET(c+i, component) = ctx->b[i];
}
}
return 1;
}
Evaluation
Once again, the family of functions for evaluation is pretty much the same as for the linear and cubic spline, but just with more coefficients.
eval_t __eval_ppoly5(void* f_, int k, double t) {
ppoly_t* f = (ppoly_t*)f_;
vec3_t* c = f->c;
eval_t e;
int aidx = 6 * k;
int bidx = aidx + 1;
int cidx = aidx + 2;
int didx = aidx + 3;
int eidx = aidx + 4;
int fidx = aidx + 5;
double t2 = t*t;
double t3 = t2*t;
double t4 = t3*t;
double t5 = t4*t;
e.p.x = c[aidx].x*t5 + c[bidx].x*t4 + c[cidx].x*t3 + c[didx].x*t2 + c[eidx].x*t + c[fidx].x;
e.p.y = c[aidx].y*t5 + c[bidx].y*t4 + c[cidx].y*t3 + c[didx].y*t2 + c[eidx].y*t + c[fidx].y;
e.p.z = c[aidx].z*t5 + c[bidx].z*t4 + c[cidx].z*t3 + c[didx].z*t2 + c[eidx].z*t + c[fidx].z;
e.v.x = 5.0*c[aidx].x*t4 + 4.0*c[bidx].x*t3 + 3.0*c[cidx].x*t2 + 2.0*c[didx].x*t + c[eidx].x;
e.v.y = 5.0*c[aidx].y*t4 + 4.0*c[bidx].y*t3 + 3.0*c[cidx].y*t2 + 2.0*c[didx].y*t + c[eidx].y;
e.v.z = 5.0*c[aidx].z*t4 + 4.0*c[bidx].z*t3 + 3.0*c[cidx].z*t2 + 2.0*c[didx].z*t + c[eidx].z;
e.a.x = 20.0*c[aidx].x*t3 + 12.0*c[bidx].x*t2 + 6.0*c[cidx].x*t + 2.0*c[didx].x;
e.a.y = 20.0*c[aidx].y*t3 + 12.0*c[bidx].y*t2 + 6.0*c[cidx].y*t + 2.0*c[didx].y;
e.a.z = 20.0*c[aidx].z*t3 + 12.0*c[bidx].z*t2 + 6.0*c[cidx].z*t + 2.0*c[didx].z;
e.m = vecmag(e.v);
return e;
}
vec3_t __eval_ppoly5_fast(void* f_, int k, double t) {
ppoly_t* f = (ppoly_t*)f_;
vec3_t* c = f->c;
vec3_t e;
int aidx = 6 * k;
int bidx = aidx + 1;
int cidx = aidx + 2;
int didx = aidx + 3;
int eidx = aidx + 4;
int fidx = aidx + 5;
double t2 = t*t;
double t3 = t2*t;
double t4 = t3*t;
double t5 = t4*t;
e.x = c[aidx].x*t5 + c[bidx].x*t4 + c[cidx].x*t3 + c[didx].x*t2 + c[eidx].x*t + c[fidx].x;
e.y = c[aidx].y*t5 + c[bidx].y*t4 + c[cidx].y*t3 + c[didx].y*t2 + c[eidx].y*t + c[fidx].y;
e.z = c[aidx].z*t5 + c[bidx].z*t4 + c[cidx].z*t3 + c[didx].z*t2 + c[eidx].z*t + c[fidx].z;
return e;
}