minidraco 0.1.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 ADDED
@@ -0,0 +1,112 @@
1
+ # minidraco
2
+
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.
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
17
+ import { MiniDRACOLoader } from 'minidraco/three'
18
+
19
+ const gltfLoader = new GLTFLoader()
20
+ gltfLoader.setDRACOLoader(new MiniDRACOLoader())
21
+ gltfLoader.load('model.glb', gltf => scene.add(gltf.scene))
22
+ ```
23
+
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.
28
+
29
+ Or decode a raw Draco bitstream without Three.js:
30
+
31
+ ```ts
32
+ import { decodeDracoMesh } from 'minidraco'
33
+
34
+ const mesh = decodeDracoMesh(new Uint8Array(bytes))
35
+ ```
36
+
37
+ ## Feature support
38
+
39
+ - Triangular meshes: edgebreaker (standard + valence) and sequential encodings, all bitstream
40
+ prediction schemes (delta, parallelogram, multi-parallelogram, constrained multi-parallelogram,
41
+ portable tex-coords, geometric octahedron normals)
42
+ - Quantized, integer, and octahedron-normal attributes
43
+ - Custom / generic attributes (glTF `_*` semantics), skinning attributes (`JOINTS_0` /
44
+ `WEIGHTS_0`), vertex colors, multiple UV sets
45
+ - Output is verified bit-identical to the official wasm decoder (≤ 1 ulp on dequantized floats)
46
+ in the test suite, on both real production GLBs and Draco's own test corpus
47
+ - Point clouds are not supported (glTF `KHR_draco_mesh_compression` only ever contains meshes)
48
+
49
+ ## Performance
50
+
51
+ Decoding the three production bundle GLBs in `example/public/models` (Apple Silicon, medians;
52
+ `bun run bench` for the harness). Raw single-threaded decode, bun/JSC:
53
+
54
+ | file (points / faces) | minidraco | draco.js | draco3d wasm |
55
+ | ------------------------------------ | --------- | -------- | ------------ |
56
+ | canine (1.1k / 0.5k) | 0.6 ms | 0.6 ms | 0.4 ms |
57
+ | player (5.1k / 2.5k) | 1.9 ms | 1.7 ms | 1.2 ms |
58
+ | static (291k / 221k, 488 primitives) | **50 ms** | 55 ms | 51 ms |
59
+
60
+ In the browser the worker pool changes the story for real scenes — wall-clock
61
+ `GLTFLoader.parse` of the 488-primitive static bundle (Chromium, warm loaders, `/bench` page of
62
+ the example):
63
+
64
+ | decoder | static bundle | main thread |
65
+ | ------------------------ | ------------- | ----------- |
66
+ | minidraco (4 workers) | ~51 ms | free |
67
+ | draco.js (main thread) | ~72 ms | blocked |
68
+ | draco3d wasm (4 workers) | ~23 ms | free |
69
+
70
+ And unlike the wasm decoder there is nothing to host or fetch: the first decode doesn't pay the
71
+ ~50–70 ms wasm download + compile + worker bootstrap, which typically makes minidraco the fastest
72
+ option for the first model on screen.
73
+
74
+ ## Download size
75
+
76
+ What the browser actually downloads per decoder (minified with esbuild, `three` external since
77
+ it's shared; gzip -9 / brotli -q 11):
78
+
79
+ | payload | plain | gzip | brotli |
80
+ | ----------------------------------------------------- | ------ | ------- | ------- |
81
+ | minidraco — `minidraco/three` in the app bundle | 102 KB | 26.6 KB | 23.1 KB |
82
+ | minidraco — `worker.js` chunk (fetched on 1st decode) | 99 KB | 25.5 KB | 22.1 KB |
83
+ | draco.js — loader + decoder in the app bundle | 96 KB | 24.8 KB | 21.5 KB |
84
+ | draco3d — `draco_wasm_wrapper.js` (runtime fetch) | 78 KB | 13.1 KB | 11.0 KB |
85
+ | draco3d — `draco_decoder.wasm` (runtime fetch) | 279 KB | 86.1 KB | 64.6 KB |
86
+
87
+ Totals over the wire (brotli): **minidraco ~45 KB** (23 KB in the app bundle + 22 KB worker
88
+ chunk, or just 23 KB with `setWorkerLimit(0)`), **draco.js ~22 KB**, **draco3d wasm ~76 KB** —
89
+ and the wasm files are separate runtime fetches you must host, on the critical path of the first
90
+ decode, while the JS decoders ship inside your existing bundle chunks.
91
+
92
+ ## Monorepo
93
+
94
+ - `library/` — the `minidraco` package
95
+ - `example/` — Next.js + React Three Fiber demo (model/decoder switcher, per-mesh filter,
96
+ animation playback, in-browser benchmark at `/bench`)
97
+
98
+ ```sh
99
+ bun install
100
+ bun dev # library watch build + example dev server
101
+ bun run all # format check, lint, typecheck, warden, tests
102
+ bun run bench # decoder comparison benchmark (bun)
103
+ ```
104
+
105
+ The test suite decodes every Draco primitive of the bundle GLBs with minidraco and the official
106
+ draco3d wasm decoder and compares indices and every attribute value, plus 13 raw `.drc` fixtures
107
+ from Draco's test corpus covering the encodings the bundles don't hit.
108
+
109
+ ## License
110
+
111
+ MIT — includes code derived from [mrdoob/draco.js](https://github.com/mrdoob/draco.js) (MIT),
112
+ implementing Google's [Draco](https://github.com/google/draco) bitstream (Apache-2.0).
@@ -0,0 +1,229 @@
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
+ declare class DataBuffer {
44
+ _data: Uint8Array;
45
+ constructor();
46
+ update(data: Uint8Array | ArrayBufferView | ArrayBuffer | null | undefined, size: number, offset?: number): boolean;
47
+ resize(newSize: number): void;
48
+ write(bytePos: number, inArray: Uint8Array | ArrayBufferView | ArrayBuffer, dataSize: number): void;
49
+ get data(): Uint8Array;
50
+ get dataSize(): number;
51
+ _resize(newSize: number): void;
52
+ }
53
+
54
+ declare const Type: {
55
+ readonly INVALID: -1;
56
+ readonly POSITION: 0;
57
+ readonly NORMAL: 1;
58
+ readonly COLOR: 2;
59
+ readonly TEX_COORD: 3;
60
+ readonly GENERIC: 4;
61
+ readonly NAMED_ATTRIBUTES_COUNT: 5;
62
+ };
63
+ declare class GeometryAttribute {
64
+ _buffer: DataBuffer | null;
65
+ _numComponents: number;
66
+ _dataType: number;
67
+ _normalized: boolean;
68
+ _byteStride: number;
69
+ _byteOffset: number;
70
+ _attributeType: number;
71
+ _uniqueId: number;
72
+ constructor();
73
+ init(attributeType: number, buffer: DataBuffer | null, numComponents: number, dataType: number, normalized: boolean, byteStride: number, byteOffset: number): void;
74
+ getAddress(attIndex: number): Uint8Array;
75
+ copyFrom(srcAtt: GeometryAttribute): boolean;
76
+ resetBuffer(buffer: DataBuffer, byteStride: number, byteOffset: number): void;
77
+ get attributeType(): number;
78
+ get dataType(): number;
79
+ get numComponents(): number;
80
+ get buffer(): DataBuffer | null;
81
+ get byteStride(): number;
82
+ get byteOffset(): number;
83
+ get uniqueId(): number;
84
+ set uniqueId(id: number);
85
+ }
86
+
87
+ declare class AttributeTransformData {
88
+ _transformType: number;
89
+ _buffer: DataBuffer;
90
+ constructor();
91
+ get transformType(): number;
92
+ set transformType(type: number);
93
+ setParameterValue(byteOffset: number, value: number, type: string): void;
94
+ appendParameterValue(value: number, type: string): void;
95
+ _typeSize(type: string): number;
96
+ }
97
+
98
+ type ExtractTypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array;
99
+ declare class PointAttribute extends GeometryAttribute {
100
+ _identityMapping: boolean;
101
+ _numUniqueEntries: number;
102
+ _indicesMap: number[] | Uint32Array;
103
+ _attributeBuffer: DataBuffer | null;
104
+ _attributeTransformData: AttributeTransformData | null;
105
+ _cachedFloat32View?: Float32Array;
106
+ _cachedFloat32Buffer?: ArrayBufferLike;
107
+ _cachedInt32View?: Int32Array;
108
+ _cachedInt32Buffer?: ArrayBufferLike;
109
+ _cachedUint32View?: Uint32Array;
110
+ _cachedUint32Buffer?: ArrayBufferLike;
111
+ _cachedUint16View?: Uint16Array;
112
+ _cachedUint16Buffer?: ArrayBufferLike;
113
+ _cachedInt16View?: Int16Array;
114
+ _cachedInt16Buffer?: ArrayBufferLike;
115
+ _cachedUint8View?: Uint8Array;
116
+ _cachedUint8Buffer?: ArrayBufferLike;
117
+ _cachedInt8View?: Int8Array;
118
+ _cachedInt8Buffer?: ArrayBufferLike;
119
+ _cachedFloat64View?: Float64Array;
120
+ _cachedFloat64Buffer?: ArrayBufferLike;
121
+ _cachedDataView?: DataView;
122
+ _cachedDVBuffer?: ArrayBufferLike;
123
+ constructor(geometryAttribute?: GeometryAttribute);
124
+ init(attributeType: number, numComponents: number, dataType: number, normalized: boolean, numAttributeValues: number): void;
125
+ reset(numAttributeValues: number): boolean;
126
+ get size(): number;
127
+ mappedIndex(pointIndex: number): number;
128
+ get isMappingIdentity(): boolean;
129
+ get indicesMapSize(): number;
130
+ get indicesMap(): number[] | Uint32Array;
131
+ setIdentityMapping(): void;
132
+ setExplicitMapping(numPoints: number): void;
133
+ setExplicitMappingUnfilled(numPoints: number): void;
134
+ setAttributeTransformData(transformData: AttributeTransformData): void;
135
+ convertValue(attIndex: number, outVal: number[] | Int32Array | Uint32Array | Float32Array | Float64Array): void;
136
+ extractTo<C extends new (length: number) => ExtractTypedArray>(OutputTypedArray: C, numPoints: number): InstanceType<C>;
137
+ copyFrom(srcAtt: PointAttribute): void;
138
+ }
139
+
140
+ declare class PointCloud {
141
+ num_points_: number;
142
+ attributes_: (PointAttribute | null)[];
143
+ named_attribute_index_: number[][];
144
+ constructor();
145
+ numNamedAttributes(type: number): number;
146
+ getNamedAttributeId(type: number, i?: number): number;
147
+ getNamedAttribute(type: number, i?: number): PointAttribute | null;
148
+ getAttributeByUniqueId(uniqueId: number): PointAttribute | null;
149
+ getAttributeIdByUniqueId(uniqueId: number): number;
150
+ numAttributes(): number;
151
+ attribute(attId: number): PointAttribute;
152
+ addAttribute(pa: PointAttribute): number;
153
+ setAttribute(attId: number, pa: PointAttribute): void;
154
+ numPoints(): number;
155
+ setNumPoints(num: number): void;
156
+ }
157
+
158
+ declare class Mesh extends PointCloud {
159
+ faces_: Int32Array;
160
+ numFaces_: number;
161
+ attribute_data_: {
162
+ elementType: number;
163
+ }[];
164
+ constructor();
165
+ _ensureFaceCapacity(numFaces: number): void;
166
+ addFace(face: ArrayLike<number>): void;
167
+ setNumFaces(numFaces: number): void;
168
+ numFaces(): number;
169
+ face(faceId: number): number[];
170
+ setAttribute(attId: number, pa: PointAttribute): void;
171
+ }
172
+
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
+ declare const decodeDracoMesh: (data: Uint8Array) => Mesh;
228
+
229
+ export { DataType, Decoder, DecoderBuffer, EncodedGeometryType, Type as GeometryAttributeType, Mesh, PointAttribute, decodeDracoMesh };