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