ApprCone3.h 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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/GaussNewtonMinimizer.h>
  9. #include <Mathematics/LevenbergMarquardtMinimizer.h>
  10. #include <Mathematics/RootsPolynomial.h>
  11. // The cone vertex is V, the unit-length axis direction is U and the
  12. // cone angle is A in (0,pi/2). The cone is defined algebraically by
  13. // those points X for which
  14. // Dot(U,X-V)/Length(X-V) = cos(A)
  15. // This can be written as a quadratic equation
  16. // (V-X)^T * (cos(A)^2 - U * U^T) * (V-X) = 0
  17. // with the implicit constraint that Dot(U, X-V) > 0 (X is on the
  18. // "positive" cone). Define W = U/cos(A), so Length(W) > 1 and
  19. // F(X;V,W) = (V-X)^T * (I - W * W^T) * (V-X) = 0
  20. // The nonlinear least squares fitting of points {X[i]}_{i=0}^{n-1}
  21. // computes V and W to minimize the error function
  22. // E(V,W) = sum_{i=0}^{n-1} F(X[i];V,W)^2
  23. // I recommend using the Gauss-Newton minimizer when your cone points
  24. // are truly nearly a cone; otherwise, try the Levenberg-Marquardt
  25. // minimizer.
  26. //
  27. // The mathematics used in this implementation are found in
  28. // https://www.geometrictools.com/Documentation/LeastSquaresFitting.pdf
  29. // In particular, the details for choosing an initial cone for fitting
  30. // are somewhat complicated.
  31. namespace WwiseGTE
  32. {
  33. template <typename Real>
  34. class ApprCone3
  35. {
  36. public:
  37. ApprCone3()
  38. :
  39. mNumPoints(0),
  40. mPoints(nullptr)
  41. {
  42. // F[i](V,W) = D^T * (I - W * W^T) * D, D = V - X[i], P = (V,W)
  43. mFFunction = [this](GVector<Real> const& P, GVector<Real>& F)
  44. {
  45. Vector<3, Real> V = { P[0], P[1], P[2] };
  46. Vector<3, Real> W = { P[3], P[4], P[5] };
  47. for (int i = 0; i < mNumPoints; ++i)
  48. {
  49. Vector<3, Real> delta = V - mPoints[i];
  50. Real deltaDotW = Dot(delta, W);
  51. F[i] = Dot(delta, delta) - deltaDotW * deltaDotW;
  52. }
  53. };
  54. // dF[i]/dV = 2 * (D - Dot(W, D) * W)
  55. // dF[i]/dW = -2 * Dot(W, D) * D
  56. mJFunction = [this](GVector<Real> const& P, GMatrix<Real>& J)
  57. {
  58. Vector<3, Real> V = { P[0], P[1], P[2] };
  59. Vector<3, Real> W = { P[3], P[4], P[5] };
  60. for (int row = 0; row < mNumPoints; ++row)
  61. {
  62. Vector<3, Real> delta = V - mPoints[row];
  63. Real deltaDotW = Dot(delta, W);
  64. Vector<3, Real> temp0 = delta - deltaDotW * W;
  65. Vector<3, Real> temp1 = deltaDotW * delta;
  66. for (int col = 0; col < 3; ++col)
  67. {
  68. J(row, col) = (Real)2 * temp0[col];
  69. J(row, col + 3) = (Real)-2 * temp1[col];
  70. }
  71. }
  72. };
  73. }
  74. // If you want to specify that coneVertex, coneAxis and coneAngle
  75. // are the initial guesses for the minimizer, set the parameter
  76. // useConeInputAsInitialGuess to 'true'. If you want the function
  77. // to compute initial guesses, set that parameter to 'false'.
  78. // A Gauss-Newton minimizer is used to fit a cone using nonlinear
  79. // least-squares. The fitted cone is returned in coneVertex,
  80. // coneAxis and coneAngle. See GaussNewtonMinimizer.h for a
  81. // description of the least-squares algorithm and the parameters
  82. // that it requires.
  83. typename GaussNewtonMinimizer<Real>::Result
  84. operator()(int numPoints, Vector<3, Real> const* points,
  85. size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance,
  86. bool useConeInputAsInitialGuess,
  87. Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneAngle)
  88. {
  89. mNumPoints = numPoints;
  90. mPoints = points;
  91. GaussNewtonMinimizer<Real> minimizer(6, mNumPoints, mFFunction, mJFunction);
  92. Real coneCosAngle;
  93. if (useConeInputAsInitialGuess)
  94. {
  95. Normalize(coneAxis);
  96. coneCosAngle = std::cos(coneAngle);
  97. }
  98. else
  99. {
  100. ComputeInitialCone(coneVertex, coneAxis, coneCosAngle);
  101. }
  102. // The initial guess for the cone vertex.
  103. GVector<Real> initial(6);
  104. initial[0] = coneVertex[0];
  105. initial[1] = coneVertex[1];
  106. initial[2] = coneVertex[2];
  107. // The initial guess for the weighted cone axis.
  108. initial[3] = coneAxis[0] / coneCosAngle;
  109. initial[4] = coneAxis[1] / coneCosAngle;
  110. initial[5] = coneAxis[2] / coneCosAngle;
  111. auto result = minimizer(initial, maxIterations, updateLengthTolerance,
  112. errorDifferenceTolerance);
  113. // No test is made for result.converged so that we return some
  114. // estimates of the cone. The caller can decide how to respond
  115. // when result.converged is false.
  116. for (int i = 0; i < 3; ++i)
  117. {
  118. coneVertex[i] = result.minLocation[i];
  119. coneAxis[i] = result.minLocation[i + 3];
  120. }
  121. // We know that coneCosAngle will be nonnegative. The std::min
  122. // call guards against rounding errors leading to a number
  123. // slightly larger than 1. The clamping ensures std::acos will
  124. // not return a NaN.
  125. coneCosAngle = std::min((Real)1 / Normalize(coneAxis), (Real)1);
  126. coneAngle = std::acos(coneCosAngle);
  127. mNumPoints = 0;
  128. mPoints = nullptr;
  129. return result;
  130. }
  131. // The parameters coneVertex, coneAxis and coneAngle are in/out
  132. // variables. The caller must provide initial guesses for these.
  133. // The function estimates the cone parameters and returns them. See
  134. // GteGaussNewtonMinimizer.h for a description of the least-squares
  135. // algorithm and the parameters that it requires.
  136. typename LevenbergMarquardtMinimizer<Real>::Result
  137. operator()(int numPoints, Vector<3, Real> const* points,
  138. size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance,
  139. Real lambdaFactor, Real lambdaAdjust, size_t maxAdjustments,
  140. bool useConeInputAsInitialGuess,
  141. Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneAngle)
  142. {
  143. mNumPoints = numPoints;
  144. mPoints = points;
  145. LevenbergMarquardtMinimizer<Real> minimizer(6, mNumPoints, mFFunction, mJFunction);
  146. Real coneCosAngle;
  147. if (useConeInputAsInitialGuess)
  148. {
  149. Normalize(coneAxis);
  150. coneCosAngle = std::cos(coneAngle);
  151. }
  152. else
  153. {
  154. ComputeInitialCone(coneVertex, coneAxis, coneCosAngle);
  155. }
  156. // The initial guess for the cone vertex.
  157. GVector<Real> initial(6);
  158. initial[0] = coneVertex[0];
  159. initial[1] = coneVertex[1];
  160. initial[2] = coneVertex[2];
  161. // The initial guess for the weighted cone axis.
  162. initial[3] = coneAxis[0] / coneCosAngle;
  163. initial[4] = coneAxis[1] / coneCosAngle;
  164. initial[5] = coneAxis[2] / coneCosAngle;
  165. auto result = minimizer(initial, maxIterations, updateLengthTolerance,
  166. errorDifferenceTolerance, lambdaFactor, lambdaAdjust, maxAdjustments);
  167. // No test is made for result.converged so that we return some
  168. // estimates of the cone. The caller can decide how to respond
  169. // when result.converged is false.
  170. for (int i = 0; i < 3; ++i)
  171. {
  172. coneVertex[i] = result.minLocation[i];
  173. coneAxis[i] = result.minLocation[i + 3];
  174. }
  175. // We know that coneCosAngle will be nonnegative. The std::min
  176. // call guards against rounding errors leading to a number
  177. // slightly larger than 1. The clamping ensures std::acos will
  178. // not return a NaN.
  179. coneCosAngle = std::min((Real)1 / Normalize(coneAxis), (Real)1);
  180. coneAngle = std::acos(coneCosAngle);
  181. mNumPoints = 0;
  182. mPoints = nullptr;
  183. return result;
  184. }
  185. private:
  186. void ComputeInitialCone(Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneCosAngle)
  187. {
  188. // Compute the average of the sample points.
  189. Vector<3, Real> center{ (Real)0, (Real)0, (Real)0 };
  190. Real const invNumPoints = (Real)1 / (Real)mNumPoints;
  191. for (int i = 0; i < mNumPoints; ++i)
  192. {
  193. center += mPoints[i];
  194. }
  195. center *= invNumPoints;
  196. // The cone axis is estimated from ZZTZ (see the PDF).
  197. coneAxis = { (Real)0, (Real)0, (Real)0 };
  198. for (int i = 0; i < mNumPoints; ++i)
  199. {
  200. Vector<3, Real> diff = mPoints[i] - center;
  201. coneAxis += Dot(diff, diff) * diff;
  202. }
  203. coneAxis *= invNumPoints;
  204. Normalize(coneAxis);
  205. // Compute the averages of powers and products of powers of
  206. // a[i] = Dot(U,X[i]-C) and b[i] = Dot(X[i]-C,X[i]-C).
  207. Real c10 = (Real)0, c20 = (Real)0, c30 = (Real)0, c01 = (Real)0;
  208. Real c02 = (Real)0, c11 = (Real)0, c21 = (Real)0;
  209. for (int i = 0; i < mNumPoints; ++i)
  210. {
  211. Vector<3, Real> diff = mPoints[i] - center;
  212. Real ai = Dot(coneAxis, diff);
  213. Real aisqr = ai * ai;
  214. Real bi = Dot(diff, diff);
  215. c10 += ai;
  216. c20 += aisqr;
  217. c30 += aisqr * ai;
  218. c01 += bi;
  219. c02 += bi * bi;
  220. c11 += ai * bi;
  221. c21 += aisqr * bi;
  222. }
  223. c10 *= invNumPoints;
  224. c20 *= invNumPoints;
  225. c30 *= invNumPoints;
  226. c01 *= invNumPoints;
  227. c02 *= invNumPoints;
  228. c11 *= invNumPoints;
  229. c21 *= invNumPoints;
  230. // Compute the coefficients of p3(t) and q3(t).
  231. Real e0 = (Real)3 * c10;
  232. Real e1 = (Real)2 * c20 + c01;
  233. Real e2 = c11;
  234. Real e3 = (Real)3 * c20;
  235. Real e4 = c30;
  236. // Compute the coefficients of g(t).
  237. Real g0 = c11 * c21 - c02 * c30;
  238. Real g1 = c01 * c21 - (Real)3 * c02 * c20 + (Real)2 * (c20 * c21 - c11 * (c30 - c11));
  239. Real g2 = (Real)3 * (c11 * (c01 - c20) + c10 * (c21 - c02));
  240. Real g3 = c21 - c02 + c01 * (c01 + c20) + (Real)2 * (c10 * (c30 - c11) - c20 * c20);
  241. Real g4 = c30 - c11 + c10 * (c01 - c20);
  242. // Compute the roots of g(t) = 0.
  243. std::map<Real, int> rmMap;
  244. RootsPolynomial<Real>::SolveQuartic(g0, g1, g2, g3, g4, rmMap);
  245. // Locate the positive root t that leads to an s = cos(theta)
  246. // in (0,1) and that has minimum least-squares error. In theory,
  247. // there must always be such a root, but floating-point rounding
  248. // errors might lead to no such root. The implementation returns
  249. // the center as the estimate of V and pi/4 as the estimate of
  250. // the angle (s = 1/2).
  251. std::vector<std::array<Real, 3>> info;
  252. Real s, t;
  253. for (auto const& element : rmMap)
  254. {
  255. t = element.first;
  256. if (t > (Real)0)
  257. {
  258. Real p3 = e2 + t * (e1 + t * (e0 + t));
  259. if (p3 != (Real)0)
  260. {
  261. Real q3 = e4 + t * (e3 + t * (e0 + t));
  262. s = q3 / p3;
  263. if ((Real)0 < s && s < (Real)1)
  264. {
  265. Real error(0);
  266. for (int i = 0; i < mNumPoints; ++i)
  267. {
  268. Vector<3, Real> diff = mPoints[i] - center;
  269. Real ai = Dot(coneAxis, diff);
  270. Real bi = Dot(diff, diff);
  271. Real tpai = t + ai;
  272. Real Fi = s * (bi + t * ((Real)2 * ai + t)) - tpai * tpai;
  273. error += Fi * Fi;
  274. }
  275. error *= invNumPoints;
  276. std::array<Real, 3> item = { s, t, error };
  277. info.push_back(item);
  278. }
  279. }
  280. }
  281. }
  282. Real minError = std::numeric_limits<Real>::max();
  283. std::array<Real, 3> minItem = { (Real)0, (Real)0, minError };
  284. for (auto const& item : info)
  285. {
  286. if (item[2] < minError)
  287. {
  288. minItem = item;
  289. }
  290. }
  291. if (minItem[2] < std::numeric_limits<Real>::max())
  292. {
  293. // minItem = { minS, minT, minError }
  294. coneVertex = center - minItem[1] * coneAxis;
  295. coneCosAngle = std::sqrt(minItem[0]);
  296. }
  297. else
  298. {
  299. coneVertex = center;
  300. coneCosAngle = (Real)GTE_C_INV_SQRT_2;
  301. }
  302. }
  303. int mNumPoints;
  304. Vector<3, Real> const* mPoints;
  305. std::function<void(GVector<Real> const&, GVector<Real>&)> mFFunction;
  306. std::function<void(GVector<Real> const&, GMatrix<Real>&)> mJFunction;
  307. };
  308. }