Skip to content

Commit 5304a88

Browse files
authored
Merge pull request #738 from JolifantoBambla/clusterizer-wasm
Add JS bindings for clusterizer API
2 parents 0a9d917 + 8bc3573 commit 5304a88

11 files changed

+941
-6
lines changed

Diff for: .github/workflows/build.yml

+5-2
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,13 @@ jobs:
6868
run: node js/meshopt_encoder.test.js
6969
- name: test simplifier
7070
run: node js/meshopt_simplifier.test.js
71+
- name: test clusterizer
72+
run: node js/meshopt_clusterizer.test.js
7173
- name: check es5
7274
run: |
7375
npm install -g es-check
74-
npx es-check es5 js/meshopt_decoder.js js/meshopt_encoder.js js/meshopt_simplifier.js
75-
npx es-check --module es5 js/meshopt_decoder.module.js js/meshopt_encoder.module.js js/meshopt_simplifier.module.js
76+
npx es-check es5 js/meshopt_decoder.js js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js
77+
npx es-check --module es5 js/meshopt_decoder.module.js js/meshopt_encoder.module.js js/meshopt_simplifier.module.js js/meshopt_clusterizer.module.js
7678
npx es-check es5 gltf/library.js
7779
7880
gltfpack:
@@ -125,6 +127,7 @@ jobs:
125127
node js/meshopt_decoder.test.js
126128
node js/meshopt_encoder.test.js
127129
node js/meshopt_simplifier.test.js
130+
node js/meshopt_clusterizer.test.js
128131
129132
gltfpack-basis:
130133
runs-on: ubuntu-latest

Diff for: Makefile

+14-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ WASM_FLAGS=--target=wasm32-wasi --sysroot=$(WASIROOT)
4242
WASM_FLAGS+=-O3 -DNDEBUG -nostartfiles -nostdlib -Wl,--no-entry -Wl,-s
4343
WASM_FLAGS+=-mcpu=mvp # make sure clang doesn't use post-MVP features like sign extension
4444
WASM_FLAGS+=-fno-slp-vectorize -fno-vectorize -fno-unroll-loops
45-
WASM_FLAGS+=-Wl,-z -Wl,stack-size=24576 -Wl,--initial-memory=65536
45+
WASM_FLAGS+=-Wl,-z -Wl,stack-size=36864 -Wl,--initial-memory=65536
4646
WASM_EXPORT_PREFIX=-Wl,--export
4747

4848
WASM_DECODER_SOURCES=src/vertexcodec.cpp src/indexcodec.cpp src/vertexfilter.cpp tools/wasmstubs.cpp
@@ -54,6 +54,9 @@ WASM_ENCODER_EXPORTS=meshopt_encodeVertexBuffer meshopt_encodeVertexBufferBound
5454
WASM_SIMPLIFIER_SOURCES=src/simplifier.cpp src/vfetchoptimizer.cpp tools/wasmstubs.cpp
5555
WASM_SIMPLIFIER_EXPORTS=meshopt_simplify meshopt_simplifyWithAttributes meshopt_simplifyScale meshopt_simplifyPoints meshopt_optimizeVertexFetchRemap sbrk __wasm_call_ctors
5656

