ApprTorus3.h 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  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/ArbitraryPrecision.h>
  9. #include <Mathematics/ApprOrthogonalPlane3.h>
  10. #include <Mathematics/RootsPolynomial.h>
  11. #include <Mathematics/GaussNewtonMinimizer.h>
  12. #include <Mathematics/LevenbergMarquardtMinimizer.h>
  13. // Let the torus center be C with plane of symmetry containing C and having
  14. // directions D0 and D1. The axis of symmetry is the line containing C and
  15. // having direction N (the plane normal). The radius from the center of the
  16. // torus is r0 and the radius of the tube of the torus is r1. A point P may
  17. // be written as P = C + x*D0 + y*D1 + z*N, where matrix [D0 D1 N] is
  18. // orthogonal and has determinant 1. Thus, x = Dot(D0,P-C), y = Dot(D1,P-C)
  19. // and z = Dot(N,P-C). The implicit equation defining the torus is
  20. // (|P-C|^2 + r0^2 - r1^2)^2 - 4*r0^2*(|P-C|^2 - (Dot(N,P-C))^2) = 0
  21. // Observe that D0 and D1 are not present in the equation, which is to be
  22. // expected by the symmetry.
  23. //
  24. // Define u = r0^2 and v = r0^2 - r1^2. Define
  25. // F(X;C,N,u,v) = (|P-C|^2 + v)^2 - 4*u*(|P-C|^2 - (Dot(N,P-C))^2)
  26. // The nonlinear least-squares fitting of points {X[i]}_{i=0}^{n-1} computes
  27. // C, N, u and v to minimize the error function
  28. // E(C,N,u,v) = sum_{i=0}^{n-1} F(X[i];C,N,u,v)^2
  29. // When the sample points are distributed so that there is large coverage
  30. // by a purported fitted torus, a variation on fitting is the following.
  31. // Compute the least-squares plane with origin C and normal N that fits the
  32. // points. Define G(X;u,v) = F(X;C,N,u,v); the only variables now are u and
  33. // v. Define L[i] = |X[i]-C|^2 and S[i] = 4 * (L[i] - (Dot(N,X[i]-C))^2).
  34. // Define the error function
  35. // H(u,v) = sum_{i=0}^{n-1} G(X[i];u,v)^2
  36. // = sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u)^2
  37. // The first-order partial derivatives are
  38. // dH/du = -2 sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u) * S[i]
  39. // dH/dv = 4 sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u) * (v + L[i])
  40. // Setting these to zero and expanding the terms, we have
  41. // 0 = a2 * v^2 + a1 * v + a0 - b0 * u
  42. // 0 = c3 * v^3 + c2 * v^2 + c1 * v + c0 - u * (d1 * v + d0)
  43. // where a2 = sum(S[i]), a1 = 2*sum(S[i]*L[i]), a2 = sum(S[i]*L[i]^2),
  44. // b0 = sum(S[i]^2), c3 = sum(1) = n, c2 = 3*sum(L[i]), c1 = 3*sum(L[i]^2),
  45. // c0 = sum(L[i]^3), d1 = sum(S[i]) = a2 and d0 = sum(S[i]*L[i]) = a1/2.
  46. // The first equation is solved for
  47. // u = (a2 * v^2 + a1 * v + a0) / b0 = e2 * v^2 + e1 * v + e0
  48. // and substituted into the second equation to obtain a cubic polynomial
  49. // equation
  50. // 0 = f3 * v^3 + f2 * v^2 + f1 * v + f0
  51. // where f3 = c3 - d1 * e2, f2 = c2 - d1 * e1 - d0 * e2,
  52. // f1 = c1 - d1 * e0 - d0 * e1 and f0 = c0 - d0 * e0. The positive v-roots
  53. // are computed. For each root compute the corresponding u. For all pairs
  54. // (u,v) with u > v > 0, evaluate H(u,v) and choose the pair that minimizes
  55. // H(u,v). The torus radii are r0 = sqrt(u) and r1 = sqrt(u - v).
  56. namespace WwiseGTE
  57. {
  58. template <typename Real>
  59. class ApprTorus3
  60. {
  61. public:
  62. ApprTorus3()
  63. {
  64. // The unit-length normal is
  65. // N = (cos(theta)*sin(phi), sin(theta)*sin(phi), cos(phi)
  66. // for theta in [0,2*pi) and phi in [0,*pi). The radii are
  67. // encoded as
  68. // u = r0^2, v = r0^2 - r1^2
  69. // with 0 < v < u. Let D = C - X[i] where X[i] is a sample point.
  70. // The parameters P = (C0,C1,C2,theta,phi,u,v).
  71. // F[i](C,theta,phi,u,v) =
  72. // (|D|^2 + v)^2 - 4*u*(|D|^2 - Dot(N,D)^2)
  73. mFFunction = [this](GVector<Real> const& P, GVector<Real>& F)
  74. {
  75. Real csTheta = std::cos(P[3]);
  76. Real snTheta = std::sin(P[3]);
  77. Real csPhi = std::cos(P[4]);
  78. Real snPhi = std::sin(P[4]);
  79. Vector<3, Real> C = { P[0], P[1], P[2] };
  80. Vector<3, Real> N = { csTheta * snPhi, snTheta * snPhi, csPhi };
  81. Real u = P[5];
  82. Real v = P[6];
  83. for (int i = 0; i < mNumPoints; ++i)
  84. {
  85. Vector<3, Real> D = C - mPoints[i];
  86. Real DdotD = Dot(D, D), NdotD = Dot(N, D);
  87. Real sum = DdotD + v;
  88. F[i] = sum * sum - (Real)4 * u * (DdotD - NdotD * NdotD);
  89. }
  90. };
  91. // dF[i]/dC = 4 * (|D|^2 + v) * D - 8 * u * (I - N*N^T) * D
  92. // dF[i]/dTheta = 8 * u * Dot(dN/dTheta, D)
  93. // dF[i]/dPhi = 8 * u * Dot(dN/dPhi, D)
  94. // dF[i]/du = -4 * u * (|D|^2 - Dot(N,D)^2)
  95. // dF[i]/dv = 2 * (|D|^2 + v)
  96. mJFunction = [this](GVector<Real> const& P, GMatrix<Real>& J)
  97. {
  98. Real const r2(2), r4(4), r8(8);
  99. Real csTheta = std::cos(P[3]);
  100. Real snTheta = std::sin(P[3]);
  101. Real csPhi = std::cos(P[4]);
  102. Real snPhi = std::sin(P[4]);
  103. Vector<3, Real> C = { P[0], P[1], P[2] };
  104. Vector<3, Real> N = { csTheta * snPhi, snTheta * snPhi, csPhi };
  105. Real u = P[5];
  106. Real v = P[6];
  107. for (int row = 0; row < mNumPoints; ++row)
  108. {
  109. Vector<3, Real> D = C - mPoints[row];
  110. Real DdotD = Dot(D, D), NdotD = Dot(N, D);
  111. Real sum = DdotD + v;
  112. Vector<3, Real> dNdTheta{ -snTheta * snPhi, csTheta * snPhi, (Real)0 };
  113. Vector<3, Real> dNdPhi{ csTheta * csPhi, snTheta * csPhi, -snPhi };
  114. Vector<3, Real> temp = r4 * sum * D - r8 * u * (D - NdotD * N);
  115. J(row, 0) = temp[0];
  116. J(row, 1) = temp[1];
  117. J(row, 2) = temp[2];
  118. J(row, 3) = r8 * u * Dot(dNdTheta, D);
  119. J(row, 4) = r8 * u * Dot(dNdPhi, D);
  120. J(row, 5) = -r4 * u * (DdotD - NdotD * NdotD);
  121. J(row, 6) = r2 * sum;
  122. }
  123. };
  124. }
  125. // When the samples are distributed approximately uniformly near a
  126. // torus, use this method. For example, if the purported torus has
  127. // center (0,0,0) and normal (0,0,1), you want the (x,y,z) samples
  128. // to occur in all 8 octants. If the samples occur, say, only in
  129. // one octant, this method will estimate a C and N that are nowhere
  130. // near (0,0,0) and (0,0,1). The function sets the output variables
  131. // C, N, r0 and r1 as the fitted torus.
  132. //
  133. // The return value is a pair <bool,Real>. The first element is
  134. // 'true' when the estimate is valid, in which case the second
  135. // element is the least-squares error for that estimate. If any
  136. // unexpected condition occurs that prevents computing an estimate,
  137. // the first element is 'false' and the second element is
  138. // std::numeric_limits<Real>::max().
  139. std::pair<bool, Real>
  140. operator()(int numPoints, Vector<3, Real> const* points,
  141. Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) const
  142. {
  143. ApprOrthogonalPlane3<Real> fitter;
  144. if (!fitter.Fit(numPoints, points))
  145. {
  146. return std::make_pair(false, std::numeric_limits<Real>::max());
  147. }
  148. C = fitter.GetParameters().first;
  149. N = fitter.GetParameters().second;
  150. Real const zero(0);
  151. Real a0 = zero, a1 = zero, a2 = zero, b0 = zero;
  152. Real c0 = zero, c1 = zero, c2 = zero, c3 = (Real)numPoints;
  153. for (int i = 0; i < numPoints; ++i)
  154. {
  155. Vector<3, Real> delta = points[i] - C;
  156. Real dot = Dot(N, delta);
  157. Real L = Dot(delta, delta), L2 = L * L, L3 = L * L2;
  158. Real S = (Real)4 * (L - dot * dot), S2 = S * S;
  159. a2 += S;
  160. a1 += S * L;
  161. a0 += S * L2;
  162. b0 += S2;
  163. c2 += L;
  164. c1 += L2;
  165. c0 += L3;
  166. }
  167. Real d1 = a2;
  168. Real d0 = a1;
  169. a1 *= (Real)2;
  170. c2 *= (Real)3;
  171. c1 *= (Real)3;
  172. Real invB0 = (Real)1 / b0;
  173. Real e0 = a0 * invB0;
  174. Real e1 = a1 * invB0;
  175. Real e2 = a2 * invB0;
  176. Rational f0 = c0 - d0 * e0;
  177. Rational f1 = c1 - d1 * e0 - d0 * e1;
  178. Rational f2 = c2 - d1 * e1 - d0 * e2;
  179. Rational f3 = c3 - d1 * e2;
  180. std::map<Real, int> rmMap;
  181. RootsPolynomial<Real>::SolveCubic(f0, f1, f2, f3, rmMap);
  182. Real hmin = std::numeric_limits<Real>::max();
  183. Real umin = zero, vmin = zero;
  184. for (auto const& element : rmMap)
  185. {
  186. Real v = element.first;
  187. if (v > zero)
  188. {
  189. Real u = e0 + v * (e1 + v * e2);
  190. if (u > v)
  191. {
  192. Real h = zero;
  193. for (int i = 0; i < numPoints; ++i)
  194. {
  195. Vector<3, Real> delta = points[i] - C;
  196. Real dot = Dot(N, delta);
  197. Real L = Dot(delta, delta);
  198. Real S = (Real)4 * (L - dot * dot);
  199. Real sum = v + L;
  200. Real term = sum * sum - S * u;
  201. h += term * term;
  202. }
  203. if (h < hmin)
  204. {
  205. hmin = h;
  206. umin = u;
  207. vmin = v;
  208. }
  209. }
  210. }
  211. }
  212. if (hmin == std::numeric_limits<Real>::max())
  213. {
  214. return std::make_pair(false, std::numeric_limits<Real>::max());
  215. }
  216. r0 = std::sqrt(umin);
  217. r1 = std::sqrt(umin - vmin);
  218. return std::make_pair(true, hmin);
  219. }
  220. // If you want to specify that C, N, r0 and r1 are the initial guesses
  221. // for the minimizer, set the parameter useTorusInputAsInitialGuess to
  222. // 'true'. If you want the function to compute initial guesses, set
  223. // useTorusInputAsInitialGuess to 'false'. A Gauss-Newton minimizer
  224. // is used to fit a torus using nonlinear least-squares. The fitted
  225. // torus is returned in C, N, r0 and r1. See GteGaussNewtonMinimizer.h
  226. // for a description of the least-squares algorithm and the parameters
  227. // that it requires.
  228. typename GaussNewtonMinimizer<Real>::Result
  229. operator()(int numPoints, Vector<3, Real> const* points,
  230. size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance,
  231. bool useTorusInputAsInitialGuess,
  232. Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) const
  233. {
  234. mNumPoints = numPoints;
  235. mPoints = points;
  236. GaussNewtonMinimizer<Real> minimizer(7, mNumPoints, mFFunction, mJFunction);
  237. if (!useTorusInputAsInitialGuess)
  238. {
  239. operator()(numPoints, points, C, N, r0, r1);
  240. }
  241. GVector<Real> initial(7);
  242. // The initial guess for the plane origin.
  243. initial[0] = C[0];
  244. initial[1] = C[1];
  245. initial[2] = C[2];
  246. // The initial guess for the plane normal. The angles must be
  247. // extracted for spherical coordinates.
  248. if (std::fabs(N[2]) < (Real)1)
  249. {
  250. initial[3] = std::atan2(N[1], N[0]);
  251. initial[4] = std::acos(N[2]);
  252. }
  253. else
  254. {
  255. initial[3] = (Real)0;
  256. initial[4] = (Real)0;
  257. }
  258. // The initial guess for the radii-related parameters.
  259. initial[5] = r0 * r0;
  260. initial[6] = initial[5] - r1 * r1;
  261. auto result = minimizer(initial, maxIterations, updateLengthTolerance,
  262. errorDifferenceTolerance);
  263. // No test is made for result.converged so that we return some
  264. // estimates of the torus. The caller can decide how to respond
  265. // when result.converged is false.
  266. C[0] = result.minLocation[0];
  267. C[1] = result.minLocation[1];
  268. C[2] = result.minLocation[2];
  269. Real theta = result.minLocation[3];
  270. Real phi = result.minLocation[4];
  271. Real csTheta = std::cos(theta);
  272. Real snTheta = std::sin(theta);
  273. Real csPhi = std::cos(phi);
  274. Real snPhi = std::sin(phi);
  275. N[0] = csTheta * snPhi;
  276. N[1] = snTheta * snPhi;
  277. N[2] = csPhi;
  278. Real u = result.minLocation[5];
  279. Real v = result.minLocation[6];
  280. r0 = std::sqrt(u);
  281. r1 = std::sqrt(u - v);
  282. mNumPoints = 0;
  283. mPoints = nullptr;
  284. return result;
  285. }
  286. // If you want to specify that C, N, r0 and r1 are the initial guesses
  287. // for the minimizer, set the parameter useTorusInputAsInitialGuess to
  288. // 'true'. If you want the function to compute initial guesses, set
  289. // useTorusInputAsInitialGuess to 'false'. A Gauss-Newton minimizer
  290. // is used to fit a torus using nonlinear least-squares. The fitted
  291. // torus is returned in C, N, r0 and r1. See GteGaussNewtonMinimizer.h
  292. // for a description of the least-squares algorithm and the parameters
  293. // that it requires.
  294. typename LevenbergMarquardtMinimizer<Real>::Result
  295. operator()(int numPoints, Vector<3, Real> const* points,
  296. size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance,
  297. Real lambdaFactor, Real lambdaAdjust, size_t maxAdjustments,
  298. bool useTorusInputAsInitialGuess,
  299. Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) const
  300. {
  301. mNumPoints = numPoints;
  302. mPoints = points;
  303. LevenbergMarquardtMinimizer<Real> minimizer(7, mNumPoints, mFFunction, mJFunction);
  304. if (!useTorusInputAsInitialGuess)
  305. {
  306. operator()(numPoints, points, C, N, r0, r1);
  307. }
  308. GVector<Real> initial(7);
  309. // The initial guess for the plane origin.
  310. initial[0] = C[0];
  311. initial[1] = C[1];
  312. initial[2] = C[2];
  313. // The initial guess for the plane normal. The angles must be
  314. // extracted for spherical coordinates.
  315. if (std::fabs(N[2]) < (Real)1)
  316. {
  317. initial[3] = std::atan2(N[1], N[0]);
  318. initial[4] = std::acos(N[2]);
  319. }
  320. else
  321. {
  322. initial[3] = (Real)0;
  323. initial[4] = (Real)0;
  324. }
  325. // The initial guess for the radii-related parameters.
  326. initial[5] = r0 * r0;
  327. initial[6] = initial[5] - r1 * r1;
  328. auto result = minimizer(initial, maxIterations, updateLengthTolerance,
  329. errorDifferenceTolerance, lambdaFactor, lambdaAdjust, maxAdjustments);
  330. // No test is made for result.converged so that we return some
  331. // estimates of the torus. The caller can decide how to respond
  332. // when result.converged is false.
  333. C[0] = result.minLocation[0];
  334. C[1] = result.minLocation[1];
  335. C[2] = result.minLocation[2];
  336. Real theta = result.minLocation[3];
  337. Real phi = result.minLocation[4];
  338. Real csTheta = std::cos(theta);
  339. Real snTheta = std::sin(theta);
  340. Real csPhi = std::cos(phi);
  341. Real snPhi = std::sin(phi);
  342. N[0] = csTheta * snPhi;
  343. N[1] = snTheta * snPhi;
  344. N[2] = csPhi;
  345. Real u = result.minLocation[5];
  346. Real v = result.minLocation[6];
  347. r0 = std::sqrt(u);
  348. r1 = std::sqrt(u - v);
  349. mNumPoints = 0;
  350. mPoints = nullptr;
  351. return result;
  352. }
  353. private:
  354. typedef BSRational<UIntegerAP32> Rational;
  355. mutable int mNumPoints;
  356. mutable Vector<3, Real> const* mPoints;
  357. std::function<void(GVector<Real> const&, GVector<Real>&)> mFFunction;
  358. std::function<void(GVector<Real> const&, GMatrix<Real>&)> mJFunction;
  359. };
  360. }