You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
393 lines
14 KiB
393 lines
14 KiB
// David Eberly, Geometric Tools, Redmond WA 98052
|
|
// Copyright (c) 1998-2021
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// https://www.boost.org/LICENSE_1_0.txt
|
|
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
|
|
// Version: 4.0.2020.11.16
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/GMatrix.h>
|
|
#include <Mathematics/ParametricCurve.h>
|
|
|
|
namespace gte
|
|
{
|
|
template <int N, typename Real>
|
|
class NaturalSplineCurve : public ParametricCurve<N, Real>
|
|
{
|
|
public:
|
|
// Construction and destruction. The object copies the input arrays.
|
|
// The number of points M must be at least 2. The first constructor
|
|
// is for a spline with second derivatives zero at the endpoints
|
|
// (isFree = true) or a spline that is closed (isFree = false). The
|
|
// second constructor is for clamped splines, where you specify the
|
|
// first derivatives at the endpoints. Usually, derivative0 =
|
|
// points[1] - points[0] at the first point and derivative1 =
|
|
// points[M-1] - points[M-2]. To validate construction, create an
|
|
// object as shown:
|
|
// NaturalSplineCurve<N, Real> curve(parameters);
|
|
// if (!curve) { <constructor failed, handle accordingly>; }
|
|
NaturalSplineCurve(bool isFree, int numPoints,
|
|
Vector<N, Real> const* points, Real const* times)
|
|
:
|
|
ParametricCurve<N, Real>(numPoints - 1, times)
|
|
{
|
|
if (numPoints < 2 || !points)
|
|
{
|
|
LogError("Invalid input.");
|
|
return;
|
|
}
|
|
|
|
auto const numSegments = numPoints - 1;
|
|
mCoefficients.resize(4 * numPoints + 1);
|
|
mA = &mCoefficients[0];
|
|
mB = mA + numPoints;
|
|
mC = mB + numSegments;
|
|
mD = mC + numSegments + 1;
|
|
for (int i = 0; i < numPoints; ++i)
|
|
{
|
|
mA[i] = points[i];
|
|
}
|
|
|
|
if (isFree)
|
|
{
|
|
CreateFree();
|
|
}
|
|
else
|
|
{
|
|
CreateClosed();
|
|
}
|
|
|
|
this->mConstructed = true;
|
|
}
|
|
|
|
NaturalSplineCurve(int numPoints, Vector<N, Real> const* points,
|
|
Real const* times, Vector<N, Real> const& derivative0,
|
|
Vector<N, Real> const& derivative1)
|
|
:
|
|
ParametricCurve<N, Real>(numPoints - 1, times)
|
|
{
|
|
if (numPoints < 2 || !points)
|
|
{
|
|
LogError("Invalid input.");
|
|
return;
|
|
}
|
|
|
|
auto const numSegments = numPoints - 1;
|
|
mCoefficients.resize(numPoints + 3 * numSegments + 1);
|
|
mA = &mCoefficients[0];
|
|
mB = mA + numPoints;
|
|
mC = mB + numSegments;
|
|
mD = mC + numSegments + 1;
|
|
for (int i = 0; i < numPoints; ++i)
|
|
{
|
|
mA[i] = points[i];
|
|
}
|
|
|
|
CreateClamped(derivative0, derivative1);
|
|
this->mConstructed = true;
|
|
}
|
|
|
|
virtual ~NaturalSplineCurve() = default;
|
|
|
|
// Member access.
|
|
inline int GetNumPoints() const
|
|
{
|
|
return static_cast<int>((mCoefficients.size() - 1) / 4);
|
|
}
|
|
|
|
inline Vector<N, Real> const* GetPoints() const
|
|
{
|
|
return &mA[0];
|
|
}
|
|
|
|
// Evaluation of the curve. The function supports derivative
|
|
// calculation through order 3; that is, order <= 3 is required. If
|
|
// you want/ only the position, pass in order of 0. If you want the
|
|
// position and first derivative, pass in order of 1, and so on. The
|
|
// output array 'jet' must have enough storage to support the maximum
|
|
// order. The values are ordered as: position, first derivative,
|
|
// second derivative, third derivative.
|
|
virtual void Evaluate(Real t, unsigned int order, Vector<N, Real>* jet) const override
|
|
{
|
|
unsigned int const supOrder = ParametricCurve<N, Real>::SUP_ORDER;
|
|
if (!this->mConstructed || order >= supOrder)
|
|
{
|
|
// Return a zero-valued jet for invalid state.
|
|
for (unsigned int i = 0; i < supOrder; ++i)
|
|
{
|
|
jet[i].MakeZero();
|
|
}
|
|
return;
|
|
}
|
|
|
|
int key = 0;
|
|
Real dt = (Real)0;
|
|
GetKeyInfo(t, key, dt);
|
|
|
|
// Compute position.
|
|
jet[0] = mA[key] + dt * (mB[key] + dt * (mC[key] + dt * mD[key]));
|
|
if (order >= 1)
|
|
{
|
|
// Compute first derivative.
|
|
jet[1] = mB[key] + dt * ((Real)2 * mC[key] + (Real)3 * dt * mD[key]);
|
|
if (order >= 2)
|
|
{
|
|
// Compute second derivative.
|
|
jet[2] = (Real)2 * mC[key] + (Real)6 * dt * mD[key];
|
|
if (order == 3)
|
|
{
|
|
jet[3] = (Real)6 * mD[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected:
|
|
// Support for construction.
|
|
void CreateFree()
|
|
{
|
|
int numSegments = GetNumPoints() - 1;
|
|
WorkingData wd(numSegments);
|
|
for (int i = 0; i < numSegments; ++i)
|
|
{
|
|
wd.dt[i] = this->mTime[i + 1] - this->mTime[i];
|
|
}
|
|
|
|
std::vector<Real> d2t(numSegments);
|
|
for (int i = 1; i < numSegments; ++i)
|
|
{
|
|
wd.d2t[i] = this->mTime[i + 1] - this->mTime[i - 1];
|
|
}
|
|
|
|
std::vector<Vector<N, Real>> alpha(numSegments);
|
|
for (int i = 1; i < numSegments; ++i)
|
|
{
|
|
Vector<N, Real> numer = (Real)3 * (wd.dt[i - 1] * mA[i + 1] - wd.d2t[i] * mA[i] + wd.dt[i] * mA[i - 1]);
|
|
Real invDenom = (Real)1 / (wd.dt[i - 1] * wd.dt[i]);
|
|
wd.alpha[i] = invDenom * numer;
|
|
}
|
|
|
|
std::vector<Real> ell(numSegments + 1);
|
|
std::vector<Real> mu(numSegments);
|
|
std::vector<Vector<N, Real>> z(numSegments + 1);
|
|
Real inv;
|
|
|
|
wd.ell[0] = (Real)1;
|
|
wd.mu[0] = (Real)0;
|
|
wd.z[0].MakeZero();
|
|
for (int i = 1; i < numSegments; ++i)
|
|
{
|
|
wd.ell[i] = (Real)2 * wd.d2t[i] - wd.dt[i - 1] * wd.mu[i - 1];
|
|
inv = (Real)1 / wd.ell[i];
|
|
wd.mu[i] = inv * wd.dt[i];
|
|
wd.z[i] = inv * (wd.alpha[i] - wd.dt[i - 1] * wd.z[i - 1]);
|
|
}
|
|
wd.ell[numSegments] = (Real)1;
|
|
wd.z[numSegments].MakeZero();
|
|
|
|
Real const oneThird = (Real)1 / (Real)3;
|
|
mC[numSegments].MakeZero();
|
|
for (int i = numSegments - 1; i >= 0; --i)
|
|
{
|
|
mC[i] = wd.z[i] - wd.mu[i] * mC[i + 1];
|
|
inv = (Real)1 / wd.dt[i];
|
|
mB[i] = inv * (mA[i + 1] - mA[i]) - oneThird * wd.dt[i] * (mC[i + 1] + (Real)2 * mC[i]);
|
|
mD[i] = oneThird * inv * (mC[i + 1] - mC[i]);
|
|
}
|
|
}
|
|
|
|
void CreateClosed()
|
|
{
|
|
// TODO: A general linear system solver is used here. The matrix
|
|
// corresponding to this case is actually "cyclic banded", so a
|
|
// faster linear solver can be used. The current linear system
|
|
// code does not have such a solver.
|
|
|
|
int numSegments = GetNumPoints() - 1;
|
|
std::vector<Real> dt(numSegments);
|
|
for (int i = 0; i < numSegments; ++i)
|
|
{
|
|
dt[i] = this->mTime[i + 1] - this->mTime[i];
|
|
}
|
|
|
|
// Construct matrix of system.
|
|
GMatrix<Real> mat(numSegments + 1, numSegments + 1);
|
|
mat(0, 0) = (Real)1;
|
|
mat(0, numSegments) = (Real)-1;
|
|
for (int i = 1; i <= numSegments - 1; ++i)
|
|
{
|
|
mat(i, i - 1) = dt[i - 1];
|
|
mat(i, i) = (Real)2 * (dt[i - 1] + dt[i]);
|
|
mat(i, i + 1) = dt[i];
|
|
}
|
|
mat(numSegments, numSegments - 1) = dt[numSegments - 1];
|
|
mat(numSegments, 0) = (Real)2 * (dt[numSegments - 1] + dt[0]);
|
|
mat(numSegments, 1) = dt[0];
|
|
|
|
// Construct right-hand side of system.
|
|
mC[0].MakeZero();
|
|
Real inv0, inv1;
|
|
for (int i = 1; i <= numSegments - 1; ++i)
|
|
{
|
|
inv0 = (Real)1 / dt[i];
|
|
inv1 = (Real)1 / dt[i - 1];
|
|
mC[i] = (Real)3 * (inv0 * (mA[i + 1] - mA[i]) -
|
|
inv1 * (mA[i] - mA[i - 1]));
|
|
}
|
|
inv0 = (Real)1 / dt[0];
|
|
inv1 = (Real)1 / dt[numSegments - 1];
|
|
mC[numSegments] = (Real)3 * (inv0 * (mA[1] - mA[0]) -
|
|
inv1 * (mA[0] - mA[numSegments - 1]));
|
|
|
|
// Solve the linear systems.
|
|
GMatrix<Real> invMat = Inverse(mat);
|
|
GVector<Real> input(numSegments + 1);
|
|
GVector<Real> output(numSegments + 1);
|
|
for (int j = 0; j < N; ++j)
|
|
{
|
|
for (int i = 0; i <= numSegments; ++i)
|
|
{
|
|
input[i] = mC[i][j];
|
|
}
|
|
output = invMat * input;
|
|
for (int i = 0; i <= numSegments; ++i)
|
|
{
|
|
mC[i][j] = output[i];
|
|
}
|
|
}
|
|
|
|
Real const oneThird = (Real)1 / (Real)3;
|
|
for (int i = 0; i < numSegments; ++i)
|
|
{
|
|
inv0 = (Real)1 / dt[i];
|
|
mB[i] = inv0 * (mA[i + 1] - mA[i]) - oneThird * (mC[i + 1] + (Real)2 * mC[i]) * dt[i];
|
|
mD[i] = oneThird * inv0 * (mC[i + 1] - mC[i]);
|
|
}
|
|
}
|
|
|
|
void CreateClamped(Vector<N, Real> const& derivative0, Vector<N, Real> const& derivative1)
|
|
{
|
|
int numSegments = GetNumPoints() - 1;
|
|
std::vector<Real> dt(numSegments);
|
|
for (int i = 0; i < numSegments; ++i)
|
|
{
|
|
dt[i] = this->mTime[i + 1] - this->mTime[i];
|
|
}
|
|
|
|
std::vector<Real> d2t(numSegments);
|
|
for (int i = 1; i < numSegments; ++i)
|
|
{
|
|
d2t[i] = this->mTime[i + 1] - this->mTime[i - 1];
|
|
}
|
|
|
|
std::vector<Vector<N, Real>> alpha(numSegments + 1);
|
|
Real inv = (Real)1 / dt[0];
|
|
alpha[0] = (Real)3 * (inv * (mA[1] - mA[0]) - derivative0);
|
|
inv = (Real)1 / dt[numSegments - 1];
|
|
alpha[numSegments] = (Real)3 * (derivative1 -
|
|
inv * (mA[numSegments] - mA[numSegments - 1]));
|
|
for (int i = 1; i < numSegments; ++i)
|
|
{
|
|
Vector<N, Real> numer = (Real)3 * (dt[i - 1] * mA[i + 1] -
|
|
d2t[i] * mA[i] + dt[i] * mA[i - 1]);
|
|
Real invDenom = (Real)1 / (dt[i - 1] * dt[i]);
|
|
alpha[i] = invDenom * numer;
|
|
}
|
|
|
|
std::vector<Real> ell(numSegments + 1);
|
|
std::vector<Real> mu(numSegments);
|
|
std::vector<Vector<N, Real>> z(numSegments + 1);
|
|
|
|
ell[0] = (Real)2 * dt[0];
|
|
mu[0] = (Real)0.5;
|
|
inv = (Real)1 / ell[0];
|
|
z[0] = inv * alpha[0];
|
|
|
|
for (int i = 1; i < numSegments; ++i)
|
|
{
|
|
ell[i] = (Real)2 * d2t[i] - dt[i - 1] * mu[i - 1];
|
|
inv = (Real)1 / ell[i];
|
|
mu[i] = inv * dt[i];
|
|
z[i] = inv * (alpha[i] - dt[i - 1] * z[i - 1]);
|
|
}
|
|
ell[numSegments] = dt[numSegments - 1] * ((Real)2 - mu[numSegments - 1]);
|
|
inv = (Real)1 / ell[numSegments];
|
|
z[numSegments] = inv * (alpha[numSegments] - dt[numSegments - 1] *
|
|
z[numSegments - 1]);
|
|
|
|
Real const oneThird = (Real)1 / (Real)3;
|
|
mC[numSegments] = z[numSegments];
|
|
for (int i = numSegments - 1; i >= 0; --i)
|
|
{
|
|
mC[i] = z[i] - mu[i] * mC[i + 1];
|
|
inv = (Real)1 / dt[i];
|
|
mB[i] = inv * (mA[i + 1] - mA[i]) - oneThird * dt[i] * (mC[i + 1] +
|
|
(Real)2 * mC[i]);
|
|
mD[i] = oneThird * inv * (mC[i + 1] - mC[i]);
|
|
}
|
|
}
|
|
|
|
// Determine the index i for which times[i] <= t < times[i+1].
|
|
void GetKeyInfo(Real t, int& key, Real& dt) const
|
|
{
|
|
int numSegments = GetNumPoints() - 1;
|
|
if (t <= this->mTime[0])
|
|
{
|
|
key = 0;
|
|
dt = (Real)0;
|
|
}
|
|
else if (t >= this->mTime[numSegments])
|
|
{
|
|
key = numSegments - 1;
|
|
dt = this->mTime[numSegments] - this->mTime[numSegments - 1];
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < numSegments; ++i)
|
|
{
|
|
if (t < this->mTime[i + 1])
|
|
{
|
|
key = i;
|
|
dt = t - this->mTime[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Polynomial coefficients. mA are the points (constant coefficients of
|
|
// polynomials. mB are the degree 1 coefficients, mC are the degree 2
|
|
// coefficients, and mD are the degree 3 coefficients.
|
|
Vector<N, Real>* mA;
|
|
Vector<N, Real>* mB;
|
|
Vector<N, Real>* mC;
|
|
Vector<N, Real>* mD;
|
|
|
|
private:
|
|
std::vector<Vector<N, Real>> mCoefficients;
|
|
|
|
struct WorkingData
|
|
{
|
|
WorkingData(int numSegments)
|
|
{
|
|
data.resize(4 * numSegments + 1 + N * (2 * numSegments + 1));
|
|
dt = &data[0];
|
|
d2t = dt + numSegments;
|
|
alpha = reinterpret_cast<Vector<N, Real>*>(d2t + numSegments);
|
|
ell = reinterpret_cast<Real*>(alpha + numSegments);
|
|
mu = ell + numSegments + 1;
|
|
z = reinterpret_cast<Vector<N, Real>*>(mu + numSegments);
|
|
}
|
|
|
|
Real* dt;
|
|
Real* d2t;
|
|
Vector<N, Real>* alpha;
|
|
Real* ell;
|
|
Real* mu;
|
|
Vector<N, Real>* z;
|
|
|
|
std::vector<Real> data;
|
|
};
|
|
};
|
|
}
|
|
|