minidraco 0.1.1 → 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,39 +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 API-compatible with `THREE.DRACOLoader` (`setDecoderPath` and friends are
25
- no-ops). Decoding runs in a pool of module workers (default 4, `setWorkerLimit(n)` to change,
26
- `0` to force synchronous main-thread decoding). If workers can't be spawned (SSR, exotic
27
- bundlers), it falls back to synchronous decoding automatically.
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:
28
20
 
29
- **Serving JS from a CDN origin** (Next.js `assetPrefix`, etc.) works out of the box: browsers
30
- refuse to construct a Worker from a cross-origin script, so minidraco bootstraps the worker
31
- through a same-origin blob module that imports the hashed CDN asset (a CORS request — your CDN
32
- must send `Access-Control-Allow-Origin`, which it already does if you load models or fonts from
33
- it). If even that fails, decoding falls back to the main thread rather than erroring.
34
- `setWorkerUrl(url)` exists as a manual override for exotic setups.
21
+ ```ts
22
+ new MinidracoLoader({ workers: false }) // decode on the main thread
23
+ new MinidracoLoader({ workerLimit: 8 }) // pool size (default 4)
24
+ ```
35
25
 
36
- Or decode a raw Draco bitstream without Three.js:
26
+ Or decode a raw bitstream without Three.js:
37
27
 
38
28
  ```ts
39
29
  import { decodeDracoMesh } from 'minidraco'
@@ -41,79 +31,50 @@ import { decodeDracoMesh } from 'minidraco'
41
31
  const mesh = decodeDracoMesh(new Uint8Array(bytes))
42
32
  ```
43
33
 
44
- ## Feature support
34
+ ## Features
45
35
 
46
- - Triangular meshes: edgebreaker (standard + valence) and sequential encodings, all bitstream
47
- prediction schemes (delta, parallelogram, multi-parallelogram, constrained multi-parallelogram,
48
- portable tex-coords, geometric octahedron normals)
49
- - Quantized, integer, and octahedron-normal attributes
50
- - Custom / generic attributes (glTF `_*` semantics), skinning attributes (`JOINTS_0` /
51
- `WEIGHTS_0`), vertex colors, multiple UV sets
52
- - Output is verified bit-identical to the official wasm decoder (≤ 1 ulp on dequantized floats)
53
- in the test suite, on both real production GLBs and Draco's own test corpus
54
- - 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).
55
39
 
56
40
  ## Performance
57
41
 
58
- Decoding the three production bundle GLBs in `example/public/models` (Apple Silicon, medians;
59
- `bun run bench` for the harness). Raw single-threaded decode, bun/JSC:
60
-
61
- | file (points / faces) | minidraco | draco.js | draco3d wasm |
62
- | ------------------------------------ | --------- | -------- | ------------ |
63
- | canine (1.1k / 0.5k) | 0.6 ms | 0.6 ms | 0.4 ms |
64
- | player (5.1k / 2.5k) | 1.9 ms | 1.7 ms | 1.2 ms |
65
- | static (291k / 221k, 488 primitives) | **50 ms** | 55 ms | 51 ms |
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)):
66
45
 
67
- In the browser the worker pool changes the story for real scenes — wall-clock
68
- `GLTFLoader.parse` of the 488-primitive static bundle (Chromium, warm loaders, `/bench` page of
69
- the example):
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 |
70
51
 
71
- | decoder | static bundle | main thread |
72
- | ------------------------ | ------------- | ----------- |
73
- | minidraco (4 workers) | ~51 ms | free |
74
- | draco.js (main thread) | ~72 ms | blocked |
75
- | draco3d wasm (4 workers) | ~23 ms | free |
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.
76
54
 
77
- And unlike the wasm decoder there is nothing to host or fetch: the first decode doesn't pay the
78
- ~50–70 ms wasm download + compile + worker bootstrap, which typically makes minidraco the fastest
79
- option for the first model on screen.
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.
80
57
 
81
58
  ## Download size
82
59
 
