Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/manifold/src/quickhull.cpp
20897 views
1
// Copyright 2024 The Manifold Authors.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
//
15
// Derived from the public domain work of Antti Kuukka at
16
// https://github.com/akuukka/quickhull
17
18
#include "quickhull.h"
19
20
#include <algorithm>
21
#include <cstddef>
22
#include <limits>
23
#include <unordered_map>
24
25
#include "impl.h"
26
27
namespace manifold {
28
29
double defaultEps() { return 0.0000001; }
30
31
inline double getSquaredDistanceBetweenPointAndRay(const vec3& p,
32
const Ray& r) {
33
const vec3 s = p - r.S;
34
double t = la::dot(s, r.V);
35
return la::dot(s, s) - t * t * r.VInvLengthSquared;
36
}
37
38
inline double getSquaredDistance(const vec3& p1, const vec3& p2) {
39
return la::dot(p1 - p2, p1 - p2);
40
}
41
// Note that the unit of distance returned is relative to plane's normal's
42
// length (divide by N.getNormalized() if needed to get the "real" distance).
43
inline double getSignedDistanceToPlane(const vec3& v, const Plane& p) {
44
return la::dot(p.N, v) + p.D;
45
}
46
47
inline vec3 getTriangleNormal(const vec3& a, const vec3& b, const vec3& c) {
48
// We want to get (a-c).crossProduct(b-c) without constructing temp vectors
49
double x = a.x - c.x;
50
double y = a.y - c.y;
51
double z = a.z - c.z;
52
double rhsx = b.x - c.x;
53
double rhsy = b.y - c.y;
54
double rhsz = b.z - c.z;
55
double px = y * rhsz - z * rhsy;
56
double py = z * rhsx - x * rhsz;
57
double pz = x * rhsy - y * rhsx;
58
return la::normalize(vec3(px, py, pz));
59
}
60
61
size_t MeshBuilder::addFace() {
62
if (disabledFaces.size()) {
63
size_t index = disabledFaces.back();
64
auto& f = faces[index];
65
DEBUG_ASSERT(f.isDisabled(), logicErr, "f should be disabled");
66
DEBUG_ASSERT(!f.pointsOnPositiveSide, logicErr,
67
"f should not be on the positive side");
68
f.mostDistantPointDist = 0;
69
disabledFaces.pop_back();
70
return index;
71
}
72
faces.emplace_back();
73
return faces.size() - 1;
74
}
75
76
size_t MeshBuilder::addHalfedge() {
77
if (disabledHalfedges.size()) {
78
const size_t index = disabledHalfedges.back();
79
disabledHalfedges.pop_back();
80
return index;
81
}
82
halfedges.push_back({});
83
halfedgeToFace.push_back(0);
84
halfedgeNext.push_back(0);
85
return halfedges.size() - 1;
86
}
87
88
void MeshBuilder::setup(int a, int b, int c, int d) {
89
faces.clear();
90
halfedges.clear();
91
halfedgeToFace.clear();
92
halfedgeNext.clear();
93
disabledFaces.clear();
94
disabledHalfedges.clear();
95
96
faces.reserve(4);
97
halfedges.reserve(12);
98
99
// Create halfedges
100
// AB
101
halfedges.push_back({0, b, 6});
102
halfedgeToFace.push_back(0);
103
halfedgeNext.push_back(1);
104
// BC
105
halfedges.push_back({0, c, 9});
106
halfedgeToFace.push_back(0);
107
halfedgeNext.push_back(2);
108
// CA
109
halfedges.push_back({0, a, 3});
110
halfedgeToFace.push_back(0);
111
halfedgeNext.push_back(0);
112
// AC
113
halfedges.push_back({0, c, 2});
114
halfedgeToFace.push_back(1);
115
halfedgeNext.push_back(4);
116
// CD
117
halfedges.push_back({0, d, 11});
118
halfedgeToFace.push_back(1);
119
halfedgeNext.push_back(5);
120
// DA
121
halfedges.push_back({0, a, 7});
122
halfedgeToFace.push_back(1);
123
halfedgeNext.push_back(3);
124
// BA
125
halfedges.push_back({0, a, 0});
126
halfedgeToFace.push_back(2);
127
halfedgeNext.push_back(7);
128
// AD
129
halfedges.push_back({0, d, 5});
130
halfedgeToFace.push_back(2);
131
halfedgeNext.push_back(8);
132
// DB
133
halfedges.push_back({0, b, 10});
134
halfedgeToFace.push_back(2);
135
halfedgeNext.push_back(6);
136
// CB
137
halfedges.push_back({0, b, 1});
138
halfedgeToFace.push_back(3);
139
halfedgeNext.push_back(10);
140
// BD
141
halfedges.push_back({0, d, 8});
142
halfedgeToFace.push_back(3);
143
halfedgeNext.push_back(11);
144
// DC
145
halfedges.push_back({0, c, 4});
146
halfedgeToFace.push_back(3);
147
halfedgeNext.push_back(9);
148
149
// Create faces
150
faces.emplace_back(0);
151
faces.emplace_back(3);
152
faces.emplace_back(6);
153
faces.emplace_back(9);
154
}
155
156
std::array<int, 3> MeshBuilder::getVertexIndicesOfFace(const Face& f) const {
157
std::array<int, 3> v;
158
size_t index = f.he;
159
auto* he = &halfedges[index];
160
v[0] = he->endVert;
161
162
index = halfedgeNext[index];
163
he = &halfedges[index];
164
v[1] = he->endVert;
165
166
index = halfedgeNext[index];
167
he = &halfedges[index];
168
v[2] = he->endVert;
169
return v;
170
}
171
172
HalfEdgeMesh::HalfEdgeMesh(const MeshBuilder& builderObject,
173
const VecView<vec3>& vertexData) {
174
std::unordered_map<size_t, size_t> faceMapping;
175
std::unordered_map<size_t, size_t> halfEdgeMapping;
176
std::unordered_map<size_t, size_t> vertexMapping;
177
178
size_t i = 0;
179
for (const auto& face : builderObject.faces) {
180
if (!face.isDisabled()) {
181
halfEdgeIndexFaces.emplace_back(static_cast<size_t>(face.he));
182
faceMapping[i] = halfEdgeIndexFaces.size() - 1;
183
184
const auto heIndices = builderObject.getHalfEdgeIndicesOfFace(face);
185
for (const auto heIndex : heIndices) {
186
const auto vertexIndex = builderObject.halfedges[heIndex].endVert;
187
if (vertexMapping.count(vertexIndex) == 0) {
188
vertices.push_back(vertexData[vertexIndex]);
189
vertexMapping[vertexIndex] = vertices.size() - 1;
190
}
191
}
192
}
193
i++;
194
}
195
196
i = 0;
197
for (const auto& halfEdge : builderObject.halfedges) {
198
if (halfEdge.pairedHalfedge != -1) {
199
halfedges.push_back({halfEdge.endVert, halfEdge.pairedHalfedge,
200
builderObject.halfedgeToFace[i]});
201
halfedgeToFace.push_back(builderObject.halfedgeToFace[i]);
202
halfedgeNext.push_back(builderObject.halfedgeNext[i]);
203
halfEdgeMapping[i] = halfedges.size() - 1;
204
}
205
i++;
206
}
207
208
for (auto& halfEdgeIndexFace : halfEdgeIndexFaces) {
209
DEBUG_ASSERT(halfEdgeMapping.count(halfEdgeIndexFace) == 1, logicErr,
210
"invalid halfedge mapping");
211
halfEdgeIndexFace = halfEdgeMapping[halfEdgeIndexFace];
212
}
213
214
for (size_t i = 0; i < halfedges.size(); i++) {
215
auto& he = halfedges[i];
216
halfedgeToFace[i] = faceMapping[halfedgeToFace[i]];
217
he.pairedHalfedge = halfEdgeMapping[he.pairedHalfedge];
218
halfedgeNext[i] = halfEdgeMapping[halfedgeNext[i]];
219
he.endVert = vertexMapping[he.endVert];
220
}
221
}
222
223
/*
224
* Implementation of the algorithm
225
*/
226
std::pair<Vec<Halfedge>, Vec<vec3>> QuickHull::buildMesh(double epsilon) {
227
if (originalVertexData.size() == 0) {
228
return {Vec<Halfedge>(), Vec<vec3>()};
229
}
230
231
// Very first: find extreme values and use them to compute the scale of the
232
// point cloud.
233
extremeValues = getExtremeValues();
234
scale = getScale(extremeValues);
235
236
// Epsilon we use depends on the scale
237
m_epsilon = epsilon * scale;
238
epsilonSquared = m_epsilon * m_epsilon;
239
240
// The planar case happens when all the points appear to lie on a two
241
// dimensional subspace of R^3.
242
planar = false;
243
createConvexHalfedgeMesh();
244
if (planar) {
245
const int extraPointIndex = planarPointCloudTemp.size() - 1;
246
for (auto& he : mesh.halfedges) {
247
if (he.endVert == extraPointIndex) {
248
he.endVert = 0;
249
}
250
}
251
planarPointCloudTemp.clear();
252
}
253
254
// reorder halfedges
255
Vec<Halfedge> halfedges(mesh.halfedges.size());
256
Vec<int> halfedgeToFace(mesh.halfedges.size());
257
Vec<int> counts(mesh.halfedges.size(), 0);
258
Vec<int> mapping(mesh.halfedges.size());
259
Vec<int> faceMap(mesh.faces.size());
260
261
// Some faces are disabled and should not go into the halfedge vector, we can
262
// update the face indices of the halfedges at the end using index/3
263
int j = 0;
264
for_each(
265
autoPolicy(mesh.halfedges.size()), countAt(0_uz),
266
countAt(mesh.halfedges.size()), [&](size_t i) {
267
if (mesh.halfedges[i].pairedHalfedge < 0) return;
268
if (mesh.faces[mesh.halfedgeToFace[i]].isDisabled()) return;
269
if (AtomicAdd(counts[mesh.halfedgeToFace[i]], 1) > 0) return;
270
int currIndex = AtomicAdd(j, 3);
271
mapping[i] = currIndex;
272
halfedges[currIndex + 0] = mesh.halfedges[i];
273
halfedgeToFace[currIndex + 0] = mesh.halfedgeToFace[i];
274
275
size_t k = mesh.halfedgeNext[i];
276
mapping[k] = currIndex + 1;
277
halfedges[currIndex + 1] = mesh.halfedges[k];
278
halfedgeToFace[currIndex + 1] = mesh.halfedgeToFace[k];
279
280
k = mesh.halfedgeNext[k];
281
mapping[k] = currIndex + 2;
282
halfedges[currIndex + 2] = mesh.halfedges[k];
283
halfedgeToFace[currIndex + 2] = mesh.halfedgeToFace[k];
284
halfedges[currIndex + 0].startVert = halfedges[currIndex + 2].endVert;
285
halfedges[currIndex + 1].startVert = halfedges[currIndex + 0].endVert;
286
halfedges[currIndex + 2].startVert = halfedges[currIndex + 1].endVert;
287
});
288
halfedges.resize(j);
289
halfedgeToFace.resize(j);
290
// fix pairedHalfedge id
291
for_each(
292
autoPolicy(halfedges.size()), halfedges.begin(), halfedges.end(),
293
[&](Halfedge& he) { he.pairedHalfedge = mapping[he.pairedHalfedge]; });
294
counts.resize_nofill(originalVertexData.size() + 1);
295
fill(counts.begin(), counts.end(), 0);
296
297
// remove unused vertices
298
for_each(autoPolicy(halfedges.size() / 3), countAt(0_uz),
299
countAt(halfedges.size() / 3), [&](size_t i) {
300
AtomicAdd(counts[halfedges[3 * i].startVert], 1);
301
AtomicAdd(counts[halfedges[3 * i + 1].startVert], 1);
302
AtomicAdd(counts[halfedges[3 * i + 2].startVert], 1);
303
});
304
auto saturate = [](int c) { return c > 0 ? 1 : 0; };
305
exclusive_scan(TransformIterator(counts.begin(), saturate),
306
TransformIterator(counts.end(), saturate), counts.begin(), 0);
307
Vec<vec3> vertices(counts.back());
308
for_each(autoPolicy(originalVertexData.size()), countAt(0_uz),
309
countAt(originalVertexData.size()), [&](size_t i) {
310
if (counts[i + 1] - counts[i] > 0) {
311
vertices[counts[i]] = originalVertexData[i];
312
}
313
});
314
for_each(autoPolicy(halfedges.size()), halfedges.begin(), halfedges.end(),
315
[&](Halfedge& he) {
316
he.startVert = counts[he.startVert];
317
he.endVert = counts[he.endVert];
318
});
319
return {std::move(halfedges), std::move(vertices)};
320
}
321
322
void QuickHull::createConvexHalfedgeMesh() {
323
visibleFaces.clear();
324
horizonEdgesData.clear();
325
possiblyVisibleFaces.clear();
326
327
// Compute base tetrahedron
328
setupInitialTetrahedron();
329
DEBUG_ASSERT(mesh.faces.size() == 4, logicErr, "not a tetrahedron");
330
331
// Init face stack with those faces that have points assigned to them
332
faceList.clear();
333
for (size_t i = 0; i < 4; i++) {
334
auto& f = mesh.faces[i];
335
if (f.pointsOnPositiveSide && f.pointsOnPositiveSide->size() > 0) {
336
faceList.push_back(i);
337
f.inFaceStack = 1;
338
}
339
}
340
341
// Process faces until the face list is empty.
342
size_t iter = 0;
343
while (!faceList.empty()) {
344
iter++;
345
if (iter == std::numeric_limits<size_t>::max()) {
346
// Visible face traversal marks visited faces with iteration counter (to
347
// mark that the face has been visited on this iteration) and the max
348
// value represents unvisited faces. At this point we have to reset
349
// iteration counter. This shouldn't be an issue on 64 bit machines.
350
iter = 0;
351
}
352
353
const auto topFaceIndex = faceList.front();
354
faceList.pop_front();
355
356
auto& tf = mesh.faces[topFaceIndex];
357
tf.inFaceStack = 0;
358
359
DEBUG_ASSERT(
360
!tf.pointsOnPositiveSide || tf.pointsOnPositiveSide->size() > 0,
361
logicErr, "there should be points on the positive side");
362
if (!tf.pointsOnPositiveSide || tf.isDisabled()) {
363
continue;
364
}
365
366
// Pick the most distant point to this triangle plane as the point to which
367
// we extrude
368
const vec3& activePoint = originalVertexData[tf.mostDistantPoint];
369
const size_t activePointIndex = tf.mostDistantPoint;
370
371
// Find out the faces that have our active point on their positive side
372
// (these are the "visible faces"). The face on top of the stack of course
373
// is one of them. At the same time, we create a list of horizon edges.
374
horizonEdgesData.clear();
375
possiblyVisibleFaces.clear();
376
visibleFaces.clear();
377
possiblyVisibleFaces.push_back({topFaceIndex, -1});
378
while (possiblyVisibleFaces.size()) {
379
const auto faceData = possiblyVisibleFaces.back();
380
possiblyVisibleFaces.pop_back();
381
auto& pvf = mesh.faces[faceData.faceIndex];
382
DEBUG_ASSERT(!pvf.isDisabled(), logicErr, "pvf should not be disabled");
383
384
if (pvf.visibilityCheckedOnIteration == iter) {
385
if (pvf.isVisibleFaceOnCurrentIteration) {
386
continue;
387
}
388
} else {
389
const Plane& P = pvf.P;
390
pvf.visibilityCheckedOnIteration = iter;
391
const double d = la::dot(P.N, activePoint) + P.D;
392
if (d > 0) {
393
pvf.isVisibleFaceOnCurrentIteration = 1;
394
pvf.horizonEdgesOnCurrentIteration = 0;
395
visibleFaces.push_back(faceData.faceIndex);
396
for (auto heIndex : mesh.getHalfEdgeIndicesOfFace(pvf)) {
397
if (mesh.halfedges[heIndex].pairedHalfedge !=
398
faceData.enteredFromHalfedge) {
399
possiblyVisibleFaces.push_back(
400
{mesh.halfedgeToFace[mesh.halfedges[heIndex].pairedHalfedge],
401
heIndex});
402
}
403
}
404
continue;
405
}
406
DEBUG_ASSERT(faceData.faceIndex != topFaceIndex, logicErr,
407
"face index invalid");
408
}
409
410
// The face is not visible. Therefore, the halfedge we came from is part
411
// of the horizon edge.
412
pvf.isVisibleFaceOnCurrentIteration = 0;
413
horizonEdgesData.push_back(faceData.enteredFromHalfedge);
414
// Store which half edge is the horizon edge. The other half edges of the
415
// face will not be part of the final mesh so their data slots can by
416
// recycled.
417
const auto halfEdgesMesh = mesh.getHalfEdgeIndicesOfFace(
418
mesh.faces[mesh.halfedgeToFace[faceData.enteredFromHalfedge]]);
419
const std::int8_t ind =
420
(halfEdgesMesh[0] == faceData.enteredFromHalfedge)
421
? 0
422
: (halfEdgesMesh[1] == faceData.enteredFromHalfedge ? 1 : 2);
423
mesh.faces[mesh.halfedgeToFace[faceData.enteredFromHalfedge]]
424
.horizonEdgesOnCurrentIteration |= (1 << ind);
425
}
426
const size_t horizonEdgeCount = horizonEdgesData.size();
427
428
// Order horizon edges so that they form a loop. This may fail due to
429
// numerical instability in which case we give up trying to solve horizon
430
// edge for this point and accept a minor degeneration in the convex hull.
431
if (!reorderHorizonEdges(horizonEdgesData)) {
432
failedHorizonEdges++;
433
int change_flag = 0;
434
for (size_t index = 0; index < tf.pointsOnPositiveSide->size(); index++) {
435
if ((*tf.pointsOnPositiveSide)[index] == activePointIndex) {
436
change_flag = 1;
437
} else if (change_flag == 1) {
438
change_flag = 2;
439
(*tf.pointsOnPositiveSide)[index - 1] =
440
(*tf.pointsOnPositiveSide)[index];
441
}
442
}
443
if (change_flag == 1)
444
tf.pointsOnPositiveSide->resize(tf.pointsOnPositiveSide->size() - 1);
445
446
if (tf.pointsOnPositiveSide->size() == 0) {
447
reclaimToIndexVectorPool(tf.pointsOnPositiveSide);
448
}
449
continue;
450
}
451
452
// Except for the horizon edges, all half edges of the visible faces can be
453
// marked as disabled. Their data slots will be reused. The faces will be
454
// disabled as well, but we need to remember the points that were on the
455
// positive side of them - therefore we save pointers to them.
456
newFaceIndices.clear();
457
newHalfedgeIndices.clear();
458
disabledFacePointVectors.clear();
459
size_t disableCounter = 0;
460
for (auto faceIndex : visibleFaces) {
461
auto& disabledFace = mesh.faces[faceIndex];
462
auto halfEdgesMesh = mesh.getHalfEdgeIndicesOfFace(disabledFace);
463
for (size_t j = 0; j < 3; j++) {
464
if ((disabledFace.horizonEdgesOnCurrentIteration & (1 << j)) == 0) {
465
if (disableCounter < horizonEdgeCount * 2) {
466
// Use on this iteration
467
newHalfedgeIndices.push_back(halfEdgesMesh[j]);
468
disableCounter++;
469
} else {
470
// Mark for reusal on later iteration step
471
mesh.disableHalfedge(halfEdgesMesh[j]);
472
}
473
}
474
}
475
// Disable the face, but retain pointer to the points that were on the
476
// positive side of it. We need to assign those points to the new faces we
477
// create shortly.
478
auto t = mesh.disableFace(faceIndex);
479
if (t) {
480
// Because we should not assign point vectors to faces unless needed...
481
DEBUG_ASSERT(t->size(), logicErr, "t should not be empty");
482
disabledFacePointVectors.push_back(std::move(t));
483
}
484
}
485
if (disableCounter < horizonEdgeCount * 2) {
486
const size_t newHalfEdgesNeeded = horizonEdgeCount * 2 - disableCounter;
487
for (size_t i = 0; i < newHalfEdgesNeeded; i++) {
488
newHalfedgeIndices.push_back(mesh.addHalfedge());
489
}
490
}
491
492
// Create new faces using the edgeloop
493
for (size_t i = 0; i < horizonEdgeCount; i++) {
494
const size_t AB = horizonEdgesData[i];
495
496
auto horizonEdgeVertexIndices =
497
mesh.getVertexIndicesOfHalfEdge(mesh.halfedges[AB]);
498
size_t A, B, C;
499
A = horizonEdgeVertexIndices[0];
500
B = horizonEdgeVertexIndices[1];
501
C = activePointIndex;
502
503
const size_t newFaceIndex = mesh.addFace();
504
newFaceIndices.push_back(newFaceIndex);
505
506
const size_t CA = newHalfedgeIndices[2 * i + 0];
507
const size_t BC = newHalfedgeIndices[2 * i + 1];
508
509
mesh.halfedgeNext[AB] = BC;
510
mesh.halfedgeNext[BC] = CA;
511
mesh.halfedgeNext[CA] = AB;
512
513
mesh.halfedgeToFace[BC] = newFaceIndex;
514
mesh.halfedgeToFace[CA] = newFaceIndex;
515
mesh.halfedgeToFace[AB] = newFaceIndex;
516
517
mesh.halfedges[CA].endVert = A;
518
mesh.halfedges[BC].endVert = C;
519
520
auto& newFace = mesh.faces[newFaceIndex];
521
522
const vec3 planeNormal = getTriangleNormal(
523
originalVertexData[A], originalVertexData[B], activePoint);
524
newFace.P = Plane(planeNormal, activePoint);
525
newFace.he = AB;
526
527
mesh.halfedges[CA].pairedHalfedge =
528
newHalfedgeIndices[i > 0 ? i * 2 - 1 : 2 * horizonEdgeCount - 1];
529
mesh.halfedges[BC].pairedHalfedge =
530
newHalfedgeIndices[((i + 1) * 2) % (horizonEdgeCount * 2)];
531
}
532
533
// Assign points that were on the positive side of the disabled faces to the
534
// new faces.
535
for (auto& disabledPoints : disabledFacePointVectors) {
536
DEBUG_ASSERT(disabledPoints != nullptr, logicErr,
537
"disabledPoints should not be null");
538
for (const auto& point : *(disabledPoints)) {
539
if (point == activePointIndex) {
540
continue;
541
}
542
for (size_t j = 0; j < horizonEdgeCount; j++) {
543
if (addPointToFace(mesh.faces[newFaceIndices[j]], point)) {
544
break;
545
}
546
}
547
}
548
// The points are no longer needed: we can move them to the vector pool
549
// for reuse.
550
reclaimToIndexVectorPool(disabledPoints);
551
}
552
553
// Increase face stack size if needed
554
for (const auto newFaceIndex : newFaceIndices) {
555
auto& newFace = mesh.faces[newFaceIndex];
556
if (newFace.pointsOnPositiveSide) {
557
DEBUG_ASSERT(newFace.pointsOnPositiveSide->size() > 0, logicErr,
558
"there should be points on the positive side");
559
if (!newFace.inFaceStack) {
560
faceList.push_back(newFaceIndex);
561
newFace.inFaceStack = 1;
562
}
563
}
564
}
565
}
566
567
// Cleanup
568
indexVectorPool.clear();
569
}
570
571
/*
572
* Private helper functions
573
*/
574
575
std::array<size_t, 6> QuickHull::getExtremeValues() {
576
std::array<size_t, 6> outIndices{0, 0, 0, 0, 0, 0};
577
double extremeVals[6] = {originalVertexData[0].x, originalVertexData[0].x,
578
originalVertexData[0].y, originalVertexData[0].y,
579
originalVertexData[0].z, originalVertexData[0].z};
580
const size_t vCount = originalVertexData.size();
581
for (size_t i = 1; i < vCount; i++) {
582
const vec3& pos = originalVertexData[i];
583
if (pos.x > extremeVals[0]) {
584
extremeVals[0] = pos.x;
585
outIndices[0] = i;
586
} else if (pos.x < extremeVals[1]) {
587
extremeVals[1] = pos.x;
588
outIndices[1] = i;
589
}
590
if (pos.y > extremeVals[2]) {
591
extremeVals[2] = pos.y;
592
outIndices[2] = i;
593
} else if (pos.y < extremeVals[3]) {
594
extremeVals[3] = pos.y;
595
outIndices[3] = i;
596
}
597
if (pos.z > extremeVals[4]) {
598
extremeVals[4] = pos.z;
599
outIndices[4] = i;
600
} else if (pos.z < extremeVals[5]) {
601
extremeVals[5] = pos.z;
602
outIndices[5] = i;
603
}
604
}
605
return outIndices;
606
}
607
608
bool QuickHull::reorderHorizonEdges(VecView<size_t>& horizonEdges) {
609
const size_t horizonEdgeCount = horizonEdges.size();
610
for (size_t i = 0; i + 1 < horizonEdgeCount; i++) {
611
const size_t endVertexCheck = mesh.halfedges[horizonEdges[i]].endVert;
612
bool foundNext = false;
613
for (size_t j = i + 1; j < horizonEdgeCount; j++) {
614
const size_t beginVertex =
615
mesh.halfedges[mesh.halfedges[horizonEdges[j]].pairedHalfedge]
616
.endVert;
617
if (beginVertex == endVertexCheck) {
618
std::swap(horizonEdges[i + 1], horizonEdges[j]);
619
foundNext = true;
620
break;
621
}
622
}
623
if (!foundNext) {
624
return false;
625
}
626
}
627
DEBUG_ASSERT(
628
mesh.halfedges[horizonEdges[horizonEdges.size() - 1]].endVert ==
629
mesh.halfedges[mesh.halfedges[horizonEdges[0]].pairedHalfedge]
630
.endVert,
631
logicErr, "invalid halfedge");
632
return true;
633
}
634
635
double QuickHull::getScale(const std::array<size_t, 6>& extremeValuesInput) {
636
double s = 0;
637
for (size_t i = 0; i < 6; i++) {
638
const double* v =
639
(const double*)(&originalVertexData[extremeValuesInput[i]]);
640
v += i / 2;
641
auto a = std::abs(*v);
642
if (a > s) {
643
s = a;
644
}
645
}
646
return s;
647
}
648
649
void QuickHull::setupInitialTetrahedron() {
650
const size_t vertexCount = originalVertexData.size();
651
652
// If we have at most 4 points, just return a degenerate tetrahedron:
653
if (vertexCount <= 4) {
654
size_t v[4] = {0, std::min((size_t)1, vertexCount - 1),
655
std::min((size_t)2, vertexCount - 1),
656
std::min((size_t)3, vertexCount - 1)};
657
const vec3 N =
658
getTriangleNormal(originalVertexData[v[0]], originalVertexData[v[1]],
659
originalVertexData[v[2]]);
660
const Plane trianglePlane(N, originalVertexData[v[0]]);
661
if (trianglePlane.isPointOnPositiveSide(originalVertexData[v[3]])) {
662
std::swap(v[0], v[1]);
663
}
664
return mesh.setup(v[0], v[1], v[2], v[3]);
665
}
666
667
// Find two most distant extreme points.
668
double maxD = epsilonSquared;
669
std::pair<size_t, size_t> selectedPoints;
670
for (size_t i = 0; i < 6; i++) {
671
for (size_t j = i + 1; j < 6; j++) {
672
// I found a function for squaredDistance but i can't seem to include it
673
// like this for some reason
674
const double d = getSquaredDistance(originalVertexData[extremeValues[i]],
675
originalVertexData[extremeValues[j]]);
676
if (d > maxD) {
677
maxD = d;
678
selectedPoints = {extremeValues[i], extremeValues[j]};
679
}
680
}
681
}
682
if (maxD == epsilonSquared) {
683
// A degenerate case: the point cloud seems to consists of a single point
684
return mesh.setup(0, std::min((size_t)1, vertexCount - 1),
685
std::min((size_t)2, vertexCount - 1),
686
std::min((size_t)3, vertexCount - 1));
687
}
688
DEBUG_ASSERT(selectedPoints.first != selectedPoints.second, logicErr,
689
"degenerate selectedPoints");
690
691
// Find the most distant point to the line between the two chosen extreme
692
// points.
693
const Ray r(originalVertexData[selectedPoints.first],
694
(originalVertexData[selectedPoints.second] -
695
originalVertexData[selectedPoints.first]));
696
maxD = epsilonSquared;
697
size_t maxI = std::numeric_limits<size_t>::max();
698
const size_t vCount = originalVertexData.size();
699
for (size_t i = 0; i < vCount; i++) {
700
const double distToRay =
701
getSquaredDistanceBetweenPointAndRay(originalVertexData[i], r);
702
if (distToRay > maxD) {
703
maxD = distToRay;
704
maxI = i;
705
}
706
}
707
if (maxD == epsilonSquared) {
708
// It appears that the point cloud belongs to a 1 dimensional subspace of
709
// R^3: convex hull has no volume => return a thin triangle Pick any point
710
// other than selectedPoints.first and selectedPoints.second as the third
711
// point of the triangle
712
auto it =
713
std::find_if(originalVertexData.begin(), originalVertexData.end(),
714
[&](const vec3& ve) {
715
return ve != originalVertexData[selectedPoints.first] &&
716
ve != originalVertexData[selectedPoints.second];
717
});
718
const size_t thirdPoint =
719
(it == originalVertexData.end())
720
? selectedPoints.first
721
: std::distance(originalVertexData.begin(), it);
722
it =
723
std::find_if(originalVertexData.begin(), originalVertexData.end(),
724
[&](const vec3& ve) {
725
return ve != originalVertexData[selectedPoints.first] &&
726
ve != originalVertexData[selectedPoints.second] &&
727
ve != originalVertexData[thirdPoint];
728
});
729
const size_t fourthPoint =
730
(it == originalVertexData.end())
731
? selectedPoints.first
732
: std::distance(originalVertexData.begin(), it);
733
return mesh.setup(selectedPoints.first, selectedPoints.second, thirdPoint,
734
fourthPoint);
735
}
736
737
// These three points form the base triangle for our tetrahedron.
738
DEBUG_ASSERT(selectedPoints.first != maxI && selectedPoints.second != maxI,
739
logicErr, "degenerate selectedPoints");
740
std::array<size_t, 3> baseTriangle{selectedPoints.first,
741
selectedPoints.second, maxI};
742
const vec3 baseTriangleVertices[] = {originalVertexData[baseTriangle[0]],
743
originalVertexData[baseTriangle[1]],
744
originalVertexData[baseTriangle[2]]};
745
746
// Next step is to find the 4th vertex of the tetrahedron. We naturally choose
747
// the point farthest away from the triangle plane.
748
maxD = m_epsilon;
749
maxI = 0;
750
const vec3 N =
751
getTriangleNormal(baseTriangleVertices[0], baseTriangleVertices[1],
752
baseTriangleVertices[2]);
753
Plane trianglePlane(N, baseTriangleVertices[0]);
754
for (size_t i = 0; i < vCount; i++) {
755
const double d = std::abs(
756
getSignedDistanceToPlane(originalVertexData[i], trianglePlane));
757
if (d > maxD) {
758
maxD = d;
759
maxI = i;
760
}
761
}
762
if (maxD == m_epsilon) {
763
// All the points seem to lie on a 2D subspace of R^3. How to handle this?
764
// Well, let's add one extra point to the point cloud so that the convex
765
// hull will have volume.
766
planar = true;
767
const vec3 N1 =
768
getTriangleNormal(baseTriangleVertices[1], baseTriangleVertices[2],
769
baseTriangleVertices[0]);
770
planarPointCloudTemp = Vec<vec3>(originalVertexData);
771
const vec3 extraPoint = N1 + originalVertexData[0];
772
planarPointCloudTemp.push_back(extraPoint);
773
maxI = planarPointCloudTemp.size() - 1;
774
originalVertexData = planarPointCloudTemp;
775
}
776
777
// Enforce CCW orientation (if user prefers clockwise orientation, swap two
778
// vertices in each triangle when final mesh is created)
779
const Plane triPlane(N, baseTriangleVertices[0]);
780
if (triPlane.isPointOnPositiveSide(originalVertexData[maxI])) {
781
std::swap(baseTriangle[0], baseTriangle[1]);
782
}
783
784
// Create a tetrahedron half edge mesh and compute planes defined by each
785
// triangle
786
mesh.setup(baseTriangle[0], baseTriangle[1], baseTriangle[2], maxI);
787
for (auto& f : mesh.faces) {
788
auto v = mesh.getVertexIndicesOfFace(f);
789
const vec3 N1 =
790
getTriangleNormal(originalVertexData[v[0]], originalVertexData[v[1]],
791
originalVertexData[v[2]]);
792
const Plane plane(N1, originalVertexData[v[0]]);
793
f.P = plane;
794
}
795
796
// Finally we assign a face for each vertex outside the tetrahedron (vertices
797
// inside the tetrahedron have no role anymore)
798
for (size_t i = 0; i < vCount; i++) {
799
for (auto& face : mesh.faces) {
800
if (addPointToFace(face, i)) {
801
break;
802
}
803
}
804
}
805
}
806
807
std::unique_ptr<Vec<size_t>> QuickHull::getIndexVectorFromPool() {
808
auto r = indexVectorPool.get();
809
r->clear();
810
return r;
811
}
812
813
void QuickHull::reclaimToIndexVectorPool(std::unique_ptr<Vec<size_t>>& ptr) {
814
const size_t oldSize = ptr->size();
815
if ((oldSize + 1) * 128 < ptr->capacity()) {
816
// Reduce memory usage! Huge vectors are needed at the beginning of
817
// iteration when faces have many points on their positive side. Later on,
818
// smaller vectors will suffice.
819
ptr.reset(nullptr);
820
return;
821
}
822
indexVectorPool.reclaim(ptr);
823
}
824
825
bool QuickHull::addPointToFace(typename MeshBuilder::Face& f,
826
size_t pointIndex) {
827
const double D =
828
getSignedDistanceToPlane(originalVertexData[pointIndex], f.P);
829
if (D > 0 && D * D > epsilonSquared * f.P.sqrNLength) {
830
if (!f.pointsOnPositiveSide) {
831
f.pointsOnPositiveSide = getIndexVectorFromPool();
832
}
833
f.pointsOnPositiveSide->push_back(pointIndex);
834
if (D > f.mostDistantPointDist) {
835
f.mostDistantPointDist = D;
836
f.mostDistantPoint = pointIndex;
837
}
838
return true;
839
}
840
return false;
841
}
842
843
// Wrapper to call the QuickHull algorithm with the given vertex data to build
844
// the Impl
845
void Manifold::Impl::Hull(VecView<vec3> vertPos) {
846
size_t numVert = vertPos.size();
847
if (numVert < 4) {
848
status_ = Error::InvalidConstruction;
849
return;
850
}
851
852
QuickHull qh(vertPos);
853
std::tie(halfedge_, vertPos_) = qh.buildMesh();
854
CalculateBBox();
855
SetEpsilon();
856
InitializeOriginal();
857
Finish();
858
MarkCoplanar();
859
}
860
861
} // namespace manifold
862
863