ApprCylinder3.h 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  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/Cylinder3.h>
  9. #include <Mathematics/Matrix3x3.h>
  10. #include <Mathematics/SymmetricEigensolver3x3.h>
  11. #include <vector>
  12. #include <thread>
  13. // The algorithm for least-squares fitting of a point set by a cylinder is
  14. // described in
  15. // https://www.geometrictools.com/Documentation/CylinderFitting.pdf
  16. // This document shows how to compute the cylinder radius r and the cylinder
  17. // axis as a line C+t*W with origin C, unit-length direction W, and any
  18. // real-valued t. The implementation here adds one addition step. It
  19. // projects the point set onto the cylinder axis, computes the bounding
  20. // t-interval [tmin,tmax] for the projections, and sets the cylinder center
  21. // to C + 0.5*(tmin+tmax)*W and the cylinder height to tmax-tmin.
  22. namespace WwiseGTE
  23. {
  24. template <typename Real>
  25. class ApprCylinder3
  26. {
  27. public:
  28. // Search the hemisphere for a minimum, choose numThetaSamples and
  29. // numPhiSamples to be positive (and preferably large). These are
  30. // used to generate a hemispherical grid of samples to be evaluated
  31. // to find the cylinder axis-direction W. If the grid samples is
  32. // quite large and the number of points to be fitted is large, you
  33. // most likely will want to run multithreaded. Set numThreads to 0
  34. // to run single-threaded in the main process. Set numThreads > 0 to
  35. // run multithreaded. If either of numThetaSamples or numPhiSamples
  36. // is zero, the operator() sets the cylinder origin and axis to the
  37. // zero vectors, the radius and height to zero, and returns
  38. // std::numeric_limits<Real>::max().
  39. ApprCylinder3(unsigned int numThreads, unsigned int numThetaSamples, unsigned int numPhiSamples)
  40. :
  41. mConstructorType(FIT_BY_HEMISPHERE_SEARCH),
  42. mNumThreads(numThreads),
  43. mNumThetaSamples(numThetaSamples),
  44. mNumPhiSamples(numPhiSamples),
  45. mEigenIndex(0),
  46. mInvNumPoints((Real)0)
  47. {
  48. mCylinderAxis = { (Real)0, (Real)0, (Real)0 };
  49. }
  50. // Choose one of the eigenvectors for the covariance matrix as the
  51. // cylinder axis direction. If eigenIndex is 0, the eigenvector
  52. // associated with the smallest eigenvalue is chosen. If eigenIndex
  53. // is 2, the eigenvector associated with the largest eigenvalue is
  54. // chosen. If eigenIndex is 1, the eigenvector associated with the
  55. // median eigenvalue is chosen; keep in mind that this could be the
  56. // minimum or maximum eigenvalue if the eigenspace has dimension 2
  57. // or 3. If eigenIndex is 3 or larger, the operator() sets the
  58. // cylinder origin and axis to the zero vectors, the radius and height
  59. // to zero, and returns std::numeric_limits<Real>::max().
  60. ApprCylinder3(unsigned int eigenIndex)
  61. :
  62. mConstructorType(FIT_USING_COVARIANCE_EIGENVECTOR),
  63. mNumThreads(0),
  64. mNumThetaSamples(0),
  65. mNumPhiSamples(0),
  66. mEigenIndex(eigenIndex),
  67. mInvNumPoints((Real)0)
  68. {
  69. mCylinderAxis = { (Real)0, (Real)0, (Real)0 };
  70. }
  71. // Choose the cylinder axis. If cylinderAxis is not the zero vector,
  72. // the constructor will normalize it. If cylinderAxis is the zero
  73. // vector, the operator() sets the cylinder origin and axis to the
  74. // zero vectors, the radius and height to zero, and returns
  75. // std::numeric_limits<Real>::max().
  76. ApprCylinder3(Vector3<Real> const& cylinderAxis)
  77. :
  78. mConstructorType(FIT_USING_SPECIFIED_AXIS),
  79. mNumThreads(0),
  80. mNumThetaSamples(0),
  81. mNumPhiSamples(0),
  82. mEigenIndex(0),
  83. mCylinderAxis(cylinderAxis),
  84. mInvNumPoints((Real)0)
  85. {
  86. Normalize(mCylinderAxis, true);
  87. }
  88. // The algorithm must estimate 6 parameters, so the number of points
  89. // must be at least 6 but preferably larger. The returned value is
  90. // the root-mean-square of the least-squares error. If numPoints is
  91. // less than 6 or if points is a null pointer, the operator() sets the
  92. // cylinder origin and axis to the zero vectors, the radius and height
  93. // to zero, and returns std::numeric_limits<Real>::max().
  94. Real operator()(unsigned int numPoints, Vector3<Real> const* points, Cylinder3<Real>& cylinder)
  95. {
  96. mX.clear();
  97. mInvNumPoints = (Real)0;
  98. cylinder.axis.origin = Vector3<Real>::Zero();
  99. cylinder.axis.direction = Vector3<Real>::Zero();
  100. cylinder.radius = (Real)0;
  101. cylinder.height = (Real)0;
  102. // Validate the input parameters.
  103. if (numPoints < 6 || !points)
  104. {
  105. return std::numeric_limits<Real>::max();
  106. }
  107. Vector3<Real> average;
  108. Preprocess(numPoints, points, average);
  109. // Fit the points based on which constructor the caller used. The
  110. // direction is either estimated or selected directly or
  111. // indirectly by the caller. The center and squared radius are
  112. // estimated.
  113. Vector3<Real> minPC, minW;
  114. Real minRSqr, minError;
  115. if (mConstructorType == FIT_BY_HEMISPHERE_SEARCH)
  116. {
  117. // Validate the relevant internal parameters.
  118. if (mNumThetaSamples == 0 || mNumPhiSamples == 0)
  119. {
  120. return std::numeric_limits<Real>::max();
  121. }
  122. // Search the hemisphere for the vector that leads to minimum
  123. // error and use it for the cylinder axis.
  124. if (mNumThreads == 0)
  125. {
  126. // Execute the algorithm in the main process.
  127. minError = ComputeSingleThreaded(minPC, minW, minRSqr);
  128. }
  129. else
  130. {
  131. // Execute the algorithm in multiple threads.
  132. minError = ComputeMultiThreaded(minPC, minW, minRSqr);
  133. }
  134. }
  135. else if (mConstructorType == FIT_USING_COVARIANCE_EIGENVECTOR)
  136. {
  137. // Validate the relevant internal parameters.
  138. if (mEigenIndex >= 3)
  139. {
  140. return std::numeric_limits<Real>::max();
  141. }
  142. // Use the eigenvector corresponding to the mEigenIndex of the
  143. // eigenvectors of the covariance matrix as the cylinder axis
  144. // direction. The eigenvectors are sorted from smallest
  145. // eigenvalue (mEigenIndex = 0) to largest eigenvalue
  146. // (mEigenIndex = 2).
  147. minError = ComputeUsingCovariance(minPC, minW, minRSqr);
  148. }
  149. else // mConstructorType == FIT_USING_SPECIFIED_AXIS
  150. {
  151. // Validate the relevant internal parameters.
  152. if (mCylinderAxis == Vector3<Real>::Zero())
  153. {
  154. return std::numeric_limits<Real>::max();
  155. }
  156. minError = ComputeUsingDirection(minPC, minW, minRSqr);
  157. }
  158. // Translate back to the original space by the average of the
  159. // points.
  160. cylinder.axis.origin = minPC + average;
  161. cylinder.axis.direction = minW;
  162. // Compute the cylinder radius.
  163. cylinder.radius = std::sqrt(minRSqr);
  164. // Project the points onto the cylinder axis and choose the
  165. // cylinder center and cylinder height as described in the
  166. // comments at the top of this header file.
  167. Real tmin = (Real)0, tmax = (Real)0;
  168. for (unsigned int i = 0; i < numPoints; ++i)
  169. {
  170. Real t = Dot(cylinder.axis.direction, points[i] - cylinder.axis.origin);
  171. tmin = std::min(t, tmin);
  172. tmax = std::max(t, tmax);
  173. }
  174. cylinder.axis.origin += ((tmin + tmax) * (Real)0.5) * cylinder.axis.direction;
  175. cylinder.height = tmax - tmin;
  176. return minError;
  177. }
  178. private:
  179. enum ConstructorType
  180. {
  181. FIT_BY_HEMISPHERE_SEARCH,
  182. FIT_USING_COVARIANCE_EIGENVECTOR,
  183. FIT_USING_SPECIFIED_AXIS
  184. };
  185. void Preprocess(unsigned int numPoints, Vector3<Real> const* points, Vector3<Real>& average)
  186. {
  187. mX.resize(numPoints);
  188. mInvNumPoints = (Real)1 / (Real)numPoints;
  189. // Copy the points and translate by the average for numerical
  190. // robustness.
  191. average.MakeZero();
  192. for (unsigned int i = 0; i < numPoints; ++i)
  193. {
  194. average += points[i];
  195. }
  196. average *= mInvNumPoints;
  197. for (unsigned int i = 0; i < numPoints; ++i)
  198. {
  199. mX[i] = points[i] - average;
  200. }
  201. Vector<6, Real> zero{ (Real)0 };
  202. std::vector<Vector<6, Real>> products(mX.size(), zero);
  203. mMu = zero;
  204. for (size_t i = 0; i < mX.size(); ++i)
  205. {
  206. products[i][0] = mX[i][0] * mX[i][0];
  207. products[i][1] = mX[i][0] * mX[i][1];
  208. products[i][2] = mX[i][0] * mX[i][2];
  209. products[i][3] = mX[i][1] * mX[i][1];
  210. products[i][4] = mX[i][1] * mX[i][2];
  211. products[i][5] = mX[i][2] * mX[i][2];
  212. mMu[0] += products[i][0];
  213. mMu[1] += (Real)2 * products[i][1];
  214. mMu[2] += (Real)2 * products[i][2];
  215. mMu[3] += products[i][3];
  216. mMu[4] += (Real)2 * products[i][4];
  217. mMu[5] += products[i][5];
  218. }
  219. mMu *= mInvNumPoints;
  220. mF0.MakeZero();
  221. mF1.MakeZero();
  222. mF2.MakeZero();
  223. for (size_t i = 0; i < mX.size(); ++i)
  224. {
  225. Vector<6, Real> delta;
  226. delta[0] = products[i][0] - mMu[0];
  227. delta[1] = (Real)2 * products[i][1] - mMu[1];
  228. delta[2] = (Real)2 * products[i][2] - mMu[2];
  229. delta[3] = products[i][3] - mMu[3];
  230. delta[4] = (Real)2 * products[i][4] - mMu[4];
  231. delta[5] = products[i][5] - mMu[5];
  232. mF0(0, 0) += products[i][0];
  233. mF0(0, 1) += products[i][1];
  234. mF0(0, 2) += products[i][2];
  235. mF0(1, 1) += products[i][3];
  236. mF0(1, 2) += products[i][4];
  237. mF0(2, 2) += products[i][5];
  238. mF1 += OuterProduct(mX[i], delta);
  239. mF2 += OuterProduct(delta, delta);
  240. }
  241. mF0 *= mInvNumPoints;
  242. mF0(1, 0) = mF0(0, 1);
  243. mF0(2, 0) = mF0(0, 2);
  244. mF0(2, 1) = mF0(1, 2);
  245. mF1 *= mInvNumPoints;
  246. mF2 *= mInvNumPoints;
  247. }
  248. Real ComputeUsingDirection(Vector3<Real>& minPC, Vector3<Real>& minW, Real& minRSqr)
  249. {
  250. minW = mCylinderAxis;
  251. return G(minW, minPC, minRSqr);
  252. }
  253. Real ComputeUsingCovariance(Vector3<Real>& minPC, Vector3<Real>& minW, Real& minRSqr)
  254. {
  255. Matrix3x3<Real> covar = Matrix3x3<Real>::Zero();
  256. for (auto const& X : mX)
  257. {
  258. covar += OuterProduct(X, X);
  259. }
  260. covar *= mInvNumPoints;
  261. std::array<Real, 3> eval;
  262. std::array<std::array<Real, 3>, 3> evec;
  263. SymmetricEigensolver3x3<Real>()(
  264. covar(0, 0), covar(0, 1), covar(0, 2), covar(1, 1), covar(1, 2), covar(2, 2),
  265. true, +1, eval, evec);
  266. minW = evec[mEigenIndex];
  267. return G(minW, minPC, minRSqr);
  268. }
  269. Real ComputeSingleThreaded(Vector3<Real>& minPC, Vector3<Real>& minW, Real& minRSqr)
  270. {
  271. Real const iMultiplier = (Real)GTE_C_TWO_PI / (Real)mNumThetaSamples;
  272. Real const jMultiplier = (Real)GTE_C_HALF_PI / (Real)mNumPhiSamples;
  273. // Handle the north pole (0,0,1) separately.
  274. minW = { (Real)0, (Real)0, (Real)1 };
  275. Real minError = G(minW, minPC, minRSqr);
  276. for (unsigned int j = 1; j <= mNumPhiSamples; ++j)
  277. {
  278. Real phi = jMultiplier * static_cast<Real>(j); // in [0,pi/2]
  279. Real csphi = std::cos(phi);
  280. Real snphi = std::sin(phi);
  281. for (unsigned int i = 0; i < mNumThetaSamples; ++i)
  282. {
  283. Real theta = iMultiplier * static_cast<Real>(i); // in [0,2*pi)
  284. Real cstheta = std::cos(theta);
  285. Real sntheta = std::sin(theta);
  286. Vector3<Real> W{ cstheta * snphi, sntheta * snphi, csphi };
  287. Vector3<Real> PC;
  288. Real rsqr;
  289. Real error = G(W, PC, rsqr);
  290. if (error < minError)
  291. {
  292. minError = error;
  293. minRSqr = rsqr;
  294. minW = W;
  295. minPC = PC;
  296. }
  297. }
  298. }
  299. return minError;
  300. }
  301. Real ComputeMultiThreaded(Vector3<Real>& minPC, Vector3<Real>& minW, Real& minRSqr)
  302. {
  303. Real const iMultiplier = (Real)GTE_C_TWO_PI / (Real)mNumThetaSamples;
  304. Real const jMultiplier = (Real)GTE_C_HALF_PI / (Real)mNumPhiSamples;
  305. // Handle the north pole (0,0,1) separately.
  306. minW = { (Real)0, (Real)0, (Real)1 };
  307. Real minError = G(minW, minPC, minRSqr);
  308. struct Local
  309. {
  310. Real error;
  311. Real rsqr;
  312. Vector3<Real> W;
  313. Vector3<Real> PC;
  314. unsigned int jmin;
  315. unsigned int jmax;
  316. };
  317. std::vector<Local> local(mNumThreads);
  318. unsigned int numPhiSamplesPerThread = mNumPhiSamples / mNumThreads;
  319. for (unsigned int t = 0; t < mNumThreads; ++t)
  320. {
  321. local[t].error = std::numeric_limits<Real>::max();
  322. local[t].rsqr = (Real)0;
  323. local[t].W = Vector3<Real>::Zero();
  324. local[t].PC = Vector3<Real>::Zero();
  325. local[t].jmin = numPhiSamplesPerThread * t;
  326. local[t].jmax = numPhiSamplesPerThread * (t + 1);
  327. }
  328. local[mNumThreads - 1].jmax = mNumPhiSamples + 1;
  329. std::vector<std::thread> process(mNumThreads);
  330. for (unsigned int t = 0; t < mNumThreads; ++t)
  331. {
  332. process[t] = std::thread
  333. (
  334. [this, t, iMultiplier, jMultiplier, &local]()
  335. {
  336. for (unsigned int j = local[t].jmin; j < local[t].jmax; ++j)
  337. {
  338. // phi in [0,pi/2]
  339. Real phi = jMultiplier * static_cast<Real>(j);
  340. Real csphi = std::cos(phi);
  341. Real snphi = std::sin(phi);
  342. for (unsigned int i = 0; i < mNumThetaSamples; ++i)
  343. {
  344. // theta in [0,2*pi)
  345. Real theta = iMultiplier * static_cast<Real>(i);
  346. Real cstheta = std::cos(theta);
  347. Real sntheta = std::sin(theta);
  348. Vector3<Real> W{ cstheta * snphi, sntheta * snphi, csphi };
  349. Vector3<Real> PC;
  350. Real rsqr;
  351. Real error = G(W, PC, rsqr);
  352. if (error < local[t].error)
  353. {
  354. local[t].error = error;
  355. local[t].rsqr = rsqr;
  356. local[t].W = W;
  357. local[t].PC = PC;
  358. }
  359. }
  360. }
  361. }
  362. );
  363. }
  364. for (unsigned int t = 0; t < mNumThreads; ++t)
  365. {
  366. process[t].join();
  367. if (local[t].error < minError)
  368. {
  369. minError = local[t].error;
  370. minRSqr = local[t].rsqr;
  371. minW = local[t].W;
  372. minPC = local[t].PC;
  373. }
  374. }
  375. return minError;
  376. }
  377. Real G(Vector3<Real> const& W, Vector3<Real>& PC, Real& rsqr)
  378. {
  379. Matrix3x3<Real> P = Matrix3x3<Real>::Identity() - OuterProduct(W, W);
  380. Matrix3x3<Real> S
  381. {
  382. (Real)0, -W[2], W[1],
  383. W[2], (Real)0, -W[0],
  384. -W[1], W[0], (Real)0
  385. };
  386. Matrix<3, 3, Real> A = P * mF0 * P;
  387. Matrix<3, 3, Real> hatA = -(S * A * S);
  388. Matrix<3, 3, Real> hatAA = hatA * A;
  389. Real trace = Trace(hatAA);
  390. Matrix<3, 3, Real> Q = hatA / trace;
  391. Vector<6, Real> pVec{ P(0, 0), P(0, 1), P(0, 2), P(1, 1), P(1, 2), P(2, 2) };
  392. Vector<3, Real> alpha = mF1 * pVec;
  393. Vector<3, Real> beta = Q * alpha;
  394. Real G = (Dot(pVec, mF2 * pVec) - (Real)4 * Dot(alpha, beta) + (Real)4 * Dot(beta, mF0 * beta)) / (Real)mX.size();
  395. PC = beta;
  396. rsqr = Dot(pVec, mMu) + Dot(PC, PC);
  397. return G;
  398. }
  399. ConstructorType mConstructorType;
  400. // Parameters for the hemisphere-search constructor.
  401. unsigned int mNumThreads;
  402. unsigned int mNumThetaSamples;
  403. unsigned int mNumPhiSamples;
  404. // Parameters for the eigenvector-index constructor.
  405. unsigned int mEigenIndex;
  406. // Parameters for the specified-axis constructor.
  407. Vector3<Real> mCylinderAxis;
  408. // A copy of the input points but translated by their average for
  409. // numerical robustness.
  410. std::vector<Vector3<Real>> mX;
  411. Real mInvNumPoints;
  412. // Preprocessed information that depends only on the sample points.
  413. // This allows precomputed summations so that G(...) can be evaluated
  414. // extremely fast.
  415. Vector<6, Real> mMu;
  416. Matrix<3, 3, Real> mF0;
  417. Matrix<3, 6, Real> mF1;
  418. Matrix<6, 6, Real> mF2;
  419. };
  420. }