quickhull.h 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // Copyright 2024 The Manifold Authors.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. //
  15. // Derived from the public domain work of Antti Kuukka at
  16. // https://github.com/akuukka/quickhull
  17. /*
  18. * INPUT: a list of points in 3D space (for example, vertices of a 3D mesh)
  19. *
  20. * OUTPUT: a ConvexHull object which provides vertex and index buffers of the
  21. *generated convex hull as a triangle mesh.
  22. *
  23. *
  24. *
  25. * The implementation is thread-safe if each thread is using its own QuickHull
  26. *object.
  27. *
  28. *
  29. * SUMMARY OF THE ALGORITHM:
  30. * - Create initial simplex (tetrahedron) using extreme points. We have
  31. *four faces now and they form a convex mesh M.
  32. * - For each point, assign them to the first face for which they are on
  33. *the positive side of (so each point is assigned to at most one face). Points
  34. *inside the initial tetrahedron are left behind now and no longer affect the
  35. *calculations.
  36. * - Add all faces that have points assigned to them to Face Stack.
  37. * - Iterate until Face Stack is empty:
  38. * - Pop topmost face F from the stack
  39. * - From the points assigned to F, pick the point P that is
  40. *farthest away from the plane defined by F.
  41. * - Find all faces of M that have P on their positive side. Let us
  42. *call these the "visible faces".
  43. * - Because of the way M is constructed, these faces are
  44. *connected. Solve their horizon edge loop.
  45. * - "Extrude to P": Create new faces by connecting
  46. *P with the points belonging to the horizon edge. Add the new faces to M and
  47. *remove the visible faces from M.
  48. * - Each point that was assigned to visible faces is now assigned
  49. *to at most one of the newly created faces.
  50. * - Those new faces that have points assigned to them are added to
  51. *the top of Face Stack.
  52. * - M is now the convex hull.
  53. *
  54. * */
  55. #pragma once
  56. #include <array>
  57. #include <deque>
  58. #include <vector>
  59. #include "shared.h"
  60. #include "vec.h"
  61. namespace manifold {
  62. class Pool {
  63. std::vector<std::unique_ptr<Vec<size_t>>> data;
  64. public:
  65. void clear() { data.clear(); }
  66. void reclaim(std::unique_ptr<Vec<size_t>>& ptr) {
  67. data.push_back(std::move(ptr));
  68. }
  69. std::unique_ptr<Vec<size_t>> get() {
  70. if (data.size() == 0) {
  71. return std::make_unique<Vec<size_t>>();
  72. }
  73. auto it = data.end() - 1;
  74. std::unique_ptr<Vec<size_t>> r = std::move(*it);
  75. data.erase(it);
  76. return r;
  77. }
  78. };
  79. class Plane {
  80. public:
  81. vec3 N;
  82. // Signed distance (if normal is of length 1) to the plane from origin
  83. double D;
  84. // Normal length squared
  85. double sqrNLength;
  86. bool isPointOnPositiveSide(const vec3& Q) const {
  87. double d = la::dot(N, Q) + D;
  88. if (d >= 0) return true;
  89. return false;
  90. }
  91. Plane() = default;
  92. // Construct a plane using normal N and any point P on the plane
  93. Plane(const vec3& N, const vec3& P)
  94. : N(N), D(la::dot(-N, P)), sqrNLength(la::dot(N, N)) {}
  95. };
  96. struct Ray {
  97. const vec3 S;
  98. const vec3 V;
  99. const double VInvLengthSquared;
  100. Ray(const vec3& S, const vec3& V)
  101. : S(S), V(V), VInvLengthSquared(1 / (la::dot(V, V))) {}
  102. };
  103. class MeshBuilder {
  104. public:
  105. struct Face {
  106. int he;
  107. Plane P{};
  108. double mostDistantPointDist = 0.0;
  109. size_t mostDistantPoint = 0;
  110. size_t visibilityCheckedOnIteration = 0;
  111. std::uint8_t isVisibleFaceOnCurrentIteration : 1;
  112. std::uint8_t inFaceStack : 1;
  113. // Bit for each half edge assigned to this face, each being 0 or 1 depending
  114. // on whether the edge belongs to horizon edge
  115. std::uint8_t horizonEdgesOnCurrentIteration : 3;
  116. std::unique_ptr<Vec<size_t>> pointsOnPositiveSide;
  117. Face(size_t he)
  118. : he(he),
  119. isVisibleFaceOnCurrentIteration(0),
  120. inFaceStack(0),
  121. horizonEdgesOnCurrentIteration(0) {}
  122. Face()
  123. : he(-1),
  124. isVisibleFaceOnCurrentIteration(0),
  125. inFaceStack(0),
  126. horizonEdgesOnCurrentIteration(0) {}
  127. void disable() { he = -1; }
  128. bool isDisabled() const { return he == -1; }
  129. };
  130. // Mesh data
  131. std::vector<Face> faces;
  132. Vec<Halfedge> halfedges;
  133. Vec<int> halfedgeToFace;
  134. Vec<int> halfedgeNext;
  135. // When the mesh is modified and faces and half edges are removed from it, we
  136. // do not actually remove them from the container vectors. Insted, they are
  137. // marked as disabled which means that the indices can be reused when we need
  138. // to add new faces and half edges to the mesh. We store the free indices in
  139. // the following vectors.
  140. Vec<size_t> disabledFaces, disabledHalfedges;
  141. size_t addFace();
  142. size_t addHalfedge();
  143. // Mark a face as disabled and return a pointer to the points that were on the
  144. // positive of it.
  145. std::unique_ptr<Vec<size_t>> disableFace(size_t faceIndex) {
  146. auto& f = faces[faceIndex];
  147. f.disable();
  148. disabledFaces.push_back(faceIndex);
  149. return std::move(f.pointsOnPositiveSide);
  150. }
  151. void disableHalfedge(size_t heIndex) {
  152. auto& he = halfedges[heIndex];
  153. he.pairedHalfedge = -1;
  154. disabledHalfedges.push_back(heIndex);
  155. }
  156. MeshBuilder() = default;
  157. // Create a mesh with initial tetrahedron ABCD. Dot product of AB with the
  158. // normal of triangle ABC should be negative.
  159. void setup(int a, int b, int c, int d);
  160. std::array<int, 3> getVertexIndicesOfFace(const Face& f) const;
  161. std::array<int, 2> getVertexIndicesOfHalfEdge(const Halfedge& he) const {
  162. return {halfedges[he.pairedHalfedge].endVert, he.endVert};
  163. }
  164. std::array<int, 3> getHalfEdgeIndicesOfFace(const Face& f) const {
  165. return {f.he, halfedgeNext[f.he], halfedgeNext[halfedgeNext[f.he]]};
  166. }
  167. };
  168. class HalfEdgeMesh {
  169. public:
  170. Vec<vec3> vertices;
  171. // Index of one of the half edges of the faces
  172. std::vector<size_t> halfEdgeIndexFaces;
  173. Vec<Halfedge> halfedges;
  174. Vec<int> halfedgeToFace;
  175. Vec<int> halfedgeNext;
  176. HalfEdgeMesh(const MeshBuilder& builderObject,
  177. const VecView<vec3>& vertexData);
  178. };
  179. double defaultEps();
  180. class QuickHull {
  181. struct FaceData {
  182. int faceIndex;
  183. // If the face turns out not to be visible, this half edge will be marked as
  184. // horizon edge
  185. int enteredFromHalfedge;
  186. };
  187. double m_epsilon, epsilonSquared, scale;
  188. bool planar;
  189. Vec<vec3> planarPointCloudTemp;
  190. VecView<vec3> originalVertexData;
  191. MeshBuilder mesh;
  192. std::array<size_t, 6> extremeValues;
  193. size_t failedHorizonEdges = 0;
  194. // Temporary variables used during iteration process
  195. Vec<size_t> newFaceIndices;
  196. Vec<size_t> newHalfedgeIndices;
  197. Vec<size_t> visibleFaces;
  198. Vec<size_t> horizonEdgesData;
  199. Vec<FaceData> possiblyVisibleFaces;
  200. std::vector<std::unique_ptr<Vec<size_t>>> disabledFacePointVectors;
  201. std::deque<int> faceList;
  202. // Create a half edge mesh representing the base tetrahedron from which the
  203. // QuickHull iteration proceeds. extremeValues must be properly set up when
  204. // this is called.
  205. void setupInitialTetrahedron();
  206. // Given a list of half edges, try to rearrange them so that they form a loop.
  207. // Return true on success.
  208. bool reorderHorizonEdges(VecView<size_t>& horizonEdges);
  209. // Find indices of extreme values (max x, min x, max y, min y, max z, min z)
  210. // for the given point cloud
  211. std::array<size_t, 6> getExtremeValues();
  212. // Compute scale of the vertex data.
  213. double getScale(const std::array<size_t, 6>& extremeValuesInput);
  214. // Each face contains a unique pointer to a vector of indices. However, many -
  215. // often most - faces do not have any points on the positive side of them
  216. // especially at the the end of the iteration. When a face is removed from the
  217. // mesh, its associated point vector, if such exists, is moved to the index
  218. // vector pool, and when we need to add new faces with points on the positive
  219. // side to the mesh, we reuse these vectors. This reduces the amount of
  220. // std::vectors we have to deal with, and impact on performance is remarkable.
  221. Pool indexVectorPool;
  222. inline std::unique_ptr<Vec<size_t>> getIndexVectorFromPool();
  223. inline void reclaimToIndexVectorPool(std::unique_ptr<Vec<size_t>>& ptr);
  224. // Associates a point with a face if the point resides on the positive side of
  225. // the plane. Returns true if the points was on the positive side.
  226. inline bool addPointToFace(typename MeshBuilder::Face& f, size_t pointIndex);
  227. // This will create HalfedgeMesh from which we create the ConvexHull object
  228. // that buildMesh function returns
  229. void createConvexHalfedgeMesh();
  230. public:
  231. // This function assumes that the pointCloudVec data resides in memory in the
  232. // following format: x_0,y_0,z_0,x_1,y_1,z_1,...
  233. QuickHull(VecView<vec3> pointCloudVec)
  234. : originalVertexData(VecView(pointCloudVec)) {}
  235. // Computes convex hull for a given point cloud. Params: eps: minimum distance
  236. // to a plane to consider a point being on positive side of it (for a point
  237. // cloud with scale 1) Returns: Convex hull of the point cloud as halfEdge
  238. // vector and vertex vector
  239. std::pair<Vec<Halfedge>, Vec<vec3>> buildMesh(double eps = defaultEps());
  240. };
  241. } // namespace manifold