BSplineCurveFit.h 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. // David Eberly, Geometric Tools, Redmond WA 98052
  2. // Copyright (c) 1998-2020
  3. // Distributed under the Boost Software License, Version 1.0.
  4. // https://www.boost.org/LICENSE_1_0.txt
  5. // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
  6. // Version: 4.0.2019.08.13
  7. #pragma once
  8. #include <Mathematics/BasisFunction.h>
  9. #include <Mathematics/BandedMatrix.h>
  10. // The algorithm implemented here is based on the document
  11. // https://www.geometrictools.com/Documentation/BSplineCurveLeastSquaresFit.pdf
  12. namespace WwiseGTE
  13. {
  14. template <typename Real>
  15. class BSplineCurveFit
  16. {
  17. public:
  18. // Construction. The preconditions for calling the constructor are
  19. // 1 <= degree && degree < numControls <= numSamples
  20. // The samples points are contiguous blocks of 'dimension' real values
  21. // stored in sampleData.
  22. BSplineCurveFit(int dimension, int numSamples, Real const* sampleData,
  23. int degree, int numControls)
  24. :
  25. mDimension(dimension),
  26. mNumSamples(numSamples),
  27. mSampleData(sampleData),
  28. mDegree(degree),
  29. mNumControls(numControls),
  30. mControlData(dimension * numControls)
  31. {
  32. LogAssert(dimension >= 1, "Invalid dimension.");
  33. LogAssert(1 <= degree && degree < numControls, "Invalid degree.");
  34. LogAssert(sampleData, "Invalid sample data.");
  35. LogAssert(numControls <= numSamples, "Invalid number of controls.");
  36. BasisFunctionInput<Real> input;
  37. input.numControls = numControls;
  38. input.degree = degree;
  39. input.uniform = true;
  40. input.periodic = false;
  41. input.numUniqueKnots = numControls - degree + 1;
  42. input.uniqueKnots.resize(input.numUniqueKnots);
  43. input.uniqueKnots[0].t = (Real)0;
  44. input.uniqueKnots[0].multiplicity = degree + 1;
  45. int last = input.numUniqueKnots - 1;
  46. Real factor = ((Real)1) / (Real)last;
  47. for (int i = 1; i < last; ++i)
  48. {
  49. input.uniqueKnots[i].t = factor * (Real)i;
  50. input.uniqueKnots[i].multiplicity = 1;
  51. }
  52. input.uniqueKnots[last].t = (Real)1;
  53. input.uniqueKnots[last].multiplicity = degree + 1;
  54. mBasis.Create(input);
  55. // Fit the data points with a B-spline curve using a least-squares
  56. // error metric. The problem is of the form A^T*A*Q = A^T*P,
  57. // where A^T*A is a banded matrix, P contains the sample data, and
  58. // Q is the unknown vector of control points.
  59. Real tMultiplier = ((Real)1) / (Real)(mNumSamples - 1);
  60. Real t;
  61. int i0, i1, i2, imin, imax, j;
  62. // Construct the matrix A^T*A.
  63. int degp1 = mDegree + 1;
  64. int numBands = (mNumControls > degp1 ? degp1 : mDegree);
  65. BandedMatrix<Real> ATAMat(mNumControls, numBands, numBands);
  66. for (i0 = 0; i0 < mNumControls; ++i0)
  67. {
  68. for (i1 = 0; i1 < i0; ++i1)
  69. {
  70. ATAMat(i0, i1) = ATAMat(i1, i0);
  71. }
  72. int i1Max = i0 + mDegree;
  73. if (i1Max >= mNumControls)
  74. {
  75. i1Max = mNumControls - 1;
  76. }
  77. for (i1 = i0; i1 <= i1Max; ++i1)
  78. {
  79. Real value = (Real)0;
  80. for (i2 = 0; i2 < mNumSamples; ++i2)
  81. {
  82. t = tMultiplier * (Real)i2;
  83. mBasis.Evaluate(t, 0, imin, imax);
  84. if (imin <= i0 && i0 <= imax && imin <= i1 && i1 <= imax)
  85. {
  86. Real b0 = mBasis.GetValue(0, i0);
  87. Real b1 = mBasis.GetValue(0, i1);
  88. value += b0 * b1;
  89. }
  90. }
  91. ATAMat(i0, i1) = value;
  92. }
  93. }
  94. // Construct the matrix A^T.
  95. Array2<Real> ATMat(mNumSamples, mNumControls);
  96. std::memset(ATMat[0], 0, mNumControls * mNumSamples * sizeof(Real));
  97. for (i0 = 0; i0 < mNumControls; ++i0)
  98. {
  99. for (i1 = 0; i1 < mNumSamples; ++i1)
  100. {
  101. t = tMultiplier * (Real)i1;
  102. mBasis.Evaluate(t, 0, imin, imax);
  103. if (imin <= i0 && i0 <= imax)
  104. {
  105. ATMat[i0][i1] = mBasis.GetValue(0, i0);
  106. }
  107. }
  108. }
  109. // Compute X0 = (A^T*A)^{-1}*A^T by solving the linear system
  110. // A^T*A*X = A^T.
  111. bool solved = ATAMat.template SolveSystem<true>(ATMat[0], mNumSamples);
  112. LogAssert(solved, "Failed to solve linear system.");
  113. // The control points for the fitted curve are stored in the
  114. // vector Q = X0*P, where P is the vector of sample data.
  115. std::fill(mControlData.begin(), mControlData.end(), (Real)0);
  116. for (i0 = 0; i0 < mNumControls; ++i0)
  117. {
  118. Real* Q = &mControlData[i0 * mDimension];
  119. for (i1 = 0; i1 < mNumSamples; ++i1)
  120. {
  121. Real const* P = mSampleData + i1 * mDimension;
  122. Real xValue = ATMat[i0][i1];
  123. for (j = 0; j < mDimension; ++j)
  124. {
  125. Q[j] += xValue * P[j];
  126. }
  127. }
  128. }
  129. // Set the first and last output control points to match the first
  130. // and last input samples. This supports the application of
  131. // fitting keyframe data with B-spline curves. The user expects
  132. // that the curve passes through the first and last positions in
  133. // order to support matching two consecutive keyframe sequences.
  134. Real* cEnd0 = &mControlData[0];
  135. Real const* sEnd0 = mSampleData;
  136. Real* cEnd1 = &mControlData[mDimension * (mNumControls - 1)];
  137. Real const* sEnd1 = &mSampleData[mDimension * (mNumSamples - 1)];
  138. for (j = 0; j < mDimension; ++j)
  139. {
  140. *cEnd0++ = *sEnd0++;
  141. *cEnd1++ = *sEnd1++;
  142. }
  143. }
  144. // Access to input sample information.
  145. inline int GetDimension() const
  146. {
  147. return mDimension;
  148. }
  149. inline int GetNumSamples() const
  150. {
  151. return mNumSamples;
  152. }
  153. inline Real const* GetSampleData() const
  154. {
  155. return mSampleData;
  156. }
  157. // Access to output control point and curve information.
  158. inline int GetDegree() const
  159. {
  160. return mDegree;
  161. }
  162. inline int GetNumControls() const
  163. {
  164. return mNumControls;
  165. }
  166. inline Real const* GetControlData() const
  167. {
  168. return &mControlData[0];
  169. }
  170. inline BasisFunction<Real> const& GetBasis() const
  171. {
  172. return mBasis;
  173. }
  174. // Evaluation of the B-spline curve. It is defined for 0 <= t <= 1.
  175. // If a t-value is outside [0,1], an open spline clamps it to [0,1].
  176. // The caller must ensure that position[] has at least 'dimension'
  177. // elements.
  178. void Evaluate(Real t, unsigned int order, Real* value) const
  179. {
  180. int imin, imax;
  181. mBasis.Evaluate(t, order, imin, imax);
  182. Real const* source = &mControlData[mDimension * imin];
  183. Real basisValue = mBasis.GetValue(order, imin);
  184. for (int j = 0; j < mDimension; ++j)
  185. {
  186. value[j] = basisValue * (*source++);
  187. }
  188. for (int i = imin + 1; i <= imax; ++i)
  189. {
  190. basisValue = mBasis.GetValue(order, i);
  191. for (int j = 0; j < mDimension; ++j)
  192. {
  193. value[j] += basisValue * (*source++);
  194. }
  195. }
  196. }
  197. void GetPosition(Real t, Real* position) const
  198. {
  199. Evaluate(t, 0, position);
  200. }
  201. private:
  202. // Input sample information.
  203. int mDimension;
  204. int mNumSamples;
  205. Real const* mSampleData;
  206. // The fitted B-spline curve, open and with uniform knots.
  207. int mDegree;
  208. int mNumControls;
  209. std::vector<Real> mControlData;
  210. BasisFunction<Real> mBasis;
  211. };
  212. }