57+
WASM_CLUSTERIZER_SOURCES=src/clusterizer.cpp tools/wasmstubs.cpp
58+
WASM_CLUSTERIZER_EXPORTS=meshopt_buildMeshletsBound meshopt_buildMeshlets meshopt_computeClusterBounds meshopt_computeMeshletBounds meshopt_optimizeMeshlet sbrk __wasm_call_ctors
59+
5760
ifeq ($(config),iphone)
5861
IPHONESDK=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk
5962
CFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK)
@@ -111,7 +114,7 @@ format:
111114
formatjs:
112115
prettier -w js/*.js gltf/*.js demo/*.html js/*.ts
113116

114-
js: js/meshopt_decoder.js js/meshopt_decoder.module.js js/meshopt_encoder.js js/meshopt_encoder.module.js js/meshopt_simplifier.js js/meshopt_simplifier.module.js
117+
js: js/meshopt_decoder.js js/meshopt_decoder.module.js js/meshopt_encoder.js js/meshopt_encoder.module.js js/meshopt_simplifier.js js/meshopt_simplifier.module.js js/meshopt_clusterizer.js js/meshopt_clusterizer.module.js
115118

116119
symbols: $(BUILD)/amalgamated.so
117120
nm $< -U -g
@@ -151,6 +154,10 @@ build/simplifier.wasm: $(WASM_SIMPLIFIER_SOURCES)
151154
@mkdir -p build
152155
$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_SIMPLIFIER_EXPORTS)) -lc -o $@
153156

157+
build/clusterizer.wasm: $(WASM_CLUSTERIZER_SOURCES)
158+
@mkdir -p build
159+
$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_CLUSTERIZER_EXPORTS)) -lc -o $@
160+
154161
js/meshopt_decoder.js: build/decoder_base.wasm build/decoder_simd.wasm tools/wasmpack.py
155162
sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1 | sed 's/\s\+(.*//')#" $@
156163
sed -i "s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#" $@
@@ -167,6 +174,11 @@ js/meshopt_simplifier.js: build/simplifier.wasm tools/wasmpack.py
167174
sed -i "s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#" $@
168175
sed -i "s#\([\"']\).*\(;\s*//\s*embed! wasm\)#\\1$$(cat build/simplifier.wasm | python3 tools/wasmpack.py)\\1\\2#" $@
169176

177+
js/meshopt_clusterizer.js: build/clusterizer.wasm tools/wasmpack.py
178+
sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1 | sed 's/\s\+(.*//')#" $@
179+
sed -i "s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#" $@
180+
sed -i "s#\([\"']\).*\(;\s*//\s*embed! wasm\)#\\1$$(cat build/clusterizer.wasm | python3 tools/wasmpack.py)\\1\\2#" $@
181+
170182
js/%.module.js: js/%.js
171183
sed '\#// export!#q' <$< >$@
172184
sed -i "/use strict.;/d" $@

Diff for: js/README.md

