Path: blob/master/thirdparty/manifold/src/quickhull.cpp
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#include "quickhull.h"1819#include <algorithm>20#include <limits>2122#include "impl.h"2324namespace manifold {2526double defaultEps() { return 0.0000001; }2728inline double getSquaredDistanceBetweenPointAndRay(const vec3& p,29const Ray& r) {30const vec3 s = p - r.S;31double t = la::dot(s, r.V);32return la::dot(s, s) - t * t * r.VInvLengthSquared;33}3435inline double getSquaredDistance(const vec3& p1, const vec3& p2) {36return la::dot(p1 - p2, p1 - p2);37}38// Note that the unit of distance returned is relative to plane's normal's39// length (divide by N.getNormalized() if needed to get the "real" distance).40inline double getSignedDistanceToPlane(const vec3& v, const Plane& p) {41return la::dot(p.N, v) + p.D;42}4344inline vec3 getTriangleNormal(const vec3& a, const vec3& b, const vec3& c) {45// We want to get (a-c).crossProduct(b-c) without constructing temp vectors46double x = a.x - c.x;47double y = a.y - c.y;48double z = a.z - c.z;49double rhsx = b.x - c.x;50double rhsy = b.y - c.y;51double rhsz = b.z - c.z;52double px = y * rhsz - z * rhsy;53double py = z * rhsx - x * rhsz;54double pz = x * rhsy - y * rhsx;55return la::normalize(vec3(px, py, pz));56}5758size_t MeshBuilder::addFace() {59if (disabledFaces.size()) {60size_t index = disabledFaces.back();61auto& f = faces[index];62DEBUG_ASSERT(f.isDisabled(), logicErr, "f should be disabled");63DEBUG_ASSERT(!f.pointsOnPositiveSide, logicErr,64"f should not be on the positive side");65f.mostDistantPointDist = 0;66disabledFaces.pop_back();67return index;68}69faces.emplace_back();70return faces.size() - 1;71}7273size_t MeshBuilder::addHalfedge() {74if (disabledHalfedges.size()) {75const size_t index = disabledHalfedges.back();76disabledHalfedges.pop_back();77return index;78}79halfedges.push_back({});80halfedgeToFace.push_back(0);81halfedgeNext.push_back(0);82return halfedges.size() - 1;83}8485void MeshBuilder::setup(int a, int b, int c, int d) {86faces.clear();87halfedges.clear();88halfedgeToFace.clear();89halfedgeNext.clear();90disabledFaces.clear();91disabledHalfedges.clear();9293faces.reserve(4);94halfedges.reserve(12);9596// Create halfedges97// AB98halfedges.push_back({0, b, 6});99halfedgeToFace.push_back(0);100halfedgeNext.push_back(1);101// BC102halfedges.push_back({0, c, 9});103halfedgeToFace.push_back(0);104halfedgeNext.push_back(2);105// CA106halfedges.push_back({0, a, 3});107halfedgeToFace.push_back(0);108halfedgeNext.push_back(0);109// AC110halfedges.push_back({0, c, 2});111halfedgeToFace.push_back(1);112halfedgeNext.push_back(4);113// CD114halfedges.push_back({0, d, 11});115halfedgeToFace.push_back(1);116halfedgeNext.push_back(5);117// DA118halfedges.push_back({0, a, 7});119halfedgeToFace.push_back(1);120halfedgeNext.push_back(3);121// BA122halfedges.push_back({0, a, 0});123halfedgeToFace.push_back(2);124halfedgeNext.push_back(7);125// AD126halfedges.push_back({0, d, 5});127halfedgeToFace.push_back(2);128halfedgeNext.push_back(8);129// DB130halfedges.push_back({0, b, 10});131halfedgeToFace.push_back(2);132halfedgeNext.push_back(6);133// CB134halfedges.push_back({0, b, 1});135halfedgeToFace.push_back(3);136halfedgeNext.push_back(10);137// BD138halfedges.push_back({0, d, 8});139halfedgeToFace.push_back(3);140halfedgeNext.push_back(11);141// DC142halfedges.push_back({0, c, 4});143halfedgeToFace.push_back(3);144halfedgeNext.push_back(9);145146// Create faces147faces.emplace_back(0);148faces.emplace_back(3);149faces.emplace_back(6);150faces.emplace_back(9);151}152153std::array<int, 3> MeshBuilder::getVertexIndicesOfFace(const Face& f) const {154std::array<int, 3> v;155size_t index = f.he;156auto* he = &halfedges[index];157v[0] = he->endVert;158159index = halfedgeNext[index];160he = &halfedges[index];161v[1] = he->endVert;162163index = halfedgeNext[index];164he = &halfedges[index];165v[2] = he->endVert;166return v;167}168169HalfEdgeMesh::HalfEdgeMesh(const MeshBuilder& builderObject,170const VecView<vec3>& vertexData) {171std::unordered_map<size_t, size_t> faceMapping;172std::unordered_map<size_t, size_t> halfEdgeMapping;173std::unordered_map<size_t, size_t> vertexMapping;174175size_t i = 0;176for (const auto& face : builderObject.faces) {177if (!face.isDisabled()) {178halfEdgeIndexFaces.emplace_back(static_cast<size_t>(face.he));179faceMapping[i] = halfEdgeIndexFaces.size() - 1;180181const auto heIndices = builderObject.getHalfEdgeIndicesOfFace(face);182for (const auto heIndex : heIndices) {183const auto vertexIndex = builderObject.halfedges[heIndex].endVert;184if (vertexMapping.count(vertexIndex) == 0) {185vertices.push_back(vertexData[vertexIndex]);186vertexMapping[vertexIndex] = vertices.size() - 1;187}188}189}190i++;191}192193i = 0;194for (const auto& halfEdge : builderObject.halfedges) {195if (halfEdge.pairedHalfedge != -1) {196halfedges.push_back({halfEdge.endVert, halfEdge.pairedHalfedge,197builderObject.halfedgeToFace[i]});198halfedgeToFace.push_back(builderObject.halfedgeToFace[i]);199halfedgeNext.push_back(builderObject.halfedgeNext[i]);200halfEdgeMapping[i] = halfedges.size() - 1;201}202i++;203}204205for (auto& halfEdgeIndexFace : halfEdgeIndexFaces) {206DEBUG_ASSERT(halfEdgeMapping.count(halfEdgeIndexFace) == 1, logicErr,207"invalid halfedge mapping");208halfEdgeIndexFace = halfEdgeMapping[halfEdgeIndexFace];209}210211for (size_t i = 0; i < halfedges.size(); i++) {212auto& he = halfedges[i];213halfedgeToFace[i] = faceMapping[halfedgeToFace[i]];214he.pairedHalfedge = halfEdgeMapping[he.pairedHalfedge];215halfedgeNext[i] = halfEdgeMapping[halfedgeNext[i]];216he.endVert = vertexMapping[he.endVert];217}218}219220/*221* Implementation of the algorithm222*/223std::pair<Vec<Halfedge>, Vec<vec3>> QuickHull::buildMesh(double epsilon) {224if (originalVertexData.size() == 0) {225return {Vec<Halfedge>(), Vec<vec3>()};226}227228// Very first: find extreme values and use them to compute the scale of the229// point cloud.230extremeValues = getExtremeValues();231scale = getScale(extremeValues);232233// Epsilon we use depends on the scale234m_epsilon = epsilon * scale;235epsilonSquared = m_epsilon * m_epsilon;236237// The planar case happens when all the points appear to lie on a two238// dimensional subspace of R^3.239planar = false;240createConvexHalfedgeMesh();241if (planar) {242const int extraPointIndex = planarPointCloudTemp.size() - 1;243for (auto& he : mesh.halfedges) {244if (he.endVert == extraPointIndex) {245he.endVert = 0;246}247}248planarPointCloudTemp.clear();249}250251// reorder halfedges252Vec<Halfedge> halfedges(mesh.halfedges.size());253Vec<int> halfedgeToFace(mesh.halfedges.size());254Vec<int> counts(mesh.halfedges.size(), 0);255Vec<int> mapping(mesh.halfedges.size());256Vec<int> faceMap(mesh.faces.size());257258// Some faces are disabled and should not go into the halfedge vector, we can259// update the face indices of the halfedges at the end using index/3260int j = 0;261for_each(262autoPolicy(mesh.halfedges.size()), countAt(0_uz),263countAt(mesh.halfedges.size()), [&](size_t i) {264if (mesh.halfedges[i].pairedHalfedge < 0) return;265if (mesh.faces[mesh.halfedgeToFace[i]].isDisabled()) return;266if (AtomicAdd(counts[mesh.halfedgeToFace[i]], 1) > 0) return;267int currIndex = AtomicAdd(j, 3);268mapping[i] = currIndex;269halfedges[currIndex + 0] = mesh.halfedges[i];270halfedgeToFace[currIndex + 0] = mesh.halfedgeToFace[i];271272size_t k = mesh.halfedgeNext[i];273mapping[k] = currIndex + 1;274halfedges[currIndex + 1] = mesh.halfedges[k];275halfedgeToFace[currIndex + 1] = mesh.halfedgeToFace[k];276277k = mesh.halfedgeNext[k];278mapping[k] = currIndex + 2;279halfedges[currIndex + 2] = mesh.halfedges[k];280halfedgeToFace[currIndex + 2] = mesh.halfedgeToFace[k];281halfedges[currIndex + 0].startVert = halfedges[currIndex + 2].endVert;282halfedges[currIndex + 1].startVert = halfedges[currIndex + 0].endVert;283halfedges[currIndex + 2].startVert = halfedges[currIndex + 1].endVert;284});285halfedges.resize(j);286halfedgeToFace.resize(j);287// fix pairedHalfedge id288for_each(289autoPolicy(halfedges.size()), halfedges.begin(), halfedges.end(),290[&](Halfedge& he) { he.pairedHalfedge = mapping[he.pairedHalfedge]; });291counts.resize_nofill(originalVertexData.size() + 1);292fill(counts.begin(), counts.end(), 0);293294// remove unused vertices295for_each(autoPolicy(halfedges.size() / 3), countAt(0_uz),296countAt(halfedges.size() / 3), [&](size_t i) {297AtomicAdd(counts[halfedges[3 * i].startVert], 1);298AtomicAdd(counts[halfedges[3 * i + 1].startVert], 1);299AtomicAdd(counts[halfedges[3 * i + 2].startVert], 1);300});301auto saturate = [](int c) { return c > 0 ? 1 : 0; };302exclusive_scan(TransformIterator(counts.begin(), saturate),303TransformIterator(counts.end(), saturate), counts.begin(), 0);304Vec<vec3> vertices(counts.back());305for_each(autoPolicy(originalVertexData.size()), countAt(0_uz),306countAt(originalVertexData.size()), [&](size_t i) {307if (counts[i + 1] - counts[i] > 0) {308vertices[counts[i]] = originalVertexData[i];309}310});311for_each(autoPolicy(halfedges.size()), halfedges.begin(), halfedges.end(),312[&](Halfedge& he) {313he.startVert = counts[he.startVert];314he.endVert = counts[he.endVert];315});316return {std::move(halfedges), std::move(vertices)};317}318319void QuickHull::createConvexHalfedgeMesh() {320visibleFaces.clear();321horizonEdgesData.clear();322possiblyVisibleFaces.clear();323324// Compute base tetrahedron325setupInitialTetrahedron();326DEBUG_ASSERT(mesh.faces.size() == 4, logicErr, "not a tetrahedron");327328// Init face stack with those faces that have points assigned to them329faceList.clear();330for (size_t i = 0; i < 4; i++) {331auto& f = mesh.faces[i];332if (f.pointsOnPositiveSide && f.pointsOnPositiveSide->size() > 0) {333faceList.push_back(i);334f.inFaceStack = 1;335}336}337338// Process faces until the face list is empty.339size_t iter = 0;340while (!faceList.empty()) {341iter++;342if (iter == std::numeric_limits<size_t>::max()) {343// Visible face traversal marks visited faces with iteration counter (to344// mark that the face has been visited on this iteration) and the max345// value represents unvisited faces. At this point we have to reset346// iteration counter. This shouldn't be an issue on 64 bit machines.347iter = 0;348}349350const auto topFaceIndex = faceList.front();351faceList.pop_front();352353auto& tf = mesh.faces[topFaceIndex];354tf.inFaceStack = 0;355356DEBUG_ASSERT(357!tf.pointsOnPositiveSide || tf.pointsOnPositiveSide->size() > 0,358logicErr, "there should be points on the positive side");359if (!tf.pointsOnPositiveSide || tf.isDisabled()) {360continue;361}362363// Pick the most distant point to this triangle plane as the point to which364// we extrude365const vec3& activePoint = originalVertexData[tf.mostDistantPoint];366const size_t activePointIndex = tf.mostDistantPoint;367368// Find out the faces that have our active point on their positive side369// (these are the "visible faces"). The face on top of the stack of course370// is one of them. At the same time, we create a list of horizon edges.371horizonEdgesData.clear();372possiblyVisibleFaces.clear();373visibleFaces.clear();374possiblyVisibleFaces.push_back({topFaceIndex, -1});375while (possiblyVisibleFaces.size()) {376const auto faceData = possiblyVisibleFaces.back();377possiblyVisibleFaces.pop_back();378auto& pvf = mesh.faces[faceData.faceIndex];379DEBUG_ASSERT(!pvf.isDisabled(), logicErr, "pvf should not be disabled");380381if (pvf.visibilityCheckedOnIteration == iter) {382if (pvf.isVisibleFaceOnCurrentIteration) {383continue;384}385} else {386const Plane& P = pvf.P;387pvf.visibilityCheckedOnIteration = iter;388const double d = la::dot(P.N, activePoint) + P.D;389if (d > 0) {390pvf.isVisibleFaceOnCurrentIteration = 1;391pvf.horizonEdgesOnCurrentIteration = 0;392visibleFaces.push_back(faceData.faceIndex);393for (auto heIndex : mesh.getHalfEdgeIndicesOfFace(pvf)) {394if (mesh.halfedges[heIndex].pairedHalfedge !=395faceData.enteredFromHalfedge) {396possiblyVisibleFaces.push_back(397{mesh.halfedgeToFace[mesh.halfedges[heIndex].pairedHalfedge],398heIndex});399}400}401continue;402}403DEBUG_ASSERT(faceData.faceIndex != topFaceIndex, logicErr,404"face index invalid");405}406407// The face is not visible. Therefore, the halfedge we came from is part408// of the horizon edge.409pvf.isVisibleFaceOnCurrentIteration = 0;410horizonEdgesData.push_back(faceData.enteredFromHalfedge);411// Store which half edge is the horizon edge. The other half edges of the412// face will not be part of the final mesh so their data slots can by413// recycled.414const auto halfEdgesMesh = mesh.getHalfEdgeIndicesOfFace(415mesh.faces[mesh.halfedgeToFace[faceData.enteredFromHalfedge]]);416const std::int8_t ind =417(halfEdgesMesh[0] == faceData.enteredFromHalfedge)418? 0419: (halfEdgesMesh[1] == faceData.enteredFromHalfedge ? 1 : 2);420mesh.faces[mesh.halfedgeToFace[faceData.enteredFromHalfedge]]421.horizonEdgesOnCurrentIteration |= (1 << ind);422}423const size_t horizonEdgeCount = horizonEdgesData.size();424425// Order horizon edges so that they form a loop. This may fail due to426// numerical instability in which case we give up trying to solve horizon427// edge for this point and accept a minor degeneration in the convex hull.428if (!reorderHorizonEdges(horizonEdgesData)) {429failedHorizonEdges++;430int change_flag = 0;431for (size_t index = 0; index < tf.pointsOnPositiveSide->size(); index++) {432if ((*tf.pointsOnPositiveSide)[index] == activePointIndex) {433change_flag = 1;434} else if (change_flag == 1) {435change_flag = 2;436(*tf.pointsOnPositiveSide)[index - 1] =437(*tf.pointsOnPositiveSide)[index];438}439}440if (change_flag == 1)441tf.pointsOnPositiveSide->resize(tf.pointsOnPositiveSide->size() - 1);442443if (tf.pointsOnPositiveSide->size() == 0) {444reclaimToIndexVectorPool(tf.pointsOnPositiveSide);445}446continue;447}448449// Except for the horizon edges, all half edges of the visible faces can be450// marked as disabled. Their data slots will be reused. The faces will be451// disabled as well, but we need to remember the points that were on the452// positive side of them - therefore we save pointers to them.453newFaceIndices.clear();454newHalfedgeIndices.clear();455disabledFacePointVectors.clear();456size_t disableCounter = 0;457for (auto faceIndex : visibleFaces) {458auto& disabledFace = mesh.faces[faceIndex];459auto halfEdgesMesh = mesh.getHalfEdgeIndicesOfFace(disabledFace);460for (size_t j = 0; j < 3; j++) {461if ((disabledFace.horizonEdgesOnCurrentIteration & (1 << j)) == 0) {462if (disableCounter < horizonEdgeCount * 2) {463// Use on this iteration464newHalfedgeIndices.push_back(halfEdgesMesh[j]);465disableCounter++;466} else {467// Mark for reusal on later iteration step468mesh.disableHalfedge(halfEdgesMesh[j]);469}470}471}472// Disable the face, but retain pointer to the points that were on the473// positive side of it. We need to assign those points to the new faces we474// create shortly.475auto t = mesh.disableFace(faceIndex);476if (t) {477// Because we should not assign point vectors to faces unless needed...478DEBUG_ASSERT(t->size(), logicErr, "t should not be empty");479disabledFacePointVectors.push_back(std::move(t));480}481}482if (disableCounter < horizonEdgeCount * 2) {483const size_t newHalfEdgesNeeded = horizonEdgeCount * 2 - disableCounter;484for (size_t i = 0; i < newHalfEdgesNeeded; i++) {485newHalfedgeIndices.push_back(mesh.addHalfedge());486}487}488489// Create new faces using the edgeloop490for (size_t i = 0; i < horizonEdgeCount; i++) {491const size_t AB = horizonEdgesData[i];492493auto horizonEdgeVertexIndices =494mesh.getVertexIndicesOfHalfEdge(mesh.halfedges[AB]);495size_t A, B, C;496A = horizonEdgeVertexIndices[0];497B = horizonEdgeVertexIndices[1];498C = activePointIndex;499500const size_t newFaceIndex = mesh.addFace();501newFaceIndices.push_back(newFaceIndex);502503const size_t CA = newHalfedgeIndices[2 * i + 0];504const size_t BC = newHalfedgeIndices[2 * i + 1];505506mesh.halfedgeNext[AB] = BC;507mesh.halfedgeNext[BC] = CA;508mesh.halfedgeNext[CA] = AB;509510mesh.halfedgeToFace[BC] = newFaceIndex;511mesh.halfedgeToFace[CA] = newFaceIndex;512mesh.halfedgeToFace[AB] = newFaceIndex;513514mesh.halfedges[CA].endVert = A;515mesh.halfedges[BC].endVert = C;516517auto& newFace = mesh.faces[newFaceIndex];518519const vec3 planeNormal = getTriangleNormal(520originalVertexData[A], originalVertexData[B], activePoint);521newFace.P = Plane(planeNormal, activePoint);522newFace.he = AB;523524mesh.halfedges[CA].pairedHalfedge =525newHalfedgeIndices[i > 0 ? i * 2 - 1 : 2 * horizonEdgeCount - 1];526mesh.halfedges[BC].pairedHalfedge =527newHalfedgeIndices[((i + 1) * 2) % (horizonEdgeCount * 2)];528}529530// Assign points that were on the positive side of the disabled faces to the531// new faces.532for (auto& disabledPoints : disabledFacePointVectors) {533DEBUG_ASSERT(disabledPoints != nullptr, logicErr,534"disabledPoints should not be null");535for (const auto& point : *(disabledPoints)) {536if (point == activePointIndex) {537continue;538}539for (size_t j = 0; j < horizonEdgeCount; j++) {540if (addPointToFace(mesh.faces[newFaceIndices[j]], point)) {541break;542}543}544}545// The points are no longer needed: we can move them to the vector pool546// for reuse.547reclaimToIndexVectorPool(disabledPoints);548}549550// Increase face stack size if needed551for (const auto newFaceIndex : newFaceIndices) {552auto& newFace = mesh.faces[newFaceIndex];553if (newFace.pointsOnPositiveSide) {554DEBUG_ASSERT(newFace.pointsOnPositiveSide->size() > 0, logicErr,555"there should be points on the positive side");556if (!newFace.inFaceStack) {557faceList.push_back(newFaceIndex);558newFace.inFaceStack = 1;559}560}561}562}563564// Cleanup565indexVectorPool.clear();566}567568/*569* Private helper functions570*/571572std::array<size_t, 6> QuickHull::getExtremeValues() {573std::array<size_t, 6> outIndices{0, 0, 0, 0, 0, 0};574double extremeVals[6] = {originalVertexData[0].x, originalVertexData[0].x,575originalVertexData[0].y, originalVertexData[0].y,576originalVertexData[0].z, originalVertexData[0].z};577const size_t vCount = originalVertexData.size();578for (size_t i = 1; i < vCount; i++) {579const vec3& pos = originalVertexData[i];580if (pos.x > extremeVals[0]) {581extremeVals[0] = pos.x;582outIndices[0] = i;583} else if (pos.x < extremeVals[1]) {584extremeVals[1] = pos.x;585outIndices[1] = i;586}587if (pos.y > extremeVals[2]) {588extremeVals[2] = pos.y;589outIndices[2] = i;590} else if (pos.y < extremeVals[3]) {591extremeVals[3] = pos.y;592outIndices[3] = i;593}594if (pos.z > extremeVals[4]) {595extremeVals[4] = pos.z;596outIndices[4] = i;597} else if (pos.z < extremeVals[5]) {598extremeVals[5] = pos.z;599outIndices[5] = i;600}601}602return outIndices;603}604605bool QuickHull::reorderHorizonEdges(VecView<size_t>& horizonEdges) {606const size_t horizonEdgeCount = horizonEdges.size();607for (size_t i = 0; i + 1 < horizonEdgeCount; i++) {608const size_t endVertexCheck = mesh.halfedges[horizonEdges[i]].endVert;609bool foundNext = false;610for (size_t j = i + 1; j < horizonEdgeCount; j++) {611const size_t beginVertex =612mesh.halfedges[mesh.halfedges[horizonEdges[j]].pairedHalfedge]613.endVert;614if (beginVertex == endVertexCheck) {615std::swap(horizonEdges[i + 1], horizonEdges[j]);616foundNext = true;617break;618}619}620if (!foundNext) {621return false;622}623}624DEBUG_ASSERT(625mesh.halfedges[horizonEdges[horizonEdges.size() - 1]].endVert ==626mesh.halfedges[mesh.halfedges[horizonEdges[0]].pairedHalfedge]627.endVert,628logicErr, "invalid halfedge");629return true;630}631632double QuickHull::getScale(const std::array<size_t, 6>& extremeValuesInput) {633double s = 0;634for (size_t i = 0; i < 6; i++) {635const double* v =636(const double*)(&originalVertexData[extremeValuesInput[i]]);637v += i / 2;638auto a = std::abs(*v);639if (a > s) {640s = a;641}642}643return s;644}645646void QuickHull::setupInitialTetrahedron() {647const size_t vertexCount = originalVertexData.size();648649// If we have at most 4 points, just return a degenerate tetrahedron:650if (vertexCount <= 4) {651size_t v[4] = {0, std::min((size_t)1, vertexCount - 1),652std::min((size_t)2, vertexCount - 1),653std::min((size_t)3, vertexCount - 1)};654const vec3 N =655getTriangleNormal(originalVertexData[v[0]], originalVertexData[v[1]],656originalVertexData[v[2]]);657const Plane trianglePlane(N, originalVertexData[v[0]]);658if (trianglePlane.isPointOnPositiveSide(originalVertexData[v[3]])) {659std::swap(v[0], v[1]);660}661return mesh.setup(v[0], v[1], v[2], v[3]);662}663664// Find two most distant extreme points.665double maxD = epsilonSquared;666std::pair<size_t, size_t> selectedPoints;667for (size_t i = 0; i < 6; i++) {668for (size_t j = i + 1; j < 6; j++) {669// I found a function for squaredDistance but i can't seem to include it670// like this for some reason671const double d = getSquaredDistance(originalVertexData[extremeValues[i]],672originalVertexData[extremeValues[j]]);673if (d > maxD) {674maxD = d;675selectedPoints = {extremeValues[i], extremeValues[j]};676}677}678}679if (maxD == epsilonSquared) {680// A degenerate case: the point cloud seems to consists of a single point681return mesh.setup(0, std::min((size_t)1, vertexCount - 1),682std::min((size_t)2, vertexCount - 1),683std::min((size_t)3, vertexCount - 1));684}685DEBUG_ASSERT(selectedPoints.first != selectedPoints.second, logicErr,686"degenerate selectedPoints");687688// Find the most distant point to the line between the two chosen extreme689// points.690const Ray r(originalVertexData[selectedPoints.first],691(originalVertexData[selectedPoints.second] -692originalVertexData[selectedPoints.first]));693maxD = epsilonSquared;694size_t maxI = std::numeric_limits<size_t>::max();695const size_t vCount = originalVertexData.size();696for (size_t i = 0; i < vCount; i++) {697const double distToRay =698getSquaredDistanceBetweenPointAndRay(originalVertexData[i], r);699if (distToRay > maxD) {700maxD = distToRay;701maxI = i;702}703}704if (maxD == epsilonSquared) {705// It appears that the point cloud belongs to a 1 dimensional subspace of706// R^3: convex hull has no volume => return a thin triangle Pick any point707// other than selectedPoints.first and selectedPoints.second as the third708// point of the triangle709auto it =710std::find_if(originalVertexData.begin(), originalVertexData.end(),711[&](const vec3& ve) {712return ve != originalVertexData[selectedPoints.first] &&713ve != originalVertexData[selectedPoints.second];714});715const size_t thirdPoint =716(it == originalVertexData.end())717? selectedPoints.first718: std::distance(originalVertexData.begin(), it);719it =720std::find_if(originalVertexData.begin(), originalVertexData.end(),721[&](const vec3& ve) {722return ve != originalVertexData[selectedPoints.first] &&723ve != originalVertexData[selectedPoints.second] &&724ve != originalVertexData[thirdPoint];725});726const size_t fourthPoint =727(it == originalVertexData.end())728? selectedPoints.first729: std::distance(originalVertexData.begin(), it);730return mesh.setup(selectedPoints.first, selectedPoints.second, thirdPoint,731fourthPoint);732}733734// These three points form the base triangle for our tetrahedron.735DEBUG_ASSERT(selectedPoints.first != maxI && selectedPoints.second != maxI,736logicErr, "degenerate selectedPoints");737std::array<size_t, 3> baseTriangle{selectedPoints.first,738selectedPoints.second, maxI};739const vec3 baseTriangleVertices[] = {originalVertexData[baseTriangle[0]],740originalVertexData[baseTriangle[1]],741originalVertexData[baseTriangle[2]]};742743// Next step is to find the 4th vertex of the tetrahedron. We naturally choose744// the point farthest away from the triangle plane.745maxD = m_epsilon;746maxI = 0;747const vec3 N =748getTriangleNormal(baseTriangleVertices[0], baseTriangleVertices[1],749baseTriangleVertices[2]);750Plane trianglePlane(N, baseTriangleVertices[0]);751for (size_t i = 0; i < vCount; i++) {752const double d = std::abs(753getSignedDistanceToPlane(originalVertexData[i], trianglePlane));754if (d > maxD) {755maxD = d;756maxI = i;757}758}759if (maxD == m_epsilon) {760// All the points seem to lie on a 2D subspace of R^3. How to handle this?761// Well, let's add one extra point to the point cloud so that the convex762// hull will have volume.763planar = true;764const vec3 N1 =765getTriangleNormal(baseTriangleVertices[1], baseTriangleVertices[2],766baseTriangleVertices[0]);767planarPointCloudTemp = Vec<vec3>(originalVertexData);768const vec3 extraPoint = N1 + originalVertexData[0];769planarPointCloudTemp.push_back(extraPoint);770maxI = planarPointCloudTemp.size() - 1;771originalVertexData = planarPointCloudTemp;772}773774// Enforce CCW orientation (if user prefers clockwise orientation, swap two775// vertices in each triangle when final mesh is created)776const Plane triPlane(N, baseTriangleVertices[0]);777if (triPlane.isPointOnPositiveSide(originalVertexData[maxI])) {778std::swap(baseTriangle[0], baseTriangle[1]);779}780781// Create a tetrahedron half edge mesh and compute planes defined by each782// triangle783mesh.setup(baseTriangle[0], baseTriangle[1], baseTriangle[2], maxI);784for (auto& f : mesh.faces) {785auto v = mesh.getVertexIndicesOfFace(f);786const vec3 N1 =787getTriangleNormal(originalVertexData[v[0]], originalVertexData[v[1]],788originalVertexData[v[2]]);789const Plane plane(N1, originalVertexData[v[0]]);790f.P = plane;791}792793// Finally we assign a face for each vertex outside the tetrahedron (vertices794// inside the tetrahedron have no role anymore)795for (size_t i = 0; i < vCount; i++) {796for (auto& face : mesh.faces) {797if (addPointToFace(face, i)) {798break;799}800}801}802}803804std::unique_ptr<Vec<size_t>> QuickHull::getIndexVectorFromPool() {805auto r = indexVectorPool.get();806r->clear();807return r;808}809810void QuickHull::reclaimToIndexVectorPool(std::unique_ptr<Vec<size_t>>& ptr) {811const size_t oldSize = ptr->size();812if ((oldSize + 1) * 128 < ptr->capacity()) {813// Reduce memory usage! Huge vectors are needed at the beginning of814// iteration when faces have many points on their positive side. Later on,815// smaller vectors will suffice.816ptr.reset(nullptr);817return;818}819indexVectorPool.reclaim(ptr);820}821822bool QuickHull::addPointToFace(typename MeshBuilder::Face& f,823size_t pointIndex) {824const double D =825getSignedDistanceToPlane(originalVertexData[pointIndex], f.P);826if (D > 0 && D * D > epsilonSquared * f.P.sqrNLength) {827if (!f.pointsOnPositiveSide) {828f.pointsOnPositiveSide = getIndexVectorFromPool();829}830f.pointsOnPositiveSide->push_back(pointIndex);831if (D > f.mostDistantPointDist) {832f.mostDistantPointDist = D;833f.mostDistantPoint = pointIndex;834}835return true;836}837return false;838}839840// Wrapper to call the QuickHull algorithm with the given vertex data to build841// the Impl842void Manifold::Impl::Hull(VecView<vec3> vertPos) {843size_t numVert = vertPos.size();844if (numVert < 4) {845status_ = Error::InvalidConstruction;846return;847}848849QuickHull qh(vertPos);850std::tie(halfedge_, vertPos_) = qh.buildMesh();851CalculateBBox();852SetEpsilon();853InitializeOriginal();854Finish();855MarkCoplanar();856}857858} // namespace manifold859860861