123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- // Copyright 2024 The Manifold Authors.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- // Derived from the public domain work of Antti Kuukka at
- // https://github.com/akuukka/quickhull
- /*
- * INPUT: a list of points in 3D space (for example, vertices of a 3D mesh)
- *
- * OUTPUT: a ConvexHull object which provides vertex and index buffers of the
- *generated convex hull as a triangle mesh.
- *
- *
- *
- * The implementation is thread-safe if each thread is using its own QuickHull
- *object.
- *
- *
- * SUMMARY OF THE ALGORITHM:
- * - Create initial simplex (tetrahedron) using extreme points. We have
- *four faces now and they form a convex mesh M.
- * - For each point, assign them to the first face for which they are on
- *the positive side of (so each point is assigned to at most one face). Points
- *inside the initial tetrahedron are left behind now and no longer affect the
- *calculations.
- * - Add all faces that have points assigned to them to Face Stack.
- * - Iterate until Face Stack is empty:
- * - Pop topmost face F from the stack
- * - From the points assigned to F, pick the point P that is
- *farthest away from the plane defined by F.
- * - Find all faces of M that have P on their positive side. Let us
- *call these the "visible faces".
- * - Because of the way M is constructed, these faces are
- *connected. Solve their horizon edge loop.
- * - "Extrude to P": Create new faces by connecting
- *P with the points belonging to the horizon edge. Add the new faces to M and
- *remove the visible faces from M.
- * - Each point that was assigned to visible faces is now assigned
- *to at most one of the newly created faces.
- * - Those new faces that have points assigned to them are added to
- *the top of Face Stack.
- * - M is now the convex hull.
- *
- * */
- #pragma once
- #include <array>
- #include <deque>
- #include <vector>
- #include "shared.h"
- #include "vec.h"
- namespace manifold {
- class Pool {
- std::vector<std::unique_ptr<Vec<size_t>>> data;
- public:
- void clear() { data.clear(); }
- void reclaim(std::unique_ptr<Vec<size_t>>& ptr) {
- data.push_back(std::move(ptr));
- }
- std::unique_ptr<Vec<size_t>> get() {
- if (data.size() == 0) {
- return std::make_unique<Vec<size_t>>();
- }
- auto it = data.end() - 1;
- std::unique_ptr<Vec<size_t>> r = std::move(*it);
- data.erase(it);
- return r;
- }
- };
- class Plane {
- public:
- vec3 N;
- // Signed distance (if normal is of length 1) to the plane from origin
- double D;
- // Normal length squared
- double sqrNLength;
- bool isPointOnPositiveSide(const vec3& Q) const {
- double d = la::dot(N, Q) + D;
- if (d >= 0) return true;
- return false;
- }
- Plane() = default;
- // Construct a plane using normal N and any point P on the plane
- Plane(const vec3& N, const vec3& P)
- : N(N), D(la::dot(-N, P)), sqrNLength(la::dot(N, N)) {}
- };
- struct Ray {
- const vec3 S;
- const vec3 V;
- const double VInvLengthSquared;
- Ray(const vec3& S, const vec3& V)
- : S(S), V(V), VInvLengthSquared(1 / (la::dot(V, V))) {}
- };
- class MeshBuilder {
- public:
- struct Face {
- int he;
- Plane P{};
- double mostDistantPointDist = 0.0;
- size_t mostDistantPoint = 0;
- size_t visibilityCheckedOnIteration = 0;
- std::uint8_t isVisibleFaceOnCurrentIteration : 1;
- std::uint8_t inFaceStack : 1;
- // Bit for each half edge assigned to this face, each being 0 or 1 depending
- // on whether the edge belongs to horizon edge
- std::uint8_t horizonEdgesOnCurrentIteration : 3;
- std::unique_ptr<Vec<size_t>> pointsOnPositiveSide;
- Face(size_t he)
- : he(he),
- isVisibleFaceOnCurrentIteration(0),
- inFaceStack(0),
- horizonEdgesOnCurrentIteration(0) {}
- Face()
- : he(-1),
- isVisibleFaceOnCurrentIteration(0),
- inFaceStack(0),
- horizonEdgesOnCurrentIteration(0) {}
- void disable() { he = -1; }
- bool isDisabled() const { return he == -1; }
- };
- // Mesh data
- std::vector<Face> faces;
- Vec<Halfedge> halfedges;
- Vec<int> halfedgeToFace;
- Vec<int> halfedgeNext;
- // 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.
- Vec<size_t> disabledFaces, disabledHalfedges;
- size_t addFace();
- size_t addHalfedge();
- // Mark a face as disabled and return a pointer to the points that were on the
- // positive of it.
- std::unique_ptr<Vec<size_t>> disableFace(size_t faceIndex) {
- auto& f = faces[faceIndex];
- f.disable();
- disabledFaces.push_back(faceIndex);
- return std::move(f.pointsOnPositiveSide);
- }
- void disableHalfedge(size_t heIndex) {
- auto& he = halfedges[heIndex];
- he.pairedHalfedge = -1;
- disabledHalfedges.push_back(heIndex);
- }
- MeshBuilder() = default;
- // Create a mesh with initial tetrahedron ABCD. Dot product of AB with the
- // normal of triangle ABC should be negative.
- void setup(int a, int b, int c, int d);
- std::array<int, 3> getVertexIndicesOfFace(const Face& f) const;
- std::array<int, 2> getVertexIndicesOfHalfEdge(const Halfedge& he) const {
- return {halfedges[he.pairedHalfedge].endVert, he.endVert};
- }
- std::array<int, 3> getHalfEdgeIndicesOfFace(const Face& f) const {
- return {f.he, halfedgeNext[f.he], halfedgeNext[halfedgeNext[f.he]]};
- }
- };
- class HalfEdgeMesh {
- public:
- Vec<vec3> vertices;
- // Index of one of the half edges of the faces
- std::vector<size_t> halfEdgeIndexFaces;
- Vec<Halfedge> halfedges;
- Vec<int> halfedgeToFace;
- Vec<int> halfedgeNext;
- HalfEdgeMesh(const MeshBuilder& builderObject,
- const VecView<vec3>& vertexData);
- };
- double defaultEps();
- class QuickHull {
- struct FaceData {
- int faceIndex;
- // If the face turns out not to be visible, this half edge will be marked as
- // horizon edge
- int enteredFromHalfedge;
- };
- double m_epsilon, epsilonSquared, scale;
- bool planar;
- Vec<vec3> planarPointCloudTemp;
- VecView<vec3> originalVertexData;
- MeshBuilder mesh;
- std::array<size_t, 6> extremeValues;
- size_t failedHorizonEdges = 0;
- // Temporary variables used during iteration process
- Vec<size_t> newFaceIndices;
- Vec<size_t> newHalfedgeIndices;
- Vec<size_t> visibleFaces;
- Vec<size_t> horizonEdgesData;
- Vec<FaceData> possiblyVisibleFaces;
- std::vector<std::unique_ptr<Vec<size_t>>> disabledFacePointVectors;
- std::deque<int> faceList;
- // Create a half edge mesh representing the base tetrahedron from which the
- // QuickHull iteration proceeds. extremeValues must be properly set up when
- // this is called.
- void setupInitialTetrahedron();
- // Given a list of half edges, try to rearrange them so that they form a loop.
- // Return true on success.
- bool reorderHorizonEdges(VecView<size_t>& 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<size_t, 6> getExtremeValues();
- // Compute scale of the vertex data.
- double getScale(const std::array<size_t, 6>& extremeValuesInput);
- // 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 indexVectorPool;
- inline std::unique_ptr<Vec<size_t>> getIndexVectorFromPool();
- inline void reclaimToIndexVectorPool(std::unique_ptr<Vec<size_t>>& 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, size_t pointIndex);
- // This will create HalfedgeMesh from which we create the ConvexHull object
- // that buildMesh function returns
- void createConvexHalfedgeMesh();
- public:
- // This function assumes that the pointCloudVec data resides in memory in the
- // following format: x_0,y_0,z_0,x_1,y_1,z_1,...
- QuickHull(VecView<vec3> pointCloudVec)
- : originalVertexData(VecView(pointCloudVec)) {}
- // Computes convex hull for a given point cloud. Params: 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 halfEdge
- // vector and vertex vector
- std::pair<Vec<Halfedge>, Vec<vec3>> buildMesh(double eps = defaultEps());
- };
- } // namespace manifold
|