Skip to content

Commit eb0b0cd

Browse files
authored
Merge pull request #989 from zeux/simp-permloop
simplifier: Use parallel collapses for complex vertices based on loops
2 parents 05befa7 + 9d73305 commit eb0b0cd

File tree

3 files changed

+44
-18
lines changed

3 files changed

+44
-18
lines changed

README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ lod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertic
503503
target_index_count, target_error, /* options= */ meshopt_SimplifyPermissive, &lod_error));
504504
```
505505

506-
To maintain appearance, it's highly recommended to use this option together with attribute-aware simplification, as shown above, as it allows the simplifier to maintain attribute appearance. In this mode, it is often desirable to selectively preserve certain attribute seams, such as UV seams or sharp creases. This can be achieved by using the `vertex_lock` array with flag `meshopt_SimplifyVertex_Protect` set for individual vertices to protect specific discontinuities. To fill this array, use `meshopt_generatePositionRemap` to create a mapping table for vertices with identical positions, and then compare each vertex to the remapped vertex to determine which attributes are different:
506+
To maintain appearance, it's highly recommended to use this option together with attribute-aware simplification, as shown above, as it allows the simplifier to maintain attribute quality. In this mode, it is often desirable to selectively preserve certain attribute seams, such as UV seams or sharp creases. This can be achieved by using the `vertex_lock` array with flag `meshopt_SimplifyVertex_Protect` set for individual vertices to protect specific discontinuities. To fill this array, use `meshopt_generatePositionRemap` to create a mapping table for vertices with identical positions, and then compare each vertex to the remapped vertex to determine which attributes are different:
507507

508508
```c++
509509
std::vector<unsigned int> remap(vertices.size());
@@ -515,9 +515,6 @@ for (size_t i = 0; i < vertices.size(); ++i) {
515515

516516
if (r != i && (vertices[r].tx != vertices[i].tx || vertices[r].ty != vertices[i].ty))
517517
locks[i] |= meshopt_SimplifyVertex_Protect; // protect UV seams
518-
519-
if (r != i && (vertices[r].nx * vertices[i].nx + vertices[r].ny * vertices[i].ny + vertices[r].nz * vertices[i].nz < 0.25f))
520-
locks[i] |= meshopt_SimplifyVertex_Protect; // protect sharp normal creases
521518
}
522519
```
523520

demo/simplify.html

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@
155155
debugOverlay: false,
156156
sloppy: false,
157157
permissive: false,
158-
permissiveProtection: 3,
158+
permissiveProtection: 2, // uvs
159159
lockBorder: false,
160160
preprune: 0.0,
161161
prune: true,
@@ -207,7 +207,7 @@
207207
guiSimplify.add(settings, 'ratio', 0, 1, 0.01).onChange(simplify);
208208
guiSimplify.add(settings, 'sloppy').onChange(simplify);
209209
guiSimplify.add(settings, 'permissive').onChange(simplify);
210-
guiSimplify.add(settings, 'permissiveProtection', { none: 0, normals: 1, uvs: 2, all: 3 }).onChange(simplify);
210+
guiSimplify.add(settings, 'permissiveProtection', { none: 0, uvs: 2, normals: 1, all: 3 }).onChange(simplify);
211211
guiSimplify.add(settings, 'lockBorder').onChange(simplify);
212212
guiSimplify.add(settings, 'prune').onChange(simplify);
213213
guiSimplify.add(settings, 'regularize').onChange(simplify);
@@ -610,7 +610,7 @@
610610

611611
var mask = (1 << 28) - 1;
612612

613-
for (var kind = 1; kind <= 5; ++kind) {
613+
for (var kind = 1; kind <= 6; ++kind) {
614614
var offset = dind.length;
615615

616616
for (var i = 0; i < dlen; i += 3) {
@@ -620,8 +620,8 @@
620620
var ak = (a >> 28) & 7,
621621
bk = (b >> 28) & 7;
622622

623-
if ((a >> 31 != 0 || kind == 3) && (ak == kind || bk == kind) && (ak == kind || ak == 4) && (bk == kind || bk == 4)) {
624-
// edge of current kind (allow one of the vertices to be locked)
623+
if (a >> 31 != 0 && (ak == kind || bk == kind) && (ak == kind || ak == 4) && (bk == kind || bk == 4)) {
624+
// loop edge of current kind (allow one of the vertices to be locked)
625625
// note: complex edges ignore loop metadata
626626
dind.push(a & mask);
627627
dind.push(a & mask);
@@ -631,8 +631,13 @@
631631
dind.push(a & mask);
632632
dind.push(a & mask);
633633
dind.push(b & mask);
634-
} else if (kind == 4 && ak == kind && bk == kind) {
635-
// locked edge (not be marked as a loop)
634+
} else if (a >> 31 == 0 && kind == 4 && ak == kind && bk == kind) {
635+
// locked edge (not marked as a loop)
636+
dind.push(a & mask);
637+
dind.push(a & mask);
638+
dind.push(b & mask);
639+
} else if (a >> 31 == 0 && kind == 6 && ak == 3 && bk == 3) {
640+
// complex edge (not marked as a loop)
636641
dind.push(a & mask);
637642
dind.push(a & mask);
638643
dind.push(b & mask);
@@ -793,6 +798,7 @@
793798
new THREE.MeshBasicMaterial({ color: 0x009f9f, wireframe: true }), // complex
794799
new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true }), // locked
795800
new THREE.MeshBasicMaterial({ color: 0xff9f00, wireframe: true }), // mixed edge
801+
new THREE.MeshBasicMaterial({ color: 0x9f5f9f, wireframe: true }), // complex, no loop
796802
];
797803
}
798804

src/simplifier.cpp

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,8 @@ static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned
367367
memset(loopback, -1, vertex_count * sizeof(unsigned int));
368368

369369
// incoming & outgoing open edges: ~0u if no open edges, i if there are more than 1
370-
// note that this is the same data as required in loop[] arrays; loop[] data is only valid for border/seam
371-
// but here it's okay to fill the data out for other types of vertices as well
370+
// note that this is the same data as required in loop[] arrays; loop[] data is only used for border/seam by default
371+
// in permissive mode we also use it to guide complex-complex collapses, so we fill it for all vertices
372372
unsigned int* openinc = loopback;
373373
unsigned int* openout = loop;
374374

@@ -1271,6 +1271,20 @@ static float getNeighborhoodRadius(const EdgeAdjacency& adjacency, const Vector3
12711271
return sqrtf(result);
12721272
}
12731273

1274+
static unsigned int getComplexTarget(unsigned int v, unsigned int target, const unsigned int* remap, const unsigned int* loop, const unsigned int* loopback)
1275+
{
1276+
unsigned int r = remap[target];
1277+
1278+
// use loop metadata to guide complex collapses towards the correct wedge
1279+
// this works for edges on attribute discontinuities because loop/loopback track the single half-edge without a pair, similar to seams
1280+
if (loop[v] != ~0u && remap[loop[v]] == r)
1281+
return loop[v];
1282+
else if (loopback[v] != ~0u && remap[loopback[v]] == r)
1283+
return loopback[v];
1284+
else
1285+
return target;
1286+
}
1287+
12741288
static size_t boundEdgeCollapses(const EdgeAdjacency& adjacency, size_t vertex_count, size_t index_count, unsigned char* vertex_kind)
12751289
{
12761290
size_t dual_count = 0;
@@ -1393,15 +1407,22 @@ static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const
13931407
}
13941408
else
13951409
{
1396-
// complex edges can have multiple wedges, so we need to aggregate errors for all wedges
1397-
// this is different from seams (where we aggregate pairwise) because all wedges collapse onto the same target
1410+
// complex edges can have multiple wedges, so we need to aggregate errors for all wedges based on the selected target
13981411
if (vertex_kind[i0] == Kind_Complex)
13991412
for (unsigned int v = wedge[i0]; v != i0; v = wedge[v])
1400-
ei += quadricError(attribute_quadrics[v], &attribute_gradients[v * attribute_count], attribute_count, vertex_positions[i1], &vertex_attributes[i1 * attribute_count]);
1413+
{
1414+
unsigned int t = getComplexTarget(v, i1, remap, loop, loopback);
1415+
1416+
ei += quadricError(attribute_quadrics[v], &attribute_gradients[v * attribute_count], attribute_count, vertex_positions[t], &vertex_attributes[t * attribute_count]);
1417+
}
14011418

14021419
if (vertex_kind[i1] == Kind_Complex && bidi)
14031420
for (unsigned int v = wedge[i1]; v != i1; v = wedge[v])
1404-
ej += quadricError(attribute_quadrics[v], &attribute_gradients[v * attribute_count], attribute_count, vertex_positions[i0], &vertex_attributes[i0 * attribute_count]);
1421+
{
1422+
unsigned int t = getComplexTarget(v, i0, remap, loop, loopback);
1423+
1424+
ej += quadricError(attribute_quadrics[v], &attribute_gradients[v * attribute_count], attribute_count, vertex_positions[t], &vertex_attributes[t * attribute_count]);
1425+
}
14051426
}
14061427
}
14071428

@@ -1553,7 +1574,9 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
15531574

15541575
do
15551576
{
1556-
collapse_remap[v] = i1;
1577+
unsigned int t = getComplexTarget(v, i1, remap, loop, loopback);
1578+
1579+
collapse_remap[v] = t;
15571580
v = wedge[v];
15581581
} while (v != i0);
15591582
}

0 commit comments

Comments
 (0)