Updated meshoptimizer.

This commit is contained in:
Бранимир Караџић 2019-07-25 21:53:38 -07:00
parent 28187d367f
commit e8422851c5
27 changed files with 13385 additions and 1343 deletions

View File

@ -22,7 +22,8 @@ script:
- if [[ "$TRAVIS_COMPILER" == "clang" ]]; then make config=sanitize test; fi - if [[ "$TRAVIS_COMPILER" == "clang" ]]; then make config=sanitize test; fi
- if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make config=debug test; fi - if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make config=debug test; fi
- if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make config=release 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 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 ./Debug/demo.exe demo/pirate.obj; fi
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake --build . -- -property:Configuration=Release -verbosity:minimal; fi - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake --build . -- -property:Configuration=Release -verbosity:minimal; fi
@ -34,4 +35,4 @@ after_script:
find . -type f -name '*.gcno' -exec gcov -p {} +; find . -type f -name '*.gcno' -exec gcov -p {} +;
sed -i -e "s/#####\(.*\)\(\/\/ unreachable.*\)/ -\1\2/" *.gcov; sed -i -e "s/#####\(.*\)\(\/\/ unreachable.*\)/ -\1\2/" *.gcov;
bash <(curl -s https://codecov.io/bash) -f 'src#*.gcov' -X search; bash <(curl -s https://codecov.io/bash) -f 'src#*.gcov' -X search;
fi fi

View File

@ -6,6 +6,7 @@ option(BUILD_TOOLS "Build tools" OFF)
set(SOURCES set(SOURCES
src/meshoptimizer.h src/meshoptimizer.h
src/allocator.cpp
src/clusterizer.cpp src/clusterizer.cpp
src/indexcodec.cpp src/indexcodec.cpp
src/indexgenerator.cpp src/indexgenerator.cpp
@ -30,11 +31,11 @@ else()
endif() endif()
if(BUILD_DEMO) 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) target_link_libraries(demo meshoptimizer)
endif() endif()
if(BUILD_TOOLS) if(BUILD_TOOLS)
add_executable(meshencoder tools/meshencoder.cpp tools/objparser.cpp) add_executable(gltfpack tools/gltfpack.cpp tools/meshloader.cpp)
target_link_libraries(meshencoder meshoptimizer) target_link_libraries(gltfpack meshoptimizer)
endif() endif()

View File

@ -9,13 +9,13 @@ BUILD=build/$(config)
LIBRARY_SOURCES=$(wildcard src/*.cpp) LIBRARY_SOURCES=$(wildcard src/*.cpp)
LIBRARY_OBJECTS=$(LIBRARY_SOURCES:%=$(BUILD)/%.o) 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) DEMO_OBJECTS=$(DEMO_SOURCES:%=$(BUILD)/%.o)
ENCODER_SOURCES=tools/meshencoder.cpp tools/objparser.cpp GLTFPACK_SOURCES=tools/gltfpack.cpp tools/meshloader.cpp
ENCODER_OBJECTS=$(ENCODER_SOURCES:%=$(BUILD)/%.o) GLTFPACK_OBJECTS=$(GLTFPACK_SOURCES:%=$(BUILD)/%.o)
OBJECTS=$(LIBRARY_OBJECTS) $(DEMO_OBJECTS) $(ENCODER_OBJECTS) OBJECTS=$(LIBRARY_OBJECTS) $(DEMO_OBJECTS) $(GLTFPACK_OBJECTS)
LIBRARY=$(BUILD)/libmeshoptimizer.a LIBRARY=$(BUILD)/libmeshoptimizer.a
EXECUTABLE=$(BUILD)/meshoptimizer EXECUTABLE=$(BUILD)/meshoptimizer
@ -65,15 +65,15 @@ dev: $(EXECUTABLE)
$(EXECUTABLE) -d $(files) $(EXECUTABLE) -d $(files)
format: 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 $@ $(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 @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 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/decoder.wasm | base64 -w 0)\";#" $@ sed -i "s#\(var wasm = \)\".*\";#\\1\"$$(cat build/meshopt_decoder.wasm | base64 -w 0)\";#" $@
$(EXECUTABLE): $(DEMO_OBJECTS) $(LIBRARY) $(EXECUTABLE): $(DEMO_OBJECTS) $(LIBRARY)
$(CXX) $^ $(LDFLAGS) -o $@ $(CXX) $^ $(LDFLAGS) -o $@

View File

@ -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: 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 ## 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. 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 ```js
// ready is a Promise that is resolved when (asynchronous) WebAssembly compilation finishes // 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); 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 ## Triangle strip conversion
@ -170,10 +170,12 @@ This library provides an algorithm for converting a vertex cache optimized trian
```c++ ```c++
std::vector<unsigned int> strip(meshopt_stripifyBound(index_count)); 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 ## 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. 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; float threshold = 0.2f;
size_t target_index_count = size_t(index_count * threshold); size_t target_index_count = size_t(index_count * threshold);
float target_error = 1e-2f; 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. 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; float threshold = 0.2f;
size_t target_index_count = size_t(index_count * threshold); 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); 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. 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. 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 ## License
This library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md). 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

File diff suppressed because it is too large Load Diff

View File

@ -30,30 +30,19 @@
<a href="https://github.com/zeux/meshoptimizer" target="_blank" rel="noopener">meshoptimizer</a> <a href="https://github.com/zeux/meshoptimizer" target="_blank" rel="noopener">meshoptimizer</a>
</div> </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="../js/meshopt_decoder.js"></script>
<script src="../tools/OptMeshLoader.js"></script> <script src="GLTFLoader.js"></script>
<script> <script>
var container; var container;
var camera, scene, renderer; var camera, scene, renderer, mixer, clock;
var windowHalfX = window.innerWidth / 2; var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 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(); init();
animate(); animate();
@ -67,8 +56,9 @@
camera.position.z = 3.0; camera.position.z = 3.0;
scene = new THREE.Scene(); 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); scene.add(ambientLight);
var pointLight = new THREE.PointLight(0xffffff, 0.8); var pointLight = new THREE.PointLight(0xffffff, 0.8);
@ -77,16 +67,27 @@
scene.add(camera); scene.add(camera);
var onProgress = function (xhr) {}; var onProgress = function (xhr) {};
var onError = function () {}; var onError = function (e) {
console.log(e);
};
new THREE.OptMeshLoader() var loader = new THREE.GLTFLoader()
.setDecoder(MeshoptDecoder) loader.setMeshoptDecoder(MeshoptDecoder)
.setMaterials(null) // materials can be fetched using MTLLoader loader.load('pirate.glb', function (gltf) {
.setPath('./') var bbox = new THREE.Box3().setFromObject(gltf.scene);
.load('pirate.optmesh', function (object) var scale = 2 / (bbox.max.y - bbox.min.y);
{
scene.add(object); gltf.scene.scale.set(scale, scale, scale);
}, onProgress, onError); 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 = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio); renderer.setPixelRatio(window.devicePixelRatio);
@ -94,6 +95,8 @@
container.appendChild(renderer.domElement); container.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false); window.addEventListener('resize', onWindowResize, false);
clock = new THREE.Clock();
} }
function onWindowResize() function onWindowResize()
@ -109,6 +112,10 @@
function animate() function animate()
{ {
if (mixer) {
mixer.update(clock.getDelta());
}
requestAnimationFrame(animate); requestAnimationFrame(animate);
render(); render();
} }

View File

@ -8,7 +8,7 @@
#include <vector> #include <vector>
#include "../tools/objparser.h" #include "../tools/fast_obj.h"
#include "miniz.h" #include "miniz.h"
// This file uses assert() to verify algorithm correctness // This file uses assert() to verify algorithm correctness
@ -66,47 +66,58 @@ union Triangle {
Mesh parseObj(const char* path, double& reindex) Mesh parseObj(const char* path, double& reindex)
{ {
ObjFile file; fastObjMesh* obj = fast_obj_read(path);
if (!obj)
if (!objParseFile(file, path))
{ {
printf("Error loading %s: file not found\n", path); printf("Error loading %s: file not found\n", path);
return Mesh(); return Mesh();
} }
if (!objValidate(file)) size_t total_indices = 0;
{
printf("Error loading %s: invalid file data\n", path);
return Mesh();
}
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); 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]; for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
int vti = file.f[i * 3 + 1]; {
int vni = file.f[i * 3 + 2]; fastObjIndex gi = obj->indices[index_offset + j];
Vertex v = Vertex v =
{ {
file.v[vi * 3 + 0], obj->positions[gi.p * 3 + 0],
file.v[vi * 3 + 1], obj->positions[gi.p * 3 + 1],
file.v[vi * 3 + 2], 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, // triangulate polygon on the fly; offset-3 is always the first polygon vertex
vni >= 0 ? file.vn[vni * 3 + 1] : 0, if (j >= 3)
vni >= 0 ? file.vn[vni * 3 + 2] : 0, {
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, vertices[vertex_offset] = v;
vti >= 0 ? file.vt[vti * 3 + 1] : 0, vertex_offset++;
}; }
vertices[i] = v; index_offset += obj->face_vertices[i];
} }
fast_obj_destroy(obj);
reindex = timestamp(); reindex = timestamp();
Mesh result; Mesh result;
@ -621,16 +632,18 @@ void encodeVertex(const Mesh& mesh, const char* pvn)
(double(result.size() * sizeof(PV)) / (1 << 30)) / (end - middle)); (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 // note: input mesh is assumed to be optimized for vertex cache and vertex fetch
double start = timestamp(); double start = timestamp();
std::vector<unsigned int> strip(meshopt_stripifyBound(mesh.indices.size())); 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(); double end = timestamp();
Mesh copy = mesh; Mesh copy = mesh;
copy.indices.resize(meshopt_unstripify(&copy.indices[0], &strip[0], strip.size())); copy.indices.resize(meshopt_unstripify(&copy.indices[0], &strip[0], strip.size(), restart_index));
assert(copy.indices.size() <= meshopt_unstripifyBound(strip.size())); assert(copy.indices.size() <= meshopt_unstripifyBound(strip.size()));
assert(isMeshValid(copy)); assert(isMeshValid(copy));
@ -641,7 +654,8 @@ void stripify(const Mesh& mesh)
meshopt_VertexCacheStatistics vcs_amd = meshopt_analyzeVertexCache(&copy.indices[0], mesh.indices.size(), mesh.vertices.size(), 14, 64, 128); meshopt_VertexCacheStatistics vcs_amd = meshopt_analyzeVertexCache(&copy.indices[0], mesh.indices.size(), mesh.vertices.size(), 14, 64, 128);
meshopt_VertexCacheStatistics vcs_intel = meshopt_analyzeVertexCache(&copy.indices[0], mesh.indices.size(), mesh.vertices.size(), 128, 0, 0); meshopt_VertexCacheStatistics vcs_intel = meshopt_analyzeVertexCache(&copy.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, vcs.acmr, vcs.atvr, vcs_nv.atvr, vcs_amd.atvr, vcs_intel.atvr,
int(strip.size()), double(strip.size()) / double(mesh.indices.size()) * 100, int(strip.size()), double(strip.size()) / double(mesh.indices.size()) * 100,
(end - start) * 1000); (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; // 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 // 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. // can trivially run it by quantizing all elements and running meshopt_encodeVertexBuffer once for each vertex stream.
ObjFile file; fastObjMesh* obj = fast_obj_read(path);
if (!objParseFile(file, path) || !objValidate(file)) 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; 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_pos(total_indices * 3);
std::vector<float> unindexed_nrm(total_indices * 3); std::vector<float> unindexed_nrm(total_indices * 3);
std::vector<float> unindexed_uv(total_indices * 2); 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]; for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
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)
{ {
unindexed_nrm[i * 3 + 0] = file.vn[vni * 3 + 0]; fastObjIndex gi = obj->indices[index_offset + j];
unindexed_nrm[i * 3 + 1] = file.vn[vni * 3 + 1];
unindexed_nrm[i * 3 + 2] = file.vn[vni * 3 + 2]; // 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) index_offset += obj->face_vertices[i];
{
unindexed_uv[i * 2 + 0] = file.vt[vti * 3 + 0];
unindexed_uv[i * 2 + 1] = file.vt[vti * 3 + 1];
}
} }
fast_obj_destroy(obj);
double start = timestamp(); double start = timestamp();
meshopt_Stream streams[] = { meshopt_Stream streams[] = {
@ -884,7 +909,9 @@ void process(const char* path)
meshopt_optimizeVertexCache(&copy.indices[0], &copy.indices[0], copy.indices.size(), copy.vertices.size()); meshopt_optimizeVertexCache(&copy.indices[0], &copy.indices[0], copy.indices.size(), copy.vertices.size());
meshopt_optimizeVertexFetch(&copy.vertices[0], &copy.indices[0], copy.indices.size(), &copy.vertices[0], copy.vertices.size(), sizeof(Vertex)); meshopt_optimizeVertexFetch(&copy.vertices[0], &copy.indices[0], copy.indices.size(), &copy.vertices[0], copy.vertices.size(), sizeof(Vertex));
stripify(copy); stripify(copy, false);
stripify(copy, true);
meshlets(copy); meshlets(copy);
shadow(copy); shadow(copy);
@ -907,7 +934,8 @@ void processDev(const char* path)
if (!loadMesh(mesh, path)) if (!loadMesh(mesh, path))
return; return;
simplify(mesh, 0.01f); simplifySloppy(mesh, 0.5f);
simplifySloppy(mesh, 0.1f);
simplifySloppy(mesh, 0.01f); simplifySloppy(mesh, 0.01f);
} }

BIN
3rdparty/meshoptimizer/demo/pirate.glb vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@ -162,7 +162,7 @@ static void writeTriangle(void* destination, size_t offset, size_t index_size, u
} }
else else
#ifdef __EMSCRIPTEN__ #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 #endif
{ {
static_cast<unsigned int*>(destination)[offset + 0] = a; static_cast<unsigned int*>(destination)[offset + 0] = a;

View File

@ -12,7 +12,7 @@
#include <stddef.h> #include <stddef.h>
/* Version macro; major * 1000 + minor * 10 + patch */ /* Version macro; major * 1000 + minor * 10 + patch */
#define MESHOPTIMIZER_VERSION 110 #define MESHOPTIMIZER_VERSION 120
/* If no API is defined, assume default */ /* If no API is defined, assume default */
#ifndef MESHOPTIMIZER_API #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); 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. * 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. * 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. * 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) * destination must contain enough space for the resulting remap table (vertex_count elements)
* indices can be NULL if the input is unindexed * 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 * 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); 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. * 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. * 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) * 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. * 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. * 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) * 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 * Vertex transform cache optimizer
@ -217,13 +217,15 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifySloppy(unsigned int* destinati
/** /**
* Mesh stripifier * 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 * 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. * 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 * 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); 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 * 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); MESHOPTIMIZER_API size_t meshopt_unstripifyBound(size_t index_count);
struct meshopt_VertexCacheStatistics 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); 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. * 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. * 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. * 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 #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */
@ -562,21 +564,21 @@ inline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t in
} }
template <typename T> 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> 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> 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> in(0, indices, index_count);
meshopt_IndexAdapter<T> out(destination, 0, (index_count - 2) * 3); 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> template <typename T>

