Updated meshoptimizer.
This commit is contained in:
parent
28187d367f
commit
e8422851c5
5
3rdparty/meshoptimizer/.travis.yml
vendored
5
3rdparty/meshoptimizer/.travis.yml
vendored
@ -22,7 +22,8 @@ script:
|
||||
- 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 cmake -G "$TARGET" -DBUILD_DEMO=ON; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make 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
|
||||
@ -34,4 +35,4 @@ after_script:
|
||||
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;
|
||||
fi
|
||||
fi
|
||||
|
7
3rdparty/meshoptimizer/CMakeLists.txt
vendored
7
3rdparty/meshoptimizer/CMakeLists.txt
vendored
@ -6,6 +6,7 @@ option(BUILD_TOOLS "Build tools" OFF)
|
||||
|
||||
set(SOURCES
|
||||
src/meshoptimizer.h
|
||||
src/allocator.cpp
|
||||
src/clusterizer.cpp
|
||||
src/indexcodec.cpp
|
||||
src/indexgenerator.cpp
|
||||
@ -30,11 +31,11 @@ else()
|
||||
endif()
|
||||
|
||||
if(BUILD_DEMO)
|
||||
add_executable(demo demo/main.cpp demo/miniz.cpp demo/tests.cpp tools/objparser.cpp)
|
||||
add_executable(demo demo/main.cpp demo/miniz.cpp demo/tests.cpp tools/meshloader.cpp)
|
||||
target_link_libraries(demo meshoptimizer)
|
||||
endif()
|
||||
|
||||
if(BUILD_TOOLS)
|
||||
add_executable(meshencoder tools/meshencoder.cpp tools/objparser.cpp)
|
||||
target_link_libraries(meshencoder meshoptimizer)
|
||||
add_executable(gltfpack tools/gltfpack.cpp tools/meshloader.cpp)
|
||||
target_link_libraries(gltfpack meshoptimizer)
|
||||
endif()
|
||||
|
18
3rdparty/meshoptimizer/Makefile
vendored
18
3rdparty/meshoptimizer/Makefile
vendored
@ -9,13 +9,13 @@ BUILD=build/$(config)
|
||||
LIBRARY_SOURCES=$(wildcard src/*.cpp)
|
||||
LIBRARY_OBJECTS=$(LIBRARY_SOURCES:%=$(BUILD)/%.o)
|
||||
|
||||
DEMO_SOURCES=$(wildcard demo/*.c demo/*.cpp) tools/objparser.cpp
|
||||
DEMO_SOURCES=$(wildcard demo/*.c demo/*.cpp) tools/meshloader.cpp
|
||||
DEMO_OBJECTS=$(DEMO_SOURCES:%=$(BUILD)/%.o)
|
||||
|
||||
ENCODER_SOURCES=tools/meshencoder.cpp tools/objparser.cpp
|
||||
ENCODER_OBJECTS=$(ENCODER_SOURCES:%=$(BUILD)/%.o)
|
||||
GLTFPACK_SOURCES=tools/gltfpack.cpp tools/meshloader.cpp
|
||||
GLTFPACK_OBJECTS=$(GLTFPACK_SOURCES:%=$(BUILD)/%.o)
|
||||
|
||||
OBJECTS=$(LIBRARY_OBJECTS) $(DEMO_OBJECTS) $(ENCODER_OBJECTS)
|
||||
OBJECTS=$(LIBRARY_OBJECTS) $(DEMO_OBJECTS) $(GLTFPACK_OBJECTS)
|
||||
|
||||
LIBRARY=$(BUILD)/libmeshoptimizer.a
|
||||
EXECUTABLE=$(BUILD)/meshoptimizer
|
||||
@ -65,15 +65,15 @@ dev: $(EXECUTABLE)
|
||||
$(EXECUTABLE) -d $(files)
|
||||
|
||||
format:
|
||||
clang-format -i $(LIBRARY_SOURCES) $(DEMO_SOURCES)
|
||||
clang-format -i $(LIBRARY_SOURCES) $(DEMO_SOURCES) $(GLTFPACK_SOURCES)
|
||||
|
||||
meshencoder: $(ENCODER_OBJECTS) $(LIBRARY)
|
||||
gltfpack: $(GLTFPACK_OBJECTS) $(LIBRARY)
|
||||
$(CXX) $^ $(LDFLAGS) -o $@
|
||||
|
||||
js/decoder.js: src/vertexcodec.cpp src/indexcodec.cpp
|
||||
js/meshopt_decoder.js: src/vertexcodec.cpp src/indexcodec.cpp
|
||||
@mkdir -p build
|
||||
emcc $(filter %.cpp,$^) -O3 -DNDEBUG -s EXPORTED_FUNCTIONS='["_meshopt_decodeVertexBuffer", "_meshopt_decodeIndexBuffer"]' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_STACK=32768 -s TOTAL_MEMORY=65536 -o build/decoder.wasm
|
||||
sed -i "s#\(var wasm = \)\".*\";#\\1\"$$(cat build/decoder.wasm | base64 -w 0)\";#" $@
|
||||
emcc $(filter %.cpp,$^) -O3 -DNDEBUG -s EXPORTED_FUNCTIONS='["_meshopt_decodeVertexBuffer", "_meshopt_decodeIndexBuffer"]' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_STACK=32768 -s TOTAL_MEMORY=65536 -o build/meshopt_decoder.wasm
|
||||
sed -i "s#\(var wasm = \)\".*\";#\\1\"$$(cat build/meshopt_decoder.wasm | base64 -w 0)\";#" $@
|
||||
|
||||
$(EXECUTABLE): $(DEMO_OBJECTS) $(LIBRARY)
|
||||
$(CXX) $^ $(LDFLAGS) -o $@
|
||||
|
66
3rdparty/meshoptimizer/README.md
vendored
66
3rdparty/meshoptimizer/README.md
vendored
@ -11,10 +11,10 @@ The library provides a C and C++ interface for all algorithms; you can use it fr
|
||||
meshoptimizer is hosted on GitHub; you can download the latest release using git:
|
||||
|
||||
```
|
||||
git clone -b v0.11 https://github.com/zeux/meshoptimizer.git
|
||||
git clone -b v0.12 https://github.com/zeux/meshoptimizer.git
|
||||
```
|
||||
|
||||
Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v0.11.zip).
|
||||
Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v0.12.zip).
|
||||
|
||||
## Building
|
||||
|
||||
@ -146,7 +146,7 @@ Note that vertex encoding assumes that vertex buffer was optimized for vertex fe
|
||||
|
||||
Decoding functions are heavily optimized and can directly target write-combined memory; you can expect both decoders to run at 1-3 GB/s on modern desktop CPUs. Compression ratios depend on the data; vertex data compression ratio is typically around 2-4x (compared to already quantized data), index data compression ratio is around 5-6x (compared to raw 16-bit index data). General purpose lossless compressors can further improve on these results.
|
||||
|
||||
Due to a very high decoding performance and compatibility with general purpose lossless compressors, the compression is a good fit for the use on the web. To that end, meshoptimizer provides both vertex and index decoders compiled into WebAssembly and wrapped into a module with JavaScript-friendly interface, `js/decoder.js`, that you can use to decode meshes that were encoded offline:
|
||||
Due to a very high decoding performance and compatibility with general purpose lossless compressors, the compression is a good fit for the use on the web. To that end, meshoptimizer provides both vertex and index decoders compiled into WebAssembly and wrapped into a module with JavaScript-friendly interface, `js/meshopt_decoder.js`, that you can use to decode meshes that were encoded offline:
|
||||
|
||||
```js
|
||||
// ready is a Promise that is resolved when (asynchronous) WebAssembly compilation finishes
|
||||
@ -157,7 +157,7 @@ MeshoptDecoder.decodeVertexBuffer(vertexBuffer, vertexCount, vertexSize, vertexD
|
||||
MeshoptDecoder.decodeIndexBuffer(indexBuffer, indexCount, indexSize, indexData);
|
||||
```
|
||||
|
||||
A THREE.js mesh loader is provided as an example in `tools/OptMeshLoader.js`; it loads meshes encoded using `tools/meshencoder.cpp`. [Usage example](https://zeuxcg.org/meshoptimizer/demo/) is available, with source in `demo/index.html`.
|
||||
[Usage example](https://meshoptimizer.org/demo/) is available, with source in `demo/index.html`; this example uses .GLB files encoded using `gltfpack`.
|
||||
|
||||
## Triangle strip conversion
|
||||
|
||||
@ -170,10 +170,12 @@ This library provides an algorithm for converting a vertex cache optimized trian
|
||||
|
||||
```c++
|
||||
std::vector<unsigned int> strip(meshopt_stripifyBound(index_count));
|
||||
size_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count);
|
||||
unsigned int restart_index = ~0u;
|
||||
size_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count, restart_index);
|
||||
```
|
||||
|
||||
Typically you should expect triangle strips to have ~50-60% of indices compared to triangle lists (~1.5-1.8 indices per triangle) and have ~5% worse ACMR. Note that triangle strips require restart index support for rendering; using degenerate triangles to connect strips is not supported.
|
||||
Typically you should expect triangle strips to have ~50-60% of indices compared to triangle lists (~1.5-1.8 indices per triangle) and have ~5% worse ACMR.
|
||||
Note that triangle strips can be stitched with or without restart index support. Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance.
|
||||
|
||||
## Deinterleaved geometry
|
||||
|
||||
@ -208,7 +210,7 @@ This library provides two simplification algorithms that reduce the number of tr
|
||||
|
||||
The first simplification algorithm, `meshopt_simplify`, follows the topology of the original mesh in an attempt to preserve attribute seams, borders and overall appearance. For meshes with inconsistent topology or many seams, such as faceted meshes, it can result in simplifier getting "stuck" and not being able to simplify the mesh fully; it's recommended to preprocess the index buffer with `meshopt_generateShadowIndexBuffer` to discard any vertex attributes that aren't critical and can be rebuilt later such as normals.
|
||||
|
||||
```
|
||||
```c++
|
||||
float threshold = 0.2f;
|
||||
size_t target_index_count = size_t(index_count * threshold);
|
||||
float target_error = 1e-2f;
|
||||
@ -221,7 +223,7 @@ Target error is an approximate measure of the deviation from the original mesh u
|
||||
|
||||
The second simplification algorithm, `meshopt_simplifySloppy`, doesn't follow the topology of the original mesh. This means that it doesn't preserve attribute seams or borders, but it can collapse internal details that are too small to matter better because it can merge mesh features that are topologically disjoint but spatially close.
|
||||
|
||||
```
|
||||
```c++
|
||||
float threshold = 0.2f;
|
||||
size_t target_index_count = size_t(index_count * threshold);
|
||||
|
||||
@ -253,12 +255,58 @@ Many algorithms allocate temporary memory to store intermediate results or accel
|
||||
meshopt_setAllocator(malloc, free);
|
||||
```
|
||||
|
||||
> Note that currently the library expects the allocation function to either throw in case of out-of-memory (in which case the exception will propagate to the caller) or abort, so technically the use of `malloc` above isn't safe.
|
||||
> Note that the library expects the allocation function to either throw in case of out-of-memory (in which case the exception will propagate to the caller) or abort, so technically the use of `malloc` above isn't safe. If you want to handle out-of-memory errors without using C++ exceptions, you can use `setjmp`/`longjmp` instead.
|
||||
|
||||
Vertex and index decoders (`meshopt_decodeVertexBuffer` and `meshopt_decodeIndexBuffer`) do not allocate memory and work completely within the buffer space provided via arguments.
|
||||
|
||||
All functions have bounded stack usage that does not exceed 32 KB for any algorithms.
|
||||
|
||||
## gltfpack
|
||||
|
||||
meshoptimizer provides many algorithms that can be integrated into a content pipeline or a rendering engine to improve performance. Often integration requires some conscious choices for optimal results - should we optimize for overdraw or not? what should the vertex format be? do we use triangle lists or strips? However, in some cases optimality is not a requirement.
|
||||
|
||||
For engines that want a relatively simple way to load meshes, and would like the meshes to perform reasonably well on target hardware and be reasonably fast to load, meshoptimizer provides a command-line tool, `gltfpack`. `gltfpack` can take an `.obj` or `.gltf` file as an input, and produce a `.gltf` or `.glb` file that is optimized for rendering performance and download size.
|
||||
|
||||
To build gltfpack on Linux/macOS, you can use make:
|
||||
|
||||
```
|
||||
make config=release gltfpack
|
||||
```
|
||||
|
||||
On Windows (and other platforms), you can use CMake:
|
||||
|
||||
```
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TOOLS=ON
|
||||
cmake --build . --config Release --target gltfpack
|
||||
```
|
||||
|
||||
You can then run the resulting command-line binary like this (run it without arguments for a list of options):
|
||||
|
||||
```
|
||||
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 can produce two types of output files:
|
||||
|
||||
- By default gltfpack outputs regular `.glb`/`.gltf` files that have been optimized for GPU consumption using various cache optimizers and quantization. These files can be loaded by standard GLTF loaders present in frameworks such as [three.js](https://threejs.org/) (r107+) and [Babylon.js](https://www.babylonjs.com/) (4.1+).
|
||||
- When using `-c` option, gltfpack outputs compressed `.glb`/`.gltf` files that use meshoptimizer codecs to reduce the download size further. Loading these files requires extending GLTF loaders with custom decompression support; `demo/GLTFLoader.js` contains a custom version of three.js loader that can be used to load them.
|
||||
|
||||
> Note: files produced by gltfpack use `MESHOPT_quantized_geometry` and `MESHOPT_compression` pseudo-extensions; both of these have *not* been standardized yet but eventually will be. glTF validator doesn't recognize these extensions and produces a large number of validation errors because of this.
|
||||
|
||||
When using compressed files, `js/meshopt_decoder.js` needs to be loaded to provide the WebAssembly decoder module like this:
|
||||
|
||||
```js
|
||||
<script src="js/meshopt_decoder.js"></script>
|
||||
|
||||
...
|
||||
|
||||
var loader = new THREE.GLTFLoader();
|
||||
loader.setMeshoptDecoder(MeshoptDecoder);
|
||||
loader.load('pirate.glb', function (gltf) { scene.add(gltf.scene); });
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md).
|
||||
|
3316
3rdparty/meshoptimizer/demo/GLTFLoader.js
vendored
Normal file
3316
3rdparty/meshoptimizer/demo/GLTFLoader.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
57
3rdparty/meshoptimizer/demo/index.html
vendored
57
3rdparty/meshoptimizer/demo/index.html
vendored
@ -30,30 +30,19 @@
|
||||
<a href="https://github.com/zeux/meshoptimizer" target="_blank" rel="noopener">meshoptimizer</a>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.min.js"></script>
|
||||
|
||||
<script src="../js/decoder.js"></script>
|
||||
<script src="../tools/OptMeshLoader.js"></script>
|
||||
<script src="../js/meshopt_decoder.js"></script>
|
||||
<script src="GLTFLoader.js"></script>
|
||||
|
||||
<script>
|
||||
var container;
|
||||
|
||||
var camera, scene, renderer;
|
||||
var camera, scene, renderer, mixer, clock;
|
||||
|
||||
var windowHalfX = window.innerWidth / 2;
|
||||
var windowHalfY = window.innerHeight / 2;
|
||||
|
||||
var timers = {};
|
||||
|
||||
console.time = function(label) {
|
||||
timers[label] = performance.now();
|
||||
};
|
||||
|
||||
console.timeEnd = function(label) {
|
||||
var time = performance.now() - timers[label];
|
||||
document.getElementById('info').append(label + " took " + time.toFixed(2) + " ms");
|
||||
};
|
||||
|
||||
init();
|
||||
animate();
|
||||
|
||||
@ -67,8 +56,9 @@
|
||||
camera.position.z = 3.0;
|
||||
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x300a24);
|
||||
|
||||
var ambientLight = new THREE.AmbientLight(0xcccccc, 0.2);
|
||||
var ambientLight = new THREE.AmbientLight(0xcccccc, 0.3);
|
||||
scene.add(ambientLight);
|
||||
|
||||
var pointLight = new THREE.PointLight(0xffffff, 0.8);
|
||||
@ -77,16 +67,27 @@
|
||||
scene.add(camera);
|
||||
|
||||
var onProgress = function (xhr) {};
|
||||
var onError = function () {};
|
||||
var onError = function (e) {
|
||||
console.log(e);
|
||||
};
|
||||
|
||||
new THREE.OptMeshLoader()
|
||||
.setDecoder(MeshoptDecoder)
|
||||
.setMaterials(null) // materials can be fetched using MTLLoader
|
||||
.setPath('./')
|
||||
.load('pirate.optmesh', function (object)
|
||||
{
|
||||
scene.add(object);
|
||||
}, onProgress, onError);
|
||||
var loader = new THREE.GLTFLoader()
|
||||
loader.setMeshoptDecoder(MeshoptDecoder)
|
||||
loader.load('pirate.glb', function (gltf) {
|
||||
var bbox = new THREE.Box3().setFromObject(gltf.scene);
|
||||
var scale = 2 / (bbox.max.y - bbox.min.y);
|
||||
|
||||
gltf.scene.scale.set(scale, scale, scale);
|
||||
gltf.scene.position.set(0, 0, 0);
|
||||
|
||||
scene.add(gltf.scene);
|
||||
|
||||
mixer = new THREE.AnimationMixer(gltf.scene);
|
||||
|
||||
if (gltf.animations.length) {
|
||||
mixer.clipAction(gltf.animations[gltf.animations.length - 1]).play();
|
||||
}
|
||||
}, onProgress, onError);
|
||||
|
||||
renderer = new THREE.WebGLRenderer();
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
@ -94,6 +95,8 @@
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
window.addEventListener('resize', onWindowResize, false);
|
||||
|
||||
clock = new THREE.Clock();
|
||||
}
|
||||
|
||||
function onWindowResize()
|
||||
@ -109,6 +112,10 @@
|
||||
|
||||
function animate()
|
||||
{
|
||||
if (mixer) {
|
||||
mixer.update(clock.getDelta());
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
render();
|
||||
}
|
||||
|
136
3rdparty/meshoptimizer/demo/main.cpp
vendored
136
3rdparty/meshoptimizer/demo/main.cpp
vendored
@ -8,7 +8,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../tools/objparser.h"
|
||||
#include "../tools/fast_obj.h"
|
||||
#include "miniz.h"
|
||||
|
||||
// This file uses assert() to verify algorithm correctness
|
||||
@ -66,47 +66,58 @@ union Triangle {
|
||||
|
||||
Mesh parseObj(const char* path, double& reindex)
|
||||
{
|
||||
ObjFile file;
|
||||
|
||||
if (!objParseFile(file, path))
|
||||
fastObjMesh* obj = fast_obj_read(path);
|
||||
if (!obj)
|
||||
{
|
||||
printf("Error loading %s: file not found\n", path);
|
||||
return Mesh();
|
||||
}
|
||||
|
||||
if (!objValidate(file))
|
||||
{
|
||||
printf("Error loading %s: invalid file data\n", path);
|
||||
return Mesh();
|
||||
}
|
||||
size_t total_indices = 0;
|
||||
|
||||
size_t total_indices = file.f_size / 3;
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
total_indices += 3 * (obj->face_vertices[i] - 2);
|
||||
|
||||
std::vector<Vertex> vertices(total_indices);
|
||||
|
||||
for (size_t i = 0; i < total_indices; ++i)
|
||||
size_t vertex_offset = 0;
|
||||
size_t index_offset = 0;
|
||||
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
{
|
||||
int vi = file.f[i * 3 + 0];
|
||||
int vti = file.f[i * 3 + 1];
|
||||
int vni = file.f[i * 3 + 2];
|
||||
for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
|
||||
{
|
||||
fastObjIndex gi = obj->indices[index_offset + j];
|
||||
|
||||
Vertex v =
|
||||
{
|
||||
file.v[vi * 3 + 0],
|
||||
file.v[vi * 3 + 1],
|
||||
file.v[vi * 3 + 2],
|
||||
Vertex v =
|
||||
{
|
||||
obj->positions[gi.p * 3 + 0],
|
||||
obj->positions[gi.p * 3 + 1],
|
||||
obj->positions[gi.p * 3 + 2],
|
||||
obj->normals[gi.n * 3 + 0],
|
||||
obj->normals[gi.n * 3 + 1],
|
||||
obj->normals[gi.n * 3 + 2],
|
||||
obj->texcoords[gi.t * 2 + 0],
|
||||
obj->texcoords[gi.t * 2 + 1],
|
||||
};
|
||||
|
||||
vni >= 0 ? file.vn[vni * 3 + 0] : 0,
|
||||
vni >= 0 ? file.vn[vni * 3 + 1] : 0,
|
||||
vni >= 0 ? file.vn[vni * 3 + 2] : 0,
|
||||
// triangulate polygon on the fly; offset-3 is always the first polygon vertex
|
||||
if (j >= 3)
|
||||
{
|
||||
vertices[vertex_offset + 0] = vertices[vertex_offset - 3];
|
||||
vertices[vertex_offset + 1] = vertices[vertex_offset - 1];
|
||||
vertex_offset += 2;
|
||||
}
|
||||
|
||||
vti >= 0 ? file.vt[vti * 3 + 0] : 0,
|
||||
vti >= 0 ? file.vt[vti * 3 + 1] : 0,
|
||||
};
|
||||
vertices[vertex_offset] = v;
|
||||
vertex_offset++;
|
||||
}
|
||||
|
||||
vertices[i] = v;
|
||||
index_offset += obj->face_vertices[i];
|
||||
}
|
||||
|
||||
fast_obj_destroy(obj);
|
||||
|
||||
reindex = timestamp();
|
||||
|
||||
Mesh result;
|
||||
@ -621,16 +632,18 @@ void encodeVertex(const Mesh& mesh, const char* pvn)
|
||||
(double(result.size() * sizeof(PV)) / (1 << 30)) / (end - middle));
|
||||
}
|
||||
|
||||
void stripify(const Mesh& mesh)
|
||||
void stripify(const Mesh& mesh, bool use_restart)
|
||||
{
|
||||
unsigned int restart_index = use_restart ? ~0u : 0;
|
||||
|
||||
// note: input mesh is assumed to be optimized for vertex cache and vertex fetch
|
||||
double start = timestamp();
|
||||
std::vector<unsigned int> strip(meshopt_stripifyBound(mesh.indices.size()));
|
||||
strip.resize(meshopt_stripify(&strip[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size()));
|
||||
strip.resize(meshopt_stripify(&strip[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), restart_index));
|
||||
double end = timestamp();
|
||||
|
||||
Mesh copy = mesh;
|
||||
copy.indices.resize(meshopt_unstripify(©.indices[0], &strip[0], strip.size()));
|
||||
copy.indices.resize(meshopt_unstripify(©.indices[0], &strip[0], strip.size(), restart_index));
|
||||
assert(copy.indices.size() <= meshopt_unstripifyBound(strip.size()));
|
||||
|
||||
assert(isMeshValid(copy));
|
||||
@ -641,7 +654,8 @@ void stripify(const Mesh& mesh)
|
||||
meshopt_VertexCacheStatistics vcs_amd = meshopt_analyzeVertexCache(©.indices[0], mesh.indices.size(), mesh.vertices.size(), 14, 64, 128);
|
||||
meshopt_VertexCacheStatistics vcs_intel = meshopt_analyzeVertexCache(©.indices[0], mesh.indices.size(), mesh.vertices.size(), 128, 0, 0);
|
||||
|
||||
printf("Stripify : ACMR %f ATVR %f (NV %f AMD %f Intel %f); %d strip indices (%.1f%%) in %.2f msec\n",
|
||||
printf("Stripify%c: ACMR %f ATVR %f (NV %f AMD %f Intel %f); %d strip indices (%.1f%%) in %.2f msec\n",
|
||||
use_restart ? 'R' : ' ',
|
||||
vcs.acmr, vcs.atvr, vcs_nv.atvr, vcs_amd.atvr, vcs_intel.atvr,
|
||||
int(strip.size()), double(strip.size()) / double(mesh.indices.size()) * 100,
|
||||
(end - start) * 1000);
|
||||
@ -779,43 +793,54 @@ void processDeinterleaved(const char* path)
|
||||
// Most algorithms in the library work out of the box with deinterleaved geometry, but some require slightly special treatment;
|
||||
// this code runs a simplified version of complete opt. pipeline using deinterleaved geo. There's no compression performed but you
|
||||
// can trivially run it by quantizing all elements and running meshopt_encodeVertexBuffer once for each vertex stream.
|
||||
ObjFile file;
|
||||
if (!objParseFile(file, path) || !objValidate(file))
|
||||
fastObjMesh* obj = fast_obj_read(path);
|
||||
if (!obj)
|
||||
{
|
||||
printf("Error loading %s: file not found or invalid file data\n", path);
|
||||
printf("Error loading %s: file not found\n", path);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t total_indices = file.f_size / 3;
|
||||
size_t total_indices = 0;
|
||||
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
total_indices += 3 * (obj->face_vertices[i] - 2);
|
||||
|
||||
std::vector<float> unindexed_pos(total_indices * 3);
|
||||
std::vector<float> unindexed_nrm(total_indices * 3);
|
||||
std::vector<float> unindexed_uv(total_indices * 2);
|
||||
|
||||
for (size_t i = 0; i < total_indices; ++i)
|
||||
size_t vertex_offset = 0;
|
||||
size_t index_offset = 0;
|
||||
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
{
|
||||
int vi = file.f[i * 3 + 0];
|
||||
int vti = file.f[i * 3 + 1];
|
||||
int vni = file.f[i * 3 + 2];
|
||||
|
||||
unindexed_pos[i * 3 + 0] = file.v[vi * 3 + 0];
|
||||
unindexed_pos[i * 3 + 1] = file.v[vi * 3 + 1];
|
||||
unindexed_pos[i * 3 + 2] = file.v[vi * 3 + 2];
|
||||
|
||||
if (vni >= 0)
|
||||
for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
|
||||
{
|
||||
unindexed_nrm[i * 3 + 0] = file.vn[vni * 3 + 0];
|
||||
unindexed_nrm[i * 3 + 1] = file.vn[vni * 3 + 1];
|
||||
unindexed_nrm[i * 3 + 2] = file.vn[vni * 3 + 2];
|
||||
fastObjIndex gi = obj->indices[index_offset + j];
|
||||
|
||||
// triangulate polygon on the fly; offset-3 is always the first polygon vertex
|
||||
if (j >= 3)
|
||||
{
|
||||
memcpy(&unindexed_pos[(vertex_offset + 0) * 3], &unindexed_pos[(vertex_offset - 3) * 3], 3 * sizeof(float));
|
||||
memcpy(&unindexed_nrm[(vertex_offset + 0) * 3], &unindexed_nrm[(vertex_offset - 3) * 3], 3 * sizeof(float));
|
||||
memcpy(&unindexed_uv[(vertex_offset + 0) * 2], &unindexed_uv[(vertex_offset - 3) * 2], 2 * sizeof(float));
|
||||
memcpy(&unindexed_pos[(vertex_offset + 1) * 3], &unindexed_pos[(vertex_offset - 1) * 3], 3 * sizeof(float));
|
||||
memcpy(&unindexed_nrm[(vertex_offset + 1) * 3], &unindexed_nrm[(vertex_offset - 1) * 3], 3 * sizeof(float));
|
||||
memcpy(&unindexed_uv[(vertex_offset + 1) * 2], &unindexed_uv[(vertex_offset - 1) * 2], 2 * sizeof(float));
|
||||
vertex_offset += 2;
|
||||
}
|
||||
|
||||
memcpy(&unindexed_pos[vertex_offset * 3], &obj->positions[gi.p * 3], 3 * sizeof(float));
|
||||
memcpy(&unindexed_nrm[vertex_offset * 3], &obj->normals[gi.n * 3], 3 * sizeof(float));
|
||||
memcpy(&unindexed_uv[vertex_offset * 2], &obj->texcoords[gi.t * 2], 2 * sizeof(float));
|
||||
vertex_offset++;
|
||||
}
|
||||
|
||||
if (vti >= 0)
|
||||
{
|
||||
unindexed_uv[i * 2 + 0] = file.vt[vti * 3 + 0];
|
||||
unindexed_uv[i * 2 + 1] = file.vt[vti * 3 + 1];
|
||||
}
|
||||
index_offset += obj->face_vertices[i];
|
||||
}
|
||||
|
||||
fast_obj_destroy(obj);
|
||||
|
||||
double start = timestamp();
|
||||
|
||||
meshopt_Stream streams[] = {
|
||||
@ -884,7 +909,9 @@ void process(const char* path)
|
||||
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));
|
||||
|
||||
stripify(copy);
|
||||
stripify(copy, false);
|
||||
stripify(copy, true);
|
||||
|
||||
meshlets(copy);
|
||||
shadow(copy);
|
||||
|
||||
@ -907,7 +934,8 @@ void processDev(const char* path)
|
||||
if (!loadMesh(mesh, path))
|
||||
return;
|
||||
|
||||
simplify(mesh, 0.01f);
|
||||
simplifySloppy(mesh, 0.5f);
|
||||
simplifySloppy(mesh, 0.1f);
|
||||
simplifySloppy(mesh, 0.01f);
|
||||
}
|
||||
|
||||
|
BIN
3rdparty/meshoptimizer/demo/pirate.glb
vendored
Normal file
BIN
3rdparty/meshoptimizer/demo/pirate.glb
vendored
Normal file
Binary file not shown.
BIN
3rdparty/meshoptimizer/demo/pirate.optmesh
vendored
BIN
3rdparty/meshoptimizer/demo/pirate.optmesh
vendored
Binary file not shown.
2
3rdparty/meshoptimizer/src/indexcodec.cpp
vendored
2
3rdparty/meshoptimizer/src/indexcodec.cpp
vendored
@ -162,7 +162,7 @@ static void writeTriangle(void* destination, size_t offset, size_t index_size, u
|
||||
}
|
||||
else
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if (index_size == 4) // work around Edge (ChakraCore) bug - without this compiler assumes index_size==2
|
||||
if (index_size == 4) // work around Edge (ChakraCore) bug - without this compiler assumes index_size==2
|
||||
#endif
|
||||
{
|
||||
static_cast<unsigned int*>(destination)[offset + 0] = a;
|
||||
|
36
3rdparty/meshoptimizer/src/meshoptimizer.h
vendored
36
3rdparty/meshoptimizer/src/meshoptimizer.h
vendored
@ -12,7 +12,7 @@
|
||||
#include <stddef.h>
|
||||
|
||||
/* Version macro; major * 1000 + minor * 10 + patch */
|
||||
#define MESHOPTIMIZER_VERSION 110
|
||||
#define MESHOPTIMIZER_VERSION 120
|
||||
|
||||
/* If no API is defined, assume default */
|
||||
#ifndef MESHOPTIMIZER_API
|
||||
@ -49,7 +49,7 @@ struct meshopt_Stream
|
||||
MESHOPTIMIZER_API size_t meshopt_generateVertexRemap(unsigned int* destination, const unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size);
|
||||
|
||||
/**
|
||||
* Experimental: Generates a vertex remap table from multiple vertex streams and an optional index buffer and returns number of unique vertices
|
||||
* Generates a vertex remap table from multiple vertex streams and an optional index buffer and returns number of unique vertices
|
||||
* As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence.
|
||||
* Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer.
|
||||
* To remap vertex buffers, you will need to call meshopt_remapVertexBuffer for each vertex stream.
|
||||
@ -57,7 +57,7 @@ MESHOPTIMIZER_API size_t meshopt_generateVertexRemap(unsigned int* destination,
|
||||
* destination must contain enough space for the resulting remap table (vertex_count elements)
|
||||
* indices can be NULL if the input is unindexed
|
||||
*/
|
||||
MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count);
|
||||
MESHOPTIMIZER_API size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count);
|
||||
|
||||
/**
|
||||
* Generates vertex buffer from the source vertex buffer and remap table generated by meshopt_generateVertexRemap
|
||||
@ -76,22 +76,22 @@ MESHOPTIMIZER_API void meshopt_remapVertexBuffer(void* destination, const void*
|
||||
MESHOPTIMIZER_API void meshopt_remapIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const unsigned int* remap);
|
||||
|
||||
/**
|
||||
* Experimental: Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary
|
||||
* Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary
|
||||
* All vertices that are binary equivalent (wrt first vertex_size bytes) map to the first vertex in the original vertex buffer.
|
||||
* This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering.
|
||||
*
|
||||
* destination must contain enough space for the resulting index buffer (index_count elements)
|
||||
*/
|
||||
MESHOPTIMIZER_EXPERIMENTAL void meshopt_generateShadowIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride);
|
||||
MESHOPTIMIZER_API void meshopt_generateShadowIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride);
|
||||
|
||||
/**
|
||||
* Experimental: Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary
|
||||
* Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary
|
||||
* All vertices that are binary equivalent (wrt specified streams) map to the first vertex in the original vertex buffer.
|
||||
* This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering.
|
||||
*
|
||||
* destination must contain enough space for the resulting index buffer (index_count elements)
|
||||
*/
|
||||
MESHOPTIMIZER_EXPERIMENTAL void meshopt_generateShadowIndexBufferMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count);
|
||||
MESHOPTIMIZER_API void meshopt_generateShadowIndexBufferMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count);
|
||||
|
||||
/**
|
||||
* Vertex transform cache optimizer
|
||||
@ -217,13 +217,15 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifySloppy(unsigned int* destinati
|
||||
|
||||
/**
|
||||
* Mesh stripifier
|
||||
* Converts a previously vertex cache optimized triangle list to triangle strip, stitching strips using restart index
|
||||
* Converts a previously vertex cache optimized triangle list to triangle strip, stitching strips using restart index or degenerate triangles
|
||||
* Returns the number of indices in the resulting strip, with destination containing new index data
|
||||
* For maximum efficiency the index buffer being converted has to be optimized for vertex cache first.
|
||||
* Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance.
|
||||
*
|
||||
* destination must contain enough space for the target index buffer, worst case can be computed with meshopt_stripifyBound
|
||||
* restart_index should be 0xffff or 0xffffffff depending on index size, or 0 to use degenerate triangles
|
||||
*/
|
||||
MESHOPTIMIZER_API size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count);
|
||||
MESHOPTIMIZER_API size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int restart_index);
|
||||
MESHOPTIMIZER_API size_t meshopt_stripifyBound(size_t index_count);
|
||||
|
||||
/**
|
||||
@ -233,7 +235,7 @@ MESHOPTIMIZER_API size_t meshopt_stripifyBound(size_t index_count);
|
||||
*
|
||||
* destination must contain enough space for the target index buffer, worst case can be computed with meshopt_unstripifyBound
|
||||
*/
|
||||
MESHOPTIMIZER_API size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count);
|
||||
MESHOPTIMIZER_API size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count, unsigned int restart_index);
|
||||
MESHOPTIMIZER_API size_t meshopt_unstripifyBound(size_t index_count);
|
||||
|
||||
struct meshopt_VertexCacheStatistics
|
||||
@ -341,12 +343,12 @@ MESHOPTIMIZER_EXPERIMENTAL struct meshopt_Bounds meshopt_computeClusterBounds(co
|
||||
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: Set allocation callbacks
|
||||
* Set allocation callbacks
|
||||
* These callbacks will be used instead of the default operator new/operator delete for all temporary allocations in the library.
|
||||
* Note that all algorithms only allocate memory for temporary use.
|
||||
* allocate/deallocate are always called in a stack-like order - last pointer to be allocated is deallocated first.
|
||||
*/
|
||||
MESHOPTIMIZER_EXPERIMENTAL void meshopt_setAllocator(void* (*allocate)(size_t), void (*deallocate)(void*));
|
||||
MESHOPTIMIZER_API void meshopt_setAllocator(void* (*allocate)(size_t), void (*deallocate)(void*));
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
@ -562,21 +564,21 @@ inline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t in
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline size_t meshopt_stripify(T* destination, const T* indices, size_t index_count, size_t vertex_count)
|
||||
inline size_t meshopt_stripify(T* destination, const T* indices, size_t index_count, size_t vertex_count, T restart_index)
|
||||
{
|
||||
meshopt_IndexAdapter<T> in(0, indices, index_count);
|
||||
meshopt_IndexAdapter<T> out(destination, 0, (index_count / 3) * 4);
|
||||
meshopt_IndexAdapter<T> out(destination, 0, (index_count / 3) * 5);
|
||||
|
||||
return meshopt_stripify(out.data, in.data, index_count, vertex_count);
|
||||
return meshopt_stripify(out.data, in.data, index_count, vertex_count, unsigned(restart_index));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline size_t meshopt_unstripify(T* destination, const T* indices, size_t index_count)
|
||||
inline size_t meshopt_unstripify(T* destination, const T* indices, size_t index_count, T restart_index)
|
||||
{
|
||||
meshopt_IndexAdapter<T> in(0, indices, index_count);
|
||||
meshopt_IndexAdapter<T> out(destination, 0, (index_count - 2) * 3);
|
||||
|
||||
return meshopt_unstripify(out.data, in.data, index_count);
|
||||
return meshopt_unstripify(out.data, in.data, index_count, unsigned(restart_index));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
@ -227,4 +227,4 @@ meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const unsigned int* indices,
|
||||
result.overdraw = result.pixels_covered ? float(result.pixels_shaded) / float(result.pixels_covered) : 0.f;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
42
3rdparty/meshoptimizer/src/simplifier.cpp
vendored
42
3rdparty/meshoptimizer/src/simplifier.cpp
vendored
@ -19,6 +19,7 @@
|
||||
// Michael Garland. Quadric-based polygonal surface simplification. 1999
|
||||
// Peter Lindstrom. Out-of-Core Simplification of Large Polygonal Models. 2000
|
||||
// Matthias Teschner, Bruno Heidelberger, Matthias Mueller, Danat Pomeranets, Markus Gross. Optimized Spatial Hashing for Collision Detection of Deformable Objects. 2003
|
||||
// Peter Van Sandt, Yannis Chronis, Jignesh M. Patel. Efficiently Searching In-Memory Sorted Arrays: Revenge of the Interpolation Search? 2019
|
||||
namespace meshopt
|
||||
{
|
||||
|
||||
@ -1080,6 +1081,14 @@ static size_t filterTriangles(unsigned int* destination, unsigned int* tritable,
|
||||
return result * 3;
|
||||
}
|
||||
|
||||
static float interpolate(float y, float x0, float y0, float x1, float y1, float x2, float y2)
|
||||
{
|
||||
// three point interpolation from "revenge of interpolation search" paper
|
||||
float num = (y1 - y) * (x1 - x2) * (x1 - x0) * (y2 - y0);
|
||||
float den = (y2 - y) * (x1 - x2) * (y0 - y1) + (y0 - y) * (x1 - x0) * (y1 - y2);
|
||||
return x1 + num / den;
|
||||
}
|
||||
|
||||
} // namespace meshopt
|
||||
|
||||
#if TRACE
|
||||
@ -1265,38 +1274,25 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind
|
||||
|
||||
unsigned int* vertex_ids = allocator.allocate<unsigned int>(vertex_count);
|
||||
|
||||
const int kInterpolationPasses = 5;
|
||||
|
||||
// invariant: # of triangles in min_grid <= target_count
|
||||
int min_grid = 0;
|
||||
int max_grid = 1025;
|
||||
size_t min_triangles = 0;
|
||||
size_t max_triangles = index_count / 3;
|
||||
|
||||
const int kInterpolationPasses = 5;
|
||||
// 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_triangles < target_index_count / 3);
|
||||
assert(max_grid - min_grid > 1);
|
||||
|
||||
int grid_size = 0;
|
||||
|
||||
if (pass == 0)
|
||||
{
|
||||
// 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...
|
||||
grid_size = int(sqrtf(float(target_cell_count)) + 0.5f);
|
||||
grid_size = (grid_size <= min_grid) ? min_grid + 1 : (grid_size >= max_grid) ? max_grid - 1 : grid_size;
|
||||
}
|
||||
else if (pass <= kInterpolationPasses)
|
||||
{
|
||||
float k = (float(target_index_count / 3) - float(min_triangles)) / (float(max_triangles) - float(min_triangles));
|
||||
grid_size = int(float(min_grid) * (1 - k) + float(max_grid) * k + 0.5f);
|
||||
grid_size = (grid_size <= min_grid) ? min_grid + 1 : (grid_size >= max_grid) ? max_grid - 1 : grid_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
grid_size = (min_grid + max_grid) / 2;
|
||||
assert(grid_size > min_grid && grid_size < max_grid);
|
||||
}
|
||||
// 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 triangles = countTriangles(vertex_ids, indices, index_count);
|
||||
@ -1308,6 +1304,8 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind
|
||||
(triangles <= target_index_count / 3) ? "under" : "over");
|
||||
#endif
|
||||
|
||||
float tip = interpolate(float(target_index_count / 3), float(min_grid), float(min_triangles), float(grid_size), float(triangles), float(max_grid), float(max_triangles));
|
||||
|
||||
if (triangles <= target_index_count / 3)
|
||||
{
|
||||
min_grid = grid_size;
|
||||
@ -1321,6 +1319,10 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind
|
||||
|
||||
if (triangles == target_index_count / 3 || 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_triangles == 0)
|
||||
|
55
3rdparty/meshoptimizer/src/stripifier.cpp
vendored
55
3rdparty/meshoptimizer/src/stripifier.cpp
vendored
@ -48,7 +48,7 @@ static int findStripNext(const unsigned int buffer[][3], unsigned int buffer_siz
|
||||
|
||||
} // namespace meshopt
|
||||
|
||||
size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count)
|
||||
size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int restart_index)
|
||||
{
|
||||
assert(destination != indices);
|
||||
assert(index_count % 3 == 0);
|
||||
@ -197,18 +197,42 @@ size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices,
|
||||
next = ec;
|
||||
}
|
||||
|
||||
// emit the new strip; we use restart indices
|
||||
if (strip_size)
|
||||
destination[strip_size++] = ~0u;
|
||||
if (restart_index)
|
||||
{
|
||||
if (strip_size)
|
||||
destination[strip_size++] = restart_index;
|
||||
|
||||
destination[strip_size++] = a;
|
||||
destination[strip_size++] = b;
|
||||
destination[strip_size++] = c;
|
||||
destination[strip_size++] = a;
|
||||
destination[strip_size++] = b;
|
||||
destination[strip_size++] = c;
|
||||
|
||||
// new strip always starts with the same edge winding
|
||||
strip[0] = b;
|
||||
strip[1] = c;
|
||||
parity = 1;
|
||||
// new strip always starts with the same edge winding
|
||||
strip[0] = b;
|
||||
strip[1] = c;
|
||||
parity = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strip_size)
|
||||
{
|
||||
// connect last strip using degenerate triangles
|
||||
destination[strip_size++] = strip[1];
|
||||
destination[strip_size++] = a;
|
||||
}
|
||||
|
||||
// note that we may need to flip the emitted triangle based on parity
|
||||
// we always end up with outgoing edge "cb" in the end
|
||||
unsigned int e0 = parity ? c : b;
|
||||
unsigned int e1 = parity ? b : c;
|
||||
|
||||
destination[strip_size++] = a;
|
||||
destination[strip_size++] = e0;
|
||||
destination[strip_size++] = e1;
|
||||
|
||||
strip[0] = e0;
|
||||
strip[1] = e1;
|
||||
parity ^= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,11 +243,12 @@ size_t meshopt_stripifyBound(size_t index_count)
|
||||
{
|
||||
assert(index_count % 3 == 0);
|
||||
|
||||
// worst case is 1 restart index and 3 indices per triangle
|
||||
return (index_count / 3) * 4;
|
||||
// worst case without restarts is 2 degenerate indices and 3 indices per triangle
|
||||
// worst case with restarts is 1 restart index and 3 indices per triangle
|
||||
return (index_count / 3) * 5;
|
||||
}
|
||||
|
||||
size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count)
|
||||
size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count, unsigned int restart_index)
|
||||
{
|
||||
assert(destination != indices);
|
||||
|
||||
@ -232,7 +257,7 @@ size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices
|
||||
|
||||
for (size_t i = 0; i < index_count; ++i)
|
||||
{
|
||||
if (indices[i] == ~0u)
|
||||
if (restart_index && indices[i] == restart_index)
|
||||
{
|
||||
start = i + 1;
|
||||
}
|
||||
|
@ -15,11 +15,11 @@ const size_t kValenceMax = 8;
|
||||
|
||||
static const float kVertexScoreTableCache[1 + kCacheSizeMax] = {
|
||||
0.f,
|
||||
0.792f, 0.767f, 0.764f, 0.956f, 0.827f, 0.751f, 0.820f, 0.864f, 0.738f, 0.788f, 0.642f, 0.646f, 0.165f, 0.654f, 0.545f, 0.284f};
|
||||
0.779f, 0.791f, 0.789f, 0.981f, 0.843f, 0.726f, 0.847f, 0.882f, 0.867f, 0.799f, 0.642f, 0.613f, 0.600f, 0.568f, 0.372f, 0.234f};
|
||||
|
||||
static const float kVertexScoreTableLive[1 + kValenceMax] = {
|
||||
0.f,
|
||||
0.994f, 0.721f, 0.479f, 0.423f, 0.174f, 0.080f, 0.249f, 0.056f};
|
||||
0.995f, 0.713f, 0.450f, 0.404f, 0.059f, 0.005f, 0.147f, 0.006f};
|
||||
|
||||
struct TriangleAdjacency
|
||||
{
|
||||
|
179
3rdparty/meshoptimizer/tools/OptMeshLoader.js
vendored
179
3rdparty/meshoptimizer/tools/OptMeshLoader.js
vendored
@ -1,179 +0,0 @@
|
||||
THREE.OptMeshLoader = (function ()
|
||||
{
|
||||
function OptMeshLoader(manager)
|
||||
{
|
||||
this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager;
|
||||
this.materials = Promise.resolve(null);
|
||||
}
|
||||
|
||||
OptMeshLoader.prototype =
|
||||
{
|
||||
constructor: OptMeshLoader,
|
||||
|
||||
load: function (url, onLoad, onProgress, onError)
|
||||
{
|
||||
var scope = this;
|
||||
|
||||
var loader = new THREE.FileLoader(scope.manager);
|
||||
loader.setResponseType('arraybuffer');
|
||||
loader.setPath(this.path);
|
||||
loader.load(url, function (data)
|
||||
{
|
||||
scope.decoder.ready.then(function ()
|
||||
{
|
||||
scope.materials.then(function (materials)
|
||||
{
|
||||
onLoad(scope.parse(data, materials));
|
||||
});
|
||||
});
|
||||
}, onProgress, onError);
|
||||
},
|
||||
|
||||
setDecoder: function (value)
|
||||
{
|
||||
this.decoder = value;
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
setPath: function (value)
|
||||
{
|
||||
this.path = value;
|
||||
return this;
|
||||
},
|
||||
|
||||
setMaterials: function (materials)
|
||||
{
|
||||
this.materials = Promise.resolve(materials);
|
||||
return this;
|
||||
},
|
||||
|
||||
setMaterialLib: function (lib)
|
||||
{
|
||||
var scope = this;
|
||||
|
||||
this.materials = new Promise(function (resolve, reject)
|
||||
{
|
||||
var loader = new THREE.MTLLoader();
|
||||
loader.setPath(scope.path);
|
||||
loader.load(lib, function (materials) { materials.preload(); resolve(materials); }, null, reject);
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
parse: function (data, materials)
|
||||
{
|
||||
console.time('OptMeshLoader');
|
||||
|
||||
var array = new Uint8Array(data);
|
||||
var view = new DataView(data);
|
||||
|
||||
var endian = true;
|
||||
var magic = view.getUint32(0, endian);
|
||||
var objectCount = view.getUint32(4, endian);
|
||||
var vertexCount = view.getUint32(8, endian);
|
||||
var indexCount = view.getUint32(12, endian);
|
||||
var vertexDataSize = view.getUint32(16, endian);
|
||||
var indexDataSize = view.getUint32(20, endian);
|
||||
var posOffsetX = view.getFloat32(24, endian);
|
||||
var posOffsetY = view.getFloat32(28, endian);
|
||||
var posOffsetZ = view.getFloat32(32, endian);
|
||||
var posScale = view.getFloat32(36, endian);
|
||||
var uvOffsetX = view.getFloat32(40, endian);
|
||||
var uvOffsetY = view.getFloat32(44, endian);
|
||||
var uvScaleX = view.getFloat32(48, endian);
|
||||
var uvScaleY = view.getFloat32(52, endian);
|
||||
|
||||
if (magic != 0x4D54504F)
|
||||
throw new Error("Malformed mesh file: unrecognized header");
|
||||
|
||||
var objectOffset = 64;
|
||||
var objectDataOffset = objectOffset + 16 * objectCount;
|
||||
|
||||
var objectDataSize = 0;
|
||||
|
||||
for (var i = 0; i < objectCount; ++i)
|
||||
objectDataSize += view.getUint32(objectOffset + 16 * i + 8, endian);
|
||||
|
||||
var vertexDataOffset = objectDataOffset + objectDataSize;
|
||||
var indexDataOffset = vertexDataOffset + vertexDataSize;
|
||||
|
||||
var endOffset = indexDataOffset + indexDataSize;
|
||||
|
||||
if (endOffset != data.byteLength)
|
||||
throw new Error("Malformed mesh file: unexpected input size");
|
||||
|
||||
var vertexSize = 16;
|
||||
var indexSize = 4;
|
||||
|
||||
var vertexBuffer = new ArrayBuffer(vertexCount * vertexSize);
|
||||
var vertexBufferU8 = new Uint8Array(vertexBuffer);
|
||||
this.decoder.decodeVertexBuffer(vertexBufferU8, vertexCount, vertexSize, array.subarray(vertexDataOffset, vertexDataOffset + vertexDataSize));
|
||||
|
||||
var indexBuffer = new ArrayBuffer(indexCount * indexSize);
|
||||
var indexBufferU8 = new Uint8Array(indexBuffer);
|
||||
this.decoder.decodeIndexBuffer(indexBufferU8, indexCount, indexSize, array.subarray(indexDataOffset, indexDataOffset + indexDataSize));
|
||||
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
|
||||
geometry.addAttribute('position', new THREE.InterleavedBufferAttribute(new THREE.InterleavedBuffer(new Uint16Array(vertexBuffer), 8), 3, 0, false));
|
||||
geometry.addAttribute('normal', new THREE.InterleavedBufferAttribute(new THREE.InterleavedBuffer(new Int8Array(vertexBuffer), 16), 3, 8, true));
|
||||
geometry.addAttribute('uv', new THREE.InterleavedBufferAttribute(new THREE.InterleavedBuffer(new Uint16Array(vertexBuffer), 8), 2, 6, false));
|
||||
geometry.setIndex(new THREE.BufferAttribute(new Uint32Array(indexBuffer), 1, false));
|
||||
|
||||
var objectDataOffsetAcc = objectDataOffset;
|
||||
|
||||
var objectMaterials = [];
|
||||
var objectMaterialsLookup = {};
|
||||
|
||||
for (var i = 0; i < objectCount; i++)
|
||||
{
|
||||
var objectIndexOffset = view.getUint32(objectOffset + 16 * i + 0, endian);
|
||||
var objectIndexCount = view.getUint32(objectOffset + 16 * i + 4, endian);
|
||||
var objectMaterialLength = view.getUint32(objectOffset + 16 * i + 8, endian);
|
||||
|
||||
var objectMaterialName = String.fromCharCode.apply(null, array.subarray(objectDataOffsetAcc, objectDataOffsetAcc + objectMaterialLength));
|
||||
var objectMaterialIndex = objectMaterialsLookup[objectMaterialName];
|
||||
|
||||
if (objectMaterialIndex == undefined)
|
||||
{
|
||||
var objectMaterial = null;
|
||||
|
||||
if (materials !== null)
|
||||
objectMaterial = materials.create(objectMaterialName);
|
||||
|
||||
if (!objectMaterial)
|
||||
objectMaterial = new THREE.MeshPhongMaterial();
|
||||
|
||||
if (objectMaterial.map)
|
||||
{
|
||||
objectMaterial.map.offset.set(uvOffsetX, uvOffsetY);
|
||||
objectMaterial.map.repeat.set(uvScaleX, uvScaleY);
|
||||
}
|
||||
|
||||
objectMaterialIndex = objectMaterials.length;
|
||||
objectMaterialsLookup[objectMaterialName] = objectMaterialIndex;
|
||||
objectMaterials.push(objectMaterial);
|
||||
}
|
||||
|
||||
geometry.addGroup(objectIndexOffset, objectIndexCount, objectMaterialIndex);
|
||||
|
||||
objectDataOffsetAcc += objectMaterialLength;
|
||||
}
|
||||
|
||||
var mesh = new THREE.Mesh(geometry, objectMaterials);
|
||||
mesh.position.set(posOffsetX, posOffsetY, posOffsetZ);
|
||||
mesh.scale.set(posScale, posScale, posScale);
|
||||
|
||||
var container = new THREE.Group();
|
||||
container.add(mesh);
|
||||
|
||||
console.timeEnd('OptMeshLoader');
|
||||
|
||||
return container;
|
||||
}
|
||||
};
|
||||
|
||||
return OptMeshLoader;
|
||||
})();
|
4733
3rdparty/meshoptimizer/tools/cgltf.h
vendored
Normal file
4733
3rdparty/meshoptimizer/tools/cgltf.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1397
3rdparty/meshoptimizer/tools/fast_obj.h
vendored
Normal file
1397
3rdparty/meshoptimizer/tools/fast_obj.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3493
3rdparty/meshoptimizer/tools/gltfpack.cpp
vendored
Normal file
3493
3rdparty/meshoptimizer/tools/gltfpack.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
114
3rdparty/meshoptimizer/tools/lodviewer.cpp
vendored
114
3rdparty/meshoptimizer/tools/lodviewer.cpp
vendored
@ -1,7 +1,8 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
|
||||
#include "../src/meshoptimizer.h"
|
||||
#include "objparser.h"
|
||||
#include "fast_obj.h"
|
||||
#include "cgltf.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@ -11,11 +12,6 @@
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#ifdef GLTF
|
||||
#define CGLTF_IMPLEMENTATION
|
||||
#include "cgltf.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma comment(lib, "opengl32.lib")
|
||||
#endif
|
||||
@ -60,41 +56,58 @@ struct Mesh
|
||||
|
||||
Mesh parseObj(const char* path)
|
||||
{
|
||||
ObjFile file;
|
||||
|
||||
if (!objParseFile(file, path) || !objValidate(file))
|
||||
fastObjMesh* obj = fast_obj_read(path);
|
||||
if (!obj)
|
||||
{
|
||||
printf("Error loading %s\n", path);
|
||||
printf("Error loading %s: file not found\n", path);
|
||||
return Mesh();
|
||||
}
|
||||
|
||||
size_t total_indices = file.f_size / 3;
|
||||
size_t total_indices = 0;
|
||||
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
total_indices += 3 * (obj->face_vertices[i] - 2);
|
||||
|
||||
std::vector<Vertex> vertices(total_indices);
|
||||
|
||||
for (size_t i = 0; i < total_indices; ++i)
|
||||
size_t vertex_offset = 0;
|
||||
size_t index_offset = 0;
|
||||
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
{
|
||||
int vi = file.f[i * 3 + 0];
|
||||
int vti = file.f[i * 3 + 1];
|
||||
int vni = file.f[i * 3 + 2];
|
||||
for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
|
||||
{
|
||||
fastObjIndex gi = obj->indices[index_offset + j];
|
||||
|
||||
Vertex v =
|
||||
{
|
||||
file.v[vi * 3 + 0],
|
||||
file.v[vi * 3 + 1],
|
||||
file.v[vi * 3 + 2],
|
||||
Vertex v =
|
||||
{
|
||||
obj->positions[gi.p * 3 + 0],
|
||||
obj->positions[gi.p * 3 + 1],
|
||||
obj->positions[gi.p * 3 + 2],
|
||||
obj->normals[gi.n * 3 + 0],
|
||||
obj->normals[gi.n * 3 + 1],
|
||||
obj->normals[gi.n * 3 + 2],
|
||||
obj->texcoords[gi.t * 2 + 0],
|
||||
obj->texcoords[gi.t * 2 + 1],
|
||||
};
|
||||
|
||||
vni >= 0 ? file.vn[vni * 3 + 0] : 0,
|
||||
vni >= 0 ? file.vn[vni * 3 + 1] : 0,
|
||||
vni >= 0 ? file.vn[vni * 3 + 2] : 0,
|
||||
// triangulate polygon on the fly; offset-3 is always the first polygon vertex
|
||||
if (j >= 3)
|
||||
{
|
||||
vertices[vertex_offset + 0] = vertices[vertex_offset - 3];
|
||||
vertices[vertex_offset + 1] = vertices[vertex_offset - 1];
|
||||
vertex_offset += 2;
|
||||
}
|
||||
|
||||
vti >= 0 ? file.vt[vti * 3 + 0] : 0,
|
||||
vti >= 0 ? file.vt[vti * 3 + 1] : 0,
|
||||
};
|
||||
vertices[vertex_offset] = v;
|
||||
vertex_offset++;
|
||||
}
|
||||
|
||||
vertices[i] = v;
|
||||
index_offset += obj->face_vertices[i];
|
||||
}
|
||||
|
||||
fast_obj_destroy(obj);
|
||||
|
||||
Mesh result;
|
||||
|
||||
std::vector<unsigned int> remap(total_indices);
|
||||
@ -109,7 +122,6 @@ Mesh parseObj(const char* path)
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef GLTF
|
||||
cgltf_accessor* getAccessor(const cgltf_attribute* attributes, size_t attribute_count, cgltf_attribute_type type, int index = 0)
|
||||
{
|
||||
for (size_t i = 0; i < attribute_count; ++i)
|
||||
@ -119,15 +131,6 @@ cgltf_accessor* getAccessor(const cgltf_attribute* attributes, size_t attribute_
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* getComponentPtr(const cgltf_accessor* a)
|
||||
{
|
||||
const char* buffer = (char*)a->buffer_view->buffer->data;
|
||||
size_t offset = a->offset + a->buffer_view->offset;
|
||||
|
||||
return reinterpret_cast<const T*>(&buffer[offset]);
|
||||
}
|
||||
|
||||
Mesh parseGltf(const char* path)
|
||||
{
|
||||
cgltf_options options = {};
|
||||
@ -205,55 +208,43 @@ Mesh parseGltf(const char* path)
|
||||
if (!ai || !ap)
|
||||
continue;
|
||||
|
||||
if (ai->component_type == cgltf_component_type_r_32u)
|
||||
{
|
||||
const unsigned int* ptr = getComponentPtr<unsigned int>(ai);
|
||||
|
||||
for (size_t i = 0; i < ai->count; ++i)
|
||||
result.indices[index_offset + i] = unsigned(vertex_offset + ptr[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
const unsigned short* ptr = getComponentPtr<unsigned short>(ai);
|
||||
|
||||
for (size_t i = 0; i < ai->count; ++i)
|
||||
result.indices[index_offset + i] = unsigned(vertex_offset + ptr[i]);
|
||||
}
|
||||
for (size_t i = 0; i < ai->count; ++i)
|
||||
result.indices[index_offset + i] = unsigned(vertex_offset + cgltf_accessor_read_index(ai, i));
|
||||
|
||||
{
|
||||
const float* ptr = getComponentPtr<float>(ap);
|
||||
|
||||
for (size_t i = 0; i < ap->count; ++i)
|
||||
{
|
||||
float ptr[3];
|
||||
cgltf_accessor_read_float(ap, i, ptr, 3);
|
||||
|
||||
result.vertices[vertex_offset + i].px = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8] + transform[12];
|
||||
result.vertices[vertex_offset + i].py = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9] + transform[13];
|
||||
result.vertices[vertex_offset + i].pz = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10] + transform[14];
|
||||
ptr += ap->stride / 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (cgltf_accessor* an = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_normal))
|
||||
{
|
||||
const float* ptr = getComponentPtr<float>(an);
|
||||
|
||||
for (size_t i = 0; i < ap->count; ++i)
|
||||
{
|
||||
float ptr[3];
|
||||
cgltf_accessor_read_float(an, i, ptr, 3);
|
||||
|
||||
result.vertices[vertex_offset + i].nx = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8];
|
||||
result.vertices[vertex_offset + i].ny = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9];
|
||||
result.vertices[vertex_offset + i].nz = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10];
|
||||
ptr += an->stride / 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (cgltf_accessor* at = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_texcoord))
|
||||
{
|
||||
const float* ptr = getComponentPtr<float>(at);
|
||||
|
||||
for (size_t i = 0; i < ap->count; ++i)
|
||||
{
|
||||
float ptr[2];
|
||||
cgltf_accessor_read_float(at, i, ptr, 2);
|
||||
|
||||
result.vertices[vertex_offset + i].tx = ptr[0];
|
||||
result.vertices[vertex_offset + i].ty = ptr[1];
|
||||
ptr += at->stride / 4;
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,17 +265,14 @@ Mesh parseGltf(const char* path)
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
Mesh loadMesh(const char* path)
|
||||
{
|
||||
if (strstr(path, ".obj"))
|
||||
return parseObj(path);
|
||||
|
||||
#ifdef GLTF
|
||||
if (strstr(path, ".gltf") || strstr(path, ".glb"))
|
||||
return parseGltf(path);
|
||||
#endif
|
||||
|
||||
return Mesh();
|
||||
}
|
||||
|
255
3rdparty/meshoptimizer/tools/meshencoder.cpp
vendored
255
3rdparty/meshoptimizer/tools/meshencoder.cpp
vendored
@ -1,255 +0,0 @@
|
||||
// Converts .obj files to .optmesh files
|
||||
// Usage: meshencoder [.obj] [.optmesh]
|
||||
|
||||
// Data layout:
|
||||
// Header: 64b
|
||||
// Object table: 16b * object_count
|
||||
// Object data
|
||||
// Vertex data
|
||||
// Index data
|
||||
|
||||
#include "../src/meshoptimizer.h"
|
||||
#include "objparser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
struct Header
|
||||
{
|
||||
char magic[4]; // OPTM
|
||||
|
||||
unsigned int group_count;
|
||||
unsigned int vertex_count;
|
||||
unsigned int index_count;
|
||||
unsigned int vertex_data_size;
|
||||
unsigned int index_data_size;
|
||||
|
||||
float pos_offset[3];
|
||||
float pos_scale;
|
||||
float uv_offset[2];
|
||||
float uv_scale[2];
|
||||
|
||||
unsigned int reserved[2];
|
||||
};
|
||||
|
||||
struct Object
|
||||
{
|
||||
unsigned int index_offset;
|
||||
unsigned int index_count;
|
||||
unsigned int material_length;
|
||||
unsigned int reserved;
|
||||
};
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
unsigned short px, py, pz, pw; // unsigned 16-bit value, use pos_offset/pos_scale to unpack
|
||||
char nx, ny, nz, nw; // normalized signed 8-bit value
|
||||
unsigned short tx, ty; // unsigned 16-bit value, use uv_offset/uv_scale to unpack
|
||||
};
|
||||
|
||||
float rcpSafe(float v)
|
||||
{
|
||||
return v == 0.f ? 0.f : 1.f / v;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc <= 2)
|
||||
{
|
||||
printf("Usage: %s [.obj] [.optmesh]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* input = argv[1];
|
||||
const char* output = argv[2];
|
||||
|
||||
ObjFile file;
|
||||
|
||||
if (!objParseFile(file, input))
|
||||
{
|
||||
printf("Error loading %s: file not found\n", input);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (!objValidate(file))
|
||||
{
|
||||
printf("Error loading %s: invalid file data\n", input);
|
||||
return 3;
|
||||
}
|
||||
|
||||
float pos_offset[3] = { FLT_MAX, FLT_MAX, FLT_MAX };
|
||||
float pos_scale = 0.f;
|
||||
|
||||
for (size_t i = 0; i < file.v_size; i += 3)
|
||||
{
|
||||
pos_offset[0] = std::min(pos_offset[0], file.v[i + 0]);
|
||||
pos_offset[1] = std::min(pos_offset[1], file.v[i + 1]);
|
||||
pos_offset[2] = std::min(pos_offset[2], file.v[i + 2]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < file.v_size; i += 3)
|
||||
{
|
||||
pos_scale = std::max(pos_scale, file.v[i + 0] - pos_offset[0]);
|
||||
pos_scale = std::max(pos_scale, file.v[i + 1] - pos_offset[1]);
|
||||
pos_scale = std::max(pos_scale, file.v[i + 2] - pos_offset[2]);
|
||||
}
|
||||
|
||||
float uv_offset[2] = { FLT_MAX, FLT_MAX };
|
||||
float uv_scale[2] = { 0, 0 };
|
||||
|
||||
for (size_t i = 0; i < file.vt_size; i += 3)
|
||||
{
|
||||
uv_offset[0] = std::min(uv_offset[0], file.vt[i + 0]);
|
||||
uv_offset[1] = std::min(uv_offset[1], file.vt[i + 1]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < file.vt_size; i += 3)
|
||||
{
|
||||
uv_scale[0] = std::max(uv_scale[0], file.vt[i + 0] - uv_offset[0]);
|
||||
uv_scale[1] = std::max(uv_scale[1], file.vt[i + 1] - uv_offset[1]);
|
||||
}
|
||||
|
||||
float pos_scale_inverse = rcpSafe(pos_scale);
|
||||
float uv_scale_inverse[2] = { rcpSafe(uv_scale[0]), rcpSafe(uv_scale[1]) };
|
||||
|
||||
size_t total_indices = file.f_size / 3;
|
||||
|
||||
std::vector<Vertex> triangles(total_indices);
|
||||
|
||||
int pos_bits = 14;
|
||||
int uv_bits = 12;
|
||||
|
||||
for (size_t i = 0; i < total_indices; ++i)
|
||||
{
|
||||
int vi = file.f[i * 3 + 0];
|
||||
int vti = file.f[i * 3 + 1];
|
||||
int vni = file.f[i * 3 + 2];
|
||||
|
||||
// note: we scale the vertices uniformly; this is not the best option wrt compression quality
|
||||
// however, it means we can scale the mesh uniformly without distorting the normals
|
||||
// this is helpful for backends like ThreeJS that apply mesh scaling to normals
|
||||
float px = (file.v[vi * 3 + 0] - pos_offset[0]) * pos_scale_inverse;
|
||||
float py = (file.v[vi * 3 + 1] - pos_offset[1]) * pos_scale_inverse;
|
||||
float pz = (file.v[vi * 3 + 2] - pos_offset[2]) * pos_scale_inverse;
|
||||
|
||||
// normal is 0 if absent from the mesh
|
||||
float nx = vni >= 0 ? file.vn[vni * 3 + 0] : 0;
|
||||
float ny = vni >= 0 ? file.vn[vni * 3 + 1] : 0;
|
||||
float nz = vni >= 0 ? file.vn[vni * 3 + 2] : 0;
|
||||
|
||||
// scale the normal to make sure the largest component is +-1.0
|
||||
// this reduces the entropy of the normal by ~1.5 bits without losing precision
|
||||
// it's better to use octahedral encoding but that requires special shader support
|
||||
float nm = std::max(fabsf(nx), std::max(fabsf(ny), fabsf(nz)));
|
||||
float ns = nm == 0.f ? 0.f : 1 / nm;
|
||||
|
||||
nx *= ns;
|
||||
ny *= ns;
|
||||
nz *= ns;
|
||||
|
||||
// texture coordinates are 0 if absent, and require a texture matrix to decode
|
||||
float tx = vti >= 0 ? (file.vt[vti * 3 + 0] - uv_offset[0]) * uv_scale_inverse[0] : 0;
|
||||
float ty = vti >= 0 ? (file.vt[vti * 3 + 1] - uv_offset[1]) * uv_scale_inverse[1] : 0;
|
||||
|
||||
Vertex v =
|
||||
{
|
||||
(unsigned short)(meshopt_quantizeUnorm(px, pos_bits)),
|
||||
(unsigned short)(meshopt_quantizeUnorm(py, pos_bits)),
|
||||
(unsigned short)(meshopt_quantizeUnorm(pz, pos_bits)),
|
||||
0,
|
||||
|
||||
char(meshopt_quantizeSnorm(nx, 8)),
|
||||
char(meshopt_quantizeSnorm(ny, 8)),
|
||||
char(meshopt_quantizeSnorm(nz, 8)),
|
||||
0,
|
||||
|
||||
(unsigned short)(meshopt_quantizeUnorm(tx, uv_bits)),
|
||||
(unsigned short)(meshopt_quantizeUnorm(ty, uv_bits)),
|
||||
};
|
||||
|
||||
triangles[i] = v;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> remap(total_indices);
|
||||
|
||||
size_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &triangles[0], total_indices, sizeof(Vertex));
|
||||
|
||||
std::vector<unsigned int> indices(total_indices);
|
||||
meshopt_remapIndexBuffer(&indices[0], NULL, total_indices, &remap[0]);
|
||||
|
||||
std::vector<Vertex> vertices(total_vertices);
|
||||
meshopt_remapVertexBuffer(&vertices[0], &triangles[0], total_indices, sizeof(Vertex), &remap[0]);
|
||||
|
||||
for (size_t i = 0; i < file.g_size; ++i)
|
||||
{
|
||||
ObjGroup& g = file.g[i];
|
||||
|
||||
meshopt_optimizeVertexCache(&indices[g.index_offset], &indices[g.index_offset], g.index_count, vertices.size());
|
||||
}
|
||||
|
||||
meshopt_optimizeVertexFetch(&vertices[0], &indices[0], indices.size(), &vertices[0], vertices.size(), sizeof(Vertex));
|
||||
|
||||
std::vector<unsigned char> vbuf(meshopt_encodeVertexBufferBound(vertices.size(), sizeof(Vertex)));
|
||||
vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &vertices[0], vertices.size(), sizeof(Vertex)));
|
||||
|
||||
std::vector<unsigned char> ibuf(meshopt_encodeIndexBufferBound(indices.size(), vertices.size()));
|
||||
ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), &indices[0], indices.size()));
|
||||
|
||||
FILE* result = fopen(output, "wb");
|
||||
if (!result)
|
||||
{
|
||||
printf("Error saving %s: can't open file for writing\n", output);
|
||||
return 4;
|
||||
}
|
||||
|
||||
Header header = {};
|
||||
memcpy(header.magic, "OPTM", 4);
|
||||
|
||||
header.group_count = unsigned(file.g_size);
|
||||
header.vertex_count = unsigned(vertices.size());
|
||||
header.index_count = unsigned(indices.size());
|
||||
header.vertex_data_size = unsigned(vbuf.size());
|
||||
header.index_data_size = unsigned(ibuf.size());
|
||||
|
||||
header.pos_offset[0] = pos_offset[0];
|
||||
header.pos_offset[1] = pos_offset[1];
|
||||
header.pos_offset[2] = pos_offset[2];
|
||||
header.pos_scale = pos_scale / float((1 << pos_bits) - 1);
|
||||
|
||||
header.uv_offset[0] = uv_offset[0];
|
||||
header.uv_offset[1] = uv_offset[1];
|
||||
header.uv_scale[0] = uv_scale[0] / float((1 << uv_bits) - 1);
|
||||
header.uv_scale[1] = uv_scale[1] / float((1 << uv_bits) - 1);
|
||||
|
||||
fwrite(&header, 1, sizeof(header), result);
|
||||
|
||||
for (size_t i = 0; i < file.g_size; ++i)
|
||||
{
|
||||
ObjGroup& g = file.g[i];
|
||||
|
||||
Object object = {};
|
||||
object.index_offset = unsigned(g.index_offset);
|
||||
object.index_count = unsigned(g.index_count);
|
||||
object.material_length = unsigned(strlen(g.material));
|
||||
|
||||
fwrite(&object, 1, sizeof(object), result);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < file.g_size; ++i)
|
||||
{
|
||||
ObjGroup& g = file.g[i];
|
||||
|
||||
fwrite(g.material, 1, strlen(g.material), result);
|
||||
}
|
||||
|
||||
fwrite(&vbuf[0], 1, vbuf.size(), result);
|
||||
fwrite(&ibuf[0], 1, ibuf.size(), result);
|
||||
fclose(result);
|
||||
|
||||
return 0;
|
||||
}
|
9
3rdparty/meshoptimizer/tools/meshloader.cpp
vendored
Normal file
9
3rdparty/meshoptimizer/tools/meshloader.cpp
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#define CGLTF_IMPLEMENTATION
|
||||
#include "cgltf.h"
|
||||
|
||||
#define FAST_OBJ_IMPLEMENTATION
|
||||
#include "fast_obj.h"
|
383
3rdparty/meshoptimizer/tools/objparser.cpp
vendored
383
3rdparty/meshoptimizer/tools/objparser.cpp
vendored
@ -1,383 +0,0 @@
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "objparser.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
template <typename T>
|
||||
static void growArray(T*& data, size_t& capacity)
|
||||
{
|
||||
size_t newcapacity = capacity == 0 ? 32 : capacity + capacity / 2;
|
||||
T* newdata = new T[newcapacity];
|
||||
|
||||
if (data)
|
||||
{
|
||||
memcpy(newdata, data, capacity * sizeof(T));
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
data = newdata;
|
||||
capacity = newcapacity;
|
||||
}
|
||||
|
||||
static int fixupIndex(int index, size_t size)
|
||||
{
|
||||
return (index >= 0) ? index - 1 : int(size) + index;
|
||||
}
|
||||
|
||||
static int parseInt(const char* s, const char** end)
|
||||
{
|
||||
// skip whitespace
|
||||
while (*s == ' ' || *s == '\t')
|
||||
s++;
|
||||
|
||||
// read sign bit
|
||||
int sign = (*s == '-');
|
||||
s += (*s == '-' || *s == '+');
|
||||
|
||||
unsigned int result = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (unsigned(*s - '0') < 10)
|
||||
result = result * 10 + (*s - '0');
|
||||
else
|
||||
break;
|
||||
|
||||
s++;
|
||||
}
|
||||
|
||||
// return end-of-string
|
||||
*end = s;
|
||||
|
||||
return sign ? -int(result) : int(result);
|
||||
}
|
||||
|
||||
static float parseFloat(const char* s, const char** end)
|
||||
{
|
||||
static const double digits[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
static const double powers[] = {1e0, 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, 1e+21, 1e+22};
|
||||
|
||||
// skip whitespace
|
||||
while (*s == ' ' || *s == '\t')
|
||||
s++;
|
||||
|
||||
// read sign
|
||||
double sign = (*s == '-') ? -1 : 1;
|
||||
s += (*s == '-' || *s == '+');
|
||||
|
||||
// read integer part
|
||||
double result = 0;
|
||||
int power = 0;
|
||||
|
||||
while (unsigned(*s - '0') < 10)
|
||||
{
|
||||
result = result * 10 + digits[*s - '0'];
|
||||
s++;
|
||||
}
|
||||
|
||||
// read fractional part
|
||||
if (*s == '.')
|
||||
{
|
||||
s++;
|
||||
|
||||
while (unsigned(*s - '0') < 10)
|
||||
{
|
||||
result = result * 10 + digits[*s - '0'];
|
||||
s++;
|
||||
power--;
|
||||
}
|
||||
}
|
||||
|
||||
// read exponent part
|
||||
if ((*s | ' ') == 'e')
|
||||
{
|
||||
s++;
|
||||
|
||||
// read exponent sign
|
||||
int expsign = (*s == '-') ? -1 : 1;
|
||||
s += (*s == '-' || *s == '+');
|
||||
|
||||
// read exponent
|
||||
int exppower = 0;
|
||||
|
||||
while (unsigned(*s - '0') < 10)
|
||||
{
|
||||
exppower = exppower * 10 + (*s - '0');
|
||||
s++;
|
||||
}
|
||||
|
||||
// done!
|
||||
power += expsign * exppower;
|
||||
}
|
||||
|
||||
// return end-of-string
|
||||
*end = s;
|
||||
|
||||
// note: this is precise if result < 9e15
|
||||
// for longer inputs we lose a bit of precision here
|
||||
if (unsigned(-power) < sizeof(powers) / sizeof(powers[0]))
|
||||
return float(sign * result / powers[-power]);
|
||||
else if (unsigned(power) < sizeof(powers) / sizeof(powers[0]))
|
||||
return float(sign * result * powers[power]);
|
||||
else
|
||||
return float(sign * result * pow(10.0, power));
|
||||
}
|
||||
|
||||
static const char* parseFace(const char* s, int& vi, int& vti, int& vni)
|
||||
{
|
||||
while (*s == ' ' || *s == '\t')
|
||||
s++;
|
||||
|
||||
vi = parseInt(s, &s);
|
||||
|
||||
if (*s != '/')
|
||||
return s;
|
||||
s++;
|
||||
|
||||
// handle vi//vni indices
|
||||
if (*s != '/')
|
||||
vti = parseInt(s, &s);
|
||||
|
||||
if (*s != '/')
|
||||
return s;
|
||||
s++;
|
||||
|
||||
vni = parseInt(s, &s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
ObjFile::ObjFile()
|
||||
: v(0)
|
||||
, v_size(0)
|
||||
, v_cap(0)
|
||||
, vt(0)
|
||||
, vt_size(0)
|
||||
, vt_cap(0)
|
||||
, vn(0)
|
||||
, vn_size(0)
|
||||
, vn_cap(0)
|
||||
, f(0)
|
||||
, f_size(0)
|
||||
, f_cap(0)
|
||||
, g(0)
|
||||
, g_size(0)
|
||||
, g_cap(0)
|
||||
{
|
||||
}
|
||||
|
||||
ObjFile::~ObjFile()
|
||||
{
|
||||
delete[] v;
|
||||
delete[] vt;
|
||||
delete[] vn;
|
||||
delete[] f;
|
||||
delete[] g;
|
||||
}
|
||||
|
||||
void objParseLine(ObjFile& result, const char* line)
|
||||
{
|
||||
if (line[0] == 'v' && line[1] == ' ')
|
||||
{
|
||||
const char* s = line + 2;
|
||||
|
||||
float x = parseFloat(s, &s);
|
||||
float y = parseFloat(s, &s);
|
||||
float z = parseFloat(s, &s);
|
||||
|
||||
if (result.v_size + 3 > result.v_cap)
|
||||
growArray(result.v, result.v_cap);
|
||||
|
||||
result.v[result.v_size++] = x;
|
||||
result.v[result.v_size++] = y;
|
||||
result.v[result.v_size++] = z;
|
||||
}
|
||||
else if (line[0] == 'v' && line[1] == 't' && line[2] == ' ')
|
||||
{
|
||||
const char* s = line + 3;
|
||||
|
||||
float u = parseFloat(s, &s);
|
||||
float v = parseFloat(s, &s);
|
||||
float w = parseFloat(s, &s);
|
||||
|
||||
if (result.vt_size + 3 > result.vt_cap)
|
||||
growArray(result.vt, result.vt_cap);
|
||||
|
||||
result.vt[result.vt_size++] = u;
|
||||
result.vt[result.vt_size++] = v;
|
||||
result.vt[result.vt_size++] = w;
|
||||
}
|
||||
else if (line[0] == 'v' && line[1] == 'n' && line[2] == ' ')
|
||||
{
|
||||
const char* s = line + 3;
|
||||
|
||||
float x = parseFloat(s, &s);
|
||||
float y = parseFloat(s, &s);
|
||||
float z = parseFloat(s, &s);
|
||||
|
||||
if (result.vn_size + 3 > result.vn_cap)
|
||||
growArray(result.vn, result.vn_cap);
|
||||
|
||||
result.vn[result.vn_size++] = x;
|
||||
result.vn[result.vn_size++] = y;
|
||||
result.vn[result.vn_size++] = z;
|
||||
}
|
||||
else if (line[0] == 'f' && line[1] == ' ')
|
||||
{
|
||||
const char* s = line + 2;
|
||||
|
||||
if (!result.g)
|
||||
{
|
||||
growArray(result.g, result.g_cap);
|
||||
|
||||
ObjGroup g = {};
|
||||
result.g[result.g_size++] = g;
|
||||
}
|
||||
|
||||
size_t v = result.v_size / 3;
|
||||
size_t vt = result.vt_size / 3;
|
||||
size_t vn = result.vn_size / 3;
|
||||
|
||||
int fv = 0;
|
||||
int f[3][3] = {};
|
||||
|
||||
while (*s)
|
||||
{
|
||||
int vi = 0, vti = 0, vni = 0;
|
||||
s = parseFace(s, vi, vti, vni);
|
||||
|
||||
if (vi == 0)
|
||||
break;
|
||||
|
||||
f[fv][0] = fixupIndex(vi, v);
|
||||
f[fv][1] = fixupIndex(vti, vt);
|
||||
f[fv][2] = fixupIndex(vni, vn);
|
||||
|
||||
if (fv == 2)
|
||||
{
|
||||
if (result.f_size + 9 > result.f_cap)
|
||||
growArray(result.f, result.f_cap);
|
||||
|
||||
memcpy(&result.f[result.f_size], f, 9 * sizeof(int));
|
||||
result.f_size += 9;
|
||||
|
||||
result.g[result.g_size - 1].index_count += 3;
|
||||
|
||||
f[1][0] = f[2][0];
|
||||
f[1][1] = f[2][1];
|
||||
f[1][2] = f[2][2];
|
||||
}
|
||||
else
|
||||
{
|
||||
fv++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strncmp(line, "usemtl", 6) == 0)
|
||||
{
|
||||
const char* s = line + 6;
|
||||
|
||||
// skip whitespace
|
||||
while (*s == ' ' || *s == '\t')
|
||||
s++;
|
||||
|
||||
if (result.g_size + 1 > result.g_cap)
|
||||
growArray(result.g, result.g_cap);
|
||||
|
||||
ObjGroup g = {};
|
||||
g.index_offset = result.f_size / 3;
|
||||
|
||||
strncpy(g.material, s, sizeof(g.material));
|
||||
g.material[sizeof(g.material) - 1] = 0;
|
||||
|
||||
result.g[result.g_size++] = g;
|
||||
}
|
||||
}
|
||||
|
||||
bool objParseFile(ObjFile& result, const char* path)
|
||||
{
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
char buffer[65536];
|
||||
size_t size = 0;
|
||||
|
||||
while (!feof(file))
|
||||
{
|
||||
size += fread(buffer + size, 1, sizeof(buffer) - size, file);
|
||||
|
||||
size_t line = 0;
|
||||
|
||||
while (line < size)
|
||||
{
|
||||
// find the end of current line
|
||||
void* eol = memchr(buffer + line, '\n', size - line);
|
||||
if (!eol)
|
||||
break;
|
||||
|
||||
// zero-terminate for objParseLine
|
||||
size_t next = static_cast<char*>(eol) - buffer;
|
||||
|
||||
buffer[next] = 0;
|
||||
|
||||
// process next line
|
||||
objParseLine(result, buffer + line);
|
||||
|
||||
line = next + 1;
|
||||
}
|
||||
|
||||
// move prefix of the last line in the buffer to the beginning of the buffer for next iteration
|
||||
assert(line <= size);
|
||||
|
||||
memmove(buffer, buffer + line, size - line);
|
||||
size -= line;
|
||||
}
|
||||
|
||||
if (size)
|
||||
{
|
||||
// process last line
|
||||
assert(size < sizeof(buffer));
|
||||
buffer[size] = 0;
|
||||
|
||||
objParseLine(result, buffer);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool objValidate(const ObjFile& result)
|
||||
{
|
||||
size_t v = result.v_size / 3;
|
||||
size_t vt = result.vt_size / 3;
|
||||
size_t vn = result.vn_size / 3;
|
||||
|
||||
for (size_t i = 0; i < result.f_size; i += 3)
|
||||
{
|
||||
int vi = result.f[i + 0];
|
||||
int vti = result.f[i + 1];
|
||||
int vni = result.f[i + 2];
|
||||
|
||||
if (vi < 0)
|
||||
return false;
|
||||
|
||||
if (vi >= 0 && size_t(vi) >= v)
|
||||
return false;
|
||||
|
||||
if (vti >= 0 && size_t(vti) >= vt)
|
||||
return false;
|
||||
|
||||
if (vni >= 0 && size_t(vni) >= vn)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
42
3rdparty/meshoptimizer/tools/objparser.h
vendored
42
3rdparty/meshoptimizer/tools/objparser.h
vendored
@ -1,42 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct ObjGroup
|
||||
{
|
||||
char material[256];
|
||||
|
||||
size_t index_offset;
|
||||
size_t index_count;
|
||||
};
|
||||
|
||||
class ObjFile
|
||||
{
|
||||
public:
|
||||
float* v; // positions; stride 3 (xyz)
|
||||
size_t v_size, v_cap;
|
||||
|
||||
float* vt; // texture coordinates; stride 3 (uvw)
|
||||
size_t vt_size, vt_cap;
|
||||
|
||||
float* vn; // vertex normals; stride 3 (xyz)
|
||||
size_t vn_size, vn_cap;
|
||||
|
||||
int* f; // face elements; stride 9 (3 groups of indices into v/vt/vn)
|
||||
size_t f_size, f_cap;
|
||||
|
||||
ObjGroup* g;
|
||||
size_t g_size, g_cap;
|
||||
|
||||
ObjFile();
|
||||
~ObjFile();
|
||||
|
||||
private:
|
||||
ObjFile(const ObjFile&);
|
||||
ObjFile& operator=(const ObjFile&);
|
||||
};
|
||||
|
||||
void objParseLine(ObjFile& result, const char* line);
|
||||
bool objParseFile(ObjFile& result, const char* path);
|
||||
|
||||
bool objValidate(const ObjFile& result);
|
40
3rdparty/meshoptimizer/tools/vcachetester.cpp
vendored
40
3rdparty/meshoptimizer/tools/vcachetester.cpp
vendored
@ -11,7 +11,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "../src/meshoptimizer.h"
|
||||
#include "objparser.h"
|
||||
#include "fast_obj.h"
|
||||
|
||||
#pragma comment(lib, "d3d11.lib")
|
||||
#pragma comment(lib, "d3dcompiler.lib")
|
||||
@ -396,26 +396,40 @@ void testCacheMeshes(IDXGIAdapter* adapter, int argc, char** argv)
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjFile file;
|
||||
|
||||
if (!objParseFile(file, path))
|
||||
fastObjMesh* obj = fast_obj_read(path);
|
||||
if (!obj)
|
||||
{
|
||||
printf("Error loading %s: file not found\n", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!objValidate(file))
|
||||
{
|
||||
printf("Error loading %s: invalid file data\n", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> ib1;
|
||||
|
||||
for (size_t i = 0; i < file.f_size; i += 3)
|
||||
ib1.push_back(file.f[i]);
|
||||
size_t index_offset = 0;
|
||||
|
||||
unsigned int vertex_count = file.v_size / 3;
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
{
|
||||
for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
|
||||
{
|
||||
fastObjIndex gi = obj->indices[index_offset + j];
|
||||
|
||||
// triangulate polygon on the fly; offset-3 is always the first polygon vertex
|
||||
if (j >= 3)
|
||||
{
|
||||
unsigned int i0 = ib1[ib1.size() - 3];
|
||||
unsigned int i1 = ib1[ib1.size() - 1];
|
||||
|
||||
ib1.push_back(i0);
|
||||
ib1.push_back(i1);
|
||||
}
|
||||
|
||||
ib1.push_back(gi.p);
|
||||
}
|
||||
|
||||
index_offset += obj->face_vertices[i];
|
||||
}
|
||||
|
||||
unsigned int vertex_count = obj->position_count;
|
||||
unsigned int index_count = ib1.size();
|
||||
|
||||
unsigned int invocations1 = queryVSInvocations(device, context, ib1.data(), index_count);
|
||||
|
337
3rdparty/meshoptimizer/tools/vcachetuner.cpp
vendored
337
3rdparty/meshoptimizer/tools/vcachetuner.cpp
vendored
@ -1,5 +1,5 @@
|
||||
#include "../src/meshoptimizer.h"
|
||||
#include "objparser.h"
|
||||
#include "fast_obj.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
@ -64,6 +64,7 @@ struct State
|
||||
{
|
||||
float cache[kCacheSizeMax];
|
||||
float live[kValenceMax];
|
||||
float fitness;
|
||||
};
|
||||
|
||||
struct Mesh
|
||||
@ -98,21 +99,17 @@ Mesh gridmesh(unsigned int N)
|
||||
|
||||
Mesh objmesh(const char* path)
|
||||
{
|
||||
ObjFile file;
|
||||
|
||||
if (!objParseFile(file, path))
|
||||
fastObjMesh* obj = fast_obj_read(path);
|
||||
if (!obj)
|
||||
{
|
||||
printf("Error loading %s: file not found\n", path);
|
||||
return Mesh();
|
||||
}
|
||||
|
||||
if (!objValidate(file))
|
||||
{
|
||||
printf("Error loading %s: invalid file data\n", path);
|
||||
return Mesh();
|
||||
}
|
||||
size_t total_indices = 0;
|
||||
|
||||
size_t total_indices = file.f_size / 3;
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
total_indices += 3 * (obj->face_vertices[i] - 2);
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
@ -123,29 +120,44 @@ Mesh objmesh(const char* path)
|
||||
|
||||
std::vector<Vertex> vertices(total_indices);
|
||||
|
||||
for (size_t i = 0; i < total_indices; ++i)
|
||||
size_t vertex_offset = 0;
|
||||
size_t index_offset = 0;
|
||||
|
||||
for (unsigned int i = 0; i < obj->face_count; ++i)
|
||||
{
|
||||
int vi = file.f[i * 3 + 0];
|
||||
int vti = file.f[i * 3 + 1];
|
||||
int vni = file.f[i * 3 + 2];
|
||||
for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
|
||||
{
|
||||
fastObjIndex gi = obj->indices[index_offset + j];
|
||||
|
||||
Vertex v =
|
||||
{
|
||||
file.v[vi * 3 + 0],
|
||||
file.v[vi * 3 + 1],
|
||||
file.v[vi * 3 + 2],
|
||||
Vertex v =
|
||||
{
|
||||
obj->positions[gi.p * 3 + 0],
|
||||
obj->positions[gi.p * 3 + 1],
|
||||
obj->positions[gi.p * 3 + 2],
|
||||
obj->normals[gi.n * 3 + 0],
|
||||
obj->normals[gi.n * 3 + 1],
|
||||
obj->normals[gi.n * 3 + 2],
|
||||
obj->texcoords[gi.t * 2 + 0],
|
||||
obj->texcoords[gi.t * 2 + 1],
|
||||
};
|
||||
|
||||
vni >= 0 ? file.vn[vni * 3 + 0] : 0,
|
||||
vni >= 0 ? file.vn[vni * 3 + 1] : 0,
|
||||
vni >= 0 ? file.vn[vni * 3 + 2] : 0,
|
||||
// triangulate polygon on the fly; offset-3 is always the first polygon vertex
|
||||
if (j >= 3)
|
||||
{
|
||||
vertices[vertex_offset + 0] = vertices[vertex_offset - 3];
|
||||
vertices[vertex_offset + 1] = vertices[vertex_offset - 1];
|
||||
vertex_offset += 2;
|
||||
}
|
||||
|
||||
vti >= 0 ? file.vt[vti * 3 + 0] : 0,
|
||||
vti >= 0 ? file.vt[vti * 3 + 1] : 0,
|
||||
};
|
||||
vertices[vertex_offset] = v;
|
||||
vertex_offset++;
|
||||
}
|
||||
|
||||
vertices[i] = v;
|
||||
index_offset += obj->face_vertices[i];
|
||||
}
|
||||
|
||||
fast_obj_destroy(obj);
|
||||
|
||||
Mesh result;
|
||||
|
||||
std::vector<unsigned int> remap(total_indices);
|
||||
@ -193,17 +205,7 @@ float fitness_score(const State& state, const std::vector<Mesh>& meshes)
|
||||
return result / count;
|
||||
}
|
||||
|
||||
float rndcache()
|
||||
{
|
||||
return rand01();
|
||||
}
|
||||
|
||||
float rndlive()
|
||||
{
|
||||
return rand01();
|
||||
}
|
||||
|
||||
std::vector<State> gen0(size_t count)
|
||||
std::vector<State> gen0(size_t count, const std::vector<Mesh>& meshes)
|
||||
{
|
||||
std::vector<State> result;
|
||||
|
||||
@ -212,10 +214,12 @@ std::vector<State> gen0(size_t count)
|
||||
State state = {};
|
||||
|
||||
for (int j = 0; j < kCacheSizeMax; ++j)
|
||||
state.cache[j] = rndcache();
|
||||
state.cache[j] = rand01();
|
||||
|
||||
for (int j = 0; j < kValenceMax; ++j)
|
||||
state.live[j] = rndlive();
|
||||
state.live[j] = rand01();
|
||||
|
||||
state.fitness = fitness_score(state, meshes);
|
||||
|
||||
result.push_back(state);
|
||||
}
|
||||
@ -223,239 +227,71 @@ std::vector<State> gen0(size_t count)
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t rndindex(const std::vector<float>& prob)
|
||||
// https://en.wikipedia.org/wiki/Differential_evolution
|
||||
// Good Parameters for Differential Evolution. Magnus Erik Hvass Pedersen, 2010
|
||||
std::pair<State, float> genN(std::vector<State>& seed, const std::vector<Mesh>& meshes, float crossover = 0.8803f, float weight = 0.4717f)
|
||||
{
|
||||
float r = rand01();
|
||||
|
||||
for (size_t i = 0; i < prob.size(); ++i)
|
||||
{
|
||||
r -= prob[i];
|
||||
|
||||
if (r <= 0)
|
||||
return i;
|
||||
}
|
||||
|
||||
return prob.size() - 1;
|
||||
}
|
||||
|
||||
State mutate(const State& state)
|
||||
{
|
||||
State result = state;
|
||||
|
||||
if (rand01() < 0.7f)
|
||||
{
|
||||
size_t idxcache = std::min(int(rand01() * kCacheSizeMax + 0.5f), int(kCacheSizeMax - 1));
|
||||
|
||||
result.cache[idxcache] = rndcache();
|
||||
}
|
||||
|
||||
if (rand01() < 0.7f)
|
||||
{
|
||||
size_t idxlive = std::min(int(rand01() * kValenceMax + 0.5f), int(kValenceMax - 1));
|
||||
|
||||
result.live[idxlive] = rndlive();
|
||||
}
|
||||
|
||||
if (rand01() < 0.2f)
|
||||
{
|
||||
uint32_t mask = rand32();
|
||||
|
||||
for (size_t i = 0; i < kCacheSizeMax; ++i)
|
||||
if (mask & (1 << i))
|
||||
result.cache[i] *= 0.9f + 0.2f * rand01();
|
||||
}
|
||||
|
||||
if (rand01() < 0.2f)
|
||||
{
|
||||
uint32_t mask = rand32();
|
||||
|
||||
for (size_t i = 0; i < kValenceMax; ++i)
|
||||
if (mask & (1 << i))
|
||||
result.live[i] *= 0.9f + 0.2f * rand01();
|
||||
}
|
||||
|
||||
if (rand01() < 0.05f)
|
||||
{
|
||||
uint32_t mask = rand32();
|
||||
|
||||
for (size_t i = 0; i < kCacheSizeMax; ++i)
|
||||
if (mask & (1 << i))
|
||||
result.cache[i] = rndcache();
|
||||
}
|
||||
|
||||
if (rand01() < 0.05f)
|
||||
{
|
||||
uint32_t mask = rand32();
|
||||
|
||||
for (size_t i = 0; i < kValenceMax; ++i)
|
||||
if (mask & (1 << i))
|
||||
result.live[i] = rndlive();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool accept(float fitnew, float fitold, float temp)
|
||||
{
|
||||
if (fitnew >= fitold)
|
||||
return true;
|
||||
|
||||
if (temp == 0)
|
||||
return false;
|
||||
|
||||
float prob = exp2((fitnew - fitold) / temp);
|
||||
|
||||
return rand01() < prob;
|
||||
}
|
||||
|
||||
std::pair<State, float> genN_SA(std::vector<State>& seed, const std::vector<Mesh>& meshes, size_t steps)
|
||||
{
|
||||
std::vector<State> result;
|
||||
result.reserve(seed.size() * (1 + steps));
|
||||
|
||||
// perform several parallel steps of mutation for each temperature
|
||||
for (size_t i = 0; i < seed.size(); ++i)
|
||||
{
|
||||
result.push_back(seed[i]);
|
||||
|
||||
for (size_t s = 0; s < steps; ++s)
|
||||
result.push_back(mutate(seed[i]));
|
||||
}
|
||||
|
||||
// compute fitness for all temperatures & mutations in parallel
|
||||
std::vector<float> resultfit(result.size());
|
||||
|
||||
#pragma omp parallel for
|
||||
for (size_t i = 0; i < result.size(); ++i)
|
||||
{
|
||||
resultfit[i] = fitness_score(result[i], meshes);
|
||||
}
|
||||
|
||||
// perform annealing for each temperature
|
||||
std::vector<float> seedfit(seed.size());
|
||||
std::vector<State> result(seed.size());
|
||||
|
||||
for (size_t i = 0; i < seed.size(); ++i)
|
||||
{
|
||||
size_t offset = i * (1 + steps);
|
||||
|
||||
seedfit[i] = resultfit[offset];
|
||||
|
||||
float temp = (float(i) / float(seed.size() - 1)) / 0.1f;
|
||||
|
||||
for (size_t s = 0; s < steps; ++s)
|
||||
for (;;)
|
||||
{
|
||||
if (accept(resultfit[offset + s + 1], seedfit[i], temp))
|
||||
int a = rand32() % seed.size();
|
||||
int b = rand32() % seed.size();
|
||||
int c = rand32() % seed.size();
|
||||
|
||||
if (a == b || a == c || b == c || a == int(i) || b == int(i) || c == int(i))
|
||||
continue;
|
||||
|
||||
int rc = rand32() % kCacheSizeMax;
|
||||
int rl = rand32() % kValenceMax;
|
||||
|
||||
for (int j = 0; j < kCacheSizeMax; ++j)
|
||||
{
|
||||
seedfit[i] = resultfit[offset + s + 1];
|
||||
seed[i] = result[offset + s + 1];
|
||||
float r = rand01();
|
||||
|
||||
if (r < crossover || j == rc)
|
||||
result[i].cache[j] = std::max(0.f, std::min(1.f, seed[a].cache[j] + weight * (seed[b].cache[j] - seed[c].cache[j])));
|
||||
else
|
||||
result[i].cache[j] = seed[i].cache[j];
|
||||
}
|
||||
|
||||
for (int j = 0; j < kValenceMax; ++j)
|
||||
{
|
||||
float r = rand01();
|
||||
|
||||
if (r < crossover || j == rl)
|
||||
result[i].live[j] = std::max(0.f, std::min(1.f, seed[a].live[j] + weight * (seed[b].live[j] - seed[c].live[j])));
|
||||
else
|
||||
result[i].live[j] = seed[i].live[j];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// perform promotion from each temperature to the next one
|
||||
for (size_t i = seed.size() - 1; i > 0; --i)
|
||||
{
|
||||
if (seedfit[i] > seedfit[i - 1])
|
||||
{
|
||||
seedfit[i - 1] = seedfit[i];
|
||||
seed[i - 1] = seed[i];
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(seed[0], seedfit[0]);
|
||||
}
|
||||
|
||||
std::pair<State, float> genN_GA(std::vector<State>& seed, const std::vector<Mesh>& meshes, float crossover, float mutate)
|
||||
{
|
||||
std::vector<State> result;
|
||||
result.reserve(seed.size());
|
||||
|
||||
std::vector<float> seedprob(seed.size());
|
||||
|
||||
#pragma omp parallel for
|
||||
#pragma omp parallel for
|
||||
for (size_t i = 0; i < seed.size(); ++i)
|
||||
{
|
||||
seedprob[i] = fitness_score(seed[i], meshes);
|
||||
result[i].fitness = fitness_score(result[i], meshes);
|
||||
}
|
||||
|
||||
State best = {};
|
||||
float bestfit = 0;
|
||||
float probsum = 0;
|
||||
|
||||
for (size_t i = 0; i < seed.size(); ++i)
|
||||
{
|
||||
float score = seedprob[i];
|
||||
probsum += score;
|
||||
if (result[i].fitness > seed[i].fitness)
|
||||
seed[i] = result[i];
|
||||
|
||||
if (score > bestfit)
|
||||
if (seed[i].fitness > bestfit)
|
||||
{
|
||||
best = seed[i];
|
||||
bestfit = score;
|
||||
bestfit = seed[i].fitness;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& prob : seedprob)
|
||||
{
|
||||
prob /= probsum;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> seedidx;
|
||||
seedidx.reserve(seed.size());
|
||||
for (size_t i = 0; i < seed.size(); ++i)
|
||||
seedidx.push_back(i);
|
||||
|
||||
std::sort(seedidx.begin(), seedidx.end(), [&](size_t l, size_t r) { return seedprob[l] < seedprob[r]; });
|
||||
|
||||
while (result.size() < seed.size() / 4)
|
||||
{
|
||||
size_t idx = seedidx.back();
|
||||
seedidx.pop_back();
|
||||
|
||||
result.push_back(seed[idx]);
|
||||
}
|
||||
|
||||
while (result.size() < seed.size())
|
||||
{
|
||||
State s0 = seed[rndindex(seedprob)];
|
||||
State s1 = seed[rndindex(seedprob)];
|
||||
|
||||
State state = s0;
|
||||
|
||||
// crossover
|
||||
if (rand01() < crossover)
|
||||
{
|
||||
size_t idxcache = std::min(int(rand01() * kCacheSizeMax + 0.5f), 15);
|
||||
|
||||
memcpy(state.cache + idxcache, s1.cache + idxcache, (kCacheSizeMax - idxcache) * sizeof(float));
|
||||
}
|
||||
|
||||
if (rand01() < crossover)
|
||||
{
|
||||
size_t idxlive = std::min(int(rand01() * kValenceMax + 0.5f), 7);
|
||||
|
||||
memcpy(state.live + idxlive, s1.live + idxlive, (kValenceMax - idxlive) * sizeof(float));
|
||||
}
|
||||
|
||||
// mutate
|
||||
if (rand01() < mutate)
|
||||
{
|
||||
size_t idxcache = std::min(int(rand01() * kCacheSizeMax + 0.5f), 15);
|
||||
|
||||
state.cache[idxcache] = rndcache();
|
||||
}
|
||||
|
||||
if (rand01() < mutate)
|
||||
{
|
||||
size_t idxlive = std::min(int(rand01() * kValenceMax + 0.5f), 7);
|
||||
|
||||
state.live[idxlive] = rndlive();
|
||||
}
|
||||
|
||||
result.push_back(state);
|
||||
}
|
||||
|
||||
seed.swap(result);
|
||||
|
||||
return std::make_pair(best, bestfit);
|
||||
}
|
||||
|
||||
@ -545,7 +381,7 @@ int main(int argc, char** argv)
|
||||
}
|
||||
else
|
||||
{
|
||||
pop = gen0(annealing ? 32 : 1000);
|
||||
pop = gen0(95, meshes);
|
||||
}
|
||||
|
||||
printf("%d meshes, %.1fM triangles\n", int(meshes.size()), double(total_triangles) / 1e6);
|
||||
@ -559,7 +395,7 @@ int main(int argc, char** argv)
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto best = annealing ? genN_SA(pop, meshes, 31) : genN_GA(pop, meshes, 0.7f, 0.3f);
|
||||
auto best = genN(pop, meshes);
|
||||
gen++;
|
||||
|
||||
compute_atvr(best.first, meshes[0], atvr_0);
|
||||
@ -571,7 +407,8 @@ int main(int argc, char** argv)
|
||||
{
|
||||
char buf[128];
|
||||
sprintf(buf, "gcloud logging write vcache-log \"fitness %f; grid %f %f %s %f %f\"", best.second, atvr_0[0], atvr_0[1], argv[argc - 1], atvr_N[0], atvr_N[1]);
|
||||
system(buf);
|
||||
int rc = system(buf);
|
||||
(void)rc;
|
||||
}
|
||||
|
||||
dump_state(best.first);
|
||||
|
Loading…
Reference in New Issue
Block a user