DistLine3Circle3.h 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  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/DCPQuery.h>
  9. #include <Mathematics/Circle3.h>
  10. #include <Mathematics/Line.h>
  11. #include <Mathematics/RootsBisection.h>
  12. #include <Mathematics/RootsPolynomial.h>
  13. // The 3D line-circle distance algorithm is described in
  14. // https://www.geometrictools.com/Documentation/DistanceToCircle3.pdf
  15. // The notation used in the code matches that of the document.
  16. namespace WwiseGTE
  17. {
  18. template <typename Real>
  19. class DCPQuery<Real, Line3<Real>, Circle3<Real>>
  20. {
  21. public:
  22. // The possible number of closest line-circle pairs is 1, 2 or all
  23. // circle points. If 1 or 2, numClosestPairs is set to this number
  24. // and 'equidistant' is false; the number of valid elements in
  25. // lineClosest[] and circleClosest[] is numClosestPairs. If all
  26. // circle points are closest, the line must be C+t*N where C is the
  27. // circle center, N is the normal to the plane of the circle, and
  28. // lineClosest[0] is set to C. In this case, 'equidistant' is true
  29. // and circleClosest[0] is set to C+r*U, where r is the circle
  30. // and U is a vector perpendicular to N.
  31. struct Result
  32. {
  33. Real distance, sqrDistance;
  34. int numClosestPairs;
  35. Vector3<Real> lineClosest[2], circleClosest[2];
  36. bool equidistant;
  37. };
  38. // The polynomial-based algorithm. Type Real can be floating-point or
  39. // rational.
  40. Result operator()(Line3<Real> const& line, Circle3<Real> const& circle)
  41. {
  42. Result result;
  43. Vector3<Real> const vzero = Vector3<Real>::Zero();
  44. Real const zero = (Real)0;
  45. Vector3<Real> D = line.origin - circle.center;
  46. Vector3<Real> NxM = Cross(circle.normal, line.direction);
  47. Vector3<Real> NxD = Cross(circle.normal, D);
  48. Real t;
  49. if (NxM != vzero)
  50. {
  51. if (NxD != vzero)
  52. {
  53. Real NdM = Dot(circle.normal, line.direction);
  54. if (NdM != zero)
  55. {
  56. // H(t) = (a*t^2 + 2*b*t + c)*(t + d)^2
  57. // - r^2*(a*t + b)^2
  58. // = h0 + h1*t + h2*t^2 + h3*t^3 + h4*t^4
  59. Real a = Dot(NxM, NxM), b = Dot(NxM, NxD);
  60. Real c = Dot(NxD, NxD), d = Dot(line.direction, D);
  61. Real rsqr = circle.radius * circle.radius;
  62. Real asqr = a * a, bsqr = b * b, dsqr = d * d;
  63. Real h0 = c * dsqr - bsqr * rsqr;
  64. Real h1 = 2 * (c * d + b * dsqr - a * b * rsqr);
  65. Real h2 = c + 4 * b * d + a * dsqr - asqr * rsqr;
  66. Real h3 = 2 * (b + a * d);
  67. Real h4 = a;
  68. std::map<Real, int> rmMap;
  69. RootsPolynomial<Real>::template SolveQuartic<Real>(
  70. h0, h1, h2, h3, h4, rmMap);
  71. std::array<ClosestInfo, 4> candidates;
  72. int numRoots = 0;
  73. for (auto const& rm : rmMap)
  74. {
  75. t = rm.first;
  76. ClosestInfo info;
  77. Vector3<Real> NxDelta = NxD + t * NxM;
  78. if (NxDelta != vzero)
  79. {
  80. GetPair(line, circle, D, t, info.lineClosest,
  81. info.circleClosest);
  82. info.equidistant = false;
  83. }
  84. else
  85. {
  86. Vector3<Real> U = GetOrthogonal(circle.normal, true);
  87. info.lineClosest = circle.center;
  88. info.circleClosest =
  89. circle.center + circle.radius * U;
  90. info.equidistant = true;
  91. }
  92. Vector3<Real> diff = info.lineClosest - info.circleClosest;
  93. info.sqrDistance = Dot(diff, diff);
  94. candidates[numRoots++] = info;
  95. }
  96. std::sort(candidates.begin(), candidates.begin() + numRoots);
  97. result.numClosestPairs = 1;
  98. result.lineClosest[0] = candidates[0].lineClosest;
  99. result.circleClosest[0] = candidates[0].circleClosest;
  100. if (numRoots > 1
  101. && candidates[1].sqrDistance == candidates[0].sqrDistance)
  102. {
  103. result.numClosestPairs = 2;
  104. result.lineClosest[1] = candidates[1].lineClosest;
  105. result.circleClosest[1] = candidates[1].circleClosest;
  106. }
  107. }
  108. else
  109. {
  110. // The line is parallel to the plane of the circle.
  111. // The polynomial has the form
  112. // H(t) = (t+v)^2*[(t+v)^2-(r^2-u^2)].
  113. Real u = Dot(NxM, D), v = Dot(line.direction, D);
  114. Real discr = circle.radius * circle.radius - u * u;
  115. if (discr > zero)
  116. {
  117. result.numClosestPairs = 2;
  118. Real rootDiscr = std::sqrt(discr);
  119. t = -v + rootDiscr;
  120. GetPair(line, circle, D, t, result.lineClosest[0],
  121. result.circleClosest[0]);
  122. t = -v - rootDiscr;
  123. GetPair(line, circle, D, t, result.lineClosest[1],
  124. result.circleClosest[1]);
  125. }
  126. else
  127. {
  128. result.numClosestPairs = 1;
  129. t = -v;
  130. GetPair(line, circle, D, t, result.lineClosest[0],
  131. result.circleClosest[0]);
  132. }
  133. }
  134. }
  135. else
  136. {
  137. // The line is C+t*M, where M is not parallel to N. The
  138. // polynomial is
  139. // H(t) = |Cross(N,M)|^2*t^2*(t^2 - r^2*|Cross(N,M)|^2)
  140. // where root t = 0 does not correspond to the global
  141. // minimum. The other roots produce the global minimum.
  142. result.numClosestPairs = 2;
  143. t = circle.radius * Length(NxM);
  144. GetPair(line, circle, D, t, result.lineClosest[0],
  145. result.circleClosest[0]);
  146. t = -t;
  147. GetPair(line, circle, D, t, result.lineClosest[1],
  148. result.circleClosest[1]);
  149. }
  150. result.equidistant = false;
  151. }
  152. else
  153. {
  154. if (NxD != vzero)
  155. {
  156. // The line is A+t*N (perpendicular to plane) but with
  157. // A != C. The polyhomial is
  158. // H(t) = |Cross(N,D)|^2*(t + Dot(M,D))^2.
  159. result.numClosestPairs = 1;
  160. t = -Dot(line.direction, D);
  161. GetPair(line, circle, D, t, result.lineClosest[0],
  162. result.circleClosest[0]);
  163. result.equidistant = false;
  164. }
  165. else
  166. {
  167. // The line is C+t*N, so C is the closest point for the
  168. // line and all circle points are equidistant from it.
  169. Vector3<Real> U = GetOrthogonal(circle.normal, true);
  170. result.numClosestPairs = 1;
  171. result.lineClosest[0] = circle.center;
  172. result.circleClosest[0] = circle.center + circle.radius * U;
  173. result.equidistant = true;
  174. }
  175. }
  176. Vector3<Real> diff = result.lineClosest[0] - result.circleClosest[0];
  177. result.sqrDistance = Dot(diff, diff);
  178. result.distance = std::sqrt(result.sqrDistance);
  179. return result;
  180. }
  181. // The nonpolynomial-based algorithm that uses bisection. Because the
  182. // bisection is iterative, you should choose Real to be a
  183. // floating-point type. However, the algorithm will still work for a
  184. // rational type, but it is costly because of the increase in
  185. // arbitrary-size integers used during the bisection.
  186. Result Robust(Line3<Real> const& line, Circle3<Real> const& circle)
  187. {
  188. // The line is P(t) = B+t*M. The circle is |X-C| = r with
  189. // Dot(N,X-C)=0.
  190. Result result;
  191. Vector3<Real> vzero = Vector3<Real>::Zero();
  192. Real const zero = (Real)0;
  193. Vector3<Real> D = line.origin - circle.center;
  194. Vector3<Real> MxN = Cross(line.direction, circle.normal);
  195. Vector3<Real> DxN = Cross(D, circle.normal);
  196. Real m0sqr = Dot(MxN, MxN);
  197. if (m0sqr > zero)
  198. {
  199. // Compute the critical points s for F'(s) = 0.
  200. Real s, t;
  201. int numRoots = 0;
  202. std::array<Real, 3> roots;
  203. // The line direction M and the plane normal N are not
  204. // parallel. Move the line origin B = (b0,b1,b2) to
  205. // B' = B + lambda*line.direction = (0,b1',b2').
  206. Real m0 = std::sqrt(m0sqr);
  207. Real rm0 = circle.radius * m0;
  208. Real lambda = -Dot(MxN, DxN) / m0sqr;
  209. Vector3<Real> oldD = D;
  210. D += lambda * line.direction;
  211. DxN += lambda * MxN;
  212. Real m2b2 = Dot(line.direction, D);
  213. Real b1sqr = Dot(DxN, DxN);
  214. if (b1sqr > zero)
  215. {
  216. // B' = (0,b1',b2') where b1' != 0. See Sections 1.1.2
  217. // and 1.2.2 of the PDF documentation.
  218. Real b1 = std::sqrt(b1sqr);
  219. Real rm0sqr = circle.radius * m0sqr;
  220. if (rm0sqr > b1)
  221. {
  222. Real const twoThirds = (Real)2 / (Real)3;
  223. Real sHat = std::sqrt(std::pow(rm0sqr * b1sqr, twoThirds) - b1sqr) / m0;
  224. Real gHat = rm0sqr * sHat / std::sqrt(m0sqr * sHat * sHat + b1sqr);
  225. Real cutoff = gHat - sHat;
  226. if (m2b2 <= -cutoff)
  227. {
  228. s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, -m2b2 + rm0);
  229. roots[numRoots++] = s;
  230. if (m2b2 == -cutoff)
  231. {
  232. roots[numRoots++] = -sHat;
  233. }
  234. }
  235. else if (m2b2 >= cutoff)
  236. {
  237. s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, -m2b2);
  238. roots[numRoots++] = s;
  239. if (m2b2 == cutoff)
  240. {
  241. roots[numRoots++] = sHat;
  242. }
  243. }
  244. else
  245. {
  246. if (m2b2 <= zero)
  247. {
  248. s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, -m2b2 + rm0);
  249. roots[numRoots++] = s;
  250. s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, -sHat);
  251. roots[numRoots++] = s;
  252. }
  253. else
  254. {
  255. s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, -m2b2);
  256. roots[numRoots++] = s;
  257. s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, sHat, -m2b2 + rm0);
  258. roots[numRoots++] = s;
  259. }
  260. }
  261. }
  262. else
  263. {
  264. if (m2b2 < zero)
  265. {
  266. s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, -m2b2 + rm0);
  267. }
  268. else if (m2b2 > zero)
  269. {
  270. s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, -m2b2);
  271. }
  272. else
  273. {
  274. s = zero;
  275. }
  276. roots[numRoots++] = s;
  277. }
  278. }
  279. else
  280. {
  281. // The new line origin is B' = (0,0,b2').
  282. if (m2b2 < zero)
  283. {
  284. s = -m2b2 + rm0;
  285. roots[numRoots++] = s;
  286. }
  287. else if (m2b2 > zero)
  288. {
  289. s = -m2b2 - rm0;
  290. roots[numRoots++] = s;
  291. }
  292. else
  293. {
  294. s = -m2b2 + rm0;
  295. roots[numRoots++] = s;
  296. s = -m2b2 - rm0;
  297. roots[numRoots++] = s;
  298. }
  299. }
  300. std::array<ClosestInfo, 4> candidates;
  301. for (int i = 0; i < numRoots; ++i)
  302. {
  303. t = roots[i] + lambda;
  304. ClosestInfo info;
  305. Vector3<Real> NxDelta =
  306. Cross(circle.normal, oldD + t * line.direction);
  307. if (NxDelta != vzero)
  308. {
  309. GetPair(line, circle, oldD, t, info.lineClosest,
  310. info.circleClosest);
  311. info.equidistant = false;
  312. }
  313. else
  314. {
  315. Vector3<Real> U = GetOrthogonal(circle.normal, true);
  316. info.lineClosest = circle.center;
  317. info.circleClosest = circle.center + circle.radius * U;
  318. info.equidistant = true;
  319. }
  320. Vector3<Real> diff = info.lineClosest - info.circleClosest;
  321. info.sqrDistance = Dot(diff, diff);
  322. candidates[i] = info;
  323. }
  324. std::sort(candidates.begin(), candidates.begin() + numRoots);
  325. result.numClosestPairs = 1;
  326. result.lineClosest[0] = candidates[0].lineClosest;
  327. result.circleClosest[0] = candidates[0].circleClosest;
  328. if (numRoots > 1
  329. && candidates[1].sqrDistance == candidates[0].sqrDistance)
  330. {
  331. result.numClosestPairs = 2;
  332. result.lineClosest[1] = candidates[1].lineClosest;
  333. result.circleClosest[1] = candidates[1].circleClosest;
  334. }
  335. result.equidistant = false;
  336. }
  337. else
  338. {
  339. // The line direction and the plane normal are parallel.
  340. if (DxN != vzero)
  341. {
  342. // The line is A+t*N but with A != C.
  343. result.numClosestPairs = 1;
  344. GetPair(line, circle, D, -Dot(line.direction, D),
  345. result.lineClosest[0], result.circleClosest[0]);
  346. result.equidistant = false;
  347. }
  348. else
  349. {
  350. // The line is C+t*N, so C is the closest point for the
  351. // line and all circle points are equidistant from it.
  352. Vector3<Real> U = GetOrthogonal(circle.normal, true);
  353. result.numClosestPairs = 1;
  354. result.lineClosest[0] = circle.center;
  355. result.circleClosest[0] = circle.center + circle.radius * U;
  356. result.equidistant = true;
  357. }
  358. }
  359. Vector3<Real> diff = result.lineClosest[0] - result.circleClosest[0];
  360. result.sqrDistance = Dot(diff, diff);
  361. result.distance = std::sqrt(result.sqrDistance);
  362. return result;
  363. }
  364. private:
  365. // Support for operator(...).
  366. struct ClosestInfo
  367. {
  368. Real sqrDistance;
  369. Vector3<Real> lineClosest, circleClosest;
  370. bool equidistant;
  371. bool operator< (ClosestInfo const& info) const
  372. {
  373. return sqrDistance < info.sqrDistance;
  374. }
  375. };
  376. void GetPair(Line3<Real> const& line, Circle3<Real> const& circle,
  377. Vector3<Real> const& D, Real t, Vector3<Real>& lineClosest,
  378. Vector3<Real>& circleClosest)
  379. {
  380. Vector3<Real> delta = D + t * line.direction;
  381. lineClosest = circle.center + delta;
  382. delta -= Dot(circle.normal, delta) * circle.normal;
  383. Normalize(delta);
  384. circleClosest = circle.center + circle.radius * delta;
  385. }
  386. // Support for Robust(...). Bisect the function
  387. // F(s) = s + m2b2 - r*m0sqr*s/sqrt(m0sqr*s*s + b1sqr)
  388. // on the specified interval [smin,smax].
  389. Real Bisect(Real m2b2, Real rm0sqr, Real m0sqr, Real b1sqr, Real smin, Real smax)
  390. {
  391. std::function<Real(Real)> G = [&, m2b2, rm0sqr, m0sqr, b1sqr](Real s)
  392. {
  393. return s + m2b2 - rm0sqr * s / std::sqrt(m0sqr * s * s + b1sqr);
  394. };
  395. // The function is known to be increasing, so we can specify -1 and +1
  396. // as the function values at the bounding interval endpoints. The use
  397. // of 'double' is intentional in case Real is a BSNumber or BSRational
  398. // type. We want the bisections to terminate in a reasonable amount of
  399. // time.
  400. unsigned int const maxIterations = GTE_C_MAX_BISECTIONS_GENERIC;
  401. Real root;
  402. RootsBisection<Real>::Find(G, smin, smax, (Real)-1, (Real)+1, maxIterations, root);
  403. return root;
  404. }
  405. };
  406. }