ConvexHull3.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  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 3D points using incremental insertion. The only
  9. // way to ensure a correct result for the input vertices (assumed to be exact)
  10. // is to choose ComputeType for exact rational arithmetic. You may use
  11. // BSNumber. No divisions are performed in this computation, so you do not
  12. // have to use BSRational.
  13. //
  14. // The worst-case choices of N for Real of type BSNumber or BSRational with
  15. // integer storage UIntegerFP32<N> are listed in the next table. The
  16. // numerical computations are encapsulated in PrimalQuery3<Real>::ToPlane.
  17. // We recommend using only BSNumber, because no divisions are performed in
  18. // the convex-hull computations.
  19. //
  20. // input type | compute type | N
  21. // -----------+--------------+------
  22. // float | BSNumber | 27
  23. // double | BSNumber | 197
  24. // float | BSRational | 2882
  25. // double | BSRational | 21688
  26. #include <Mathematics/ETManifoldMesh.h>
  27. #include <Mathematics/PrimalQuery3.h>
  28. #include <Mathematics/Line.h>
  29. #include <Mathematics/Hyperplane.h>
  30. #include <functional>
  31. #include <set>
  32. #include <thread>
  33. namespace WwiseGTE
  34. {
  35. template <typename InputType, typename ComputeType>
  36. class ConvexHull3
  37. {
  38. public:
  39. // The class is a functor to support computing the convex hull of
  40. // multiple data sets using the same class object. For
  41. // multithreading in Update, choose 'numThreads' subject to the
  42. // constraints 1 <= numThreads <= std::thread::hardware_concurrency().
  43. ConvexHull3(unsigned int numThreads = 1)
  44. :
  45. mEpsilon((InputType)0),
  46. mDimension(0),
  47. mLine(Vector3<InputType>::Zero(), Vector3<InputType>::Zero()),
  48. mPlane(Vector3<InputType>::Zero(), (InputType)0),
  49. mNumPoints(0),
  50. mNumUniquePoints(0),
  51. mPoints(nullptr),
  52. mNumThreads(numThreads)
  53. {
  54. }
  55. // The input is the array of points whose convex hull is required.
  56. // The epsilon value is used to determine the intrinsic dimensionality
  57. // of the vertices (d = 0, 1, 2, or 3). When epsilon is positive, the
  58. // determination is fuzzy--points approximately the same point,
  59. // approximately on a line, approximately planar, or volumetric.
  60. bool operator()(int numPoints, Vector3<InputType> const* points, InputType epsilon)
  61. {
  62. mEpsilon = std::max(epsilon, (InputType)0);
  63. mDimension = 0;
  64. mLine.origin = Vector3<InputType>::Zero();
  65. mLine.direction = Vector3<InputType>::Zero();
  66. mPlane.normal = Vector3<InputType>::Zero();
  67. mPlane.constant = (InputType)0;
  68. mNumPoints = numPoints;
  69. mNumUniquePoints = 0;
  70. mPoints = points;
  71. mHullUnordered.clear();
  72. mHullMesh.Clear();
  73. int i, j;
  74. if (mNumPoints < 4)
  75. {
  76. // ConvexHull3 should be called with at least four points.
  77. return false;
  78. }
  79. IntrinsicsVector3<InputType> info(mNumPoints, mPoints, mEpsilon);
  80. if (info.dimension == 0)
  81. {
  82. // The set is (nearly) a point.
  83. return false;
  84. }
  85. if (info.dimension == 1)
  86. {
  87. // The set is (nearly) collinear.
  88. mDimension = 1;
  89. mLine = Line3<InputType>(info.origin, info.direction[0]);
  90. return false;
  91. }
  92. if (info.dimension == 2)
  93. {
  94. // The set is (nearly) coplanar.
  95. mDimension = 2;
  96. mPlane = Plane3<InputType>(UnitCross(info.direction[0],
  97. info.direction[1]), info.origin);
  98. return false;
  99. }
  100. mDimension = 3;
  101. // Compute the vertices for the queries.
  102. mComputePoints.resize(mNumPoints);
  103. mQuery.Set(mNumPoints, &mComputePoints[0]);
  104. for (i = 0; i < mNumPoints; ++i)
  105. {
  106. for (j = 0; j < 3; ++j)
  107. {
  108. mComputePoints[i][j] = points[i][j];
  109. }
  110. }
  111. // Insert the faces of the (nondegenerate) tetrahedron
  112. // constructed by the call to GetInformation.
  113. if (!info.extremeCCW)
  114. {
  115. std::swap(info.extreme[2], info.extreme[3]);
  116. }
  117. mHullUnordered.push_back(TriangleKey<true>(info.extreme[1],
  118. info.extreme[2], info.extreme[3]));
  119. mHullUnordered.push_back(TriangleKey<true>(info.extreme[0],
  120. info.extreme[3], info.extreme[2]));
  121. mHullUnordered.push_back(TriangleKey<true>(info.extreme[0],
  122. info.extreme[1], info.extreme[3]));
  123. mHullUnordered.push_back(TriangleKey<true>(info.extreme[0],
  124. info.extreme[2], info.extreme[1]));
  125. // Incrementally update the hull. The set of processed points is
  126. // maintained to eliminate duplicates, either in the original
  127. // input points or in the points obtained by snap rounding.
  128. std::set<Vector3<InputType>> processed;
  129. for (i = 0; i < 4; ++i)
  130. {
  131. processed.insert(points[info.extreme[i]]);
  132. }
  133. for (i = 0; i < mNumPoints; ++i)
  134. {
  135. if (processed.find(points[i]) == processed.end())
  136. {
  137. Update(i);
  138. processed.insert(points[i]);
  139. }
  140. }
  141. mNumUniquePoints = static_cast<int>(processed.size());
  142. return true;
  143. }
  144. // Dimensional information. If GetDimension() returns 1, the points
  145. // lie on a line P+t*D (fuzzy comparison when epsilon > 0). You can
  146. // sort these if you need a polyline output by projecting onto the
  147. // line each vertex X = P+t*D, where t = Dot(D,X-P). If GetDimension()
  148. // returns 2, the points line on a plane P+s*U+t*V (fuzzy comparison
  149. // when epsilon > 0). You can project each point X = P+s*U+t*V, where
  150. // s = Dot(U,X-P) and t = Dot(V,X-P), then apply ConvexHull2 to the
  151. // (s,t) tuples.
  152. inline InputType GetEpsilon() const
  153. {
  154. return mEpsilon;
  155. }
  156. inline int GetDimension() const
  157. {
  158. return mDimension;
  159. }
  160. inline Line3<InputType> const& GetLine() const
  161. {
  162. return mLine;
  163. }
  164. inline Plane3<InputType> const& GetPlane() const
  165. {
  166. return mPlane;
  167. }
  168. // Member access.
  169. inline int GetNumPoints() const
  170. {
  171. return mNumPoints;
  172. }
  173. inline int GetNumUniquePoints() const
  174. {
  175. return mNumUniquePoints;
  176. }
  177. inline Vector3<InputType> const* GetPoints() const
  178. {
  179. return mPoints;
  180. }
  181. inline PrimalQuery3<ComputeType> const& GetQuery() const
  182. {
  183. return mQuery;
  184. }
  185. // The convex hull is a convex polyhedron with triangular faces.
  186. inline std::vector<TriangleKey<true>> const& GetHullUnordered() const
  187. {
  188. return mHullUnordered;
  189. }
  190. ETManifoldMesh const& GetHullMesh() const
  191. {
  192. // Create the mesh only on demand.
  193. if (mHullMesh.GetTriangles().size() == 0)
  194. {
  195. for (auto const& tri : mHullUnordered)
  196. {
  197. mHullMesh.Insert(tri.V[0], tri.V[1], tri.V[2]);
  198. }
  199. }
  200. return mHullMesh;
  201. }
  202. private:
  203. // Support for incremental insertion.
  204. void Update(int i)
  205. {
  206. // The terminator that separates visible faces from nonvisible
  207. // faces is constructed by this code. Visible faces for the
  208. // incoming hull are removed, and the boundary of that set of
  209. // triangles is the terminator. New visible faces are added
  210. // using the incoming point and the edges of the terminator.
  211. //
  212. // A simple algorithm for computing terminator edges is the
  213. // following. Back-facing triangles are located and the three
  214. // edges are processed. The first time an edge is visited,
  215. // insert it into the terminator. If it is visited a second time,
  216. // the edge is removed because it is shared by another back-facing
  217. // triangle and, therefore, cannot be a terminator edge. After
  218. // visiting all back-facing triangles, the only remaining edges in
  219. // the map are the terminator edges.
  220. //
  221. // The order of vertices of an edge is important for adding new
  222. // faces with the correct vertex winding. However, the edge
  223. // "toggle" (insert edge, remove edge) should use edges with
  224. // unordered vertices, because the edge shared by one triangle has
  225. // opposite ordering relative to that of the other triangle. The
  226. // map uses unordered edges as the keys but stores the ordered
  227. // edge as the value. This avoids having to look up an edge twice
  228. // in a map with ordered edge keys.
  229. unsigned int numFaces = static_cast<unsigned int>(mHullUnordered.size());
  230. std::vector<int> queryResult(numFaces);
  231. if (mNumThreads > 1 && numFaces >= mNumThreads)
  232. {
  233. // Partition the data for multiple threads.
  234. unsigned int numFacesPerThread = numFaces / mNumThreads;
  235. std::vector<unsigned int> jmin(mNumThreads), jmax(mNumThreads);
  236. for (unsigned int t = 0; t < mNumThreads; ++t)
  237. {
  238. jmin[t] = t * numFacesPerThread;
  239. jmax[t] = jmin[t] + numFacesPerThread - 1;
  240. }
  241. jmax[mNumThreads - 1] = numFaces - 1;
  242. // Execute the point-plane queries in multiple threads.
  243. std::vector<std::thread> process(mNumThreads);
  244. for (unsigned int t = 0; t < mNumThreads; ++t)
  245. {
  246. process[t] = std::thread([this, i, t, &jmin, &jmax,
  247. &queryResult]()
  248. {
  249. for (unsigned int j = jmin[t]; j <= jmax[t]; ++j)
  250. {
  251. TriangleKey<true> const& tri = mHullUnordered[j];
  252. queryResult[j] = mQuery.ToPlane(i, tri.V[0], tri.V[1], tri.V[2]);
  253. }
  254. });
  255. }
  256. // Wait for all threads to finish.
  257. for (unsigned int t = 0; t < mNumThreads; ++t)
  258. {
  259. process[t].join();
  260. }
  261. }
  262. else
  263. {
  264. unsigned int j = 0;
  265. for (auto const& tri : mHullUnordered)
  266. {
  267. queryResult[j++] = mQuery.ToPlane(i, tri.V[0], tri.V[1], tri.V[2]);
  268. }
  269. }
  270. std::map<EdgeKey<false>, std::pair<int, int>> terminator;
  271. std::vector<TriangleKey<true>> backFaces;
  272. bool existsFrontFacingTriangle = false;
  273. unsigned int j = 0;
  274. for (auto const& tri : mHullUnordered)
  275. {
  276. if (queryResult[j++] <= 0)
  277. {
  278. // The triangle is back facing. These include triangles
  279. // that are coplanar with the incoming point.
  280. backFaces.push_back(tri);
  281. // The current hull is a 2-manifold watertight mesh. The
  282. // terminator edges are those shared with a front-facing
  283. // triangle. The first time an edge of a back-facing
  284. // triangle is visited, insert it into the terminator. If
  285. // it is visited a second time, the edge is removed
  286. // because it is shared by another back-facing triangle.
  287. // After all back-facing triangles are visited, the only
  288. // remaining edges are shared by a single back-facing
  289. // triangle, which makes them terminator edges.
  290. for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++)
  291. {
  292. int v0 = tri.V[j0], v1 = tri.V[j1];
  293. EdgeKey<false> edge(v0, v1);
  294. auto iter = terminator.find(edge);
  295. if (iter == terminator.end())
  296. {
  297. // The edge is visited for the first time.
  298. terminator.insert(std::make_pair(edge, std::make_pair(v0, v1)));
  299. }
  300. else
  301. {
  302. // The edge is visited for the second time.
  303. terminator.erase(edge);
  304. }
  305. }
  306. }
  307. else
  308. {
  309. // If there are no strictly front-facing triangles, then
  310. // the incoming point is inside or on the convex hull. If
  311. // we get to this code, then the point is truly outside
  312. // and we can update the hull.
  313. existsFrontFacingTriangle = true;
  314. }
  315. }
  316. if (!existsFrontFacingTriangle)
  317. {
  318. // The incoming point is inside or on the current hull, so no
  319. // update of the hull is necessary.
  320. return;
  321. }
  322. // The updated hull contains the triangles not visible to the
  323. // incoming point.
  324. mHullUnordered = backFaces;
  325. // Insert the triangles formed by the incoming point and the
  326. // terminator edges.
  327. for (auto const& edge : terminator)
  328. {
  329. mHullUnordered.push_back(TriangleKey<true>(i, edge.second.second, edge.second.first));
  330. }
  331. }
  332. // The epsilon value is used for fuzzy determination of intrinsic
  333. // dimensionality. If the dimension is 0, 1, or 2, the constructor
  334. // returns early. The caller is responsible for retrieving the
  335. // dimension and taking an alternate path should the dimension be
  336. // smaller than 3. If the dimension is 0, the caller may as well
  337. // treat all points[] as a single point, say, points[0]. If the
  338. // dimension is 1, the caller can query for the approximating line
  339. // and project points[] onto it for further processing. If the
  340. // dimension is 2, the caller can query for the approximating plane
  341. // and project points[] onto it for further processing.
  342. InputType mEpsilon;
  343. int mDimension;
  344. Line3<InputType> mLine;
  345. Plane3<InputType> mPlane;
  346. // The array of points used for geometric queries. If you want to be
  347. // certain of a correct result, choose ComputeType to be BSNumber.
  348. std::vector<Vector3<ComputeType>> mComputePoints;
  349. PrimalQuery3<ComputeType> mQuery;
  350. int mNumPoints;
  351. int mNumUniquePoints;
  352. Vector3<InputType> const* mPoints;
  353. std::vector<TriangleKey<true>> mHullUnordered;
  354. mutable ETManifoldMesh mHullMesh;
  355. unsigned int mNumThreads;
  356. };
  357. }