diff --git a/3rdparty/meshoptimizer/.github/workflows/build.yml b/3rdparty/meshoptimizer/.github/workflows/build.yml
new file mode 100644
index 000000000..37bc09b6f
--- /dev/null
+++ b/3rdparty/meshoptimizer/.github/workflows/build.yml
@@ -0,0 +1,63 @@
+name: build
+
+on: [push, pull_request]
+
+jobs:
+ linux:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ - name: make test
+ run: |
+ make -j2 config=sanitize test
+ make -j2 config=debug test
+ make -j2 config=release test
+ make -j2 config=release gltfpack
+ - name: make coverage
+ run: |
+ make -j2 config=coverage test
+ find . -type f -name '*.gcno' -exec gcov -p {} +
+ sed -i -e "s/#####\(.*\)\(\/\/ unreachable.*\)/ -\1\2/" *.gcov
+ bash <(curl -s https://codecov.io/bash) -f 'src#*.gcov' -X search -t ${{secrets.CODECOV_TOKEN}}
+ - uses: actions/upload-artifact@v1
+ with:
+ name: gltfpack-linux
+ path: gltfpack
+
+ macos:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v1
+ - name: make test
+ run: |
+ make -j2 config=sanitize test
+ make -j2 config=debug test
+ make -j2 config=release test
+ make -j2 config=release gltfpack
+ - name: make iphone
+ run: make -j2 config=iphone
+ - uses: actions/upload-artifact@v1
+ with:
+ name: gltfpack-macos
+ path: gltfpack
+
+ windows:
+ runs-on: windows-latest
+ strategy:
+ matrix:
+ arch: [Win32, x64]
+ steps:
+ - uses: actions/checkout@v1
+ - name: cmake configure
+ run: cmake . -DBUILD_DEMO=ON -DBUILD_TOOLS=ON -A ${{matrix.arch}}
+ - name: cmake test
+ shell: bash # necessary for fail-fast
+ run: |
+ cmake --build . -- -property:Configuration=Debug -verbosity:minimal
+ Debug/demo.exe demo/pirate.obj
+ cmake --build . -- -property:Configuration=Release -verbosity:minimal
+ Release/demo.exe demo/pirate.obj
+ - uses: actions/upload-artifact@v1
+ with:
+ name: gltfpack-windows
+ path: Release/gltfpack.exe
diff --git a/3rdparty/meshoptimizer/.travis.yml b/3rdparty/meshoptimizer/.travis.yml
index 8ba189cbc..8ffa5308f 100644
--- a/3rdparty/meshoptimizer/.travis.yml
+++ b/3rdparty/meshoptimizer/.travis.yml
@@ -18,17 +18,17 @@ matrix:
- TARGET="Visual Studio 15 2017 Win64"
script:
- - if [[ "$TRAVIS_COMPILER" == "gcc" ]]; then make config=coverage test; fi
- - if [[ "$TRAVIS_COMPILER" == "clang" ]]; then make config=sanitize test; fi
- - if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make config=debug test; fi
- - if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make config=release test; fi
- - if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make config=release gltfpack; fi
+ - if [[ "$TRAVIS_COMPILER" == "gcc" ]]; then make -j2 config=coverage test; fi
+ - if [[ "$TRAVIS_COMPILER" == "clang" ]]; then make -j2 config=sanitize test; fi
+ - if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make -j2 config=debug test; fi
+ - if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make -j2 config=release test; fi
+ - if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make -j2 config=release gltfpack; fi
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake -G "$TARGET" -DBUILD_DEMO=ON -DBUILD_TOOLS=ON; fi
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake --build . -- -property:Configuration=Debug -verbosity:minimal; fi
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then ./Debug/demo.exe demo/pirate.obj; fi
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake --build . -- -property:Configuration=Release -verbosity:minimal; fi
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then ./Release/demo.exe demo/pirate.obj; fi
- - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then make config=iphone; fi
+ - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then make -j2 config=iphone; fi
after_script:
- if [[ "$TRAVIS_COMPILER" == "gcc" ]]; then
diff --git a/3rdparty/meshoptimizer/CMakeLists.txt b/3rdparty/meshoptimizer/CMakeLists.txt
index b59baa9be..f57793a3f 100644
--- a/3rdparty/meshoptimizer/CMakeLists.txt
+++ b/3rdparty/meshoptimizer/CMakeLists.txt
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.0)
-project(meshoptimizer VERSION 0.12)
+project(meshoptimizer VERSION 0.12 LANGUAGES CXX)
option(BUILD_DEMO "Build demo" OFF)
option(BUILD_TOOLS "Build tools" OFF)
diff --git a/3rdparty/meshoptimizer/Makefile b/3rdparty/meshoptimizer/Makefile
index c9391c527..86a50f798 100644
--- a/3rdparty/meshoptimizer/Makefile
+++ b/3rdparty/meshoptimizer/Makefile
@@ -28,7 +28,7 @@ ifeq ($(config),iphone)
IPHONESDK=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk
CFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK)
CXXFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) -stdlib=libc++
- LDFLAGS+=-arch armv7 -arch arm64 -L $(IPHONESDK)/usr/lib -mios-version-min=7.0
+ LDFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) -L $(IPHONESDK)/usr/lib -mios-version-min=7.0
endif
ifeq ($(config),trace)
diff --git a/3rdparty/meshoptimizer/README.md b/3rdparty/meshoptimizer/README.md
index ea3301b0f..c63dc9212 100644
--- a/3rdparty/meshoptimizer/README.md
+++ b/3rdparty/meshoptimizer/README.md
@@ -1,4 +1,4 @@
-# meshoptimizer [](https://travis-ci.org/zeux/meshoptimizer) [](https://codecov.io/github/zeux/meshoptimizer?branch=master)  [](https://github.com/zeux/meshoptimizer)
+# meshoptimizer [](https://github.com/zeux/meshoptimizer/actions) [](https://travis-ci.org/zeux/meshoptimizer) [](https://codecov.io/github/zeux/meshoptimizer?branch=master)  [](https://github.com/zeux/meshoptimizer)
## Purpose
@@ -288,7 +288,7 @@ You can then run the resulting command-line binary like this (run it without arg
gltfpack -i scene.gltf -o scene.glb
```
-gltfpack substantially changes the glTF data by optimizing the meshes for vertex fetch and transform cache, quantizing the geometry to reduce the memory consumption and size, merging meshes to reduce the draw call count, quantizing and resampling animations to reduce animation size and simplify playback, and pruning the node tree by removing or collapsing redundant nodes.
+gltfpack substantially changes the glTF data by optimizing the meshes for vertex fetch and transform cache, quantizing the geometry to reduce the memory consumption and size, merging meshes to reduce the draw call count, quantizing and resampling animations to reduce animation size and simplify playback, and pruning the node tree by removing or collapsing redundant nodes. It will also simplify the meshes when requested to do so.
gltfpack can produce two types of output files:
diff --git a/3rdparty/meshoptimizer/demo/GLTFLoader.js b/3rdparty/meshoptimizer/demo/GLTFLoader.js
index 5fffcb06a..6a54452a8 100644
--- a/3rdparty/meshoptimizer/demo/GLTFLoader.js
+++ b/3rdparty/meshoptimizer/demo/GLTFLoader.js
@@ -2270,6 +2270,7 @@ THREE.GLTFLoader = ( function () {
pointsMaterial.color.copy( material.color );
pointsMaterial.map = material.map;
pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet
+ pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px
this.cache.add( cacheKey, pointsMaterial );
diff --git a/3rdparty/meshoptimizer/demo/babylon.MESHOPT_compression.js b/3rdparty/meshoptimizer/demo/babylon.MESHOPT_compression.js
new file mode 100644
index 000000000..0bb48cc7b
--- /dev/null
+++ b/3rdparty/meshoptimizer/demo/babylon.MESHOPT_compression.js
@@ -0,0 +1,50 @@
+/* Babylon.js extension for MESHOPT_compression; requires Babylon.js 4.1 */
+var NAME = "MESHOPT_compression";
+var MESHOPT_compression = /** @class */ (function () {
+ /** @hidden */
+ function MESHOPT_compression(loader, decoder) {
+ /** The name of this extension. */
+ this.name = NAME;
+ /** Defines whether this extension is enabled. */
+ this.enabled = true;
+ this._loader = loader;
+ this._decoder = decoder;
+ }
+ /** @hidden */
+ MESHOPT_compression.prototype.dispose = function () {
+ delete this._loader;
+ };
+ /** @hidden */
+ MESHOPT_compression.prototype.loadBufferViewAsync = function (context, bufferView) {
+ if (bufferView.extensions && bufferView.extensions[NAME]) {
+ if (bufferView._decoded) {
+ return bufferView._decoded;
+ }
+ var view = this._loader.loadBufferViewAsync(context, bufferView);
+ var decoder = this._decoder;
+ bufferView._decoded = Promise.all([view, decoder.ready]).then(function (res) {
+ var source = res[0];
+ var extensionDef = bufferView.extensions[NAME];
+ var count = extensionDef.count;
+ var stride = extensionDef.byteStride;
+ var result = new Uint8Array(new ArrayBuffer(count * stride));
+ switch (extensionDef.mode) {
+ case 0:
+ decoder.decodeVertexBuffer(result, count, stride, source);
+ break;
+ case 1:
+ decoder.decodeIndexBuffer(result, count, stride, source);
+ break;
+ default:
+ throw new Error("GLTFLoader: Unrecognized meshopt compression mode.");
+ }
+ return Promise.resolve(result);
+ });
+ return bufferView._decoded;
+ } else {
+ return null;
+ }
+ };
+ return MESHOPT_compression;
+}());
+/* BABYLON.GLTF2.GLTFLoader.RegisterExtension("MESHOPT_compression", (loader) => new MESHOPT_compression(loader, MeshoptDecoder)); */
diff --git a/3rdparty/meshoptimizer/demo/demo.html b/3rdparty/meshoptimizer/demo/demo.html
new file mode 100644
index 000000000..d7c4bf302
--- /dev/null
+++ b/3rdparty/meshoptimizer/demo/demo.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+ meshoptimizer - demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/3rdparty/meshoptimizer/demo/main.cpp b/3rdparty/meshoptimizer/demo/main.cpp
index d4d75e30f..9b3bd0c5c 100644
--- a/3rdparty/meshoptimizer/demo/main.cpp
+++ b/3rdparty/meshoptimizer/demo/main.cpp
@@ -411,6 +411,22 @@ void simplifySloppy(const Mesh& mesh, float threshold = 0.2f)
int(mesh.indices.size() / 3), int(lod.indices.size() / 3), (end - start) * 1000);
}
+void simplifyPoints(const Mesh& mesh, float threshold = 0.2f)
+{
+ double start = timestamp();
+
+ size_t target_vertex_count = size_t(mesh.vertices.size() * threshold);
+
+ std::vector indices(target_vertex_count);
+ indices.resize(meshopt_simplifyPoints(&indices[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_vertex_count));
+
+ double end = timestamp();
+
+ printf("%-9s: %d points => %d points in %.2f msec\n",
+ "SimplifyP",
+ int(mesh.vertices.size()), int(indices.size()), (end - start) * 1000);
+}
+
void simplifyComplete(const Mesh& mesh)
{
static const size_t lod_count = 5;
@@ -985,6 +1001,7 @@ void process(const char* path)
simplify(mesh);
simplifySloppy(mesh);
simplifyComplete(mesh);
+ simplifyPoints(mesh);
spatialSort(mesh);
spatialSortTriangles(mesh);
@@ -999,15 +1016,7 @@ void processDev(const char* path)
if (!loadMesh(mesh, path))
return;
- 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);
+ simplifyPoints(mesh);
}
int main(int argc, char** argv)
diff --git a/3rdparty/meshoptimizer/src/meshoptimizer.h b/3rdparty/meshoptimizer/src/meshoptimizer.h
index 711f9c782..daadee4bc 100644
--- a/3rdparty/meshoptimizer/src/meshoptimizer.h
+++ b/3rdparty/meshoptimizer/src/meshoptimizer.h
@@ -215,6 +215,18 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplify(unsigned int* destination, co
*/
MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count);
+/**
+ * Experimental: Point cloud simplifier
+ * Reduces the number of points in the cloud to reach the given target
+ * Returns the number of points after simplification, with destination containing new index data
+ * The resulting index buffer references vertices from the original vertex buffer.
+ * If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.
+ *
+ * destination must contain enough space for the target index buffer
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
+ */
+MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_vertex_count);
+
/**
* Mesh stripifier
* Converts a previously vertex cache optimized triangle list to triangle strip, stitching strips using restart index or degenerate triangles
diff --git a/3rdparty/meshoptimizer/src/simplifier.cpp b/3rdparty/meshoptimizer/src/simplifier.cpp
index 8f5893460..400c9ccde 100644
--- a/3rdparty/meshoptimizer/src/simplifier.cpp
+++ b/3rdparty/meshoptimizer/src/simplifier.cpp
@@ -505,6 +505,22 @@ static void quadricFromPlane(Quadric& Q, float a, float b, float c, float d, flo
Q.w = w;
}
+static void quadricFromPoint(Quadric& Q, float x, float y, float z, float w)
+{
+ // we need to encode (x - X) ^ 2 + (y - Y)^2 + (z - Z)^2 into the quadric
+ Q.a00 = w;
+ Q.a11 = w;
+ Q.a22 = w;
+ Q.a10 = 0.f;
+ Q.a20 = 0.f;
+ Q.a21 = 0.f;
+ Q.b0 = -2.f * x * w;
+ Q.b1 = -2.f * y * w;
+ Q.b2 = -2.f * z * w;
+ Q.c = (x * x + y * y + z * z) * w;
+ Q.w = w;
+}
+
static void quadricFromTriangle(Quadric& Q, const Vector3& p0, const Vector3& p1, const Vector3& p2, float weight)
{
Vector3 p10 = {p1.x - p0.x, p1.y - p0.y, p1.z - p0.z};
@@ -909,6 +925,25 @@ struct CellHasher
}
};
+struct IdHasher
+{
+ size_t hash(unsigned int id) const
+ {
+ unsigned int h = id;
+
+ // MurmurHash2 finalizer
+ h ^= h >> 13;
+ h *= 0x5bd1e995;
+ h ^= h >> 15;
+ return h;
+ }
+
+ bool equal(unsigned int lhs, unsigned int rhs) const
+ {
+ return lhs == rhs;
+ }
+};
+
struct TriangleHasher
{
unsigned int* indices;
@@ -989,6 +1024,26 @@ static size_t fillVertexCells(unsigned int* table, size_t table_size, unsigned i
return result;
}
+static size_t countVertexCells(unsigned int* table, size_t table_size, const unsigned int* vertex_ids, size_t vertex_count)
+{
+ IdHasher hasher;
+
+ memset(table, -1, table_size * sizeof(unsigned int));
+
+ size_t result = 0;
+
+ for (size_t i = 0; i < vertex_count; ++i)
+ {
+ unsigned int id = vertex_ids[i];
+ unsigned int* entry = hashLookup2(table, table_size, hasher, id, ~0u);
+
+ result += (*entry == ~0u);
+ *entry = id;
+ }
+
+ return result;
+}
+
static void fillCellQuadrics(Quadric* cell_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* vertex_cells)
{
for (size_t i = 0; i < index_count; i += 3)
@@ -1019,6 +1074,20 @@ static void fillCellQuadrics(Quadric* cell_quadrics, const unsigned int* indices
}
}
+static void fillCellQuadrics(Quadric* cell_quadrics, const Vector3* vertex_positions, size_t vertex_count, const unsigned int* vertex_cells)
+{
+ for (size_t i = 0; i < vertex_count; ++i)
+ {
+ unsigned int c = vertex_cells[i];
+ const Vector3& v = vertex_positions[i];
+
+ Quadric Q;
+ quadricFromPoint(Q, v.x, v.y, v.z, 1.f);
+
+ quadricAdd(cell_quadrics[c], Q);
+ }
+}
+
static void fillCellRemap(unsigned int* cell_remap, float* cell_errors, size_t cell_count, const unsigned int* vertex_cells, const Quadric* cell_quadrics, const Vector3* vertex_positions, size_t vertex_count)
{
memset(cell_remap, -1, cell_count * sizeof(unsigned int));
@@ -1363,3 +1432,112 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind
return write;
}
+
+size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_vertex_count)
+{
+ using namespace meshopt;
+
+ assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+ assert(vertex_positions_stride % sizeof(float) == 0);
+ assert(target_vertex_count <= vertex_count);
+
+ size_t target_cell_count = target_vertex_count;
+
+ meshopt_Allocator allocator;
+
+ Vector3* vertex_positions = allocator.allocate(vertex_count);
+ rescalePositions(vertex_positions, vertex_positions_data, vertex_count, vertex_positions_stride);
+
+ // find the optimal grid size using guided binary search
+#if TRACE
+ printf("source: %d vertices\n", int(vertex_count));
+ printf("target: %d cells\n", int(target_cell_count));
+#endif
+
+ unsigned int* vertex_ids = allocator.allocate(vertex_count);
+
+ size_t table_size = hashBuckets2(vertex_count);
+ unsigned int* table = allocator.allocate(table_size);
+
+ const int kInterpolationPasses = 5;
+
+ // invariant: # of vertices in min_grid <= target_count
+ int min_grid = 0;
+ int max_grid = 1025;
+ size_t min_vertices = 0;
+ size_t max_vertices = vertex_count;
+
+ // instead of starting in the middle, let's guess as to what the answer might be! triangle count usually grows as a square of grid size...
+ int next_grid_size = int(sqrtf(float(target_cell_count)) + 0.5f);
+
+ for (int pass = 0; pass < 10 + kInterpolationPasses; ++pass)
+ {
+ assert(min_vertices < target_vertex_count);
+ assert(max_grid - min_grid > 1);
+
+ // we clamp the prediction of the grid size to make sure that the search converges
+ int grid_size = next_grid_size;
+ grid_size = (grid_size <= min_grid) ? min_grid + 1 : (grid_size >= max_grid) ? max_grid - 1 : grid_size;
+
+ computeVertexIds(vertex_ids, vertex_positions, vertex_count, grid_size);
+ size_t vertices = countVertexCells(table, table_size, vertex_ids, vertex_count);
+
+#if TRACE
+ printf("pass %d (%s): grid size %d, vertices %d, %s\n",
+ pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses) ? "lerp" : "binary",
+ grid_size, int(vertices),
+ (vertices <= target_vertex_count) ? "under" : "over");
+#endif
+
+ float tip = interpolate(float(target_vertex_count), float(min_grid), float(min_vertices), float(grid_size), float(vertices), float(max_grid), float(max_vertices));
+
+ if (vertices <= target_vertex_count)
+ {
+ min_grid = grid_size;
+ min_vertices = vertices;
+ }
+ else
+ {
+ max_grid = grid_size;
+ max_vertices = vertices;
+ }
+
+ if (vertices == target_vertex_count || max_grid - min_grid <= 1)
+ break;
+
+ // we start by using interpolation search - it usually converges faster
+ // however, interpolation search has a worst case of O(N) so we switch to binary search after a few iterations which converges in O(logN)
+ next_grid_size = (pass < kInterpolationPasses) ? int(tip + 0.5f) : (min_grid + max_grid) / 2;
+ }
+
+ if (min_vertices == 0)
+ return 0;
+
+ // build vertex->cell association by mapping all vertices with the same quantized position to the same cell
+ unsigned int* vertex_cells = allocator.allocate(vertex_count);
+
+ computeVertexIds(vertex_ids, vertex_positions, vertex_count, min_grid);
+ size_t cell_count = fillVertexCells(table, table_size, vertex_cells, vertex_ids, vertex_count);
+
+ // build a quadric for each target cell
+ Quadric* cell_quadrics = allocator.allocate(cell_count);
+ memset(cell_quadrics, 0, cell_count * sizeof(Quadric));
+
+ fillCellQuadrics(cell_quadrics, vertex_positions, vertex_count, vertex_cells);
+
+ // for each target cell, find the vertex with the minimal error
+ unsigned int* cell_remap = allocator.allocate(cell_count);
+ float* cell_errors = allocator.allocate(cell_count);
+
+ fillCellRemap(cell_remap, cell_errors, cell_count, vertex_cells, cell_quadrics, vertex_positions, vertex_count);
+
+ // copy results to the output
+ assert(cell_count <= target_vertex_count);
+ memcpy(destination, cell_remap, sizeof(unsigned int) * cell_count);
+
+#if TRACE
+ printf("result: %d cells\n", int(cell_count));
+#endif
+
+ return cell_count;
+}
diff --git a/3rdparty/meshoptimizer/tools/cgltf.h b/3rdparty/meshoptimizer/tools/cgltf.h
index c0775fcef..6b7ff41e2 100644
--- a/3rdparty/meshoptimizer/tools/cgltf.h
+++ b/3rdparty/meshoptimizer/tools/cgltf.h
@@ -373,6 +373,8 @@ typedef struct cgltf_mesh {
cgltf_size primitives_count;
cgltf_float* weights;
cgltf_size weights_count;
+ char** target_names;
+ cgltf_size target_names_count;
cgltf_extras extras;
} cgltf_mesh;
@@ -1171,6 +1173,14 @@ cgltf_result cgltf_validate(cgltf_data* data)
}
}
+ if (data->meshes[i].target_names)
+ {
+ if (data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].target_names_count)
+ {
+ return cgltf_result_invalid_gltf;
+ }
+ }
+
for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j)
{
if (data->meshes[i].primitives[j].targets_count != data->meshes[i].primitives[0].targets_count)
@@ -1321,6 +1331,13 @@ void cgltf_free(cgltf_data* data)
data->memory_free(data->memory_user_data, data->meshes[i].primitives);
data->memory_free(data->memory_user_data, data->meshes[i].weights);
+
+ for (cgltf_size j = 0; j < data->meshes[i].target_names_count; ++j)
+ {
+ data->memory_free(data->memory_user_data, data->meshes[i].target_names[j]);
+ }
+
+ data->memory_free(data->memory_user_data, data->meshes[i].target_names);
}
data->memory_free(data->memory_user_data, data->meshes);
@@ -1441,17 +1458,17 @@ void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix)
float sz = node->scale[2];
lm[0] = (1 - 2 * qy*qy - 2 * qz*qz) * sx;
- lm[1] = (2 * qx*qy + 2 * qz*qw) * sy;
- lm[2] = (2 * qx*qz - 2 * qy*qw) * sz;
+ lm[1] = (2 * qx*qy + 2 * qz*qw) * sx;
+ lm[2] = (2 * qx*qz - 2 * qy*qw) * sx;
lm[3] = 0.f;
- lm[4] = (2 * qx*qy - 2 * qz*qw) * sx;
+ lm[4] = (2 * qx*qy - 2 * qz*qw) * sy;
lm[5] = (1 - 2 * qx*qx - 2 * qz*qz) * sy;
- lm[6] = (2 * qy*qz + 2 * qx*qw) * sz;
+ lm[6] = (2 * qy*qz + 2 * qx*qw) * sy;
lm[7] = 0.f;
- lm[8] = (2 * qx*qz + 2 * qy*qw) * sx;
- lm[9] = (2 * qy*qz - 2 * qx*qw) * sy;
+ lm[8] = (2 * qx*qz + 2 * qy*qw) * sz;
+ lm[9] = (2 * qy*qz - 2 * qx*qw) * sz;
lm[10] = (1 - 2 * qx*qx - 2 * qy*qy) * sz;
lm[11] = 0.f;
@@ -1512,9 +1529,9 @@ static cgltf_size cgltf_component_read_index(const void* in, cgltf_component_typ
case cgltf_component_type_r_8:
return *((const int8_t*) in);
case cgltf_component_type_r_8u:
- case cgltf_component_type_invalid:
- default:
return *((const uint8_t*) in);
+ default:
+ return 0;
}
}
@@ -1529,18 +1546,17 @@ static cgltf_float cgltf_component_read_float(const void* in, cgltf_component_ty
{
switch (component_type)
{
- case cgltf_component_type_r_32u:
- return *((const uint32_t*) in) / (float) UINT_MAX;
+ // note: glTF spec doesn't currently define normalized conversions for 32-bit integers
case cgltf_component_type_r_16:
- return *((const int16_t*) in) / (float) SHRT_MAX;
+ return *((const int16_t*) in) / (cgltf_float)32767;
case cgltf_component_type_r_16u:
- return *((const uint16_t*) in) / (float) USHRT_MAX;
+ return *((const uint16_t*) in) / (cgltf_float)65535;
case cgltf_component_type_r_8:
- return *((const int8_t*) in) / (float) SCHAR_MAX;
+ return *((const int8_t*) in) / (cgltf_float)127;
case cgltf_component_type_r_8u:
- case cgltf_component_type_invalid:
+ return *((const uint8_t*) in) / (cgltf_float)255;
default:
- return *((const uint8_t*) in) / (float) CHAR_MAX;
+ return 0;
}
}
@@ -1996,7 +2012,39 @@ static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens
}
else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
{
- i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_mesh->extras);
+ ++i;
+
+ out_mesh->extras.start_offset = tokens[i].start;
+ out_mesh->extras.end_offset = tokens[i].end;
+
+ if (tokens[i].type == JSMN_OBJECT)
+ {
+ int extras_size = tokens[i].size;
+ ++i;
+
+ for (int k = 0; k < extras_size; ++k)
+ {
+ CGLTF_CHECK_KEY(tokens[i]);
+
+ if (cgltf_json_strcmp(tokens+i, json_chunk, "targetNames") == 0)
+ {
+ i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_mesh->target_names, &out_mesh->target_names_count);
+ }
+ else
+ {
+ i = cgltf_skip_json(tokens, i+1);
+ }
+
+ if (i < 0)
+ {
+ return i;
+ }
+ }
+ }
+ else
+ {
+ i = cgltf_skip_json(tokens, i);
+ }
}
else
{
diff --git a/3rdparty/meshoptimizer/tools/gltfpack.cpp b/3rdparty/meshoptimizer/tools/gltfpack.cpp
index cf295e1b7..aa6c49bb0 100644
--- a/3rdparty/meshoptimizer/tools/gltfpack.cpp
+++ b/3rdparty/meshoptimizer/tools/gltfpack.cpp
@@ -64,7 +64,8 @@ struct Mesh
std::vector indices;
size_t targets;
- std::vector weights;
+ std::vector target_weights;
+ std::vector target_names;
};
struct Settings
@@ -79,6 +80,9 @@ struct Settings
bool keep_named;
+ float simplify_threshold;
+ bool simplify_aggressive;
+
bool compress;
int verbose;
};
@@ -320,7 +324,8 @@ void parseMeshesGltf(cgltf_data* data, std::vector& meshes)
}
result.targets = primitive.targets_count;
- result.weights.assign(mesh.weights, mesh.weights + mesh.weights_count);
+ result.target_weights.assign(mesh.weights, mesh.weights + mesh.weights_count);
+ result.target_names.assign(mesh.target_names, mesh.target_names + mesh.target_names_count);
meshes.push_back(result);
}
@@ -627,6 +632,28 @@ void mergeMeshMaterials(cgltf_data* data, std::vector& meshes)
}
}
+bool compareMeshTargets(const Mesh& lhs, const Mesh& rhs)
+{
+ if (lhs.targets != rhs.targets)
+ return false;
+
+ if (lhs.target_weights.size() != rhs.target_weights.size())
+ return false;
+
+ for (size_t i = 0; i < lhs.target_weights.size(); ++i)
+ if (lhs.target_weights[i] != rhs.target_weights[i])
+ return false;
+
+ if (lhs.target_names.size() != rhs.target_names.size())
+ return false;
+
+ for (size_t i = 0; i < lhs.target_names.size(); ++i)
+ if (strcmp(lhs.target_names[i], rhs.target_names[i]) != 0)
+ return false;
+
+ return true;
+}
+
bool canMergeMeshes(const Mesh& lhs, const Mesh& rhs, const Settings& settings)
{
if (lhs.node != rhs.node)
@@ -665,16 +692,9 @@ bool canMergeMeshes(const Mesh& lhs, const Mesh& rhs, const Settings& settings)
if (lhs.type != rhs.type)
return false;
- if (lhs.targets != rhs.targets)
+ if (!compareMeshTargets(lhs, rhs))
return false;
- if (lhs.weights.size() != rhs.weights.size())
- return false;
-
- for (size_t i = 0; i < lhs.weights.size(); ++i)
- if (lhs.weights[i] != rhs.weights[i])
- return false;
-
if (lhs.indices.empty() != rhs.indices.empty())
return false;
@@ -810,6 +830,38 @@ const Stream* getPositionStream(const Mesh& mesh)
return 0;
}
+void simplifyMesh(Mesh& mesh, float threshold, bool aggressive)
+{
+ if (threshold >= 1)
+ return;
+
+ const Stream* positions = getPositionStream(mesh);
+ if (!positions)
+ return;
+
+ size_t vertex_count = mesh.streams[0].data.size();
+
+ size_t target_index_count = size_t(double(mesh.indices.size() / 3) * threshold) * 3;
+ float target_error = 1e-2f;
+
+ if (target_index_count < 1)
+ return;
+
+ std::vector indices(mesh.indices.size());
+ indices.resize(meshopt_simplify(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr), target_index_count, target_error));
+ mesh.indices.swap(indices);
+
+ // Note: if the simplifier got stuck, we can try to reindex without normals/tangents and retry
+ // For now we simply fall back to aggressive simplifier instead
+
+ // if the mesh is complex enough and the precise simplifier got "stuck", we'll try to simplify using the sloppy simplifier which is guaranteed to reach the target count
+ if (aggressive && target_index_count > 50 * 3 && mesh.indices.size() > target_index_count)
+ {
+ indices.resize(meshopt_simplifySloppy(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr), target_index_count));
+ mesh.indices.swap(indices);
+ }
+}
+
void optimizeMesh(Mesh& mesh)
{
size_t vertex_count = mesh.streams[0].data.size();
@@ -818,9 +870,7 @@ void optimizeMesh(Mesh& mesh)
std::vector remap(vertex_count);
size_t unique_vertices = meshopt_optimizeVertexFetchRemap(&remap[0], &mesh.indices[0], mesh.indices.size(), vertex_count);
-
- assert(unique_vertices == vertex_count);
- (void)unique_vertices;
+ assert(unique_vertices <= vertex_count);
meshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), &remap[0]);
@@ -829,6 +879,41 @@ void optimizeMesh(Mesh& mesh)
assert(mesh.streams[i].data.size() == vertex_count);
meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], vertex_count, sizeof(Attr), &remap[0]);
+ mesh.streams[i].data.resize(unique_vertices);
+ }
+}
+
+void simplifyPointMesh(Mesh& mesh, float threshold)
+{
+ if (threshold >= 1)
+ return;
+
+ const Stream* positions = getPositionStream(mesh);
+ if (!positions)
+ return;
+
+ size_t vertex_count = mesh.streams[0].data.size();
+
+ size_t target_vertex_count = size_t(double(vertex_count) * threshold);
+
+ if (target_vertex_count < 1)
+ return;
+
+ std::vector indices(target_vertex_count);
+ indices.resize(meshopt_simplifyPoints(&indices[0], positions->data[0].f, vertex_count, sizeof(Attr), target_vertex_count));
+
+ std::vector scratch(indices.size());
+
+ for (size_t i = 0; i < mesh.streams.size(); ++i)
+ {
+ std::vector& data = mesh.streams[i].data;
+
+ assert(data.size() == vertex_count);
+
+ for (size_t j = 0; j < indices.size(); ++j)
+ scratch[j] = data[indices[j]];
+
+ data = scratch;
}
}
@@ -838,8 +923,6 @@ void sortPointMesh(Mesh& mesh)
if (!positions)
return;
- assert(mesh.indices.empty());
-
size_t vertex_count = mesh.streams[0].data.size();
std::vector remap(vertex_count);
@@ -2813,7 +2896,23 @@ void writeLight(std::string& json, const cgltf_light& light)
append(json, "}");
}
-void printStats(const std::vector& views, BufferView::Kind kind, const char* name)
+void printMeshStats(const std::vector& meshes, const char* name)
+{
+ size_t triangles = 0;
+ size_t vertices = 0;
+
+ for (size_t i = 0; i < meshes.size(); ++i)
+ {
+ const Mesh& mesh = meshes[i];
+
+ triangles += mesh.indices.size() / 3;
+ vertices += mesh.streams.empty() ? 0 : mesh.streams[0].data.size();
+ }
+
+ printf("%s: %d triangles, %d vertices\n", name, int(triangles), int(vertices));
+}
+
+void printAttributeStats(const std::vector& views, BufferView::Kind kind, const char* name)
{
for (size_t i = 0; i < views.size(); ++i)
{
@@ -2897,6 +2996,11 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin
markNeededMaterials(data, materials, meshes);
+ if (settings.verbose)
+ {
+ printMeshStats(meshes, "input");
+ }
+
for (size_t i = 0; i < meshes.size(); ++i)
{
Mesh& mesh = meshes[i];
@@ -2904,11 +3008,14 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin
switch (mesh.type)
{
case cgltf_primitive_type_points:
+ assert(mesh.indices.empty());
+ simplifyPointMesh(mesh, settings.simplify_threshold);
sortPointMesh(mesh);
break;
case cgltf_primitive_type_triangles:
reindexMesh(mesh);
+ simplifyMesh(mesh, settings.simplify_threshold, settings.simplify_aggressive);
optimizeMesh(mesh);
break;
@@ -2919,18 +3026,7 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin
if (settings.verbose)
{
- size_t triangles = 0;
- size_t vertices = 0;
-
- for (size_t i = 0; i < meshes.size(); ++i)
- {
- const Mesh& mesh = meshes[i];
-
- triangles += mesh.indices.size() / 3;
- vertices += mesh.streams.empty() ? 0 : mesh.streams[0].data.size();
- }
-
- printf("meshes: %d triangles, %d vertices\n", int(triangles), int(vertices));
+ printMeshStats(meshes, "output");
}
QuantizationParams qp = prepareQuantization(meshes, settings);
@@ -3044,7 +3140,7 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin
if (prim.node != mesh.node || prim.skin != mesh.skin || prim.targets != mesh.targets)
break;
- if (mesh.weights.size() && (prim.weights.size() != mesh.weights.size() || memcmp(&mesh.weights[0], &prim.weights[0], mesh.weights.size() * sizeof(float)) != 0))
+ if (!compareMeshTargets(mesh, prim))
break;
comma(json_meshes);
@@ -3089,16 +3185,30 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin
append(json_meshes, "]");
- if (mesh.weights.size())
+ if (mesh.target_weights.size())
{
append(json_meshes, ",\"weights\":[");
- for (size_t j = 0; j < mesh.weights.size(); ++j)
+ for (size_t j = 0; j < mesh.target_weights.size(); ++j)
{
comma(json_meshes);
- append(json_meshes, mesh.weights[j]);
+ append(json_meshes, mesh.target_weights[j]);
}
append(json_meshes, "]");
}
+
+ if (mesh.target_names.size())
+ {
+ append(json_meshes, ",\"extras\":{\"targetNames\":[");
+ for (size_t j = 0; j < mesh.target_names.size(); ++j)
+ {
+ comma(json_meshes);
+ append(json_meshes, "\"");
+ append(json_meshes, mesh.target_names[j]);
+ append(json_meshes, "\"");
+ }
+ append(json_meshes, "]}");
+ }
+
append(json_meshes, "}");
writeMeshNode(json_nodes, mesh_offset, mesh, data, qp);
@@ -3365,9 +3475,9 @@ void process(cgltf_data* data, std::vector& meshes, const Settings& settin
if (settings.verbose > 1)
{
- printStats(views, BufferView::Kind_Vertex, "vertex");
- printStats(views, BufferView::Kind_Index, "index");
- printStats(views, BufferView::Kind_Keyframe, "keyframe");
+ printAttributeStats(views, BufferView::Kind_Vertex, "vertex");
+ printAttributeStats(views, BufferView::Kind_Index, "index");
+ printAttributeStats(views, BufferView::Kind_Keyframe, "keyframe");
}
}
@@ -3529,6 +3639,7 @@ int main(int argc, char** argv)
settings.tex_bits = 12;
settings.nrm_bits = 8;
settings.anim_freq = 30;
+ settings.simplify_threshold = 1.f;
const char* input = 0;
const char* output = 0;
@@ -3567,6 +3678,14 @@ int main(int argc, char** argv)
{
settings.keep_named = true;
}
+ else if (strcmp(arg, "-si") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))
+ {
+ settings.simplify_threshold = float(atof(argv[++i]));
+ }
+ else if (strcmp(arg, "-sa") == 0)
+ {
+ settings.simplify_aggressive = true;
+ }
else if (strcmp(arg, "-i") == 0 && i + 1 < argc && !input)
{
input = argv[++i];
@@ -3628,6 +3747,8 @@ int main(int argc, char** argv)
fprintf(stderr, "-af N: resample animations at N Hz (default: 30)\n");
fprintf(stderr, "-ac: keep constant animation tracks even if they don't modify the node transform\n");
fprintf(stderr, "-kn: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally\n");
+ fprintf(stderr, "-si R: simplify meshes to achieve the ratio R (default: 1; R should be between 0 and 1)\n");
+ fprintf(stderr, "-sa: aggressively simplify to the target ratio disregarding quality\n");
fprintf(stderr, "-c: produce compressed glb files\n");
fprintf(stderr, "-v: verbose output\n");
fprintf(stderr, "-h: display this help and exit\n");