minidraco 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -100
- package/dist/index.d.ts +11 -99
- package/dist/index.js +2839 -2025
- package/dist/three.d.ts +16 -7
- package/dist/three.js +2867 -2049
- package/dist/worker.js +2840 -2023
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,51 +1,29 @@
|
|
|
1
|
-
# minidraco
|
|
1
|
+
# 🐲 minidraco
|
|
2
2
|
|
|
3
3
|
A fast, pure-TypeScript [Draco](https://google.github.io/draco/) mesh decoder with a drop-in
|
|
4
|
-
`DRACOLoader`
|
|
5
|
-
|
|
6
|
-
main thread.
|
|
7
|
-
|
|
8
|
-
minidraco started as a TypeScript port of [mrdoob/draco.js](https://github.com/mrdoob/draco.js)
|
|
9
|
-
(MIT), then restructured and optimized (rANS table pooling, seam-list corner tables, allocation
|
|
10
|
-
elimination in the entropy decoders, specialized attribute extraction) to close the gap with the
|
|
11
|
-
official [draco3d](https://www.npmjs.com/package/draco3d) wasm decoder.
|
|
4
|
+
`DRACOLoader` for [Three.js](https://threejs.org/) — no wasm to host or fetch, and a worker pool
|
|
5
|
+
so decoding never blocks the main thread.
|
|
12
6
|
|
|
13
7
|
## Usage
|
|
14
8
|
|
|
15
9
|
```ts
|
|
16
10
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
|
17
|
-
import {
|
|
11
|
+
import { MinidracoLoader } from 'minidraco/three'
|
|
18
12
|
|
|
19
13
|
const gltfLoader = new GLTFLoader()
|
|
20
|
-
gltfLoader.setDRACOLoader(new
|
|
14
|
+
gltfLoader.setDRACOLoader(new MinidracoLoader())
|
|
21
15
|
gltfLoader.load('model.glb', gltf => scene.add(gltf.scene))
|
|
22
16
|
```
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
`gltfLoader.setDRACOLoader(new MiniDRACOLoader())` just type-checks.
|
|
27
|
-
|
|
28
|
-
Decoding runs in a pool of module workers by default (parallel across primitives, main thread
|
|
29
|
-
stays free). If workers can't be spawned (SSR, exotic bundlers), it falls back to synchronous
|
|
30
|
-
decoding automatically. Configure it at construction or with fluent setters:
|
|
18
|
+
A drop-in for `THREE.DRACOLoader`, no cast needed. Decoding runs in a worker pool by default, with
|
|
19
|
+
a main-thread fallback. Options:
|
|
31
20
|
|
|
32
21
|
```ts
|
|
33
|
-
new
|
|
34
|
-
new
|
|
35
|
-
new MiniDRACOLoader(loadingManager) // a three.js LoadingManager, as usual
|
|
36
|
-
|
|
37
|
-
const loader = new MiniDRACOLoader()
|
|
38
|
-
loader.setWorkers(false) // ...or toggle later (same as setWorkerLimit(0))
|
|
22
|
+
new MinidracoLoader({ workers: false }) // decode on the main thread
|
|
23
|
+
new MinidracoLoader({ workerLimit: 8 }) // pool size (default 4)
|
|
39
24
|
```
|
|
40
25
|
|
|
41
|
-
|
|
42
|
-
refuse to construct a Worker from a cross-origin script, so minidraco bootstraps the worker
|
|
43
|
-
through a same-origin blob module that imports the hashed CDN asset (a CORS request — your CDN
|
|
44
|
-
must send `Access-Control-Allow-Origin`, which it already does if you load models or fonts from
|
|
45
|
-
it). If even that fails, decoding falls back to the main thread rather than erroring.
|
|
46
|
-
`setWorkerUrl(url)` exists as a manual override for exotic setups.
|
|
47
|
-
|
|
48
|
-
Or decode a raw Draco bitstream without Three.js:
|
|
26
|
+
Or decode a raw bitstream without Three.js:
|
|
49
27
|
|
|
50
28
|
```ts
|
|
51
29
|
import { decodeDracoMesh } from 'minidraco'
|
|
@@ -53,90 +31,49 @@ import { decodeDracoMesh } from 'minidraco'
|
|
|
53
31
|
const mesh = decodeDracoMesh(new Uint8Array(bytes))
|
|
54
32
|
```
|
|
55
33
|
|
|
56
|
-
##
|
|
34
|
+
## Features
|
|
57
35
|
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- Quantized, integer, and octahedron-normal attributes
|
|
62
|
-
- Custom / generic attributes (glTF `_*` semantics), skinning attributes (`JOINTS_0` /
|
|
63
|
-
`WEIGHTS_0`), vertex colors, multiple UV sets
|
|
64
|
-
- Output is verified bit-identical to the official wasm decoder (≤ 1 ulp on dequantized floats)
|
|
65
|
-
in the test suite, on both real production GLBs and Draco's own test corpus
|
|
66
|
-
- Point clouds are not supported (glTF `KHR_draco_mesh_compression` only ever contains meshes)
|
|
36
|
+
- Every Draco triangle mesh — all encodings, prediction schemes, and attribute types.
|
|
37
|
+
- Bit-identical to the official wasm decoder, verified in the tests.
|
|
38
|
+
- No point clouds (glTF Draco is always meshes).
|
|
67
39
|
|
|
68
40
|
## Performance
|
|
69
41
|
|
|
70
|
-
|
|
71
|
-
[
|
|
72
|
-
[BENCH.md](https://github.com/verekia/minidraco/blob/main/BENCH.md)
|
|
73
|
-
`bun run bench` (with machine-readable
|
|
74
|
-
[BENCH.json](https://github.com/verekia/minidraco/blob/main/BENCH.json) alongside for diffing
|
|
75
|
-
perf work). Highlights, decoding the production bundle GLBs in
|
|
76
|
-
`example/public/models` (Apple Silicon, medians). Raw single-threaded decode, bun/JSC:
|
|
77
|
-
|
|
78
|
-
| file (points / faces) | minidraco | draco.js | draco3d wasm |
|
|
79
|
-
| ---------------------------------------------- | --------- | -------- | ------------ |
|
|
80
|
-
| manablade-characters (5.1k / 2.5k) | 1.9 ms | 1.7 ms | 1.2 ms |
|
|
81
|
-
| manablade-static (291k / 221k, 488 primitives) | **50 ms** | 55 ms | 51 ms |
|
|
82
|
-
|
|
83
|
-
The `/bench` page of the example runs the same fair comparison in the browser (V8): a raw
|
|
84
|
-
single-threaded mode where all three decoders decode on the main thread, over the bundles plus
|
|
85
|
-
the draco.js sample models (synced locally by `bun dev`, never deployed). Browser runs — both
|
|
86
|
-
the single-threaded mode and the multi-threaded GLTFLoader wall clock — are tracked in
|
|
87
|
-
[BENCH.browser.json](https://github.com/verekia/minidraco/blob/main/BENCH.browser.json) via the
|
|
88
|
-
page's "Save to BENCH.browser.json" button (local dev only).
|
|
89
|
-
|
|
90
|
-
In the browser the worker pool changes the story for real scenes — wall-clock
|
|
91
|
-
`GLTFLoader.parse` of the 488-primitive manablade-static bundle (Chromium, warm loaders, `/bench`
|
|
92
|
-
page of the example):
|
|
93
|
-
|
|
94
|
-
| decoder | manablade-static | main thread |
|
|
95
|
-
| ------------------------ | ---------------- | ----------- |
|
|
96
|
-
| minidraco (4 workers) | ~51 ms | free |
|
|
97
|
-
| draco.js (main thread) | ~72 ms | blocked |
|
|
98
|
-
| draco3d wasm (4 workers) | ~23 ms | free |
|
|
99
|
-
|
|
100
|
-
And unlike the wasm decoder there is nothing to host or fetch: the first decode doesn't pay the
|
|
101
|
-
~50–70 ms wasm download + compile + worker bootstrap, which typically makes minidraco the fastest
|
|
102
|
-
option for the first model on screen.
|
|
42
|
+
Median across an 18-model corpus vs [draco.js](https://github.com/mrdoob/draco.js) and the official
|
|
43
|
+
[draco3d](https://www.npmjs.com/package/draco3d) wasm decoder (full results in
|
|
44
|
+
[BENCH.md](https://github.com/verekia/minidraco/blob/main/BENCH.md)):
|
|
103
45
|
|
|
104
|
-
|
|
46
|
+
| benchmark | vs draco.js | vs draco3d wasm |
|
|
47
|
+
| --------------------------------------------- | --------------- | --------------- |
|
|
48
|
+
| single-threaded decode — bun (JSC) | 🟢 1.23× faster | 🟢 1.32× faster |
|
|
49
|
+
| single-threaded decode — Chrome (V8) | 🟢 1.26× faster | 🟢 1.06× faster |
|
|
50
|
+
| `GLTFLoader.parse`, worker pool — Chrome (V8) | 🟢 1.46× faster | 🔴 1.05× slower |
|
|
51
|
+
|
|
52
|
+
Faster than draco.js across the corpus, ahead of the wasm decoder single-threaded, and
|
|
53
|
+
competitive in a real `GLTFLoader.parse` with the main thread left free.
|
|
105
54
|
|
|
106
|
-
|
|
107
|
-
it's shared; gzip -9 / brotli -q 11):
|
|
55
|
+
🟢 There's no `draco_decoder.wasm` to fetch and compile before the first decode, so minidraco (and draco.js) often beat the wasm decoder on first load (not reflected on the benchmark).
|
|
108
56
|
|
|
109
|
-
|
|
110
|
-
| ----------------------------------------------------- | ------ | ------- | ------- |
|
|
111
|
-
| minidraco — `minidraco/three` in the app bundle | 102 KB | 26.6 KB | 23.1 KB |
|
|
112
|
-
| minidraco — `worker.js` chunk (fetched on 1st decode) | 99 KB | 25.5 KB | 22.1 KB |
|
|
113
|
-
| draco.js — loader + decoder in the app bundle | 96 KB | 24.8 KB | 21.5 KB |
|
|
114
|
-
| draco3d — `draco_wasm_wrapper.js` (runtime fetch) | 78 KB | 13.1 KB | 11.0 KB |
|
|
115
|
-
| draco3d — `draco_decoder.wasm` (runtime fetch) | 279 KB | 86.1 KB | 64.6 KB |
|
|
57
|
+
## Download size
|
|
116
58
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
59
|
+
Over the wire (brotli): **~23 KB** ships in your app bundle. With the worker pool on (default) the
|
|
60
|
+
browser also fetches a **~22 KB** worker chunk on the first decode — **~45 KB** total; `workers: false`
|
|
61
|
+
skips that fetch. **draco.js ~22 KB**, **draco3d wasm ~76 KB** — the wasm is a separate file you
|
|
62
|
+
must host, while the JS decoders ship inside your bundle.
|
|
121
63
|
|
|
122
64
|
## Monorepo
|
|
123
65
|
|
|
124
66
|
- `library/` — the `minidraco` package
|
|
125
|
-
- `example/` — Next.js + React Three Fiber demo
|
|
126
|
-
animation playback, in-browser benchmark at `/bench`)
|
|
67
|
+
- `example/` — Next.js + React Three Fiber demo with an in-browser benchmark at `/bench`
|
|
127
68
|
|
|
128
69
|
```sh
|
|
129
70
|
bun install
|
|
130
|
-
bun dev #
|
|
131
|
-
bun run all # format
|
|
132
|
-
bun run bench # decoder
|
|
71
|
+
bun dev # watch build + demo
|
|
72
|
+
bun run all # format, lint, typecheck, tests
|
|
73
|
+
bun run bench # cross-decoder benchmark
|
|
133
74
|
```
|
|
134
75
|
|
|
135
|
-
The test suite decodes every Draco primitive of the bundle GLBs with minidraco and the official
|
|
136
|
-
draco3d wasm decoder and compares indices and every attribute value, plus 13 raw `.drc` fixtures
|
|
137
|
-
from Draco's test corpus covering the encodings the bundles don't hit.
|
|
138
|
-
|
|
139
76
|
## License
|
|
140
77
|
|
|
141
|
-
MIT —
|
|
142
|
-
|
|
78
|
+
MIT — derived from [mrdoob/draco.js](https://github.com/mrdoob/draco.js) (MIT), implementing
|
|
79
|
+
Google's [Draco](https://github.com/google/draco) bitstream (Apache-2.0).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,51 +1,9 @@
|
|
|
1
|
-
declare class BitDecoder {
|
|
2
|
-
_bitBuffer: Uint8Array | null;
|
|
3
|
-
_bitOffset: number;
|
|
4
|
-
_byteLength: number;
|
|
5
|
-
constructor();
|
|
6
|
-
reset(uint8Array: Uint8Array, byteLength: number): void;
|
|
7
|
-
bitsDecoded(): number;
|
|
8
|
-
getBits(nbits: number): number | undefined;
|
|
9
|
-
}
|
|
10
|
-
declare class DecoderBuffer {
|
|
11
|
-
_data: Uint8Array | null;
|
|
12
|
-
_dataView: DataView | null;
|
|
13
|
-
_dataSize: number;
|
|
14
|
-
_pos: number;
|
|
15
|
-
_bitDecoder: BitDecoder;
|
|
16
|
-
_bitMode: boolean;
|
|
17
|
-
_bitstreamVersion: number;
|
|
18
|
-
constructor();
|
|
19
|
-
init(data: ArrayBuffer | Uint8Array | ArrayLike<number>, dataSize?: number, version?: number): void;
|
|
20
|
-
decodeUint8(): number | undefined;
|
|
21
|
-
decodeInt8(): number | undefined;
|
|
22
|
-
decodeUint16(): number | undefined;
|
|
23
|
-
decodeUint32(): number | undefined;
|
|
24
|
-
decodeInt32(): number | undefined;
|
|
25
|
-
decodeFloat32(): number | undefined;
|
|
26
|
-
decodeUint64(): number | undefined;
|
|
27
|
-
decodeBytes(size: number): Uint8Array | undefined;
|
|
28
|
-
decodeBytesView(size: number): Uint8Array | undefined;
|
|
29
|
-
startBitDecoding(decodeSize: boolean): number | undefined;
|
|
30
|
-
endBitDecoding(): void;
|
|
31
|
-
decodeLeastSignificantBits32(nbits: number): number | undefined;
|
|
32
|
-
decodeVarintUint32(): number | undefined;
|
|
33
|
-
decodeVarintUint64(): number | undefined;
|
|
34
|
-
advance(bytes: number): void;
|
|
35
|
-
get bitstreamVersion(): number;
|
|
36
|
-
set bitstreamVersion(v: number);
|
|
37
|
-
get data(): Uint8Array;
|
|
38
|
-
get dataHead(): Uint8Array;
|
|
39
|
-
get remainingSize(): number;
|
|
40
|
-
get decodedSize(): number;
|
|
41
|
-
get bitDecoderActive(): boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
1
|
declare class DataBuffer {
|
|
45
2
|
_data: Uint8Array;
|
|
46
3
|
constructor();
|
|
47
4
|
update(data: Uint8Array | ArrayBufferView | ArrayBuffer | null | undefined, size: number, offset?: number): boolean;
|
|
48
5
|
resize(newSize: number): void;
|
|
6
|
+
adoptScratch(size: number): void;
|
|
49
7
|
write(bytePos: number, inArray: Uint8Array | ArrayBufferView | ArrayBuffer, dataSize: number): void;
|
|
50
8
|
get data(): Uint8Array;
|
|
51
9
|
get dataSize(): number;
|
|
@@ -87,10 +45,15 @@ declare class GeometryAttribute {
|
|
|
87
45
|
|
|
88
46
|
declare class AttributeTransformData {
|
|
89
47
|
_transformType: number;
|
|
90
|
-
|
|
48
|
+
_bytes: Uint8Array;
|
|
49
|
+
_view: DataView;
|
|
50
|
+
_size: number;
|
|
91
51
|
constructor();
|
|
92
52
|
get transformType(): number;
|
|
93
53
|
set transformType(type: number);
|
|
54
|
+
get dataSize(): number;
|
|
55
|
+
get data(): Uint8Array;
|
|
56
|
+
_reserve(sizeNeeded: number): void;
|
|
94
57
|
setParameterValue(byteOffset: number, value: number, type: string): void;
|
|
95
58
|
appendParameterValue(value: number, type: string): void;
|
|
96
59
|
_typeSize(type: string): number;
|
|
@@ -124,6 +87,7 @@ declare class PointAttribute extends GeometryAttribute {
|
|
|
124
87
|
constructor(geometryAttribute?: GeometryAttribute);
|
|
125
88
|
init(attributeType: number, numComponents: number, dataType: number, normalized: boolean, numAttributeValues: number): void;
|
|
126
89
|
reset(numAttributeValues: number): boolean;
|
|
90
|
+
resetScratch(numAttributeValues: number): boolean;
|
|
127
91
|
get size(): number;
|
|
128
92
|
mappedIndex(pointIndex: number): number;
|
|
129
93
|
get isMappingIdentity(): boolean;
|
|
@@ -132,6 +96,8 @@ declare class PointAttribute extends GeometryAttribute {
|
|
|
132
96
|
setIdentityMapping(): void;
|
|
133
97
|
setExplicitMapping(numPoints: number): void;
|
|
134
98
|
setExplicitMappingUnfilled(numPoints: number): void;
|
|
99
|
+
setExplicitMappingShared(map: Uint32Array): void;
|
|
100
|
+
setExplicitMappingScratch(numPoints: number): void;
|
|
135
101
|
setAttributeTransformData(transformData: AttributeTransformData): void;
|
|
136
102
|
convertValue(attIndex: number, outVal: number[] | Int32Array | Uint32Array | Float32Array | Float64Array): void;
|
|
137
103
|
extractTo<C extends new (length: number) => ExtractTypedArray>(OutputTypedArray: C, numPoints: number): InstanceType<C>;
|
|
@@ -169,60 +135,6 @@ declare class Mesh extends PointCloud {
|
|
|
169
135
|
setAttribute(attId: number, pa: PointAttribute): void;
|
|
170
136
|
}
|
|
171
137
|
|
|
172
|
-
declare class DracoOptions {
|
|
173
|
-
_globalOptions: Map<string, unknown>;
|
|
174
|
-
_attributeOptions: Map<number, Map<string, unknown>>;
|
|
175
|
-
constructor();
|
|
176
|
-
getGlobalBool(name: string, defaultVal: boolean): boolean;
|
|
177
|
-
findAttributeOptions(attKey: number): Map<string, unknown> | null;
|
|
178
|
-
getAttributeBool(attKey: number, name: string, defaultVal: boolean): boolean;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
declare class DecoderOptions extends DracoOptions {
|
|
182
|
-
constructor();
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
declare class Decoder {
|
|
186
|
-
options_: DecoderOptions;
|
|
187
|
-
constructor();
|
|
188
|
-
static getEncodedGeometryType(inBuffer: DecoderBuffer): number;
|
|
189
|
-
decodeMeshFromBuffer(inBuffer: DecoderBuffer): {
|
|
190
|
-
mesh: Mesh | null;
|
|
191
|
-
ok: boolean;
|
|
192
|
-
message: string;
|
|
193
|
-
};
|
|
194
|
-
decodeBufferToMesh(inBuffer: DecoderBuffer, outGeometry: Mesh): {
|
|
195
|
-
ok: boolean;
|
|
196
|
-
message: string;
|
|
197
|
-
};
|
|
198
|
-
options(): DecoderOptions;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
declare const DataType: {
|
|
202
|
-
readonly INVALID: 0;
|
|
203
|
-
readonly INT8: 1;
|
|
204
|
-
readonly UINT8: 2;
|
|
205
|
-
readonly INT16: 3;
|
|
206
|
-
readonly UINT16: 4;
|
|
207
|
-
readonly INT32: 5;
|
|
208
|
-
readonly UINT32: 6;
|
|
209
|
-
readonly INT64: 7;
|
|
210
|
-
readonly UINT64: 8;
|
|
211
|
-
readonly FLOAT32: 9;
|
|
212
|
-
readonly FLOAT64: 10;
|
|
213
|
-
readonly BOOL: 11;
|
|
214
|
-
readonly TYPES_COUNT: 12;
|
|
215
|
-
};
|
|
216
|
-
type DataType = (typeof DataType)[keyof typeof DataType];
|
|
217
|
-
|
|
218
|
-
declare const EncodedGeometryType: {
|
|
219
|
-
readonly INVALID_GEOMETRY_TYPE: -1;
|
|
220
|
-
readonly POINT_CLOUD: 0;
|
|
221
|
-
readonly TRIANGULAR_MESH: 1;
|
|
222
|
-
readonly NUM_ENCODED_GEOMETRY_TYPES: 2;
|
|
223
|
-
};
|
|
224
|
-
type EncodedGeometryType = (typeof EncodedGeometryType)[keyof typeof EncodedGeometryType];
|
|
225
|
-
|
|
226
138
|
declare const decodeDracoMesh: (data: Uint8Array) => Mesh;
|
|
227
139
|
|
|
228
|
-
export {
|
|
140
|
+
export { Type as GeometryAttributeType, Mesh, PointAttribute, decodeDracoMesh };
|