minidraco 0.2.0 → 0.3.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 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` replacement for [Three.js](https://threejs.org/) — no wasm files to host, no
5
- external decoder to fetch and compile, and a built-in worker pool so decoding never blocks the
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 { MiniDRACOLoader } from 'minidraco/three'
11
+ import { MinidracoLoader } from 'minidraco/three'
18
12
 
19
13
  const gltfLoader = new GLTFLoader()
20
- gltfLoader.setDRACOLoader(new MiniDRACOLoader())
14
+ gltfLoader.setDRACOLoader(new MinidracoLoader())
21
15
  gltfLoader.load('model.glb', gltf => scene.add(gltf.scene))
22
16
  ```
23
17
 
24
- `MiniDRACOLoader` is a structural drop-in for `THREE.DRACOLoader`: it's assignable to it with no
25
- cast on any three version (`setDecoderPath` and friends are no-ops), so
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 MiniDRACOLoader({ workers: false }) // decode synchronously on the main thread
34
- new MiniDRACOLoader({ workerLimit: 8 }) // pool size (default 4)
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
- **Serving JS from a CDN origin** (Next.js `assetPrefix`, etc.) works out of the box: browsers
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,50 @@ import { decodeDracoMesh } from 'minidraco'
53
31
  const mesh = decodeDracoMesh(new Uint8Array(bytes))
54
32
  ```
55
33
 
56
- ## Feature support
34
+ ## Features
57
35
 
58
- - Triangular meshes: edgebreaker (standard + valence) and sequential encodings, all bitstream
59
- prediction schemes (delta, parallelogram, multi-parallelogram, constrained multi-parallelogram,
60
- portable tex-coords, geometric octahedron normals)
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
- Full cross-decoder results the production bundles plus the 17 sample models shipped with
71
- [mrdoob/draco.js](https://github.com/mrdoob/draco.js) are in
72
- [BENCH.md](https://github.com/verekia/minidraco/blob/main/BENCH.md), regenerated by
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 a 19-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
- ## Download size
46
+ | benchmark | vs draco.js | vs draco3d wasm |
47
+ | --------------------------------------------- | --------------- | --------------- |
48
+ | single-threaded decode — bun (JSC) | ⚪ even | 🟢 1.13× faster |
49
+ | single-threaded decode — Chrome (V8) | ⚪ even | 🟢 1.50× faster |
50
+ | `GLTFLoader.parse`, worker pool — Chrome (V8) | 🟢 1.23× faster | 🔴 1.10× slower |
51
+
52
+ On par with draco.js, faster than the wasm decoder single-threaded, and competitive in a real
53
+ `GLTFLoader.parse` with the main thread left free.
105
54
 
106
- What the browser actually downloads per decoder (minified with esbuild, `three` external since
107
- it's shared; gzip -9 / brotli -q 11):
55
+ 🟢 **Cold start is the real win, and no warm benchmark shows it:** there's no `draco_decoder.wasm`
56
+ to fetch and compile before the first decode, so the first model on screen appears sooner.
108
57
 
109
- | payload | plain | gzip | brotli |
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 |
58
+ ## Download size
116
59
 
117
- Totals over the wire (brotli): **minidraco ~45 KB** (23 KB in the app bundle + 22 KB worker
118
- chunk, or just 23 KB with `setWorkerLimit(0)`), **draco.js ~22 KB**, **draco3d wasm ~76 KB**
119
- and the wasm files are separate runtime fetches you must host, on the critical path of the first
120
- decode, while the JS decoders ship inside your existing bundle chunks.
60
+ Over the wire (brotli): **~23 KB** ships in your app bundle. With the worker pool on (default) the
61
+ browser also fetches a **~22 KB** worker chunk on the first decode **~45 KB** total; `workers: false`
62
+ skips that fetch. **draco.js ~22 KB**, **draco3d wasm ~76 KB** the wasm is a separate file you
63
+ must host, while the JS decoders ship inside your bundle.
121
64
 
122
65
  ## Monorepo
123
66
 
124
67
  - `library/` — the `minidraco` package
125
- - `example/` — Next.js + React Three Fiber demo (model/decoder switcher, per-mesh filter,
126
- animation playback, in-browser benchmark at `/bench`)
68
+ - `example/` — Next.js + React Three Fiber demo with an in-browser benchmark at `/bench`
127
69
 
128
70
  ```sh
129
71
  bun install
130
- bun dev # library watch build + example dev server
131
- bun run all # format check, lint, typecheck, warden, tests
132
- bun run bench # decoder comparison benchmark (bun) — rewrites BENCH.md
72
+ bun dev # watch build + demo
73
+ bun run all # format, lint, typecheck, tests
74
+ bun run bench # cross-decoder benchmark
133
75
  ```
134
76
 
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
77
  ## License
140
78
 
141
- MIT — includes code derived from [mrdoob/draco.js](https://github.com/mrdoob/draco.js) (MIT),
142
- implementing Google's [Draco](https://github.com/google/draco) bitstream (Apache-2.0).
79
+ MIT — derived from [mrdoob/draco.js](https://github.com/mrdoob/draco.js) (MIT), implementing
80
+ Google's [Draco](https://github.com/google/draco) bitstream (Apache-2.0).
package/dist/index.d.ts CHANGED
@@ -1,46 +1,3 @@
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();
@@ -169,60 +126,6 @@ declare class Mesh extends PointCloud {
169
126
  setAttribute(attId: number, pa: PointAttribute): void;
170
127
  }
171
128
 
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
129
  declare const decodeDracoMesh: (data: Uint8Array) => Mesh;
227
130
 
228
- export { DataType, Decoder, DecoderBuffer, EncodedGeometryType, Type as GeometryAttributeType, Mesh, PointAttribute, decodeDracoMesh };
131
+ export { Type as GeometryAttributeType, Mesh, PointAttribute, decodeDracoMesh };