View File

@ -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; result.overdraw = result.pixels_covered ? float(result.pixels_shaded) / float(result.pixels_covered) : 0.f;
return result; return result;
} }

View File

@ -19,6 +19,7 @@
// Michael Garland. Quadric-based polygonal surface simplification. 1999 // Michael Garland. Quadric-based polygonal surface simplification. 1999
// Peter Lindstrom. Out-of-Core Simplification of Large Polygonal Models. 2000 // 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 // 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 namespace meshopt
{ {
@ -1080,6 +1081,14 @@ static size_t filterTriangles(unsigned int* destination, unsigned int* tritable,
return result * 3; 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 } // namespace meshopt
#if TRACE #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); unsigned int* vertex_ids = allocator.allocate<unsigned int>(vertex_count);
const int kInterpolationPasses = 5;
// invariant: # of triangles in min_grid <= target_count // invariant: # of triangles in min_grid <= target_count
int min_grid = 0; int min_grid = 0;
int max_grid = 1025; int max_grid = 1025;
size_t min_triangles = 0; size_t min_triangles = 0;
size_t max_triangles = index_count / 3; 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) for (int pass = 0; pass < 10 + kInterpolationPasses; ++pass)
{ {
assert(min_triangles < target_index_count / 3); assert(min_triangles < target_index_count / 3);
assert(max_grid - min_grid > 1); assert(max_grid - min_grid > 1);
int grid_size = 0; // we clamp the prediction of the grid size to make sure that the search converges
int grid_size = next_grid_size;
if (pass == 0) grid_size = (grid_size <= min_grid) ? min_grid + 1 : (grid_size >= max_grid) ? max_grid - 1 : grid_size;
{
// 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);
}
computeVertexIds(vertex_ids, vertex_positions, vertex_count, grid_size); computeVertexIds(vertex_ids, vertex_positions, vertex_count, grid_size);
size_t triangles = countTriangles(vertex_ids, indices, index_count); 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"); (triangles <= target_index_count / 3) ? "under" : "over");
#endif #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) if (triangles <= target_index_count / 3)
{ {
min_grid = grid_size; 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) if (triangles == target_index_count / 3 || max_grid - min_grid <= 1)
break; 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) if (min_triangles == 0)

View File

@ -48,7 +48,7 @@ static int findStripNext(const unsigned int buffer[][3], unsigned int buffer_siz
} // namespace meshopt } // 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(destination != indices);
assert(index_count % 3 == 0); assert(index_count % 3 == 0);
@ -197,18 +197,42 @@ size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices,
next = ec; next = ec;
} }
// emit the new strip; we use restart indices if (restart_index)
if (strip_size) {
destination[strip_size++] = ~0u; if (strip_size)
destination[strip_size++] = restart_index;
destination[strip_size++] = a; destination[strip_size++] = a;
destination[strip_size++] = b; destination[strip_size++] = b;
destination[strip_size++] = c; destination[strip_size++] = c;
// new strip always starts with the same edge winding // new strip always starts with the same edge winding
strip[0] = b; strip[0] = b;
strip[1] = c; strip[1] = c;
parity = 1; 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); assert(index_count % 3 == 0);
// worst case is 1 restart index and 3 indices per triangle // worst case without restarts is 2 degenerate indices and 3 indices per triangle
return (index_count / 3) * 4; // 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); 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) for (size_t i = 0; i < index_count; ++i)
{ {
if (indices[i] == ~0u) if (restart_index && indices[i] == restart_index)
{ {
start = i + 1; start = i + 1;
} }

View File

@ -15,11 +15,11 @@ const size_t kValenceMax = 8;
static const float kVertexScoreTableCache[1 + kCacheSizeMax] = { static const float kVertexScoreTableCache[1 + kCacheSizeMax] = {
0.f, 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] = { static const float kVertexScoreTableLive[1 + kValenceMax] = {
0.f, 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 struct TriangleAdjacency
{ {

View File

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
#define _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS
#include "../src/meshoptimizer.h" #include "../src/meshoptimizer.h"
#include "objparser.h" #include "fast_obj.h"
#include "cgltf.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
@ -11,11 +12,6 @@
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#ifdef GLTF
#define CGLTF_IMPLEMENTATION
#include "cgltf.h"
#endif
#ifdef _WIN32 #ifdef _WIN32
#pragma comment(lib, "opengl32.lib") #pragma comment(lib, "opengl32.lib")
#endif #endif
@ -60,41 +56,58 @@ struct Mesh
Mesh parseObj(const char* path) Mesh parseObj(const char* path)
{ {
ObjFile file; fastObjMesh* obj = fast_obj_read(path);
if (!obj)
if (!objParseFile(file, path) || !objValidate(file))
{ {
printf("Error loading %s\n", path); printf("Error loading %s: file not found\n", path);
return Mesh(); 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); 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]; for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
int vti = file.f[i * 3 + 1]; {
int vni = file.f[i * 3 + 2]; fastObjIndex gi = obj->indices[index_offset + j];
Vertex v = Vertex v =
{ {
file.v[vi * 3 + 0], obj->positions[gi.p * 3 + 0],
file.v[vi * 3 + 1], obj->positions[gi.p * 3 + 1],
file.v[vi * 3 + 2], 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, // triangulate polygon on the fly; offset-3 is always the first polygon vertex
vni >= 0 ? file.vn[vni * 3 + 1] : 0, if (j >= 3)
vni >= 0 ? file.vn[vni * 3 + 2] : 0, {
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, vertices[vertex_offset] = v;
vti >= 0 ? file.vt[vti * 3 + 1] : 0, vertex_offset++;
}; }
vertices[i] = v; index_offset += obj->face_vertices[i];
} }
fast_obj_destroy(obj);
Mesh result; Mesh result;
std::vector<unsigned int> remap(total_indices); std::vector<unsigned int> remap(total_indices);
@ -109,7 +122,6 @@ Mesh parseObj(const char* path)
return result; return result;
} }
#ifdef GLTF
cgltf_accessor* getAccessor(const cgltf_attribute* attributes, size_t attribute_count, cgltf_attribute_type type, int index = 0) 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) 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; 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) Mesh parseGltf(const char* path)
{ {
cgltf_options options = {}; cgltf_options options = {};
@ -205,55 +208,43 @@ Mesh parseGltf(const char* path)
if (!ai || !ap) if (!ai || !ap)
continue; continue;
if (ai->component_type == cgltf_component_type_r_32u) for (size_t i = 0; i < ai->count; ++i)
{ result.indices[index_offset + i] = unsigned(vertex_offset + cgltf_accessor_read_index(ai, i));
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]);
}
{ {
const float* ptr = getComponentPtr<float>(ap);
for (size_t i = 0; i < ap->count; ++i) 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].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].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]; 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)) 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) 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].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].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]; 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)) 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) 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].tx = ptr[0];
result.vertices[vertex_offset + i].ty = ptr[1]; result.vertices[vertex_offset + i].ty = ptr[1];
ptr += at->stride / 4;
} }
} }
@ -274,17 +265,14 @@ Mesh parseGltf(const char* path)
return result; return result;
} }
#endif
Mesh loadMesh(const char* path) Mesh loadMesh(const char* path)
{ {
if (strstr(path, ".obj")) if (strstr(path, ".obj"))
return parseObj(path); return parseObj(path);
#ifdef GLTF
if (strstr(path, ".gltf") || strstr(path, ".glb")) if (strstr(path, ".gltf") || strstr(path, ".glb"))
return parseGltf(path); return parseGltf(path);
#endif
return Mesh(); return Mesh();
} }

