1
0
mirror of https://github.com/godotengine/godot.git synced 2025-12-31 18:41:20 +00:00

meshoptimizer: Update to 1.0

This commit is contained in:
Rémi Verschelde
2025-12-13 10:38:34 +01:00
parent 08e6cd181f
commit f99d094778
8 changed files with 667 additions and 358 deletions

View File

@@ -243,14 +243,18 @@ static unsigned int* buildSparseRemap(unsigned int* indices, size_t index_count,
{
// use a bit set to compute the precise number of unique vertices
unsigned char* filter = allocator.allocate<unsigned char>((vertex_count + 7) / 8);
memset(filter, 0, (vertex_count + 7) / 8);
for (size_t i = 0; i < index_count; ++i)
{
unsigned int index = indices[i];
assert(index < vertex_count);
filter[index / 8] = 0;
}
size_t unique = 0;
for (size_t i = 0; i < index_count; ++i)
{
unsigned int index = indices[i];
assert(index < vertex_count);
unique += (filter[index / 8] & (1 << (index % 8))) == 0;
filter[index / 8] |= 1 << (index % 8);
}
@@ -269,7 +273,6 @@ static unsigned int* buildSparseRemap(unsigned int* indices, size_t index_count,
for (size_t i = 0; i < index_count; ++i)
{
unsigned int index = indices[i];
unsigned int* entry = hashLookup2(revremap, revremap_size, hasher, index, ~0u);
if (*entry == ~0u)
@@ -364,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;
@@ -617,7 +620,7 @@ static void rescaleAttributes(float* result, const float* vertex_attributes_data
}
}
static void finalizeVertices(float* vertex_positions_data, size_t vertex_positions_stride, float* vertex_attributes_data, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, size_t vertex_count, const Vector3* vertex_positions, const float* vertex_attributes, const unsigned int* sparse_remap, const unsigned int* attribute_remap, float vertex_scale, const float* vertex_offset, const unsigned char* vertex_update)
static void finalizeVertices(float* vertex_positions_data, size_t vertex_positions_stride, float* vertex_attributes_data, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, size_t vertex_count, const Vector3* vertex_positions, const float* vertex_attributes, const unsigned int* sparse_remap, const unsigned int* attribute_remap, float vertex_scale, const float* vertex_offset, const unsigned char* vertex_kind, const unsigned char* vertex_update, const unsigned char* vertex_lock)
{
size_t vertex_positions_stride_float = vertex_positions_stride / sizeof(float);
size_t vertex_attributes_stride_float = vertex_attributes_stride / sizeof(float);
@@ -629,12 +632,20 @@ static void finalizeVertices(float* vertex_positions_data, size_t vertex_positio
unsigned int ri = sparse_remap ? sparse_remap[i] : unsigned(i);
const Vector3& p = vertex_positions[i];
float* v = vertex_positions_data + ri * vertex_positions_stride_float;
// updating externally locked vertices is not allowed
if (vertex_lock && (vertex_lock[ri] & meshopt_SimplifyVertex_Lock) != 0)
continue;
v[0] = p.x * vertex_scale + vertex_offset[0];
v[1] = p.y * vertex_scale + vertex_offset[1];
v[2] = p.z * vertex_scale + vertex_offset[2];
// moving locked vertices may result in floating point drift
if (vertex_kind[i] != Kind_Locked)
{
const Vector3& p = vertex_positions[i];
float* v = vertex_positions_data + ri * vertex_positions_stride_float;
v[0] = p.x * vertex_scale + vertex_offset[0];
v[1] = p.y * vertex_scale + vertex_offset[1];
v[2] = p.z * vertex_scale + vertex_offset[2];
}
if (attribute_count)
{
@@ -1260,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;
@@ -1382,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]);
}
}
}
@@ -1542,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);
}
@@ -1634,10 +1668,10 @@ static void updateQuadrics(const unsigned int* collapse_remap, size_t vertex_cou
}
}
static void solveQuadrics(Vector3* vertex_positions, float* vertex_attributes, size_t vertex_count, const Quadric* vertex_quadrics, const QuadricGrad* volume_gradients, const Quadric* attribute_quadrics, const QuadricGrad* attribute_gradients, size_t attribute_count, const unsigned int* remap, const unsigned int* wedge, const EdgeAdjacency& adjacency, const unsigned char* vertex_kind, const unsigned char* vertex_update)
static void solvePositions(Vector3* vertex_positions, size_t vertex_count, const Quadric* vertex_quadrics, const QuadricGrad* volume_gradients, const Quadric* attribute_quadrics, const QuadricGrad* attribute_gradients, size_t attribute_count, const unsigned int* remap, const unsigned int* wedge, const EdgeAdjacency& adjacency, const unsigned char* vertex_kind, const unsigned char* vertex_update)
{
#if TRACE
size_t stats[5] = {};
size_t stats[6] = {};
#endif
for (size_t i = 0; i < vertex_count; ++i)
@@ -1645,7 +1679,6 @@ static void solveQuadrics(Vector3* vertex_positions, float* vertex_attributes, s
if (!vertex_update[i])
continue;
// moving externally locked vertices is prohibited
// moving vertices on an attribute discontinuity may result in extrapolating UV outside of the chart bounds
// moving vertices on a border requires a stronger edge quadric to preserve the border geometry
if (vertex_kind[i] == Kind_Locked || vertex_kind[i] == Kind_Seam || vertex_kind[i] == Kind_Border)
@@ -1709,36 +1742,64 @@ static void solveQuadrics(Vector3* vertex_positions, float* vertex_attributes, s
continue;
}
// reject updates that increase positional error too much; allow some tolerance to improve attribute quality
if (quadricError(vertex_quadrics[i], p) > quadricError(vertex_quadrics[i], vp) * 1.5f + 1e-6f)
{
TRACESTATS(5);
continue;
}
TRACESTATS(1);
vertex_positions[i] = p;
}
#if TRACE
printf("updated %d/%d positions; failed solve %d bounds %d flip %d\n", int(stats[1]), int(stats[0]), int(stats[2]), int(stats[3]), int(stats[4]));
printf("updated %d/%d positions; failed solve %d bounds %d flip %d error %d\n", int(stats[1]), int(stats[0]), int(stats[2]), int(stats[3]), int(stats[4]), int(stats[5]));
#endif
}
if (attribute_count == 0)
return;
static void solveAttributes(Vector3* vertex_positions, float* vertex_attributes, size_t vertex_count, const Quadric* attribute_quadrics, const QuadricGrad* attribute_gradients, size_t attribute_count, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_kind, const unsigned char* vertex_update)
{
for (size_t i = 0; i < vertex_count; ++i)
{
if (!vertex_update[i])
continue;
// updating externally locked vertices is prohibited
if (vertex_kind[i] == Kind_Locked)
if (remap[i] != i)
continue;
const Vector3& p = vertex_positions[remap[i]];
const Quadric& A = attribute_quadrics[i];
float iw = A.w == 0 ? 0.f : 1.f / A.w;
for (size_t k = 0; k < attribute_count; ++k)
{
const QuadricGrad& G = attribute_gradients[i * attribute_count + k];
unsigned int shared = ~0u;
vertex_attributes[i * attribute_count + k] = (G.gx * p.x + G.gy * p.y + G.gz * p.z + G.gw) * iw;
// for complex vertices, preserve attribute continuity and use highest weight wedge if values were shared
if (vertex_kind[i] == Kind_Complex)
{
shared = unsigned(i);
for (unsigned int v = wedge[i]; v != i; v = wedge[v])
if (vertex_attributes[v * attribute_count + k] != vertex_attributes[i * attribute_count + k])
shared = ~0u;
else if (shared != ~0u && attribute_quadrics[v].w > attribute_quadrics[shared].w)
shared = v;
}
// update attributes for all wedges
unsigned int v = unsigned(i);
do
{
unsigned int r = (shared == ~0u) ? v : shared;
const Vector3& p = vertex_positions[i]; // same for all wedges
const Quadric& A = attribute_quadrics[r];
const QuadricGrad& G = attribute_gradients[r * attribute_count + k];
float iw = A.w == 0 ? 0.f : 1.f / A.w;
float av = (G.gx * p.x + G.gy * p.y + G.gz * p.z + G.gw) * iw;
vertex_attributes[v * attribute_count + k] = av;
v = wedge[v];
} while (v != i);
}
}
}
@@ -2264,7 +2325,7 @@ static float interpolate(float y, float x0, float y0, float x1, float y1, float
// three point interpolation from "revenge of interpolation search" paper
float num = (y1 - y) * (x1 - x2) * (x1 - x0) * (y2 - y0);
float den = (y2 - y) * (x1 - x2) * (y0 - y1) + (y0 - y) * (x1 - x0) * (y1 - y2);
return x1 + num / den;
return x1 + (den == 0.f ? 0.f : num / den);
}
} // namespace meshopt
@@ -2519,16 +2580,19 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic
{
unsigned int v = result[i];
// recomputing externally locked vertices may result in floating point drift
vertex_update[v] = vertex_kind[v] != Kind_Locked;
// mark the vertex for finalizeVertices and root vertex for solve*
vertex_update[remap[v]] = vertex_update[v] = 1;
}
// edge adjacency may be stale as we haven't updated it after last series of edge collapses
updateEdgeAdjacency(adjacency, result, result_count, vertex_count, remap);
solveQuadrics(vertex_positions, vertex_attributes, vertex_count, vertex_quadrics, volume_gradients, attribute_quadrics, attribute_gradients, attribute_count, remap, wedge, adjacency, vertex_kind, vertex_update);
solvePositions(vertex_positions, vertex_count, vertex_quadrics, volume_gradients, attribute_quadrics, attribute_gradients, attribute_count, remap, wedge, adjacency, vertex_kind, vertex_update);
finalizeVertices(const_cast<float*>(vertex_positions_data), vertex_positions_stride, const_cast<float*>(vertex_attributes_data), vertex_attributes_stride, attribute_weights, attribute_count, vertex_count, vertex_positions, vertex_attributes, sparse_remap, attribute_remap, vertex_scale, vertex_offset, vertex_update);
if (attribute_count)
solveAttributes(vertex_positions, vertex_attributes, vertex_count, attribute_quadrics, attribute_gradients, attribute_count, remap, wedge, vertex_kind, vertex_update);
finalizeVertices(const_cast<float*>(vertex_positions_data), vertex_positions_stride, const_cast<float*>(vertex_attributes_data), vertex_attributes_stride, attribute_weights, attribute_count, vertex_count, vertex_positions, vertex_attributes, sparse_remap, attribute_remap, vertex_scale, vertex_offset, vertex_kind, vertex_update, vertex_lock);
}
// if debug visualization data is requested, fill it instead of index data; for simplicity, this doesn't work with sparsity