+68
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,74 @@ The simplification algorithm uses relative errors for input and output; to conve
155155
getScale: (vertex_positions: Float32Array, vertex_positions_stride: number) => number;
156156
```
157157

158+
## Clusterizer
159+
160+
`MeshoptClusterizer` (`meshopt_clusterizer.js`) implements meshlet generation and optimization.
161+
162+
To split a triangle mesh into clusters, call `buildMeshlets`, which tries to balance topological efficiency (by maximizing vertex reuse inside meshlets) with culling efficiency.
163+
164+
```ts
165+
buildMeshlets(indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, max_vertices: number, max_triangles: number, cone_weight?: number) => MeshletBuffers;
166+
```
167+
168+
The algorithm uses position data stored in a strided array; `vertex_positions_stride` represents the distance between subsequent positions in `Float32` units.
169+
170+
The maximum number of triangles and number of vertices per meshlet can be controlled via `max_triangles` and `max_vertices` parameters. However, `max_vertices` must not be greater than 255 and `max_triangles` must not be greater than 512.
171+
172+
Additionally, if cluster cone culling is to be used, `buildMeshlets` allows specifying a `cone_weight` as a value between 0 and 1 to balance culling efficiency with other forms of culling. By default, `cone_weight` is set to 0.
173+
174+
All meshlets are implicitly optimized for better triangle and vertex locality by `buildMeshlets`.
175+
176+
The algorithm returns the meshlet data as packed buffers:
177+
178+
```ts
179+
const buffers = MeshoptClusterizer.buildMeshlets(indices, positions, stride, /* args */);
180+
181+
console.log(buffers.meshlets); // prints the raw packed Uint32Array containing the meshlet data, i.e., the indices into the vertices and triangles array
182+
console.log(buffers.vertices); // prints the raw packed Uint32Array containing the indices into the original meshes vertices
183+
console.log(buffers.triangles); // prints the raw packed Uint8Array containing the indices into the verices array.
184+
console.log(buffers.meshletCount); // prints the number of meshlets - this is not the same as buffers.meshlets.length because each meshlet consists of 4 unsigned 32-bit integers
185+
```
186+
187+
Individual meshlets can be extracted from the packed buffers using `extractMeshlet`. The memory of the returned `Meshlet` object's `vertices` and `triangles` arrays is backed by the `MeshletBuffers` object.
188+
189+
```ts
190+
const buffers = MeshoptClusterizer.buildMeshlets(indices, positions, stride, /* args */);
191+
192+
const meshlet = MeshoptClusterizer.extractMeshlet(buffers, 0);
193+
console.log(meshlet.vertices); // prints the packed Uint32Array of the first meshlet's vertex indices, i.e., indices into the original meshes vertex buffer
194+
console.log(meshlet.triangles); // prints the packed Uint8Array of the first meshlet's indices into its own vertices array
195+
196+
console.log(MeshoptClusterizer.extractMeshlet(buffers, 0).triangles[0] === meshlet.triangles[0]) // prints true
197+
198+
meshlet.triangles.set([123], 0);
199+
console.log(MeshoptClusterizer.extractMeshlet(buffers, 0).triangles[0] === meshlet.triangles[0]) // still prints true
200+
```
201+
202+
After generating the meshlet data, it's also possible to generate extra culling data for one or more meshlets:
203+
204+
```ts
205+
computeMeshletBounds(buffers: MeshletBuffers, vertex_positions: Float32Array, vertex_positions_stride: number) => Bounds | Bounds[];
206+
```
207+
208+
If `buffers` contains more than one meshlet, `computeMeshletBounds` returns an array of `Bounds`. Otherwise, a single `Bounds` object is returned.
209+
210+
```ts
211+
const buffers = MeshoptClusterizer.buildMeshlets(indices, positions, stride, /* args */);
212+
const bounds = MeshoptClusterizer.computeMeshletBounds(buffers, positions, stride);
213+
console.log(bounds[0].centerX, bounds[0].centerY, bounds[0].centerZ); // prints the center of the first meshlet's bounding sphere
214+
console.log(bounds[0].radius); // prints the radius of the first meshlet's bounding sphere
215+
console.log(bounds[0].coneApexX, bounds[0].coneApexY, bounds[0].coneApexZ); // prints the apex of the first meshlet's normal cone
216+
console.log(bounds[0].coneAxisX, bounds[0].coneAxisY, bounds[0].coneAxisZ); // prints the axis of the first meshlet's normal cone
217+
console.log(bounds[0].coneCutoff); // prins the cutoff angle of the first meshlet's normal cone
218+
```
219+
220+
It is also possible to compute bounds of a vertex cluster that is not generated by `MeshoptClusterizer` using `computeClusterBounds`. Like `buildMeshlets`, this algorithm takes vertex indices and a strided vertex positions array with a vertex stride in `Float32` units as input.
221+
222+
```ts
223+
computeClusterBounds(indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number) => Bounds;
224+
```
225+
158226
## License
159227

160228
This library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md).

Diff for: js/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const MeshoptEncoder = require('./meshopt_encoder.js');
22
const MeshoptDecoder = require('./meshopt_decoder.js');
33
const MeshoptSimplifier = require('./meshopt_simplifier.js');
4+
const MeshoptClusterizer = require('./meshopt_clusterizer');
45

5-
module.exports = { MeshoptEncoder, MeshoptDecoder, MeshoptSimplifier };
6+
module.exports = { MeshoptEncoder, MeshoptDecoder, MeshoptSimplifier, MeshoptClusterizer };

Diff for: js/index.module.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './meshopt_encoder.module';
22
export * from './meshopt_decoder.module';
33
export * from './meshopt_simplifier.module';
4+
export * from './meshopt_clusterizer.module';

Diff for: js/index.module.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './meshopt_encoder.module.js';
22
export * from './meshopt_decoder.module.js';
33
export * from './meshopt_simplifier.module.js';
4+
export * from './meshopt_clusterizer.module.js';

0 commit comments

Comments
 (0)