diff --git a/libtascar/src/quickhull/ConvexHull.hpp b/libtascar/src/quickhull/ConvexHull.hpp index 1abc22f4..71b2ee11 100644 --- a/libtascar/src/quickhull/ConvexHull.hpp +++ b/libtascar/src/quickhull/ConvexHull.hpp @@ -92,7 +92,8 @@ namespace quickhull { if (faceStack.size()==0) { return; } - + + const size_t iCCW = CCW ? 1 : 0; const size_t finalMeshFaceCount = mesh.m_faces.size() - mesh.m_disabledFaces.size(); m_indices.reserve(finalMeshFaceCount*3); @@ -116,26 +117,20 @@ namespace quickhull { auto vertices = mesh.getVertexIndicesOfFace(mesh.m_faces[top]); if (!useOriginalIndices) { for (auto& v : vertices) { - auto it = vertexIndexMapping.find(v); - if (it == vertexIndexMapping.end()) { + auto itV = vertexIndexMapping.find(v); + if (itV == vertexIndexMapping.end()) { m_optimizedVertexBuffer->push_back(pointCloud[v]); vertexIndexMapping[v] = m_optimizedVertexBuffer->size()-1; v = m_optimizedVertexBuffer->size()-1; } else { - v = it->second; + v = itV->second; } } } m_indices.push_back(vertices[0]); - if (CCW) { - m_indices.push_back(vertices[2]); - m_indices.push_back(vertices[1]); - } - else { - m_indices.push_back(vertices[1]); - m_indices.push_back(vertices[2]); - } + m_indices.push_back(vertices[1 + iCCW]); + m_indices.push_back(vertices[2 - iCCW]); } } @@ -151,12 +146,20 @@ namespace quickhull { return m_indices; } + const std::vector& getIndexBuffer() const { + return m_indices; + } + VertexDataSource& getVertexBuffer() { return m_vertices; } + const VertexDataSource& getVertexBuffer() const { + return m_vertices; + } + // Export the mesh to a Waveform OBJ file - void writeWaveformOBJ(const std::string& filename, const std::string& objectName = "quickhull") + void writeWaveformOBJ(const std::string& filename, const std::string& objectName = "quickhull") const { std::ofstream objFile; objFile.open (filename); diff --git a/libtascar/src/quickhull/QuickHull.cpp b/libtascar/src/quickhull/QuickHull.cpp index d09a63f7..fc02f3d0 100644 --- a/libtascar/src/quickhull/QuickHull.cpp +++ b/libtascar/src/quickhull/QuickHull.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "Structs/Mesh.hpp" @@ -43,14 +42,19 @@ namespace quickhull { } template - HalfEdgeMesh QuickHull::getConvexHullAsMesh(const FloatType* vertexData, size_t vertexCount, bool CCW, FloatType epsilon) { + HalfEdgeMesh QuickHull::getConvexHullAsMesh(const FloatType* vertexData, size_t vertexCount, bool CCW, FloatType epsilon) { VertexDataSource vertexDataSource((const vec3*)vertexData,vertexCount); buildMesh(vertexDataSource, CCW, false, epsilon); - return HalfEdgeMesh(m_mesh, m_vertexData); + return HalfEdgeMesh(m_mesh, m_vertexData); } template - void QuickHull::buildMesh(const VertexDataSource& pointCloud, bool, bool , T epsilon) { + void QuickHull::buildMesh(const VertexDataSource& pointCloud, bool CCW, bool useOriginalIndices, T epsilon) { + // CCW is unused for now + (void)CCW; + // useOriginalIndices is unused for now + (void)useOriginalIndices; + if (pointCloud.size()==0) { m_mesh = MeshBuilder(); return; @@ -90,33 +94,27 @@ namespace quickhull { template void QuickHull::createConvexHalfEdgeMesh() { - // Temporary variables used during iteration - std::vector visibleFaces; - std::vector horizonEdges; - struct FaceData { - IndexType m_faceIndex; - IndexType m_enteredFromHalfEdge; // If the face turns out not to be visible, this half edge will be marked as horizon edge - FaceData(IndexType fi, IndexType he) : m_faceIndex(fi),m_enteredFromHalfEdge(he) {} - }; - std::vector possiblyVisibleFaces; - + m_visibleFaces.clear(); + m_horizonEdges.clear(); + m_possiblyVisibleFaces.clear(); + // Compute base tetrahedron - m_mesh = getInitialTetrahedron(); + setupInitialTetrahedron(); assert(m_mesh.m_faces.size()==4); // Init face stack with those faces that have points assigned to them - std::deque faceList; + m_faceList.clear(); for (size_t i=0;i < 4;i++) { auto& f = m_mesh.m_faces[i]; if (f.m_pointsOnPositiveSide && f.m_pointsOnPositiveSide->size()>0) { - faceList.push_back(i); + m_faceList.push_back(i); f.m_inFaceStack = 1; } } // Process faces until the face list is empty. size_t iter = 0; - while (!faceList.empty()) { + while (!m_faceList.empty()) { iter++; if (iter == std::numeric_limits::max()) { // Visible face traversal marks visited faces with iteration counter (to mark that the face has been visited on this iteration) and the max value represents unvisited faces. At this point we have to reset iteration counter. This shouldn't be an @@ -124,8 +122,8 @@ namespace quickhull { iter = 0; } - const IndexType topFaceIndex = faceList.front(); - faceList.pop_front(); + const size_t topFaceIndex = m_faceList.front(); + m_faceList.pop_front(); auto& tf = m_mesh.m_faces[topFaceIndex]; tf.m_inFaceStack = 0; @@ -140,13 +138,13 @@ namespace quickhull { const size_t activePointIndex = tf.m_mostDistantPoint; // Find out the faces that have our active point on their positive side (these are the "visible faces"). The face on top of the stack of course is one of them. At the same time, we create a list of horizon edges. - horizonEdges.clear(); - possiblyVisibleFaces.clear(); - visibleFaces.clear(); - possiblyVisibleFaces.emplace_back(topFaceIndex,std::numeric_limits::max()); - while (possiblyVisibleFaces.size()) { - const auto faceData = possiblyVisibleFaces.back(); - possiblyVisibleFaces.pop_back(); + m_horizonEdges.clear(); + m_possiblyVisibleFaces.clear(); + m_visibleFaces.clear(); + m_possiblyVisibleFaces.emplace_back(topFaceIndex,std::numeric_limits::max()); + while (m_possiblyVisibleFaces.size()) { + const auto faceData = m_possiblyVisibleFaces.back(); + m_possiblyVisibleFaces.pop_back(); auto& pvf = m_mesh.m_faces[faceData.m_faceIndex]; assert(!pvf.isDisabled()); @@ -162,10 +160,10 @@ namespace quickhull { if (d>0) { pvf.m_isVisibleFaceOnCurrentIteration = 1; pvf.m_horizonEdgesOnCurrentIteration = 0; - visibleFaces.push_back(faceData.m_faceIndex); + m_visibleFaces.push_back(faceData.m_faceIndex); for (auto heIndex : m_mesh.getHalfEdgeIndicesOfFace(pvf)) { if (m_mesh.m_halfEdges[heIndex].m_opp != faceData.m_enteredFromHalfEdge) { - possiblyVisibleFaces.emplace_back( m_mesh.m_halfEdges[m_mesh.m_halfEdges[heIndex].m_opp].m_face,heIndex ); + m_possiblyVisibleFaces.emplace_back( m_mesh.m_halfEdges[m_mesh.m_halfEdges[heIndex].m_opp].m_face,heIndex ); } } continue; @@ -175,16 +173,16 @@ namespace quickhull { // The face is not visible. Therefore, the halfedge we came from is part of the horizon edge. pvf.m_isVisibleFaceOnCurrentIteration = 0; - horizonEdges.push_back(faceData.m_enteredFromHalfEdge); + m_horizonEdges.push_back(faceData.m_enteredFromHalfEdge); // Store which half edge is the horizon edge. The other half edges of the face will not be part of the final mesh so their data slots can by recycled. const auto halfEdges = m_mesh.getHalfEdgeIndicesOfFace(m_mesh.m_faces[m_mesh.m_halfEdges[faceData.m_enteredFromHalfEdge].m_face]); const std::int8_t ind = (halfEdges[0]==faceData.m_enteredFromHalfEdge) ? 0 : (halfEdges[1]==faceData.m_enteredFromHalfEdge ? 1 : 2); m_mesh.m_faces[m_mesh.m_halfEdges[faceData.m_enteredFromHalfEdge].m_face].m_horizonEdgesOnCurrentIteration |= (1<begin(),tf.m_pointsOnPositiveSide->end(),activePointIndex); @@ -202,7 +200,7 @@ namespace quickhull { m_newHalfEdgeIndices.clear(); m_disabledFacePointVectors.clear(); size_t disableCounter = 0; - for (auto faceIndex : visibleFaces) { + for (auto faceIndex : m_visibleFaces) { auto& disabledFace = m_mesh.m_faces[faceIndex]; auto halfEdges = m_mesh.getHalfEdgeIndicesOfFace(disabledFace); for (size_t j=0;j<3;j++) { @@ -220,7 +218,7 @@ namespace quickhull { } // Disable the face, but retain pointer to the points that were on the positive side of it. We need to assign those points // to the new faces we create shortly. - auto t = std::move(m_mesh.disableFace(faceIndex)); + auto t = m_mesh.disableFace(faceIndex); if (t) { assert(t->size()); // Because we should not assign point vectors to faces unless needed... m_disabledFacePointVectors.push_back(std::move(t)); @@ -235,19 +233,19 @@ namespace quickhull { // Create new faces using the edgeloop for (size_t i = 0; i < horizonEdgeCount; i++) { - const IndexType AB = horizonEdges[i]; + const size_t AB = m_horizonEdges[i]; auto horizonEdgeVertexIndices = m_mesh.getVertexIndicesOfHalfEdge(m_mesh.m_halfEdges[AB]); - IndexType A,B,C; + size_t A,B,C; A = horizonEdgeVertexIndices[0]; B = horizonEdgeVertexIndices[1]; C = activePointIndex; - const IndexType newFaceIndex = m_mesh.addFace(); + const size_t newFaceIndex = m_mesh.addFace(); m_newFaceIndices.push_back(newFaceIndex); - const IndexType CA = m_newHalfEdgeIndices[2*i+0]; - const IndexType BC = m_newHalfEdgeIndices[2*i+1]; + const size_t CA = m_newHalfEdgeIndices[2*i+0]; + const size_t BC = m_newHalfEdgeIndices[2*i+1]; m_mesh.m_halfEdges[AB].m_next = BC; m_mesh.m_halfEdges[BC].m_next = CA; @@ -293,7 +291,7 @@ namespace quickhull { if (newFace.m_pointsOnPositiveSide) { assert(newFace.m_pointsOnPositiveSide->size()>0); if (!newFace.m_inFaceStack) { - faceList.push_back(newFaceIndex); + m_faceList.push_back(newFaceIndex); newFace.m_inFaceStack = 1; } } @@ -309,48 +307,48 @@ namespace quickhull { */ template - std::array QuickHull::getExtremeValues() { - std::array outIndices{0,0,0,0,0,0}; + std::array QuickHull::getExtremeValues() { + std::array outIndices{0,0,0,0,0,0}; T extremeVals[6] = {m_vertexData[0].x,m_vertexData[0].x,m_vertexData[0].y,m_vertexData[0].y,m_vertexData[0].z,m_vertexData[0].z}; const size_t vCount = m_vertexData.size(); for (size_t i=1;i& pos = m_vertexData[i]; if (pos.x>extremeVals[0]) { extremeVals[0]=pos.x; - outIndices[0]=(IndexType)i; + outIndices[0]=i; } else if (pos.xextremeVals[2]) { extremeVals[2]=pos.y; - outIndices[2]=(IndexType)i; + outIndices[2]=i; } else if (pos.yextremeVals[4]) { extremeVals[4]=pos.z; - outIndices[4]=(IndexType)i; + outIndices[4]=i; } else if (pos.z - bool QuickHull::reorderHorizonEdges(std::vector& horizonEdges) { + bool QuickHull::reorderHorizonEdges(std::vector& horizonEdges) { const size_t horizonEdgeCount = horizonEdges.size(); for (size_t i=0;i - T QuickHull::getScale(const std::array& extremeValues) { + T QuickHull::getScale(const std::array& extremeValues) { T s = 0; for (size_t i=0;i<6;i++) { const T* v = (const T*)(&m_vertexData[extremeValues[i]]); @@ -379,24 +377,24 @@ namespace quickhull { return s; } - template - MeshBuilder QuickHull::getInitialTetrahedron() { + template + void QuickHull::setupInitialTetrahedron() { const size_t vertexCount = m_vertexData.size(); // If we have at most 4 points, just return a degenerate tetrahedron: if (vertexCount <= 4) { - IndexType v[4] = {0,std::min((size_t)1,vertexCount-1),std::min((size_t)2,vertexCount-1),std::min((size_t)3,vertexCount-1)}; + size_t v[4] = {0,std::min((size_t)1,vertexCount-1),std::min((size_t)2,vertexCount-1),std::min((size_t)3,vertexCount-1)}; const Vector3 N = mathutils::getTriangleNormal(m_vertexData[v[0]],m_vertexData[v[1]],m_vertexData[v[2]]); const Plane trianglePlane(N,m_vertexData[v[0]]); if (trianglePlane.isPointOnPositiveSide(m_vertexData[v[3]])) { std::swap(v[0],v[1]); } - return MeshBuilder(v[0],v[1],v[2],v[3]); + return m_mesh.setup(v[0],v[1],v[2],v[3]); } // Find two most distant extreme points. T maxD = m_epsilonSquared; - std::pair selectedPoints; + std::pair selectedPoints; for (size_t i=0;i<6;i++) { for (size_t j=i+1;j<6;j++) { const T d = m_vertexData[ m_extremeValues[i] ].getSquaredDistanceTo( m_vertexData[ m_extremeValues[j] ] ); @@ -408,7 +406,7 @@ namespace quickhull { } if (maxD == m_epsilonSquared) { // A degenerate case: the point cloud seems to consists of a single point - return MeshBuilder(0,std::min((size_t)1,vertexCount-1),std::min((size_t)2,vertexCount-1),std::min((size_t)3,vertexCount-1)); + return m_mesh.setup(0,std::min((size_t)1,vertexCount-1),std::min((size_t)2,vertexCount-1),std::min((size_t)3,vertexCount-1)); } assert(selectedPoints.first != selectedPoints.second); @@ -430,12 +428,12 @@ namespace quickhull { auto it = std::find_if(m_vertexData.begin(),m_vertexData.end(),[&](const vec3& ve) { return ve != m_vertexData[selectedPoints.first] && ve != m_vertexData[selectedPoints.second]; }); - const IndexType thirdPoint = (it == m_vertexData.end()) ? selectedPoints.first : std::distance(m_vertexData.begin(),it); + const size_t thirdPoint = (it == m_vertexData.end()) ? selectedPoints.first : std::distance(m_vertexData.begin(),it); it = std::find_if(m_vertexData.begin(),m_vertexData.end(),[&](const vec3& ve) { return ve != m_vertexData[selectedPoints.first] && ve != m_vertexData[selectedPoints.second] && ve != m_vertexData[thirdPoint]; }); - const IndexType fourthPoint = (it == m_vertexData.end()) ? selectedPoints.first : std::distance(m_vertexData.begin(),it); - return MeshBuilder(selectedPoints.first,selectedPoints.second,thirdPoint,fourthPoint); + const size_t fourthPoint = (it == m_vertexData.end()) ? selectedPoints.first : std::distance(m_vertexData.begin(),it); + return m_mesh.setup(selectedPoints.first,selectedPoints.second,thirdPoint,fourthPoint); } // These three points form the base triangle for our tetrahedron. @@ -458,10 +456,10 @@ namespace quickhull { if (maxD == m_epsilon) { // All the points seem to lie on a 2D subspace of R^3. How to handle this? Well, let's add one extra point to the point cloud so that the convex hull will have volume. m_planar = true; - const vec3 N = mathutils::getTriangleNormal(baseTriangleVertices[1],baseTriangleVertices[2],baseTriangleVertices[0]); + const vec3 N1 = mathutils::getTriangleNormal(baseTriangleVertices[1],baseTriangleVertices[2],baseTriangleVertices[0]); m_planarPointCloudTemp.clear(); m_planarPointCloudTemp.insert(m_planarPointCloudTemp.begin(),m_vertexData.begin(),m_vertexData.end()); - const vec3 extraPoint = N + m_vertexData[0]; + const vec3 extraPoint = N1 + m_vertexData[0]; m_planarPointCloudTemp.push_back(extraPoint); maxI = m_planarPointCloudTemp.size()-1; m_vertexData = VertexDataSource(m_planarPointCloudTemp); @@ -474,26 +472,25 @@ namespace quickhull { } // Create a tetrahedron half edge mesh and compute planes defined by each triangle - MeshBuilder mesh(baseTriangle[0],baseTriangle[1],baseTriangle[2],maxI); - for (auto& f : mesh.m_faces) { - auto v = mesh.getVertexIndicesOfFace(f); + m_mesh.setup(baseTriangle[0],baseTriangle[1],baseTriangle[2],maxI); + for (auto& f : m_mesh.m_faces) { + auto v = m_mesh.getVertexIndicesOfFace(f); const Vector3& va = m_vertexData[v[0]]; const Vector3& vb = m_vertexData[v[1]]; const Vector3& vc = m_vertexData[v[2]]; - const Vector3 N = mathutils::getTriangleNormal(va, vb, vc); - const Plane trianglePlane(N,va); - f.m_P = trianglePlane; + const Vector3 N1 = mathutils::getTriangleNormal(va, vb, vc); + const Plane plane(N1,va); + f.m_P = plane; } // Finally we assign a face for each vertex outside the tetrahedron (vertices inside the tetrahedron have no role anymore) for (size_t i=0;i #include #include #include @@ -68,36 +68,45 @@ namespace quickhull { std::vector m_planarPointCloudTemp; VertexDataSource m_vertexData; MeshBuilder m_mesh; - std::array m_extremeValues; + std::array m_extremeValues; DiagnosticsData m_diagnostics; // Temporary variables used during iteration process - std::vector m_newFaceIndices; - std::vector m_newHalfEdgeIndices; - std::vector< std::unique_ptr> > m_disabledFacePointVectors; + std::vector m_newFaceIndices; + std::vector m_newHalfEdgeIndices; + std::vector< std::unique_ptr> > m_disabledFacePointVectors; + std::vector m_visibleFaces; + std::vector m_horizonEdges; + struct FaceData { + size_t m_faceIndex; + size_t m_enteredFromHalfEdge; // If the face turns out not to be visible, this half edge will be marked as horizon edge + FaceData(size_t fi, size_t he) : m_faceIndex(fi),m_enteredFromHalfEdge(he) {} + }; + std::vector m_possiblyVisibleFaces; + std::deque m_faceList; // Create a half edge mesh representing the base tetrahedron from which the QuickHull iteration proceeds. m_extremeValues must be properly set up when this is called. - MeshBuilder getInitialTetrahedron(); + void setupInitialTetrahedron(); // Given a list of half edges, try to rearrange them so that they form a loop. Return true on success. - bool reorderHorizonEdges(std::vector& horizonEdges); + bool reorderHorizonEdges(std::vector& horizonEdges); // Find indices of extreme values (max x, min x, max y, min y, max z, min z) for the given point cloud - std::array getExtremeValues(); + std::array getExtremeValues(); // Compute scale of the vertex data. - FloatType getScale(const std::array& extremeValues); + FloatType getScale(const std::array& extremeValues); // Each face contains a unique pointer to a vector of indices. However, many - often most - faces do not have any points on the positive // side of them especially at the the end of the iteration. When a face is removed from the mesh, its associated point vector, if such // exists, is moved to the index vector pool, and when we need to add new faces with points on the positive side to the mesh, // we reuse these vectors. This reduces the amount of std::vectors we have to deal with, and impact on performance is remarkable. - Pool> m_indexVectorPool; - inline std::unique_ptr> getIndexVectorFromPool(); - inline void reclaimToIndexVectorPool(std::unique_ptr>& ptr); + Pool> m_indexVectorPool; + inline std::unique_ptr> getIndexVectorFromPool(); + inline void reclaimToIndexVectorPool(std::unique_ptr>& ptr); // Associates a point with a face if the point resides on the positive side of the plane. Returns true if the points was on the positive side. - inline bool addPointToFace(typename MeshBuilder::Face& f, IndexType pointIndex); + inline bool addPointToFace(typename MeshBuilder::Face& f, size_t pointIndex); // This will update m_mesh from which we create the ConvexHull object that getConvexHull function returns void createConvexHalfEdgeMesh(); @@ -115,7 +124,10 @@ namespace quickhull { // useOriginalIndices: should the output mesh use same vertex indices as the original point cloud. If this is false, // then we generate a new vertex buffer which contains only the vertices that are part of the convex hull. // eps: minimum distance to a plane to consider a point being on positive of it (for a point cloud with scale 1) - ConvexHull getConvexHull(const std::vector>& pointCloud, bool CCW, bool useOriginalIndices, FloatType eps = defaultEps()); + ConvexHull getConvexHull(const std::vector>& pointCloud, + bool CCW, + bool useOriginalIndices, + FloatType eps = defaultEps()); // Computes convex hull for a given point cloud. // Params: @@ -124,8 +136,12 @@ namespace quickhull { // CCW: whether the output mesh triangles should have CCW orientation // useOriginalIndices: should the output mesh use same vertex indices as the original point cloud. If this is false, // then we generate a new vertex buffer which contains only the vertices that are part of the convex hull. - // eps: minimum distance to a plane to consider a point being on positive of it (for a point cloud with scale 1) - ConvexHull getConvexHull(const Vector3* vertexData, size_t vertexCount, bool CCW, bool useOriginalIndices, FloatType eps = defaultEps()); + // eps: minimum distance to a plane to consider a point being on positive side of it (for a point cloud with scale 1) + ConvexHull getConvexHull(const Vector3* vertexData, + size_t vertexCount, + bool CCW, + bool useOriginalIndices, + FloatType eps = defaultEps()); // Computes convex hull for a given point cloud. This function assumes that the vertex data resides in memory // in the following format: x_0,y_0,z_0,x_1,y_1,z_1,... @@ -135,8 +151,12 @@ namespace quickhull { // CCW: whether the output mesh triangles should have CCW orientation // useOriginalIndices: should the output mesh use same vertex indices as the original point cloud. If this is false, // then we generate a new vertex buffer which contains only the vertices that are part of the convex hull. - // eps: minimum distance to a plane to consider a point being on positive of it (for a point cloud with scale 1) - ConvexHull getConvexHull(const FloatType* vertexData, size_t vertexCount, bool CCW, bool useOriginalIndices, FloatType eps = defaultEps()); + // eps: minimum distance to a plane to consider a point being on positive side of it (for a point cloud with scale 1) + ConvexHull getConvexHull(const FloatType* vertexData, + size_t vertexCount, + bool CCW, + bool useOriginalIndices, + FloatType eps = defaultEps()); // Computes convex hull for a given point cloud. This function assumes that the vertex data resides in memory // in the following format: x_0,y_0,z_0,x_1,y_1,z_1,... @@ -144,10 +164,13 @@ namespace quickhull { // vertexData: pointer to the X component of the first point of the point cloud. // vertexCount: number of vertices in the point cloud // CCW: whether the output mesh triangles should have CCW orientation - // eps: minimum distance to a plane to consider a point being on positive of it (for a point cloud with scale 1) + // eps: minimum distance to a plane to consider a point being on positive side of it (for a point cloud with scale 1) // Returns: // Convex hull of the point cloud as a mesh object with half edge structure. - HalfEdgeMesh getConvexHullAsMesh(const FloatType* vertexData, size_t vertexCount, bool CCW, FloatType eps = defaultEps()); + HalfEdgeMesh getConvexHullAsMesh(const FloatType* vertexData, + size_t vertexCount, + bool CCW, + FloatType eps = defaultEps()); // Get diagnostics about last generated convex hull const DiagnosticsData& getDiagnostics() { @@ -160,14 +183,14 @@ namespace quickhull { */ template - std::unique_ptr> QuickHull::getIndexVectorFromPool() { - auto r = std::move(m_indexVectorPool.get()); + std::unique_ptr> QuickHull::getIndexVectorFromPool() { + auto r = m_indexVectorPool.get(); r->clear(); return r; } template - void QuickHull::reclaimToIndexVectorPool(std::unique_ptr>& ptr) { + void QuickHull::reclaimToIndexVectorPool(std::unique_ptr>& ptr) { const size_t oldSize = ptr->size(); if ((oldSize+1)*128 < ptr->capacity()) { // Reduce memory usage! Huge vectors are needed at the beginning of iteration when faces have many points on their positive side. Later on, smaller vectors will suffice. @@ -178,7 +201,7 @@ namespace quickhull { } template - bool QuickHull::addPointToFace(typename MeshBuilder::Face& f, IndexType pointIndex) { + bool QuickHull::addPointToFace(typename MeshBuilder::Face& f, size_t pointIndex) { const T D = mathutils::getSignedDistanceToPlane(m_vertexData[ pointIndex ],f.m_P); if (D>0 && D*D > m_epsilonSquared*f.m_P.m_sqrNLength) { if (!f.m_pointsOnPositiveSide) { diff --git a/libtascar/src/quickhull/README.md b/libtascar/src/quickhull/README.md index 6c608ec5..a36c081b 100644 --- a/libtascar/src/quickhull/README.md +++ b/libtascar/src/quickhull/README.md @@ -14,15 +14,17 @@ Basic usage: // Add points to point cloud ... auto hull = qh.getConvexHull(pointCloud, true, false); - auto indexBuffer = hull.getIndexBuffer(); - auto vertexBuffer = hull.getVertexBuffer(); + const auto& indexBuffer = hull.getIndexBuffer(); + const auto& vertexBuffer = hull.getVertexBuffer(); // Do what you want with the convex triangle mesh Vertex data can be passed as a pointer to float/double as long as the data is in X_0,Y_0,Z_0,X_1,Y_1,Z_1,...,X_N,Y_N_Z_N format: auto hull = qh.getConvexHull(&pointCloud[0].x, pointCloud.size(), true, false); -The first boolean parameter of getConvexHull specifies whether the output mesh should have its triangles in CCW orientation. The second boolean parameter specifies whether the output mesh should use vertex indices of the original point cloud. If it is false, a new vertex buffer is generated which consists only of those vertices that are part of the convex hull. +The first boolean parameter of getConvexHull specifies whether the resulting mesh should have its triangles in CCW orientation. + +The second boolean parameter specifies whether the mesh should use vertex indices of the original point cloud. If it is false, a new vertex buffer is generated which consists only of those vertices that are part of the convex hull. In this case, the new vertex buffer is owned by the returned ConvexHull object. Otherwise, the original point cloud is used as vertex buffer and since the vertices are not copied, make sure you don't call ConvexHull::getVertexBuffer after releasing the memory that contains the original point cloud data. This implementation is fast, because the convex hull is internally built using a half edge mesh representation which provides quick access to adjacent faces. It is also possible to get the output convex hull as a half edge mesh: diff --git a/libtascar/src/quickhull/Structs/Mesh.hpp b/libtascar/src/quickhull/Structs/Mesh.hpp index b9b24c70..f58c5afc 100644 --- a/libtascar/src/quickhull/Structs/Mesh.hpp +++ b/libtascar/src/quickhull/Structs/Mesh.hpp @@ -5,14 +5,13 @@ #include "Vector3.hpp" #include "Plane.hpp" #include "Pool.hpp" -#include "../Types.hpp" -#include #include #include #include #include #include "VertexDataSource.hpp" #include +#include namespace quickhull { @@ -20,33 +19,32 @@ namespace quickhull { class MeshBuilder { public: struct HalfEdge { - IndexType m_endVertex; - IndexType m_opp; - IndexType m_face; - IndexType m_next; + size_t m_endVertex; + size_t m_opp; + size_t m_face; + size_t m_next; void disable() { - m_endVertex = std::numeric_limits::max(); + m_endVertex = std::numeric_limits::max(); } bool isDisabled() const { - return m_endVertex == std::numeric_limits::max(); + return m_endVertex == std::numeric_limits::max(); } }; struct Face { - IndexType m_he; - Plane m_P; + size_t m_he; + Plane m_P{}; T m_mostDistantPointDist; - IndexType m_mostDistantPoint; + size_t m_mostDistantPoint; size_t m_visibilityCheckedOnIteration; std::uint8_t m_isVisibleFaceOnCurrentIteration : 1; std::uint8_t m_inFaceStack : 1; std::uint8_t m_horizonEdgesOnCurrentIteration : 3; // Bit for each half edge assigned to this face, each being 0 or 1 depending on whether the edge belongs to horizon edge - std::unique_ptr> m_pointsOnPositiveSide; + std::unique_ptr> m_pointsOnPositiveSide; - Face() : m_he(std::numeric_limits::max()), - m_P(), + Face() : m_he(std::numeric_limits::max()), m_mostDistantPointDist(0), m_mostDistantPoint(0), m_visibilityCheckedOnIteration(0), @@ -58,11 +56,11 @@ namespace quickhull { } void disable() { - m_he = std::numeric_limits::max(); + m_he = std::numeric_limits::max(); } bool isDisabled() const { - return m_he == std::numeric_limits::max(); + return m_he == std::numeric_limits::max(); } }; @@ -73,11 +71,11 @@ namespace quickhull { // When the mesh is modified and faces and half edges are removed from it, we do not actually remove them from the container vectors. // Insted, they are marked as disabled which means that the indices can be reused when we need to add new faces and half edges to the mesh. // We store the free indices in the following vectors. - std::vector m_disabledFaces,m_disabledHalfEdges; + std::vector m_disabledFaces,m_disabledHalfEdges; - IndexType addFace() { + size_t addFace() { if (m_disabledFaces.size()) { - IndexType index = m_disabledFaces.back(); + size_t index = m_disabledFaces.back(); auto& f = m_faces[index]; assert(f.isDisabled()); assert(!f.m_pointsOnPositiveSide); @@ -89,9 +87,9 @@ namespace quickhull { return m_faces.size()-1; } - IndexType addHalfEdge() { + size_t addHalfEdge() { if (m_disabledHalfEdges.size()) { - const IndexType index = m_disabledHalfEdges.back(); + const size_t index = m_disabledHalfEdges.back(); m_disabledHalfEdges.pop_back(); return index; } @@ -100,14 +98,14 @@ namespace quickhull { } // Mark a face as disabled and return a pointer to the points that were on the positive of it. - std::unique_ptr> disableFace(IndexType faceIndex) { + std::unique_ptr> disableFace(size_t faceIndex) { auto& f = m_faces[faceIndex]; f.disable(); m_disabledFaces.push_back(faceIndex); return std::move(f.m_pointsOnPositiveSide); } - void disableHalfEdge(IndexType heIndex) { + void disableHalfEdge(size_t heIndex) { auto& he = m_halfEdges[heIndex]; he.disable(); m_disabledHalfEdges.push_back(heIndex); @@ -116,7 +114,15 @@ namespace quickhull { MeshBuilder() = default; // Create a mesh with initial tetrahedron ABCD. Dot product of AB with the normal of triangle ABC should be negative. - MeshBuilder(IndexType a, IndexType b, IndexType c, IndexType d) { + void setup(size_t a, size_t b, size_t c, size_t d) { + m_faces.clear(); + m_halfEdges.clear(); + m_disabledFaces.clear(); + m_disabledHalfEdges.clear(); + + m_faces.reserve(4); + m_halfEdges.reserve(12); + // Create halfedges HalfEdge AB; AB.m_endVertex = b; @@ -220,8 +226,8 @@ namespace quickhull { m_faces.push_back(std::move(CBD)); } - std::array getVertexIndicesOfFace(const Face& f) const { - std::array v; + std::array getVertexIndicesOfFace(const Face& f) const { + std::array v; const HalfEdge* he = &m_halfEdges[f.m_he]; v[0] = he->m_endVertex; he = &m_halfEdges[he->m_next]; @@ -231,11 +237,11 @@ namespace quickhull { return v; } - std::array getVertexIndicesOfHalfEdge(const HalfEdge& he) const { + std::array getVertexIndicesOfHalfEdge(const HalfEdge& he) const { return {m_halfEdges[he.m_opp].m_endVertex,he.m_endVertex}; } - std::array getHalfEdgeIndicesOfFace(const Face& f) const { + std::array getHalfEdgeIndicesOfFace(const Face& f) const { return {f.m_he,m_halfEdges[f.m_he].m_next,m_halfEdges[m_halfEdges[f.m_he].m_next].m_next}; } }; diff --git a/libtascar/src/quickhull/Structs/VertexDataSource.hpp b/libtascar/src/quickhull/Structs/VertexDataSource.hpp index 162b643b..b59b15c5 100644 --- a/libtascar/src/quickhull/Structs/VertexDataSource.hpp +++ b/libtascar/src/quickhull/Structs/VertexDataSource.hpp @@ -15,7 +15,7 @@ namespace quickhull { } - VertexDataSource(const std::vector>& vec) : m_ptr(&vec[0]), m_count(vec.size()) { + VertexDataSource(const std::vector>& vec) : m_ptr(vec.data()), m_count(vec.size()) { } diff --git a/libtascar/src/quickhull/Tests/CMakeLists.txt b/libtascar/src/quickhull/Tests/CMakeLists.txt index 6cb06397..b9c285c8 100644 --- a/libtascar/src/quickhull/Tests/CMakeLists.txt +++ b/libtascar/src/quickhull/Tests/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.11) +project(QuickHullTests) set(SOURCE_FILES ${CMAKE_SOURCE_DIR}/QuickHullTests.cpp @@ -6,5 +7,5 @@ set(SOURCE_FILES ${CMAKE_SOURCE_DIR}/../QuickHull.cpp ) -add_definitions(-std=c++17) +add_definitions(-std=c++11) add_executable(QuickHullTests ${SOURCE_FILES}) diff --git a/libtascar/src/quickhull/Tests/QuickHullTests.cpp b/libtascar/src/quickhull/Tests/QuickHullTests.cpp index 57ecbcaa..dd833ff2 100644 --- a/libtascar/src/quickhull/Tests/QuickHullTests.cpp +++ b/libtascar/src/quickhull/Tests/QuickHullTests.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace quickhull { @@ -19,11 +20,11 @@ namespace quickhull { return from + (FloatType)dist(rng)*(to-from); }; - void assertSameValue(FloatType a, FloatType b) { + static void assertSameValue(FloatType a, FloatType b) { assert(std::abs(a-b)<0.0001f); } - void testVector3() { + static void testVector3() { typedef Vector3 vec3; vec3 a(1,0,0); vec3 b(1,0,0); @@ -38,7 +39,7 @@ namespace quickhull { } template - std::vector> createSphere(T radius, size_t M, Vector3 offset = Vector3(0,0,0)) { + static std::vector> createSphere(T radius, size_t M, Vector3 offset = Vector3(0,0,0)) { std::vector> pc; const T pi = 3.14159f; for (int i=0;i<=M;i++) { @@ -55,7 +56,7 @@ namespace quickhull { return pc; } - void sphereTest() { + static void sphereTest() { QuickHull qh; FloatType y = 1; for (;;) { @@ -75,7 +76,6 @@ namespace quickhull { for (;;) { auto pc = createSphere(1, i, Vector3(0,0,0)); auto hull = qh.getConvexHull(pc,true,false,eps); - std::cout << i << ":" << pc.size() << " : " << hull.getVertexBuffer().size() << " at eps=" << eps << std::endl; if (qh.getDiagnostics().m_failedHorizonEdges) { // This should not happen assert(false); @@ -87,16 +87,14 @@ namespace quickhull { } else { eps *= 0.5f; - std::cout << "Epsilon to " << eps << std::endl; } - - if (i == 500) { + if (i == 100) { break; } } } - void testPlanarCase() { + static void testPlanarCase() { QuickHull qh; std::vector pc; pc.emplace_back(-3.000000f, -0.250000f, -0.800000f); @@ -108,10 +106,12 @@ namespace quickhull { assert(hull.getVertexBuffer().size()==4); } - void testHalfEdgeOutput() { - QuickHull qh; + static void testHalfEdgeOutput() { + QuickHull qh; - // 8 corner vertices of a cube + tons of vertices inside. Output should be a half edge mesh with 12 faces (6 cube faces with 2 triangles per face) and 36 half edges (3 half edges per face). + // 8 corner vertices of a cube + tons of vertices inside. + // Output should be a half edge mesh with 12 faces (6 cube faces with 2 triangles + // per face) and 36 half edges (3 half edges per face). std::vector pc; for (int h=0;h<1000;h++) { pc.emplace_back(rnd(-1,1),rnd(-1,1),rnd(-1,1)); @@ -123,9 +123,17 @@ namespace quickhull { assert(mesh.m_faces.size() == 12); assert(mesh.m_halfEdges.size() == 36); assert(mesh.m_vertices.size() == 8); + + // Verify that for each face f, f.halfedgeIndex equals next(next(next(f.halfedgeIndex))). + for (const auto& f : mesh.m_faces) { + size_t next = mesh.m_halfEdges[f.m_halfEdgeIndex].m_next; + next = mesh.m_halfEdges[next].m_next; + next = mesh.m_halfEdges[next].m_next; + assert(next == f.m_halfEdgeIndex); + } } - void testPlanes() { + static void testPlanes() { Vector3 N(1,0,0); Vector3 p(2,0,0); Plane P(N,p); @@ -141,6 +149,44 @@ namespace quickhull { assertSameValue(dist,8); } + static void testVertexBufferAddress() { + QuickHull qh; + std::vector pc; + pc.emplace_back(0, 0, 0); + pc.emplace_back(1, 0, 0); + pc.emplace_back(0, 1, 0); + + for (size_t i=0;i<2;i++) { + const bool useOriginalIndices = i != 0; + const auto hull = qh.getConvexHull(pc,false, useOriginalIndices); + const auto vertices = hull.getVertexBuffer(); + assert(vertices.size() > 0); + assert((&vertices[0] == &pc[0]) == useOriginalIndices); + } + } + + static void testNormals() { + QuickHull qh; + std::vector pc; + pc.emplace_back(0, 0, 0); + pc.emplace_back(1, 0, 0); + pc.emplace_back(0, 1, 0); + + std::array normal; + for (size_t i=0;i<2;i++) { + const bool CCW = i; + const auto hull = qh.getConvexHull(pc,CCW,false); + const auto vertices = hull.getVertexBuffer(); + const auto indices = hull.getIndexBuffer(); + assert(vertices.size() == 3); + assert(indices.size() >= 6); + const vec3 triangle[3] = { vertices[indices[0]], vertices[indices[1]], vertices[indices[2]] }; + normal[i] = mathutils::getTriangleNormal(triangle[0], triangle[1], triangle[2]); + } + const auto dot = normal[0].dotProduct(normal[1]); + assertSameValue(dot, -1); + } + int run() { // Setup test env const size_t N = 200; @@ -252,22 +298,9 @@ namespace quickhull { } } - // Test 7 - for (int h=0;h<100;h++) { - pc.clear(); - const vec3 v1(rnd(-1,1),rnd(-1,1),rnd(-1,1)); - const vec3 v2(rnd(-1,1),rnd(-1,1),rnd(-1,1)); - pc.push_back(v1); - pc.push_back(v2); - for (int i=0;i