Skip to content

Commit

Permalink
Merge pull request #742 from zeux/simp-seam
Browse files Browse the repository at this point in the history
simplify: Separate attribute quadrics on attribute discontinuities
  • Loading branch information
zeux authored Aug 20, 2024
2 parents c913d04 + d2367d8 commit 0a9d917
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 24 deletions.
73 changes: 73 additions & 0 deletions demo/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,78 @@ static void simplifyErrorAbsolute()
assert(fabsf(error - 0.85f) < 0.01f);
}

static void simplifySeam()
{
// xyz+attr
float vb[] = {
0, 0, 0, 0,
0, 1, 0, 0,
0, 1, 0, 1,
0, 2, 0, 1,
1, 0, 0, 0,
1, 1, 0.3f, 0,
1, 1, 0.3f, 1,
1, 2, 0, 1,
2, 0, 0, 0,
2, 1, 0.1f, 0,
2, 1, 0.1f, 1,
2, 2, 0, 1,
3, 0, 0, 0,
3, 1, 0, 0,
3, 1, 0, 1,
3, 2, 0, 1, // clang-format :-/
};

// 0 1-2 3
// 4 5-6 7
// 8 9-10 11
// 12 13-14 15

unsigned int ib[] = {
0, 1, 4,
4, 1, 5,
2, 3, 6,
6, 3, 7,
4, 5, 8,
8, 5, 9,
6, 7, 10,
10, 7, 11,
8, 9, 12,
12, 9, 13,
10, 11, 14,
14, 11, 15, // clang-format :-/
};

// note: vertices 1-2 and 13-14 are classified as locked, because they are on a seam & a border
// since seam->locked collapses are restriced, we only get to 3 triangles on each side as the seam is simplified to 3 vertices

// so we get this structure initially, and then one of the internal seam vertices is collapsed to the other one:
// 0 1-2 3
// 5-6
// 9-10
// 12 13-14 15
unsigned int expected[] = {
0, 1, 5,
2, 3, 6,
0, 5, 12,
12, 5, 13,
6, 3, 14,
14, 3, 15, // clang-format :-/
};

unsigned int res[36];
float error = 0.f;

assert(meshopt_simplify(res, ib, 36, vb, 16, 16, 18, 1.f, 0, &error) == 18);
assert(memcmp(res, expected, sizeof(expected)) == 0);
assert(fabsf(error - 0.04f) < 0.01f); // note: the error is not zero because there is a small difference in height between the seam vertices

float aw = 1;
assert(meshopt_simplifyWithAttributes(res, ib, 36, vb, 16, 16, vb + 3, 16, &aw, 1, NULL, 18, 2.f, 0, &error) == 18);
assert(memcmp(res, expected, sizeof(expected)) == 0);
assert(fabsf(error - 0.04f) < 0.01f); // note: this is the same error as above because the attribute is constant on either side of the seam
}

static void adjacency()
{
// 0 1/4
Expand Down Expand Up @@ -1556,6 +1628,7 @@ void runTests()
simplifyLockFlags();
simplifySparse();
simplifyErrorAbsolute();
simplifySeam();

adjacency();
tessellation();
Expand Down
83 changes: 59 additions & 24 deletions src/simplifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,7 @@ static void fillEdgeQuadrics(Quadric* vertex_quadrics, const unsigned int* indic
}
}

static void fillAttributeQuadrics(Quadric* attribute_quadrics, QuadricGrad* attribute_gradients, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const float* vertex_attributes, size_t attribute_count, const unsigned int* remap)
static void fillAttributeQuadrics(Quadric* attribute_quadrics, QuadricGrad* attribute_gradients, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const float* vertex_attributes, size_t attribute_count)
{
for (size_t i = 0; i < index_count; i += 3)
{
Expand All @@ -862,14 +862,13 @@ static void fillAttributeQuadrics(Quadric* attribute_quadrics, QuadricGrad* attr
QuadricGrad G[kMaxAttributes];
quadricFromAttributes(QA, G, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], &vertex_attributes[i0 * attribute_count], &vertex_attributes[i1 * attribute_count], &vertex_attributes[i2 * attribute_count], attribute_count);

// TODO: This blends together attribute weights across attribute discontinuities, which is probably not a great idea
quadricAdd(attribute_quadrics[remap[i0]], QA);
quadricAdd(attribute_quadrics[remap[i1]], QA);
quadricAdd(attribute_quadrics[remap[i2]], QA);
quadricAdd(attribute_quadrics[i0], QA);
quadricAdd(attribute_quadrics[i1], QA);
quadricAdd(attribute_quadrics[i2], QA);

quadricAdd(&attribute_gradients[remap[i0] * attribute_count], G, attribute_count);
quadricAdd(&attribute_gradients[remap[i1] * attribute_count], G, attribute_count);
quadricAdd(&attribute_gradients[remap[i2] * attribute_count], G, attribute_count);
quadricAdd(&attribute_gradients[i0 * attribute_count], G, attribute_count);
quadricAdd(&attribute_gradients[i1 * attribute_count], G, attribute_count);
quadricAdd(&attribute_gradients[i2 * attribute_count], G, attribute_count);
}
}