83
- What the browser actually downloads per decoder (minified with esbuild, `three` external since
84
- it's shared; gzip -9 / brotli -q 11):
85
-
86
- | payload | plain | gzip | brotli |
87
- | ----------------------------------------------------- | ------ | ------- | ------- |
88
- | minidraco — `minidraco/three` in the app bundle | 102 KB | 26.6 KB | 23.1 KB |
89
- | minidraco — `worker.js` chunk (fetched on 1st decode) | 99 KB | 25.5 KB | 22.1 KB |
90
- | draco.js — loader + decoder in the app bundle | 96 KB | 24.8 KB | 21.5 KB |
91
- | draco3d — `draco_wasm_wrapper.js` (runtime fetch) | 78 KB | 13.1 KB | 11.0 KB |
92
- | draco3d — `draco_decoder.wasm` (runtime fetch) | 279 KB | 86.1 KB | 64.6 KB |
93
-
94
- Totals over the wire (brotli): **minidraco ~45 KB** (23 KB in the app bundle + 22 KB worker
95
- chunk, or just 23 KB with `setWorkerLimit(0)`), **draco.js ~22 KB**, **draco3d wasm ~76 KB** —
96
- and the wasm files are separate runtime fetches you must host, on the critical path of the first
97
- 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.
98
64
 
99
65
  ## Monorepo
100
66
 
101
67
  - `library/` — the `minidraco` package
102
- - `example/` — Next.js + React Three Fiber demo (model/decoder switcher, per-mesh filter,
103
- animation playback, in-browser benchmark at `/bench`)
68
+ - `example/` — Next.js + React Three Fiber demo with an in-browser benchmark at `/bench`
104
69
 
105
70
  ```sh
106
71
  bun install
107
- bun dev # library watch build + example dev server
108
- bun run all # format check, lint, typecheck, warden, tests
109
- bun run bench # decoder comparison benchmark (bun)
72
+ bun dev # watch build + demo
73
+ bun run all # format, lint, typecheck, tests
74
+ bun run bench # cross-decoder benchmark
110
75
  ```
111
76
 
112
- The test suite decodes every Draco primitive of the bundle GLBs with minidraco and the official
113
- draco3d wasm decoder and compares indices and every attribute value, plus 13 raw `.drc` fixtures
114
- from Draco's test corpus covering the encodings the bundles don't hit.
115
-
116
77
  ## License
117
78
 
118
- MIT — includes code derived from [mrdoob/draco.js](https://github.com/mrdoob/draco.js) (MIT),
119
- 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,45 +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
- startBitDecoding(decodeSize: boolean): number | undefined;
29
- endBitDecoding(): void;
30
- decodeLeastSignificantBits32(nbits: number): number | undefined;
31
- decodeVarintUint32(): number | undefined;
32
- decodeVarintUint64(): number | undefined;
33
- advance(bytes: number): void;
34
- get bitstreamVersion(): number;
35
- set bitstreamVersion(v: number);
36
- get data(): Uint8Array;
37
- get dataHead(): Uint8Array;
38
- get remainingSize(): number;
39
- get decodedSize(): number;
40
- get bitDecoderActive(): boolean;
41
- }
42
-
43
1
  declare class DataBuffer {
44
2
  _data: Uint8Array;
45
3
  constructor();
@@ -162,68 +120,12 @@ declare class Mesh extends PointCloud {
162
120
  elementType: number;
163
121
  }[];
164
122
  constructor();
165
- _ensureFaceCapacity(numFaces: number): void;
166
- addFace(face: ArrayLike<number>): void;
167
123
  setNumFaces(numFaces: number): void;
168
124
  numFaces(): number;
169
125
  face(faceId: number): number[];
170
126
  setAttribute(attId: number, pa: PointAttribute): void;
171
127
  }
172
128
 
173
- declare class DracoOptions {
174
- _globalOptions: Map<string, unknown>;
175
- _attributeOptions: Map<number, Map<string, unknown>>;
176
- constructor();
177
- getGlobalBool(name: string, defaultVal: boolean): boolean;
178
- findAttributeOptions(attKey: number): Map<string, unknown> | null;
179
- getAttributeBool(attKey: number, name: string, defaultVal: boolean): boolean;
180
- }
181
-
182
- declare class DecoderOptions extends DracoOptions {
183
- constructor();
184
- }
185
-
186
- declare class Decoder {
187
- options_: DecoderOptions;
188
- constructor();
189
- static getEncodedGeometryType(inBuffer: DecoderBuffer): number;
190
- decodeMeshFromBuffer(inBuffer: DecoderBuffer): {
191
- mesh: Mesh | null;
192
- ok: boolean;
193
- message: string;
194
- };
195
- decodeBufferToMesh(inBuffer: DecoderBuffer, outGeometry: Mesh): {
196
- ok: boolean;
197
- message: string;
198
- };
199
- options(): DecoderOptions;
200
- }
201
-
202
- declare const DataType: {
203
- readonly INVALID: 0;
204
- readonly INT8: 1;
205
- readonly UINT8: 2;
206
- readonly INT16: 3;
207
- readonly UINT16: 4;
208
- readonly INT32: 5;
209
- readonly UINT32: 6;
210
- readonly INT64: 7;
211
- readonly UINT64: 8;
212
- readonly FLOAT32: 9;
213
- readonly FLOAT64: 10;
214
- readonly BOOL: 11;
215
- readonly TYPES_COUNT: 12;
216
- };
217
- type DataType = (typeof DataType)[keyof typeof DataType];
218
-
219
- declare const EncodedGeometryType: {
220
- readonly INVALID_GEOMETRY_TYPE: -1;
221
- readonly POINT_CLOUD: 0;
222
- readonly TRIANGULAR_MESH: 1;
223
- readonly NUM_ENCODED_GEOMETRY_TYPES: 2;
224
- };
225
- type EncodedGeometryType = (typeof EncodedGeometryType)[keyof typeof EncodedGeometryType];
226
-
227
129
  declare const decodeDracoMesh: (data: Uint8Array) => Mesh;
228
130
 
229
- export { DataType, Decoder, DecoderBuffer, EncodedGeometryType, Type as GeometryAttributeType, Mesh, PointAttribute, decodeDracoMesh };
131
+ export { Type as GeometryAttributeType, Mesh, PointAttribute, decodeDracoMesh };