Skip to content

Commit eaaf861

Browse files
committed
simplifier: Use parallel collapses for complex vertices based on loops
Collapsing all wedges into a single wedge for complex-complex collapses is simple but results in high-error collapses alongside attribute discontinuities. This is a problem because it either results in poor attribute quality, or in poor geometric quality elsewhere as the simplifier is forced to look for lower error collapses. This change instead adjusts complex collapses to use parallel collapses if possible: wedges are collapsed individually towards different wedges along the original edges. This makes complex collapses work similarly to seam collapses if possible. To implement this without extra performance issues, we use loop metadata. Because loops are really tracking a single incoming/outgoing half-edge in the original (unwelded) topology, a typical attribute discontinuity will have X wedges (seams only have 2), and each of the wedges will have their own loop/loopback that correctly trace the adjoining triangle boundary. Thus we can use these to guide the collapses. In some complex topological situations this may not always find the correct wedge target, but fundamentally complex-complex collapses may *not* have a correct wedge target in all cases (if a discontinuity has 3 wedges then any collapse will have to collapse one of them onto a wedge that was not connected to the original). In the future we could use error or position patching to resolve these cases but that requires invasive changes with potentially problematic consequences, whereas using loop metadata should be cheap and safe. This has no effect on performance on most meshes (and no effect unless permissive mode is used); on meshes where almost every vertex is complex, this costs under 10% performance but results in much better quality. In general, after this "soft" protection like normal creases is mostly unnecessary because the simplifier will do the right thing while it's possible. Protecting UV edges may still be important to avoid UV distortion, although even without UV protection the quality can remain quite high if the UVs are weighted properly.
1 parent fa0f478 commit eaaf861

File tree

1 file changed

+30
-7
lines changed

1 file changed

+30
-7
lines changed

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)