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