Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/manifold/src/subdivision.cpp
20897 views
1
// Copyright 2024 The Manifold Authors.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include <unordered_map>
16
17
#include "impl.h"
18
#include "parallel.h"
19
20
template <>
21
struct std::hash<manifold::ivec4> {
22
size_t operator()(const manifold::ivec4& p) const {
23
return std::hash<int>()(p.x) ^ std::hash<int>()(p.y) ^
24
std::hash<int>()(p.z) ^ std::hash<int>()(p.w);
25
}
26
};
27
28
namespace {
29
using namespace manifold;
30
31
class Partition {
32
public:
33
// The cached partitions don't have idx - it's added to the copy returned
34
// from GetPartition that contains the mapping of the input divisions into the
35
// sorted divisions that are uniquely cached.
36
ivec4 idx;
37
ivec4 sortedDivisions;
38
Vec<vec4> vertBary;
39
Vec<ivec3> triVert;
40
41
int InteriorOffset() const {
42
return sortedDivisions[0] + sortedDivisions[1] + sortedDivisions[2] +
43
sortedDivisions[3];
44
}
45
46
int NumInterior() const { return vertBary.size() - InteriorOffset(); }
47
48
static Partition GetPartition(ivec4 divisions) {
49
if (divisions[0] == 0) return Partition(); // skip wrong side of quad
50
51
ivec4 sortedDiv = divisions;
52
ivec4 triIdx = {0, 1, 2, 3};
53
if (divisions[3] == 0) { // triangle
54
if (sortedDiv[2] > sortedDiv[1]) {
55
std::swap(sortedDiv[2], sortedDiv[1]);
56
std::swap(triIdx[2], triIdx[1]);
57
}
58
if (sortedDiv[1] > sortedDiv[0]) {
59
std::swap(sortedDiv[1], sortedDiv[0]);
60
std::swap(triIdx[1], triIdx[0]);
61
if (sortedDiv[2] > sortedDiv[1]) {
62
std::swap(sortedDiv[2], sortedDiv[1]);
63
std::swap(triIdx[2], triIdx[1]);
64
}
65
}
66
} else { // quad
67
int minIdx = 0;
68
int min = divisions[minIdx];
69
int next = divisions[1];
70
for (const int i : {1, 2, 3}) {
71
const int n = divisions[(i + 1) % 4];
72
if (divisions[i] < min || (divisions[i] == min && n < next)) {
73
minIdx = i;
74
min = divisions[i];
75
next = n;
76
}
77
}
78
// Backwards (mirrored) quads get a separate cache key for now for
79
// simplicity, so there is no reversal necessary for quads when
80
// re-indexing.
81
ivec4 tmp = sortedDiv;
82
for (const int i : {0, 1, 2, 3}) {
83
triIdx[i] = (i + minIdx) % 4;
84
sortedDiv[i] = tmp[triIdx[i]];
85
}
86
}
87
88
Partition partition = GetCachedPartition(sortedDiv);
89
partition.idx = triIdx;
90
91
return partition;
92
}
93
94
Vec<ivec3> Reindex(ivec4 triVerts, ivec4 edgeOffsets, bvec4 edgeFwd,
95
int interiorOffset) const {
96
Vec<int> newVerts;
97
newVerts.reserve(vertBary.size());
98
ivec4 triIdx = idx;
99
ivec4 outTri = {0, 1, 2, 3};
100
if (triVerts[3] < 0 && idx[1] != Next3(idx[0])) {
101
triIdx = {idx[2], idx[0], idx[1], idx[3]};
102
edgeFwd = !edgeFwd;
103
std::swap(outTri[0], outTri[1]);
104
}
105
for (const int i : {0, 1, 2, 3}) {
106
if (triVerts[triIdx[i]] >= 0) newVerts.push_back(triVerts[triIdx[i]]);
107
}
108
for (const int i : {0, 1, 2, 3}) {
109
const int n = sortedDivisions[i] - 1;
110
int offset = edgeOffsets[idx[i]] + (edgeFwd[idx[i]] ? 0 : n - 1);
111
for (int j = 0; j < n; ++j) {
112
newVerts.push_back(offset);
113
offset += edgeFwd[idx[i]] ? 1 : -1;
114
}
115
}
116
const int offset = interiorOffset - newVerts.size();
117
size_t old = newVerts.size();
118
newVerts.resize_nofill(vertBary.size());
119
std::iota(newVerts.begin() + old, newVerts.end(), old + offset);
120
121
const int numTri = triVert.size();
122
Vec<ivec3> newTriVert(numTri);
123
for_each_n(autoPolicy(numTri), countAt(0), numTri,
124
[&newTriVert, &outTri, &newVerts, this](const int tri) {
125
for (const int j : {0, 1, 2}) {
126
newTriVert[tri][outTri[j]] = newVerts[triVert[tri][j]];
127
}
128
});
129
return newTriVert;
130
}
131
132
private:
133
static inline auto cacheLock = std::mutex();
134
static inline auto cache =
135
std::unordered_map<ivec4, std::unique_ptr<Partition>>();
136
137
// This triangulation is purely topological - it depends only on the number of
138
// divisions of the three sides of the triangle. This allows them to be cached
139
// and reused for similar triangles. The shape of the final surface is defined
140
// by the tangents and the barycentric coordinates of the new verts. For
141
// triangles, the input must be sorted: n[0] >= n[1] >= n[2] > 0.
142
static Partition GetCachedPartition(ivec4 n) {
143
{
144
auto lockGuard = std::lock_guard<std::mutex>(cacheLock);
145
auto cached = cache.find(n);
146
if (cached != cache.end()) {
147
return *cached->second;
148
}
149
}
150
Partition partition;
151
partition.sortedDivisions = n;
152
if (n[3] > 0) { // quad
153
partition.vertBary.push_back({1, 0, 0, 0});
154
partition.vertBary.push_back({0, 1, 0, 0});
155
partition.vertBary.push_back({0, 0, 1, 0});
156
partition.vertBary.push_back({0, 0, 0, 1});
157
ivec4 edgeOffsets;
158
edgeOffsets[0] = 4;
159
for (const int i : {0, 1, 2, 3}) {
160
if (i > 0) {
161
edgeOffsets[i] = edgeOffsets[i - 1] + n[i - 1] - 1;
162
}
163
const vec4 nextBary = partition.vertBary[(i + 1) % 4];
164
for (int j = 1; j < n[i]; ++j) {
165
partition.vertBary.push_back(
166
la::lerp(partition.vertBary[i], nextBary, (double)j / n[i]));
167
}
168
}
169
PartitionQuad(partition.triVert, partition.vertBary, {0, 1, 2, 3},
170
edgeOffsets, n - 1, {true, true, true, true});
171
} else { // tri
172
partition.vertBary.push_back({1, 0, 0, 0});
173
partition.vertBary.push_back({0, 1, 0, 0});
174
partition.vertBary.push_back({0, 0, 1, 0});
175
for (const int i : {0, 1, 2}) {
176
const vec4 nextBary = partition.vertBary[(i + 1) % 3];
177
for (int j = 1; j < n[i]; ++j) {
178
partition.vertBary.push_back(
179
la::lerp(partition.vertBary[i], nextBary, (double)j / n[i]));
180
}
181
}
182
const ivec3 edgeOffsets = {3, 3 + n[0] - 1, 3 + n[0] - 1 + n[1] - 1};
183
184
const double f = n[2] * n[2] + n[0] * n[0];
185
if (n[1] == 1) {
186
if (n[0] == 1) {
187
partition.triVert.push_back({0, 1, 2});
188
} else {
189
PartitionFan(partition.triVert, {0, 1, 2}, n[0] - 1, edgeOffsets[0]);
190
}
191
} else if (n[1] * n[1] > f - std::sqrt(2.0) * n[0] * n[2]) { // acute-ish
192
partition.triVert.push_back({edgeOffsets[1] - 1, 1, edgeOffsets[1]});
193
PartitionQuad(partition.triVert, partition.vertBary,
194
{edgeOffsets[1] - 1, edgeOffsets[1], 2, 0},
195
{-1, edgeOffsets[1] + 1, edgeOffsets[2], edgeOffsets[0]},
196
{0, n[1] - 2, n[2] - 1, n[0] - 2},
197
{true, true, true, true});
198
} else { // obtuse -> spit into two acute
199
// portion of n[0] under n[2]
200
const int ns =
201
std::min(n[0] - 2, (int)std::round((f - n[1] * n[1]) / (2 * n[0])));
202
// height from n[0]: nh <= n[2]
203
const int nh =
204
std::max(1., std::round(std::sqrt(n[2] * n[2] - ns * ns)));
205
206
const int hOffset = partition.vertBary.size();
207
const vec4 middleBary = partition.vertBary[edgeOffsets[0] + ns - 1];
208
for (int j = 1; j < nh; ++j) {
209
partition.vertBary.push_back(
210
la::lerp(partition.vertBary[2], middleBary, (double)j / nh));
211
}
212
213
partition.triVert.push_back({edgeOffsets[1] - 1, 1, edgeOffsets[1]});
214
PartitionQuad(
215
partition.triVert, partition.vertBary,
216
{edgeOffsets[1] - 1, edgeOffsets[1], 2, edgeOffsets[0] + ns - 1},
217
{-1, edgeOffsets[1] + 1, hOffset, edgeOffsets[0] + ns},
218
{0, n[1] - 2, nh - 1, n[0] - ns - 2}, {true, true, true, true});
219
220
if (n[2] == 1) {
221
PartitionFan(partition.triVert, {0, edgeOffsets[0] + ns - 1, 2},
222
ns - 1, edgeOffsets[0]);
223
} else {
224
if (ns == 1) {
225
partition.triVert.push_back({hOffset, 2, edgeOffsets[2]});
226
PartitionQuad(partition.triVert, partition.vertBary,
227
{hOffset, edgeOffsets[2], 0, edgeOffsets[0]},
228
{-1, edgeOffsets[2] + 1, -1, hOffset + nh - 2},
229
{0, n[2] - 2, ns - 1, nh - 2},
230
{true, true, true, false});
231
} else {
232
partition.triVert.push_back({hOffset - 1, 0, edgeOffsets[0]});
233
PartitionQuad(
234
partition.triVert, partition.vertBary,
235
{hOffset - 1, edgeOffsets[0], edgeOffsets[0] + ns - 1, 2},
236
{-1, edgeOffsets[0] + 1, hOffset + nh - 2, edgeOffsets[2]},
237
{0, ns - 2, nh - 1, n[2] - 2}, {true, true, false, true});
238
}
239
}
240
}
241
}
242
243
auto lockGuard = std::lock_guard<std::mutex>(cacheLock);
244
cache.insert({n, std::make_unique<Partition>(partition)});
245
return partition;
246
}
247
248
// Side 0 has added edges while sides 1 and 2 do not. Fan spreads from vert 2.
249
static void PartitionFan(Vec<ivec3>& triVert, ivec3 cornerVerts, int added,
250
int edgeOffset) {
251
int last = cornerVerts[0];
252
for (int i = 0; i < added; ++i) {
253
const int next = edgeOffset + i;
254
triVert.push_back({last, next, cornerVerts[2]});
255
last = next;
256
}
257
triVert.push_back({last, cornerVerts[1], cornerVerts[2]});
258
}
259
260
// Partitions are parallel to the first edge unless two consecutive edgeAdded
261
// are zero, in which case a terminal triangulation is performed.
262
static void PartitionQuad(Vec<ivec3>& triVert, Vec<vec4>& vertBary,
263
ivec4 cornerVerts, ivec4 edgeOffsets,
264
ivec4 edgeAdded, bvec4 edgeFwd) {
265
auto GetEdgeVert = [&](int edge, int idx) {
266
return edgeOffsets[edge] + (edgeFwd[edge] ? 1 : -1) * idx;
267
};
268
269
DEBUG_ASSERT(la::all(la::gequal(edgeAdded, ivec4(0))), logicErr,
270
"negative divisions!");
271
272
int corner = -1;
273
int last = 3;
274
int maxEdge = -1;
275
for (const int i : {0, 1, 2, 3}) {
276
if (corner == -1 && edgeAdded[i] == 0 && edgeAdded[last] == 0) {
277
corner = i;
278
}
279
if (edgeAdded[i] > 0) {
280
maxEdge = maxEdge == -1 ? i : -2;
281
}
282
last = i;
283
}
284
if (corner >= 0) { // terminate
285
if (maxEdge >= 0) {
286
ivec4 edge = (ivec4(0, 1, 2, 3) + maxEdge) % 4;
287
const int middle = edgeAdded[maxEdge] / 2;
288
triVert.push_back({cornerVerts[edge[2]], cornerVerts[edge[3]],
289
GetEdgeVert(maxEdge, middle)});
290
int last = cornerVerts[edge[0]];
291
for (int i = 0; i <= middle; ++i) {
292
const int next = GetEdgeVert(maxEdge, i);
293
triVert.push_back({cornerVerts[edge[3]], last, next});
294
last = next;
295
}
296
last = cornerVerts[edge[1]];
297
for (int i = edgeAdded[maxEdge] - 1; i >= middle; --i) {
298
const int next = GetEdgeVert(maxEdge, i);
299
triVert.push_back({cornerVerts[edge[2]], next, last});
300
last = next;
301
}
302
} else {
303
int sideVert = cornerVerts[0]; // initial value is unused
304
for (const int j : {1, 2}) {
305
const int side = (corner + j) % 4;
306
if (j == 2 && edgeAdded[side] > 0) {
307
triVert.push_back(
308
{cornerVerts[side], GetEdgeVert(side, 0), sideVert});
309
} else {
310
sideVert = cornerVerts[side];
311
}
312
for (int i = 0; i < edgeAdded[side]; ++i) {
313
const int nextVert = GetEdgeVert(side, i);
314
triVert.push_back({cornerVerts[corner], sideVert, nextVert});
315
sideVert = nextVert;
316
}
317
if (j == 2 || edgeAdded[side] == 0) {
318
triVert.push_back({cornerVerts[corner], sideVert,
319
cornerVerts[(corner + j + 1) % 4]});
320
}
321
}
322
}
323
return;
324
}
325
// recursively partition
326
const int partitions = 1 + std::min(edgeAdded[1], edgeAdded[3]);
327
ivec4 newCornerVerts = {cornerVerts[1], -1, -1, cornerVerts[0]};
328
ivec4 newEdgeOffsets = {edgeOffsets[1], -1,
329
GetEdgeVert(3, edgeAdded[3] + 1), edgeOffsets[0]};
330
ivec4 newEdgeAdded = {0, -1, 0, edgeAdded[0]};
331
bvec4 newEdgeFwd = {edgeFwd[1], true, edgeFwd[3], edgeFwd[0]};
332
333
for (int i = 1; i < partitions; ++i) {
334
const int cornerOffset1 = (edgeAdded[1] * i) / partitions;
335
const int cornerOffset3 =
336
edgeAdded[3] - 1 - (edgeAdded[3] * i) / partitions;
337
const int nextOffset1 = GetEdgeVert(1, cornerOffset1 + 1);
338
const int nextOffset3 = GetEdgeVert(3, cornerOffset3 + 1);
339
const int added = std::round(la::lerp(
340
(double)edgeAdded[0], (double)edgeAdded[2], (double)i / partitions));
341
342
newCornerVerts[1] = GetEdgeVert(1, cornerOffset1);
343
newCornerVerts[2] = GetEdgeVert(3, cornerOffset3);
344
newEdgeAdded[0] = std::abs(nextOffset1 - newEdgeOffsets[0]) - 1;
345
newEdgeAdded[1] = added;
346
newEdgeAdded[2] = std::abs(nextOffset3 - newEdgeOffsets[2]) - 1;
347
newEdgeOffsets[1] = vertBary.size();
348
newEdgeOffsets[2] = nextOffset3;
349
350
for (int j = 0; j < added; ++j) {
351
vertBary.push_back(la::lerp(vertBary[newCornerVerts[1]],
352
vertBary[newCornerVerts[2]],
353
(j + 1.0) / (added + 1.0)));
354
}
355
356
PartitionQuad(triVert, vertBary, newCornerVerts, newEdgeOffsets,
357
newEdgeAdded, newEdgeFwd);
358
359
newCornerVerts[0] = newCornerVerts[1];
360
newCornerVerts[3] = newCornerVerts[2];
361
newEdgeAdded[3] = newEdgeAdded[1];
362
newEdgeOffsets[0] = nextOffset1;
363
newEdgeOffsets[3] = newEdgeOffsets[1] + newEdgeAdded[1] - 1;
364
newEdgeFwd[3] = false;
365
}
366
367
newCornerVerts[1] = cornerVerts[2];
368
newCornerVerts[2] = cornerVerts[3];
369
newEdgeOffsets[1] = edgeOffsets[2];
370
newEdgeAdded[0] =
371
edgeAdded[1] - std::abs(newEdgeOffsets[0] - edgeOffsets[1]);
372
newEdgeAdded[1] = edgeAdded[2];
373
newEdgeAdded[2] = std::abs(newEdgeOffsets[2] - edgeOffsets[3]) - 1;
374
newEdgeOffsets[2] = edgeOffsets[3];
375
newEdgeFwd[1] = edgeFwd[2];
376
377
PartitionQuad(triVert, vertBary, newCornerVerts, newEdgeOffsets,
378
newEdgeAdded, newEdgeFwd);
379
}
380
};
381
} // namespace
382
383
namespace manifold {
384
385
/**
386
* Returns the tri side index (0-2) connected to the other side of this quad if
387
* this tri is part of a quad, or -1 otherwise.
388
*/
389
int Manifold::Impl::GetNeighbor(int tri) const {
390
int neighbor = -1;
391
for (const int i : {0, 1, 2}) {
392
if (IsMarkedInsideQuad(3 * tri + i)) {
393
neighbor = neighbor == -1 ? i : -2;
394
}
395
}
396
return neighbor;
397
}
398
399
/**
400
* For the given triangle index, returns either the three halfedge indices of
401
* that triangle and halfedges[3] = -1, or if the triangle is part of a quad, it
402
* returns those four indices. If the triangle is part of a quad and is not the
403
* lower of the two triangle indices, it returns all -1s.
404
*/
405
ivec4 Manifold::Impl::GetHalfedges(int tri) const {
406
ivec4 halfedges(-1);
407
for (const int i : {0, 1, 2}) {
408
halfedges[i] = 3 * tri + i;
409
}
410
const int neighbor = GetNeighbor(tri);
411
if (neighbor >= 0) { // quad
412
const int pair = halfedge_[3 * tri + neighbor].pairedHalfedge;
413
if (pair / 3 < tri) {
414
return ivec4(-1); // only process lower tri index
415
}
416
// The order here matters to keep small quads split the way they started, or
417
// else it can create a 4-manifold edge.
418
halfedges[2] = NextHalfedge(halfedges[neighbor]);
419
halfedges[3] = NextHalfedge(halfedges[2]);
420
halfedges[0] = NextHalfedge(pair);
421
halfedges[1] = NextHalfedge(halfedges[0]);
422
}
423
return halfedges;
424
}
425
426
/**
427
* Returns the BaryIndices, which gives the tri and indices (0-3), such that
428
* GetHalfedges(val.tri)[val.start4] points back to this halfedge, and val.end4
429
* will point to the next one. This function handles this for both triangles and
430
* quads. Returns {-1, -1, -1} if the edge is the interior of a quad.
431
*/
432
Manifold::Impl::BaryIndices Manifold::Impl::GetIndices(int halfedge) const {
433
int tri = halfedge / 3;
434
int idx = halfedge % 3;
435
const int neighbor = GetNeighbor(tri);
436
if (idx == neighbor) {
437
return {-1, -1, -1};
438
}
439
440
if (neighbor < 0) { // tri
441
return {tri, idx, Next3(idx)};
442
} else { // quad
443
const int pair = halfedge_[3 * tri + neighbor].pairedHalfedge;
444
if (pair / 3 < tri) {
445
tri = pair / 3;
446
idx = Next3(neighbor) == idx ? 0 : 1;
447
} else {
448
idx = Next3(neighbor) == idx ? 2 : 3;
449
}
450
return {tri, idx, (idx + 1) % 4};
451
}
452
}
453
454
/**
455
* Retained verts are part of several triangles, and it doesn't matter which one
456
* the vertBary refers to. Here, whichever is last will win and it's done on the
457
* CPU for simplicity for now. Using AtomicCAS on .tri should work for a GPU
458
* version if desired.
459
*/
460
void Manifold::Impl::FillRetainedVerts(Vec<Barycentric>& vertBary) const {
461
const int numTri = halfedge_.size() / 3;
462
for (int tri = 0; tri < numTri; ++tri) {
463
for (const int i : {0, 1, 2}) {
464
const BaryIndices indices = GetIndices(3 * tri + i);
465
if (indices.start4 < 0) continue; // skip quad interiors
466
vec4 uvw(0.0);
467
uvw[indices.start4] = 1;
468
vertBary[halfedge_[3 * tri + i].startVert] = {indices.tri, uvw};
469
}
470
}
471
}
472
473
/**
474
* Split each edge into n pieces as defined by calling the edgeDivisions
475
* function, and sub-triangulate each triangle accordingly. This function
476
* doesn't run Finish(), as that is expensive and it'll need to be run after
477
* the new vertices have moved, which is a likely scenario after refinement
478
* (smoothing).
479
*/
480
Vec<Barycentric> Manifold::Impl::Subdivide(
481
std::function<int(vec3, vec4, vec4)> edgeDivisions, bool keepInterior) {
482
Vec<TmpEdge> edges = CreateTmpEdges(halfedge_);
483
const int numVert = NumVert();
484
const int numEdge = edges.size();
485
const int numTri = NumTri();
486
Vec<int> half2Edge(2 * numEdge);
487
auto policy = autoPolicy(numEdge, 1e4);
488
for_each_n(policy, countAt(0), numEdge,
489
[&half2Edge, &edges, this](const int edge) {
490
const int idx = edges[edge].halfedgeIdx;
491
half2Edge[idx] = edge;
492
half2Edge[halfedge_[idx].pairedHalfedge] = edge;
493
});
494
495
Vec<ivec4> faceHalfedges(numTri);
496
for_each_n(policy, countAt(0), numTri, [&faceHalfedges, this](const int tri) {
497
faceHalfedges[tri] = GetHalfedges(tri);
498
});
499
500
Vec<int> edgeAdded(numEdge);
501
for_each_n(policy, countAt(0), numEdge,
502
[&edgeAdded, &edges, edgeDivisions, this](const int i) {
503
const TmpEdge edge = edges[i];
504
const int hIdx = edge.halfedgeIdx;
505
if (IsMarkedInsideQuad(hIdx)) {
506
edgeAdded[i] = 0;
507
return;
508
}
509
const vec3 vec = vertPos_[edge.first] - vertPos_[edge.second];
510
const vec4 tangent0 = halfedgeTangent_.empty()
511
? vec4(0.0)
512
: halfedgeTangent_[hIdx];
513
const vec4 tangent1 =
514
halfedgeTangent_.empty()
515
? vec4(0.0)
516
: halfedgeTangent_[halfedge_[hIdx].pairedHalfedge];
517
edgeAdded[i] = edgeDivisions(vec, tangent0, tangent1);
518
});
519
520
if (keepInterior) {
521
// Triangles where the greatest number of divisions exceeds the sum of the
522
// other two sides will be triangulated as a strip, since if the sub-edges
523
// were all equal length it would be degenerate. This leads to poor results
524
// with RefineToTolerance, so we avoid this case by adding some extra
525
// divisions to the short sides so that the triangulation has some thickness
526
// and creates more interior facets.
527
Vec<int> tmp(numEdge);
528
for_each_n(
529
policy, countAt(0), numEdge,
530
[&tmp, &edgeAdded, &edges, &half2Edge, this](const int i) {
531
tmp[i] = edgeAdded[i];
532
const TmpEdge edge = edges[i];
533
int hIdx = edge.halfedgeIdx;
534
if (IsMarkedInsideQuad(hIdx)) return;
535
536
const int thisAdded = tmp[i];
537
auto Added = [&edgeAdded, &half2Edge, thisAdded, this](int hIdx) {
538
int longest = 0;
539
int total = 0;
540
for (int _ : {0, 1, 2}) {
541
const int added = edgeAdded[half2Edge[hIdx]];
542
longest = la::max(longest, added);
543
total += added;
544
hIdx = NextHalfedge(hIdx);
545
if (IsMarkedInsideQuad(hIdx)) {
546
// No extra on quads
547
longest = 0;
548
total = 1;
549
break;
550
}
551
}
552
const int minExtra = longest * 0.2 + 1;
553
const int extra = 2 * longest + minExtra - total;
554
return extra > 0 ? (extra * (longest - thisAdded)) / longest : 0;
555
};
556
557
tmp[i] += la::max(Added(hIdx), Added(halfedge_[hIdx].pairedHalfedge));
558
});
559
edgeAdded.swap(tmp);
560
}
561
562
Vec<int> edgeOffset(numEdge);
563
exclusive_scan(edgeAdded.begin(), edgeAdded.end(), edgeOffset.begin(),
564
numVert);
565
566
Vec<Barycentric> vertBary(edgeOffset.back() + edgeAdded.back());
567
const int totalEdgeAdded = vertBary.size() - numVert;
568
FillRetainedVerts(vertBary);
569
for_each_n(policy, countAt(0), numEdge,
570
[&vertBary, &edges, &edgeAdded, &edgeOffset, this](const int i) {
571
const int n = edgeAdded[i];
572
const int offset = edgeOffset[i];
573
574
const BaryIndices indices = GetIndices(edges[i].halfedgeIdx);
575
if (indices.tri < 0) {
576
return; // inside quad
577
}
578
const double frac = 1.0 / (n + 1);
579
580
for (int i = 0; i < n; ++i) {
581
vec4 uvw(0.0);
582
uvw[indices.end4] = (i + 1) * frac;
583
uvw[indices.start4] = 1 - uvw[indices.end4];
584
vertBary[offset + i].uvw = uvw;
585
vertBary[offset + i].tri = indices.tri;
586
}
587
});
588
589
std::vector<Partition> subTris(numTri);
590
for_each_n(policy, countAt(0), numTri,
591
[&subTris, &half2Edge, &edgeAdded, &faceHalfedges](int tri) {
592
const ivec4 halfedges = faceHalfedges[tri];
593
ivec4 divisions(0);
594
for (const int i : {0, 1, 2, 3}) {
595
if (halfedges[i] >= 0) {
596
divisions[i] = edgeAdded[half2Edge[halfedges[i]]] + 1;
597
}
598
}
599
subTris[tri] = Partition::GetPartition(divisions);
600
});
601
602
Vec<int> triOffset(numTri);
603
auto numSubTris =
604
TransformIterator(subTris.begin(), [](const Partition& part) {
605
return static_cast<int>(part.triVert.size());
606
});
607
manifold::exclusive_scan(numSubTris, numSubTris + numTri, triOffset.begin(),
608
0);
609
610
Vec<int> interiorOffset(numTri);
611
auto numInterior =
612
TransformIterator(subTris.begin(), [](const Partition& part) {
613
return static_cast<int>(part.NumInterior());
614
});
615
manifold::exclusive_scan(numInterior, numInterior + numTri,
616
interiorOffset.begin(),
617
static_cast<int>(vertBary.size()));
618
619
Vec<ivec3> triVerts(triOffset.back() + subTris.back().triVert.size());
620
vertBary.resize(interiorOffset.back() + subTris.back().NumInterior());
621
Vec<TriRef> triRef(triVerts.size());
622
for_each_n(
623
policy, countAt(0), numTri,
624
[this, &triVerts, &triRef, &vertBary, &subTris, &edgeOffset, &half2Edge,
625
&triOffset, &interiorOffset, &faceHalfedges](int tri) {
626
const ivec4 halfedges = faceHalfedges[tri];
627
if (halfedges[0] < 0) return;
628
ivec4 tri3;
629
ivec4 edgeOffsets;
630
bvec4 edgeFwd(false);
631
for (const int i : {0, 1, 2, 3}) {
632
if (halfedges[i] < 0) {
633
tri3[i] = -1;
634
continue;
635
}
636
const Halfedge& halfedge = halfedge_[halfedges[i]];
637
tri3[i] = halfedge.startVert;
638
edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]];
639
edgeFwd[i] = halfedge.IsForward();
640
}
641
642
Vec<ivec3> newTris = subTris[tri].Reindex(tri3, edgeOffsets, edgeFwd,
643
interiorOffset[tri]);
644
copy(newTris.begin(), newTris.end(), triVerts.begin() + triOffset[tri]);
645
auto start = triRef.begin() + triOffset[tri];
646
fill(start, start + newTris.size(), meshRelation_.triRef[tri]);
647
648
const ivec4 idx = subTris[tri].idx;
649
const ivec4 vIdx = halfedges[3] >= 0 || idx[1] == Next3(idx[0])
650
? idx
651
: ivec4(idx[2], idx[0], idx[1], idx[3]);
652
ivec4 rIdx;
653
for (const int i : {0, 1, 2, 3}) {
654
rIdx[vIdx[i]] = i;
655
}
656
657
const auto& subBary = subTris[tri].vertBary;
658
transform(subBary.begin() + subTris[tri].InteriorOffset(),
659
subBary.end(), vertBary.begin() + interiorOffset[tri],
660
[tri, rIdx](vec4 bary) {
661
return Barycentric({tri,
662
{bary[rIdx[0]], bary[rIdx[1]],
663
bary[rIdx[2]], bary[rIdx[3]]}});
664
});
665
});
666
meshRelation_.triRef = triRef;
667
668
Vec<vec3> newVertPos(vertBary.size());
669
for_each_n(policy, countAt(0), vertBary.size(),
670
[&newVertPos, &vertBary, &faceHalfedges, this](const int vert) {
671
const Barycentric bary = vertBary[vert];
672
const ivec4 halfedges = faceHalfedges[bary.tri];
673
if (halfedges[3] < 0) {
674
mat3 triPos;
675
for (const int i : {0, 1, 2}) {
676
triPos[i] = vertPos_[halfedge_[halfedges[i]].startVert];
677
}
678
newVertPos[vert] = triPos * vec3(bary.uvw);
679
} else {
680
mat3x4 quadPos;
681
for (const int i : {0, 1, 2, 3}) {
682
quadPos[i] = vertPos_[halfedge_[halfedges[i]].startVert];
683
}
684
newVertPos[vert] = quadPos * bary.uvw;
685
}
686
});
687
vertPos_ = newVertPos;
688
689
faceNormal_.clear();
690
691
if (numProp_ > 0) {
692
const int numPropVert = NumPropVert();
693
const int addedVerts = NumVert() - numVert;
694
const int propOffset = numPropVert - numVert;
695
// duplicate the prop verts along all new edges even though this is
696
// unnecessary for edges that share the same prop verts. The duplicates will
697
// be removed by CompactProps.
698
Vec<double> prop(numProp_ * (numPropVert + addedVerts + totalEdgeAdded));
699
700
// copy retained prop verts
701
copy(properties_.begin(), properties_.end(), prop.begin());
702
703
// copy interior prop verts and forward edge prop verts
704
for_each_n(
705
policy, countAt(0), addedVerts,
706
[&prop, &vertBary, &faceHalfedges, numVert, numPropVert,
707
this](const int i) {
708
const int vert = numPropVert + i;
709
const Barycentric bary = vertBary[numVert + i];
710
const ivec4 halfedges = faceHalfedges[bary.tri];
711
const int numProp = NumProp();
712
713
for (int p = 0; p < numProp; ++p) {
714
if (halfedges[3] < 0) {
715
vec3 triProp;
716
for (const int i : {0, 1, 2}) {
717
triProp[i] =
718
properties_[halfedge_[3 * bary.tri + i].propVert * numProp +
719
p];
720
}
721
prop[vert * numProp + p] = la::dot(triProp, vec3(bary.uvw));
722
} else {
723
vec4 quadProp;
724
for (const int i : {0, 1, 2, 3}) {
725
quadProp[i] =
726
properties_[halfedge_[halfedges[i]].propVert * numProp + p];
727
}
728
prop[vert * numProp + p] = la::dot(quadProp, bary.uvw);
729
}
730
}
731
});
732
733
// copy backward edge prop verts, some of which will be unreferenced
734
// duplicates.
735
for_each_n(policy, countAt(0), numEdge,
736
[this, &prop, &edges, &edgeAdded, &edgeOffset, propOffset,
737
addedVerts](const int i) {
738
const int n = edgeAdded[i];
739
const int offset = edgeOffset[i] + propOffset + addedVerts;
740
const int numProp = NumProp();
741
742
const double frac = 1.0 / (n + 1);
743
const int halfedgeIdx =
744
halfedge_[edges[i].halfedgeIdx].pairedHalfedge;
745
const int prop0 = halfedge_[halfedgeIdx].propVert;
746
const int prop1 =
747
halfedge_[NextHalfedge(halfedgeIdx)].propVert;
748
for (int i = 0; i < n; ++i) {
749
for (int p = 0; p < numProp; ++p) {
750
prop[(offset + i) * numProp + p] = la::lerp(
751
properties_[prop0 * numProp + p],
752
properties_[prop1 * numProp + p], (i + 1) * frac);
753
}
754
}
755
});
756
757
Vec<ivec3> triProp(triVerts.size());
758
for_each_n(
759
policy, countAt(0), numTri,
760
[this, &triProp, &subTris, &edgeOffset, &half2Edge, &triOffset,
761
&interiorOffset, &faceHalfedges, propOffset,
762
addedVerts](const int tri) {
763
const ivec4 halfedges = faceHalfedges[tri];
764
if (halfedges[0] < 0) return;
765
766
ivec4 tri3;
767
ivec4 edgeOffsets;
768
bvec4 edgeFwd(true);
769
for (const int i : {0, 1, 2, 3}) {
770
if (halfedges[i] < 0) {
771
tri3[i] = -1;
772
continue;
773
}
774
const Halfedge& halfedge = halfedge_[halfedges[i]];
775
tri3[i] = halfedge.propVert;
776
edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]];
777
if (!halfedge.IsForward()) {
778
if (halfedge_[halfedge.pairedHalfedge].propVert !=
779
halfedge_[NextHalfedge(halfedges[i])].propVert ||
780
halfedge_[NextHalfedge(halfedge.pairedHalfedge)].propVert !=
781
halfedge.propVert) {
782
// if the edge doesn't match, point to the backward edge
783
// propverts.
784
edgeOffsets[i] += addedVerts;
785
} else {
786
edgeFwd[i] = false;
787
}
788
}
789
}
790
791
Vec<ivec3> newTris =
792
subTris[tri].Reindex(tri3, edgeOffsets + propOffset, edgeFwd,
793
interiorOffset[tri] + propOffset);
794
copy(newTris.begin(), newTris.end(),
795
triProp.begin() + triOffset[tri]);
796
});
797
798
properties_ = prop;
799
CreateHalfedges(triProp, triVerts);
800
} else {
801
CreateHalfedges(triVerts);
802
}
803
804
return vertBary;
805
}
806
807
} // namespace manifold
808
809