From 52e1fb9d9f12fb30fece528d8fb8a41b3b402e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=80=D0=B0=D0=BD=D0=B8=D0=BC=D0=B8=D1=80=20=D0=9A?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D1=9F=D0=B8=D1=9B?= Date: Fri, 9 Aug 2019 19:39:39 -0700 Subject: [PATCH] Updated meshoptimizer. --- 3rdparty/meshoptimizer/CMakeLists.txt | 62 ++++++- 3rdparty/meshoptimizer/config.cmake.in | 4 + 3rdparty/meshoptimizer/demo/main.cpp | 77 +++++++- 3rdparty/meshoptimizer/src/meshoptimizer.h | 28 +++ 3rdparty/meshoptimizer/src/spatialorder.cpp | 194 ++++++++++++++++++++ 3rdparty/meshoptimizer/tools/gltfpack.cpp | 153 +++++++++++---- 6 files changed, 474 insertions(+), 44 deletions(-) create mode 100644 3rdparty/meshoptimizer/config.cmake.in create mode 100644 3rdparty/meshoptimizer/src/spatialorder.cpp diff --git a/3rdparty/meshoptimizer/CMakeLists.txt b/3rdparty/meshoptimizer/CMakeLists.txt index cc8a512a4..b59baa9be 100644 --- a/3rdparty/meshoptimizer/CMakeLists.txt +++ b/3rdparty/meshoptimizer/CMakeLists.txt @@ -1,8 +1,10 @@ -project(meshoptimizer) cmake_minimum_required(VERSION 3.0) +project(meshoptimizer VERSION 0.12) + option(BUILD_DEMO "Build demo" OFF) option(BUILD_TOOLS "Build tools" OFF) +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) set(SOURCES src/meshoptimizer.h @@ -13,6 +15,7 @@ set(SOURCES src/overdrawanalyzer.cpp src/overdrawoptimizer.cpp src/simplifier.cpp + src/spatialorder.cpp src/stripifier.cpp src/vcacheanalyzer.cpp src/vcacheoptimizer.cpp @@ -21,15 +24,29 @@ set(SOURCES src/vfetchoptimizer.cpp ) -add_library(meshoptimizer STATIC ${SOURCES}) -target_include_directories(meshoptimizer INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/src") +add_library(meshoptimizer ${SOURCES}) +target_include_directories(meshoptimizer INTERFACE "$") if(MSVC) - target_compile_options(meshoptimizer PRIVATE /W4 /WX) + target_compile_options(meshoptimizer PRIVATE /W4 /WX) else() - target_compile_options(meshoptimizer PRIVATE -Wall -Wextra -Werror) + target_compile_options(meshoptimizer PRIVATE -Wall -Wextra -Werror) endif() +if(BUILD_SHARED_LIBS) + set_target_properties(meshoptimizer PROPERTIES CXX_VISIBILITY_PRESET hidden) + set_target_properties(meshoptimizer PROPERTIES VISIBILITY_INLINES_HIDDEN ON) + + if(WIN32) + target_compile_definitions(meshoptimizer INTERFACE "MESHOPTIMIZER_API=__declspec(dllimport)") + target_compile_definitions(meshoptimizer PRIVATE "MESHOPTIMIZER_API=__declspec(dllexport)") + else() + target_compile_definitions(meshoptimizer PUBLIC "MESHOPTIMIZER_API=__attribute__((visibility(\"default\")))") + endif() +endif() + +set(TARGETS meshoptimizer) + if(BUILD_DEMO) add_executable(demo demo/main.cpp demo/miniz.cpp demo/tests.cpp tools/meshloader.cpp) target_link_libraries(demo meshoptimizer) @@ -38,4 +55,39 @@ endif() if(BUILD_TOOLS) add_executable(gltfpack tools/gltfpack.cpp tools/meshloader.cpp) target_link_libraries(gltfpack meshoptimizer) + list(APPEND TARGETS gltfpack) + + set_target_properties(gltfpack PROPERTIES INSTALL_RPATH "$ORIGIN/../lib") endif() + +install(TARGETS ${TARGETS} EXPORT meshoptimizerTargets + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + INCLUDES DESTINATION include) + +install(FILES src/meshoptimizer.h DESTINATION include) +install(EXPORT meshoptimizerTargets DESTINATION lib/cmake/meshoptimizer NAMESPACE meshoptimizer::) + +# TARGET_PDB_FILE is available since 3.1 +if(MSVC AND NOT (CMAKE_VERSION VERSION_LESS "3.1")) + foreach(TARGET ${TARGETS}) + get_target_property(TARGET_TYPE ${TARGET} TYPE) + if(NOT ${TARGET_TYPE} STREQUAL "STATIC_LIBRARY") + install(FILES $ DESTINATION bin OPTIONAL) + endif() + endforeach(TARGET) +endif() + +include(CMakePackageConfigHelpers) + +configure_package_config_file(config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfig.cmake + INSTALL_DESTINATION lib/cmake/meshoptimizer NO_SET_AND_CHECK_MACRO) + +write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfigVersion.cmake COMPATIBILITY ExactVersion) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfigVersion.cmake + DESTINATION lib/cmake/meshoptimizer) diff --git a/3rdparty/meshoptimizer/config.cmake.in b/3rdparty/meshoptimizer/config.cmake.in new file mode 100644 index 000000000..130ab28f2 --- /dev/null +++ b/3rdparty/meshoptimizer/config.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/meshoptimizerTargets.cmake") +check_required_components(meshoptimizer) diff --git a/3rdparty/meshoptimizer/demo/main.cpp b/3rdparty/meshoptimizer/demo/main.cpp index 603613421..d4d75e30f 100644 --- a/3rdparty/meshoptimizer/demo/main.cpp +++ b/3rdparty/meshoptimizer/demo/main.cpp @@ -771,6 +771,68 @@ void meshlets(const Mesh& mesh) (endc - startc) * 1000); } +void spatialSort(const Mesh& mesh) +{ + typedef PackedVertexOct PV; + + std::vector pv(mesh.vertices.size()); + packMesh(pv, mesh.vertices); + + double start = timestamp(); + + std::vector remap(mesh.vertices.size()); + meshopt_spatialSortRemap(&remap[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex)); + + double end = timestamp(); + + meshopt_remapVertexBuffer(&pv[0], &pv[0], mesh.vertices.size(), sizeof(PV), &remap[0]); + + std::vector vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV))); + vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV))); + + size_t csize = compress(vbuf); + + printf("Spatial : %.1f bits/vertex (post-deflate %.1f bits/vertex); sort %.2f msec\n", + double(vbuf.size() * 8) / double(mesh.vertices.size()), + double(csize * 8) / double(mesh.vertices.size()), + (end - start) * 1000); +} + +void spatialSortTriangles(const Mesh& mesh) +{ + typedef PackedVertexOct PV; + + Mesh copy = mesh; + + double start = timestamp(); + + meshopt_spatialSortTriangles(©.indices[0], ©.indices[0], mesh.indices.size(), ©.vertices[0].px, copy.vertices.size(), sizeof(Vertex)); + + double end = timestamp(); + + meshopt_optimizeVertexCache(©.indices[0], ©.indices[0], copy.indices.size(), copy.vertices.size()); + meshopt_optimizeVertexFetch(©.vertices[0], ©.indices[0], copy.indices.size(), ©.vertices[0], copy.vertices.size(), sizeof(Vertex)); + + std::vector pv(mesh.vertices.size()); + packMesh(pv, copy.vertices); + + std::vector vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV))); + vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV))); + + std::vector ibuf(meshopt_encodeIndexBufferBound(mesh.indices.size(), mesh.vertices.size())); + ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), ©.indices[0], mesh.indices.size())); + + size_t csizev = compress(vbuf); + size_t csizei = compress(ibuf); + + printf("SpatialT : %.1f bits/vertex (post-deflate %.1f bits/vertex); %.1f bits/triangle (post-deflate %.1f bits/triangle); sort %.2f msec\n", + double(vbuf.size() * 8) / double(mesh.vertices.size()), + double(csizev * 8) / double(mesh.vertices.size()), + double(ibuf.size() * 8) / double(mesh.indices.size() / 3), + double(csizei * 8) / double(mesh.indices.size() / 3), + (end - start) * 1000); +} + bool loadMesh(Mesh& mesh, const char* path) { double start = timestamp(); @@ -924,6 +986,9 @@ void process(const char* path) simplifySloppy(mesh); simplifyComplete(mesh); + spatialSort(mesh); + spatialSortTriangles(mesh); + if (path) processDeinterleaved(path); } @@ -934,9 +999,15 @@ void processDev(const char* path) if (!loadMesh(mesh, path)) return; - simplifySloppy(mesh, 0.5f); - simplifySloppy(mesh, 0.1f); - simplifySloppy(mesh, 0.01f); + Mesh copy = mesh; + meshopt_optimizeVertexCache(©.indices[0], ©.indices[0], copy.indices.size(), copy.vertices.size()); + meshopt_optimizeVertexFetch(©.vertices[0], ©.indices[0], copy.indices.size(), ©.vertices[0], copy.vertices.size(), sizeof(Vertex)); + + encodeIndex(copy); + encodeVertex(copy, "O"); + + spatialSort(mesh); + spatialSortTriangles(mesh); } int main(int argc, char** argv) diff --git a/3rdparty/meshoptimizer/src/meshoptimizer.h b/3rdparty/meshoptimizer/src/meshoptimizer.h index ce4830d04..0fa213501 100644 --- a/3rdparty/meshoptimizer/src/meshoptimizer.h +++ b/3rdparty/meshoptimizer/src/meshoptimizer.h @@ -342,6 +342,25 @@ struct meshopt_Bounds MESHOPTIMIZER_EXPERIMENTAL struct meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); MESHOPTIMIZER_EXPERIMENTAL struct meshopt_Bounds meshopt_computeMeshletBounds(const struct meshopt_Meshlet* meshlet, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +/** + * Experimental: Spatial sorter + * Generates a remap table that can be used to reorder points for spatial locality. + * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer. + * + * destination must contain enough space for the resulting remap table (vertex_count elements) + */ +MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortRemap(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); + +/** + * Experimental: Spatial sorter + * Reorders triangles for spatial locality, and generates a new index buffer. The resulting index buffer can be used with other functions like optimizeVertexCache. + * + * destination must contain enough space for the resulting index buffer (index_count elements) + * indices must contain index data that is the result of meshopt_optimizeVertexCache (*not* the original mesh indices!) + * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer + */ +MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortTriangles(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); + /** * Set allocation callbacks * These callbacks will be used instead of the default operator new/operator delete for all temporary allocations in the library. @@ -620,6 +639,15 @@ inline meshopt_Bounds meshopt_computeClusterBounds(const T* indices, size_t inde return meshopt_computeClusterBounds(in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride); } + +template +inline void meshopt_spatialSortTriangles(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) +{ + meshopt_IndexAdapter in(0, indices, index_count); + meshopt_IndexAdapter out(destination, 0, index_count); + + meshopt_spatialSortTriangles(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride); +} #endif /* Inline implementation */ diff --git a/3rdparty/meshoptimizer/src/spatialorder.cpp b/3rdparty/meshoptimizer/src/spatialorder.cpp new file mode 100644 index 000000000..b09f80ac6 --- /dev/null +++ b/3rdparty/meshoptimizer/src/spatialorder.cpp @@ -0,0 +1,194 @@ +// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +#include "meshoptimizer.h" + +#include +#include +#include + +// This work is based on: +// Fabian Giesen. Decoding Morton codes. 2009 +namespace meshopt +{ + +// "Insert" two 0 bits after each of the 10 low bits of x +inline unsigned int part1By2(unsigned int x) +{ + x &= 0x000003ff; // x = ---- ---- ---- ---- ---- --98 7654 3210 + x = (x ^ (x << 16)) & 0xff0000ff; // x = ---- --98 ---- ---- ---- ---- 7654 3210 + x = (x ^ (x << 8)) & 0x0300f00f; // x = ---- --98 ---- ---- 7654 ---- ---- 3210 + x = (x ^ (x << 4)) & 0x030c30c3; // x = ---- --98 ---- 76-- --54 ---- 32-- --10 + x = (x ^ (x << 2)) & 0x09249249; // x = ---- 9--8 --7- -6-- 5--4 --3- -2-- 1--0 + return x; +} + +static void computeOrder(unsigned int* result, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride) +{ + size_t vertex_stride_float = vertex_positions_stride / sizeof(float); + + float minv[3] = {FLT_MAX, FLT_MAX, FLT_MAX}; + float maxv[3] = {-FLT_MAX, -FLT_MAX, -FLT_MAX}; + + for (size_t i = 0; i < vertex_count; ++i) + { + const float* v = vertex_positions_data + i * vertex_stride_float; + + for (int j = 0; j < 3; ++j) + { + float vj = v[j]; + + minv[j] = minv[j] > vj ? vj : minv[j]; + maxv[j] = maxv[j] < vj ? vj : maxv[j]; + } + } + + float extent = 0.f; + + extent = (maxv[0] - minv[0]) < extent ? extent : (maxv[0] - minv[0]); + extent = (maxv[1] - minv[1]) < extent ? extent : (maxv[1] - minv[1]); + extent = (maxv[2] - minv[2]) < extent ? extent : (maxv[2] - minv[2]); + + float scale = extent == 0 ? 0.f : 1.f / extent; + + // generate Morton order based on the position inside a unit cube + for (size_t i = 0; i < vertex_count; ++i) + { + const float* v = vertex_positions_data + i * vertex_stride_float; + + int x = int((v[0] - minv[0]) * scale * 1023.f + 0.5f); + int y = int((v[1] - minv[1]) * scale * 1023.f + 0.5f); + int z = int((v[2] - minv[2]) * scale * 1023.f + 0.5f); + + result[i] = part1By2(x) | (part1By2(y) << 1) | (part1By2(z) << 2); + } +} + +static void computeHistogram(unsigned int (&hist)[1024][3], const unsigned int* data, size_t count) +{ + memset(hist, 0, sizeof(hist)); + + // compute 3 10-bit histograms in parallel + for (size_t i = 0; i < count; ++i) + { + unsigned int id = data[i]; + + hist[(id >> 0) & 1023][0]++; + hist[(id >> 10) & 1023][1]++; + hist[(id >> 20) & 1023][2]++; + } + + unsigned int sumx = 0, sumy = 0, sumz = 0; + + // replace histogram data with prefix histogram sums in-place + for (int i = 0; i < 1024; ++i) + { + unsigned int hx = hist[i][0], hy = hist[i][1], hz = hist[i][2]; + + hist[i][0] = sumx; + hist[i][1] = sumy; + hist[i][2] = sumz; + + sumx += hx; + sumy += hy; + sumz += hz; + } + + assert(sumx == count && sumy == count && sumz == count); +} + +static void radixPass(unsigned int* destination, const unsigned int* source, const unsigned int* keys, size_t count, unsigned int (&hist)[1024][3], int pass) +{ + int bitoff = pass * 10; + + for (size_t i = 0; i < count; ++i) + { + unsigned int id = (keys[source[i]] >> bitoff) & 1023; + + destination[hist[id][pass]++] = source[i]; + } +} + +} // namespace meshopt + +void meshopt_spatialSortRemap(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) +{ + using namespace meshopt; + + assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256); + assert(vertex_positions_stride % sizeof(float) == 0); + + meshopt_Allocator allocator; + + unsigned int* keys = allocator.allocate(vertex_count); + computeOrder(keys, vertex_positions, vertex_count, vertex_positions_stride); + + unsigned int hist[1024][3]; + computeHistogram(hist, keys, vertex_count); + + unsigned int* scratch = allocator.allocate(vertex_count); + + for (size_t i = 0; i < vertex_count; ++i) + destination[i] = unsigned(i); + + // 3-pass radix sort computes the resulting order into scratch + radixPass(scratch, destination, keys, vertex_count, hist, 0); + radixPass(destination, scratch, keys, vertex_count, hist, 1); + radixPass(scratch, destination, keys, vertex_count, hist, 2); + + // since our remap table is mapping old=>new, we need to reverse it + for (size_t i = 0; i < vertex_count; ++i) + destination[scratch[i]] = unsigned(i); +} + +void meshopt_spatialSortTriangles(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) +{ + using namespace meshopt; + + assert(index_count % 3 == 0); + assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256); + assert(vertex_positions_stride % sizeof(float) == 0); + + (void)vertex_count; + + size_t face_count = index_count / 3; + size_t vertex_stride_float = vertex_positions_stride / sizeof(float); + + meshopt_Allocator allocator; + + float* centroids = allocator.allocate(face_count * 3); + + for (size_t i = 0; i < face_count; ++i) + { + unsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2]; + assert(a < vertex_count && b < vertex_count && c < vertex_count); + + const float* va = vertex_positions + a * vertex_stride_float; + const float* vb = vertex_positions + b * vertex_stride_float; + const float* vc = vertex_positions + c * vertex_stride_float; + + centroids[i * 3 + 0] = (va[0] + vb[0] + vc[0]) / 3.f; + centroids[i * 3 + 1] = (va[1] + vb[1] + vc[1]) / 3.f; + centroids[i * 3 + 2] = (va[2] + vb[2] + vc[2]) / 3.f; + } + + unsigned int* remap = allocator.allocate(face_count); + + meshopt_spatialSortRemap(remap, centroids, face_count, sizeof(float) * 3); + + // support in-order remap + if (destination == indices) + { + unsigned int* indices_copy = allocator.allocate(index_count); + memcpy(indices_copy, indices, index_count * sizeof(unsigned int)); + indices = indices_copy; + } + + for (size_t i = 0; i < face_count; ++i) + { + unsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2]; + unsigned int r = remap[i]; + + destination[r * 3 + 0] = a; + destination[r * 3 + 1] = b; + destination[r * 3 + 2] = c; + } +} diff --git a/3rdparty/meshoptimizer/tools/gltfpack.cpp b/3rdparty/meshoptimizer/tools/gltfpack.cpp index 561a86fda..1eb2e3a64 100644 --- a/3rdparty/meshoptimizer/tools/gltfpack.cpp +++ b/3rdparty/meshoptimizer/tools/gltfpack.cpp @@ -58,6 +58,8 @@ struct Mesh cgltf_material* material; cgltf_skin* skin; + cgltf_primitive_type type; + std::vector streams; std::vector indices; @@ -237,12 +239,18 @@ void parseMeshesGltf(cgltf_data* data, std::vector& meshes) { const cgltf_primitive& primitive = mesh.primitives[pi]; - if (primitive.type != cgltf_primitive_type_triangles) + if (primitive.type != cgltf_primitive_type_triangles && primitive.type != cgltf_primitive_type_points) { fprintf(stderr, "Warning: ignoring primitive %d of mesh %d because type %d is not supported\n", int(pi), mesh_id, primitive.type); continue; } + if (primitive.type == cgltf_primitive_type_points && primitive.indices) + { + fprintf(stderr, "Warning: ignoring primitive %d of mesh %d because indexed points are not supported\n", int(pi), mesh_id); + continue; + } + Mesh result; result.node = &node; @@ -250,13 +258,15 @@ void parseMeshesGltf(cgltf_data* data, std::vector& meshes) result.material = primitive.material; result.skin = node.skin; + result.type = primitive.type; + if (primitive.indices) { result.indices.resize(primitive.indices->count); for (size_t i = 0; i < primitive.indices->count; ++i) result.indices[i] = unsigned(cgltf_accessor_read_index(primitive.indices, i)); } - else + else if (primitive.type != cgltf_primitive_type_points) { size_t count = primitive.attributes ? primitive.attributes[0].data->count : 0; @@ -431,6 +441,8 @@ void parseMeshesObj(fastObjMesh* obj, cgltf_data* data, std::vector& meshe mesh.material = &data->materials[mi]; } + mesh.type = cgltf_primitive_type_triangles; + mesh.streams.resize(3); mesh.streams[0].type = cgltf_attribute_type_position; mesh.streams[0].data.resize(vertex_count[mi]); @@ -601,9 +613,6 @@ void mergeMeshMaterials(cgltf_data* data, std::vector& meshes) { Mesh& mesh = meshes[i]; - if (mesh.indices.empty()) - continue; - if (!mesh.material) continue; @@ -653,6 +662,9 @@ bool canMergeMeshes(const Mesh& lhs, const Mesh& rhs, const Settings& settings) if (lhs.skin != rhs.skin) return false; + if (lhs.type != rhs.type) + return false; + if (lhs.targets != rhs.targets) return false; @@ -663,6 +675,9 @@ bool canMergeMeshes(const Mesh& lhs, const Mesh& rhs, const Settings& settings) if (lhs.weights[i] != rhs.weights[i]) return false; + if (lhs.indices.empty() != rhs.indices.empty()) + return false; + if (lhs.streams.size() != rhs.streams.size()) return false; @@ -693,25 +708,65 @@ void mergeMeshes(Mesh& target, const Mesh& mesh) void mergeMeshes(std::vector& meshes, const Settings& settings) { + size_t write = 0; + for (size_t i = 0; i < meshes.size(); ++i) { - Mesh& mesh = meshes[i]; + if (meshes[i].streams.empty()) + continue; - for (size_t j = 0; j < i; ++j) + Mesh& target = meshes[write]; + + if (i != write) { - Mesh& target = meshes[j]; + Mesh& mesh = meshes[i]; - if (target.indices.size() && canMergeMeshes(mesh, target, settings)) + // note: this copy is expensive; we could use move in C++11 or swap manually which is a bit painful... + target = mesh; + + mesh.streams.clear(); + mesh.indices.clear(); + } + + size_t target_vertices = target.streams[0].data.size(); + size_t target_indices = target.indices.size(); + + for (size_t j = i + 1; j < meshes.size(); ++j) + { + Mesh& mesh = meshes[j]; + + if (!mesh.streams.empty() && canMergeMeshes(target, mesh, settings)) + { + target_vertices += mesh.streams[0].data.size(); + target_indices += mesh.indices.size(); + } + } + + for (size_t j = 0; j < target.streams.size(); ++j) + target.streams[j].data.reserve(target_vertices); + + target.indices.reserve(target_indices); + + for (size_t j = i + 1; j < meshes.size(); ++j) + { + Mesh& mesh = meshes[j]; + + if (!mesh.streams.empty() && canMergeMeshes(target, mesh, settings)) { mergeMeshes(target, mesh); mesh.streams.clear(); mesh.indices.clear(); - - break; } } + + assert(target.streams[0].data.size() == target_vertices); + assert(target.indices.size() == target_indices); + + write++; } + + meshes.resize(write); } void reindexMesh(Mesh& mesh) @@ -764,6 +819,33 @@ void optimizeMesh(Mesh& mesh) meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], vertex_count, sizeof(Attr), &remap[0]); } +void sortPointMesh(Mesh& mesh) +{ + size_t positions = 0; + + for (size_t i = 0; i < mesh.streams.size(); ++i) + if (mesh.streams[i].type == cgltf_attribute_type_position) + { + positions = i; + break; + } + + assert(mesh.streams[positions].type == cgltf_attribute_type_position); + assert(mesh.indices.empty()); + + size_t total_vertices = mesh.streams[positions].data.size(); + + std::vector remap(total_vertices); + meshopt_spatialSortRemap(&remap[0], mesh.streams[positions].data[0].f, total_vertices, sizeof(Attr)); + + for (size_t i = 0; i < mesh.streams.size(); ++i) + { + assert(mesh.streams[i].data.size() == total_vertices); + + meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], total_vertices, sizeof(Attr), &remap[0]); + } +} + bool getAttributeBounds(const std::vector& meshes, cgltf_attribute_type type, Attr& min, Attr& max) { min.f[0] = min.f[1] = min.f[2] = min.f[3] = +FLT_MAX; @@ -2070,9 +2152,6 @@ void markNeededNodes(cgltf_data* data, std::vector& nodes, const std:: { const Mesh& mesh = meshes[i]; - if (mesh.indices.empty()) - continue; - if (mesh.node) { NodeInfo& ni = nodes[mesh.node - data->nodes]; @@ -2114,9 +2193,6 @@ void markNeededMaterials(cgltf_data* data, std::vector& materials, { const Mesh& mesh = meshes[i]; - if (mesh.indices.empty()) - continue; - if (mesh.material) { MaterialInfo& mi = materials[mesh.material - data->materials]; @@ -2781,11 +2857,20 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin { Mesh& mesh = meshes[i]; - if (mesh.indices.empty()) - continue; + switch (mesh.type) + { + case cgltf_primitive_type_points: + sortPointMesh(mesh); + break; - reindexMesh(mesh); - optimizeMesh(mesh); + case cgltf_primitive_type_triangles: + reindexMesh(mesh); + optimizeMesh(mesh); + break; + + default: + assert(!"Unknown primitive type"); + } } if (settings.verbose) @@ -2904,9 +2989,6 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin { const Mesh& mesh = meshes[i]; - if (mesh.indices.empty()) - continue; - comma(json_meshes); append(json_meshes, "{\"primitives\":["); @@ -2915,9 +2997,6 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin { const Mesh& prim = meshes[pi]; - if (prim.indices.empty()) - continue; - if (prim.node != mesh.node || prim.skin != mesh.skin || prim.targets != mesh.targets) break; @@ -2928,6 +3007,8 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin append(json_meshes, "{\"attributes\":{"); writeMeshAttributes(json_meshes, views, json_accessors, accr_offset, prim, 0, qp, settings); append(json_meshes, "}"); + append(json_meshes, ",\"mode\":"); + append(json_meshes, size_t(prim.type)); if (mesh.targets) { @@ -2942,10 +3023,14 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin append(json_meshes, "]"); } - size_t index_accr = writeMeshIndices(views, json_accessors, accr_offset, prim, settings); + if (!prim.indices.empty()) + { + size_t index_accr = writeMeshIndices(views, json_accessors, accr_offset, prim, settings); + + append(json_meshes, ",\"indices\":"); + append(json_meshes, index_accr); + } - append(json_meshes, ",\"indices\":"); - append(json_meshes, index_accr); if (prim.material) { MaterialInfo& mi = materials[prim.material - data->materials]; @@ -2954,6 +3039,7 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin append(json_meshes, ",\"material\":"); append(json_meshes, size_t(mi.remap)); } + append(json_meshes, "}"); } @@ -3226,12 +3312,7 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin if (settings.verbose) { - size_t primitives = 0; - - for (size_t i = 0; i < meshes.size(); ++i) - primitives += !meshes[i].indices.empty(); - - printf("output: %d nodes, %d meshes (%d primitives), %d materials\n", int(node_offset), int(mesh_offset), int(primitives), int(material_offset)); + printf("output: %d nodes, %d meshes (%d primitives), %d materials\n", int(node_offset), int(mesh_offset), int(meshes.size()), int(material_offset)); printf("output: JSON %d bytes, buffers %d bytes\n", int(json.size()), int(bin.size())); printf("output: buffers: vertex %d bytes, index %d bytes, skin %d bytes, time %d bytes, keyframe %d bytes, image %d bytes\n", int(bytes[BufferView::Kind_Vertex]), int(bytes[BufferView::Kind_Index]), int(bytes[BufferView::Kind_Skin]),