#include "NConvexHull.h" #include "MathUtil.h" #include "Common/CScopedTimer.h" #include "Common/Log.h" #include "Common/NBasics.h" #include "Common/Math/CPlane.h" #include "Common/Math/CVector3f.h" #include #include #include #include namespace NConvexHull { /** Quickhull implementation for building convex hulls */ class CQuickhullImpl { struct SVertex; struct SHalfEdge; struct SFace; /** A convex hull vertex */ struct SVertex { // The vertex position CVector3f Position; // Conflict face for this vertex (a hull face that is visible from this vertex) SFace* pConflictFace; // Squared distance from conflict face float ConflictDistance; }; /** A convex hull half-edge */ struct SHalfEdge { // The first vertex in the edge uint32_t Tail; // Circularly linked list of edges in the face SHalfEdge* pPrev; SHalfEdge* pNext; // The mirror of this edge on the opposing face SHalfEdge* pTwin; // The face this edge belongs to SFace* pFace; }; /** A convex hull face */ struct SFace { // Linked list of faces in the hull SFace* pPrev; SFace* pNext; // Circular linked list of edges in the face SHalfEdge* pFirstEdge; // Face plane CPlane Plane; // "Visited" flag for horizon check bool Visited; }; /** Input vertex data. This data is generated at the start of the algorithm and not modified. * As such, this array represents the input data, and may contain vertices not used in the output hull. */ std::vector mVertices; /** Face linked list tail */ SFace* mpFaceTail = nullptr; /** The number of faces in the hull */ uint32_t mNumFaces = 0; /** Threshold for distance comparisons (epsilon) */ float mEpsilon = FLT_EPSILON; /** True if the algorithm completed successfully */ bool mSuccess = false; /** Create and initialize a new empty face with no edges */ SFace* CreateFace() { SFace* pFace = new SFace; pFace->pPrev = mpFaceTail; pFace->pNext = nullptr; pFace->pFirstEdge = nullptr; pFace->Visited = false; if (mpFaceTail) mpFaceTail->pNext = pFace; mpFaceTail = pFace; mNumFaces++; return pFace; } /** Delete a face from the hull. This function can leave dangling pointers. */ void DeleteFace(const SFace* pFace) { if (pFace->pPrev) pFace->pPrev->pNext = pFace->pNext; if (pFace->pNext) pFace->pNext->pPrev = pFace->pPrev; if (pFace == mpFaceTail) mpFaceTail = pFace->pPrev; const SHalfEdge* pEdge = pFace->pFirstEdge; do { const SHalfEdge* pNext = pEdge->pNext; delete pEdge; pEdge = pNext; } while (pEdge != pFace->pFirstEdge); delete pFace; mNumFaces--; } /** Creates and initializes a half edge. */ static SHalfEdge* CreateHalfEdge(SFace* pFace, uint32_t TailVertexIdx, SHalfEdge* pPrev, SHalfEdge* pNext, SHalfEdge* pTwin) { SHalfEdge* pEdge = new SHalfEdge; pEdge->Tail = TailVertexIdx; pEdge->pTwin = pTwin; pEdge->pPrev = pPrev; pEdge->pNext = pNext; pEdge->pFace = pFace; if (pPrev) pPrev->pNext = pEdge; if (pNext) pNext->pPrev = pEdge; if (pTwin) pTwin->pTwin = pEdge; if (pFace && !pFace->pFirstEdge) pFace->pFirstEdge = pEdge; return pEdge; } /** Calculate and set the plane that a face lies on */ void CalcFacePlane(SFace* pFace) const { ASSERT(pFace->pFirstEdge != nullptr); ASSERT(pFace->pFirstEdge->pNext != nullptr); ASSERT(pFace->pFirstEdge->pNext->pNext != nullptr); uint32_t Idx0 = pFace->pFirstEdge->Tail; uint32_t Idx1 = pFace->pFirstEdge->pNext->Tail; uint32_t Idx2 = pFace->pFirstEdge->pNext->pNext->Tail; const CVector3f& V0 = mVertices[Idx0].Position; const CVector3f& V1 = mVertices[Idx1].Position; const CVector3f& V2 = mVertices[Idx2].Position; const CVector3f V0to1 = (V1 - V0); const CVector3f V1to2 = (V2 - V1); const CVector3f Norm = V0to1.Cross(V1to2).Normalized(); pFace->Plane = CPlane(Norm, V0); } /** Calculate the centroid of a face */ CVector3f CalcFaceCentroid(const SFace* pFace) const { CVector3f Out; uint32_t NumVertices = 0; const SHalfEdge* pEdge = pFace->pFirstEdge; do { Out += mVertices[pEdge->Tail].Position; NumVertices++; pEdge = pEdge->pNext; } while (pEdge != pFace->pFirstEdge); return Out; } /** Initialize face conflict data on all vertices. */ void FillFaceConflicts() { for (size_t i = 0; i < mVertices.size(); i++) { SVertex& V = mVertices[i]; V.pConflictFace = nullptr; V.ConflictDistance = FLT_MAX; for (SFace* pFace = mpFaceTail; pFace; pFace = pFace->pPrev) { float Dist = pFace->Plane.DistanceFromPoint(V.Position); if (Dist < -mEpsilon && std::abs(Dist) < V.ConflictDistance) { V.ConflictDistance = std::abs(Dist); V.pConflictFace = pFace; } } } } /** Generate the initial tetrahedron for the hull */ bool InitHull(const std::vector& kInPoints) { // We need at least 4 input vertices to create a hull. If this is not the case, fail out. if (kInPoints.size() < 4) { return false; } // Generate vertices mVertices.resize(kInPoints.size()); for (size_t i = 0; i < kInPoints.size(); i++) { mVertices[i].Position = kInPoints[i]; mVertices[i].pConflictFace = nullptr; mVertices[i].ConflictDistance = 0.f; } // Identify the most extremal vertices along each cardinal axis. // These will be the first vertices to be added to the hull. const CVector3f& V0 = mVertices[0].Position; int MinXIdx = 0, MinYIdx = 0, MinZIdx = 0, MaxXIdx = 0, MaxYIdx = 0, MaxZIdx = 0; float MinX = V0.X, MinY = V0.Y, MinZ = V0.Z, MaxX = V0.X, MaxY = V0.Y, MaxZ = V0.Z; for (int i = 1; i < kInPoints.size(); i++) { const CVector3f& V = kInPoints[i]; if (V.X < MinX) { MinX = V.X; MinXIdx = i; } if (V.X > MaxX) { MaxX = V.X; MaxXIdx = i; } if (V.Y < MinY) { MinY = V.Y; MinYIdx = i; } if (V.Y > MaxY) { MaxY = V.Y; MaxYIdx = i; } if (V.Z < MinZ) { MinZ = V.Z; MinZIdx = i; } if (V.Z > MaxZ) { MaxZ = V.Z; MaxZIdx = i; } } // Now we have the the most extremal points along each axis - determine which are furthest apart int Point0, Point1; float XRange = MaxX - MinX; float YRange = MaxY - MinY; float ZRange = MaxZ - MinZ; if (XRange > YRange && XRange > ZRange) { Point0 = MinXIdx; Point1 = MaxXIdx; } else if (YRange > XRange && YRange > ZRange) { Point0 = MinYIdx; Point1 = MaxYIdx; } else { Point0 = MinZIdx; Point1 = MaxZIdx; } // Also, calculate our epsilon threshold we will use for various distance comparisons float AbsX = std::max(std::abs(MinX), std::abs(MaxX)); float AbsY = std::max(std::abs(MinY), std::abs(MaxY)); float AbsZ = std::max(std::abs(MinZ), std::abs(MaxZ)); mEpsilon = (AbsX + AbsY + AbsZ) * FLT_EPSILON * 2.f; // We now have a line segment defined by Point0 and Point1. // Determine which remaining vertex is furthest from this line. const CVector3f& kP0 = kInPoints[Point0]; const CVector3f& kP1 = kInPoints[Point1]; const CVector3f& kLineNormal = (kP1 - kP0).Normalized(); float MaxSqDist = 0.f; int MaxIdx = -1; for (int i = 0; i < kInPoints.size(); i++) { if (i != Point0 && i != Point1) { const CVector3f& V = kInPoints[i]; float SqBase = (V - kP0).Dot(kLineNormal); float SqHypotenuse = kP0.SquaredDistance(V); float SqDist = SqHypotenuse - SqBase; if (SqDist > MaxSqDist) { MaxSqDist = SqDist; MaxIdx = i; } } } int Point2 = MaxIdx; const CVector3f& kP2 = kInPoints[Point2]; // Now find the point which is furthest from this plane to form a tetrahedron // This is the initial hull from which we can expand and add more vertices const CVector3f& kPlaneNormal = (kP1-kP0).Cross(kP2-kP1).Normalized(); const CPlane kBasePlane(kPlaneNormal, kP0); float MaxDist = 0.f; MaxIdx = -1; for (int i = 0; i < kInPoints.size(); i++) { if (i != Point0 && i != Point1 && i != Point2) { const CVector3f& V = kInPoints[i]; float Dist = std::abs(kBasePlane.DistanceFromPoint(V)); if (Dist > MaxDist) { MaxDist = Dist; MaxIdx = i; } } } // This is an edge case that can occur if all input points are planar if (MaxIdx == -1 || MaxDist <= mEpsilon) { return false; } int Point3 = MaxIdx; const CVector3f& kP3 = kInPoints[Point3]; // Check which side of the initial triangle point 3 is on to determine if we need to flip winding if (kBasePlane.DistanceFromPoint(kP3) < 0.f) { int Tmp = Point2; Point2 = Point0; Point0 = Tmp; } // Now we can construct the first faces of the hull. // Base: 0,1,2 SFace* pBase = CreateFace(); SHalfEdge* pEdge01 = CreateHalfEdge(pBase, Point0, 0, 0, 0); SHalfEdge* pEdge12 = CreateHalfEdge(pBase, Point1, pEdge01, 0, 0); SHalfEdge* pEdge20 = CreateHalfEdge(pBase, Point2, pEdge12, pEdge01, 0); CalcFacePlane(pBase); // Side0: 0,3,1 SFace* pSide0 = CreateFace(); SHalfEdge* pEdge03 = CreateHalfEdge(pSide0, Point0, 0, 0, 0); SHalfEdge* pEdge31 = CreateHalfEdge(pSide0, Point3, pEdge03, 0, 0); SHalfEdge* pEdge10 = CreateHalfEdge(pSide0, Point1, pEdge31, pEdge03, pEdge01); CalcFacePlane(pSide0); // Side1: 3,2,1 SFace* pSide1 = CreateFace(); SHalfEdge* pEdge32 = CreateHalfEdge(pSide1, Point3, 0, 0, 0); SHalfEdge* pEdge21 = CreateHalfEdge(pSide1, Point2, pEdge32, 0, pEdge12); SHalfEdge* pEdge13 = CreateHalfEdge(pSide1, Point1, pEdge21, pEdge32, pEdge31); CalcFacePlane(pSide1); // Side2: 0,2,3 SFace* pSide2 = CreateFace(); SHalfEdge* pEdge02 = CreateHalfEdge(pSide2, Point0, 0, 0, pEdge20); SHalfEdge* pEdge23 = CreateHalfEdge(pSide2, Point2, pEdge02, 0, pEdge32); SHalfEdge* pEdge30 = CreateHalfEdge(pSide2, Point3, pEdge23, pEdge02, pEdge03); CalcFacePlane(pSide2); // Calculate initial face conflicts, then clear conflict data for vertices in the initial hull FillFaceConflicts(); mVertices[Point0].pConflictFace = nullptr; mVertices[Point1].pConflictFace = nullptr; mVertices[Point2].pConflictFace = nullptr; mVertices[Point3].pConflictFace = nullptr; return true; } /** Look for the next vertex to add to the hull. Returns false when hull is complete. */ bool ResolveNextConflict() { // Look for the conflict vertex with the largest distance from the hull. // This is the next vertex that will be added to the hull. uint32_t VertexIndex = 0xFFFFFFFF; float MaxDist = 0.f; for (uint32_t i = 0; i < mVertices.size(); i++) { if (mVertices[i].pConflictFace != nullptr && mVertices[i].ConflictDistance > MaxDist) { VertexIndex = i; MaxDist = mVertices[i].ConflictDistance; } } // No vertex found? All conflicts are resolved if (VertexIndex == 0xFFFFFFFF) return false; AddToHull(VertexIndex); return true; } /** Recursive function for building a list of horizon edges for a given vertex */ void RecursiveBuildHorizon(SVertex* pVertex, SHalfEdge* pFirstEdge, std::vector& OutVisibleFaces, std::vector& OutHorizon) { // Ensure we don't visit the same face multiple times SFace* pFace = pFirstEdge->pFace; if (pFace->Visited) return; pFace->Visited = true; OutVisibleFaces.push_back(pFace); // Depth-first iteration of all edges SHalfEdge* pEdge = pFirstEdge; do { // Check if the face on the other side of the edge is visible. if (pEdge->pTwin->pFace->Plane.DistanceFromPoint(pVertex->Position) < -mEpsilon) { RecursiveBuildHorizon(pVertex, pEdge->pTwin, OutVisibleFaces, OutHorizon); } else { OutHorizon.push_back(pEdge); } pEdge = pEdge->pNext; } while (pEdge != pFirstEdge); } /** Adds a new vertex to the convex hull */ void AddToHull(uint32_t VertexIndex) { SVertex& Vertex = mVertices[VertexIndex]; std::vector Horizon; std::vector VisibleFaces; RecursiveBuildHorizon(&Vertex, Vertex.pConflictFace->pFirstEdge, VisibleFaces, Horizon); // With a list of edges and faces, we can now expand the hull to include the new vertex. // Start by creating a new face linking each horizon edge to the new vertex. std::vector NewFaces(Horizon.size()); SHalfEdge* pFirstNewSide = nullptr; SHalfEdge* pLastNewSide = nullptr; for (size_t EdgeIdx = 0; EdgeIdx < Horizon.size(); EdgeIdx++) { SHalfEdge* pEdge = Horizon[EdgeIdx]; uint32_t V0 = pEdge->Tail; uint32_t V1 = pEdge->pTwin->Tail; uint32_t V2 = VertexIndex; if (V0 == V1 || V1 == V2 || V2 == V0) { NLog::Error("Quickhull: Degenerate face being constructed: {}/{}/{}", V0, V1, V2); } // Disassociate the edge's twin from the edge, since it is being deleted soon SHalfEdge* pOldTwin = pEdge->pTwin; pEdge->pTwin = nullptr; pOldTwin->pTwin = nullptr; // Create the new face SFace* pFace = CreateFace(); SHalfEdge* pEdge01 = CreateHalfEdge(pFace, V0, 0, 0, pOldTwin); SHalfEdge* pEdge12 = CreateHalfEdge(pFace, V1, pEdge01, 0, 0); SHalfEdge* pEdge20 = CreateHalfEdge(pFace, V2, pEdge12, pEdge01, pLastNewSide); CalcFacePlane(pFace); NewFaces[EdgeIdx] = pFace; if (EdgeIdx == 0) { pFirstNewSide = pEdge20; } pLastNewSide = pEdge12; } pFirstNewSide->pTwin = pLastNewSide; pLastNewSide->pTwin = pFirstNewSide; // Clear conflict data from the vertex, since it is now part of the hull. Vertex.pConflictFace = nullptr; Vertex.ConflictDistance = FLT_MAX; // Assign old conflicts to one of the new faces, then delete old faces/edges for (auto* pOldFace : VisibleFaces) { for (auto& V : mVertices) { if (V.pConflictFace == pOldFace) { // Found a conflict. Remap to one of the new faces. V.pConflictFace = nullptr; V.ConflictDistance = FLT_MAX; for (auto* Face : NewFaces) { float Dist = Face->Plane.DistanceFromPoint(V.Position); if (Dist < -mEpsilon && std::abs(Dist) < V.ConflictDistance) { V.ConflictDistance = std::abs(Dist); V.pConflictFace = Face; } } } } DeleteFace(pOldFace); } } public: /** Quickhull entry point; instantiating the class will run the algorithm */ explicit CQuickhullImpl(const std::vector& kInPoints) { SCOPED_TIMER(Quickhull); if (InitHull(kInPoints)) { while (ResolveNextConflict()) {} mSuccess = true; } } ~CQuickhullImpl() { while (mpFaceTail) { DeleteFace(mpFaceTail); } } /** Returns whether the algorithm completed successfully */ inline bool CompletedSuccessfully() const { return mSuccess; } /** Retrieve output data from the algorithm */ void GetHullVertices(std::vector& OutVertices) const { // Note that our internal vertex array matches the input vertices, not the output vertices. // Iterate all faces to get a list of used vertices. OutVertices.reserve(mVertices.size()); SFace* pFace = mpFaceTail; while (pFace) { SHalfEdge* pEdge = pFace->pFirstEdge; do { NBasics::VectorAddUnique(OutVertices, mVertices[pEdge->Tail].Position); pEdge = pEdge->pNext; } while (pEdge != pFace->pFirstEdge); } } void GetHullPlanes(std::vector& OutPlanes) const { // This is a slight hack due to the fact that there is currently no face merging OutPlanes.reserve(mNumFaces); SFace* pFace = mpFaceTail; while (pFace) { NBasics::VectorAddUnique(OutPlanes, pFace->Plane); pFace = pFace->pPrev; } OutPlanes.shrink_to_fit(); } void GetHullTriangles(std::vector& OutVertices, std::vector& OutTriangleIndices) const { // Note that our internal vertex array matches the input vertices, not the output vertices. // Iterate all faces to get a list of used vertices and generate triangle data. // This function assumes that faces may have more than 3 edges. OutVertices.reserve(mVertices.size()); OutTriangleIndices.reserve(mNumFaces * 3); SFace* pFace = mpFaceTail; while (pFace) { SHalfEdge* pEdge = pFace->pFirstEdge; uint16_t FirstVert = 0xFFFF; uint16_t LastVert = 0xFFFF; do { const SVertex& Vertex = mVertices[pEdge->Tail]; auto VertexIndex = (uint16_t) NBasics::VectorAddUnique(OutVertices, Vertex.Position); if (FirstVert == 0xFFFF) { FirstVert = VertexIndex; } else if (LastVert != 0xFFFF) { OutTriangleIndices.push_back(FirstVert); OutTriangleIndices.push_back(LastVert); OutTriangleIndices.push_back(VertexIndex); } LastVert = VertexIndex; pEdge = pEdge->pNext; } while (pEdge != pFace->pFirstEdge); pFace = pFace->pPrev; } OutVertices.shrink_to_fit(); OutTriangleIndices.shrink_to_fit(); } }; /** Generate vertices for a convex hull */ bool BuildConvexHullVertices(const std::vector& kInPoints, std::vector& OutVertices) { CQuickhullImpl Quickhull(kInPoints); if (Quickhull.CompletedSuccessfully()) { Quickhull.GetHullVertices(OutVertices); return true; } return false; } /** Generate face planes for a convex hull */ bool BuildConvexHullPlanes(const std::vector& kInPoints, std::vector& OutPlanes) { CQuickhullImpl Quickhull(kInPoints); if (Quickhull.CompletedSuccessfully()) { Quickhull.GetHullPlanes(OutPlanes); return true; } return false; } /** Generate mesh data for a convex hull */ bool BuildConvexHullMesh(const std::vector& kInPoints, std::vector& OutVertices, std::vector& OutTriangleIndices) { CQuickhullImpl Quickhull(kInPoints); if (Quickhull.CompletedSuccessfully()) { Quickhull.GetHullTriangles(OutVertices, OutTriangleIndices); return true; } return false; } }