// David Eberly, Geometric Tools, Redmond WA 98052 // Copyright (c) 1998-2020 // 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 #include #include #include // IntpBSplineUniform is the class for B-spline interpolation of uniformly // spaced N-dimensional data. The algorithm is described in // https://www.geometrictools.com/Documentation/BSplineInterpolation.pdf // A sample application for this topic is // GeometricTools/GTEngine/Samples/Imagics/BSplineInterpolation // // The Controls adapter allows access to your control points without regard // to how you organize your data. You can even defer the computation of a // control point until it is needed via the operator()(...) calls that // Controls must provide, and you can cache the points according to your own // needs. The minimal interface for a Controls adapter is // // struct Controls // { // // The control_point_type is of your choosing. It must support // // assignment, scalar multiplication and addition. Specifically, if // // C0, C1 and C2 are control points and s is a scalar, the interpolator // // needs to perform operations // // C1 = C0; // // C1 = C0 * s; // // C2 = C0 + C1; // typedef control_point_type Type; // // // The number of elements in the specified dimension. // int GetSize(int dimension) const; // // // Get a control point based on an n-tuple lookup. The interpolator // // does not need to know your organization; all it needs is the // // desired control point. The 'tuple' input must have n elements. // Type operator() (int const* tuple) const; // // // If you choose to use the specialized interpolators for dimensions // // 1, 2 or 3, you must also provide the following accessor, where // // the input is an n-tuple listed component-by-component (1, 2 or 3 // // components). // Type operator() (int i0, int i1, ..., int inm1) const; // } namespace WwiseGTE { template class IntpBSplineUniformShared { protected: // Abstract base class construction. A virtual destructor is not // provided because there are no required side effects in the base // class when destroying objects from the derived classes. IntpBSplineUniformShared(int numDimensions, int const* degrees, Controls const& controls, typename Controls::Type ctZero, int cacheMode) : mNumDimensions(numDimensions), mDegree(numDimensions), mControls(&controls), mCTZero(ctZero), mCacheMode(cacheMode), mNumLocalControls(0), mDegreeP1(numDimensions), mNumControls(numDimensions), mTMin(numDimensions), mTMax(numDimensions), mBlender(numDimensions), mDCoefficient(numDimensions), mLMax(numDimensions), mPowerDSDT(numDimensions), mITuple(numDimensions), mJTuple(numDimensions), mKTuple(numDimensions), mLTuple(numDimensions), mSumIJTuple(numDimensions), mUTuple(numDimensions), mPTuple(numDimensions) { // The condition c+1 > d+1 is required so that when s = c+1-d, its // maximum value, we have at least two s-knots (d and d + 1). for (int dim = 0; dim < mNumDimensions; ++dim) { if (mControls->GetSize(dim) <= degrees[dim] + 1) { LogError("Incompatible degree and number of controls."); } } mNumLocalControls = 1; for (int dim = 0; dim < mNumDimensions; ++dim) { mDegree[dim] = degrees[dim]; mDegreeP1[dim] = degrees[dim] + 1; mNumLocalControls *= mDegreeP1[dim]; mNumControls[dim] = controls.GetSize(dim); mTMin[dim] = (Real)-0.5; mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); } if (mCacheMode == NO_CACHING) { mPhi.resize(mNumDimensions); for (int dim = 0; dim < mNumDimensions; ++dim) { mPhi[dim].resize(mDegreeP1[dim]); } } else { InitializeTensors(); } } #if !defined(GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION) IntpBSplineUniformShared() : mNumDimensions(0), mControls(nullptr), mCTZero(), mCacheMode(0), mNumLocalControls(0) { } #endif public: // Support for caching the intermediate tensor product of control // points with the blending matrices. A precached container has all // elements precomputed before any Evaluate(...) calls. The 'bool' // flags are all set to 'true'. A cached container fills the elements // on demand. The 'bool' flags are initially 'false', indicating the // EvalType component has not yet been computed. After it is computed // and stored, the flag is set to 'true'. enum { NO_CACHING, PRE_CACHING, ON_DEMAND_CACHING }; // Member access. inline int GetDegree(int dim) const { return mDegree[dim]; } inline int GetNumControls(int dim) const { return mNumControls[dim]; } inline Real GetTMin(int dim) const { return mTMin[dim]; } inline Real GetTMax(int dim) const { return mTMax[dim]; } inline int GetCacheMode() const { return mCacheMode; } protected: // Disallow copying and moving. IntpBSplineUniformShared(IntpBSplineUniformShared const&) = delete; IntpBSplineUniformShared& operator=(IntpBSplineUniformShared const&) = delete; IntpBSplineUniformShared(IntpBSplineUniformShared&&) = delete; IntpBSplineUniformShared& operator=(IntpBSplineUniformShared&&) = delete; // ComputeBlendingMatrix, ComputeDCoefficients, ComputePowers and // GetKey are used by the general-dimension derived classes and by // the specializations for dimensions 1, 2 and 3. // Compute the blending matrix that combines the control points and // the polynomial vector. The matrix A is stored in row-major order. static void ComputeBlendingMatrix(int degree, std::vector& A) { int const degreeP1 = degree + 1; A.resize(degreeP1 * degreeP1); if (degree == 0) { A[0] = (Real)1; return; } // P_{0,0}(s) std::vector> P(degreeP1); P[0][0] = (Real)1; // L0 = s/j Polynomial1 L0(1); L0[0] = (Real)0; // L1(s) = (j + 1 - s)/j Polynomial1 L1(1); // s-1 is used in computing translated P_{j-1,k-1}(s-1) Polynomial1 sm1 = { (Real)-1, (Real)1 }; // Compute // P_{j,k}(s) = L0(s)*P_{j-1,k}(s) + L1(s)*P_{j-1,k-1}(s-1) // for 0 <= k <= j where 1 <= j <= degree. When k = 0, // P_{j-1,-1}(s) = 0, so P_{j,0}(s) = L0(s)*P_{j-1,0}(s). When // k = j, P_{j-1,j}(s) = 0, so P_{j,j}(s) = L1(s)*P_{j-1,j-1}(s). // The polynomials at level j-1 are currently stored in P[0] // through P[j-1]. The polynomials at level j are computed and // stored in P[0] through P[j]; that is, they are computed in // place to reduce memory usage and copying. This requires // computing P[k] (level j) from P[k] (level j-1) and P[k-1] // (level j-1), which means we have to process k = j down to // k = 0. for (int j = 1; j <= degree; ++j) { Real invJ = (Real)1 / (Real)j; L0[1] = invJ; L1[0] = (Real)1 + invJ; L1[1] = -invJ; for (int k = j; k >= 0; --k) { Polynomial1 result = { (Real)0 }; if (k > 0) { result += L1 * P[k - 1].GetTranslation((Real)1); } if (k < j) { result += L0 * P[k]; } P[k] = result; } } // Compute Q_{d,k}(s) = P_{d,k}(s + k). std::vector> Q(degreeP1); for (int k = 0; k <= degree; ++k) { Q[k] = P[k].GetTranslation(static_cast(-k)); } // Extract the matrix A from the Q-polynomials. Row r of A // contains the coefficients of Q_{d,d-r}(s). for (int k = 0, row = degree; k <= degree; ++k, --row) { for (int col = 0; col <= degree; ++col) { A[col + degreeP1 * row] = Q[k][col]; } } } // Compute the coefficients for the derivative polynomial terms. static void ComputeDCoefficients(int degree, std::vector& dCoefficients, std::vector& ellMax) { int numDCoefficients = (degree + 1) * (degree + 2) / 2; dCoefficients.resize(numDCoefficients); for (int i = 0; i < numDCoefficients; ++i) { dCoefficients[i] = 1; } for (int order = 1, col0 = 0, col1 = degree + 1; order <= degree; ++order) { ++col0; for (int c = order, m = 1; c <= degree; ++c, ++m, ++col0, ++col1) { dCoefficients[col1] = dCoefficients[col0] * m; } } ellMax.resize(degree + 1); ellMax[0] = degree; for (int i0 = 0, i1 = 1; i1 <= degree; i0 = i1++) { ellMax[i1] = ellMax[i0] + degree - i0; } } // Compute powers of ds/dt. void ComputePowers(int degree, int numControls, Real tmin, Real tmax, std::vector& powerDSDT) { Real dsdt = static_cast(numControls - degree) / (tmax - tmin); powerDSDT.resize(degree + 1); powerDSDT[0] = (Real)1; powerDSDT[1] = dsdt; for (int i = 2; i <= degree; ++i) { powerDSDT[i] = powerDSDT[i - 1] * dsdt; } } // Determine the interval [index,index+1) corresponding to the // specified value of t and compute u in that interval. static void GetKey(Real t, Real tmin, Real tmax, Real dsdt, int numControls, int degree, int& index, Real& u) { // Compute s - d = ((c + 1 - d)/(c + 1))(t + 1/2), the index for // which d + index <= s < d + index + 1. Let u = s - d - index so // that 0 <= u < 1. if (t > tmin) { if (t < tmax) { Real smd = dsdt * (t - tmin); index = static_cast(std::floor(smd)); u = smd - static_cast(index); } else { // In the evaluation, s = c + 1 - d and i = c - d. This // causes s-d-i to be 1 in G_c(c+1-d). Effectively, the // selection of i extends the s-domain [d,c+1) to its // support [d,c+1]. index = numControls - 1 - degree; u = (Real)1; } } else { index = 0; u = (Real)0; } } // The remaining functions are used only by the general-dimension // derived classes when caching is enabled. // For the multidimensional tensor Phi{iTuple, kTuple), compute the // portion of the 1-dimensional index that corresponds to iTuple. int GetRowIndex(std::vector const& i) const { int rowIndex = i[mNumDimensions - 1]; int j1 = 2 * mNumDimensions - 2; for (int j0 = mNumDimensions - 2; j0 >= 0; --j0, --j1) { rowIndex = mTBound[j1] * rowIndex + i[j0]; } rowIndex = mTBound[j1] * rowIndex; return rowIndex; } // For the multidimensional tensor Phi{iTuple, kTuple), combine the // GetRowIndex(...) output with kTuple to produce the full // 1-dimensional index. int GetIndex(int rowIndex, std::vector const& k) const { int index = rowIndex + k[mNumDimensions - 1]; for (int j = mNumDimensions - 2; j >= 0; --j) { index = mTBound[j] * index + k[j]; } return index; } // Compute Phi(iTuple, kTuple). The 'index' value is an already // computed 1-dimensional index for the tensor. void ComputeTensor(int const* i, int const* k, int index) { auto element = mCTZero; for (int dim = 0; dim < mNumDimensions; ++dim) { mComputeJTuple[dim] = 0; } for (int iterate = 0; iterate < mNumLocalControls; ++iterate) { Real blend(1); for (int dim = 0; dim < mNumDimensions; ++dim) { blend *= mBlender[dim][k[dim] + mDegreeP1[dim] * mComputeJTuple[dim]]; mComputeSumIJTuple[dim] = i[dim] + mComputeJTuple[dim]; } element = element + (*mControls)(mComputeSumIJTuple.data()) * blend; for (int dim = 0; dim < mNumDimensions; ++dim) { if (++mComputeJTuple[dim] < mDegreeP1[dim]) { break; } mComputeJTuple[dim] = 0; } } mTensor[index] = element; } // Allocate the containers used for caching and fill in the tensor // for precaching when that mode is selected. void InitializeTensors() { mTBound.resize(2 * mNumDimensions); mComputeJTuple.resize(mNumDimensions); mComputeSumIJTuple.resize(mNumDimensions); mDegreeMinusOrder.resize(mNumDimensions); mTerm.resize(mNumDimensions); int current = 0; int numCached = 1; for (int dim = 0; dim < mNumDimensions; ++dim, ++current) { mTBound[current] = mDegreeP1[dim]; numCached *= mTBound[current]; } for (int dim = 0; dim < mNumDimensions; ++dim, ++current) { mTBound[current] = mNumControls[dim] - mDegree[dim]; numCached *= mTBound[current]; } mTensor.resize(numCached); mCached.resize(numCached); if (mCacheMode == PRE_CACHING) { std::vector tuple(2 * mNumDimensions, 0); for (int index = 0; index < numCached; ++index) { ComputeTensor(&tuple[mNumDimensions], &tuple[0], index); for (int i = 0; i < 2 * mNumDimensions; ++i) { if (++tuple[i] < mTBound[i]) { break; } tuple[i] = 0; } } std::fill(mCached.begin(), mCached.end(), true); } else { std::fill(mCached.begin(), mCached.end(), false); } } // Evaluate the interpolator. Each element of 'order' indicates the // order of the derivative you want to compute. For the function // value itself, pass in 'order' that has all 0 elements. typename Controls::Type EvaluateNoCaching(int const* order, Real const* t) { auto result = mCTZero; for (int dim = 0; dim < mNumDimensions; ++dim) { if (order[dim] < 0 || order[dim] > mDegree[dim]) { return result; } } for (int dim = 0; dim < mNumDimensions; ++dim) { GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], mNumControls[dim], mDegree[dim], mITuple[dim], mUTuple[dim]); } for (int dim = 0; dim < mNumDimensions; ++dim) { int jIndex = 0; for (int j = 0; j <= mDegree[dim]; ++j) { int kjIndex = mDegree[dim] + jIndex; int ell = mLMax[dim][order[dim]]; mPhi[dim][j] = (Real)0; for (int k = mDegree[dim]; k >= order[dim]; --k) { mPhi[dim][j] = mPhi[dim][j] * mUTuple[dim] + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; } jIndex += mDegreeP1[dim]; } } for (int dim = 0; dim < mNumDimensions; ++dim) { mJTuple[dim] = 0; mSumIJTuple[dim] = mITuple[dim]; mPTuple[dim] = mPhi[dim][0]; } for (int iterate = 0; iterate < mNumLocalControls; ++iterate) { Real product(1); for (int dim = 0; dim < mNumDimensions; ++dim) { product *= mPTuple[dim]; } result = result + (*mControls)(mSumIJTuple.data()) * product; for (int dim = 0; dim < mNumDimensions; ++dim) { if (++mJTuple[dim] <= mDegree[dim]) { mSumIJTuple[dim] = mITuple[dim] + mJTuple[dim]; mPTuple[dim] = mPhi[dim][mJTuple[dim]]; break; } mJTuple[dim] = 0; mSumIJTuple[dim] = mITuple[dim]; mPTuple[dim] = mPhi[dim][0]; } } Real adjust(1); for (int dim = 0; dim < mNumDimensions; ++dim) { adjust *= mPowerDSDT[dim][order[dim]]; } result = result * adjust; return result; } typename Controls::Type EvaluateCaching(int const* order, Real const* t) { int numIterates = 1; for (int dim = 0; dim < mNumDimensions; ++dim) { mDegreeMinusOrder[dim] = mDegree[dim] - order[dim]; if (mDegreeMinusOrder[dim] < 0 || mDegreeMinusOrder[dim] > mDegree[dim]) { return mCTZero; } numIterates *= mDegreeMinusOrder[dim] + 1; } for (int dim = 0; dim < mNumDimensions; ++dim) { GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], mNumControls[dim], mDegree[dim], mITuple[dim], mUTuple[dim]); } int rowIndex = GetRowIndex(mITuple); for (int dim = 0; dim < mNumDimensions; ++dim) { mJTuple[dim] = 0; mKTuple[dim] = mDegree[dim]; mLTuple[dim] = mLMax[dim][order[dim]]; mTerm[dim] = mCTZero; } for (int iterate = 0; iterate < numIterates; ++iterate) { int index = GetIndex(rowIndex, mKTuple); if (mCacheMode == ON_DEMAND_CACHING && !mCached[index]) { ComputeTensor(mITuple.data(), mKTuple.data(), index); mCached[index] = true; } mTerm[0] = mTerm[0] * mUTuple[0] + mTensor[index] * mDCoefficient[0][mLTuple[0]]; for (int dim = 0; dim < mNumDimensions; ++dim) { if (++mJTuple[dim] <= mDegreeMinusOrder[dim]) { --mKTuple[dim]; --mLTuple[dim]; break; } int dimp1 = dim + 1; if (dimp1 < mNumDimensions) { mTerm[dimp1] = mTerm[dimp1] * mUTuple[dimp1] + mTerm[dim] * mDCoefficient[dimp1][mLTuple[dimp1]]; mTerm[dim] = mCTZero; mJTuple[dim] = 0; mKTuple[dim] = mDegree[dim]; mLTuple[dim] = mLMax[dim][order[dim]]; } } } auto result = mTerm[mNumDimensions - 1]; Real adjust(1); for (int dim = 0; dim < mNumDimensions; ++dim) { adjust *= mPowerDSDT[dim][order[dim]]; } result = result * adjust; return result; } // Constructor inputs. int const mNumDimensions; // N std::vector mDegree; // degree[N] Controls const* mControls; typename Controls::Type const mCTZero; int const mCacheMode; // Parameters for B-spline evaluation. All std::vector containers // have N elements. int mNumLocalControls; // product of (degree[]+1) std::vector mDegreeP1; std::vector mNumControls; std::vector mTMin; std::vector mTMax; std::vector> mBlender; std::vector> mDCoefficient; std::vector> mLMax; std::vector> mPowerDSDT; std::vector mITuple; std::vector mJTuple; std::vector mKTuple; std::vector mLTuple; std::vector mSumIJTuple; std::vector mUTuple; std::vector mPTuple; // Support for no-cached B-spline evaluation. The std::vector // container has N elements. std::vector> mPhi; // Support for cached B-spline evaluation. std::vector mTBound; // tbound[2*N] std::vector mComputeJTuple; // computejtuple[N] std::vector mComputeSumIJTuple; // computesumijtuple[N] std::vector mDegreeMinusOrder; // degreeminusorder[N] std::vector mTerm; // mTerm[N] std::vector mTensor; // depends on numcontrols std::vector mCached; // same size as mTensor }; } // Implementation for B-spline interpolation whose dimension is known at // compile time. namespace WwiseGTE { template class IntpBSplineUniform : public IntpBSplineUniformShared { public: // The caller is responsible for ensuring that the IntpBSplineUniform // object persists as long as the input 'controls' exists. IntpBSplineUniform(std::array const& degrees, Controls const& controls, typename Controls::Type ctZero, int cacheMode) : IntpBSplineUniformShared(N, degrees.data(), controls, ctZero, cacheMode) { } // Disallow copying and moving. IntpBSplineUniform(IntpBSplineUniform const&) = delete; IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; IntpBSplineUniform(IntpBSplineUniform&&) = delete; IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; // Evaluate the interpolator. Each element of 'order' indicates the // order of the derivative you want to compute. For the function // value itself, pass in 'order' that has all 0 elements. typename Controls::Type Evaluate(std::array const& order, std::array const& t) { if (this->mCacheMode == this->NO_CACHING) { return this->EvaluateNoCaching(order.data(), t.data()); } else { return this->EvaluateCaching(order.data(), t.data()); } } }; } // Implementation for B-spline interpolation whose dimension is known only // at run time. namespace WwiseGTE { template class IntpBSplineUniform : public IntpBSplineUniformShared { public: // The caller is responsible for ensuring that the IntpBSplineUniform // object persists as long as the input 'controls' exists. IntpBSplineUniform(std::vector const& degrees, Controls const& controls, typename Controls::Type ctZero, int cacheMode) : IntpBSplineUniformShared(static_cast(degrees.size()), degrees.data(), controls, ctZero, cacheMode) { } // Disallow copying and moving. IntpBSplineUniform(IntpBSplineUniform const&) = delete; IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; IntpBSplineUniform(IntpBSplineUniform&&) = delete; IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; // Evaluate the interpolator. Each element of 'order' indicates the // order of the derivative you want to compute. For the function // value itself, pass in 'order' that has all 0 elements. typename Controls::Type Evaluate(std::vector const& order, std::vector const& t) { if (static_cast(order.size()) >= this->mNumDimensions && static_cast(t.size()) >= this->mNumDimensions) { if (this->mCacheMode == this->NO_CACHING) { return this->EvaluateNoCaching(order.data(), t.data()); } else { return this->EvaluateCaching(order.data(), t.data()); } } else { return this->mCTZero; } } }; } // To use only the N-dimensional template code above, define the symbol // GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION to disable the specializations // that occur below. Notice that the interfaces are different between the // specializations and the general code. #if !defined(GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION) // Specialization for 1-dimensional data. namespace WwiseGTE { template class IntpBSplineUniform : public IntpBSplineUniformShared { public: // The caller is responsible for ensuring that the IntpBSplineUniform // object persists as long as the input 'controls' exists. IntpBSplineUniform(int degree, Controls const& controls, typename Controls::Type ctZero, int cacheMode) : IntpBSplineUniformShared(), mDegree(degree), mControls(&controls), mCTZero(ctZero), mCacheMode(cacheMode) { // The condition c+1 > d+1 is required so that when s = c+1-d, its // maximum value, we have at least two s-knots (d and d + 1). if (mControls->GetSize(0) <= mDegree + 1) { LogError("Incompatible degree and number of controls."); } mDegreeP1 = mDegree + 1; mNumControls = mControls->GetSize(0); mTMin = (Real)-0.5; mTMax = static_cast(mNumControls) - (Real)0.5; mNumTRows = 0; mNumTCols = 0; this->ComputeBlendingMatrix(mDegree, mBlender); this->ComputeDCoefficients(mDegree, mDCoefficient, mLMax); this->ComputePowers(mDegree, mNumControls, mTMin, mTMax, mPowerDSDT); if (mCacheMode == this->NO_CACHING) { mPhi.resize(mDegreeP1); } else { InitializeTensors(); } } // Disallow copying and moving. IntpBSplineUniform(IntpBSplineUniform const&) = delete; IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; IntpBSplineUniform(IntpBSplineUniform&&) = delete; IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; // Member access. inline int GetDegree(int) const { return mDegree; } inline int GetNumControls(int) const { return mNumControls; } inline Real GetTMin(int) const { return mTMin; } inline Real GetTMax(int) const { return mTMax; } inline int GetCacheMode() const { return mCacheMode; } // Evaluate the interpolator. The order is 0 when you want the B-spline // function value itself. The order is 1 for the first derivative of the // function, and so on. typename Controls::Type Evaluate(std::array const& order, std::array const& t) { auto result = mCTZero; if (0 <= order[0] && order[0] <= mDegree) { int i; Real u; this->GetKey(t[0], mTMin, mTMax, mPowerDSDT[1], mNumControls, mDegree, i, u); if (mCacheMode == this->NO_CACHING) { int jIndex = 0; for (int j = 0; j <= mDegree; ++j) { int kjIndex = mDegree + jIndex; int ell = mLMax[order[0]]; mPhi[j] = (Real)0; for (int k = mDegree; k >= order[0]; --k) { mPhi[j] = mPhi[j] * u + mBlender[kjIndex--] * mDCoefficient[ell--]; } jIndex += mDegreeP1; } for (int j = 0; j <= mDegree; ++j) { result = result + (*mControls)(i + j) * mPhi[j]; } } else { int iIndex = mNumTCols * i; int kiIndex = mDegree + iIndex; int ell = mLMax[order[0]]; for (int k = mDegree; k >= order[0]; --k) { if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[kiIndex]) { ComputeTensor(i, k, kiIndex); mCached[kiIndex] = true; } result = result * u + mTensor[kiIndex--] * mDCoefficient[ell--]; } } result = result * mPowerDSDT[order[0]]; } return result; } protected: void ComputeTensor(int r, int c, int index) { auto element = mCTZero; for (int j = 0; j <= mDegree; ++j) { element = element + (*mControls)(r + j) * mBlender[c + mDegreeP1 * j]; } mTensor[index] = element; } void InitializeTensors() { mNumTRows = mNumControls - mDegree; mNumTCols = mDegreeP1; int numCached = mNumTRows * mNumTCols; mTensor.resize(numCached); mCached.resize(numCached); if (mCacheMode == this->PRE_CACHING) { for (int r = 0, index = 0; r < mNumTRows; ++r) { for (int c = 0; c < mNumTCols; ++c, ++index) { ComputeTensor(r, c, index); } } std::fill(mCached.begin(), mCached.end(), true); } else { std::fill(mCached.begin(), mCached.end(), false); } } // Constructor inputs. int mDegree; Controls const* mControls; typename Controls::Type mCTZero; int mCacheMode; // Parameters for B-spline evaluation. int mDegreeP1; int mNumControls; Real mTMin, mTMax; std::vector mBlender; std::vector mDCoefficient; std::vector mLMax; std::vector mPowerDSDT; // Support for no-cached B-spline evaluation. std::vector mPhi; // Support for cached B-spline evaluation. int mNumTRows, mNumTCols; std::vector mTensor; std::vector mCached; }; } // Specialization for 2-dimensional data. namespace WwiseGTE { template class IntpBSplineUniform : public IntpBSplineUniformShared { public: // The caller is responsible for ensuring that the IntpBSplineUniform2 // object persists as long as the input 'controls' exists. IntpBSplineUniform(std::array const& degrees, Controls const& controls, typename Controls::Type ctZero, int cacheMode) : IntpBSplineUniformShared(), mDegree(degrees), mControls(&controls), mCTZero(ctZero), mCacheMode(cacheMode) { // The condition c+1 > d+1 is required so that when s = c+1-d, its // maximum value, we have at least two s-knots (d and d + 1). for (int dim = 0; dim < 2; ++dim) { if (mControls->GetSize(dim) <= mDegree[dim] + 1) { LogError("Incompatible degree and number of controls."); } } for (int dim = 0; dim < 2; ++dim) { mDegreeP1[dim] = mDegree[dim] + 1; mNumControls[dim] = mControls->GetSize(dim); mTMin[dim] = (Real)-0.5; mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; mNumTRows[dim] = 0; mNumTCols[dim] = 0; this->ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); this->ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); this->ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); } if (mCacheMode == this->NO_CACHING) { for (int dim = 0; dim < 2; ++dim) { mPhi[dim].resize(mDegreeP1[dim]); } } else { InitializeTensors(); } } // Disallow copying and moving. IntpBSplineUniform(IntpBSplineUniform const&) = delete; IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; IntpBSplineUniform(IntpBSplineUniform&&) = delete; IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; // Member access. inline int GetDegree(int dim) const { return mDegree[dim]; } inline int GetNumControls(int dim) const { return mNumControls[dim]; } inline Real GetTMin(int dim) const { return mTMin[dim]; } inline Real GetTMax(int dim) const { return mTMax[dim]; } inline int GetCacheMode() const { return mCacheMode; } // Evaluate the interpolator. The order is (0,0) when you want the // B-spline function value itself. The order0 is 1 for the first // derivative with respect to t0 and the order1 is 1 for the first // derivative with respect to t1. Higher-order derivatives in other // t-inputs are computed similarly. typename Controls::Type Evaluate(std::array const& order, std::array const& t) { auto result = mCTZero; if (0 <= order[0] && order[0] <= mDegree[0] && 0 <= order[1] && order[1] <= mDegree[1]) { std::array i; std::array u; for (int dim = 0; dim < 2; ++dim) { this->GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], mNumControls[dim], mDegree[dim], i[dim], u[dim]); } if (mCacheMode == this->NO_CACHING) { for (int dim = 0; dim < 2; ++dim) { int jIndex = 0; for (int j = 0; j <= mDegree[dim]; ++j) { int kjIndex = mDegree[dim] + jIndex; int ell = mLMax[dim][order[dim]]; mPhi[dim][j] = (Real)0; for (int k = mDegree[dim]; k >= order[dim]; --k) { mPhi[dim][j] = mPhi[dim][j] * u[dim] + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; } jIndex += mDegreeP1[dim]; } } for (int j1 = 0; j1 <= mDegree[1]; ++j1) { Real phi1 = mPhi[1][j1]; for (int j0 = 0; j0 <= mDegree[0]; ++j0) { Real phi0 = mPhi[0][j0]; Real phi01 = phi0 * phi1; result = result + (*mControls)(i[0] + j0, i[1] + j1) * phi01; } } } else { int i0i1Index = mNumTCols[1] * (i[0] + mNumTRows[0] * i[1]); int k1i0i1Index = mDegree[1] + i0i1Index; int ell1 = mLMax[1][order[1]]; for (int k1 = mDegree[1]; k1 >= order[1]; --k1) { int k0k1i0i1Index = mDegree[0] + mNumTCols[0] * k1i0i1Index; int ell0 = mLMax[0][order[0]]; auto term = mCTZero; for (int k0 = mDegree[0]; k0 >= order[0]; --k0) { if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[k0k1i0i1Index]) { ComputeTensor(i[0], i[1], k0, k1, k0k1i0i1Index); mCached[k0k1i0i1Index] = true; } term = term * u[0] + mTensor[k0k1i0i1Index--] * mDCoefficient[0][ell0--]; } result = result * u[1] + term * mDCoefficient[1][ell1--]; --k1i0i1Index; } } Real adjust(1); for (int dim = 0; dim < 2; ++dim) { adjust *= mPowerDSDT[dim][order[dim]]; } result = result * adjust; } return result; } void ComputeTensor(int r0, int r1, int c0, int c1, int index) { auto element = mCTZero; for (int j1 = 0; j1 <= mDegree[1]; ++j1) { Real blend1 = mBlender[1][c1 + mDegreeP1[1] * j1]; for (int j0 = 0; j0 <= mDegree[0]; ++j0) { Real blend0 = mBlender[0][c0 + mDegreeP1[0] * j0]; Real blend01 = blend0 * blend1; element = element + (*mControls)(r0 + j0, r1 + j1) * blend01; } } mTensor[index] = element; } void InitializeTensors() { int numCached = 1; for (int dim = 0; dim < 2; ++dim) { mNumTRows[dim] = mNumControls[dim] - mDegree[dim]; mNumTCols[dim] = mDegreeP1[dim]; numCached *= mNumTRows[dim] * mNumTCols[dim]; } mTensor.resize(numCached); mCached.resize(numCached); if (mCacheMode == this->PRE_CACHING) { for (int r1 = 0, index = 0; r1 < mNumTRows[1]; ++r1) { for (int r0 = 0; r0 < mNumTRows[0]; ++r0) { for (int c1 = 0; c1 < mNumTCols[1]; ++c1) { for (int c0 = 0; c0 < mNumTCols[0]; ++c0, ++index) { ComputeTensor(r0, r1, c0, c1, index); } } } } std::fill(mCached.begin(), mCached.end(), true); } else { std::fill(mCached.begin(), mCached.end(), false); } } // Constructor inputs. std::array mDegree; Controls const* mControls; typename Controls::Type mCTZero; int mCacheMode; // Parameters for B-spline evaluation. std::array mDegreeP1; std::array mNumControls; std::array mTMin, mTMax; std::array, 2> mBlender; std::array, 2> mDCoefficient; std::array, 2> mLMax; std::array, 2> mPowerDSDT; // Support for no-cached B-spline evaluation. std::array, 2> mPhi; // Support for cached B-spline evaluation. std::array mNumTRows, mNumTCols; std::vector mTensor; std::vector mCached; }; } // Specialization for 3-dimensional data. namespace WwiseGTE { template class IntpBSplineUniform : public IntpBSplineUniformShared { public: // The caller is responsible for ensuring that the IntpBSplineUniform3 // object persists as long as the input 'controls' exists. IntpBSplineUniform(std::array const& degrees, Controls const& controls, typename Controls::Type ctZero, int cacheMode) : IntpBSplineUniformShared(), mDegree(degrees), mControls(&controls), mCTZero(ctZero), mCacheMode(cacheMode) { // The condition c+1 > d+1 is required so that when s = c+1-d, its // maximum value, we have at least two s-knots (d and d + 1). for (int dim = 0; dim < 3; ++dim) { if (mControls->GetSize(dim) <= mDegree[dim] + 1) { LogError("Incompatible degree and number of controls."); } } for (int dim = 0; dim < 3; ++dim) { mDegreeP1[dim] = mDegree[dim] + 1; mNumControls[dim] = mControls->GetSize(dim); mTMin[dim] = (Real)-0.5; mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; mNumTRows[dim] = 0; mNumTCols[dim] = 0; this->ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); this->ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); this->ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); } if (mCacheMode == this->NO_CACHING) { for (int dim = 0; dim < 3; ++dim) { mPhi[dim].resize(mDegreeP1[dim]); } } else { InitializeTensors(); } } // Disallow copying and moving. IntpBSplineUniform(IntpBSplineUniform const&) = delete; IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; IntpBSplineUniform(IntpBSplineUniform&&) = delete; IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; // Member access. The input i specifies the dimension (0, 1, 2). inline int GetDegree(int dim) const { return mDegree[dim]; } inline int GetNumControls(int dim) const { return mNumControls[dim]; } inline Real GetTMin(int dim) const { return mTMin[dim]; } inline Real GetTMax(int dim) const { return mTMax[dim]; } // Evaluate the interpolator. The order is (0,0,0) when you want the // B-spline function value itself. The order0 is 1 for the first // derivative with respect to t0, the order1 is 1 for the first // derivative with respect to t1 or the order2 is 1 for the first // derivative with respect to t2. Higher-order derivatives in other // t-inputs are computed similarly. typename Controls::Type Evaluate(std::array const& order, std::array const& t) { auto result = mCTZero; if (0 <= order[0] && order[0] <= mDegree[0] && 0 <= order[1] && order[1] <= mDegree[1] && 0 <= order[2] && order[2] <= mDegree[2]) { std::array i; std::array u; for (int dim = 0; dim < 3; ++dim) { this->GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], mNumControls[dim], mDegree[dim], i[dim], u[dim]); } if (mCacheMode == this->NO_CACHING) { for (int dim = 0; dim < 3; ++dim) { int jIndex = 0; for (int j = 0; j <= mDegree[dim]; ++j) { int kjIndex = mDegree[dim] + jIndex; int ell = mLMax[dim][order[dim]]; mPhi[dim][j] = (Real)0; for (int k = mDegree[dim]; k >= order[dim]; --k) { mPhi[dim][j] = mPhi[dim][j] * u[dim] + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; } jIndex += mDegreeP1[dim]; } } for (int j2 = 0; j2 <= mDegree[2]; ++j2) { Real phi2 = mPhi[2][j2]; for (int j1 = 0; j1 <= mDegree[1]; ++j1) { Real phi1 = mPhi[1][j1]; Real phi12 = phi1 * phi2; for (int j0 = 0; j0 <= mDegree[0]; ++j0) { Real phi0 = mPhi[0][j0]; Real phi012 = phi0 * phi12; result = result + (*mControls)(i[0] + j0, i[1] + j1, i[2] + j2) * phi012; } } } } else { int i0i1i2Index = mNumTCols[2] * (i[0] + mNumTRows[0] * (i[1] + mNumTRows[1] * i[2])); int k2i0i1i2Index = mDegree[2] + i0i1i2Index; int ell2 = mLMax[2][order[2]]; for (int k2 = mDegree[2]; k2 >= order[2]; --k2) { int k1k2i0i1i2Index = mDegree[1] + mNumTCols[1] * k2i0i1i2Index; int ell1 = mLMax[1][order[1]]; auto term1 = mCTZero; for (int k1 = mDegree[1]; k1 >= order[1]; --k1) { int k0k1k2i0i1i2Index = mDegree[0] + mNumTCols[0] * k1k2i0i1i2Index; int ell0 = mLMax[0][order[0]]; auto term0 = mCTZero; for (int k0 = mDegree[0]; k0 >= order[0]; --k0) { if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[k0k1k2i0i1i2Index]) { ComputeTensor(i[0], i[1], i[2], k0, k1, k2, k0k1k2i0i1i2Index); mCached[k0k1k2i0i1i2Index] = true; } term0 = term0 * u[0] + mTensor[k0k1k2i0i1i2Index--] * mDCoefficient[0][ell0--]; } term1 = term1 * u[1] + term0 * mDCoefficient[1][ell1--]; --k1k2i0i1i2Index; } result = result * u[2] + term1 * mDCoefficient[2][ell2--]; --k2i0i1i2Index; } } Real adjust(1); for (int dim = 0; dim < 3; ++dim) { adjust *= mPowerDSDT[dim][order[dim]]; } result = result * adjust; } return result; } protected: void ComputeTensor(int r0, int r1, int r2, int c0, int c1, int c2, int index) { auto element = mCTZero; for (int j2 = 0; j2 <= mDegree[2]; ++j2) { Real blend2 = mBlender[2][c2 + mDegreeP1[2] * j2]; for (int j1 = 0; j1 <= mDegree[1]; ++j1) { Real blend1 = mBlender[1][c1 + mDegreeP1[1] * j1]; Real blend12 = blend1 * blend2; for (int j0 = 0; j0 <= mDegree[0]; ++j0) { Real blend0 = mBlender[0][c0 + mDegreeP1[0] * j0]; Real blend012 = blend0 * blend12; element = element + (*mControls)(r0 + j0, r1 + j1, r2 + j2) * blend012; } } } mTensor[index] = element; } void InitializeTensors() { int numCached = 1; for (int dim = 0; dim < 3; ++dim) { mNumTRows[dim] = mNumControls[dim] - mDegree[dim]; mNumTCols[dim] = mDegreeP1[dim]; numCached *= mNumTRows[dim] * mNumTCols[dim]; } mTensor.resize(numCached); mCached.resize(numCached); if (mCacheMode == this->PRE_CACHING) { for (int r2 = 0, index = 0; r2 < mNumTRows[2]; ++r2) { for (int r1 = 0; r1 < mNumTRows[1]; ++r1) { for (int r0 = 0; r0 < mNumTRows[0]; ++r0) { for (int c2 = 0; c2 < mNumTCols[2]; ++c2) { for (int c1 = 0; c1 < mNumTCols[1]; ++c1) { for (int c0 = 0; c0 < mNumTCols[0]; ++c0, ++index) { ComputeTensor(r0, r1, r2, c0, c1, c2, index); } } } } } } std::fill(mCached.begin(), mCached.end(), true); } else { std::fill(mCached.begin(), mCached.end(), false); } } // Constructor inputs. std::array mDegree; Controls const* mControls; typename Controls::Type mCTZero; int mCacheMode; // Parameters for B-spline evaluation. std::array mDegreeP1; std::array mNumControls; std::array mTMin, mTMax; std::array, 3> mBlender; std::array, 3> mDCoefficient; std::array, 3> mLMax; std::array, 3> mPowerDSDT; // Support for no-cached B-spline evaluation. std::array, 3> mPhi; // Support for cached B-spline evaluation. std::array mNumTRows, mNumTCols; std::vector mTensor; std::vector mCached; }; } #endif