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