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 +112 -0
- package/dist/index.d.ts +229 -0
- package/dist/index.js +6685 -0
- package/dist/three.d.ts +177 -0
- package/dist/three.js +6932 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.js +6726 -0
- package/package.json +37 -0
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).
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|