Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ lod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertic
target_index_count, target_error, /* options= */ meshopt_SimplifyPermissive, &lod_error));
```

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:
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:

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

if (r != i && (vertices[r].tx != vertices[i].tx || vertices[r].ty != vertices[i].ty))
locks[i] |= meshopt_SimplifyVertex_Protect; // protect UV seams

if (r != i && (vertices[r].nx * vertices[i].nx + vertices[r].ny * vertices[i].ny + vertices[r].nz * vertices[i].nz < 0.25f))
locks[i] |= meshopt_SimplifyVertex_Protect; // protect sharp normal creases
}
```

Expand Down
20 changes: 13 additions & 7 deletions demo/simplify.html
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@
debugOverlay: false,
sloppy: false,
permissive: false,
permissiveProtection: 3,
permissiveProtection: 2, // uvs
lockBorder: false,
preprune: 0.0,
prune: true,
Expand Down Expand Up @@ -207,7 +207,7 @@
guiSimplify.add(settings, 'ratio', 0, 1, 0.01).onChange(simplify);
guiSimplify.add(settings, 'sloppy').onChange(simplify);
guiSimplify.add(settings, 'permissive').onChange(simplify);
guiSimplify.add(settings, 'permissiveProtection', { none: 0, normals: 1, uvs: 2, all: 3 }).onChange(simplify);
guiSimplify.add(settings, 'permissiveProtection', { none: 0, uvs: 2, normals: 1, all: 3 }).onChange(simplify);
guiSimplify.add(settings, 'lockBorder').onChange(simplify);
guiSimplify.add(settings, 'prune').onChange(simplify);
guiSimplify.add(settings, 'regularize').onChange(simplify);
Expand Down Expand Up @@ -610,7 +610,7 @@

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

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

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

if ((a >> 31 != 0 || kind == 3) && (ak == kind || bk == kind) && (ak == kind || ak == 4) && (bk == kind || bk == 4)) {
// edge of current kind (allow one of the vertices to be locked)
if (a >> 31 != 0 && (ak == kind || bk == kind) && (ak == kind || ak == 4) && (bk == kind || bk == 4)) {
// loop edge of current kind (allow one of the vertices to be locked)
// note: complex edges ignore loop metadata
dind.push(a & mask);
dind.push(a & mask);
Expand All @@ -631,8 +631,13 @@
dind.push(a & mask);
dind.push(a & mask);
dind.push(b & mask);
} else if (kind == 4 && ak == kind && bk == kind) {
// locked edge (not be marked as a loop)
} else if (a >> 31 == 0 && kind == 4 && ak == kind && bk == kind) {
// locked edge (not marked as a loop)
dind.push(a & mask);
dind.push(a & mask);
dind.push(b & mask);
} else if (a >> 31 == 0 && kind == 6 && ak == 3 && bk == 3) {
// complex edge (not marked as a loop)
dind.push(a & mask);
dind.push(a & mask);
dind.push(b & mask);
Expand Down Expand Up @@ -793,6 +798,7 @@
new THREE.MeshBasicMaterial({ color: 0x009f9f, wireframe: true }), // complex
new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true }), // locked
new THREE.MeshBasicMaterial({ color: 0xff9f00, wireframe: true }), // mixed edge
new THREE.MeshBasicMaterial({ color: 0x9f5f9f, wireframe: true }), // complex, no loop
];
}

Expand Down
37 changes: 30 additions & 7 deletions src/simplifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,8 @@ static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned
memset(loopback, -1, vertex_count * sizeof(unsigned int));

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

Expand Down Expand Up @@ -1271,6 +1271,20 @@ static float getNeighborhoodRadius(const EdgeAdjacency& adjacency, const Vector3
return sqrtf(result);
}

static unsigned int getComplexTarget(unsigned int v, unsigned int target, const unsigned int* remap, const unsigned int* loop, const unsigned int* loopback)
{
unsigned int r = remap[target];

// use loop metadata to guide complex collapses towards the correct wedge
// this works for edges on attribute discontinuities because loop/loopback track the single half-edge without a pair, similar to seams
if (loop[v] != ~0u && remap[loop[v]] == r)
return loop[v];
else if (loopback[v] != ~0u && remap[loopback[v]] == r)
return loopback[v];
else
return target;
}

static size_t boundEdgeCollapses(const EdgeAdjacency& adjacency, size_t vertex_count, size_t index_count, unsigned char* vertex_kind)
{
size_t dual_count = 0;
Expand Down Expand Up @@ -1393,15 +1407,22 @@ static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const
}
else
{
// complex edges can have multiple wedges, so we need to aggregate errors for all wedges
// this is different from seams (where we aggregate pairwise) because all wedges collapse onto the same target
// complex edges can have multiple wedges, so we need to aggregate errors for all wedges based on the selected target
if (vertex_kind[i0] == Kind_Complex)
for (unsigned int v = wedge[i0]; v != i0; v = wedge[v])
ei += quadricError(attribute_quadrics[v], &attribute_gradients[v * attribute_count], attribute_count, vertex_positions[i1], &vertex_attributes[i1 * attribute_count]);
{
unsigned int t = getComplexTarget(v, i1, remap, loop, loopback);

ei += quadricError(attribute_quadrics[v], &attribute_gradients[v * attribute_count], attribute_count, vertex_positions[t], &vertex_attributes[t * attribute_count]);
}

if (vertex_kind[i1] == Kind_Complex && bidi)
for (unsigned int v = wedge[i1]; v != i1; v = wedge[v])
ej += quadricError(attribute_quadrics[v], &attribute_gradients[v * attribute_count], attribute_count, vertex_positions[i0], &vertex_attributes[i0 * attribute_count]);
{
unsigned int t = getComplexTarget(v, i0, remap, loop, loopback);

ej += quadricError(attribute_quadrics[v], &attribute_gradients[v * attribute_count], attribute_count, vertex_positions[t], &vertex_attributes[t * attribute_count]);
}
}
}

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

do
{
collapse_remap[v] = i1;
unsigned int t = getComplexTarget(v, i1, remap, loop, loopback);

collapse_remap[v] = t;
v = wedge[v];
} while (v != i0);
}
Expand Down