godot-module-template/engine/thirdparty/manifold/src/quickhull.h

289 lines
9.3 KiB
C++

// 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