Skip to content

Commit f116303

Browse files
authored
Merge pull request #1051 from zeux/js-tangents
js: Add MeshoptTangents module for tangent generation
2 parents c835967 + 4fec35d commit f116303

10 files changed

Lines changed: 292 additions & 9 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ jobs:
8989
run: node js/meshopt_simplifier.test.js
9090
- name: test clusterizer
9191
run: node js/meshopt_clusterizer.test.js
92+
- name: check typescript declarations
93+
run: npx --yes -p typescript@6 tsc --noEmit --moduleResolution node16 --module node16 --strict js/index.d.ts
9294
- name: check es5
9395
run: |
9496
npm install -g es-check@7.2.1

Makefile

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ WASM_SIMPLIFIER_EXPORTS=meshopt_simplify meshopt_simplifyWithAttributes meshopt_
5757
WASM_CLUSTERIZER_SOURCES=src/clusterizer.cpp src/meshletutils.cpp tools/wasmstubs.cpp
5858
WASM_CLUSTERIZER_EXPORTS=meshopt_buildMeshletsBound meshopt_buildMeshletsFlex meshopt_buildMeshletsSpatial meshopt_computeClusterBounds meshopt_computeMeshletBounds meshopt_computeSphereBounds meshopt_optimizeMeshlet sbrk __wasm_call_ctors
5959

60+
WASM_TANGENTS_SOURCES=src/tangentspace.cpp tools/wasmstubs.cpp
61+
WASM_TANGENTS_EXPORTS=meshopt_generateTangents sbrk __wasm_call_ctors
62+
6063
ifneq ($(werror),)
6164
CFLAGS+=-Werror
6265
CXXFLAGS+=-Werror
@@ -141,7 +144,7 @@ format:
141144
formatjs:
142145
prettier -w js/*.js gltf/*.js demo/*.html js/*.ts
143146

144-
js: js/meshopt_decoder.cjs js/meshopt_decoder.mjs js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js
147+
js: js/meshopt_decoder.cjs js/meshopt_decoder.mjs js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js js/meshopt_tangents.js
145148

146149
symbols: $(BUILD)/amalgamated.so
147150
nm $< -U -g
@@ -185,6 +188,10 @@ build/clusterizer.wasm: $(WASM_CLUSTERIZER_SOURCES)
185188
@mkdir -p build
186189
$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_CLUSTERIZER_EXPORTS)) -lc -o $@
187190

191+
build/tangents.wasm: $(WASM_TANGENTS_SOURCES)
192+
@mkdir -p build
193+
$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_TANGENTS_EXPORTS)) -lc -o $@
194+
188195
js/meshopt_decoder.mjs: build/decoder_base.wasm build/decoder_simd.wasm tools/wasmpack.py
189196
sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1 | sed 's/\s\+(.*//')#" $@
190197
sed -i "s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#" $@
@@ -194,8 +201,9 @@ js/meshopt_decoder.mjs: build/decoder_base.wasm build/decoder_simd.wasm tools/wa
194201
js/meshopt_encoder.js: build/encoder.wasm tools/wasmpack.py
195202
js/meshopt_simplifier.js: build/simplifier.wasm tools/wasmpack.py
196203
js/meshopt_clusterizer.js: build/clusterizer.wasm tools/wasmpack.py
204+
js/meshopt_tangents.js: build/tangents.wasm tools/wasmpack.py
197205

198-
js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js:
206+
js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js js/meshopt_tangents.js:
199207
sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1 | sed 's/\s\+(.*//')#" $@
200208
sed -i "s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#" $@
201209
python3 tools/wasmpack.py patch $@ wasm <$<

js/README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ By default, `encodeVertexBuffer` uses v1 version of the encoding; this encoding
135135
To simplify the mesh, the following function needs to be called first:
136136

137137
```ts
138-
simplify(indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, target_index_count: number, target_error: number, flags?: Flags[]) => [Uint32Array, number];
138+
simplify(indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, target_index_count: number, target_error: number, flags?: SimplifierFlags[]) => [Uint32Array, number];
139139
```
140140

141141
Given an input triangle mesh represented by an index buffer and a position buffer, the algorithm tries to simplify the mesh down to the target index count while maintaining the 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. Therefore it's critical that identical vertices are "welded" together, that is, the input vertex buffer does not contain duplicates. Additionally, it may be possible to preprocess the index buffer to discard any vertex attributes that aren't critical and can be rebuilt later.
@@ -165,7 +165,7 @@ This can be done before regular simplification or as the only step, which is use
165165
While `simplify` is aware of attribute discontinuities by default (and infers them through the supplied index buffer) and tries to preserve them, it can be useful to provide information about attribute values. This allows the simplifier to take attribute error into account which can improve shading (by using vertex normals), texture deformation (by using texture coordinates), and may be necessary to preserve vertex colors when textures are not used in the first place. This can be done by using a variant of the simplification function that takes attribute values and weight factors, `simplifyWithAttributes`:
166166

167167
```ts
168-
simplifyWithAttributes: (indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, vertex_attributes: Float32Array, vertex_attributes_stride: number, attribute_weights: number[], vertex_lock: Uint8Array | null, target_index_count: number, target_error: number, flags?: Flags[]) => [Uint32Array, number];
168+
simplifyWithAttributes: (indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, vertex_attributes: Float32Array, vertex_attributes_stride: number, attribute_weights: number[], vertex_lock: Uint8Array | null, target_index_count: number, target_error: number, flags?: SimplifierFlags[]) => [Uint32Array, number];
169169
```
170170