View File

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

View 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"

View File

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

View File

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

View File

@ -11,7 +11,7 @@
#include <vector> #include <vector>
#include "../src/meshoptimizer.h" #include "../src/meshoptimizer.h"
#include "objparser.h" #include "fast_obj.h"
#pragma comment(lib, "d3d11.lib") #pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib") #pragma comment(lib, "d3dcompiler.lib")
@ -396,26 +396,40 @@ void testCacheMeshes(IDXGIAdapter* adapter, int argc, char** argv)
continue; continue;
} }
ObjFile file; fastObjMesh* obj = fast_obj_read(path);
if (!obj)
if (!objParseFile(file, path))
{ {
printf("Error loading %s: file not found\n", path); printf("Error loading %s: file not found\n", path);
continue; continue;
} }
if (!objValidate(file))
{
printf("Error loading %s: invalid file data\n", path);
continue;
}
std::vector<unsigned int> ib1; std::vector<unsigned int> ib1;
for (size_t i = 0; i < file.f_size; i += 3) size_t index_offset = 0;
ib1.push_back(file.f[i]);
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 index_count = ib1.size();
unsigned int invocations1 = queryVSInvocations(device, context, ib1.data(), index_count); unsigned int invocations1 = queryVSInvocations(device, context, ib1.data(), index_count);

