diff --git a/README.md b/README.md index 355e76bd5..65ee66655 100644 --- a/README.md +++ b/README.md @@ -764,6 +764,7 @@ Currently, the following APIs are experimental: - `meshopt_SimplifyPermissive` mode for `meshopt_simplify*` functions (and associated `meshopt_SimplifyVertex_*` flags) - `meshopt_encodeMeshlet` and `meshopt_encodeMeshletBound` functions - `meshopt_decodeMeshlet` and `meshopt_decodeMeshletRaw` functions +- `meshopt_extractMeshletIndices` function ## License diff --git a/demo/clusterlod.h b/demo/clusterlod.h index 54fb767cc..7263b48ab 100644 --- a/demo/clusterlod.h +++ b/demo/clusterlod.h @@ -669,55 +669,7 @@ size_t clodBuild(clodConfig config, clodMesh mesh, void* output_context, clodOut size_t clodLocalIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count) { - size_t unique = 0; - - // direct mapped cache for fast lookups based on low index bits; inspired by vk_lod_clusters from NVIDIA - short cache[1024]; - memset(cache, -1, sizeof(cache)); - - for (size_t i = 0; i < index_count; ++i) - { - unsigned int v = indices[i]; - unsigned int key = v & (sizeof(cache) / sizeof(cache[0]) - 1); - short c = cache[key]; - - // fast path: vertex has been seen before - if (c >= 0 && vertices[c] == v) - { - triangles[i] = (unsigned char)c; - continue; - } - - // fast path: vertex has never been seen before - if (c < 0) - { - cache[key] = short(unique); - triangles[i] = (unsigned char)unique; - vertices[unique++] = v; - continue; - } - - // slow path: hash collision with a different vertex, so we need to look through all vertices - int pos = -1; - for (size_t j = 0; j < unique; ++j) - if (vertices[j] == v) - { - pos = int(j); - break; - } - - if (pos < 0) - { - pos = int(unique); - vertices[unique++] = v; - } - - cache[key] = short(pos); - triangles[i] = (unsigned char)pos; - } - - assert(unique <= 256); - return unique; + return meshopt_extractMeshletIndices(vertices, triangles, indices, index_count); } #endif diff --git a/demo/tests.cpp b/demo/tests.cpp index 695b72519..e2989d8b1 100644 --- a/demo/tests.cpp +++ b/demo/tests.cpp @@ -1395,6 +1395,24 @@ static void meshletsMax() } } +static void extractMeshlet() +{ + // 15 and 1039 collide in low 10 bits + const unsigned int indices[] = {0, 7, 15, 1039, 7, 15}; + + unsigned int vertices[4]; + unsigned char triangles[6]; + size_t unique = meshopt_extractMeshletIndices(vertices, triangles, indices, 6); + assert(unique == 4); + + // verify the invariant: vertices[triangles[i]] == indices[i] + for (size_t i = 0; i < 6; ++i) + assert(vertices[triangles[i]] == indices[i]); + + assert(vertices[0] == 0 && vertices[1] == 7 && vertices[2] == 15 && vertices[3] == 1039); + assert(triangles[0] == 0 && triangles[1] == 1 && triangles[2] == 2 && triangles[3] == 3 && triangles[4] == 1 && triangles[5] == 2); +} + static void meshletsSpatial() { // two tetrahedrons far apart @@ -2987,6 +3005,8 @@ void runTests() clusterBoundsDegenerate(); sphereBounds(); + extractMeshlet(); + meshletsEmpty(); meshletsDense(); meshletsSparse(); diff --git a/src/meshletutils.cpp b/src/meshletutils.cpp index 2f30eeb5f..5bb144c64 100644 --- a/src/meshletutils.cpp +++ b/src/meshletutils.cpp @@ -472,3 +472,63 @@ void meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned char* mesh assert(vertex_offset <= vertex_count); memcpy(vertices, order, vertex_offset * sizeof(unsigned int)); } + +size_t meshopt_extractMeshletIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count) +{ + using namespace meshopt; + + assert(index_count % 3 == 0); + assert(index_count / 3 <= kMeshletMaxTriangles); + + size_t unique = 0; + + // direct mapped cache for fast lookups based on low index bits; inspired by vk_lod_clusters from NVIDIA + short cache[1024]; + memset(cache, -1, sizeof(cache)); + + for (size_t i = 0; i < index_count; ++i) + { + unsigned int v = indices[i]; + unsigned int key = v & (sizeof(cache) / sizeof(cache[0]) - 1); + short c = cache[key]; + + // fast path: vertex has been seen before + if (c >= 0 && vertices[c] == v) + { + triangles[i] = (unsigned char)c; + continue; + } + + // fast path: vertex has never been seen before + if (c < 0) + { + assert(unique < kMeshletMaxVertices); + cache[key] = short(unique); + triangles[i] = (unsigned char)unique; + vertices[unique++] = v; + continue; + } + + // slow path: collision with a different vertex, so we need to look through all vertices + int pos = -1; + for (size_t j = 0; j < unique; ++j) + if (vertices[j] == v) + { + pos = int(j); + break; + } + + if (pos < 0) + { + assert(unique < kMeshletMaxVertices); + pos = int(unique); + vertices[unique++] = v; + } + + cache[key] = short(pos); + triangles[i] = (unsigned char)pos; + } + + assert(unique <= kMeshletMaxVertices); + return unique; +} diff --git a/src/meshoptimizer.h b/src/meshoptimizer.h index c7723433a..322978552 100644 --- a/src/meshoptimizer.h +++ b/src/meshoptimizer.h @@ -789,6 +789,15 @@ MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeMeshletBounds(const unsig */ MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeSphereBounds(const float* positions, size_t count, size_t positions_stride, const float* radii, size_t radii_stride); +/** + * Experimental: Extract meshlet-local vertex and triangle indices from absolute cluster indices. + * Fills triangles[] and vertices[] such that vertices[triangles[i]] == indices[i], and returns the number of unique vertices. + * + * indices should have at most 256 unique vertex indices + * index_count/3 must not exceed implementation limits (<= 512) + */ +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_extractMeshletIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count); + /** * Cluster partitioner * Partitions clusters into groups of similar size, prioritizing grouping clusters that share vertices or are close to each other.