@driftengine/drft 3.63.0 → 4.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/NOTICE +20 -0
- package/README.md +1 -1
- package/dist/drftFormat.d.ts +52 -1
- package/dist/drftFormat.js +59 -1
- package/dist/drftMeshlets.d.ts +52 -0
- package/dist/drftMeshlets.js +140 -0
- package/dist/drftRead.d.ts +47 -0
- package/dist/drftRead.js +37 -3
- package/dist/drftWrite.d.ts +48 -0
- package/dist/drftWrite.js +59 -3
- package/dist/dtex.d.ts +127 -0
- package/dist/dtex.js +324 -0
- package/dist/ents.d.ts +47 -0
- package/dist/ents.js +60 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +22 -0
- package/dist/navm.d.ts +58 -0
- package/dist/navm.js +105 -0
- package/dist/ngrf.d.ts +29 -0
- package/dist/ngrf.js +214 -0
- package/dist/nnet.d.ts +76 -0
- package/dist/nnet.js +194 -0
- package/dist/sdfv.d.ts +71 -0
- package/dist/sdfv.js +142 -0
- package/package.json +4 -2
- package/src/drftFormat.ts +66 -1
- package/src/drftMeshlets.ts +185 -0
- package/src/drftRead.ts +82 -2
- package/src/drftWrite.ts +112 -2
- package/src/dtex.ts +404 -0
- package/src/ents.ts +105 -0
- package/src/index.ts +43 -0
- package/src/navm.ts +171 -0
- package/src/ngrf.ts +299 -0
- package/src/nnet.ts +250 -0
- package/src/sdfv.ts +178 -0
package/NOTICE
CHANGED
|
@@ -7,3 +7,23 @@ This product includes software developed at Drift Technologies
|
|
|
7
7
|
Licensed under the Apache License, Version 2.0. Section 4(d) of that licence
|
|
8
8
|
requires this NOTICE to be reproduced in any derivative work you distribute,
|
|
9
9
|
in the source, the documentation, or a display generated by the work.
|
|
10
|
+
|
|
11
|
+
Models run by DriftCapture
|
|
12
|
+
--------------------------
|
|
13
|
+
DriftCapture runs the models below. Their weights are not in this repository
|
|
14
|
+
and are not distributed with it: tools/capture-weights/ fetches each at the
|
|
15
|
+
revision its manifest pins, verifies it, and converts it, and a game that
|
|
16
|
+
ships one ships it under that model's own licence. Each model's definition in
|
|
17
|
+
this engine is written from its project's published code.
|
|
18
|
+
|
|
19
|
+
- Depth Anything 3 Small and Depth Anything 3 Base. Apache License 2.0.
|
|
20
|
+
Copyright (c) 2025 ByteDance Ltd. and/or its affiliates.
|
|
21
|
+
- Depth Anything V2 Small. Apache License 2.0. The University of Hong Kong
|
|
22
|
+
and TikTok; converted to the Transformers layout by The HuggingFace Team.
|
|
23
|
+
- MobileSAM. Apache License 2.0. Its image encoder is TinyViT, Copyright (c)
|
|
24
|
+
2022 Microsoft; its prompt encoder and mask decoder are Segment Anything's,
|
|
25
|
+
Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
26
|
+
- SAM 2.1 tiny. Apache License 2.0. Copyright (c) Meta Platforms, Inc. and
|
|
27
|
+
affiliates.
|
|
28
|
+
- OWLv2 base patch16. Apache License 2.0. Copyright 2023 Google AI and The
|
|
29
|
+
HuggingFace Team.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The `.drft` container: read and write a baked scene. Declares no runtime dependency.
|
|
4
4
|
|
|
5
|
-
**Cost:
|
|
5
|
+
**Cost: 8.1 KB gzipped.** Measured by `scripts/size-gate.test.mjs`, which fails if it drifts more than
|
|
6
6
|
3% — the number is a fact about the build rather than a claim in a document.
|
|
7
7
|
|
|
8
8
|
Part of [DriftEngine](../../README.md). See [`docs/ARCHITECTURE.md`](../../docs/ARCHITECTURE.md)
|
package/dist/drftFormat.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare const DRFT_VERSION_MAJOR = 1;
|
|
|
20
20
|
* Additive revisions within a generation: new optional chunks, new attribute bits, new
|
|
21
21
|
* enum values with a defined fallback. Never a changed meaning.
|
|
22
22
|
*/
|
|
23
|
-
export declare const DRFT_VERSION_MINOR =
|
|
23
|
+
export declare const DRFT_VERSION_MINOR = 17;
|
|
24
24
|
/** Bytes before the chunk table. */
|
|
25
25
|
export declare const HEADER_BYTES = 32;
|
|
26
26
|
/** Bytes per chunk table entry. */
|
|
@@ -169,6 +169,57 @@ export declare const CHUNK_MORP: number;
|
|
|
169
169
|
* fire. See `drftSubs.ts` for the layout.
|
|
170
170
|
*/
|
|
171
171
|
export declare const CHUNK_SUBS: number;
|
|
172
|
+
/**
|
|
173
|
+
* `MSHL`: a mesh's clusters and the level-of-detail graph over them.
|
|
174
|
+
*
|
|
175
|
+
* Additive, and additive is what keeps the freeze in `FORMAT.md` intact: a reader that does not
|
|
176
|
+
* know this code skips it by its length and loses only the GPU-driven path, which is the one thing
|
|
177
|
+
* it could not have drawn anyway.
|
|
178
|
+
*/
|
|
179
|
+
export declare const CHUNK_MSHL: number;
|
|
180
|
+
/**
|
|
181
|
+
* `DTEX`: a material as a decode program over a latent, rather than as pictures.
|
|
182
|
+
*
|
|
183
|
+
* Additive on the same terms as `MSHL`: a reader that does not know this code skips it by its
|
|
184
|
+
* length and loses only the material, which it had no way to decode. See `dtex.ts` for the layout
|
|
185
|
+
* and for why the chunk validates containment rather than meaning.
|
|
186
|
+
*/
|
|
187
|
+
export declare const CHUNK_DTEX: number;
|
|
188
|
+
/**
|
|
189
|
+
* `SDFV`: an object's signed distance field, for tracing indirect light through it.
|
|
190
|
+
*
|
|
191
|
+
* Additive on the same terms as `MSHL` and `DTEX`: a reader that does not know this code skips it
|
|
192
|
+
* by its length and loses only indirect light, which it had no way to trace anyway. See `sdfv.ts`
|
|
193
|
+
* for the layout, and for why it carries no version of its own.
|
|
194
|
+
*/
|
|
195
|
+
export declare const CHUNK_SDFV: number;
|
|
196
|
+
/**
|
|
197
|
+
* `NNET`: the weights of the small networks the engine evaluates, found by role.
|
|
198
|
+
*
|
|
199
|
+
* Additive on the same terms as `SDFV`: a reader that does not know this code skips it by its
|
|
200
|
+
* length and loses only the networks, which it had nothing to evaluate them with. See `nnet.ts`.
|
|
201
|
+
*/
|
|
202
|
+
export declare const CHUNK_NNET: number;
|
|
203
|
+
/**
|
|
204
|
+
* `NGRF`: a network as a graph of operators and its tensors, found by role — the form a transformer
|
|
205
|
+
* takes, which `NNET`'s perceptron table cannot. Additive on the same terms. See `ngrf.ts`.
|
|
206
|
+
*/
|
|
207
|
+
export declare const CHUNK_NGRF: number;
|
|
208
|
+
/**
|
|
209
|
+
* `NAVM`: a polygon mesh a character can walk, with the placement it was built at.
|
|
210
|
+
*
|
|
211
|
+
* Additive on the same terms as `DTEX`: a reader that does not know this code skips it by its
|
|
212
|
+
* length and loses only the navigation, which it had nothing to walk with. See `navm.ts` for the
|
|
213
|
+
* layout, and for why the placement travels inside the chunk rather than beside it.
|
|
214
|
+
*/
|
|
215
|
+
export declare const CHUNK_NAVM: number;
|
|
216
|
+
/**
|
|
217
|
+
* `ENTS`: the things in a scene, as the entity model wrote them.
|
|
218
|
+
*
|
|
219
|
+
* Additive on the same terms. See `ents.ts` for the layout, and for why a chunk in a binary
|
|
220
|
+
* container holds text.
|
|
221
|
+
*/
|
|
222
|
+
export declare const CHUNK_ENTS: number;
|
|
172
223
|
/** Bytes before the records in a `SPLT` payload. See `DrftSplatBlock` for the fields. */
|
|
173
224
|
export declare const SPLAT_BLOCK_PREFIX = 40;
|
|
174
225
|
/**
|
package/dist/drftFormat.js
CHANGED
|
@@ -20,7 +20,7 @@ export const DRFT_VERSION_MAJOR = 1;
|
|
|
20
20
|
* Additive revisions within a generation: new optional chunks, new attribute bits, new
|
|
21
21
|
* enum values with a defined fallback. Never a changed meaning.
|
|
22
22
|
*/
|
|
23
|
-
export const DRFT_VERSION_MINOR =
|
|
23
|
+
export const DRFT_VERSION_MINOR = 17;
|
|
24
24
|
/** Bytes before the chunk table. */
|
|
25
25
|
export const HEADER_BYTES = 32;
|
|
26
26
|
/** Bytes per chunk table entry. */
|
|
@@ -180,6 +180,57 @@ export const CHUNK_MORP = fourCC('MORP');
|
|
|
180
180
|
* fire. See `drftSubs.ts` for the layout.
|
|
181
181
|
*/
|
|
182
182
|
export const CHUNK_SUBS = fourCC('SUBS');
|
|
183
|
+
/**
|
|
184
|
+
* `MSHL`: a mesh's clusters and the level-of-detail graph over them.
|
|
185
|
+
*
|
|
186
|
+
* Additive, and additive is what keeps the freeze in `FORMAT.md` intact: a reader that does not
|
|
187
|
+
* know this code skips it by its length and loses only the GPU-driven path, which is the one thing
|
|
188
|
+
* it could not have drawn anyway.
|
|
189
|
+
*/
|
|
190
|
+
export const CHUNK_MSHL = fourCC('MSHL');
|
|
191
|
+
/**
|
|
192
|
+
* `DTEX`: a material as a decode program over a latent, rather than as pictures.
|
|
193
|
+
*
|
|
194
|
+
* Additive on the same terms as `MSHL`: a reader that does not know this code skips it by its
|
|
195
|
+
* length and loses only the material, which it had no way to decode. See `dtex.ts` for the layout
|
|
196
|
+
* and for why the chunk validates containment rather than meaning.
|
|
197
|
+
*/
|
|
198
|
+
export const CHUNK_DTEX = fourCC('DTEX');
|
|
199
|
+
/**
|
|
200
|
+
* `SDFV`: an object's signed distance field, for tracing indirect light through it.
|
|
201
|
+
*
|
|
202
|
+
* Additive on the same terms as `MSHL` and `DTEX`: a reader that does not know this code skips it
|
|
203
|
+
* by its length and loses only indirect light, which it had no way to trace anyway. See `sdfv.ts`
|
|
204
|
+
* for the layout, and for why it carries no version of its own.
|
|
205
|
+
*/
|
|
206
|
+
export const CHUNK_SDFV = fourCC('SDFV');
|
|
207
|
+
/**
|
|
208
|
+
* `NNET`: the weights of the small networks the engine evaluates, found by role.
|
|
209
|
+
*
|
|
210
|
+
* Additive on the same terms as `SDFV`: a reader that does not know this code skips it by its
|
|
211
|
+
* length and loses only the networks, which it had nothing to evaluate them with. See `nnet.ts`.
|
|
212
|
+
*/
|
|
213
|
+
export const CHUNK_NNET = fourCC('NNET');
|
|
214
|
+
/**
|
|
215
|
+
* `NGRF`: a network as a graph of operators and its tensors, found by role — the form a transformer
|
|
216
|
+
* takes, which `NNET`'s perceptron table cannot. Additive on the same terms. See `ngrf.ts`.
|
|
217
|
+
*/
|
|
218
|
+
export const CHUNK_NGRF = fourCC('NGRF');
|
|
219
|
+
/**
|
|
220
|
+
* `NAVM`: a polygon mesh a character can walk, with the placement it was built at.
|
|
221
|
+
*
|
|
222
|
+
* Additive on the same terms as `DTEX`: a reader that does not know this code skips it by its
|
|
223
|
+
* length and loses only the navigation, which it had nothing to walk with. See `navm.ts` for the
|
|
224
|
+
* layout, and for why the placement travels inside the chunk rather than beside it.
|
|
225
|
+
*/
|
|
226
|
+
export const CHUNK_NAVM = fourCC('NAVM');
|
|
227
|
+
/**
|
|
228
|
+
* `ENTS`: the things in a scene, as the entity model wrote them.
|
|
229
|
+
*
|
|
230
|
+
* Additive on the same terms. See `ents.ts` for the layout, and for why a chunk in a binary
|
|
231
|
+
* container holds text.
|
|
232
|
+
*/
|
|
233
|
+
export const CHUNK_ENTS = fourCC('ENTS');
|
|
183
234
|
/** Bytes before the records in a `SPLT` payload. See `DrftSplatBlock` for the fields. */
|
|
184
235
|
export const SPLAT_BLOCK_PREFIX = 40;
|
|
185
236
|
/**
|
|
@@ -204,6 +255,13 @@ export const KNOWN_CHUNKS = new Set([
|
|
|
204
255
|
CHUNK_ANIM,
|
|
205
256
|
CHUNK_MORP,
|
|
206
257
|
CHUNK_SUBS,
|
|
258
|
+
CHUNK_MSHL,
|
|
259
|
+
CHUNK_DTEX,
|
|
260
|
+
CHUNK_SDFV,
|
|
261
|
+
CHUNK_NNET,
|
|
262
|
+
CHUNK_NGRF,
|
|
263
|
+
CHUNK_NAVM,
|
|
264
|
+
CHUNK_ENTS,
|
|
207
265
|
]);
|
|
208
266
|
/** Chunk flags. */
|
|
209
267
|
export const CHUNK_REQUIRED = 1 << 0;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MSHL`: the clusters a mesh draws as, and the level-of-detail graph over them.
|
|
3
|
+
*
|
|
4
|
+
* **One chunk per level, not one for the whole graph.** A level is a complete alternative mesh —
|
|
5
|
+
* its own positions, its own indices, its own clusters — so folding every level into one chunk
|
|
6
|
+
* would mean one payload whose parts are only meaningful together. Separate chunks let a loader
|
|
7
|
+
* stream the coarsest level first and refine, which is the same argument `coarseFirst.ts` already
|
|
8
|
+
* makes for splats.
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* u32 clusterCount
|
|
12
|
+
* u32 indexTotal
|
|
13
|
+
* u32 triangleOffsets[clusterCount] first triangle of each cluster
|
|
14
|
+
* u32 triangleCounts[clusterCount]
|
|
15
|
+
* f32 boundsCentre[clusterCount * 3]
|
|
16
|
+
* f32 boundsRadius[clusterCount]
|
|
17
|
+
* f32 coneAxis[clusterCount * 3] average face normal, normalised
|
|
18
|
+
* f32 coneCutoff[clusterCount] minimum dot of any face against the axis
|
|
19
|
+
* f32 ownError[clusterCount] this cluster's geometric error
|
|
20
|
+
* f32 parentError[clusterCount] the error of what replaces it one level up
|
|
21
|
+
* u32 indices[indexTotal]
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* **No per-chunk version field**, matching `COLL` and every other chunk here: the file's own
|
|
25
|
+
* version covers the format and a second one would be a second thing to keep in step.
|
|
26
|
+
*
|
|
27
|
+
* **`parentError` may be infinite**, which is how the coarsest level says it has no parent. A
|
|
28
|
+
* float32 carries that exactly, so a reader needs no sentinel and a comparison against it needs
|
|
29
|
+
* no special case.
|
|
30
|
+
*/
|
|
31
|
+
/** How many clusters one level may carry. Well past what a streaming budget permits. */
|
|
32
|
+
export declare const MAX_CLUSTERS: number;
|
|
33
|
+
export interface MeshletLevel {
|
|
34
|
+
triangleOffsets: Uint32Array;
|
|
35
|
+
triangleCounts: Uint32Array;
|
|
36
|
+
boundsCentre: Float32Array;
|
|
37
|
+
boundsRadius: Float32Array;
|
|
38
|
+
coneAxis: Float32Array;
|
|
39
|
+
coneCutoff: Float32Array;
|
|
40
|
+
ownError: Float32Array;
|
|
41
|
+
parentError: Float32Array;
|
|
42
|
+
indices: Uint32Array;
|
|
43
|
+
count: number;
|
|
44
|
+
}
|
|
45
|
+
export declare function buildMeshlets(level: MeshletLevel): Uint8Array;
|
|
46
|
+
/**
|
|
47
|
+
* The level an `MSHL` chunk carries, as views over the fetched buffer.
|
|
48
|
+
*
|
|
49
|
+
* Views rather than copies, like every other chunk here, so a load allocates nothing beyond the
|
|
50
|
+
* object holding them.
|
|
51
|
+
*/
|
|
52
|
+
export declare function readMeshlets(buffer: ArrayBuffer, offset: number, byteLength: number): MeshletLevel;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { DrftError, align } from './drftFormat.js';
|
|
2
|
+
/**
|
|
3
|
+
* `MSHL`: the clusters a mesh draws as, and the level-of-detail graph over them.
|
|
4
|
+
*
|
|
5
|
+
* **One chunk per level, not one for the whole graph.** A level is a complete alternative mesh —
|
|
6
|
+
* its own positions, its own indices, its own clusters — so folding every level into one chunk
|
|
7
|
+
* would mean one payload whose parts are only meaningful together. Separate chunks let a loader
|
|
8
|
+
* stream the coarsest level first and refine, which is the same argument `coarseFirst.ts` already
|
|
9
|
+
* makes for splats.
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* u32 clusterCount
|
|
13
|
+
* u32 indexTotal
|
|
14
|
+
* u32 triangleOffsets[clusterCount] first triangle of each cluster
|
|
15
|
+
* u32 triangleCounts[clusterCount]
|
|
16
|
+
* f32 boundsCentre[clusterCount * 3]
|
|
17
|
+
* f32 boundsRadius[clusterCount]
|
|
18
|
+
* f32 coneAxis[clusterCount * 3] average face normal, normalised
|
|
19
|
+
* f32 coneCutoff[clusterCount] minimum dot of any face against the axis
|
|
20
|
+
* f32 ownError[clusterCount] this cluster's geometric error
|
|
21
|
+
* f32 parentError[clusterCount] the error of what replaces it one level up
|
|
22
|
+
* u32 indices[indexTotal]
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* **No per-chunk version field**, matching `COLL` and every other chunk here: the file's own
|
|
26
|
+
* version covers the format and a second one would be a second thing to keep in step.
|
|
27
|
+
*
|
|
28
|
+
* **`parentError` may be infinite**, which is how the coarsest level says it has no parent. A
|
|
29
|
+
* float32 carries that exactly, so a reader needs no sentinel and a comparison against it needs
|
|
30
|
+
* no special case.
|
|
31
|
+
*/
|
|
32
|
+
/** How many clusters one level may carry. Well past what a streaming budget permits. */
|
|
33
|
+
export const MAX_CLUSTERS = 1 << 20;
|
|
34
|
+
const HEADER = 8;
|
|
35
|
+
function layoutOf(clusterCount, indexTotal) {
|
|
36
|
+
const triangleOffsets = HEADER;
|
|
37
|
+
const triangleCounts = triangleOffsets + clusterCount * 4;
|
|
38
|
+
const boundsCentre = triangleCounts + clusterCount * 4;
|
|
39
|
+
const boundsRadius = boundsCentre + clusterCount * 12;
|
|
40
|
+
const coneAxis = boundsRadius + clusterCount * 4;
|
|
41
|
+
const coneCutoff = coneAxis + clusterCount * 12;
|
|
42
|
+
const ownError = coneCutoff + clusterCount * 4;
|
|
43
|
+
const parentError = ownError + clusterCount * 4;
|
|
44
|
+
const indices = parentError + clusterCount * 4;
|
|
45
|
+
return {
|
|
46
|
+
triangleOffsets,
|
|
47
|
+
triangleCounts,
|
|
48
|
+
boundsCentre,
|
|
49
|
+
boundsRadius,
|
|
50
|
+
coneAxis,
|
|
51
|
+
coneCutoff,
|
|
52
|
+
ownError,
|
|
53
|
+
parentError,
|
|
54
|
+
indices,
|
|
55
|
+
size: indices + indexTotal * 4,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function buildMeshlets(level) {
|
|
59
|
+
const { count } = level;
|
|
60
|
+
if (count > MAX_CLUSTERS) {
|
|
61
|
+
throw new DrftError(`${count} clusters exceeds the ${MAX_CLUSTERS} cap`);
|
|
62
|
+
}
|
|
63
|
+
if (level.triangleOffsets.length < count || level.triangleCounts.length < count) {
|
|
64
|
+
throw new DrftError('MSHL was given fewer cluster offsets or counts than clusters');
|
|
65
|
+
}
|
|
66
|
+
if (level.indices.length % 3 !== 0) {
|
|
67
|
+
throw new DrftError(`MSHL was given ${level.indices.length} indices, which is not whole triangles`);
|
|
68
|
+
}
|
|
69
|
+
/*
|
|
70
|
+
* Every cluster's triangles are checked to lie inside the index array before anything is
|
|
71
|
+
* written. A cluster running past the end would otherwise be a subarray of arbitrary geometry on
|
|
72
|
+
* the way back in, which is the same failure `COLL` checks its starts table for.
|
|
73
|
+
*/
|
|
74
|
+
const triangles = level.indices.length / 3;
|
|
75
|
+
for (let c = 0; c < count; c += 1) {
|
|
76
|
+
const at = level.triangleOffsets[c];
|
|
77
|
+
const n = level.triangleCounts[c];
|
|
78
|
+
if (at + n > triangles) {
|
|
79
|
+
throw new DrftError(`MSHL cluster ${c} runs past the ${triangles} triangles it was given`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const layout = layoutOf(count, level.indices.length);
|
|
83
|
+
const bytes = new Uint8Array(align(layout.size));
|
|
84
|
+
const view = new DataView(bytes.buffer);
|
|
85
|
+
view.setUint32(0, count, true);
|
|
86
|
+
view.setUint32(4, level.indices.length, true);
|
|
87
|
+
new Uint32Array(bytes.buffer, layout.triangleOffsets, count).set(level.triangleOffsets.subarray(0, count));
|
|
88
|
+
new Uint32Array(bytes.buffer, layout.triangleCounts, count).set(level.triangleCounts.subarray(0, count));
|
|
89
|
+
new Float32Array(bytes.buffer, layout.boundsCentre, count * 3).set(level.boundsCentre.subarray(0, count * 3));
|
|
90
|
+
new Float32Array(bytes.buffer, layout.boundsRadius, count).set(level.boundsRadius.subarray(0, count));
|
|
91
|
+
new Float32Array(bytes.buffer, layout.coneAxis, count * 3).set(level.coneAxis.subarray(0, count * 3));
|
|
92
|
+
new Float32Array(bytes.buffer, layout.coneCutoff, count).set(level.coneCutoff.subarray(0, count));
|
|
93
|
+
new Float32Array(bytes.buffer, layout.ownError, count).set(level.ownError.subarray(0, count));
|
|
94
|
+
new Float32Array(bytes.buffer, layout.parentError, count).set(level.parentError.subarray(0, count));
|
|
95
|
+
new Uint32Array(bytes.buffer, layout.indices, level.indices.length).set(level.indices);
|
|
96
|
+
return bytes;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The level an `MSHL` chunk carries, as views over the fetched buffer.
|
|
100
|
+
*
|
|
101
|
+
* Views rather than copies, like every other chunk here, so a load allocates nothing beyond the
|
|
102
|
+
* object holding them.
|
|
103
|
+
*/
|
|
104
|
+
export function readMeshlets(buffer, offset, byteLength) {
|
|
105
|
+
if (byteLength < HEADER)
|
|
106
|
+
throw new DrftError('MSHL is too short to hold its counts');
|
|
107
|
+
const view = new DataView(buffer, offset, byteLength);
|
|
108
|
+
const count = view.getUint32(0, true);
|
|
109
|
+
const indexTotal = view.getUint32(4, true);
|
|
110
|
+
if (count > MAX_CLUSTERS) {
|
|
111
|
+
throw new DrftError(`MSHL claims ${count} clusters, over the ${MAX_CLUSTERS} cap`);
|
|
112
|
+
}
|
|
113
|
+
if (indexTotal % 3 !== 0) {
|
|
114
|
+
throw new DrftError(`MSHL claims ${indexTotal} indices, which is not whole triangles`);
|
|
115
|
+
}
|
|
116
|
+
const layout = layoutOf(count, indexTotal);
|
|
117
|
+
if (byteLength < layout.size) {
|
|
118
|
+
throw new DrftError(`MSHL claims ${count} clusters and ${indexTotal} indices, which runs past the chunk`);
|
|
119
|
+
}
|
|
120
|
+
const triangleOffsets = new Uint32Array(buffer, offset + layout.triangleOffsets, count);
|
|
121
|
+
const triangleCounts = new Uint32Array(buffer, offset + layout.triangleCounts, count);
|
|
122
|
+
const triangles = indexTotal / 3;
|
|
123
|
+
for (let c = 0; c < count; c += 1) {
|
|
124
|
+
if (triangleOffsets[c] + triangleCounts[c] > triangles) {
|
|
125
|
+
throw new DrftError(`MSHL cluster ${c} runs past the ${triangles} triangles the chunk holds`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
triangleOffsets,
|
|
130
|
+
triangleCounts,
|
|
131
|
+
boundsCentre: new Float32Array(buffer, offset + layout.boundsCentre, count * 3),
|
|
132
|
+
boundsRadius: new Float32Array(buffer, offset + layout.boundsRadius, count),
|
|
133
|
+
coneAxis: new Float32Array(buffer, offset + layout.coneAxis, count * 3),
|
|
134
|
+
coneCutoff: new Float32Array(buffer, offset + layout.coneCutoff, count),
|
|
135
|
+
ownError: new Float32Array(buffer, offset + layout.ownError, count),
|
|
136
|
+
parentError: new Float32Array(buffer, offset + layout.parentError, count),
|
|
137
|
+
indices: new Uint32Array(buffer, offset + layout.indices, indexTotal),
|
|
138
|
+
count,
|
|
139
|
+
};
|
|
140
|
+
}
|
package/dist/drftRead.d.ts
CHANGED
|
@@ -18,6 +18,12 @@ import type { AnimationClip, DrftSkin } from './animationData.ts';
|
|
|
18
18
|
import type { DrftNode } from './drftSkin.ts';
|
|
19
19
|
import type { MeshData } from './meshData.ts';
|
|
20
20
|
import type { DrftChunk, DrftHead, DrftMaterial, DrftSplatBlock } from './drftFormat.ts';
|
|
21
|
+
import { type DrftSdfvEntry } from './sdfv.ts';
|
|
22
|
+
import { type DtexEntry } from './dtex.ts';
|
|
23
|
+
import { type EntsScene } from './ents.ts';
|
|
24
|
+
import { type NavPolyMesh } from './navm.ts';
|
|
25
|
+
import { type DrftNetwork } from './nnet.ts';
|
|
26
|
+
import { type DrftGraph } from './ngrf.ts';
|
|
21
27
|
/** An embedded image, still compressed. Decoding is the consumer's, through `createImageBitmap`. */
|
|
22
28
|
export interface DrftTexture {
|
|
23
29
|
/** What the source called this image. Empty for a file written before names existed. */
|
|
@@ -65,6 +71,24 @@ export interface DrftAsset {
|
|
|
65
71
|
* file's coarse-first order, so a consumer that draws a prefix of them draws a sparse whole.
|
|
66
72
|
*/
|
|
67
73
|
readonly splats: DrftSplatBlock | null;
|
|
74
|
+
/**
|
|
75
|
+
* A decode program per material, or empty for a file that carries none.
|
|
76
|
+
*
|
|
77
|
+
* Each says which `MATL` entry it belongs to. **A reader that does not know this chunk skips it
|
|
78
|
+
* by its length** and loses only the material it could not have decoded, which is what keeps the
|
|
79
|
+
* format's freeze intact.
|
|
80
|
+
*/
|
|
81
|
+
readonly dtex: readonly DtexEntry[];
|
|
82
|
+
/**
|
|
83
|
+
* A way across the scene, or `null` for a file that carries none.
|
|
84
|
+
*
|
|
85
|
+
* **`null` rather than an empty mesh**, because *nobody built one* and *there is nowhere to walk*
|
|
86
|
+
* are different facts, and a consumer that cannot tell them apart cannot decide whether to build
|
|
87
|
+
* one itself.
|
|
88
|
+
*/
|
|
89
|
+
readonly navigation: NavPolyMesh | null;
|
|
90
|
+
/** The things in the scene, or `null` for a file that is an asset rather than a scene. */
|
|
91
|
+
readonly entities: EntsScene | null;
|
|
68
92
|
/**
|
|
69
93
|
* The asset's own hierarchy, or empty for a file carrying no `NODE`.
|
|
70
94
|
*
|
|
@@ -89,6 +113,29 @@ export interface DrftAsset {
|
|
|
89
113
|
readonly material: number;
|
|
90
114
|
readonly substance: string;
|
|
91
115
|
}[];
|
|
116
|
+
/**
|
|
117
|
+
* The signed distance field of each mesh that carries one, or empty for a file with none.
|
|
118
|
+
*
|
|
119
|
+
* The `SDFV` chunk, added at 1.13. Views over the fetched buffer, so a file that carries fields
|
|
120
|
+
* for geometry a consumer never traces through costs one view apiece and no copy. Hand them to
|
|
121
|
+
* `composeGlobalField`, which is what turns per-object fields into a world around the camera.
|
|
122
|
+
*/
|
|
123
|
+
readonly fields: readonly DrftSdfvEntry[];
|
|
124
|
+
/**
|
|
125
|
+
* The networks the file carries, each with the role a consumer asks for it by, or empty.
|
|
126
|
+
*
|
|
127
|
+
* The `NNET` chunk, added at 1.14. Weights are views over the fetched buffer, in the precision
|
|
128
|
+
* they were written in — half-precision bits stay bits, because the device uploads them as such.
|
|
129
|
+
*/
|
|
130
|
+
readonly networks: readonly DrftNetwork[];
|
|
131
|
+
/**
|
|
132
|
+
* The graphs the file carries — networks built from operators — each with its role, or empty.
|
|
133
|
+
*
|
|
134
|
+
* The `NGRF` chunk, added at 1.15. Tensors are views over the fetched buffer where its alignment
|
|
135
|
+
* allows, in the precision they were written in. `@driftengine/texture` validates one before it
|
|
136
|
+
* runs, naming any operator it lacks.
|
|
137
|
+
*/
|
|
138
|
+
readonly graphs: readonly DrftGraph[];
|
|
92
139
|
/**
|
|
93
140
|
* The convex hulls this asset collides as, or empty for a file carrying no `COLL`.
|
|
94
141
|
*
|
package/dist/drftRead.js
CHANGED
|
@@ -16,8 +16,14 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { readClip, readMorph, readNodes, readSkin } from './drftSkin.js';
|
|
18
18
|
import { validateMeshData } from './meshData.js';
|
|
19
|
-
import { ATTR_EMISSIVE_COLOR, ATTR_GRAIN, ATTR_JOINTS, ATTR_RELIEF, ATTR_TANGENT, ATTR_ROUGHNESS, ATTR_SPECULAR, ATTR_UVS, ATTR_WEIGHTS, CHUNK_ENTRY_BYTES, CHUNK_HEAD, CHUNK_LODM, CHUNK_LODF, CHUNK_SPLT, SPLAT_BLOCK_PREFIX, CHUNK_MESH, CHUNK_REQUIRED, DRFT_MAGIC, DRFT_VERSION_MAJOR, DrftError, HEADER_BYTES, CHUNK_MATL, CHUNK_TEXS, KNOWN_CHUNKS, MATERIAL_ENTRY_BYTES, align, fourCCName, CHUNK_ANIM, CHUNK_NODE, CHUNK_SKIN, CHUNK_MORP, CHUNK_SUBS, CHUNK_COLL, } from './drftFormat.js';
|
|
19
|
+
import { ATTR_EMISSIVE_COLOR, ATTR_GRAIN, ATTR_JOINTS, ATTR_RELIEF, ATTR_TANGENT, ATTR_ROUGHNESS, ATTR_SPECULAR, ATTR_UVS, ATTR_WEIGHTS, CHUNK_ENTRY_BYTES, CHUNK_HEAD, CHUNK_LODM, CHUNK_LODF, CHUNK_DTEX, CHUNK_ENTS, CHUNK_NAVM, CHUNK_SPLT, SPLAT_BLOCK_PREFIX, CHUNK_MESH, CHUNK_REQUIRED, DRFT_MAGIC, DRFT_VERSION_MAJOR, DrftError, HEADER_BYTES, CHUNK_MATL, CHUNK_TEXS, KNOWN_CHUNKS, MATERIAL_ENTRY_BYTES, align, fourCCName, CHUNK_ANIM, CHUNK_NODE, CHUNK_SKIN, CHUNK_MORP, CHUNK_NNET, CHUNK_NGRF, CHUNK_SDFV, CHUNK_SUBS, CHUNK_COLL, } from './drftFormat.js';
|
|
20
20
|
import { readSubs } from './drftSubs.js';
|
|
21
|
+
import { readSdfv } from './sdfv.js';
|
|
22
|
+
import { readDtex } from './dtex.js';
|
|
23
|
+
import { readEnts } from './ents.js';
|
|
24
|
+
import { readNavm } from './navm.js';
|
|
25
|
+
import { readNnet } from './nnet.js';
|
|
26
|
+
import { readNgrf } from './ngrf.js';
|
|
21
27
|
import { readColliders } from './drftColliders.js';
|
|
22
28
|
const decoder = new TextDecoder();
|
|
23
29
|
function readString(view, buffer, at, limit) {
|
|
@@ -153,11 +159,17 @@ export function readDrft(buffer) {
|
|
|
153
159
|
const textures = [];
|
|
154
160
|
let materials = [];
|
|
155
161
|
const splatBlocks = [];
|
|
162
|
+
const dtex = [];
|
|
163
|
+
let navigation = null;
|
|
164
|
+
let entities = null;
|
|
156
165
|
let nodes = [];
|
|
157
166
|
const skins = [];
|
|
158
167
|
const clips = [];
|
|
159
168
|
const morphs = [];
|
|
160
169
|
let subs = null;
|
|
170
|
+
let fields = null;
|
|
171
|
+
let nnet = null;
|
|
172
|
+
let graphs = [];
|
|
161
173
|
let colliders = [];
|
|
162
174
|
const skipped = [];
|
|
163
175
|
let head = null;
|
|
@@ -207,6 +219,12 @@ export function readDrft(buffer) {
|
|
|
207
219
|
materials = readMaterials(buffer, chunk);
|
|
208
220
|
else if (chunk.code === CHUNK_SPLT)
|
|
209
221
|
splatBlocks.push({ block: readSplatBlock(buffer, chunk), at: chunk.index });
|
|
222
|
+
else if (chunk.code === CHUNK_DTEX)
|
|
223
|
+
dtex.push(readDtex(buffer, chunk.offset, chunk.byteLength));
|
|
224
|
+
else if (chunk.code === CHUNK_NAVM)
|
|
225
|
+
navigation = readNavm(buffer, chunk.offset, chunk.byteLength);
|
|
226
|
+
else if (chunk.code === CHUNK_ENTS)
|
|
227
|
+
entities = readEnts(buffer, chunk.offset, chunk.byteLength);
|
|
210
228
|
else if (chunk.code === CHUNK_NODE)
|
|
211
229
|
nodes = readNodes(buffer, chunk.offset, chunk.byteLength);
|
|
212
230
|
else if (chunk.code === CHUNK_SKIN)
|
|
@@ -217,6 +235,12 @@ export function readDrft(buffer) {
|
|
|
217
235
|
morphs.push(readMorph(buffer, chunk.offset, chunk.byteLength));
|
|
218
236
|
else if (chunk.code === CHUNK_SUBS)
|
|
219
237
|
subs = readSubs(buffer, chunk.offset, chunk.byteLength);
|
|
238
|
+
else if (chunk.code === CHUNK_SDFV)
|
|
239
|
+
fields = readSdfv(buffer, chunk.offset, chunk.byteLength);
|
|
240
|
+
else if (chunk.code === CHUNK_NNET)
|
|
241
|
+
nnet = readNnet(buffer, chunk.offset, chunk.byteLength);
|
|
242
|
+
else if (chunk.code === CHUNK_NGRF)
|
|
243
|
+
graphs = readNgrf(buffer, chunk.offset, chunk.byteLength);
|
|
220
244
|
else if (chunk.code === CHUNK_COLL)
|
|
221
245
|
colliders = readColliders(buffer, chunk.offset, chunk.byteLength);
|
|
222
246
|
/* Every other known chunk is defined but not yet carried; see docs/FORMAT.md phase table. */
|
|
@@ -229,8 +253,12 @@ export function readDrft(buffer) {
|
|
|
229
253
|
* for the absence of a chunk kind it never needed would be the format telling a consumer what
|
|
230
254
|
* its scene may be made of.
|
|
231
255
|
*/
|
|
232
|
-
|
|
233
|
-
|
|
256
|
+
/* And networks, since 1.15: a converted model is weights and nothing else. */
|
|
257
|
+
if (meshes.length === 0 &&
|
|
258
|
+
splatBlocks.length === 0 &&
|
|
259
|
+
(nnet?.networks.length ?? 0) === 0 &&
|
|
260
|
+
graphs.length === 0) {
|
|
261
|
+
throw new DrftError('no MESH, SPLT, NNET or NGRF chunk — an asset with no geometry, capture or network');
|
|
234
262
|
}
|
|
235
263
|
/*
|
|
236
264
|
* Checked here rather than trusted, because the pairing is by ordinal and a mismatch is
|
|
@@ -292,7 +320,13 @@ export function readDrft(buffer) {
|
|
|
292
320
|
materials,
|
|
293
321
|
textures,
|
|
294
322
|
substances: subs?.entries ?? [],
|
|
323
|
+
fields: fields?.entries ?? [],
|
|
324
|
+
networks: nnet?.networks ?? [],
|
|
325
|
+
graphs,
|
|
295
326
|
colliders,
|
|
327
|
+
dtex,
|
|
328
|
+
navigation,
|
|
329
|
+
entities,
|
|
296
330
|
splats: joinSplatBlocks(splatBlocks),
|
|
297
331
|
skipped,
|
|
298
332
|
versionMajor,
|
package/dist/drftWrite.d.ts
CHANGED
|
@@ -13,6 +13,12 @@ import type { DrftNode } from './drftSkin.ts';
|
|
|
13
13
|
import type { MeshData } from './meshData.ts';
|
|
14
14
|
import type { DrftHead, DrftMaterial, DrftSplats } from './drftFormat.ts';
|
|
15
15
|
import { type DrftSubstanceEntry } from './drftSubs.ts';
|
|
16
|
+
import { type DrftSdfvEntry } from './sdfv.ts';
|
|
17
|
+
import { type DrftNetwork } from './nnet.ts';
|
|
18
|
+
import { type DtexEntry } from './dtex.ts';
|
|
19
|
+
import { type EntsScene } from './ents.ts';
|
|
20
|
+
import { type NavPolyMesh } from './navm.ts';
|
|
21
|
+
import { type DrftGraph } from './ngrf.ts';
|
|
16
22
|
/** An image to embed, already compressed, with what its own header said about it. */
|
|
17
23
|
export interface DrftTextureSource {
|
|
18
24
|
/**
|
|
@@ -48,6 +54,25 @@ export interface DrftSource {
|
|
|
48
54
|
* than by position, so a file labelling only its fourth material says so.
|
|
49
55
|
*/
|
|
50
56
|
readonly substances?: readonly DrftSubstanceEntry[];
|
|
57
|
+
/**
|
|
58
|
+
* The signed distance field of each mesh that has one, or absent for a file carrying none.
|
|
59
|
+
*
|
|
60
|
+
* Written as one `SDFV` chunk at 1.13. It pairs by the mesh ordinal carried per entry rather
|
|
61
|
+
* than by position, so a file with a field for only its fourth mesh says so — `SUBS`'s rule,
|
|
62
|
+
* and `MORP` is the chunk that found out why it is a rule.
|
|
63
|
+
*/
|
|
64
|
+
readonly fields?: readonly DrftSdfvEntry[];
|
|
65
|
+
/**
|
|
66
|
+
* The networks this file carries, or absent for a file carrying none.
|
|
67
|
+
*
|
|
68
|
+
* Written as one `NNET` chunk at 1.14, each network found by its role rather than by position.
|
|
69
|
+
*/
|
|
70
|
+
readonly networks?: readonly DrftNetwork[];
|
|
71
|
+
/**
|
|
72
|
+
* The graphs this file carries — networks built from operators rather than dense layers — or
|
|
73
|
+
* absent for a file carrying none. Written as one `NGRF` chunk at 1.15, each found by its role.
|
|
74
|
+
*/
|
|
75
|
+
readonly graphs?: readonly DrftGraph[];
|
|
51
76
|
/**
|
|
52
77
|
* The convex hulls this asset collides as, or absent for a file that carries none.
|
|
53
78
|
*
|
|
@@ -93,6 +118,29 @@ export interface DrftSource {
|
|
|
93
118
|
* `CHUNK_SPLT` for why it is several chunks rather than one.
|
|
94
119
|
*/
|
|
95
120
|
readonly splats?: DrftSplats;
|
|
121
|
+
/**
|
|
122
|
+
* A decode program per material, or absent for a file whose materials are ordinary.
|
|
123
|
+
*
|
|
124
|
+
* One `DTEX` chunk each, **paired to a `MATL` entry by the index inside the chunk** rather than
|
|
125
|
+
* by the order they appear. A file may carry one for some of its materials and not others — a
|
|
126
|
+
* scene where one surface came from a capture and the rest were authored — and a reader pairing
|
|
127
|
+
* by position would hand the wrong material the wrong texture with nothing to fail on.
|
|
128
|
+
*/
|
|
129
|
+
readonly dtex?: readonly DtexEntry[];
|
|
130
|
+
/**
|
|
131
|
+
* A way across the scene, or absent for a file nobody navigates.
|
|
132
|
+
*
|
|
133
|
+
* The mesh carries the origin and cell size it was built at, because the numbers in it are cell
|
|
134
|
+
* indices and a reader without those has a mesh in the wrong units at the wrong place.
|
|
135
|
+
*/
|
|
136
|
+
readonly navigation?: NavPolyMesh;
|
|
137
|
+
/**
|
|
138
|
+
* The things in the scene, as `serializeWorld` wrote them.
|
|
139
|
+
*
|
|
140
|
+
* A *scene*, not an asset: this is what a capture proposes or an editor saves, and a file may
|
|
141
|
+
* carry geometry with no scene at all — which is every baked model this format has ever held.
|
|
142
|
+
*/
|
|
143
|
+
readonly entities?: EntsScene;
|
|
96
144
|
}
|
|
97
145
|
/**
|
|
98
146
|
* Bake a `.drft` into one `ArrayBuffer`.
|