171171
This function takes an additional `vertex_attributes` buffer that contains all the attributes to be used. The `attribute_weights` array contains a weight for each attribute, which is used to balance the importance of each attribute during simplification. For normalized attributes like normals and vertex colors, a weight around 1.0 is usually appropriate; internally, a change of `1/weight` in attribute value over a distance `d` is approximately equivalent to a change of `d` in position. Using higher weights may be appropriate to preserve attribute quality at the cost of position quality. If the attribute has a different scale (e.g. unnormalized vertex colors in [0..255] range), the weight should be divided by the scaling factor (1/255 in this example).
@@ -268,6 +268,20 @@ Finally, it is possible to compute spherical bounds of an arbitrary set of point
268268
computeSphereBounds: (positions: Float32Array, positions_stride: number, radii?: Float32Array, radii_stride?: number) => Bounds;
269269
```
270270

271+
## Tangents
272+
273+
`MeshoptTangents` (`meshopt_tangents.js`) implements tangent space generation for meshes that use tangent space normal maps. These meshes often require per-vertex tangent vectors in addition to normals; these could be exported alongside mesh data, but this module can generate them from positions, normals and texture coordinates:
274+
275+
```ts
276+
generateTangents: (indices: Uint32Array | null, vertex_positions: Float32Array, vertex_positions_stride: number, vertex_normals: Float32Array, vertex_normals_stride: number, vertex_uvs: Float32Array, vertex_uvs_stride: number, flags?: TangentsFlags[]) => Float32Array;
277+
```
278+
279+
For each triangle *corner* this writes a normalized tangent vector (xyz) and an orientation sign (+-1); the bitangent can be reconstructed in the shader as `cross(normal, tangent.xyz) * tangent.w`. Note that some coordinate space conventions that flip V direction in the texture space require negating orientation sign. The input can be indexed, or not (`indices=null`); this does not affect the output tangents.
280+
281+
Because tangents are computed per corner, applying them to mesh vertices requires de-indexing the mesh and generating a new index/vertex buffer afterwards, or using indexed data and copying tangents to existing vertex data while duplicating vertices with different tangents. With the indexed input, if it contains UV mirroring, vertices along the mirror edge may have different tangent spaces on different sides of the edge and need to be split - copying tangents to existing vertex data without splitting will not produce correct results.
282+
283+
The algorithm uses a MikkTSpace-like construction but by default, uses a modified weighting scheme that significantly improves tangent quality around beveled regions in the mesh. If the normal maps are baked from higher resolution geometry using MikkTSpace weighting, it's possible to produce MikkTSpace-compatible tangents by passing `'Compatible'` option in `flags`
284+
271285
## License
272286

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

js/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './meshopt_encoder.js';
22
export * from './meshopt_decoder.js';
33
export * from './meshopt_simplifier.js';
44
export * from './meshopt_clusterizer.js';
5+
export * from './meshopt_tangents.js';

js/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './meshopt_encoder.js';
22
export * from './meshopt_decoder.mjs';
33
export * from './meshopt_simplifier.js';
44
export * from './meshopt_clusterizer.js';
5+
export * from './meshopt_tangents.js';

js/meshopt_simplifier.d.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// This file is part of meshoptimizer library and is distributed under the terms of MIT License.
22
// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
3-
export type Flags = 'LockBorder' | 'Sparse' | 'ErrorAbsolute' | 'Prune' | 'Regularize' | 'Permissive';
3+
export type SimplifierFlags = 'LockBorder' | 'Sparse' | 'ErrorAbsolute' | 'Prune' | 'Regularize' | 'Permissive';
4+
5+
/** @deprecated Use SimplifierFlags instead. */
6+
export type Flags = SimplifierFlags;
47

58
export const MeshoptSimplifier: {
69
supported: boolean;
@@ -15,7 +18,7 @@ export const MeshoptSimplifier: {
1518
vertex_positions_stride: number,
1619
target_index_count: number,
1720
target_error: number,
18-
flags?: Flags[]
21+
flags?: SimplifierFlags[]
1922
) => [Uint32Array, number];
2023

2124
simplifyWithAttributes: (
@@ -28,7 +31,7 @@ export const MeshoptSimplifier: {
2831
vertex_lock: Uint8Array | null,
2932
target_index_count: number,
3033
target_error: number,
31-
flags?: Flags[]
34+
flags?: SimplifierFlags[]
3235
) => [Uint32Array, number];
3336

3437
simplifyWithUpdate: (
@@ -41,7 +44,7 @@ export const MeshoptSimplifier: {
4144
vertex_lock: Uint8Array | null,
4245
target_index_count: number,
4346
target_error: number,
44-
flags?: Flags[]
47+
flags?: SimplifierFlags[]
4548
) => [number, number];
4649

4750
simplifySloppy: (

js/meshopt_tangents.d.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This file is part of meshoptimizer library and is distributed under the terms of MIT License.
2+
// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
3+
4+
export type TangentsFlags = 'Compatible' | 'ZeroFallback';
5+
6+
export const MeshoptTangents: {
7+
supported: boolean;
8+
ready: Promise<void>;
9+
10+
generateTangents: (
11+
indices: Uint32Array | Int32Array | Uint16Array | Int16Array | null,
12+
vertex_positions: Float32Array,
13+
vertex_positions_stride: number,
14+
vertex_normals: Float32Array,
15+
vertex_normals_stride: number,
16+
vertex_uvs: Float32Array,
17+
vertex_uvs_stride: number,
18+
flags?: TangentsFlags[]
19+
) => Float32Array;
20+
};

0 commit comments

Comments
 (0)