@draco-rust/decoder 0.1.2 → 0.2.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 +20 -20
- package/legacy/three.d.ts +11 -57
- package/legacy/three.js +66 -107
- package/mesh/three.d.ts +11 -57
- package/mesh/three.js +66 -107
- package/package.json +10 -2
- package/point-cloud/three.d.ts +11 -57
- package/point-cloud/three.js +66 -107
- package/three.d.ts +11 -57
- package/three.js +66 -107
package/README.md
CHANGED
|
@@ -85,36 +85,36 @@ webpack do, emits the worker on its own.
|
|
|
85
85
|
|
|
86
86
|
### With three.js
|
|
87
87
|
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
`@draco-rust/decoder/three` exports `DRACOLoader`: three.js's own
|
|
89
|
+
`DRACOLoader`, decoding on this package's workers instead of the Draco decoder
|
|
90
|
+
three fetches with `setDecoderPath`. Everything else is three's class, so it
|
|
91
|
+
goes wherever one is expected, TypeScript included:
|
|
91
92
|
|
|
92
93
|
```js
|
|
93
|
-
import * as THREE from "three";
|
|
94
94
|
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
95
|
-
import {
|
|
95
|
+
import { DRACOLoader } from "@draco-rust/decoder/mesh/three";
|
|
96
96
|
|
|
97
|
-
const gltfLoader = new GLTFLoader();
|
|
98
|
-
gltfLoader.setDRACOLoader(createDracoLoader(THREE));
|
|
97
|
+
const gltfLoader = new GLTFLoader().setDRACOLoader(new DRACOLoader());
|
|
99
98
|
const gltf = await gltfLoader.loadAsync("model.glb");
|
|
100
99
|
```
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
attribute
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
`
|
|
112
|
-
|
|
101
|
+
`load`, `parse`, `setWorkerLimit`, `preload` and `dispose` work as three's do,
|
|
102
|
+
and so does the geometry: three builds it, so vertex colours, normalization and
|
|
103
|
+
the padding r181 added follow the three you have. Byte for byte it is the
|
|
104
|
+
geometry three's own loader builds, with one difference: an attribute that
|
|
105
|
+
does not convert to the type asked for -- a negative value into a
|
|
106
|
+
`Uint16Array`, say -- is an error here, where three's loader fills the
|
|
107
|
+
attribute with whatever its failed conversion left in memory.
|
|
108
|
+
|
|
109
|
+
The constructor takes three's `LoadingManager` and then the pool's options,
|
|
110
|
+
`new DRACOLoader(manager, { workers: 2 })`, or `{ pool }` to share one pool
|
|
111
|
+
between loaders. `three` is a peer dependency, from r150 (`0.150.0`); the
|
|
112
|
+
package's other entries do not need it.
|
|
113
113
|
|
|
114
114
|
### From a CDN, without a bundler
|
|
115
115
|
|
|
116
116
|
```js
|
|
117
|
-
import init, { parse_drc_bytes } from "https://cdn.jsdelivr.net/npm/@draco-rust/decoder@0.
|
|
117
|
+
import init, { parse_drc_bytes } from "https://cdn.jsdelivr.net/npm/@draco-rust/decoder@0.2/mesh/index.js";
|
|
118
118
|
await init(); // finds index_bg.wasm next to the script
|
|
119
119
|
```
|
|
120
120
|
|
|
@@ -141,4 +141,4 @@ For a subpath entry, resolve its own file, for example
|
|
|
141
141
|
- Licensed under Apache-2.0, like Draco.
|
|
142
142
|
## This build
|
|
143
143
|
|
|
144
|
-
`@draco-rust/decoder@0.
|
|
144
|
+
`@draco-rust/decoder@0.2.0` was built from draco-rust commit `8650e1ca`, with draco-core 2.3.2, draco-io 0.6.0, draco-gltf 0.7.0.
|
package/legacy/three.d.ts
CHANGED
|
@@ -1,65 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LoadingManager } from 'three';
|
|
2
|
+
import { DRACOLoader as ThreeDRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
2
3
|
import type { DecoderPool, DecoderPoolOptions } from './pool.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
* The three.js classes the loader builds with, passed in so the package uses
|
|
6
|
-
* the application's own copy of three: `import * as THREE from "three"` will
|
|
7
|
-
* do. With `InterleavedBuffer` and `InterleavedBufferAttribute`, an attribute
|
|
8
|
-
* whose items are not a multiple of four bytes is padded to one, as DRACOLoader
|
|
9
|
-
* pads it since r181; with `Color` and `ColorManagement`, a `.drc` file's sRGB
|
|
10
|
-
* vertex colours are made linear as DRACOLoader makes them.
|
|
11
|
-
*/
|
|
12
|
-
export interface ThreeClasses<Geometry> {
|
|
13
|
-
BufferGeometry: new () => Geometry;
|
|
14
|
-
BufferAttribute: new (array: any, itemSize: number, normalized?: boolean) => any;
|
|
15
|
-
InterleavedBuffer?: new (array: any, stride: number) => any;
|
|
16
|
-
InterleavedBufferAttribute?: new (buffer: any, itemSize: number, offset: number) => any;
|
|
17
|
-
Color?: new () => any;
|
|
18
|
-
/** `colorSpaceToWorking` since three r177, `toWorkingColorSpace` before. */
|
|
19
|
-
ColorManagement?:
|
|
20
|
-
| { colorSpaceToWorking(color: any, colorSpace: string): any }
|
|
21
|
-
| { toWorkingColorSpace(color: any, colorSpace: string): any };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface DracoLoaderOptions extends DecoderPoolOptions {
|
|
5
|
+
export interface DRACOLoaderOptions extends DecoderPoolOptions {
|
|
25
6
|
/** A pool to share with other loaders, instead of one of the loader's own. */
|
|
26
7
|
pool?: DecoderPool;
|
|
27
8
|
}
|
|
28
9
|
|
|
29
|
-
export interface DecodeGeometryOptions {
|
|
30
|
-
/** three.js attribute name to Draco unique id, as glTF names them. Without
|
|
31
|
-
* it, `position`, `normal`, `color` and `uv` are read by type. */
|
|
32
|
-
attributeIDs?: Record<string, number>;
|
|
33
|
-
/** three.js attribute name to the typed array it comes back in. */
|
|
34
|
-
attributeTypes?: Record<string, DracoArrayType>;
|
|
35
|
-
/** `"srgb"` makes vertex colours linear; the default leaves them as stored. */
|
|
36
|
-
vertexColorSpace?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The part of three.js's DRACOLoader that GLTFLoader and `.drc` users call. */
|
|
40
|
-
export interface DracoLoader<Geometry> {
|
|
41
|
-
readonly pool: DecoderPool;
|
|
42
|
-
decodeGeometry(buffer: ArrayBuffer | ArrayBufferView, options?: DecodeGeometryOptions): Promise<Geometry>;
|
|
43
|
-
decodeDracoFile(
|
|
44
|
-
buffer: ArrayBuffer,
|
|
45
|
-
callback: (geometry: Geometry) => void,
|
|
46
|
-
attributeIDs?: Record<string, number> | null,
|
|
47
|
-
attributeTypes?: Record<string, DracoArrayType> | null,
|
|
48
|
-
vertexColorSpace?: string,
|
|
49
|
-
onError?: (error: unknown) => void,
|
|
50
|
-
): Promise<void>;
|
|
51
|
-
parse(buffer: ArrayBuffer, onLoad: (geometry: Geometry) => void, onError?: (error: unknown) => void): void;
|
|
52
|
-
parseAsync(buffer: ArrayBuffer | ArrayBufferView): Promise<Geometry>;
|
|
53
|
-
preload(): this;
|
|
54
|
-
setWorkerLimit(workers: number): this;
|
|
55
|
-
dispose(): this;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
10
|
/**
|
|
59
|
-
*
|
|
60
|
-
* `
|
|
11
|
+
* three.js's `DRACOLoader`, decoding on this entry's worker pool instead of
|
|
12
|
+
* the Draco decoder three fetches from `setDecoderPath`. It is three's class,
|
|
13
|
+
* so it goes wherever one is expected:
|
|
14
|
+
* `gltfLoader.setDRACOLoader(new DRACOLoader())`.
|
|
61
15
|
*/
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
16
|
+
export class DRACOLoader extends ThreeDRACOLoader {
|
|
17
|
+
constructor(manager?: LoadingManager, options?: DRACOLoaderOptions);
|
|
18
|
+
readonly pool: DecoderPool;
|
|
19
|
+
}
|
package/legacy/three.js
CHANGED
|
@@ -1,123 +1,82 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// three.js's DRACOLoader, decoding on this entry's worker pool. Everything a
|
|
2
|
+
// caller sees -- load, parse, decodeDracoFile, GLTFLoader's use of it, the
|
|
3
|
+
// geometry it builds -- is three's own class; only the decode is replaced, so
|
|
4
|
+
// the result follows whichever three the application has.
|
|
5
|
+
import { REVISION } from 'three';
|
|
6
|
+
import { DRACOLoader as ThreeDRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
4
7
|
import { createDecoderPool } from './pool.js';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
// From r181 DRACOLoader pads an attribute whose items are not a multiple of
|
|
10
|
+
// four bytes and builds it interleaved, from the stride its worker reports.
|
|
11
|
+
const PADS = Number.parseInt(REVISION, 10) >= 181;
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export function createDracoLoader(three, options = {}) {
|
|
18
|
-
const { BufferGeometry, BufferAttribute, InterleavedBuffer, InterleavedBufferAttribute, Color, ColorManagement } =
|
|
19
|
-
three;
|
|
20
|
-
if (typeof BufferGeometry !== 'function' || typeof BufferAttribute !== 'function') {
|
|
21
|
-
throw new TypeError("createDracoLoader needs three.js's BufferGeometry and BufferAttribute");
|
|
13
|
+
function padded(array, itemSize) {
|
|
14
|
+
const bytes = array.BYTES_PER_ELEMENT;
|
|
15
|
+
const stride = (Math.ceil((itemSize * bytes) / 4) * 4) / bytes;
|
|
16
|
+
if (!PADS || stride === itemSize) return { array, stride: itemSize };
|
|
17
|
+
const count = array.length / itemSize;
|
|
18
|
+
const out = new array.constructor(count * stride);
|
|
19
|
+
for (let i = 0; i < count; i += 1) {
|
|
20
|
+
out.set(array.subarray(i * itemSize, (i + 1) * itemSize), i * stride);
|
|
22
21
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// decode a second time; GLTFLoader asks again for a primitive it shares, and
|
|
26
|
-
// gets the first answer, as DRACOLoader gives it.
|
|
27
|
-
const tasks = new WeakMap();
|
|
22
|
+
return { array: out, stride };
|
|
23
|
+
}
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
function toWorkingColorSpace(attribute) {
|
|
32
|
-
const convert = ColorManagement?.colorSpaceToWorking ?? ColorManagement?.toWorkingColorSpace;
|
|
33
|
-
if (!Color || !convert) return;
|
|
34
|
-
const color = new Color();
|
|
35
|
-
for (let i = 0; i < attribute.count; i += 1) {
|
|
36
|
-
color.fromBufferAttribute(attribute, i);
|
|
37
|
-
convert.call(ColorManagement, color, SRGB);
|
|
38
|
-
attribute.setXYZ(i, color.r, color.g, color.b);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
25
|
+
export class DRACOLoader extends ThreeDRACOLoader {
|
|
26
|
+
#tasks = new WeakMap();
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function attributeOf(array, itemSize) {
|
|
46
|
-
const bytes = array.BYTES_PER_ELEMENT;
|
|
47
|
-
const stride = Math.ceil((itemSize * bytes) / 4) * (4 / bytes);
|
|
48
|
-
if (stride === itemSize || !InterleavedBuffer || !InterleavedBufferAttribute) {
|
|
49
|
-
return new BufferAttribute(array, itemSize);
|
|
50
|
-
}
|
|
51
|
-
const count = array.length / itemSize;
|
|
52
|
-
const padded = new array.constructor(count * stride);
|
|
53
|
-
for (let i = 0; i < count; i += 1) {
|
|
54
|
-
padded.set(array.subarray(i * itemSize, (i + 1) * itemSize), i * stride);
|
|
55
|
-
}
|
|
56
|
-
return new InterleavedBufferAttribute(new InterleavedBuffer(padded, stride), itemSize, 0);
|
|
28
|
+
constructor(manager, options = {}) {
|
|
29
|
+
super(manager);
|
|
30
|
+
this.pool = options.pool ?? createDecoderPool(options);
|
|
57
31
|
}
|
|
58
32
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
33
|
+
// The buffer goes to a worker by transfer, which leaves nothing here to
|
|
34
|
+
// decode again; a second request for it gets the first answer, as three's
|
|
35
|
+
// own task cache gives it.
|
|
36
|
+
decodeGeometry(buffer, taskConfig) {
|
|
37
|
+
const key = JSON.stringify(taskConfig);
|
|
38
|
+
const cached = this.#tasks.get(buffer);
|
|
39
|
+
if (cached?.key === key) return cached.promise;
|
|
40
|
+
if (cached && buffer.byteLength === 0) {
|
|
41
|
+
return Promise.reject(new Error('THREE.DRACOLoader: Unable to re-decode a buffer with different settings. Buffer has already been transferred.'));
|
|
42
|
+
}
|
|
43
|
+
const { attributeIDs, attributeTypes, useUniqueIDs, vertexColorSpace } = taskConfig;
|
|
44
|
+
const requests = Object.keys(attributeIDs).map((name) => ({
|
|
62
45
|
name,
|
|
63
|
-
...(
|
|
64
|
-
type: attributeTypes
|
|
46
|
+
...(useUniqueIDs ? { id: attributeIDs[name] } : { semantic: attributeIDs[name] }),
|
|
47
|
+
type: attributeTypes[name],
|
|
65
48
|
}));
|
|
66
|
-
const
|
|
67
|
-
|
|
49
|
+
const promise = this.pool.decode(buffer, requests, { transfer: buffer instanceof ArrayBuffer }).then((result) => {
|
|
50
|
+
if (!result.success) throw new Error(`THREE.DRACOLoader: Decoding failed: ${result.error}`);
|
|
51
|
+
return this._createGeometry({
|
|
52
|
+
index: result.index ? { array: result.index, itemSize: 1 } : null,
|
|
53
|
+
attributes: result.attributes.map(({ name, array, itemSize }) => ({
|
|
54
|
+
name,
|
|
55
|
+
itemSize,
|
|
56
|
+
...padded(array, itemSize),
|
|
57
|
+
...(name === 'color' ? { vertexColorSpace } : {}),
|
|
58
|
+
})),
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
if (buffer instanceof ArrayBuffer) this.#tasks.set(buffer, { key, promise });
|
|
62
|
+
return promise;
|
|
63
|
+
}
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (name === 'color') {
|
|
74
|
-
if (vertexColorSpace === SRGB) toWorkingColorSpace(attribute);
|
|
75
|
-
attribute.normalized = !(array instanceof Float32Array);
|
|
76
|
-
}
|
|
77
|
-
geometry.setAttribute(name, attribute);
|
|
78
|
-
}
|
|
79
|
-
return geometry;
|
|
65
|
+
setWorkerLimit(workerLimit) {
|
|
66
|
+
super.setWorkerLimit(workerLimit);
|
|
67
|
+
this.pool.setWorkerLimit(workerLimit);
|
|
68
|
+
return this;
|
|
80
69
|
}
|
|
81
70
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (cached && buffer.byteLength === 0) {
|
|
87
|
-
return Promise.reject(new Error('this buffer was already decoded with other settings and handed to a worker'));
|
|
88
|
-
}
|
|
89
|
-
const promise = decode(buffer, attributeIDs, attributeTypes, vertexColorSpace);
|
|
90
|
-
if (buffer instanceof ArrayBuffer) tasks.set(buffer, { key, promise });
|
|
91
|
-
return promise;
|
|
71
|
+
preload() {
|
|
72
|
+
// A failure shows again on the first decode, which is where it is handled.
|
|
73
|
+
this.pool.preload().catch(() => {});
|
|
74
|
+
return this;
|
|
92
75
|
}
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return decodeGeometry(buffer, { attributeIDs, attributeTypes, vertexColorSpace }).then(callback).catch(onError);
|
|
100
|
-
},
|
|
101
|
-
/** A `.drc` file's contents, as DRACOLoader's `parse`. */
|
|
102
|
-
parse(buffer, onLoad, onError = () => {}) {
|
|
103
|
-
loader.decodeDracoFile(buffer, onLoad, null, null, SRGB, onError);
|
|
104
|
-
},
|
|
105
|
-
parseAsync(buffer) {
|
|
106
|
-
return decodeGeometry(buffer, { vertexColorSpace: SRGB });
|
|
107
|
-
},
|
|
108
|
-
preload() {
|
|
109
|
-
// A failure shows again on the first decode, which is where it is handled.
|
|
110
|
-
pool.preload().catch(() => {});
|
|
111
|
-
return loader;
|
|
112
|
-
},
|
|
113
|
-
setWorkerLimit(workers) {
|
|
114
|
-
pool.setWorkerLimit(workers);
|
|
115
|
-
return loader;
|
|
116
|
-
},
|
|
117
|
-
dispose() {
|
|
118
|
-
pool.dispose();
|
|
119
|
-
return loader;
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
return loader;
|
|
77
|
+
dispose() {
|
|
78
|
+
super.dispose();
|
|
79
|
+
this.pool.dispose();
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
123
82
|
}
|
package/mesh/three.d.ts
CHANGED
|
@@ -1,65 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LoadingManager } from 'three';
|
|
2
|
+
import { DRACOLoader as ThreeDRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
2
3
|
import type { DecoderPool, DecoderPoolOptions } from './pool.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
* The three.js classes the loader builds with, passed in so the package uses
|
|
6
|
-
* the application's own copy of three: `import * as THREE from "three"` will
|
|
7
|
-
* do. With `InterleavedBuffer` and `InterleavedBufferAttribute`, an attribute
|
|
8
|
-
* whose items are not a multiple of four bytes is padded to one, as DRACOLoader
|
|
9
|
-
* pads it since r181; with `Color` and `ColorManagement`, a `.drc` file's sRGB
|
|
10
|
-
* vertex colours are made linear as DRACOLoader makes them.
|
|
11
|
-
*/
|
|
12
|
-
export interface ThreeClasses<Geometry> {
|
|
13
|
-
BufferGeometry: new () => Geometry;
|
|
14
|
-
BufferAttribute: new (array: any, itemSize: number, normalized?: boolean) => any;
|
|
15
|
-
InterleavedBuffer?: new (array: any, stride: number) => any;
|
|
16
|
-
InterleavedBufferAttribute?: new (buffer: any, itemSize: number, offset: number) => any;
|
|
17
|
-
Color?: new () => any;
|
|
18
|
-
/** `colorSpaceToWorking` since three r177, `toWorkingColorSpace` before. */
|
|
19
|
-
ColorManagement?:
|
|
20
|
-
| { colorSpaceToWorking(color: any, colorSpace: string): any }
|
|
21
|
-
| { toWorkingColorSpace(color: any, colorSpace: string): any };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface DracoLoaderOptions extends DecoderPoolOptions {
|
|
5
|
+
export interface DRACOLoaderOptions extends DecoderPoolOptions {
|
|
25
6
|
/** A pool to share with other loaders, instead of one of the loader's own. */
|
|
26
7
|
pool?: DecoderPool;
|
|
27
8
|
}
|
|
28
9
|
|
|
29
|
-
export interface DecodeGeometryOptions {
|
|
30
|
-
/** three.js attribute name to Draco unique id, as glTF names them. Without
|
|
31
|
-
* it, `position`, `normal`, `color` and `uv` are read by type. */
|
|
32
|
-
attributeIDs?: Record<string, number>;
|
|
33
|
-
/** three.js attribute name to the typed array it comes back in. */
|
|
34
|
-
attributeTypes?: Record<string, DracoArrayType>;
|
|
35
|
-
/** `"srgb"` makes vertex colours linear; the default leaves them as stored. */
|
|
36
|
-
vertexColorSpace?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The part of three.js's DRACOLoader that GLTFLoader and `.drc` users call. */
|
|
40
|
-
export interface DracoLoader<Geometry> {
|
|
41
|
-
readonly pool: DecoderPool;
|
|
42
|
-
decodeGeometry(buffer: ArrayBuffer | ArrayBufferView, options?: DecodeGeometryOptions): Promise<Geometry>;
|
|
43
|
-
decodeDracoFile(
|
|
44
|
-
buffer: ArrayBuffer,
|
|
45
|
-
callback: (geometry: Geometry) => void,
|
|
46
|
-
attributeIDs?: Record<string, number> | null,
|
|
47
|
-
attributeTypes?: Record<string, DracoArrayType> | null,
|
|
48
|
-
vertexColorSpace?: string,
|
|
49
|
-
onError?: (error: unknown) => void,
|
|
50
|
-
): Promise<void>;
|
|
51
|
-
parse(buffer: ArrayBuffer, onLoad: (geometry: Geometry) => void, onError?: (error: unknown) => void): void;
|
|
52
|
-
parseAsync(buffer: ArrayBuffer | ArrayBufferView): Promise<Geometry>;
|
|
53
|
-
preload(): this;
|
|
54
|
-
setWorkerLimit(workers: number): this;
|
|
55
|
-
dispose(): this;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
10
|
/**
|
|
59
|
-
*
|
|
60
|
-
* `
|
|
11
|
+
* three.js's `DRACOLoader`, decoding on this entry's worker pool instead of
|
|
12
|
+
* the Draco decoder three fetches from `setDecoderPath`. It is three's class,
|
|
13
|
+
* so it goes wherever one is expected:
|
|
14
|
+
* `gltfLoader.setDRACOLoader(new DRACOLoader())`.
|
|
61
15
|
*/
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
16
|
+
export class DRACOLoader extends ThreeDRACOLoader {
|
|
17
|
+
constructor(manager?: LoadingManager, options?: DRACOLoaderOptions);
|
|
18
|
+
readonly pool: DecoderPool;
|
|
19
|
+
}
|
package/mesh/three.js
CHANGED
|
@@ -1,123 +1,82 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// three.js's DRACOLoader, decoding on this entry's worker pool. Everything a
|
|
2
|
+
// caller sees -- load, parse, decodeDracoFile, GLTFLoader's use of it, the
|
|
3
|
+
// geometry it builds -- is three's own class; only the decode is replaced, so
|
|
4
|
+
// the result follows whichever three the application has.
|
|
5
|
+
import { REVISION } from 'three';
|
|
6
|
+
import { DRACOLoader as ThreeDRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
4
7
|
import { createDecoderPool } from './pool.js';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
// From r181 DRACOLoader pads an attribute whose items are not a multiple of
|
|
10
|
+
// four bytes and builds it interleaved, from the stride its worker reports.
|
|
11
|
+
const PADS = Number.parseInt(REVISION, 10) >= 181;
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export function createDracoLoader(three, options = {}) {
|
|
18
|
-
const { BufferGeometry, BufferAttribute, InterleavedBuffer, InterleavedBufferAttribute, Color, ColorManagement } =
|
|
19
|
-
three;
|
|
20
|
-
if (typeof BufferGeometry !== 'function' || typeof BufferAttribute !== 'function') {
|
|
21
|
-
throw new TypeError("createDracoLoader needs three.js's BufferGeometry and BufferAttribute");
|
|
13
|
+
function padded(array, itemSize) {
|
|
14
|
+
const bytes = array.BYTES_PER_ELEMENT;
|
|
15
|
+
const stride = (Math.ceil((itemSize * bytes) / 4) * 4) / bytes;
|
|
16
|
+
if (!PADS || stride === itemSize) return { array, stride: itemSize };
|
|
17
|
+
const count = array.length / itemSize;
|
|
18
|
+
const out = new array.constructor(count * stride);
|
|
19
|
+
for (let i = 0; i < count; i += 1) {
|
|
20
|
+
out.set(array.subarray(i * itemSize, (i + 1) * itemSize), i * stride);
|
|
22
21
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// decode a second time; GLTFLoader asks again for a primitive it shares, and
|
|
26
|
-
// gets the first answer, as DRACOLoader gives it.
|
|
27
|
-
const tasks = new WeakMap();
|
|
22
|
+
return { array: out, stride };
|
|
23
|
+
}
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
function toWorkingColorSpace(attribute) {
|
|
32
|
-
const convert = ColorManagement?.colorSpaceToWorking ?? ColorManagement?.toWorkingColorSpace;
|
|
33
|
-
if (!Color || !convert) return;
|
|
34
|
-
const color = new Color();
|
|
35
|
-
for (let i = 0; i < attribute.count; i += 1) {
|
|
36
|
-
color.fromBufferAttribute(attribute, i);
|
|
37
|
-
convert.call(ColorManagement, color, SRGB);
|
|
38
|
-
attribute.setXYZ(i, color.r, color.g, color.b);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
25
|
+
export class DRACOLoader extends ThreeDRACOLoader {
|
|
26
|
+
#tasks = new WeakMap();
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function attributeOf(array, itemSize) {
|
|
46
|
-
const bytes = array.BYTES_PER_ELEMENT;
|
|
47
|
-
const stride = Math.ceil((itemSize * bytes) / 4) * (4 / bytes);
|
|
48
|
-
if (stride === itemSize || !InterleavedBuffer || !InterleavedBufferAttribute) {
|
|
49
|
-
return new BufferAttribute(array, itemSize);
|
|
50
|
-
}
|
|
51
|
-
const count = array.length / itemSize;
|
|
52
|
-
const padded = new array.constructor(count * stride);
|
|
53
|
-
for (let i = 0; i < count; i += 1) {
|
|
54
|
-
padded.set(array.subarray(i * itemSize, (i + 1) * itemSize), i * stride);
|
|
55
|
-
}
|
|
56
|
-
return new InterleavedBufferAttribute(new InterleavedBuffer(padded, stride), itemSize, 0);
|
|
28
|
+
constructor(manager, options = {}) {
|
|
29
|
+
super(manager);
|
|
30
|
+
this.pool = options.pool ?? createDecoderPool(options);
|
|
57
31
|
}
|
|
58
32
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
33
|
+
// The buffer goes to a worker by transfer, which leaves nothing here to
|
|
34
|
+
// decode again; a second request for it gets the first answer, as three's
|
|
35
|
+
// own task cache gives it.
|
|
36
|
+
decodeGeometry(buffer, taskConfig) {
|
|
37
|
+
const key = JSON.stringify(taskConfig);
|
|
38
|
+
const cached = this.#tasks.get(buffer);
|
|
39
|
+
if (cached?.key === key) return cached.promise;
|
|
40
|
+
if (cached && buffer.byteLength === 0) {
|
|
41
|
+
return Promise.reject(new Error('THREE.DRACOLoader: Unable to re-decode a buffer with different settings. Buffer has already been transferred.'));
|
|
42
|
+
}
|
|
43
|
+
const { attributeIDs, attributeTypes, useUniqueIDs, vertexColorSpace } = taskConfig;
|
|
44
|
+
const requests = Object.keys(attributeIDs).map((name) => ({
|
|
62
45
|
name,
|
|
63
|
-
...(
|
|
64
|
-
type: attributeTypes
|
|
46
|
+
...(useUniqueIDs ? { id: attributeIDs[name] } : { semantic: attributeIDs[name] }),
|
|
47
|
+
type: attributeTypes[name],
|
|
65
48
|
}));
|
|
66
|
-
const
|
|
67
|
-
|
|
49
|
+
const promise = this.pool.decode(buffer, requests, { transfer: buffer instanceof ArrayBuffer }).then((result) => {
|
|
50
|
+
if (!result.success) throw new Error(`THREE.DRACOLoader: Decoding failed: ${result.error}`);
|
|
51
|
+
return this._createGeometry({
|
|
52
|
+
index: result.index ? { array: result.index, itemSize: 1 } : null,
|
|
53
|
+
attributes: result.attributes.map(({ name, array, itemSize }) => ({
|
|
54
|
+
name,
|
|
55
|
+
itemSize,
|
|
56
|
+
...padded(array, itemSize),
|
|
57
|
+
...(name === 'color' ? { vertexColorSpace } : {}),
|
|
58
|
+
})),
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
if (buffer instanceof ArrayBuffer) this.#tasks.set(buffer, { key, promise });
|
|
62
|
+
return promise;
|
|
63
|
+
}
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (name === 'color') {
|
|
74
|
-
if (vertexColorSpace === SRGB) toWorkingColorSpace(attribute);
|
|
75
|
-
attribute.normalized = !(array instanceof Float32Array);
|
|
76
|
-
}
|
|
77
|
-
geometry.setAttribute(name, attribute);
|
|
78
|
-
}
|
|
79
|
-
return geometry;
|
|
65
|
+
setWorkerLimit(workerLimit) {
|
|
66
|
+
super.setWorkerLimit(workerLimit);
|
|
67
|
+
this.pool.setWorkerLimit(workerLimit);
|
|
68
|
+
return this;
|
|
80
69
|
}
|
|
81
70
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (cached && buffer.byteLength === 0) {
|
|
87
|
-
return Promise.reject(new Error('this buffer was already decoded with other settings and handed to a worker'));
|
|
88
|
-
}
|
|
89
|
-
const promise = decode(buffer, attributeIDs, attributeTypes, vertexColorSpace);
|
|
90
|
-
if (buffer instanceof ArrayBuffer) tasks.set(buffer, { key, promise });
|
|
91
|
-
return promise;
|
|
71
|
+
preload() {
|
|
72
|
+
// A failure shows again on the first decode, which is where it is handled.
|
|
73
|
+
this.pool.preload().catch(() => {});
|
|
74
|
+
return this;
|
|
92
75
|
}
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return decodeGeometry(buffer, { attributeIDs, attributeTypes, vertexColorSpace }).then(callback).catch(onError);
|
|
100
|
-
},
|
|
101
|
-
/** A `.drc` file's contents, as DRACOLoader's `parse`. */
|
|
102
|
-
parse(buffer, onLoad, onError = () => {}) {
|
|
103
|
-
loader.decodeDracoFile(buffer, onLoad, null, null, SRGB, onError);
|
|
104
|
-
},
|
|
105
|
-
parseAsync(buffer) {
|
|
106
|
-
return decodeGeometry(buffer, { vertexColorSpace: SRGB });
|
|
107
|
-
},
|
|
108
|
-
preload() {
|
|
109
|
-
// A failure shows again on the first decode, which is where it is handled.
|
|
110
|
-
pool.preload().catch(() => {});
|
|
111
|
-
return loader;
|
|
112
|
-
},
|
|
113
|
-
setWorkerLimit(workers) {
|
|
114
|
-
pool.setWorkerLimit(workers);
|
|
115
|
-
return loader;
|
|
116
|
-
},
|
|
117
|
-
dispose() {
|
|
118
|
-
pool.dispose();
|
|
119
|
-
return loader;
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
return loader;
|
|
77
|
+
dispose() {
|
|
78
|
+
super.dispose();
|
|
79
|
+
this.pool.dispose();
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
123
82
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@draco-rust/decoder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Draco geometry decoder in WebAssembly, a safe Rust port of Google Draco: meshes, point clouds and older bitstreams, each as its own small entry.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"draco",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"directory": "web/npm/decoder"
|
|
25
25
|
},
|
|
26
26
|
"draco-rust": {
|
|
27
|
-
"commit": "
|
|
27
|
+
"commit": "8650e1ca",
|
|
28
28
|
"draco-core": "2.3.2",
|
|
29
29
|
"draco-io": "0.6.0",
|
|
30
30
|
"draco-gltf": "0.7.0"
|
|
@@ -72,6 +72,14 @@
|
|
|
72
72
|
"**/*.d.ts",
|
|
73
73
|
"**/*.wasm"
|
|
74
74
|
],
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"three": ">=0.150.0"
|
|
77
|
+
},
|
|
78
|
+
"peerDependenciesMeta": {
|
|
79
|
+
"three": {
|
|
80
|
+
"optional": true
|
|
81
|
+
}
|
|
82
|
+
},
|
|
75
83
|
"publishConfig": {
|
|
76
84
|
"access": "public"
|
|
77
85
|
}
|
package/point-cloud/three.d.ts
CHANGED
|
@@ -1,65 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LoadingManager } from 'three';
|
|
2
|
+
import { DRACOLoader as ThreeDRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
2
3
|
import type { DecoderPool, DecoderPoolOptions } from './pool.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
* The three.js classes the loader builds with, passed in so the package uses
|
|
6
|
-
* the application's own copy of three: `import * as THREE from "three"` will
|
|
7
|
-
* do. With `InterleavedBuffer` and `InterleavedBufferAttribute`, an attribute
|
|
8
|
-
* whose items are not a multiple of four bytes is padded to one, as DRACOLoader
|
|
9
|
-
* pads it since r181; with `Color` and `ColorManagement`, a `.drc` file's sRGB
|
|
10
|
-
* vertex colours are made linear as DRACOLoader makes them.
|
|
11
|
-
*/
|
|
12
|
-
export interface ThreeClasses<Geometry> {
|
|
13
|
-
BufferGeometry: new () => Geometry;
|
|
14
|
-
BufferAttribute: new (array: any, itemSize: number, normalized?: boolean) => any;
|
|
15
|
-
InterleavedBuffer?: new (array: any, stride: number) => any;
|
|
16
|
-
InterleavedBufferAttribute?: new (buffer: any, itemSize: number, offset: number) => any;
|
|
17
|
-
Color?: new () => any;
|
|
18
|
-
/** `colorSpaceToWorking` since three r177, `toWorkingColorSpace` before. */
|
|
19
|
-
ColorManagement?:
|
|
20
|
-
| { colorSpaceToWorking(color: any, colorSpace: string): any }
|
|
21
|
-
| { toWorkingColorSpace(color: any, colorSpace: string): any };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface DracoLoaderOptions extends DecoderPoolOptions {
|
|
5
|
+
export interface DRACOLoaderOptions extends DecoderPoolOptions {
|
|
25
6
|
/** A pool to share with other loaders, instead of one of the loader's own. */
|
|
26
7
|
pool?: DecoderPool;
|
|
27
8
|
}
|
|
28
9
|
|
|
29
|
-
export interface DecodeGeometryOptions {
|
|
30
|
-
/** three.js attribute name to Draco unique id, as glTF names them. Without
|
|
31
|
-
* it, `position`, `normal`, `color` and `uv` are read by type. */
|
|
32
|
-
attributeIDs?: Record<string, number>;
|
|
33
|
-
/** three.js attribute name to the typed array it comes back in. */
|
|
34
|
-
attributeTypes?: Record<string, DracoArrayType>;
|
|
35
|
-
/** `"srgb"` makes vertex colours linear; the default leaves them as stored. */
|
|
36
|
-
vertexColorSpace?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The part of three.js's DRACOLoader that GLTFLoader and `.drc` users call. */
|
|
40
|
-
export interface DracoLoader<Geometry> {
|
|
41
|
-
readonly pool: DecoderPool;
|
|
42
|
-
decodeGeometry(buffer: ArrayBuffer | ArrayBufferView, options?: DecodeGeometryOptions): Promise<Geometry>;
|
|
43
|
-
decodeDracoFile(
|
|
44
|
-
buffer: ArrayBuffer,
|
|
45
|
-
callback: (geometry: Geometry) => void,
|
|
46
|
-
attributeIDs?: Record<string, number> | null,
|
|
47
|
-
attributeTypes?: Record<string, DracoArrayType> | null,
|
|
48
|
-
vertexColorSpace?: string,
|
|
49
|
-
onError?: (error: unknown) => void,
|
|
50
|
-
): Promise<void>;
|
|
51
|
-
parse(buffer: ArrayBuffer, onLoad: (geometry: Geometry) => void, onError?: (error: unknown) => void): void;
|
|
52
|
-
parseAsync(buffer: ArrayBuffer | ArrayBufferView): Promise<Geometry>;
|
|
53
|
-
preload(): this;
|
|
54
|
-
setWorkerLimit(workers: number): this;
|
|
55
|
-
dispose(): this;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
10
|
/**
|
|
59
|
-
*
|
|
60
|
-
* `
|
|
11
|
+
* three.js's `DRACOLoader`, decoding on this entry's worker pool instead of
|
|
12
|
+
* the Draco decoder three fetches from `setDecoderPath`. It is three's class,
|
|
13
|
+
* so it goes wherever one is expected:
|
|
14
|
+
* `gltfLoader.setDRACOLoader(new DRACOLoader())`.
|
|
61
15
|
*/
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
16
|
+
export class DRACOLoader extends ThreeDRACOLoader {
|
|
17
|
+
constructor(manager?: LoadingManager, options?: DRACOLoaderOptions);
|
|
18
|
+
readonly pool: DecoderPool;
|
|
19
|
+
}
|
package/point-cloud/three.js
CHANGED
|
@@ -1,123 +1,82 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// three.js's DRACOLoader, decoding on this entry's worker pool. Everything a
|
|
2
|
+
// caller sees -- load, parse, decodeDracoFile, GLTFLoader's use of it, the
|
|
3
|
+
// geometry it builds -- is three's own class; only the decode is replaced, so
|
|
4
|
+
// the result follows whichever three the application has.
|
|
5
|
+
import { REVISION } from 'three';
|
|
6
|
+
import { DRACOLoader as ThreeDRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
4
7
|
import { createDecoderPool } from './pool.js';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
// From r181 DRACOLoader pads an attribute whose items are not a multiple of
|
|
10
|
+
// four bytes and builds it interleaved, from the stride its worker reports.
|
|
11
|
+
const PADS = Number.parseInt(REVISION, 10) >= 181;
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export function createDracoLoader(three, options = {}) {
|
|
18
|
-
const { BufferGeometry, BufferAttribute, InterleavedBuffer, InterleavedBufferAttribute, Color, ColorManagement } =
|
|
19
|
-
three;
|
|
20
|
-
if (typeof BufferGeometry !== 'function' || typeof BufferAttribute !== 'function') {
|
|
21
|
-
throw new TypeError("createDracoLoader needs three.js's BufferGeometry and BufferAttribute");
|
|
13
|
+
function padded(array, itemSize) {
|
|
14
|
+
const bytes = array.BYTES_PER_ELEMENT;
|
|
15
|
+
const stride = (Math.ceil((itemSize * bytes) / 4) * 4) / bytes;
|
|
16
|
+
if (!PADS || stride === itemSize) return { array, stride: itemSize };
|
|
17
|
+
const count = array.length / itemSize;
|
|
18
|
+
const out = new array.constructor(count * stride);
|
|
19
|
+
for (let i = 0; i < count; i += 1) {
|
|
20
|
+
out.set(array.subarray(i * itemSize, (i + 1) * itemSize), i * stride);
|
|
22
21
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// decode a second time; GLTFLoader asks again for a primitive it shares, and
|
|
26
|
-
// gets the first answer, as DRACOLoader gives it.
|
|
27
|
-
const tasks = new WeakMap();
|
|
22
|
+
return { array: out, stride };
|
|
23
|
+
}
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
function toWorkingColorSpace(attribute) {
|
|
32
|
-
const convert = ColorManagement?.colorSpaceToWorking ?? ColorManagement?.toWorkingColorSpace;
|
|
33
|
-
if (!Color || !convert) return;
|
|
34
|
-
const color = new Color();
|
|
35
|
-
for (let i = 0; i < attribute.count; i += 1) {
|
|
36
|
-
color.fromBufferAttribute(attribute, i);
|
|
37
|
-
convert.call(ColorManagement, color, SRGB);
|
|
38
|
-
attribute.setXYZ(i, color.r, color.g, color.b);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
25
|
+
export class DRACOLoader extends ThreeDRACOLoader {
|
|
26
|
+
#tasks = new WeakMap();
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function attributeOf(array, itemSize) {
|
|
46
|
-
const bytes = array.BYTES_PER_ELEMENT;
|
|
47
|
-
const stride = Math.ceil((itemSize * bytes) / 4) * (4 / bytes);
|
|
48
|
-
if (stride === itemSize || !InterleavedBuffer || !InterleavedBufferAttribute) {
|
|
49
|
-
return new BufferAttribute(array, itemSize);
|
|
50
|
-
}
|
|
51
|
-
const count = array.length / itemSize;
|
|
52
|
-
const padded = new array.constructor(count * stride);
|
|
53
|
-
for (let i = 0; i < count; i += 1) {
|
|
54
|
-
padded.set(array.subarray(i * itemSize, (i + 1) * itemSize), i * stride);
|
|
55
|
-
}
|
|
56
|
-
return new InterleavedBufferAttribute(new InterleavedBuffer(padded, stride), itemSize, 0);
|
|
28
|
+
constructor(manager, options = {}) {
|
|
29
|
+
super(manager);
|
|
30
|
+
this.pool = options.pool ?? createDecoderPool(options);
|
|
57
31
|
}
|
|
58
32
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
33
|
+
// The buffer goes to a worker by transfer, which leaves nothing here to
|
|
34
|
+
// decode again; a second request for it gets the first answer, as three's
|
|
35
|
+
// own task cache gives it.
|
|
36
|
+
decodeGeometry(buffer, taskConfig) {
|
|
37
|
+
const key = JSON.stringify(taskConfig);
|
|
38
|
+
const cached = this.#tasks.get(buffer);
|
|
39
|
+
if (cached?.key === key) return cached.promise;
|
|
40
|
+
if (cached && buffer.byteLength === 0) {
|
|
41
|
+
return Promise.reject(new Error('THREE.DRACOLoader: Unable to re-decode a buffer with different settings. Buffer has already been transferred.'));
|
|
42
|
+
}
|
|
43
|
+
const { attributeIDs, attributeTypes, useUniqueIDs, vertexColorSpace } = taskConfig;
|
|
44
|
+
const requests = Object.keys(attributeIDs).map((name) => ({
|
|
62
45
|
name,
|
|
63
|
-
...(
|
|
64
|
-
type: attributeTypes
|
|
46
|
+
...(useUniqueIDs ? { id: attributeIDs[name] } : { semantic: attributeIDs[name] }),
|
|
47
|
+
type: attributeTypes[name],
|
|
65
48
|
}));
|
|
66
|
-
const
|
|
67
|
-
|
|
49
|
+
const promise = this.pool.decode(buffer, requests, { transfer: buffer instanceof ArrayBuffer }).then((result) => {
|
|
50
|
+
if (!result.success) throw new Error(`THREE.DRACOLoader: Decoding failed: ${result.error}`);
|
|
51
|
+
return this._createGeometry({
|
|
52
|
+
index: result.index ? { array: result.index, itemSize: 1 } : null,
|
|
53
|
+
attributes: result.attributes.map(({ name, array, itemSize }) => ({
|
|
54
|
+
name,
|
|
55
|
+
itemSize,
|
|
56
|
+
...padded(array, itemSize),
|
|
57
|
+
...(name === 'color' ? { vertexColorSpace } : {}),
|
|
58
|
+
})),
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
if (buffer instanceof ArrayBuffer) this.#tasks.set(buffer, { key, promise });
|
|
62
|
+
return promise;
|
|
63
|
+
}
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (name === 'color') {
|
|
74
|
-
if (vertexColorSpace === SRGB) toWorkingColorSpace(attribute);
|
|
75
|
-
attribute.normalized = !(array instanceof Float32Array);
|
|
76
|
-
}
|
|
77
|
-
geometry.setAttribute(name, attribute);
|
|
78
|
-
}
|
|
79
|
-
return geometry;
|
|
65
|
+
setWorkerLimit(workerLimit) {
|
|
66
|
+
super.setWorkerLimit(workerLimit);
|
|
67
|
+
this.pool.setWorkerLimit(workerLimit);
|
|
68
|
+
return this;
|
|
80
69
|
}
|
|
81
70
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (cached && buffer.byteLength === 0) {
|
|
87
|
-
return Promise.reject(new Error('this buffer was already decoded with other settings and handed to a worker'));
|
|
88
|
-
}
|
|
89
|
-
const promise = decode(buffer, attributeIDs, attributeTypes, vertexColorSpace);
|
|
90
|
-
if (buffer instanceof ArrayBuffer) tasks.set(buffer, { key, promise });
|
|
91
|
-
return promise;
|
|
71
|
+
preload() {
|
|
72
|
+
// A failure shows again on the first decode, which is where it is handled.
|
|
73
|
+
this.pool.preload().catch(() => {});
|
|
74
|
+
return this;
|
|
92
75
|
}
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return decodeGeometry(buffer, { attributeIDs, attributeTypes, vertexColorSpace }).then(callback).catch(onError);
|
|
100
|
-
},
|
|
101
|
-
/** A `.drc` file's contents, as DRACOLoader's `parse`. */
|
|
102
|
-
parse(buffer, onLoad, onError = () => {}) {
|
|
103
|
-
loader.decodeDracoFile(buffer, onLoad, null, null, SRGB, onError);
|
|
104
|
-
},
|
|
105
|
-
parseAsync(buffer) {
|
|
106
|
-
return decodeGeometry(buffer, { vertexColorSpace: SRGB });
|
|
107
|
-
},
|
|
108
|
-
preload() {
|
|
109
|
-
// A failure shows again on the first decode, which is where it is handled.
|
|
110
|
-
pool.preload().catch(() => {});
|
|
111
|
-
return loader;
|
|
112
|
-
},
|
|
113
|
-
setWorkerLimit(workers) {
|
|
114
|
-
pool.setWorkerLimit(workers);
|
|
115
|
-
return loader;
|
|
116
|
-
},
|
|
117
|
-
dispose() {
|
|
118
|
-
pool.dispose();
|
|
119
|
-
return loader;
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
return loader;
|
|
77
|
+
dispose() {
|
|
78
|
+
super.dispose();
|
|
79
|
+
this.pool.dispose();
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
123
82
|
}
|
package/three.d.ts
CHANGED
|
@@ -1,65 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LoadingManager } from 'three';
|
|
2
|
+
import { DRACOLoader as ThreeDRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
2
3
|
import type { DecoderPool, DecoderPoolOptions } from './pool.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
* The three.js classes the loader builds with, passed in so the package uses
|
|
6
|
-
* the application's own copy of three: `import * as THREE from "three"` will
|
|
7
|
-
* do. With `InterleavedBuffer` and `InterleavedBufferAttribute`, an attribute
|
|
8
|
-
* whose items are not a multiple of four bytes is padded to one, as DRACOLoader
|
|
9
|
-
* pads it since r181; with `Color` and `ColorManagement`, a `.drc` file's sRGB
|
|
10
|
-
* vertex colours are made linear as DRACOLoader makes them.
|
|
11
|
-
*/
|
|
12
|
-
export interface ThreeClasses<Geometry> {
|
|
13
|
-
BufferGeometry: new () => Geometry;
|
|
14
|
-
BufferAttribute: new (array: any, itemSize: number, normalized?: boolean) => any;
|
|
15
|
-
InterleavedBuffer?: new (array: any, stride: number) => any;
|
|
16
|
-
InterleavedBufferAttribute?: new (buffer: any, itemSize: number, offset: number) => any;
|
|
17
|
-
Color?: new () => any;
|
|
18
|
-
/** `colorSpaceToWorking` since three r177, `toWorkingColorSpace` before. */
|
|
19
|
-
ColorManagement?:
|
|
20
|
-
| { colorSpaceToWorking(color: any, colorSpace: string): any }
|
|
21
|
-
| { toWorkingColorSpace(color: any, colorSpace: string): any };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface DracoLoaderOptions extends DecoderPoolOptions {
|
|
5
|
+
export interface DRACOLoaderOptions extends DecoderPoolOptions {
|
|
25
6
|
/** A pool to share with other loaders, instead of one of the loader's own. */
|
|
26
7
|
pool?: DecoderPool;
|
|
27
8
|
}
|
|
28
9
|
|
|
29
|
-
export interface DecodeGeometryOptions {
|
|
30
|
-
/** three.js attribute name to Draco unique id, as glTF names them. Without
|
|
31
|
-
* it, `position`, `normal`, `color` and `uv` are read by type. */
|
|
32
|
-
attributeIDs?: Record<string, number>;
|
|
33
|
-
/** three.js attribute name to the typed array it comes back in. */
|
|
34
|
-
attributeTypes?: Record<string, DracoArrayType>;
|
|
35
|
-
/** `"srgb"` makes vertex colours linear; the default leaves them as stored. */
|
|
36
|
-
vertexColorSpace?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The part of three.js's DRACOLoader that GLTFLoader and `.drc` users call. */
|
|
40
|
-
export interface DracoLoader<Geometry> {
|
|
41
|
-
readonly pool: DecoderPool;
|
|
42
|
-
decodeGeometry(buffer: ArrayBuffer | ArrayBufferView, options?: DecodeGeometryOptions): Promise<Geometry>;
|
|
43
|
-
decodeDracoFile(
|
|
44
|
-
buffer: ArrayBuffer,
|
|
45
|
-
callback: (geometry: Geometry) => void,
|
|
46
|
-
attributeIDs?: Record<string, number> | null,
|
|
47
|
-
attributeTypes?: Record<string, DracoArrayType> | null,
|
|
48
|
-
vertexColorSpace?: string,
|
|
49
|
-
onError?: (error: unknown) => void,
|
|
50
|
-
): Promise<void>;
|
|
51
|
-
parse(buffer: ArrayBuffer, onLoad: (geometry: Geometry) => void, onError?: (error: unknown) => void): void;
|
|
52
|
-
parseAsync(buffer: ArrayBuffer | ArrayBufferView): Promise<Geometry>;
|
|
53
|
-
preload(): this;
|
|
54
|
-
setWorkerLimit(workers: number): this;
|
|
55
|
-
dispose(): this;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
10
|
/**
|
|
59
|
-
*
|
|
60
|
-
* `
|
|
11
|
+
* three.js's `DRACOLoader`, decoding on this entry's worker pool instead of
|
|
12
|
+
* the Draco decoder three fetches from `setDecoderPath`. It is three's class,
|
|
13
|
+
* so it goes wherever one is expected:
|
|
14
|
+
* `gltfLoader.setDRACOLoader(new DRACOLoader())`.
|
|
61
15
|
*/
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
16
|
+
export class DRACOLoader extends ThreeDRACOLoader {
|
|
17
|
+
constructor(manager?: LoadingManager, options?: DRACOLoaderOptions);
|
|
18
|
+
readonly pool: DecoderPool;
|
|
19
|
+
}
|
package/three.js
CHANGED
|
@@ -1,123 +1,82 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// three.js's DRACOLoader, decoding on this entry's worker pool. Everything a
|
|
2
|
+
// caller sees -- load, parse, decodeDracoFile, GLTFLoader's use of it, the
|
|
3
|
+
// geometry it builds -- is three's own class; only the decode is replaced, so
|
|
4
|
+
// the result follows whichever three the application has.
|
|
5
|
+
import { REVISION } from 'three';
|
|
6
|
+
import { DRACOLoader as ThreeDRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
4
7
|
import { createDecoderPool } from './pool.js';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
// From r181 DRACOLoader pads an attribute whose items are not a multiple of
|
|
10
|
+
// four bytes and builds it interleaved, from the stride its worker reports.
|
|
11
|
+
const PADS = Number.parseInt(REVISION, 10) >= 181;
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export function createDracoLoader(three, options = {}) {
|
|
18
|
-
const { BufferGeometry, BufferAttribute, InterleavedBuffer, InterleavedBufferAttribute, Color, ColorManagement } =
|
|
19
|
-
three;
|
|
20
|
-
if (typeof BufferGeometry !== 'function' || typeof BufferAttribute !== 'function') {
|
|
21
|
-
throw new TypeError("createDracoLoader needs three.js's BufferGeometry and BufferAttribute");
|
|
13
|
+
function padded(array, itemSize) {
|
|
14
|
+
const bytes = array.BYTES_PER_ELEMENT;
|
|
15
|
+
const stride = (Math.ceil((itemSize * bytes) / 4) * 4) / bytes;
|
|
16
|
+
if (!PADS || stride === itemSize) return { array, stride: itemSize };
|
|
17
|
+
const count = array.length / itemSize;
|
|
18
|
+
const out = new array.constructor(count * stride);
|
|
19
|
+
for (let i = 0; i < count; i += 1) {
|
|
20
|
+
out.set(array.subarray(i * itemSize, (i + 1) * itemSize), i * stride);
|
|
22
21
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// decode a second time; GLTFLoader asks again for a primitive it shares, and
|
|
26
|
-
// gets the first answer, as DRACOLoader gives it.
|
|
27
|
-
const tasks = new WeakMap();
|
|
22
|
+
return { array: out, stride };
|
|
23
|
+
}
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
function toWorkingColorSpace(attribute) {
|
|
32
|
-
const convert = ColorManagement?.colorSpaceToWorking ?? ColorManagement?.toWorkingColorSpace;
|
|
33
|
-
if (!Color || !convert) return;
|
|
34
|
-
const color = new Color();
|
|
35
|
-
for (let i = 0; i < attribute.count; i += 1) {
|
|
36
|
-
color.fromBufferAttribute(attribute, i);
|
|
37
|
-
convert.call(ColorManagement, color, SRGB);
|
|
38
|
-
attribute.setXYZ(i, color.r, color.g, color.b);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
25
|
+
export class DRACOLoader extends ThreeDRACOLoader {
|
|
26
|
+
#tasks = new WeakMap();
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function attributeOf(array, itemSize) {
|
|
46
|
-
const bytes = array.BYTES_PER_ELEMENT;
|
|
47
|
-
const stride = Math.ceil((itemSize * bytes) / 4) * (4 / bytes);
|
|
48
|
-
if (stride === itemSize || !InterleavedBuffer || !InterleavedBufferAttribute) {
|
|
49
|
-
return new BufferAttribute(array, itemSize);
|
|
50
|
-
}
|
|
51
|
-
const count = array.length / itemSize;
|
|
52
|
-
const padded = new array.constructor(count * stride);
|
|
53
|
-
for (let i = 0; i < count; i += 1) {
|
|
54
|
-
padded.set(array.subarray(i * itemSize, (i + 1) * itemSize), i * stride);
|
|
55
|
-
}
|
|
56
|
-
return new InterleavedBufferAttribute(new InterleavedBuffer(padded, stride), itemSize, 0);
|
|
28
|
+
constructor(manager, options = {}) {
|
|
29
|
+
super(manager);
|
|
30
|
+
this.pool = options.pool ?? createDecoderPool(options);
|
|
57
31
|
}
|
|
58
32
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
33
|
+
// The buffer goes to a worker by transfer, which leaves nothing here to
|
|
34
|
+
// decode again; a second request for it gets the first answer, as three's
|
|
35
|
+
// own task cache gives it.
|
|
36
|
+
decodeGeometry(buffer, taskConfig) {
|
|
37
|
+
const key = JSON.stringify(taskConfig);
|
|
38
|
+
const cached = this.#tasks.get(buffer);
|
|
39
|
+
if (cached?.key === key) return cached.promise;
|
|
40
|
+
if (cached && buffer.byteLength === 0) {
|
|
41
|
+
return Promise.reject(new Error('THREE.DRACOLoader: Unable to re-decode a buffer with different settings. Buffer has already been transferred.'));
|
|
42
|
+
}
|
|
43
|
+
const { attributeIDs, attributeTypes, useUniqueIDs, vertexColorSpace } = taskConfig;
|
|
44
|
+
const requests = Object.keys(attributeIDs).map((name) => ({
|
|
62
45
|
name,
|
|
63
|
-
...(
|
|
64
|
-
type: attributeTypes
|
|
46
|
+
...(useUniqueIDs ? { id: attributeIDs[name] } : { semantic: attributeIDs[name] }),
|
|
47
|
+
type: attributeTypes[name],
|
|
65
48
|
}));
|
|
66
|
-
const
|
|
67
|
-
|
|
49
|
+
const promise = this.pool.decode(buffer, requests, { transfer: buffer instanceof ArrayBuffer }).then((result) => {
|
|
50
|
+
if (!result.success) throw new Error(`THREE.DRACOLoader: Decoding failed: ${result.error}`);
|
|
51
|
+
return this._createGeometry({
|
|
52
|
+
index: result.index ? { array: result.index, itemSize: 1 } : null,
|
|
53
|
+
attributes: result.attributes.map(({ name, array, itemSize }) => ({
|
|
54
|
+
name,
|
|
55
|
+
itemSize,
|
|
56
|
+
...padded(array, itemSize),
|
|
57
|
+
...(name === 'color' ? { vertexColorSpace } : {}),
|
|
58
|
+
})),
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
if (buffer instanceof ArrayBuffer) this.#tasks.set(buffer, { key, promise });
|
|
62
|
+
return promise;
|
|
63
|
+
}
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (name === 'color') {
|
|
74
|
-
if (vertexColorSpace === SRGB) toWorkingColorSpace(attribute);
|
|
75
|
-
attribute.normalized = !(array instanceof Float32Array);
|
|
76
|
-
}
|
|
77
|
-
geometry.setAttribute(name, attribute);
|
|
78
|
-
}
|
|
79
|
-
return geometry;
|
|
65
|
+
setWorkerLimit(workerLimit) {
|
|
66
|
+
super.setWorkerLimit(workerLimit);
|
|
67
|
+
this.pool.setWorkerLimit(workerLimit);
|
|
68
|
+
return this;
|
|
80
69
|
}
|
|
81
70
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (cached && buffer.byteLength === 0) {
|
|
87
|
-
return Promise.reject(new Error('this buffer was already decoded with other settings and handed to a worker'));
|
|
88
|
-
}
|
|
89
|
-
const promise = decode(buffer, attributeIDs, attributeTypes, vertexColorSpace);
|
|
90
|
-
if (buffer instanceof ArrayBuffer) tasks.set(buffer, { key, promise });
|
|
91
|
-
return promise;
|
|
71
|
+
preload() {
|
|
72
|
+
// A failure shows again on the first decode, which is where it is handled.
|
|
73
|
+
this.pool.preload().catch(() => {});
|
|
74
|
+
return this;
|
|
92
75
|
}
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return decodeGeometry(buffer, { attributeIDs, attributeTypes, vertexColorSpace }).then(callback).catch(onError);
|
|
100
|
-
},
|
|
101
|
-
/** A `.drc` file's contents, as DRACOLoader's `parse`. */
|
|
102
|
-
parse(buffer, onLoad, onError = () => {}) {
|
|
103
|
-
loader.decodeDracoFile(buffer, onLoad, null, null, SRGB, onError);
|
|
104
|
-
},
|
|
105
|
-
parseAsync(buffer) {
|
|
106
|
-
return decodeGeometry(buffer, { vertexColorSpace: SRGB });
|
|
107
|
-
},
|
|
108
|
-
preload() {
|
|
109
|
-
// A failure shows again on the first decode, which is where it is handled.
|
|
110
|
-
pool.preload().catch(() => {});
|
|
111
|
-
return loader;
|
|
112
|
-
},
|
|
113
|
-
setWorkerLimit(workers) {
|
|
114
|
-
pool.setWorkerLimit(workers);
|
|
115
|
-
return loader;
|
|
116
|
-
},
|
|
117
|
-
dispose() {
|
|
118
|
-
pool.dispose();
|
|
119
|
-
return loader;
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
return loader;
|
|
77
|
+
dispose() {
|
|
78
|
+
super.dispose();
|
|
79
|
+
this.pool.dispose();
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
123
82
|
}
|