Updated meshoptimizer.

This commit is contained in:
Бранимир Караџић
2019-10-05 10:19:58 -07:00
parent ecd4c00363
commit 0c5c2fcdcf
13 changed files with 622 additions and 70 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -1,4 +1,4 @@
# meshoptimizer [![Build Status](https://travis-ci.org/zeux/meshoptimizer.svg?branch=master)](https://travis-ci.org/zeux/meshoptimizer) [![codecov.io](https://codecov.io/github/zeux/meshoptimizer/coverage.svg?branch=master)](https://codecov.io/github/zeux/meshoptimizer?branch=master) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![GitHub](https://img.shields.io/badge/repo-github-green.svg)](https://github.com/zeux/meshoptimizer)
# meshoptimizer [![Actions Status](https://github.com/zeux/meshoptimizer/workflows/build/badge.svg)](https://github.com/zeux/meshoptimizer/actions) [![Build Status](https://travis-ci.org/zeux/meshoptimizer.svg?branch=master)](https://travis-ci.org/zeux/meshoptimizer) [![codecov.io](https://codecov.io/github/zeux/meshoptimizer/coverage.svg?branch=master)](https://codecov.io/github/zeux/meshoptimizer?branch=master) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![GitHub](https://img.shields.io/badge/repo-github-green.svg)](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:

View File

@@ -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 );

View File

@@ -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)); */

70
3rdparty/meshoptimizer/demo/demo.html vendored Normal file
View File

@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>meshoptimizer - demo</title>
<script src="https://preview.babylonjs.com/babylon.js"></script>
<script src="https://preview.babylonjs.com/loaders/babylon.glTF2FileLoader.js"></script>
<script src="babylon.MESHOPT_compression.js"></script>
<script src="../js/meshopt_decoder.js"></script>
<style>
html, body {
overflow: hidden;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#renderCanvas {
width: 100%;
height: 100%;
touch-action: none;
}
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script>
var canvas = document.getElementById("renderCanvas");
BABYLON.GLTF2.GLTFLoader.RegisterExtension("MESHOPT_compression", (loader) => new MESHOPT_compression(loader, MeshoptDecoder));
var createScene = function () {
var scene = new BABYLON.Scene(engine);
var light = new BABYLON.HemisphericLight();
var camera = new BABYLON.ArcRotateCamera("Camera", 0, 0.8, 10, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, false);
BABYLON.SceneLoader.Append("", "pirate.glb", scene, function (newMeshes) {
scene.activeCamera = null;
scene.createDefaultCameraOrLight(true);
scene.activeCamera.attachControl(canvas, false);
scene.activeCamera.alpha = Math.PI / 2;
});
return scene;
}
var engine = new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true });
var scene = createScene();
engine.runRenderLoop(function () {
if (scene) {
scene.render();
}
});
// Resize
window.addEventListener("resize", function () {
engine.resize();
});
</script>
</body>
</html>

View File

@@ -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<unsigned int> 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(&copy.indices[0], &copy.indices[0], copy.indices.size(), copy.vertices.size());
meshopt_optimizeVertexFetch(&copy.vertices[0], &copy.indices[0], copy.indices.size(), &copy.vertices[0], copy.vertices.size(), sizeof(Vertex));
encodeIndex(copy);
encodeVertex<PackedVertexOct>(copy, "O");
spatialSort(mesh);
spatialSortTriangles(mesh);
simplifyPoints(mesh);
}
int main(int argc, char** argv)

View File

@@ -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

View File

@@ -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<Vector3>(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<unsigned int>(vertex_count);
size_t table_size = hashBuckets2(vertex_count);
unsigned int* table = allocator.allocate<unsigned int>(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<unsigned int>(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<Quadric>(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<unsigned int>(cell_count);
float* cell_errors = allocator.allocate<float>(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;
}

View File

@@ -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
{

View File

@@ -64,7 +64,8 @@ struct Mesh
std::vector<unsigned int> indices;
size_t targets;
std::vector<float> weights;
std::vector<float> target_weights;
std::vector<const char*> 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<Mesh>& 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<Mesh>& 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<unsigned int> 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<unsigned int> 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<unsigned int> indices(target_vertex_count);
indices.resize(meshopt_simplifyPoints(&indices[0], positions->data[0].f, vertex_count, sizeof(Attr), target_vertex_count));
std::vector<Attr> scratch(indices.size());
for (size_t i = 0; i < mesh.streams.size(); ++i)
{
std::vector<Attr>& 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<unsigned int> remap(vertex_count);
@@ -2813,7 +2896,23 @@ void writeLight(std::string& json, const cgltf_light& light)
append(json, "}");
}
void printStats(const std::vector<BufferView>& views, BufferView::Kind kind, const char* name)
void printMeshStats(const std::vector<Mesh>& 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<BufferView>& 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<Mesh>& 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<Mesh>& 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<Mesh>& 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<Mesh>& 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<Mesh>& 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<Mesh>& 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");