Expand Down Expand Up @@ -914,7 +913,13 @@ static bool hasTriangleFlips(const EdgeAdjacency& adjacency, const Vector3* vert

// early-out when at least one triangle flips due to a collapse
if (hasTriangleFlip(vertex_positions[a], vertex_positions[b], v0, v1))
{
#if TRACE >= 2
printf("edge block %d -> %d: flip welded %d %d %d\n", i0, i1, a, i0, b);
#endif

return true;
}
}

return false;
Expand Down Expand Up @@ -1017,16 +1022,31 @@ static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const
float ei = quadricError(vertex_quadrics[remap[i0]], vertex_positions[i1]);
float ej = quadricError(vertex_quadrics[remap[j0]], vertex_positions[j1]);

#if TRACE >= 2
float di = ei, dj = ej;
#endif

if (attribute_count)
{
ei += quadricError(attribute_quadrics[remap[i0]], &attribute_gradients[remap[i0] * attribute_count], attribute_count, vertex_positions[i1], &vertex_attributes[i1 * attribute_count]);
ej += quadricError(attribute_quadrics[remap[j0]], &attribute_gradients[remap[j0] * attribute_count], attribute_count, vertex_positions[j1], &vertex_attributes[j1 * attribute_count]);
// note: ideally we would evaluate max/avg of attribute errors for seam edges, but it's not clear if it's worth the extra cost
ei += quadricError(attribute_quadrics[i0], &attribute_gradients[i0 * attribute_count], attribute_count, vertex_positions[i1], &vertex_attributes[i1 * attribute_count]);
ej += quadricError(attribute_quadrics[j0], &attribute_gradients[j0 * attribute_count], attribute_count, vertex_positions[j1], &vertex_attributes[j1 * attribute_count]);
}

// pick edge direction with minimal error
c.v0 = ei <= ej ? i0 : j0;
c.v1 = ei <= ej ? i1 : j1;
c.error = ei <= ej ? ei : ej;

#if TRACE >= 2
if (i0 == j0) // c.bidi has been overwritten
printf("edge eval %d -> %d: error %f (pos %f, attr %f)\n", c.v0, c.v1,
sqrtf(c.error), sqrtf(ei <= ej ? di : dj), sqrtf(ei <= ej ? ei - di : ej - dj));
else
printf("edge eval %d -> %d: error %f (pos %f, attr %f); reverse %f (pos %f, attr %f)\n", c.v0, c.v1,
sqrtf(ei <= ej ? ei : ej), sqrtf(ei <= ej ? di : dj), sqrtf(ei <= ej ? ei - di : ej - dj),
sqrtf(ei <= ej ? ej : ei), sqrtf(ei <= ej ? dj : di), sqrtf(ei <= ej ? ej - dj : ei - di));
#endif
}
}

Expand Down Expand Up @@ -1108,6 +1128,8 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
unsigned int r0 = remap[i0];
unsigned int r1 = remap[i1];

unsigned char kind = vertex_kind[i0];

// we don't collapse vertices that had source or target vertex involved in a collapse
// it's important to not move the vertices twice since it complicates the tracking/remapping logic
// it's important to not move other vertices towards a moved vertex to preserve error since we don't re-rank collapses mid-pass
Expand All @@ -1126,33 +1148,46 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
continue;
}

