Path: blob/master/thirdparty/manifold/src/quickhull.h
9902 views
// Copyright 2024 The Manifold Authors.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.13//14// Derived from the public domain work of Antti Kuukka at15// https://github.com/akuukka/quickhull1617/*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 the21*generated convex hull as a triangle mesh.22*23*24*25* The implementation is thread-safe if each thread is using its own QuickHull26*object.27*28*29* SUMMARY OF THE ALGORITHM:30* - Create initial simplex (tetrahedron) using extreme points. We have31*four faces now and they form a convex mesh M.32* - For each point, assign them to the first face for which they are on33*the positive side of (so each point is assigned to at most one face). Points34*inside the initial tetrahedron are left behind now and no longer affect the35*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 stack39* - From the points assigned to F, pick the point P that is40*farthest away from the plane defined by F.41* - Find all faces of M that have P on their positive side. Let us42*call these the "visible faces".43* - Because of the way M is constructed, these faces are44*connected. Solve their horizon edge loop.45* - "Extrude to P": Create new faces by connecting46*P with the points belonging to the horizon edge. Add the new faces to M and47*remove the visible faces from M.48* - Each point that was assigned to visible faces is now assigned49*to at most one of the newly created faces.50* - Those new faces that have points assigned to them are added to51*the top of Face Stack.52* - M is now the convex hull.53*54* */55#pragma once56#include <array>57#include <deque>58#include <vector>5960#include "shared.h"61#include "vec.h"6263namespace manifold {6465class Pool {66std::vector<std::unique_ptr<Vec<size_t>>> data;6768public:69void clear() { data.clear(); }7071void reclaim(std::unique_ptr<Vec<size_t>>& ptr) {72data.push_back(std::move(ptr));73}7475std::unique_ptr<Vec<size_t>> get() {76if (data.size() == 0) {77return std::make_unique<Vec<size_t>>();78}79auto it = data.end() - 1;80std::unique_ptr<Vec<size_t>> r = std::move(*it);81data.erase(it);82return r;83}84};8586class Plane {87public:88vec3 N;8990// Signed distance (if normal is of length 1) to the plane from origin91double D;9293// Normal length squared94double sqrNLength;9596bool isPointOnPositiveSide(const vec3& Q) const {97double d = la::dot(N, Q) + D;98if (d >= 0) return true;99return false;100}101102Plane() = default;103104// Construct a plane using normal N and any point P on the plane105Plane(const vec3& N, const vec3& P)106: N(N), D(la::dot(-N, P)), sqrNLength(la::dot(N, N)) {}107};108109struct Ray {110const vec3 S;111const vec3 V;112const double VInvLengthSquared;113114Ray(const vec3& S, const vec3& V)115: S(S), V(V), VInvLengthSquared(1 / (la::dot(V, V))) {}116};117118class MeshBuilder {119public:120struct Face {121int he;122Plane P{};123double mostDistantPointDist = 0.0;124size_t mostDistantPoint = 0;125size_t visibilityCheckedOnIteration = 0;126std::uint8_t isVisibleFaceOnCurrentIteration : 1;127std::uint8_t inFaceStack : 1;128// Bit for each half edge assigned to this face, each being 0 or 1 depending129// on whether the edge belongs to horizon edge130std::uint8_t horizonEdgesOnCurrentIteration : 3;131std::unique_ptr<Vec<size_t>> pointsOnPositiveSide;132133Face(size_t he)134: he(he),135isVisibleFaceOnCurrentIteration(0),136inFaceStack(0),137horizonEdgesOnCurrentIteration(0) {}138139Face()140: he(-1),141isVisibleFaceOnCurrentIteration(0),142inFaceStack(0),143horizonEdgesOnCurrentIteration(0) {}144145void disable() { he = -1; }146147bool isDisabled() const { return he == -1; }148};149150// Mesh data151std::vector<Face> faces;152Vec<Halfedge> halfedges;153Vec<int> halfedgeToFace;154Vec<int> halfedgeNext;155156// When the mesh is modified and faces and half edges are removed from it, we157// do not actually remove them from the container vectors. Insted, they are158// marked as disabled which means that the indices can be reused when we need159// to add new faces and half edges to the mesh. We store the free indices in160// the following vectors.161Vec<size_t> disabledFaces, disabledHalfedges;162163size_t addFace();164165size_t addHalfedge();166167// Mark a face as disabled and return a pointer to the points that were on the168// positive of it.169std::unique_ptr<Vec<size_t>> disableFace(size_t faceIndex) {170auto& f = faces[faceIndex];171f.disable();172disabledFaces.push_back(faceIndex);173return std::move(f.pointsOnPositiveSide);174}175176void disableHalfedge(size_t heIndex) {177auto& he = halfedges[heIndex];178he.pairedHalfedge = -1;179disabledHalfedges.push_back(heIndex);180}181182MeshBuilder() = default;183184// Create a mesh with initial tetrahedron ABCD. Dot product of AB with the185// normal of triangle ABC should be negative.186void setup(int a, int b, int c, int d);187188std::array<int, 3> getVertexIndicesOfFace(const Face& f) const;189190std::array<int, 2> getVertexIndicesOfHalfEdge(const Halfedge& he) const {191return {halfedges[he.pairedHalfedge].endVert, he.endVert};192}193194std::array<int, 3> getHalfEdgeIndicesOfFace(const Face& f) const {195return {f.he, halfedgeNext[f.he], halfedgeNext[halfedgeNext[f.he]]};196}197};198199class HalfEdgeMesh {200public:201Vec<vec3> vertices;202// Index of one of the half edges of the faces203std::vector<size_t> halfEdgeIndexFaces;204Vec<Halfedge> halfedges;205Vec<int> halfedgeToFace;206Vec<int> halfedgeNext;207208HalfEdgeMesh(const MeshBuilder& builderObject,209const VecView<vec3>& vertexData);210};211212double defaultEps();213214class QuickHull {215struct FaceData {216int faceIndex;217// If the face turns out not to be visible, this half edge will be marked as218// horizon edge219int enteredFromHalfedge;220};221222double m_epsilon, epsilonSquared, scale;223bool planar;224Vec<vec3> planarPointCloudTemp;225VecView<vec3> originalVertexData;226MeshBuilder mesh;227std::array<size_t, 6> extremeValues;228size_t failedHorizonEdges = 0;229230// Temporary variables used during iteration process231Vec<size_t> newFaceIndices;232Vec<size_t> newHalfedgeIndices;233Vec<size_t> visibleFaces;234Vec<size_t> horizonEdgesData;235Vec<FaceData> possiblyVisibleFaces;236std::vector<std::unique_ptr<Vec<size_t>>> disabledFacePointVectors;237std::deque<int> faceList;238239// Create a half edge mesh representing the base tetrahedron from which the240// QuickHull iteration proceeds. extremeValues must be properly set up when241// this is called.242void setupInitialTetrahedron();243244// Given a list of half edges, try to rearrange them so that they form a loop.245// Return true on success.246bool reorderHorizonEdges(VecView<size_t>& horizonEdges);247248// Find indices of extreme values (max x, min x, max y, min y, max z, min z)249// for the given point cloud250std::array<size_t, 6> getExtremeValues();251252// Compute scale of the vertex data.253double getScale(const std::array<size_t, 6>& extremeValuesInput);254255// Each face contains a unique pointer to a vector of indices. However, many -256// often most - faces do not have any points on the positive side of them257// especially at the the end of the iteration. When a face is removed from the258// mesh, its associated point vector, if such exists, is moved to the index259// vector pool, and when we need to add new faces with points on the positive260// side to the mesh, we reuse these vectors. This reduces the amount of261// std::vectors we have to deal with, and impact on performance is remarkable.262Pool indexVectorPool;263inline std::unique_ptr<Vec<size_t>> getIndexVectorFromPool();264inline void reclaimToIndexVectorPool(std::unique_ptr<Vec<size_t>>& ptr);265266// Associates a point with a face if the point resides on the positive side of267// the plane. Returns true if the points was on the positive side.268inline bool addPointToFace(typename MeshBuilder::Face& f, size_t pointIndex);269270// This will create HalfedgeMesh from which we create the ConvexHull object271// that buildMesh function returns272void createConvexHalfedgeMesh();273274public:275// This function assumes that the pointCloudVec data resides in memory in the276// following format: x_0,y_0,z_0,x_1,y_1,z_1,...277QuickHull(VecView<vec3> pointCloudVec)278: originalVertexData(VecView(pointCloudVec)) {}279280// Computes convex hull for a given point cloud. Params: eps: minimum distance281// to a plane to consider a point being on positive side of it (for a point282// cloud with scale 1) Returns: Convex hull of the point cloud as halfEdge283// vector and vertex vector284std::pair<Vec<Halfedge>, Vec<vec3>> buildMesh(double eps = defaultEps());285};286287} // namespace manifold288289290