Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/manifold/src/edge_op.cpp
20997 views
1
// Copyright 2021 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
#include <unordered_map>
16
17
#include "impl.h"
18
#include "parallel.h"
19
#include "shared.h"
20
21
namespace {
22
using namespace manifold;
23
24
ivec3 TriOf(int edge) {
25
ivec3 triEdge;
26
triEdge[0] = edge;
27
triEdge[1] = NextHalfedge(triEdge[0]);
28
triEdge[2] = NextHalfedge(triEdge[1]);
29
return triEdge;
30
}
31
32
bool Is01Longest(vec2 v0, vec2 v1, vec2 v2) {
33
const vec2 e[3] = {v1 - v0, v2 - v1, v0 - v2};
34
double l[3];
35
for (int i : {0, 1, 2}) l[i] = la::dot(e[i], e[i]);
36
return l[0] > l[1] && l[0] > l[2];
37
}
38
39
struct DuplicateEdge {
40
const Halfedge* sortedHalfedge;
41
42
bool operator()(int edge) {
43
const Halfedge& halfedge = sortedHalfedge[edge];
44
const Halfedge& nextHalfedge = sortedHalfedge[edge + 1];
45
return halfedge.startVert == nextHalfedge.startVert &&
46
halfedge.endVert == nextHalfedge.endVert;
47
}
48
};
49
50
struct ShortEdge {
51
VecView<const Halfedge> halfedge;
52
VecView<const vec3> vertPos;
53
const double epsilon;
54
const int firstNewVert;
55
56
bool operator()(int edge) const {
57
const Halfedge& half = halfedge[edge];
58
if (half.pairedHalfedge < 0 ||
59
(half.startVert < firstNewVert && half.endVert < firstNewVert))
60
return false;
61
// Flag short edges
62
const vec3 delta = vertPos[half.endVert] - vertPos[half.startVert];
63
return la::dot(delta, delta) < epsilon * epsilon;
64
}
65
};
66
67
struct FlagEdge {
68
VecView<const Halfedge> halfedge;
69
VecView<const TriRef> triRef;
70
const int firstNewVert;
71
72
bool operator()(int edge) const {
73
const Halfedge& half = halfedge[edge];
74
if (half.pairedHalfedge < 0 || half.startVert < firstNewVert) return false;
75
// Flag redundant edges - those where the startVert is surrounded by only
76
// two original triangles.
77
const TriRef ref0 = triRef[edge / 3];
78
int current = NextHalfedge(half.pairedHalfedge);
79
TriRef ref1 = triRef[current / 3];
80
bool ref1Updated = !ref0.SameFace(ref1);
81
while (current != edge) {
82
current = NextHalfedge(halfedge[current].pairedHalfedge);
83
int tri = current / 3;
84
const TriRef ref = triRef[tri];
85
if (!ref.SameFace(ref0) && !ref.SameFace(ref1)) {
86
if (!ref1Updated) {
87
ref1 = ref;
88
ref1Updated = true;
89
} else {
90
return false;
91
}
92
}
93
}
94
return true;
95
}
96
};
97
98
struct SwappableEdge {
99
VecView<const Halfedge> halfedge;
100
VecView<const vec3> vertPos;
101
VecView<const vec3> triNormal;
102
const double tolerance;
103
const int firstNewVert;
104
105
bool operator()(int edge) const {
106
const Halfedge& half = halfedge[edge];
107
if (half.pairedHalfedge < 0) return false;
108
if (half.startVert < firstNewVert && half.endVert < firstNewVert &&
109
halfedge[NextHalfedge(edge)].endVert < firstNewVert &&
110
halfedge[NextHalfedge(half.pairedHalfedge)].endVert < firstNewVert)
111
return false;
112
113
int tri = edge / 3;
114
ivec3 triEdge = TriOf(edge);
115
mat2x3 projection = GetAxisAlignedProjection(triNormal[tri]);
116
vec2 v[3];
117
for (int i : {0, 1, 2})
118
v[i] = projection * vertPos[halfedge[triEdge[i]].startVert];
119
if (CCW(v[0], v[1], v[2], tolerance) > 0 || !Is01Longest(v[0], v[1], v[2]))
120
return false;
121
122
// Switch to neighbor's projection.
123
edge = half.pairedHalfedge;
124
tri = edge / 3;
125
triEdge = TriOf(edge);
126
projection = GetAxisAlignedProjection(triNormal[tri]);
127
for (int i : {0, 1, 2})
128
v[i] = projection * vertPos[halfedge[triEdge[i]].startVert];
129
return CCW(v[0], v[1], v[2], tolerance) > 0 ||
130
Is01Longest(v[0], v[1], v[2]);
131
}
132
};
133
134
struct FlagStore {
135
#if MANIFOLD_PAR == 1
136
tbb::combinable<std::vector<size_t>> store;
137
#endif
138
std::vector<size_t> s;
139
140
template <typename Pred, typename F>
141
void run_seq(size_t n, Pred pred, F f) {
142
for (size_t i = 0; i < n; ++i)
143
if (pred(i)) s.push_back(i);
144
for (size_t i : s) f(i);
145
s.clear();
146
}
147
148
#if MANIFOLD_PAR == 1
149
template <typename Pred, typename F>
150
void run_par(size_t n, Pred pred, F f) {
151
// Test pred in parallel, store i into thread-local vectors when pred(i) is
152
// true. After testing pred, iterate and call f over the indices in
153
// ascending order by using a heap in a single thread
154
auto& store = this->store;
155
tbb::parallel_for(tbb::blocked_range<size_t>(0, n),
156
[&store, &pred](const auto& r) {
157
auto& local = store.local();
158
for (auto i = r.begin(); i < r.end(); ++i) {
159
if (pred(i)) local.push_back(i);
160
}
161
});
162
163
std::vector<std::vector<size_t>> stores;
164
std::vector<size_t> result;
165
store.combine_each(
166
[&](auto& data) { stores.emplace_back(std::move(data)); });
167
std::vector<size_t> sizes;
168
size_t total_size = 0;
169
for (const auto& tmp : stores) {
170
sizes.push_back(total_size);
171
total_size += tmp.size();
172
}
173
result.resize(total_size);
174
for_each_n(ExecutionPolicy::Seq, countAt(0), stores.size(), [&](size_t i) {
175
std::copy(stores[i].begin(), stores[i].end(), result.begin() + sizes[i]);
176
});
177
stable_sort(autoPolicy(result.size()), result.begin(), result.end());
178
for (size_t x : result) f(x);
179
}
180
#endif
181
182
template <typename Pred, typename F>
183
void run(size_t n, Pred pred, F f) {
184
#if MANIFOLD_PAR == 1
185
if (n > 1e5) {
186
run_par(n, pred, f);
187
} else
188
#endif
189
{
190
run_seq(n, pred, f);
191
}
192
}
193
};
194
195
} // namespace
196
197
namespace manifold {
198
199
/**
200
* Duplicates just enough verts to covert an even-manifold to a proper
201
* 2-manifold, splitting non-manifold verts and edges with too many triangles.
202
*/
203
void Manifold::Impl::CleanupTopology() {
204
if (!halfedge_.size()) return;
205
DEBUG_ASSERT(IsManifold(), logicErr, "polygon mesh is not manifold!");
206
207
// In the case of a very bad triangulation, it is possible to create pinched
208
// verts. They must be removed before edge collapse.
209
SplitPinchedVerts();
210
DedupeEdges();
211
}
212
213
/**
214
* Collapses degenerate triangles by removing edges shorter than tolerance_ and
215
* any edge that is preceeded by an edge that joins the same two face relations.
216
* It also performs edge swaps on the long edges of degenerate triangles, though
217
* there are some configurations of degenerates that cannot be removed this way.
218
*
219
* Before collapsing edges, the mesh is checked for duplicate edges (more than
220
* one pair of triangles sharing the same edge), which are removed by
221
* duplicating one vert and adding two triangles. These degenerate triangles are
222
* likely to be collapsed again in the subsequent simplification.
223
*
224
* Note when an edge collapse would result in something non-manifold, the
225
* vertices are duplicated in such a way as to remove handles or separate
226
* meshes, thus decreasing the Genus(). It only increases when meshes that have
227
* collapsed to just a pair of triangles are removed entirely.
228
*
229
* Verts with index less than firstNewVert will be left uncollapsed. This is
230
* zero by default so that everything can be collapsed.
231
*
232
* Rather than actually removing the edges, this step merely marks them for
233
* removal, by setting vertPos to NaN and halfedge to {-1, -1, -1, -1}.
234
*/
235
void Manifold::Impl::SimplifyTopology(int firstNewVert) {
236
if (!halfedge_.size()) return;
237
238
CleanupTopology();
239
CollapseShortEdges(firstNewVert);
240
CollapseColinearEdges(firstNewVert);
241
SwapDegenerates(firstNewVert);
242
}
243
244
void Manifold::Impl::RemoveDegenerates(int firstNewVert) {
245
if (!halfedge_.size()) return;
246
247
CleanupTopology();
248
CollapseShortEdges(firstNewVert);
249
SwapDegenerates(firstNewVert);
250
}
251
252
void Manifold::Impl::CollapseShortEdges(int firstNewVert) {
253
ZoneScopedN("CollapseShortEdge");
254
FlagStore s;
255
size_t numFlagged = 0;
256
const size_t nbEdges = halfedge_.size();
257
258
std::vector<int> scratchBuffer;
259
scratchBuffer.reserve(10);
260
// Short edges get to skip several checks and hence remove more classes of
261
// degenerate triangles than flagged edges do, but this could in theory lead
262
// to error stacking where a vertex moves too far. For this reason this is
263
// restricted to epsilon, rather than tolerance.
264
ShortEdge se{halfedge_, vertPos_, epsilon_, firstNewVert};
265
s.run(nbEdges, se, [&](size_t i) {
266
const bool didCollapse = CollapseEdge(i, scratchBuffer);
267
if (didCollapse) numFlagged++;
268
scratchBuffer.resize(0);
269
});
270
271
#ifdef MANIFOLD_DEBUG
272
if (ManifoldParams().verbose > 0 && numFlagged > 0) {
273
std::cout << "collapsed " << numFlagged << " short edges" << std::endl;
274
}
275
#endif
276
}
277
278
void Manifold::Impl::CollapseColinearEdges(int firstNewVert) {
279
FlagStore s;
280
size_t numFlagged = 0;
281
const size_t nbEdges = halfedge_.size();
282
std::vector<int> scratchBuffer;
283
scratchBuffer.reserve(10);
284
while (1) {
285
ZoneScopedN("CollapseFlaggedEdge");
286
numFlagged = 0;
287
// Collapse colinear edges, but only remove new verts, i.e. verts with
288
// index
289
// >= firstNewVert. This is used to keep the Boolean from changing the
290
// non-intersecting parts of the input meshes. Colinear is defined not by a
291
// local check, but by the global MarkCoplanar function, which keeps this
292
// from being vulnerable to error stacking.
293
FlagEdge se{halfedge_, meshRelation_.triRef, firstNewVert};
294
s.run(nbEdges, se, [&](size_t i) {
295
const bool didCollapse = CollapseEdge(i, scratchBuffer);
296
if (didCollapse) numFlagged++;
297
scratchBuffer.resize(0);
298
});
299
if (numFlagged == 0) break;
300
301
#ifdef MANIFOLD_DEBUG
302
if (ManifoldParams().verbose > 0 && numFlagged > 0) {
303
std::cout << "collapsed " << numFlagged << " colinear edges" << std::endl;
304
}
305
#endif
306
}
307
}
308
309
void Manifold::Impl::SwapDegenerates(int firstNewVert) {
310
ZoneScopedN("RecursiveEdgeSwap");
311
FlagStore s;
312
size_t numFlagged = 0;
313
const size_t nbEdges = halfedge_.size();
314
std::vector<int> scratchBuffer;
315
scratchBuffer.reserve(10);
316
317
SwappableEdge se{halfedge_, vertPos_, faceNormal_, tolerance_, firstNewVert};
318
std::vector<int> edgeSwapStack;
319
std::vector<int> visited(halfedge_.size(), -1);
320
int tag = 0;
321
s.run(nbEdges, se, [&](size_t i) {
322
numFlagged++;
323
tag++;
324
RecursiveEdgeSwap(i, tag, visited, edgeSwapStack, scratchBuffer);
325
while (!edgeSwapStack.empty()) {
326
int last = edgeSwapStack.back();
327
edgeSwapStack.pop_back();
328
RecursiveEdgeSwap(last, tag, visited, edgeSwapStack, scratchBuffer);
329
}
330
});
331
332
#ifdef MANIFOLD_DEBUG
333
if (ManifoldParams().verbose > 0 && numFlagged > 0) {
334
std::cout << "swapped " << numFlagged << " edges" << std::endl;
335
}
336
#endif
337
}
338
339
// Deduplicate the given 4-manifold edge by duplicating endVert, thus making the
340
// edges distinct. Also duplicates startVert if it becomes pinched.
341
void Manifold::Impl::DedupeEdge(const int edge) {
342
// Orbit endVert
343
const int startVert = halfedge_[edge].startVert;
344
const int endVert = halfedge_[edge].endVert;
345
const int endProp = halfedge_[NextHalfedge(edge)].propVert;
346
int current = halfedge_[NextHalfedge(edge)].pairedHalfedge;
347
while (current != edge) {
348
const int vert = halfedge_[current].startVert;
349
if (vert == startVert) {
350
// Single topological unit needs 2 faces added to be split
351
const int newVert = vertPos_.size();
352
vertPos_.push_back(vertPos_[endVert]);
353
if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);
354
current = halfedge_[NextHalfedge(current)].pairedHalfedge;
355
const int opposite = halfedge_[NextHalfedge(edge)].pairedHalfedge;
356
357
UpdateVert(newVert, current, opposite);
358
359
int newHalfedge = halfedge_.size();
360
int oldFace = current / 3;
361
int outsideVert = halfedge_[current].startVert;
362
halfedge_.push_back({endVert, newVert, -1, endProp});
363
halfedge_.push_back({newVert, outsideVert, -1, endProp});
364
halfedge_.push_back(
365
{outsideVert, endVert, -1, halfedge_[current].propVert});
366
PairUp(newHalfedge + 2, halfedge_[current].pairedHalfedge);
367
PairUp(newHalfedge + 1, current);
368
if (meshRelation_.triRef.size() > 0)
369
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);
370
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
371
372
newHalfedge += 3;
373
oldFace = opposite / 3;
374
outsideVert = halfedge_[opposite].startVert;
375
halfedge_.push_back({newVert, endVert, -1, endProp}); // fix prop
376
halfedge_.push_back({endVert, outsideVert, -1, endProp});
377
halfedge_.push_back(
378
{outsideVert, newVert, -1, halfedge_[opposite].propVert});
379
PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge);
380
PairUp(newHalfedge + 1, opposite);
381
PairUp(newHalfedge, newHalfedge - 3);
382
if (meshRelation_.triRef.size() > 0)
383
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);
384
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
385
386
break;
387
}
388
389
current = halfedge_[NextHalfedge(current)].pairedHalfedge;
390
}
391
392
if (current == edge) {
393
// Separate topological unit needs no new faces to be split
394
const int newVert = vertPos_.size();
395
vertPos_.push_back(vertPos_[endVert]);
396
if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);
397
398
ForVert(NextHalfedge(current), [this, newVert](int e) {
399
halfedge_[e].startVert = newVert;
400
halfedge_[halfedge_[e].pairedHalfedge].endVert = newVert;
401
});
402
}
403
404
// Orbit startVert
405
const int pair = halfedge_[edge].pairedHalfedge;
406
current = halfedge_[NextHalfedge(pair)].pairedHalfedge;
407
while (current != pair) {
408
const int vert = halfedge_[current].startVert;
409
if (vert == endVert) {
410
break; // Connected: not a pinched vert
411
}
412
current = halfedge_[NextHalfedge(current)].pairedHalfedge;
413
}
414
415
if (current == pair) {
416
// Split the pinched vert the previous split created.
417
const int newVert = vertPos_.size();
418
vertPos_.push_back(vertPos_[endVert]);
419
if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);
420
421
ForVert(NextHalfedge(current), [this, newVert](int e) {
422
halfedge_[e].startVert = newVert;
423
halfedge_[halfedge_[e].pairedHalfedge].endVert = newVert;
424
});
425
}
426
}
427
428
void Manifold::Impl::PairUp(int edge0, int edge1) {
429
halfedge_[edge0].pairedHalfedge = edge1;
430
halfedge_[edge1].pairedHalfedge = edge0;
431
}
432
433
// Traverses CW around startEdge.endVert from startEdge to endEdge
434
// (edgeEdge.endVert must == startEdge.endVert), updating each edge to point
435
// to vert instead.
436
void Manifold::Impl::UpdateVert(int vert, int startEdge, int endEdge) {
437
int current = startEdge;
438
while (current != endEdge) {
439
halfedge_[current].endVert = vert;
440
current = NextHalfedge(current);
441
halfedge_[current].startVert = vert;
442
current = halfedge_[current].pairedHalfedge;
443
DEBUG_ASSERT(current != startEdge, logicErr, "infinite loop in decimator!");
444
}
445
}
446
447
// In the event that the edge collapse would create a non-manifold edge,
448
// instead we duplicate the two verts and attach the manifolds the other way
449
// across this edge.
450
void Manifold::Impl::FormLoop(int current, int end) {
451
int startVert = vertPos_.size();
452
vertPos_.push_back(vertPos_[halfedge_[current].startVert]);
453
int endVert = vertPos_.size();
454
vertPos_.push_back(vertPos_[halfedge_[current].endVert]);
455
456
int oldMatch = halfedge_[current].pairedHalfedge;
457
int newMatch = halfedge_[end].pairedHalfedge;
458
459
UpdateVert(startVert, oldMatch, newMatch);
460
UpdateVert(endVert, end, current);
461
462
halfedge_[current].pairedHalfedge = newMatch;
463
halfedge_[newMatch].pairedHalfedge = current;
464
halfedge_[end].pairedHalfedge = oldMatch;
465
halfedge_[oldMatch].pairedHalfedge = end;
466
467
RemoveIfFolded(end);
468
}
469
470
void Manifold::Impl::CollapseTri(const ivec3& triEdge) {
471
if (halfedge_[triEdge[1]].pairedHalfedge == -1) return;
472
int pair1 = halfedge_[triEdge[1]].pairedHalfedge;
473
int pair2 = halfedge_[triEdge[2]].pairedHalfedge;
474
halfedge_[pair1].pairedHalfedge = pair2;
475
halfedge_[pair2].pairedHalfedge = pair1;
476
for (int i : {0, 1, 2}) {
477
halfedge_[triEdge[i]] = {-1, -1, -1, halfedge_[triEdge[i]].propVert};
478
}
479
}
480
481
void Manifold::Impl::RemoveIfFolded(int edge) {
482
const ivec3 tri0edge = TriOf(edge);
483
const ivec3 tri1edge = TriOf(halfedge_[edge].pairedHalfedge);
484
if (halfedge_[tri0edge[1]].pairedHalfedge == -1) return;
485
if (halfedge_[tri0edge[1]].endVert == halfedge_[tri1edge[1]].endVert) {
486
if (halfedge_[tri0edge[1]].pairedHalfedge == tri1edge[2]) {
487
if (halfedge_[tri0edge[2]].pairedHalfedge == tri1edge[1]) {
488
for (int i : {0, 1, 2})
489
vertPos_[halfedge_[tri0edge[i]].startVert] = vec3(NAN);
490
} else {
491
vertPos_[halfedge_[tri0edge[1]].startVert] = vec3(NAN);
492
}
493
} else {
494
if (halfedge_[tri0edge[2]].pairedHalfedge == tri1edge[1]) {
495
vertPos_[halfedge_[tri1edge[1]].startVert] = vec3(NAN);
496
}
497
}
498
PairUp(halfedge_[tri0edge[1]].pairedHalfedge,
499
halfedge_[tri1edge[2]].pairedHalfedge);
500
PairUp(halfedge_[tri0edge[2]].pairedHalfedge,
501
halfedge_[tri1edge[1]].pairedHalfedge);
502
for (int i : {0, 1, 2}) {
503
halfedge_[tri0edge[i]] = {-1, -1, -1};
504
halfedge_[tri1edge[i]] = {-1, -1, -1};
505
}
506
}
507
}
508
509
// Collapses the given edge by removing startVert - returns false if the edge
510
// cannot be collapsed. May split the mesh topologically if the collapse would
511
// have resulted in a 4-manifold edge. Do not collapse an edge if startVert is
512
// pinched - the vert would be marked NaN, but other edges could still be
513
// pointing to it.
514
bool Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {
515
Vec<TriRef>& triRef = meshRelation_.triRef;
516
517
const Halfedge toRemove = halfedge_[edge];
518
if (toRemove.pairedHalfedge < 0) return false;
519
520
const int endVert = toRemove.endVert;
521
const ivec3 tri0edge = TriOf(edge);
522
const ivec3 tri1edge = TriOf(toRemove.pairedHalfedge);
523
524
const vec3 pNew = vertPos_[endVert];
525
const vec3 pOld = vertPos_[toRemove.startVert];
526
const vec3 delta = pNew - pOld;
527
const bool shortEdge = la::dot(delta, delta) < epsilon_ * epsilon_;
528
529
// Orbit startVert
530
int start = halfedge_[tri1edge[1]].pairedHalfedge;
531
int current = tri1edge[2];
532
if (!shortEdge) {
533
current = start;
534
TriRef refCheck = triRef[toRemove.pairedHalfedge / 3];
535
vec3 pLast = vertPos_[halfedge_[tri1edge[1]].endVert];
536
while (current != tri1edge[0]) {
537
current = NextHalfedge(current);
538
vec3 pNext = vertPos_[halfedge_[current].endVert];
539
const int tri = current / 3;
540
const TriRef ref = triRef[tri];
541
const mat2x3 projection = GetAxisAlignedProjection(faceNormal_[tri]);
542
// Don't collapse if the edge is not redundant (this may have changed due
543
// to the collapse of neighbors).
544
if (!ref.SameFace(refCheck)) {
545
const TriRef oldRef = refCheck;
546
refCheck = triRef[edge / 3];
547
if (!ref.SameFace(refCheck)) {
548
return false;
549
}
550
if (ref.meshID != oldRef.meshID || ref.faceID != oldRef.faceID ||
551
la::dot(faceNormal_[toRemove.pairedHalfedge / 3],
552
faceNormal_[tri]) < -0.5) {
553
// Restrict collapse to colinear edges when the edge separates faces
554
// or the edge is sharp. This ensures large shifts are not introduced
555
// parallel to the tangent plane.
556
if (CCW(projection * pLast, projection * pOld, projection * pNew,
557
epsilon_) != 0)
558
return false;
559
}
560
}
561
562
// Don't collapse edge if it would cause a triangle to invert.
563
if (CCW(projection * pNext, projection * pLast, projection * pNew,
564
epsilon_) < 0)
565
return false;
566
567
pLast = pNext;
568
current = halfedge_[current].pairedHalfedge;
569
}
570
}
571
572
// Orbit endVert
573
{
574
int current = halfedge_[tri0edge[1]].pairedHalfedge;
575
while (current != tri1edge[2]) {
576
current = NextHalfedge(current);
577
edges.push_back(current);
578
current = halfedge_[current].pairedHalfedge;
579
}
580
}
581
582
// Remove toRemove.startVert and replace with endVert.
583
vertPos_[toRemove.startVert] = vec3(NAN);
584
CollapseTri(tri1edge);
585
586
// Orbit startVert
587
const int tri0 = edge / 3;
588
const int tri1 = toRemove.pairedHalfedge / 3;
589
current = start;
590
while (current != tri0edge[2]) {
591
current = NextHalfedge(current);
592
593
if (NumProp() > 0) {
594
// Update the shifted triangles to the vertBary of endVert
595
const int tri = current / 3;
596
if (triRef[tri].SameFace(triRef[tri0])) {
597
halfedge_[current].propVert = halfedge_[NextHalfedge(edge)].propVert;
598
} else if (triRef[tri].SameFace(triRef[tri1])) {
599
halfedge_[current].propVert =
600
halfedge_[toRemove.pairedHalfedge].propVert;
601
}
602
}
603
604
const int vert = halfedge_[current].endVert;
605
const int next = halfedge_[current].pairedHalfedge;
606
for (size_t i = 0; i < edges.size(); ++i) {
607
if (vert == halfedge_[edges[i]].endVert) {
608
FormLoop(edges[i], current);
609
start = next;
610
edges.resize(i);
611
break;
612
}
613
}
614
current = next;
615
}
616
617
UpdateVert(endVert, start, tri0edge[2]);
618
CollapseTri(tri0edge);
619
RemoveIfFolded(start);
620
return true;
621
}
622
623
void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag,
624
std::vector<int>& visited,
625
std::vector<int>& edgeSwapStack,
626
std::vector<int>& edges) {
627
Vec<TriRef>& triRef = meshRelation_.triRef;
628
629
if (edge < 0) return;
630
const int pair = halfedge_[edge].pairedHalfedge;
631
if (pair < 0) return;
632
633
// avoid infinite recursion
634
if (visited[edge] == tag && visited[pair] == tag) return;
635
636
const ivec3 tri0edge = TriOf(edge);
637
const ivec3 tri1edge = TriOf(pair);
638
639
mat2x3 projection = GetAxisAlignedProjection(faceNormal_[edge / 3]);
640
vec2 v[4];
641
for (int i : {0, 1, 2})
642
v[i] = projection * vertPos_[halfedge_[tri0edge[i]].startVert];
643
// Only operate on the long edge of a degenerate triangle.
644
if (CCW(v[0], v[1], v[2], tolerance_) > 0 || !Is01Longest(v[0], v[1], v[2]))
645
return;
646
647
// Switch to neighbor's projection.
648
projection = GetAxisAlignedProjection(faceNormal_[pair / 3]);
649
for (int i : {0, 1, 2})
650
v[i] = projection * vertPos_[halfedge_[tri0edge[i]].startVert];
651
v[3] = projection * vertPos_[halfedge_[tri1edge[2]].startVert];
652
653
auto SwapEdge = [&]() {
654
// The 0-verts are swapped to the opposite 2-verts.
655
const int v0 = halfedge_[tri0edge[2]].startVert;
656
const int v1 = halfedge_[tri1edge[2]].startVert;
657
halfedge_[tri0edge[0]].startVert = v1;
658
halfedge_[tri0edge[2]].endVert = v1;
659
halfedge_[tri1edge[0]].startVert = v0;
660
halfedge_[tri1edge[2]].endVert = v0;
661
PairUp(tri0edge[0], halfedge_[tri1edge[2]].pairedHalfedge);
662
PairUp(tri1edge[0], halfedge_[tri0edge[2]].pairedHalfedge);
663
PairUp(tri0edge[2], tri1edge[2]);
664
// Both triangles are now subsets of the neighboring triangle.
665
const int tri0 = tri0edge[0] / 3;
666
const int tri1 = tri1edge[0] / 3;
667
faceNormal_[tri0] = faceNormal_[tri1];
668
triRef[tri0] = triRef[tri1];
669
const double l01 = la::length(v[1] - v[0]);
670
const double l02 = la::length(v[2] - v[0]);
671
const double a = std::max(0.0, std::min(1.0, l02 / l01));
672
// Update properties if applicable
673
if (properties_.size() > 0) {
674
Vec<double>& prop = properties_;
675
halfedge_[tri0edge[1]].propVert = halfedge_[tri1edge[0]].propVert;
676
halfedge_[tri0edge[0]].propVert = halfedge_[tri1edge[2]].propVert;
677
halfedge_[tri0edge[2]].propVert = halfedge_[tri1edge[2]].propVert;
678
const int numProp = NumProp();
679
const int newProp = prop.size() / numProp;
680
const int propIdx0 = halfedge_[tri1edge[0]].propVert;
681
const int propIdx1 = halfedge_[tri1edge[1]].propVert;
682
for (int p = 0; p < numProp; ++p) {
683
prop.push_back(a * prop[numProp * propIdx0 + p] +
684
(1 - a) * prop[numProp * propIdx1 + p]);
685
}
686
halfedge_[tri1edge[0]].propVert = newProp;
687
halfedge_[tri0edge[2]].propVert = newProp;
688
}
689
690
// if the new edge already exists, duplicate the verts and split the mesh.
691
int current = halfedge_[tri1edge[0]].pairedHalfedge;
692
const int endVert = halfedge_[tri1edge[1]].endVert;
693
while (current != tri0edge[1]) {
694
current = NextHalfedge(current);
695
if (halfedge_[current].endVert == endVert) {
696
FormLoop(tri0edge[2], current);
697
RemoveIfFolded(tri0edge[2]);
698
return;
699
}
700
current = halfedge_[current].pairedHalfedge;
701
}
702
};
703
704
// Only operate if the other triangles are not degenerate.
705
if (CCW(v[1], v[0], v[3], tolerance_) <= 0) {
706
if (!Is01Longest(v[1], v[0], v[3])) return;
707
// Two facing, long-edge degenerates can swap.
708
SwapEdge();
709
const vec2 e23 = v[3] - v[2];
710
if (la::dot(e23, e23) < tolerance_ * tolerance_) {
711
tag++;
712
CollapseEdge(tri0edge[2], edges);
713
edges.resize(0);
714
} else {
715
visited[edge] = tag;
716
visited[pair] = tag;
717
edgeSwapStack.insert(edgeSwapStack.end(), {tri1edge[1], tri1edge[0],
718
tri0edge[1], tri0edge[0]});
719
}
720
return;
721
} else if (CCW(v[0], v[3], v[2], tolerance_) <= 0 ||
722
CCW(v[1], v[2], v[3], tolerance_) <= 0) {
723
return;
724
}
725
// Normal path
726
SwapEdge();
727
visited[edge] = tag;
728
visited[pair] = tag;
729
edgeSwapStack.insert(edgeSwapStack.end(),
730
{halfedge_[tri1edge[0]].pairedHalfedge,
731
halfedge_[tri0edge[1]].pairedHalfedge});
732
}
733
734
void Manifold::Impl::SplitPinchedVerts() {
735
ZoneScoped;
736
737
auto nbEdges = halfedge_.size();
738
#if MANIFOLD_PAR == 1
739
if (nbEdges > 1e4) {
740
std::mutex mutex;
741
std::vector<size_t> pinched;
742
// This parallelized version is non-trivial so we can't reuse the code
743
//
744
// The idea here is to identify cycles of halfedges that can be iterated
745
// through using ForVert. Pinched verts are vertices where there are
746
// multiple cycles associated with the vertex. Each cycle is identified with
747
// the largest halfedge index within the cycle, and when there are multiple
748
// cycles associated with the same starting vertex but with different ids,
749
// it means we have a pinched vertex. This check is done by using a single
750
// atomic cas operation, the expected case is either invalid id (the vertex
751
// was not processed) or with the same id.
752
//
753
// The local store is to store the processed halfedges, so to avoid
754
// repetitive processing. Note that it only approximates the processed
755
// halfedges because it is thread local. This is why we need a vector to
756
// deduplicate the probematic halfedges we found.
757
std::vector<std::atomic<size_t>> largestEdge(NumVert());
758
for_each(ExecutionPolicy::Par, countAt(0), countAt(NumVert()),
759
[&largestEdge](size_t i) {
760
largestEdge[i].store(std::numeric_limits<size_t>::max());
761
});
762
tbb::combinable<std::vector<bool>> store(
763
[nbEdges]() { return std::vector<bool>(nbEdges, false); });
764
tbb::parallel_for(
765
tbb::blocked_range<size_t>(0, nbEdges),
766
[&store, &mutex, &pinched, &largestEdge, this](const auto& r) {
767
auto& local = store.local();
768
std::vector<size_t> pinchedLocal;
769
for (auto i = r.begin(); i < r.end(); ++i) {
770
if (local[i]) continue;
771
local[i] = true;
772
const int vert = halfedge_[i].startVert;
773
if (vert == -1) continue;
774
size_t largest = i;
775
ForVert(i, [&local, &largest](int current) {
776
local[current] = true;
777
largest = std::max(largest, static_cast<size_t>(current));
778
});
779
size_t expected = std::numeric_limits<size_t>::max();
780
if (!largestEdge[vert].compare_exchange_strong(expected, largest) &&
781
expected != largest) {
782
// we know that there is another loop...
783
pinchedLocal.push_back(largest);
784
}
785
}
786
if (!pinchedLocal.empty()) {
787
std::lock_guard<std::mutex> lock(mutex);
788
pinched.insert(pinched.end(), pinchedLocal.begin(),
789
pinchedLocal.end());
790
}
791
});
792
793
manifold::stable_sort(pinched.begin(), pinched.end());
794
std::vector<bool> halfedgeProcessed(nbEdges, false);
795
for (size_t i : pinched) {
796
if (halfedgeProcessed[i]) continue;
797
vertPos_.push_back(vertPos_[halfedge_[i].startVert]);
798
const int vert = NumVert() - 1;
799
ForVert(i, [this, vert, &halfedgeProcessed](int current) {
800
halfedgeProcessed[current] = true;
801
halfedge_[current].startVert = vert;
802
halfedge_[halfedge_[current].pairedHalfedge].endVert = vert;
803
});
804
}
805
} else
806
#endif
807
{
808
std::vector<bool> vertProcessed(NumVert(), false);
809
std::vector<bool> halfedgeProcessed(nbEdges, false);
810
for (size_t i = 0; i < nbEdges; ++i) {
811
if (halfedgeProcessed[i]) continue;
812
int vert = halfedge_[i].startVert;
813
if (vert == -1) continue;
814
if (vertProcessed[vert]) {
815
vertPos_.push_back(vertPos_[vert]);
816
vert = NumVert() - 1;
817
} else {
818
vertProcessed[vert] = true;
819
}
820
ForVert(i, [this, &halfedgeProcessed, vert](int current) {
821
halfedgeProcessed[current] = true;
822
halfedge_[current].startVert = vert;
823
halfedge_[halfedge_[current].pairedHalfedge].endVert = vert;
824
});
825
}
826
}
827
}
828
829
void Manifold::Impl::DedupeEdges() {
830
while (1) {
831
ZoneScopedN("DedupeEdge");
832
833
const size_t nbEdges = halfedge_.size();
834
std::vector<size_t> duplicates;
835
auto localLoop = [&](size_t start, size_t end, std::vector<bool>& local,
836
std::vector<size_t>& results) {
837
// Iterate over all halfedges that start with the same vertex, and check
838
// for halfedges with the same ending vertex.
839
// Note: we use Vec and linear search when the number of neighbor is
840
// small because unordered_set requires allocations and is expensive.
841
// We switch to unordered_set when the number of neighbor is
842
// larger to avoid making things quadratic.
843
// We do it in two pass, the first pass to find the minimal halfedges with
844
// the target start and end verts, the second pass flag all the duplicated
845
// halfedges that are not having the minimal index as duplicates.
846
// This ensures deterministic result.
847
//
848
// The local store is to store the processed halfedges, so to avoid
849
// repetitive processing. Note that it only approximates the processed
850
// halfedges because it is thread local.
851
Vec<std::pair<int, int>> endVerts;
852
std::unordered_map<int, int> endVertSet;
853
for (auto i = start; i < end; ++i) {
854
if (local[i] || halfedge_[i].startVert == -1 ||
855
halfedge_[i].endVert == -1)
856
continue;
857
// we want to keep the allocation
858
endVerts.clear(false);
859
endVertSet.clear();
860
861
// first iteration, populate entries
862
// this makes sure we always report the same set of entries
863
ForVert(i, [&local, &endVerts, &endVertSet, this](int current) {
864
local[current] = true;
865
if (halfedge_[current].startVert == -1 ||
866
halfedge_[current].endVert == -1) {
867
return;
868
}
869
int endV = halfedge_[current].endVert;
870
if (endVertSet.empty()) {
871
auto iter = std::find_if(endVerts.begin(), endVerts.end(),
872
[endV](const std::pair<int, int>& pair) {
873
return pair.first == endV;
874
});
875
if (iter != endVerts.end()) {
876
iter->second = std::min(iter->second, current);
877
} else {
878
endVerts.push_back({endV, current});
879
if (endVerts.size() > 32) {
880
endVertSet.insert(endVerts.begin(), endVerts.end());
881
endVerts.clear(false);
882
}
883
}
884
} else {
885
auto pair = endVertSet.insert({endV, current});
886
if (!pair.second)
887
pair.first->second = std::min(pair.first->second, current);
888
}
889
});
890
// second iteration, actually check for duplicates
891
// we always report the same set of duplicates, excluding the smallest
892
// halfedge in the set of duplicates
893
ForVert(i, [&endVerts, &endVertSet, &results, this](int current) {
894
if (halfedge_[current].startVert == -1 ||
895
halfedge_[current].endVert == -1) {
896
return;
897
}
898
int endV = halfedge_[current].endVert;
899
if (endVertSet.empty()) {
900
auto iter = std::find_if(endVerts.begin(), endVerts.end(),
901
[endV](const std::pair<int, int>& pair) {
902
return pair.first == endV;
903
});
904
if (iter->second != current) results.push_back(current);
905
} else {
906
auto iter = endVertSet.find(endV);
907
if (iter->second != current) results.push_back(current);
908
}
909
});
910
}
911
};
912
#if MANIFOLD_PAR == 1
913
if (nbEdges > 1e4) {
914
std::mutex mutex;
915
tbb::combinable<std::vector<bool>> store(
916
[nbEdges]() { return std::vector<bool>(nbEdges, false); });
917
tbb::parallel_for(
918
tbb::blocked_range<size_t>(0, nbEdges),
919
[&store, &mutex, &duplicates, &localLoop](const auto& r) {
920
auto& local = store.local();
921
std::vector<size_t> duplicatesLocal;
922
localLoop(r.begin(), r.end(), local, duplicatesLocal);
923
if (!duplicatesLocal.empty()) {
924
std::lock_guard<std::mutex> lock(mutex);
925
duplicates.insert(duplicates.end(), duplicatesLocal.begin(),
926
duplicatesLocal.end());
927
}
928
});
929
manifold::stable_sort(duplicates.begin(), duplicates.end());
930
duplicates.resize(
931
std::distance(duplicates.begin(),
932
std::unique(duplicates.begin(), duplicates.end())));
933
} else
934
#endif
935
{
936
std::vector<bool> local(nbEdges, false);
937
localLoop(0, nbEdges, local, duplicates);
938
}
939
940
size_t numFlagged = 0;
941
for (size_t i : duplicates) {
942
DedupeEdge(i);
943
numFlagged++;
944
}
945
946
if (numFlagged == 0) break;
947
948
#ifdef MANIFOLD_DEBUG
949
if (ManifoldParams().verbose > 0) {
950
std::cout << "found " << numFlagged << " duplicate edges to split"
951
<< std::endl;
952
}
953
#endif
954
}
955
}
956
} // namespace manifold
957
958