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.
 
 
 
 
 
 

474 lines
17 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.2019.08.13
#pragma once
#include <Mathematics/Logger.h>
#include <Mathematics/Math.h>
#include <Mathematics/Array2.h>
#include <array>
#include <cstring>
namespace gte
{
template <typename Real>
struct UniqueKnot
{
Real t;
int multiplicity;
};
template <typename Real>
struct BasisFunctionInput
{
// The members are uninitialized.
BasisFunctionInput()
{
}
// Construct an open uniform curve with t in [0,1].
BasisFunctionInput(int inNumControls, int inDegree)
:
numControls(inNumControls),
degree(inDegree),
uniform(true),
periodic(false),
numUniqueKnots(numControls - degree + 1),
uniqueKnots(numUniqueKnots)
{
uniqueKnots.front().t = (Real)0;
uniqueKnots.front().multiplicity = degree + 1;
for (int i = 1; i <= numUniqueKnots - 2; ++i)
{
uniqueKnots[i].t = i / (numUniqueKnots - (Real)1);
uniqueKnots[i].multiplicity = 1;
}
uniqueKnots.back().t = (Real)1;
uniqueKnots.back().multiplicity = degree + 1;
}
int numControls;
int degree;
bool uniform;
bool periodic;
int numUniqueKnots;
std::vector<UniqueKnot<Real>> uniqueKnots;
};
template <typename Real>
class BasisFunction
{
public:
// Let n be the number of control points. Let d be the degree, where
// 1 <= d <= n-1. The number of knots is k = n + d + 1. The knots
// are t[i] for 0 <= i < k and must be nondecreasing, t[i] <= t[i+1],
// but a knot value can be repeated. Let s be the number of distinct
// knots. Let the distinct knots be u[j] for 0 <= j < s, so u[j] <
// u[j+1] for all j. The set of u[j] is called a 'breakpoint
// sequence'. Let m[j] >= 1 be the multiplicity; that is, if t[i] is
// the first occurrence of u[j], then t[i+r] = t[i] for 1 <= r < m[j].
// The multiplicities have the constraints m[0] <= d+1, m[s-1] <= d+1,
// and m[j] <= d for 1 <= j <= s-2. Also, k = sum_{j=0}^{s-1} m[j],
// which says the multiplicities account for all k knots.
//
// Given a knot vector (t[0],...,t[n+d]), the domain of the
// corresponding B-spline curve is the interval [t[d],t[n]].
//
// The corresponding B-spline or NURBS curve is characterized as
// follows. See "Geometric Modeling with Splines: An Introduction" by
// Elaine Cohen, Richard F. Riesenfeld and Gershon Elber, AK Peters,
// 2001, Natick MA. The curve is 'open' when m[0] = m[s-1] = d+1;
// otherwise, it is 'floating'. An open curve is uniform when the
// knots t[d] through t[n] are equally spaced; that is, t[i+1] - t[i]
// are a common value for d <= i <= n-1. By implication, s = n-d+1
// and m[j] = 1 for 1 <= j <= s-2. An open curve that does not
// satisfy these conditions is said to be nonuniform. A floating
// curve is uniform when m[j] = 1 for 0 <= j <= s-1 and t[i+1] - t[i]
// are a common value for 0 <= i <= k-2; otherwise, the floating curve
// is nonuniform.
//
// A special case of a floating curve is a periodic curve. The intent
// is that the curve is closed, so the first and last control points
// should be the same, which ensures C^{0} continuity. Higher-order
// continuity is obtained by repeating more control points. If the
// control points are P[0] through P[n-1], append the points P[0]
// through P[d-1] to ensure C^{d-1} continuity. Additionally, the
// knots must be chosen properly. You may choose t[d] through t[n] as
// you wish. The other knots are defined by
// t[i] - t[i-1] = t[n-d+i] - t[n-d+i-1]
// t[n+i] - t[n+i-1] = t[d+i] - t[d+i-1]
// for 1 <= i <= d.
// Construction and destruction. The determination that the curve is
// open or floating is based on the multiplicities. The 'uniform'
// input is used to avoid misclassifications due to floating-point
// rounding errors. Specifically, the breakpoints might be equally
// spaced (uniform) as real numbers, but the floating-point
// representations can have rounding errors that cause the knot
// differences not to be exactly the same constant. A periodic curve
// can have uniform or nonuniform knots. This object makes copies of
// the input arrays.
BasisFunction()
:
mNumControls(0),
mDegree(0),
mTMin((Real)0),
mTMax((Real)0),
mTLength((Real)0),
mOpen(false),
mUniform(false),
mPeriodic(false)
{
}
BasisFunction(BasisFunctionInput<Real> const& input)
:
mNumControls(0),
mDegree(0),
mTMin((Real)0),
mTMax((Real)0),
mTLength((Real)0),
mOpen(false),
mUniform(false),
mPeriodic(false)
{
Create(input);
}
~BasisFunction()
{
}
// No copying is allowed.
BasisFunction(BasisFunction const&) = delete;
BasisFunction& operator=(BasisFunction const&) = delete;
// Support for explicit creation in classes that have std::array
// members involving BasisFunction. This is a call-once function.
void Create(BasisFunctionInput<Real> const& input)
{
LogAssert(mNumControls == 0 && mDegree == 0, "Object already created.");
LogAssert(input.numControls >= 2, "Invalid number of control points.");
LogAssert(1 <= input.degree && input.degree < input.numControls, "Invalid degree.");
LogAssert(input.numUniqueKnots >= 2, "Invalid number of unique knots.");
mNumControls = (input.periodic ? input.numControls + input.degree : input.numControls);
mDegree = input.degree;
mTMin = (Real)0;
mTMax = (Real)0;
mTLength = (Real)0;
mOpen = false;
mUniform = input.uniform;
mPeriodic = input.periodic;
for (int i = 0; i < 4; ++i)
{
mJet[i] = Array2<Real>();
}
mUniqueKnots.resize(input.numUniqueKnots);
std::copy(input.uniqueKnots.begin(),
input.uniqueKnots.begin() + input.numUniqueKnots,
mUniqueKnots.begin());
Real u = mUniqueKnots.front().t;
for (int i = 1; i < input.numUniqueKnots - 1; ++i)
{
Real uNext = mUniqueKnots[i].t;
LogAssert(u < uNext, "Unique knots are not strictly increasing.");
u = uNext;
}
int mult0 = mUniqueKnots.front().multiplicity;
LogAssert(mult0 >= 1 && mult0 <= mDegree + 1, "Invalid first multiplicity.");
int mult1 = mUniqueKnots.back().multiplicity;
LogAssert(mult1 >= 1 && mult1 <= mDegree + 1, "Invalid last multiplicity.");
for (int i = 1; i <= input.numUniqueKnots - 2; ++i)
{
int mult = mUniqueKnots[i].multiplicity;
LogAssert(mult >= 1 && mult <= mDegree + 1, "Invalid interior multiplicity.");
}
mOpen = (mult0 == mult1 && mult0 == mDegree + 1);
mKnots.resize(mNumControls + mDegree + 1);
mKeys.resize(input.numUniqueKnots);
int sum = 0;
for (int i = 0, j = 0; i < input.numUniqueKnots; ++i)
{
Real tCommon = mUniqueKnots[i].t;
int mult = mUniqueKnots[i].multiplicity;
for (int k = 0; k < mult; ++k, ++j)
{
mKnots[j] = tCommon;
}
mKeys[i].first = tCommon;
mKeys[i].second = sum - 1;
sum += mult;
}
mTMin = mKnots[mDegree];
mTMax = mKnots[mNumControls];
mTLength = mTMax - mTMin;
size_t numRows = mDegree + 1;
size_t numCols = mNumControls + mDegree;
size_t numBytes = numRows * numCols * sizeof(Real);
for (int i = 0; i < 4; ++i)
{
mJet[i] = Array2<Real>(numCols, numRows);
std::memset(mJet[i][0], 0, numBytes);
}
}
// Member access.
inline int GetNumControls() const
{
return mNumControls;
}
inline int GetDegree() const
{
return mDegree;
}
inline int GetNumUniqueKnots() const
{
return static_cast<int>(mUniqueKnots.size());
}
inline int GetNumKnots() const
{
return static_cast<int>(mKnots.size());
}
inline Real GetMinDomain() const
{
return mTMin;
}
inline Real GetMaxDomain() const
{
return mTMax;
}
inline bool IsOpen() const
{
return mOpen;
}
inline bool IsUniform() const
{
return mUniform;
}
inline bool IsPeriodic() const
{
return mPeriodic;
}
inline UniqueKnot<Real> const* GetUniqueKnots() const
{
return &mUniqueKnots[0];
}
inline Real const* GetKnots() const
{
return &mKnots[0];
}
// Evaluation of the basis function and its derivatives through
// order 3. For the function value only, pass order 0. For the
// function and first derivative, pass order 1, and so on.
void Evaluate(Real t, unsigned int order, int& minIndex, int& maxIndex) const
{
LogAssert(order <= 3, "Invalid order.");
int i = GetIndex(t);
mJet[0][0][i] = (Real)1;
if (order >= 1)
{
mJet[1][0][i] = (Real)0;
if (order >= 2)
{
mJet[2][0][i] = (Real)0;
if (order >= 3)
{
mJet[3][0][i] = (Real)0;
}
}
}
Real n0 = t - mKnots[i], n1 = mKnots[i + 1] - t;
Real e0, e1, d0, d1, invD0, invD1;
int j;
for (j = 1; j <= mDegree; j++)
{
d0 = mKnots[i + j] - mKnots[i];
d1 = mKnots[i + 1] - mKnots[i - j + 1];
invD0 = (d0 > (Real)0 ? (Real)1 / d0 : (Real)0);
invD1 = (d1 > (Real)0 ? (Real)1 / d1 : (Real)0);
e0 = n0 * mJet[0][j - 1][i];
mJet[0][j][i] = e0 * invD0;
e1 = n1 * mJet[0][j - 1][i - j + 1];
mJet[0][j][i - j] = e1 * invD1;
if (order >= 1)
{
e0 = n0 * mJet[1][j - 1][i] + mJet[0][j - 1][i];
mJet[1][j][i] = e0 * invD0;
e1 = n1 * mJet[1][j - 1][i - j + 1] - mJet[0][j - 1][i - j + 1];
mJet[1][j][i - j] = e1 * invD1;
if (order >= 2)
{
e0 = n0 * mJet[2][j - 1][i] + ((Real)2) * mJet[1][j - 1][i];
mJet[2][j][i] = e0 * invD0;
e1 = n1 * mJet[2][j - 1][i - j + 1] - ((Real)2) * mJet[1][j - 1][i - j + 1];
mJet[2][j][i - j] = e1 * invD1;
if (order >= 3)
{
e0 = n0 * mJet[3][j - 1][i] + ((Real)3) * mJet[2][j - 1][i];
mJet[3][j][i] = e0 * invD0;
e1 = n1 * mJet[3][j - 1][i - j + 1] - ((Real)3) * mJet[2][j - 1][i - j + 1];
mJet[3][j][i - j] = e1 * invD1;
}
}
}
}
for (j = 2; j <= mDegree; ++j)
{
for (int k = i - j + 1; k < i; ++k)
{
n0 = t - mKnots[k];
n1 = mKnots[k + j + 1] - t;
d0 = mKnots[k + j] - mKnots[k];
d1 = mKnots[k + j + 1] - mKnots[k + 1];
invD0 = (d0 > (Real)0 ? (Real)1 / d0 : (Real)0);
invD1 = (d1 > (Real)0 ? (Real)1 / d1 : (Real)0);
e0 = n0 * mJet[0][j - 1][k];
e1 = n1 * mJet[0][j - 1][k + 1];
mJet[0][j][k] = e0 * invD0 + e1 * invD1;
if (order >= 1)
{
e0 = n0 * mJet[1][j - 1][k] + mJet[0][j - 1][k];
e1 = n1 * mJet[1][j - 1][k + 1] - mJet[0][j - 1][k + 1];
mJet[1][j][k] = e0 * invD0 + e1 * invD1;
if (order >= 2)
{
e0 = n0 * mJet[2][j - 1][k] + ((Real)2) * mJet[1][j - 1][k];
e1 = n1 * mJet[2][j - 1][k + 1] - ((Real)2) * mJet[1][j - 1][k + 1];
mJet[2][j][k] = e0 * invD0 + e1 * invD1;
if (order >= 3)
{
e0 = n0 * mJet[3][j - 1][k] + ((Real)3) * mJet[2][j - 1][k];
e1 = n1 * mJet[3][j - 1][k + 1] - ((Real)3) * mJet[2][j - 1][k + 1];
mJet[3][j][k] = e0 * invD0 + e1 * invD1;
}
}
}
}
}
minIndex = i - mDegree;
maxIndex = i;
}
// Access the results of the call to Evaluate(...). The index i must
// satisfy minIndex <= i <= maxIndex. If it is not, the function
// returns zero. The separation of evaluation and access is based on
// local control of the basis function; that is, only the accessible
// values are (potentially) not zero.
Real GetValue(unsigned int order, int i) const
{
if (order < 4)
{
if (0 <= i && i < mNumControls + mDegree)
{
return mJet[order][mDegree][i];
}
LogError("Invalid index.");
}
LogError("Invalid order.");
}
private:
// Determine the index i for which knot[i] <= t < knot[i+1]. The
// t-value is modified (wrapped for periodic splines, clamped for
// nonperiodic splines).
int GetIndex(Real& t) const
{
// Find the index i for which knot[i] <= t < knot[i+1].
if (mPeriodic)
{
// Wrap to [tmin,tmax].
Real r = std::fmod(t - mTMin, mTLength);
if (r < (Real)0)
{
r += mTLength;
}
t = mTMin + r;
}
// Clamp to [tmin,tmax]. For the periodic case, this handles
// small numerical rounding errors near the domain endpoints.
if (t <= mTMin)
{
t = mTMin;
return mDegree;
}
if (t >= mTMax)
{
t = mTMax;
return mNumControls - 1;
}
// At this point, tmin < t < tmax.
for (auto const& key : mKeys)
{
if (t < key.first)
{
return key.second;
}
}
// We should not reach this code.
LogError("Unexpected condition.");
}
// Constructor inputs and values derived from them.
int mNumControls;
int mDegree;
Real mTMin, mTMax, mTLength;
bool mOpen;
bool mUniform;
bool mPeriodic;
std::vector<UniqueKnot<Real>> mUniqueKnots;
std::vector<Real> mKnots;
// Lookup information for the GetIndex() function. The first element
// of the pair is a unique knot value. The second element is the
// index in mKnots[] for the last occurrence of that knot value.
std::vector<std::pair<Real, int>> mKeys;
// Storage for the basis functions and their first three derivatives;
// mJet[i] is array[d+1][n+d].
mutable std::array<Array2<Real>, 4> mJet;
};
}