View File

@ -1,5 +1,5 @@
#include "../src/meshoptimizer.h" #include "../src/meshoptimizer.h"
#include "objparser.h" #include "fast_obj.h"
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
@ -64,6 +64,7 @@ struct State
{ {
float cache[kCacheSizeMax]; float cache[kCacheSizeMax];
float live[kValenceMax]; float live[kValenceMax];
float fitness;
}; };
struct Mesh struct Mesh
@ -98,21 +99,17 @@ Mesh gridmesh(unsigned int N)
Mesh objmesh(const char* path) Mesh objmesh(const char* path)
{ {
ObjFile file; fastObjMesh* obj = fast_obj_read(path);
if (!obj)
if (!objParseFile(file, path))
{ {
printf("Error loading %s: file not found\n", path); printf("Error loading %s: file not found\n", path);
return Mesh(); return Mesh();
} }
if (!objValidate(file)) size_t total_indices = 0;
{
printf("Error loading %s: invalid file data\n", path);
return Mesh();
}
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 struct Vertex
{ {
@ -123,29 +120,44 @@ Mesh objmesh(const char* path)
std::vector<Vertex> vertices(total_indices); 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]; for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
int vti = file.f[i * 3 + 1]; {
int vni = file.f[i * 3 + 2]; fastObjIndex gi = obj->indices[index_offset + j];
Vertex v = Vertex v =
{ {
file.v[vi * 3 + 0], obj->positions[gi.p * 3 + 0],
file.v[vi * 3 + 1], obj->positions[gi.p * 3 + 1],
file.v[vi * 3 + 2], 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, // triangulate polygon on the fly; offset-3 is always the first polygon vertex
vni >= 0 ? file.vn[vni * 3 + 1] : 0, if (j >= 3)
vni >= 0 ? file.vn[vni * 3 + 2] : 0, {
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, vertices[vertex_offset] = v;
vti >= 0 ? file.vt[vti * 3 + 1] : 0, vertex_offset++;
}; }
vertices[i] = v; index_offset += obj->face_vertices[i];
} }
fast_obj_destroy(obj);
Mesh result; Mesh result;
std::vector<unsigned int> remap(total_indices); 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; return result / count;
} }
float rndcache() std::vector<State> gen0(size_t count, const std::vector<Mesh>& meshes)
{
return rand01();
}
float rndlive()
{
return rand01();
}
std::vector<State> gen0(size_t count)
{ {
std::vector<State> result; std::vector<State> result;
@ -212,10 +214,12 @@ std::vector<State> gen0(size_t count)
State state = {}; State state = {};
for (int j = 0; j < kCacheSizeMax; ++j) for (int j = 0; j < kCacheSizeMax; ++j)
state.cache[j] = rndcache(); state.cache[j] = rand01();
for (int j = 0; j < kValenceMax; ++j) 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); result.push_back(state);
} }
@ -223,239 +227,71 @@ std::vector<State> gen0(size_t count)
return result; 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(); std::vector<State> result(seed.size());
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());
for (size_t i = 0; i < seed.size(); ++i) for (size_t i = 0; i < seed.size(); ++i)
{ {
size_t offset = i * (1 + steps); for (;;)
seedfit[i] = resultfit[offset];
float temp = (float(i) / float(seed.size() - 1)) / 0.1f;
for (size_t s = 0; s < steps; ++s)
{ {
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]; float r = rand01();
seed[i] = result[offset + s + 1];
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 #pragma omp parallel for
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
for (size_t i = 0; i < seed.size(); ++i) 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 = {}; State best = {};
float bestfit = 0; float bestfit = 0;
float probsum = 0;
for (size_t i = 0; i < seed.size(); ++i) for (size_t i = 0; i < seed.size(); ++i)
{ {
float score = seedprob[i]; if (result[i].fitness > seed[i].fitness)
probsum += score; seed[i] = result[i];
if (score > bestfit) if (seed[i].fitness > bestfit)
{ {
best = seed[i]; 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); return std::make_pair(best, bestfit);
} }
@ -545,7 +381,7 @@ int main(int argc, char** argv)
} }
else else
{ {
pop = gen0(annealing ? 32 : 1000); pop = gen0(95, meshes);
} }
printf("%d meshes, %.1fM triangles\n", int(meshes.size()), double(total_triangles) / 1e6); printf("%d meshes, %.1fM triangles\n", int(meshes.size()), double(total_triangles) / 1e6);
@ -559,7 +395,7 @@ int main(int argc, char** argv)
for (;;) for (;;)
{ {
auto best = annealing ? genN_SA(pop, meshes, 31) : genN_GA(pop, meshes, 0.7f, 0.3f); auto best = genN(pop, meshes);
gen++; gen++;
compute_atvr(best.first, meshes[0], atvr_0); compute_atvr(best.first, meshes[0], atvr_0);
@ -571,7 +407,8 @@ int main(int argc, char** argv)
{ {
char buf[128]; 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]); 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); dump_state(best.first);