ConvexHull2.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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. // Compute the convex hull of 2D points using a divide-and-conquer algorithm.
  9. // This is an O(N log N) algorithm for N input points. The only way to ensure
  10. // a correct result for the input vertices (assumed to be exact) is to choose
  11. // ComputeType for exact rational arithmetic. You may use BSNumber. No
  12. // divisions are performed in this computation, so you do not have to use
  13. // BSRational.
  14. //
  15. // The worst-case choices of N for Real of type BSNumber or BSRational with
  16. // integer storage UIntegerFP32<N> are listed in the next table. The numerical
  17. // computations are encapsulated in PrimalQuery2<Real>::ToLineExtended. We
  18. // recommend using only BSNumber, because no divisions are performed in the
  19. // convex-hull computations.
  20. //
  21. // input type | compute type | N
  22. // -----------+--------------+------
  23. // float | BSNumber | 18
  24. // double | BSNumber | 132
  25. // float | BSRational | 214
  26. // double | BSRational | 1587
  27. #include <Mathematics/Logger.h>
  28. #include <Mathematics/PrimalQuery2.h>
  29. #include <Mathematics/Line.h>
  30. #include <vector>
  31. // Uncomment this to assert when an infinite loop is encountered in
  32. // ConvexHull2::GetTangent.
  33. //#define GTE_THROW_ON_CONVEXHULL2_INFINITE_LOOP
  34. namespace WwiseGTE
  35. {
  36. template <typename InputType, typename ComputeType>
  37. class ConvexHull2
  38. {
  39. public:
  40. // The class is a functor to support computing the convex hull of
  41. // multiple data sets using the same class object.
  42. ConvexHull2()
  43. :
  44. mEpsilon((InputType)0),
  45. mDimension(0),
  46. mLine(Vector2<InputType>::Zero(), Vector2<InputType>::Zero()),
  47. mNumPoints(0),
  48. mNumUniquePoints(0),
  49. mPoints(nullptr)
  50. {
  51. }
  52. // The input is the array of points whose convex hull is required.
  53. // The epsilon value is used to determine the intrinsic dimensionality
  54. // of the vertices (d = 0, 1, or 2). When epsilon is positive, the
  55. // determination is fuzzy: points approximately the same point,
  56. // approximately on a line, or planar. The return value is 'true' if
  57. // and only if the hull/ construction is successful.
  58. bool operator()(int numPoints, Vector2<InputType> const* points, InputType epsilon)
  59. {
  60. mEpsilon = std::max(epsilon, (InputType)0);
  61. mDimension = 0;
  62. mLine.origin = Vector2<InputType>::Zero();
  63. mLine.direction = Vector2<InputType>::Zero();
  64. mNumPoints = numPoints;
  65. mNumUniquePoints = 0;
  66. mPoints = points;
  67. mMerged.clear();
  68. mHull.clear();
  69. int i, j;
  70. if (mNumPoints < 3)
  71. {
  72. // ConvexHull2 should be called with at least three points.
  73. return false;
  74. }
  75. IntrinsicsVector2<InputType> info(mNumPoints, mPoints, mEpsilon);
  76. if (info.dimension == 0)
  77. {
  78. // mDimension is 0
  79. return false;
  80. }
  81. if (info.dimension == 1)
  82. {
  83. // The set is (nearly) collinear.
  84. mDimension = 1;
  85. mLine = Line2<InputType>(info.origin, info.direction[0]);
  86. return false;
  87. }
  88. mDimension = 2;
  89. // Compute the points for the queries.
  90. mComputePoints.resize(mNumPoints);
  91. mQuery.Set(mNumPoints, &mComputePoints[0]);
  92. for (i = 0; i < mNumPoints; ++i)
  93. {
  94. for (j = 0; j < 2; ++j)
  95. {
  96. mComputePoints[i][j] = points[i][j];
  97. }
  98. }
  99. // Sort the points.
  100. mHull.resize(mNumPoints);
  101. for (i = 0; i < mNumPoints; ++i)
  102. {
  103. mHull[i] = i;
  104. }
  105. std::sort(mHull.begin(), mHull.end(),
  106. [points](int i0, int i1)
  107. {
  108. if (points[i0][0] < points[i1][0])
  109. {
  110. return true;
  111. }
  112. if (points[i0][0] > points[i1][0])
  113. {
  114. return false;
  115. }
  116. return points[i0][1] < points[i1][1];
  117. }
  118. );
  119. // Remove duplicates.
  120. auto newEnd = std::unique(mHull.begin(), mHull.end(),
  121. [points](int i0, int i1)
  122. {
  123. return points[i0] == points[i1];
  124. }
  125. );
  126. mHull.erase(newEnd, mHull.end());
  127. mNumUniquePoints = static_cast<int>(mHull.size());
  128. // Use a divide-and-conquer algorithm. The merge step computes
  129. // the convex hull of two convex polygons.
  130. mMerged.resize(mNumUniquePoints);
  131. int i0 = 0, i1 = mNumUniquePoints - 1;
  132. GetHull(i0, i1);
  133. mHull.resize(i1 - i0 + 1);
  134. return true;
  135. }
  136. // Dimensional information. If GetDimension() returns 1, the points
  137. // lie on a line P+t*D (fuzzy comparison when epsilon > 0). You can
  138. // sort these if you need a polyline output by projecting onto the
  139. // line each vertex X = P+t*D, where t = Dot(D,X-P).
  140. inline InputType GetEpsilon() const
  141. {
  142. return mEpsilon;
  143. }
  144. inline int GetDimension() const
  145. {
  146. return mDimension;
  147. }
  148. inline Line2<InputType> const& GetLine() const
  149. {
  150. return mLine;
  151. }
  152. // Member access.
  153. inline int GetNumPoints() const
  154. {
  155. return mNumPoints;
  156. }
  157. inline int GetNumUniquePoints() const
  158. {
  159. return mNumUniquePoints;
  160. }
  161. inline Vector2<InputType> const* GetPoints() const
  162. {
  163. return mPoints;
  164. }
  165. inline PrimalQuery2<ComputeType> const& GetQuery() const
  166. {
  167. return mQuery;
  168. }
  169. // The convex hull is a convex polygon whose vertices are listed in
  170. // counterclockwise order.
  171. inline std::vector<int> const& GetHull() const
  172. {
  173. return mHull;
  174. }
  175. private:
  176. // Support for divide-and-conquer.
  177. void GetHull(int& i0, int& i1)
  178. {
  179. int numVertices = i1 - i0 + 1;
  180. if (numVertices > 1)
  181. {
  182. // Compute the middle index of input range.
  183. int mid = (i0 + i1) / 2;
  184. // Compute the hull of subsets (mid-i0+1 >= i1-mid).
  185. int j0 = i0, j1 = mid, j2 = mid + 1, j3 = i1;
  186. GetHull(j0, j1);
  187. GetHull(j2, j3);
  188. // Merge the convex hulls into a single convex hull.
  189. Merge(j0, j1, j2, j3, i0, i1);
  190. }
  191. // else: The convex hull is a single point.
  192. }
  193. void Merge(int j0, int j1, int j2, int j3, int& i0, int& i1)
  194. {
  195. // Subhull0 is to the left of subhull1 because of the initial
  196. // sorting of the points by x-components. We need to find two
  197. // mutually visible points, one on the left subhull and one on
  198. // the right subhull.
  199. int size0 = j1 - j0 + 1;
  200. int size1 = j3 - j2 + 1;
  201. int i;
  202. Vector2<ComputeType> p;
  203. // Find the right-most point of the left subhull.
  204. Vector2<ComputeType> pmax0 = mComputePoints[mHull[j0]];
  205. int imax0 = j0;
  206. for (i = j0 + 1; i <= j1; ++i)
  207. {
  208. p = mComputePoints[mHull[i]];
  209. if (pmax0 < p)
  210. {
  211. pmax0 = p;
  212. imax0 = i;
  213. }
  214. }
  215. // Find the left-most point of the right subhull.
  216. Vector2<ComputeType> pmin1 = mComputePoints[mHull[j2]];
  217. int imin1 = j2;
  218. for (i = j2 + 1; i <= j3; ++i)
  219. {
  220. p = mComputePoints[mHull[i]];
  221. if (p < pmin1)
  222. {
  223. pmin1 = p;
  224. imin1 = i;
  225. }
  226. }
  227. // Get the lower tangent to hulls (LL = lower-left,
  228. // LR = lower-right).
  229. int iLL = imax0, iLR = imin1;
  230. GetTangent(j0, j1, j2, j3, iLL, iLR);
  231. // Get the upper tangent to hulls (UL = upper-left,
  232. // UR = upper-right).
  233. int iUL = imax0, iUR = imin1;
  234. GetTangent(j2, j3, j0, j1, iUR, iUL);
  235. // Construct the counterclockwise-ordered merged-hull vertices.
  236. int k;
  237. int numMerged = 0;
  238. i = iUL;
  239. for (k = 0; k < size0; ++k)
  240. {
  241. mMerged[numMerged++] = mHull[i];
  242. if (i == iLL)
  243. {
  244. break;
  245. }
  246. i = (i < j1 ? i + 1 : j0);
  247. }
  248. LogAssert(k < size0, "Unexpected condition.");
  249. i = iLR;
  250. for (k = 0; k < size1; ++k)
  251. {
  252. mMerged[numMerged++] = mHull[i];
  253. if (i == iUR)
  254. {
  255. break;
  256. }
  257. i = (i < j3 ? i + 1 : j2);
  258. }
  259. LogAssert(k < size1, "Unexpected condition.");
  260. int next = j0;
  261. for (k = 0; k < numMerged; ++k)
  262. {
  263. mHull[next] = mMerged[k];
  264. ++next;
  265. }
  266. i0 = j0;
  267. i1 = next - 1;
  268. }
  269. void GetTangent(int j0, int j1, int j2, int j3, int& i0, int& i1)
  270. {
  271. // In theory the loop terminates in a finite number of steps,
  272. // but the upper bound for the loop variable is used to trap
  273. // problems caused by floating-point roundoff errors that might
  274. // lead to an infinite loop.
  275. int size0 = j1 - j0 + 1;
  276. int size1 = j3 - j2 + 1;
  277. int const imax = size0 + size1;
  278. int i, iLm1, iRp1;
  279. Vector2<ComputeType> L0, L1, R0, R1;
  280. for (i = 0; i < imax; i++)
  281. {
  282. // Get the endpoints of the potential tangent.
  283. L1 = mComputePoints[mHull[i0]];
  284. R0 = mComputePoints[mHull[i1]];
  285. // Walk along the left hull to find the point of tangency.
  286. if (size0 > 1)
  287. {
  288. iLm1 = (i0 > j0 ? i0 - 1 : j1);
  289. L0 = mComputePoints[mHull[iLm1]];
  290. auto order = mQuery.ToLineExtended(R0, L0, L1);
  291. if (order == PrimalQuery2<ComputeType>::ORDER_NEGATIVE
  292. || order == PrimalQuery2<ComputeType>::ORDER_COLLINEAR_RIGHT)
  293. {
  294. i0 = iLm1;
  295. continue;
  296. }
  297. }
  298. // Walk along right hull to find the point of tangency.
  299. if (size1 > 1)
  300. {
  301. iRp1 = (i1 < j3 ? i1 + 1 : j2);
  302. R1 = mComputePoints[mHull[iRp1]];
  303. auto order = mQuery.ToLineExtended(L1, R0, R1);
  304. if (order == PrimalQuery2<ComputeType>::ORDER_NEGATIVE
  305. || order == PrimalQuery2<ComputeType>::ORDER_COLLINEAR_LEFT)
  306. {
  307. i1 = iRp1;
  308. continue;
  309. }
  310. }
  311. // The tangent segment has been found.
  312. break;
  313. }
  314. // Detect an "infinite loop" caused by floating point round-off
  315. // errors.
  316. #if defined(GTE_THROW_ON_CONVEXHULL2_INFINITE_LOOP)
  317. LogAssert(i < imax, "Unexpected condition.");
  318. #endif
  319. }
  320. // The epsilon value is used for fuzzy determination of intrinsic
  321. // dimensionality. If the dimension is 0 or 1, the constructor
  322. // returns early. The caller is responsible for retrieving the
  323. // dimension and taking an alternate path should the dimension be
  324. // smaller than 2. If the dimension is 0, the caller may as well
  325. // treat all points[] as a single point, say, points[0]. If the
  326. // dimension is 1, the caller can query for the approximating line
  327. // and project points[] onto it for further processing.
  328. InputType mEpsilon;
  329. int mDimension;
  330. Line2<InputType> mLine;
  331. // The array of points used for geometric queries. If you want to
  332. // be certain of a correct result, choose ComputeType to be BSNumber.
  333. std::vector<Vector2<ComputeType>> mComputePoints;
  334. PrimalQuery2<ComputeType> mQuery;
  335. int mNumPoints;
  336. int mNumUniquePoints;
  337. Vector2<InputType> const* mPoints;
  338. std::vector<int> mMerged, mHull;
  339. };
  340. }