Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ jobs:
run: node js/meshopt_simplifier.test.js
- name: test clusterizer
run: node js/meshopt_clusterizer.test.js
- name: check typescript declarations
run: npx --yes -p typescript@6 tsc --noEmit --moduleResolution node16 --module node16 --strict js/index.d.ts
- name: check es5
run: |
npm install -g es-check@7.2.1
Expand Down
12 changes: 10 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ WASM_SIMPLIFIER_EXPORTS=meshopt_simplify meshopt_simplifyWithAttributes meshopt_
WASM_CLUSTERIZER_SOURCES=src/clusterizer.cpp src/meshletutils.cpp tools/wasmstubs.cpp
WASM_CLUSTERIZER_EXPORTS=meshopt_buildMeshletsBound meshopt_buildMeshletsFlex meshopt_buildMeshletsSpatial meshopt_computeClusterBounds meshopt_computeMeshletBounds meshopt_computeSphereBounds meshopt_optimizeMeshlet sbrk __wasm_call_ctors

WASM_TANGENTS_SOURCES=src/tangentspace.cpp tools/wasmstubs.cpp
WASM_TANGENTS_EXPORTS=meshopt_generateTangents sbrk __wasm_call_ctors

ifneq ($(werror),)
CFLAGS+=-Werror
CXXFLAGS+=-Werror
Expand Down Expand Up @@ -141,7 +144,7 @@ format:
formatjs:
prettier -w js/*.js gltf/*.js demo/*.html js/*.ts

js: js/meshopt_decoder.cjs js/meshopt_decoder.mjs js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js
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

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

build/tangents.wasm: $(WASM_TANGENTS_SOURCES)
@mkdir -p build
$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_TANGENTS_EXPORTS)) -lc -o $@

js/meshopt_decoder.mjs: build/decoder_base.wasm build/decoder_simd.wasm tools/wasmpack.py
sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1 | sed 's/\s\+(.*//')#" $@
sed -i "s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#" $@
Expand All @@ -194,8 +201,9 @@ js/meshopt_decoder.mjs: build/decoder_base.wasm build/decoder_simd.wasm tools/wa
js/meshopt_encoder.js: build/encoder.wasm tools/wasmpack.py
js/meshopt_simplifier.js: build/simplifier.wasm tools/wasmpack.py
js/meshopt_clusterizer.js: build/clusterizer.wasm tools/wasmpack.py
js/meshopt_tangents.js: build/tangents.wasm tools/wasmpack.py

js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js:
js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js js/meshopt_tangents.js:
sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1 | sed 's/\s\+(.*//')#" $@
sed -i "s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#" $@
python3 tools/wasmpack.py patch $@ wasm <$<
Expand Down
18 changes: 16 additions & 2 deletions js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ By default, `encodeVertexBuffer` uses v1 version of the encoding; this encoding
To simplify the mesh, the following function needs to be called first:

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

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.
Expand Down Expand Up @@ -165,7 +165,7 @@ This can be done before regular simplification or as the only step, which is use
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`:

```ts
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];
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];
```

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).
Expand Down Expand Up @@ -268,6 +268,20 @@ Finally, it is possible to compute spherical bounds of an arbitrary set of point
computeSphereBounds: (positions: Float32Array, positions_stride: number, radii?: Float32Array, radii_stride?: number) => Bounds;
```

## Tangents

`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:

```ts
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;
```

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.

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.

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`

## License

This library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md).
1 change: 1 addition & 0 deletions js/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './meshopt_encoder.js';
export * from './meshopt_decoder.js';
export * from './meshopt_simplifier.js';
export * from './meshopt_clusterizer.js';
export * from './meshopt_tangents.js';
1 change: 1 addition & 0 deletions js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './meshopt_encoder.js';
export * from './meshopt_decoder.mjs';
export * from './meshopt_simplifier.js';
export * from './meshopt_clusterizer.js';
export * from './meshopt_tangents.js';
11 changes: 7 additions & 4 deletions js/meshopt_simplifier.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// This file is part of meshoptimizer library and is distributed under the terms of MIT License.
// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
export type Flags = 'LockBorder' | 'Sparse' | 'ErrorAbsolute' | 'Prune' | 'Regularize' | 'Permissive';
export type SimplifierFlags = 'LockBorder' | 'Sparse' | 'ErrorAbsolute' | 'Prune' | 'Regularize' | 'Permissive';

/** @deprecated Use SimplifierFlags instead. */
export type Flags = SimplifierFlags;

export const MeshoptSimplifier: {
supported: boolean;
Expand All @@ -15,7 +18,7 @@ export const MeshoptSimplifier: {
vertex_positions_stride: number,
target_index_count: number,
target_error: number,
flags?: Flags[]
flags?: SimplifierFlags[]
) => [Uint32Array, number];

simplifyWithAttributes: (
Expand All @@ -28,7 +31,7 @@ export const MeshoptSimplifier: {
vertex_lock: Uint8Array | null,
target_index_count: number,
target_error: number,
flags?: Flags[]
flags?: SimplifierFlags[]
) => [Uint32Array, number];

simplifyWithUpdate: (
Expand All @@ -41,7 +44,7 @@ export const MeshoptSimplifier: {
vertex_lock: Uint8Array | null,
target_index_count: number,
target_error: number,
flags?: Flags[]
flags?: SimplifierFlags[]
) => [number, number];

simplifySloppy: (
Expand Down
20 changes: 20 additions & 0 deletions js/meshopt_tangents.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This file is part of meshoptimizer library and is distributed under the terms of MIT License.
// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)

export type TangentsFlags = 'Compatible' | 'ZeroFallback';

export const MeshoptTangents: {
supported: boolean;
ready: Promise<void>;

generateTangents: (
indices: Uint32Array | Int32Array | Uint16Array | Int16Array | 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;
};
Loading
Loading