#if TRACE >= 2
printf("edge commit %d -> %d: kind %d->%d, error %f\n", i0, i1, vertex_kind[i0], vertex_kind[i1], sqrtf(c.error));
#endif

assert(collapse_remap[r0] == r0);
assert(collapse_remap[r1] == r1);

quadricAdd(vertex_quadrics[r1], vertex_quadrics[r0]);

if (attribute_count)
{
quadricAdd(attribute_quadrics[r1], attribute_quadrics[r0]);
quadricAdd(&attribute_gradients[r1 * attribute_count], &attribute_gradients[r0 * attribute_count], attribute_count);
quadricAdd(attribute_quadrics[i1], attribute_quadrics[i0]);
quadricAdd(&attribute_gradients[i1 * attribute_count], &attribute_gradients[i0 * attribute_count], attribute_count);

// note: this is intentionally missing handling for Kind_Complex; we assume that complex vertices have similar attribute values so just using the primary vertex is fine
if (kind == Kind_Seam)
{
// seam collapses involve two edges so we need to update attribute quadrics for both target vertices; position quadrics are shared
unsigned int s0 = wedge[i0], s1 = wedge[i1];

quadricAdd(attribute_quadrics[s1], attribute_quadrics[s0]);
quadricAdd(&attribute_gradients[s1 * attribute_count], &attribute_gradients[s0 * attribute_count], attribute_count);
}
}

if (vertex_kind[i0] == Kind_Complex)
if (kind == Kind_Complex)
{
// remap all vertices in the complex to the target vertex
unsigned int v = i0;

do
{
collapse_remap[v] = r1;
collapse_remap[v] = i1;
v = wedge[v];
} while (v != i0);
}
else if (vertex_kind[i0] == Kind_Seam)
else if (kind == Kind_Seam)
{
// remap v0 to v1 and seam pair of v0 to seam pair of v1
unsigned int s0 = wedge[i0];
unsigned int s1 = wedge[i1];

unsigned int s0 = wedge[i0], s1 = wedge[i1];
assert(s0 != i0 && s1 != i1);
assert(wedge[s0] == i0 && wedge[s1] == i1);

Expand All @@ -1170,7 +1205,7 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
collapse_locked[r1] = 1;

// border edges collapse 1 triangle, other edges collapse 2 or more
triangle_collapses += (vertex_kind[i0] == Kind_Border) ? 1 : 2;
triangle_collapses += (kind == Kind_Border) ? 1 : 2;
edge_collapses++;

result_error = result_error < c.error ? c.error : result_error;
Expand Down Expand Up @@ -1641,7 +1676,7 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic
fillEdgeQuadrics(vertex_quadrics, result, index_count, vertex_positions, remap, vertex_kind, loop, loopback);

if (attribute_count)
fillAttributeQuadrics(attribute_quadrics, attribute_gradients, result, index_count, vertex_positions, vertex_attributes, attribute_count, remap);
fillAttributeQuadrics(attribute_quadrics, attribute_gradients, result, index_count, vertex_positions, vertex_attributes, attribute_count);

#if TRACE
size_t pass_count = 0;
Expand Down Expand Up @@ -1673,6 +1708,10 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic
if (edge_collapse_count == 0)
break;

#if TRACE
printf("pass %d:%c", int(pass_count++), TRACE >= 2 ? '\n' : ' ');
#endif

rankEdgeCollapses(edge_collapses, edge_collapse_count, vertex_positions, vertex_attributes, vertex_quadrics, attribute_quadrics, attribute_gradients, attribute_count, remap);

sortEdgeCollapses(collapse_order, edge_collapses, edge_collapse_count);
Expand All @@ -1684,10 +1723,6 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic

memset(collapse_locked, 0, vertex_count);

#if TRACE
printf("pass %d: ", int(pass_count++));
#endif

size_t collapses = performEdgeCollapses(collapse_remap, collapse_locked, vertex_quadrics, attribute_quadrics, attribute_gradients, attribute_count, edge_collapses, edge_collapse_count, collapse_order, remap, wedge, vertex_kind, vertex_positions, adjacency, triangle_collapse_goal, error_limit, result_error);

// no edges can be collapsed any more due to hitting the error limit or triangle collapse limit
Expand Down

0 comments on commit 0a9d917

Please sign in to comment.