Path: blob/master/thirdparty/manifold/src/quickhull.cpp
20897 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#include "quickhull.h"1819#include <algorithm>20#include <cstddef>21#include <limits>22#include <unordered_map>2324#include "impl.h"2526namespace manifold {2728double defaultEps() { return 0.0000001; }2930inline double getSquaredDistanceBetweenPointAndRay(const vec3& p,31const Ray& r) {32const vec3 s = p - r.S;33double t = la::dot(s, r.V);34return la::dot(s, s) - t * t * r.VInvLengthSquared;35}3637inline double getSquaredDistance(const vec3& p1, const vec3& p2) {38return la::dot(p1 - p2, p1 - p2);39}40// Note that the unit of distance returned is relative to plane's normal's41// length (divide by N.getNormalized() if needed to get the "real" distance).42inline double getSignedDistanceToPlane(const vec3& v, const Plane& p) {43return la::dot(p.N, v) + p.D;44}4546inline vec3 getTriangleNormal(const vec3& a, const vec3& b, const vec3& c) {47// We want to get (a-c).crossProduct(b-c) without constructing temp vectors48double x = a.x - c.x;49double y = a.y - c.y;50double z = a.z - c.z;51double rhsx = b.x - c.x;52double rhsy = b.y - c.y;53double rhsz = b.z - c.z;54double px = y * rhsz - z * rhsy;55double py = z * rhsx - x * rhsz;56double pz = x * rhsy - y * rhsx;57return la::normalize(vec3(px, py, pz));58}5960size_t MeshBuilder::addFace() {61if (disabledFaces.size()) {62size_t index = disabledFaces.back();63auto& f = faces[index];64DEBUG_ASSERT(f.isDisabled(), logicErr, "f should be disabled");65DEBUG_ASSERT(!f.pointsOnPositiveSide, logicErr,66"f should not be on the positive side");67f.mostDistantPointDist = 0;68disabledFaces.pop_back();69return index;70}71faces.emplace_back();72return faces.size() - 1;73}7475size_t MeshBuilder::addHalfedge() {76if (disabledHalfedges.size()) {77const size_t index = disabledHalfedges.back();78disabledHalfedges.pop_back();79return index;80}81halfedges.push_back({});82halfedgeToFace.push_back(0);83halfedgeNext.push_back(0);84return halfedges.size() - 1;85}8687void MeshBuilder::setup(int a, int b, int c, int d) {88faces.clear();89halfedges.clear();90halfedgeToFace.clear();91halfedgeNext.clear();92disabledFaces.clear();93disabledHalfedges.clear();9495faces.reserve(4);96halfedges.reserve(12);9798// Create halfedges99// AB100halfedges.push_back({0, b, 6});101halfedgeToFace.push_back(0);102halfedgeNext.push_back(1);103// BC104halfedges.push_back({0, c, 9});105halfedgeToFace.push_back(0);106halfedgeNext.push_back(2);107// CA108halfedges.push_back({0, a, 3});109halfedgeToFace.push_back(0);110halfedgeNext.push_back(0);111// AC112halfedges.push_back({0, c, 2});113halfedgeToFace.push_back(1);114halfedgeNext.push_back(4);115// CD116halfedges.push_back({0, d, 11});117halfedgeToFace.push_back(1);118halfedgeNext.push_back(5);119// DA120halfedges.push_back({0, a, 7});121halfedgeToFace.push_back(1);122halfedgeNext.push_back(3);123// BA124halfedges.push_back({0, a, 0});125halfedgeToFace.push_back(2);126halfedgeNext.push_back(7);127// AD128halfedges.push_back({0, d, 5});129halfedgeToFace.push_back(2);130halfedgeNext.push_back(8);131// DB132halfedges.push_back({0, b, 10});133halfedgeToFace.push_back(2);134halfedgeNext.push_back(6);135// CB136halfedges.push_back({0, b, 1});137halfedgeToFace.push_back(3);138halfedgeNext.push_back(10);139// BD140halfedges.push_back({0, d, 8});141halfedgeToFace.push_back(3);142halfedgeNext.push_back(11);143// DC144halfedges.push_back({0, c, 4});145halfedgeToFace.push_back(3);146halfedgeNext.push_back(9);147148// Create faces149faces.emplace_back(0);150faces.emplace_back(3);151faces.emplace_back(6);152faces.emplace_back(9);153}154155std::array<int, 3> MeshBuilder::getVertexIndicesOfFace(const Face& f) const {156std::array<int, 3> v;157size_t index = f.he;158auto* he = &halfedges[index];159v[0] = he->endVert;160161index = halfedgeNext[index];162he = &halfedges[index];163v[1] = he->endVert;164165index = halfedgeNext[index];166he = &halfedges[index];167v[2] = he->endVert;168return v;169}170171HalfEdgeMesh::HalfEdgeMesh(const MeshBuilder& builderObject,172const VecView<vec3>& vertexData) {173std::unordered_map<size_t, size_t> faceMapping;174std::unordered_map<size_t, size_t> halfEdgeMapping;175std::unordered_map<size_t, size_t> vertexMapping;176177size_t i = 0;178for (const auto& face : builderObject.faces) {179if (!face.isDisabled()) {180halfEdgeIndexFaces.emplace_back(static_cast<size_t>(face.he));181faceMapping[i] = halfEdgeIndexFaces.size() - 1;182183const auto heIndices = builderObject.getHalfEdgeIndicesOfFace(face);184for (const auto heIndex : heIndices) {185const auto vertexIndex = builderObject.halfedges[heIndex].endVert;186if (vertexMapping.count(vertexIndex) == 0) {187vertices.push_back(vertexData[vertexIndex]);188vertexMapping[vertexIndex] = vertices.size() - 1;189}190}191}192i++;193}194195i = 0;196for (const auto& halfEdge : builderObject.halfedges) {197if (halfEdge.pairedHalfedge != -1) {198halfedges.push_back({halfEdge.endVert, halfEdge.pairedHalfedge,199builderObject.halfedgeToFace[i]});200halfedgeToFace.push_back(builderObject.halfedgeToFace[i]);201halfedgeNext.push_back(builderObject.halfedgeNext[i]);202halfEdgeMapping[i] = halfedges.size() - 1;203}204i++;205}206207for (auto& halfEdgeIndexFace : halfEdgeIndexFaces) {208DEBUG_ASSERT(halfEdgeMapping.count(halfEdgeIndexFace) == 1, logicErr,209"invalid halfedge mapping");210halfEdgeIndexFace = halfEdgeMapping[halfEdgeIndexFace];211}212213for (size_t i = 0; i < halfedges.size(); i++) {214auto& he = halfedges[i];215halfedgeToFace[i] = faceMapping[halfedgeToFace[i]];216he.pairedHalfedge = halfEdgeMapping[he.pairedHalfedge];217halfedgeNext[i] = halfEdgeMapping[halfedgeNext[i]];218he.endVert = vertexMapping[he.endVert];219}220}221222/*223* Implementation of the algorithm224*/225std::pair<Vec<Halfedge>, Vec<vec3>> QuickHull::buildMesh(double epsilon) {226if (originalVertexData.size() == 0) {227return {Vec<Halfedge>(), Vec<vec3>()};228}229230// Very first: find extreme values and use them to compute the scale of the231// point cloud.232extremeValues = getExtremeValues();233scale = getScale(extremeValues);234235// Epsilon we use depends on the scale236m_epsilon = epsilon * scale;237epsilonSquared = m_epsilon * m_epsilon;238239// The planar case happens when all the points appear to lie on a two240// dimensional subspace of R^3.241planar = false;242createConvexHalfedgeMesh();243if (planar) {244const int extraPointIndex = planarPointCloudTemp.size() - 1;245for (auto& he : mesh.halfedges) {246if (he.endVert == extraPointIndex) {247he.endVert = 0;248}249}250planarPointCloudTemp.clear();251}252253// reorder halfedges254Vec<Halfedge> halfedges(mesh.halfedges.size());255Vec<int> halfedgeToFace(mesh.halfedges.size());256Vec<int> counts(mesh.halfedges.size(), 0);257Vec<int> mapping(mesh.halfedges.size());258Vec<int> faceMap(mesh.faces.size());259260// Some faces are disabled and should not go into the halfedge vector, we can261// update the face indices of the halfedges at the end using index/3262int j = 0;263for_each(264autoPolicy(mesh.halfedges.size()), countAt(0_uz),265countAt(mesh.halfedges.size()), [&](size_t i) {266if (mesh.halfedges[i].pairedHalfedge < 0) return;267if (mesh.faces[mesh.halfedgeToFace[i]].isDisabled()) return;268if (AtomicAdd(counts[mesh.halfedgeToFace[i]], 1) > 0) return;269int currIndex = AtomicAdd(j, 3);270mapping[i] = currIndex;271halfedges[currIndex + 0] = mesh.halfedges[i];272halfedgeToFace[currIndex + 0] = mesh.halfedgeToFace[i];273274size_t k = mesh.halfedgeNext[i];275mapping[k] = currIndex + 1;276halfedges[currIndex + 1] = mesh.halfedges[k];277halfedgeToFace[currIndex + 1] = mesh.halfedgeToFace[k];278279k = mesh.halfedgeNext[k];280mapping[k] = currIndex + 2;281halfedges[currIndex + 2] = mesh.halfedges[k];282halfedgeToFace[currIndex + 2] = mesh.halfedgeToFace[k];283halfedges[currIndex + 0].startVert = halfedges[currIndex + 2].endVert;284halfedges[currIndex + 1].startVert = halfedges[currIndex + 0].endVert;285halfedges[currIndex + 2].startVert = halfedges[currIndex + 1].endVert;286});287halfedges.resize(j);288halfedgeToFace.resize(j);289// fix pairedHalfedge id290for_each(291autoPolicy(halfedges.size()), halfedges.begin(), halfedges.end(),292[&](Halfedge& he) { he.pairedHalfedge = mapping[he.pairedHalfedge]; });293counts.resize_nofill(originalVertexData.size() + 1);294fill(counts.begin(), counts.end(), 0);295296// remove unused vertices297for_each(autoPolicy(halfedges.size() / 3), countAt(0_uz),298countAt(halfedges.size() / 3), [&](size_t i) {299AtomicAdd(counts[halfedges[3 * i].startVert], 1);300AtomicAdd(counts[halfedges[3 * i + 1].startVert], 1);301AtomicAdd(counts[halfedges[3 * i + 2].startVert], 1);302});303auto saturate = [](int c) { return c > 0 ? 1 : 0; };304exclusive_scan(TransformIterator(counts.begin(), saturate),305TransformIterator(counts.end(), saturate), counts.begin(), 0);306Vec<vec3> vertices(counts.back());307for_each(autoPolicy(originalVertexData.size()), countAt(0_uz),308countAt(originalVertexData.size()), [&](size_t i) {309if (counts[i + 1] - counts[i] > 0) {310vertices[counts[i]] = originalVertexData[i];311}312});313for_each(autoPolicy(halfedges.size()), halfedges.begin(), halfedges.end(),314[&](Halfedge& he) {315he.startVert = counts[he.startVert];316he.endVert = counts[he.endVert];317});318return {std::move(halfedges), std::move(vertices)};319}320321void QuickHull::createConvexHalfedgeMesh() {322visibleFaces.clear();323horizonEdgesData.clear();324possiblyVisibleFaces.clear();325326// Compute base tetrahedron327setupInitialTetrahedron();328DEBUG_ASSERT(mesh.faces.size() == 4, logicErr, "not a tetrahedron");329330// Init face stack with those faces that have points assigned to them331faceList.clear();332for (size_t i = 0; i < 4; i++) {333auto& f = mesh.faces[i];334if (f.pointsOnPositiveSide && f.pointsOnPositiveSide->size() > 0) {335faceList.push_back(i);336f.inFaceStack = 1;337}338}339340// Process faces until the face list is empty.341size_t iter = 0;342while (!faceList.empty()) {343iter++;344if (iter == std::numeric_limits<size_t>::max()) {345// Visible face traversal marks visited faces with iteration counter (to346// mark that the face has been visited on this iteration) and the max347// value represents unvisited faces. At this point we have to reset348// iteration counter. This shouldn't be an issue on 64 bit machines.349iter = 0;350}351352const auto topFaceIndex = faceList.front();353faceList.pop_front();354355auto& tf = mesh.faces[topFaceIndex];356tf.inFaceStack = 0;357358DEBUG_ASSERT(359!tf.pointsOnPositiveSide || tf.pointsOnPositiveSide->size() > 0,360logicErr, "there should be points on the positive side");361if (!tf.pointsOnPositiveSide || tf.isDisabled()) {362continue;363}364365// Pick the most distant point to this triangle plane as the point to which366// we extrude367const vec3& activePoint = originalVertexData[tf.mostDistantPoint];368const size_t activePointIndex = tf.mostDistantPoint;369370// Find out the faces that have our active point on their positive side371// (these are the "visible faces"). The face on top of the stack of course372// is one of them. At the same time, we create a list of horizon edges.373horizonEdgesData.clear();374possiblyVisibleFaces.clear();375visibleFaces.clear();376possiblyVisibleFaces.push_back({topFaceIndex, -1});377while (possiblyVisibleFaces.size()) {378const auto faceData = possiblyVisibleFaces.back();379possiblyVisibleFaces.pop_back();380auto& pvf = mesh.faces[faceData.faceIndex];381DEBUG_ASSERT(!pvf.isDisabled(), logicErr, "pvf should not be disabled");382383if (pvf.visibilityCheckedOnIteration == iter) {384if (pvf.isVisibleFaceOnCurrentIteration) {385continue;386}387} else {388const Plane& P = pvf.P;389pvf.visibilityCheckedOnIteration = iter;390const double d = la::dot(P.N, activePoint) + P.D;391if (d > 0) {392pvf.isVisibleFaceOnCurrentIteration = 1;393pvf.horizonEdgesOnCurrentIteration = 0;394visibleFaces.push_back(faceData.faceIndex);395for (auto heIndex : mesh.getHalfEdgeIndicesOfFace(pvf)) {396if (mesh.halfedges[heIndex].pairedHalfedge !=397faceData.enteredFromHalfedge) {398possiblyVisibleFaces.push_back(399{mesh.halfedgeToFace[mesh.halfedges[heIndex].pairedHalfedge],400heIndex});401}402}403continue;404}405DEBUG_ASSERT(faceData.faceIndex != topFaceIndex, logicErr,406"face index invalid");407}408409// The face is not visible. Therefore, the halfedge we came from is part410// of the horizon edge.411pvf.isVisibleFaceOnCurrentIteration = 0;412horizonEdgesData.push_back(faceData.enteredFromHalfedge);413// Store which half edge is the horizon edge. The other half edges of the414// face will not be part of the final mesh so their data slots can by415// recycled.416const auto halfEdgesMesh = mesh.getHalfEdgeIndicesOfFace(417mesh.faces[mesh.halfedgeToFace[faceData.enteredFromHalfedge]]);418const std::int8_t ind =419(halfEdgesMesh[0] == faceData.enteredFromHalfedge)420? 0421: (halfEdgesMesh[1] == faceData.enteredFromHalfedge ? 1 : 2);422mesh.faces[mesh.halfedgeToFace[faceData.enteredFromHalfedge]]423.horizonEdgesOnCurrentIteration |= (1 << ind);424}425const size_t horizonEdgeCount = horizonEdgesData.size();426427// Order horizon edges so that they form a loop. This may fail due to428// numerical instability in which case we give up trying to solve horizon429// edge for this point and accept a minor degeneration in the convex hull.430if (!reorderHorizonEdges(horizonEdgesData)) {431failedHorizonEdges++;432int change_flag = 0;433for (size_t index = 0; index < tf.pointsOnPositiveSide->size(); index++) {434if ((*tf.pointsOnPositiveSide)[index] == activePointIndex) {435change_flag = 1;436} else if (change_flag == 1) {437change_flag = 2;438(*tf.pointsOnPositiveSide)[index - 1] =439(*tf.pointsOnPositiveSide)[index];440}441}442if (change_flag == 1)443tf.pointsOnPositiveSide->resize(tf.pointsOnPositiveSide->size() - 1);444445if (tf.pointsOnPositiveSide->size() == 0) {446reclaimToIndexVectorPool(tf.pointsOnPositiveSide);447}448continue;449}450451// Except for the horizon edges, all half edges of the visible faces can be452// marked as disabled. Their data slots will be reused. The faces will be453// disabled as well, but we need to remember the points that were on the454// positive side of them - therefore we save pointers to them.455newFaceIndices.clear();456newHalfedgeIndices.clear();457disabledFacePointVectors.clear();458size_t disableCounter = 0;459for (auto faceIndex : visibleFaces) {460auto& disabledFace = mesh.faces[faceIndex];461auto halfEdgesMesh = mesh.getHalfEdgeIndicesOfFace(disabledFace);462for (size_t j = 0; j < 3; j++) {463if ((disabledFace.horizonEdgesOnCurrentIteration & (1 << j)) == 0) {464if (disableCounter < horizonEdgeCount * 2) {465// Use on this iteration466newHalfedgeIndices.push_back(halfEdgesMesh[j]);467disableCounter++;468} else {469// Mark for reusal on later iteration step470mesh.disableHalfedge(halfEdgesMesh[j]);471}472}473}474// Disable the face, but retain pointer to the points that were on the475// positive side of it. We need to assign those points to the new faces we476// create shortly.477auto t = mesh.disableFace(faceIndex);478if (t) {479// Because we should not assign point vectors to faces unless needed...480DEBUG_ASSERT(t->size(), logicErr, "t should not be empty");481disabledFacePointVectors.push_back(std::move(t));482}483}484if (disableCounter < horizonEdgeCount * 2) {485const size_t newHalfEdgesNeeded = horizonEdgeCount * 2 - disableCounter;486for (size_t i = 0; i < newHalfEdgesNeeded; i++) {487newHalfedgeIndices.push_back(mesh.addHalfedge());488}489}490491// Create new faces using the edgeloop492for (size_t i = 0; i < horizonEdgeCount; i++) {493const size_t AB = horizonEdgesData[i];494495auto horizonEdgeVertexIndices =496mesh.getVertexIndicesOfHalfEdge(mesh.halfedges[AB]);497size_t A, B, C;498A = horizonEdgeVertexIndices[0];499B = horizonEdgeVertexIndices[1];500C = activePointIndex;501502const size_t newFaceIndex = mesh.addFace();503newFaceIndices.push_back(newFaceIndex);504505const size_t CA = newHalfedgeIndices[2 * i + 0];506const size_t BC = newHalfedgeIndices[2 * i + 1];507508mesh.halfedgeNext[AB] = BC;509mesh.halfedgeNext[BC] = CA;510mesh.halfedgeNext[CA] = AB;511512mesh.halfedgeToFace[BC] = newFaceIndex;513mesh.halfedgeToFace[CA] = newFaceIndex;514mesh.halfedgeToFace[AB] = newFaceIndex;515516mesh.halfedges[CA].endVert = A;517mesh.halfedges[BC].endVert = C;518519auto& newFace = mesh.faces[newFaceIndex];520521const vec3 planeNormal = getTriangleNormal(522originalVertexData[A], originalVertexData[B], activePoint);523newFace.P = Plane(planeNormal, activePoint);524newFace.he = AB;525526mesh.halfedges[CA].pairedHalfedge =527newHalfedgeIndices[i > 0 ? i * 2 - 1 : 2 * horizonEdgeCount - 1];528mesh.halfedges[BC].pairedHalfedge =529newHalfedgeIndices[((i + 1) * 2) % (horizonEdgeCount * 2)];530}531532// Assign points that were on the positive side of the disabled faces to the533// new faces.534for (auto& disabledPoints : disabledFacePointVectors) {535DEBUG_ASSERT(disabledPoints != nullptr, logicErr,536"disabledPoints should not be null");537for (const auto& point : *(disabledPoints)) {538if (point == activePointIndex) {539continue;540}541for (size_t j = 0; j < horizonEdgeCount; j++) {542if (addPointToFace(mesh.faces[newFaceIndices[j]], point)) {543break;544}545}546}547// The points are no longer needed: we can move them to the vector pool548// for reuse.549reclaimToIndexVectorPool(disabledPoints);550}551552// Increase face stack size if needed553for (const auto newFaceIndex : newFaceIndices) {554auto& newFace = mesh.faces[newFaceIndex];555if (newFace.pointsOnPositiveSide) {556DEBUG_ASSERT(newFace.pointsOnPositiveSide->size() > 0, logicErr,557"there should be points on the positive side");558if (!newFace.inFaceStack) {559faceList.push_back(newFaceIndex);560newFace.inFaceStack = 1;561}562}563}564}565566// Cleanup567indexVectorPool.clear();568}569570/*571* Private helper functions572*/573574std::array<size_t, 6> QuickHull::getExtremeValues() {575std::array<size_t, 6> outIndices{0, 0, 0, 0, 0, 0};576double extremeVals[6] = {originalVertexData[0].x, originalVertexData[0].x,577originalVertexData[0].y, originalVertexData[0].y,578originalVertexData[0].z, originalVertexData[0].z};579const size_t vCount = originalVertexData.size();580for (size_t i = 1; i < vCount; i++) {581const vec3& pos = originalVertexData[i];582if (pos.x > extremeVals[0]) {583extremeVals[0] = pos.x;584outIndices[0] = i;585} else if (pos.x < extremeVals[1]) {586extremeVals[1] = pos.x;587outIndices[1] = i;588}589if (pos.y > extremeVals[2]) {590extremeVals[2] = pos.y;591outIndices[2] = i;592} else if (pos.y < extremeVals[3]) {593extremeVals[3] = pos.y;594outIndices[3] = i;595}596if (pos.z > extremeVals[4]) {597extremeVals[4] = pos.z;598outIndices[4] = i;599} else if (pos.z < extremeVals[5]) {600extremeVals[5] = pos.z;601outIndices[5] = i;602}603}604return outIndices;605}606607bool QuickHull::reorderHorizonEdges(VecView<size_t>& horizonEdges) {608const size_t horizonEdgeCount = horizonEdges.size();609for (size_t i = 0; i + 1 < horizonEdgeCount; i++) {610const size_t endVertexCheck = mesh.halfedges[horizonEdges[i]].endVert;611bool foundNext = false;612for (size_t j = i + 1; j < horizonEdgeCount; j++) {613const size_t beginVertex =614mesh.halfedges[mesh.halfedges[horizonEdges[j]].pairedHalfedge]615.endVert;616if (beginVertex == endVertexCheck) {617std::swap(horizonEdges[i + 1], horizonEdges[j]);618foundNext = true;619break;620}621}622if (!foundNext) {623return false;624}625}626DEBUG_ASSERT(627mesh.halfedges[horizonEdges[horizonEdges.size() - 1]].endVert ==628mesh.halfedges[mesh.halfedges[horizonEdges[0]].pairedHalfedge]629.endVert,630logicErr, "invalid halfedge");631return true;632}633634double QuickHull::getScale(const std::array<size_t, 6>& extremeValuesInput) {635double s = 0;636for (size_t i = 0; i < 6; i++) {637const double* v =638(const double*)(&originalVertexData[extremeValuesInput[i]]);639v += i / 2;640auto a = std::abs(*v);641if (a > s) {642s = a;643}644}645return s;646}647648void QuickHull::setupInitialTetrahedron() {649const size_t vertexCount = originalVertexData.size();650651// If we have at most 4 points, just return a degenerate tetrahedron:652if (vertexCount <= 4) {653size_t v[4] = {0, std::min((size_t)1, vertexCount - 1),654std::min((size_t)2, vertexCount - 1),655std::min((size_t)3, vertexCount - 1)};656const vec3 N =657getTriangleNormal(originalVertexData[v[0]], originalVertexData[v[1]],658originalVertexData[v[2]]);659const Plane trianglePlane(N, originalVertexData[v[0]]);660if (trianglePlane.isPointOnPositiveSide(originalVertexData[v[3]])) {661std::swap(v[0], v[1]);662}663return mesh.setup(v[0], v[1], v[2], v[3]);664}665666// Find two most distant extreme points.667double maxD = epsilonSquared;668std::pair<size_t, size_t> selectedPoints;669for (size_t i = 0; i < 6; i++) {670for (size_t j = i + 1; j < 6; j++) {671// I found a function for squaredDistance but i can't seem to include it672// like this for some reason673const double d = getSquaredDistance(originalVertexData[extremeValues[i]],674originalVertexData[extremeValues[j]]);675if (d > maxD) {676maxD = d;677selectedPoints = {extremeValues[i], extremeValues[j]};678}679}680}681if (maxD == epsilonSquared) {682// A degenerate case: the point cloud seems to consists of a single point683return mesh.setup(0, std::min((size_t)1, vertexCount - 1),684std::min((size_t)2, vertexCount - 1),685std::min((size_t)3, vertexCount - 1));686}687DEBUG_ASSERT(selectedPoints.first != selectedPoints.second, logicErr,688"degenerate selectedPoints");689690// Find the most distant point to the line between the two chosen extreme691// points.692const Ray r(originalVertexData[selectedPoints.first],693(originalVertexData[selectedPoints.second] -694originalVertexData[selectedPoints.first]));695maxD = epsilonSquared;696size_t maxI = std::numeric_limits<size_t>::max();697const size_t vCount = originalVertexData.size();698for (size_t i = 0; i < vCount; i++) {699const double distToRay =700getSquaredDistanceBetweenPointAndRay(originalVertexData[i], r);701if (distToRay > maxD) {702maxD = distToRay;703maxI = i;704}705}706if (maxD == epsilonSquared) {707// It appears that the point cloud belongs to a 1 dimensional subspace of708// R^3: convex hull has no volume => return a thin triangle Pick any point709// other than selectedPoints.first and selectedPoints.second as the third710// point of the triangle711auto it =712std::find_if(originalVertexData.begin(), originalVertexData.end(),713[&](const vec3& ve) {714return ve != originalVertexData[selectedPoints.first] &&715ve != originalVertexData[selectedPoints.second];716});717const size_t thirdPoint =718(it == originalVertexData.end())719? selectedPoints.first720: std::distance(originalVertexData.begin(), it);721it =722std::find_if(originalVertexData.begin(), originalVertexData.end(),723[&](const vec3& ve) {724return ve != originalVertexData[selectedPoints.first] &&725ve != originalVertexData[selectedPoints.second] &&726ve != originalVertexData[thirdPoint];727});728const size_t fourthPoint =729(it == originalVertexData.end())730? selectedPoints.first731: std::distance(originalVertexData.begin(), it);732return mesh.setup(selectedPoints.first, selectedPoints.second, thirdPoint,733fourthPoint);734}735736// These three points form the base triangle for our tetrahedron.737DEBUG_ASSERT(selectedPoints.first != maxI && selectedPoints.second != maxI,738logicErr, "degenerate selectedPoints");739std::array<size_t, 3> baseTriangle{selectedPoints.first,740selectedPoints.second, maxI};741const vec3 baseTriangleVertices[] = {originalVertexData[baseTriangle[0]],742originalVertexData[baseTriangle[1]],743originalVertexData[baseTriangle[2]]};744745// Next step is to find the 4th vertex of the tetrahedron. We naturally choose746// the point farthest away from the triangle plane.747maxD = m_epsilon;748maxI = 0;749const vec3 N =750getTriangleNormal(baseTriangleVertices[0], baseTriangleVertices[1],751baseTriangleVertices[2]);752Plane trianglePlane(N, baseTriangleVertices[0]);753for (size_t i = 0; i < vCount; i++) {754const double d = std::abs(755getSignedDistanceToPlane(originalVertexData[i], trianglePlane));756if (d > maxD) {757maxD = d;758maxI = i;759}760}761if (maxD == m_epsilon) {762// All the points seem to lie on a 2D subspace of R^3. How to handle this?763// Well, let's add one extra point to the point cloud so that the convex764// hull will have volume.765planar = true;766const vec3 N1 =767getTriangleNormal(baseTriangleVertices[1], baseTriangleVertices[2],768baseTriangleVertices[0]);769planarPointCloudTemp = Vec<vec3>(originalVertexData);770const vec3 extraPoint = N1 + originalVertexData[0];771planarPointCloudTemp.push_back(extraPoint);772maxI = planarPointCloudTemp.size() - 1;773originalVertexData = planarPointCloudTemp;774}775776// Enforce CCW orientation (if user prefers clockwise orientation, swap two777// vertices in each triangle when final mesh is created)778const Plane triPlane(N, baseTriangleVertices[0]);779if (triPlane.isPointOnPositiveSide(originalVertexData[maxI])) {780std::swap(baseTriangle[0], baseTriangle[1]);781}782783// Create a tetrahedron half edge mesh and compute planes defined by each784// triangle785mesh.setup(baseTriangle[0], baseTriangle[1], baseTriangle[2], maxI);786for (auto& f : mesh.faces) {787auto v = mesh.getVertexIndicesOfFace(f);788const vec3 N1 =789getTriangleNormal(originalVertexData[v[0]], originalVertexData[v[1]],790originalVertexData[v[2]]);791const Plane plane(N1, originalVertexData[v[0]]);792f.P = plane;793}794795// Finally we assign a face for each vertex outside the tetrahedron (vertices796// inside the tetrahedron have no role anymore)797for (size_t i = 0; i < vCount; i++) {798for (auto& face : mesh.faces) {799if (addPointToFace(face, i)) {800break;801}802}803}804}805806std::unique_ptr<Vec<size_t>> QuickHull::getIndexVectorFromPool() {807auto r = indexVectorPool.get();808r->clear();809return r;810}811812void QuickHull::reclaimToIndexVectorPool(std::unique_ptr<Vec<size_t>>& ptr) {813const size_t oldSize = ptr->size();814if ((oldSize + 1) * 128 < ptr->capacity()) {815// Reduce memory usage! Huge vectors are needed at the beginning of816// iteration when faces have many points on their positive side. Later on,817// smaller vectors will suffice.818ptr.reset(nullptr);819return;820}821indexVectorPool.reclaim(ptr);822}823824bool QuickHull::addPointToFace(typename MeshBuilder::Face& f,825size_t pointIndex) {826const double D =827getSignedDistanceToPlane(originalVertexData[pointIndex], f.P);828if (D > 0 && D * D > epsilonSquared * f.P.sqrNLength) {829if (!f.pointsOnPositiveSide) {830f.pointsOnPositiveSide = getIndexVectorFromPool();831}832f.pointsOnPositiveSide->push_back(pointIndex);833if (D > f.mostDistantPointDist) {834f.mostDistantPointDist = D;835f.mostDistantPoint = pointIndex;836}837return true;838}839return false;840}841842// Wrapper to call the QuickHull algorithm with the given vertex data to build843// the Impl844void Manifold::Impl::Hull(VecView<vec3> vertPos) {845size_t numVert = vertPos.size();846if (numVert < 4) {847status_ = Error::InvalidConstruction;848return;849}850851QuickHull qh(vertPos);852std::tie(halfedge_, vertPos_) = qh.buildMesh();853CalculateBBox();854SetEpsilon();855InitializeOriginal();856Finish();857MarkCoplanar();858}859860} // namespace manifold861862863