@driftengine/drft 3.61.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/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +9 -0
- package/dist/animationData.d.ts +53 -0
- package/dist/animationData.js +13 -0
- package/dist/coarseFirst.d.ts +27 -0
- package/dist/coarseFirst.js +118 -0
- package/dist/drftColliders.d.ts +53 -0
- package/dist/drftColliders.js +137 -0
- package/dist/drftFormat.d.ts +454 -0
- package/dist/drftFormat.js +350 -0
- package/dist/drftRead.d.ts +121 -0
- package/dist/drftRead.js +487 -0
- package/dist/drftSkin.d.ts +40 -0
- package/dist/drftSkin.js +270 -0
- package/dist/drftStream.d.ts +170 -0
- package/dist/drftStream.js +315 -0
- package/dist/drftSubs.d.ts +18 -0
- package/dist/drftSubs.js +70 -0
- package/dist/drftWrite.d.ts +103 -0
- package/dist/drftWrite.js +478 -0
- package/dist/fixtures/v1-0.d.ts +9 -0
- package/dist/fixtures/v1-0.js +9 -0
- package/dist/fixtures/v1-1.d.ts +16 -0
- package/dist/fixtures/v1-1.js +16 -0
- package/dist/fixtures/v1-11.d.ts +14 -0
- package/dist/fixtures/v1-11.js +14 -0
- package/dist/fixtures/v1-2.d.ts +19 -0
- package/dist/fixtures/v1-2.js +19 -0
- package/dist/fixtures/v1-3.d.ts +18 -0
- package/dist/fixtures/v1-3.js +18 -0
- package/dist/fixtures/v1-4.d.ts +13 -0
- package/dist/fixtures/v1-4.js +13 -0
- package/dist/fixtures/v1-5.d.ts +14 -0
- package/dist/fixtures/v1-5.js +14 -0
- package/dist/fixtures/v1-6.d.ts +9 -0
- package/dist/fixtures/v1-6.js +9 -0
- package/dist/fixtures/v1-7.d.ts +9 -0
- package/dist/fixtures/v1-7.js +9 -0
- package/dist/fixtures/v1-9.d.ts +9 -0
- package/dist/fixtures/v1-9.js +9 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +29 -0
- package/dist/meshData.d.ts +210 -0
- package/dist/meshData.js +105 -0
- package/package.json +57 -0
- package/src/animationData.ts +58 -0
- package/src/coarseFirst.ts +113 -0
- package/src/drftColliders.ts +153 -0
- package/src/drftFormat.ts +537 -0
- package/src/drftRead.ts +652 -0
- package/src/drftSkin.ts +326 -0
- package/src/drftStream.ts +436 -0
- package/src/drftSubs.ts +86 -0
- package/src/drftWrite.ts +613 -0
- package/src/fixtures/v1-0.ts +10 -0
- package/src/fixtures/v1-1.ts +17 -0
- package/src/fixtures/v1-11.ts +15 -0
- package/src/fixtures/v1-2.ts +20 -0
- package/src/fixtures/v1-3.ts +19 -0
- package/src/fixtures/v1-4.ts +14 -0
- package/src/fixtures/v1-5.ts +15 -0
- package/src/fixtures/v1-6.ts +10 -0
- package/src/fixtures/v1-7.ts +10 -0
- package/src/fixtures/v1-9.ts +10 -0
- package/src/index.ts +56 -0
- package/src/meshData.ts +301 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `.drft` container: constants, layout and the compatibility rules.
|
|
3
|
+
*
|
|
4
|
+
* Shared by the reader and the writer so the two cannot disagree about a byte. Everything
|
|
5
|
+
* here is a fact about the format rather than about either side of it — if a number lives
|
|
6
|
+
* in this file, changing it changes the format.
|
|
7
|
+
*
|
|
8
|
+
* See docs/FORMAT.md for the specification this implements and the reasoning behind it.
|
|
9
|
+
*/
|
|
10
|
+
/** `DRFT`, little-endian, as a u32. The first four bytes of every file. */
|
|
11
|
+
export const DRFT_MAGIC = 0x54465244;
|
|
12
|
+
/**
|
|
13
|
+
* The generation of the layout. Bumped only when a byte changes meaning.
|
|
14
|
+
*
|
|
15
|
+
* A file whose major exceeds the reader's is refused — that is the one direction
|
|
16
|
+
* compatibility is allowed to fail, and it fails loudly rather than by misreading.
|
|
17
|
+
*/
|
|
18
|
+
export const DRFT_VERSION_MAJOR = 1;
|
|
19
|
+
/**
|
|
20
|
+
* Additive revisions within a generation: new optional chunks, new attribute bits, new
|
|
21
|
+
* enum values with a defined fallback. Never a changed meaning.
|
|
22
|
+
*/
|
|
23
|
+
export const DRFT_VERSION_MINOR = 12;
|
|
24
|
+
/** Bytes before the chunk table. */
|
|
25
|
+
export const HEADER_BYTES = 32;
|
|
26
|
+
/** Bytes per chunk table entry. */
|
|
27
|
+
export const CHUNK_ENTRY_BYTES = 16;
|
|
28
|
+
/**
|
|
29
|
+
* Everything is 4-byte aligned so a reader can build a `Float32Array` view straight over
|
|
30
|
+
* the fetched buffer. A typed array cannot start at an arbitrary offset, so alignment is
|
|
31
|
+
* not tidiness here — it is the entire reason this format exists rather than glTF.
|
|
32
|
+
*/
|
|
33
|
+
export const ALIGNMENT = 4;
|
|
34
|
+
/** Round up to the next aligned boundary. */
|
|
35
|
+
export function align(value) {
|
|
36
|
+
return (value + (ALIGNMENT - 1)) & ~(ALIGNMENT - 1);
|
|
37
|
+
}
|
|
38
|
+
/** A four-character code as a u32, so chunk identity is one integer compare. */
|
|
39
|
+
export function fourCC(code) {
|
|
40
|
+
if (code.length !== 4)
|
|
41
|
+
throw new Error(`drft: a FourCC is four characters, got "${code}"`);
|
|
42
|
+
return ((code.charCodeAt(0) & 0xff) |
|
|
43
|
+
((code.charCodeAt(1) & 0xff) << 8) |
|
|
44
|
+
((code.charCodeAt(2) & 0xff) << 16) |
|
|
45
|
+
((code.charCodeAt(3) & 0xff) << 24));
|
|
46
|
+
}
|
|
47
|
+
/** Back to text, for error messages that name what was not understood. */
|
|
48
|
+
export function fourCCName(code) {
|
|
49
|
+
return String.fromCharCode(code & 0xff, (code >> 8) & 0xff, (code >> 16) & 0xff, (code >> 24) & 0xff);
|
|
50
|
+
}
|
|
51
|
+
export const CHUNK_HEAD = fourCC('HEAD');
|
|
52
|
+
export const CHUNK_MESH = fourCC('MESH');
|
|
53
|
+
/**
|
|
54
|
+
* The asset's own hierarchy: parent index, TRS, mesh index and name. Optional.
|
|
55
|
+
*
|
|
56
|
+
* **Specified in v1 and implemented in 1.6**, which is worth stating rather than quietly fixing.
|
|
57
|
+
* This constant and its `KNOWN_CHUNKS` entry have existed since the format did, and docs/FORMAT.md
|
|
58
|
+
* §4.3 described the payload — while no writer emitted one and no reader consumed one, for the
|
|
59
|
+
* whole of v1. Rigid TRS animation is the first thing that needs a hierarchy, so the chunk is
|
|
60
|
+
* finally built, and to the layout §4.3 already promised rather than to a new one.
|
|
61
|
+
*/
|
|
62
|
+
export const CHUNK_NODE = fourCC('NODE');
|
|
63
|
+
export const CHUNK_MATL = fourCC('MATL');
|
|
64
|
+
export const CHUNK_TEXS = fourCC('TEXS');
|
|
65
|
+
/**
|
|
66
|
+
* Baked colliders: a set of convex hulls, as the points whose hulls they are.
|
|
67
|
+
*
|
|
68
|
+
* **Claimed in v1 and defined in 1.12.** The code and the `KNOWN_CHUNKS` entry existed from the
|
|
69
|
+
* start while no writer emitted one and no reader consumed one — the same history `CHUNK_NODE` and
|
|
70
|
+
* `CHUNK_ANIM` had. `drftColliders.ts` carries the payload and the reason it is points rather than
|
|
71
|
+
* shapes.
|
|
72
|
+
*/
|
|
73
|
+
export const CHUNK_COLL = fourCC('COLL');
|
|
74
|
+
/**
|
|
75
|
+
* A discrete level of detail, as a **complete nested `.drft`**. Added at 1.9.
|
|
76
|
+
*
|
|
77
|
+
* **Not `LODM`, and the difference is what the chunk exists for.** `LODM` is one merged,
|
|
78
|
+
* material-less outline standing in for a whole asset while it loads. A discrete level is a whole
|
|
79
|
+
* model in its own right — its own meshes, its own materials, its own hierarchy — which is what a
|
|
80
|
+
* source that ships four hand-authored levels actually has, and folding one into `LODM` would
|
|
81
|
+
* merge its parts into a single mesh and discard every material it carries.
|
|
82
|
+
*
|
|
83
|
+
* **A nested file rather than appended arrays, and that is what keeps it additive.** `MATL` is
|
|
84
|
+
* parallel to `MESH` by ordinal and `NODE.mesh` indexes `MESH`, so appending another level's
|
|
85
|
+
* meshes to the same arrays would leave an older reader drawing every level at once, on top of
|
|
86
|
+
* itself. Nesting means an older reader skips one unknown chunk under §4.4 rule 2 and opens the
|
|
87
|
+
* file as the full-detail model it also is, which is the degradation that rule promises.
|
|
88
|
+
*
|
|
89
|
+
* Read as bytes rather than parsed, so a consumer pays only for a level it decides to use.
|
|
90
|
+
*/
|
|
91
|
+
export const CHUNK_LODF = fourCC('LODF');
|
|
92
|
+
export const CHUNK_LITE = fourCC('LITE');
|
|
93
|
+
/**
|
|
94
|
+
* A coarse whole-asset level of detail, added in 1.2. Its payload is a `MESH` payload.
|
|
95
|
+
*
|
|
96
|
+
* **A chunk of its own rather than a level field inside `MESH`**, which is what §4.6 of
|
|
97
|
+
* docs/FORMAT.md proposed and is the one part of that design that could not stand. Three
|
|
98
|
+
* things break if a coarse level arrives as a `MESH`, and every one of them is silent:
|
|
99
|
+
*
|
|
100
|
+
* - `MESH` is a *required* chunk kind, so rule 2's protection does not apply. A 1.1 reader
|
|
101
|
+
* would draw the coarse level as a real part, on top of the model it stands for.
|
|
102
|
+
* - `MATL` pairs with `MESH` **by ordinal** and both sides refuse a count mismatch, so a
|
|
103
|
+
* file with one extra `MESH` either fails to open or shifts every material by one.
|
|
104
|
+
* - The manifest counts `MESH` chunks, so "142 of 187 parts" would gain a part that is not
|
|
105
|
+
* one.
|
|
106
|
+
*
|
|
107
|
+
* Optional and skipped in silence, which is rule 2 doing exactly its job: a 1.1 reader opens
|
|
108
|
+
* a 1.2 file as the model without its outline, and the outline is not something a reader that
|
|
109
|
+
* has never heard of it should draw. The table's `index` is the **level ordinal, coarsest
|
|
110
|
+
* first**, which is the same "ordinal within a FourCC" every other chunk kind uses.
|
|
111
|
+
*/
|
|
112
|
+
export const CHUNK_LODM = fourCC('LODM');
|
|
113
|
+
/**
|
|
114
|
+
* One block of a Gaussian splat capture, added in 1.5. Optional, and there are usually several.
|
|
115
|
+
*
|
|
116
|
+
* **Several chunks rather than one, and that is the whole point of putting splats in this
|
|
117
|
+
* container at all.** A chunk is reported when its last byte lands, so a single chunk holding a
|
|
118
|
+
* million splats arrives all at once and streaming buys nothing. Split into blocks whose contents
|
|
119
|
+
* are interleaved across the *whole* capture — see `coarseFirst.ts` — the first block that lands
|
|
120
|
+
* is a complete sparse capture rather than a finished corner of one, and the load opens on a
|
|
121
|
+
* recognisable place that densifies. The table's `index` is the block ordinal.
|
|
122
|
+
*
|
|
123
|
+
* Optional, so rule 2 of docs/FORMAT.md §4.4 applies exactly as it does to `LODM`: a 1.4 reader
|
|
124
|
+
* skips every block in silence and opens the file as whatever else it holds. That is the right
|
|
125
|
+
* degradation — a reader that has never heard of a splat cannot draw one, and a capture is not a
|
|
126
|
+
* mesh it could approximate.
|
|
127
|
+
*
|
|
128
|
+
* **The payload is opaque to this format.** Its per-splat record is `wordsPerSplat` `u32`s whose
|
|
129
|
+
* meaning belongs to whoever packed them, which is `@driftengine/splats`. That keeps the container
|
|
130
|
+
* free of the shader's texel layout and lets a later minor version widen the record — for
|
|
131
|
+
* spherical harmonics, say — without this file learning anything new.
|
|
132
|
+
*/
|
|
133
|
+
export const CHUNK_SPLT = fourCC('SPLT');
|
|
134
|
+
/**
|
|
135
|
+
* A skin: joints, their parents and their inverse bind matrices. Optional, added in 1.6.
|
|
136
|
+
*
|
|
137
|
+
* Written **parents-first**, and the reader refuses any other order. A palette is resolved in
|
|
138
|
+
* index order and reads each joint's parent as it goes, so a nearly-sorted rig is wrong in one
|
|
139
|
+
* limb — which reads as a bad animation rather than as a bad file. The importer is the layer that
|
|
140
|
+
* sorts, because it is also the one that can remap every index naming a joint.
|
|
141
|
+
*/
|
|
142
|
+
export const CHUNK_SKIN = fourCC('SKIN');
|
|
143
|
+
/**
|
|
144
|
+
* One animation clip. Optional, one chunk per clip, added in 1.6.
|
|
145
|
+
*
|
|
146
|
+
* **This FourCC was claimed and undefined for the whole of v1** — docs/FORMAT.md §4.3 listed it as
|
|
147
|
+
* "Reserved. Not in v1", and §4.4's own example of a free additive change was "a v1.4 file carrying
|
|
148
|
+
* `ANIM` opens in a v1.0 reader as a v1.0 file would". That example stops being hypothetical here,
|
|
149
|
+
* and it holds: the chunk is optional, so rule 2 skips it in silence.
|
|
150
|
+
*
|
|
151
|
+
* The table's `index` is the clip ordinal, which is the same "ordinal within a FourCC" every other
|
|
152
|
+
* repeated chunk kind uses.
|
|
153
|
+
*/
|
|
154
|
+
export const CHUNK_ANIM = fourCC('ANIM');
|
|
155
|
+
/**
|
|
156
|
+
* One mesh's morph target deltas. Optional, one chunk per mesh that has any, added in 1.7.
|
|
157
|
+
*
|
|
158
|
+
* **A chunk of its own rather than more attribute bits in `MESH`**, and the reason is the one
|
|
159
|
+
* `CHUNK_LODM` gives about not being a `MESH`: an attribute covers every vertex once, and morph
|
|
160
|
+
* deltas cover every vertex once *per target*. There is no attribute bit that can express a length
|
|
161
|
+
* that scales with a count stored elsewhere, and a reader that guessed would read the wrong number
|
|
162
|
+
* of floats and hand back geometry rather than fail.
|
|
163
|
+
*
|
|
164
|
+
* The table's `index` is the **mesh ordinal** it belongs to, which is how a reader pairs them — the
|
|
165
|
+
* same "ordinal within a FourCC" `MATL` uses, except that `MATL` pairs by position and this pairs
|
|
166
|
+
* by an index a mesh may not have. A mesh with no targets simply has no chunk.
|
|
167
|
+
*/
|
|
168
|
+
export const CHUNK_MORP = fourCC('MORP');
|
|
169
|
+
/**
|
|
170
|
+
* Which substance each material is made of. Optional, one chunk per file, added in 1.8.
|
|
171
|
+
*
|
|
172
|
+
* **`§16` of the chemistry design, and it exists so there is no code between an artist and a fire**:
|
|
173
|
+
* the baker reads `extras.substance` off a glTF material and writes it here, and a consumer hands
|
|
174
|
+
* the ids to `installChemistry().match`. Labelling the oak in Blender is the whole of what a
|
|
175
|
+
* consumer does to make a log burn like oak.
|
|
176
|
+
*
|
|
177
|
+
* **One chunk for the file, pairing by an ordinal in the payload.** `CHUNK_MORP`'s note records why:
|
|
178
|
+
* the table's `index` is the ordinal within a FourCC, so a file where only the fourth material is
|
|
179
|
+
* labelled would carry a chunk at index 0 and a reader pairing by it would set the wrong material on
|
|
180
|
+
* fire. See `drftSubs.ts` for the layout.
|
|
181
|
+
*/
|
|
182
|
+
export const CHUNK_SUBS = fourCC('SUBS');
|
|
183
|
+
/** Bytes before the records in a `SPLT` payload. See `DrftSplatBlock` for the fields. */
|
|
184
|
+
export const SPLAT_BLOCK_PREFIX = 40;
|
|
185
|
+
/**
|
|
186
|
+
* Chunks this reader understands.
|
|
187
|
+
*
|
|
188
|
+
* The set matters because of rule 3 in docs/FORMAT.md §4.4: a *required* chunk that is not in
|
|
189
|
+
* here is a refusal. An optional one is skipped in silence, which is what makes adding a
|
|
190
|
+
* chunk in a minor version free.
|
|
191
|
+
*/
|
|
192
|
+
export const KNOWN_CHUNKS = new Set([
|
|
193
|
+
CHUNK_HEAD,
|
|
194
|
+
CHUNK_MESH,
|
|
195
|
+
CHUNK_NODE,
|
|
196
|
+
CHUNK_MATL,
|
|
197
|
+
CHUNK_TEXS,
|
|
198
|
+
CHUNK_COLL,
|
|
199
|
+
CHUNK_LITE,
|
|
200
|
+
CHUNK_LODM,
|
|
201
|
+
CHUNK_LODF,
|
|
202
|
+
CHUNK_SPLT,
|
|
203
|
+
CHUNK_SKIN,
|
|
204
|
+
CHUNK_ANIM,
|
|
205
|
+
CHUNK_MORP,
|
|
206
|
+
CHUNK_SUBS,
|
|
207
|
+
]);
|
|
208
|
+
/** Chunk flags. */
|
|
209
|
+
export const CHUNK_REQUIRED = 1 << 0;
|
|
210
|
+
/** File flags. Bit 0 is set on every file this engine writes. */
|
|
211
|
+
export const FLAG_LITTLE_ENDIAN = 1 << 0;
|
|
212
|
+
/**
|
|
213
|
+
* Which optional attributes a `MESH` chunk carries.
|
|
214
|
+
*
|
|
215
|
+
* Positions, normals, colours and emissive are mandatory — every `MeshData` has them —
|
|
216
|
+
* so they take no bit. The four optional arrays do, in the order they appear in the
|
|
217
|
+
* payload, and the order is frozen: a new attribute takes the next free bit and appends
|
|
218
|
+
* to the payload, which is why an older reader can stop at the bits it knows.
|
|
219
|
+
*/
|
|
220
|
+
export const ATTR_SPECULAR = 1 << 0;
|
|
221
|
+
export const ATTR_UVS = 1 << 1;
|
|
222
|
+
export const ATTR_EMISSIVE_COLOR = 1 << 2;
|
|
223
|
+
export const ATTR_ROUGHNESS = 1 << 3;
|
|
224
|
+
/**
|
|
225
|
+
* Added in 1.1, and the worked example of why the order is frozen rather than tidy.
|
|
226
|
+
*
|
|
227
|
+
* It takes the next free bit and appends to the end of the payload, so a 1.0 reader meets
|
|
228
|
+
* exactly the arrays it knows in exactly the places it expects them and stops before this
|
|
229
|
+
* one. The asset then draws without grain, which is what a reader that has never heard of
|
|
230
|
+
* grain should produce. Inserting it beside `roughness`, where it belongs conceptually,
|
|
231
|
+
* would have shifted every array after it and repainted the model with no error raised.
|
|
232
|
+
*/
|
|
233
|
+
export const ATTR_GRAIN = 1 << 4;
|
|
234
|
+
/**
|
|
235
|
+
* Added in 1.3, and it is the same story as `grain` one bit along.
|
|
236
|
+
*
|
|
237
|
+
* `MeshData.relief` and `MeshBuilder.setRelief` shipped in engine 0.23.0 and the container was
|
|
238
|
+
* never taught the bit, so a mesh carrying microscopic relief went through `writeDrft` and came
|
|
239
|
+
* back without it. Everything else survived, which is what made it invisible: a round trip gave
|
|
240
|
+
* the right shape with its surface texture gone, and that reads as a lighting problem rather
|
|
241
|
+
* than as a format dropping an array. Found from outside by a test that writes every optional
|
|
242
|
+
* attribute and reports *all* the losses rather than the first.
|
|
243
|
+
*
|
|
244
|
+
* Next free bit and appended last, for the reason spelled out above: a 1.2 reader meets the
|
|
245
|
+
* arrays it knows where it expects them, stops before this one, and draws the asset smooth,
|
|
246
|
+
* which is what a reader that has never heard of relief should produce.
|
|
247
|
+
*/
|
|
248
|
+
export const ATTR_RELIEF = 1 << 5;
|
|
249
|
+
/**
|
|
250
|
+
* Added in 1.4, and the first optional attribute wider than three floats a vertex.
|
|
251
|
+
*
|
|
252
|
+
* A tangent frame: `xyz` running with u, and the bitangent's handedness in `w`. Four floats
|
|
253
|
+
* rather than three because a UV layout can mirror, and a bitangent computed without the sign
|
|
254
|
+
* lights one side of a model inside out. See `generateTangents` for the derivation.
|
|
255
|
+
*
|
|
256
|
+
* Next free bit, appended last, exactly as `grain` and `relief` each explain at length: a 1.3
|
|
257
|
+
* reader meets the six arrays it knows in the six places it expects them, stops before this one,
|
|
258
|
+
* and draws the asset with no tangent frame — which is what a reader that has never heard of
|
|
259
|
+
* tangents should produce, and which is precisely what today's shaders do with one.
|
|
260
|
+
*/
|
|
261
|
+
export const ATTR_TANGENT = 1 << 6;
|
|
262
|
+
/**
|
|
263
|
+
* Added in 1.11, and it is `relief` again with more at stake.
|
|
264
|
+
*
|
|
265
|
+
* **Which joints move a vertex, four of them, as indices into a skinning palette.** `MeshData`
|
|
266
|
+
* declared `joints` and `weights` from the day skinning shipped and `validateMeshData` checked
|
|
267
|
+
* both — four floats a vertex, and neither present without the other — and this format wrote
|
|
268
|
+
* neither, because `buildMesh` listed the six optional arrays it knew and there was no bit to
|
|
269
|
+
* list. The `SKIN` chunk beside it was correct the whole time, so a file carried a skeleton, its
|
|
270
|
+
* bind pose and its clips, pointing at vertices that recorded no influence on any of them.
|
|
271
|
+
*
|
|
272
|
+
* **The failure was silent, which is what makes it the worse kind.** A baker handed the writer a
|
|
273
|
+
* correctly-validated skinned mesh, got no error, and produced a file nothing could skin — and
|
|
274
|
+
* `Mesh.isSkinned` reads `data.joints`, so what came back drew in bind pose for ever. Confine
|
|
275
|
+
* wrote its own binary for one human body rather than use this, about seventy lines across a
|
|
276
|
+
* baker and a loader, and that is the cost this closes.
|
|
277
|
+
*
|
|
278
|
+
* Two bits rather than one, because they are two arrays in the payload and the frozen order is a
|
|
279
|
+
* property of arrays. They are still both-or-neither: `validateMeshData` refuses one without the
|
|
280
|
+
* other on the way out and again on the way in, so the pair cannot half-arrive.
|
|
281
|
+
*
|
|
282
|
+
* Next free bits, appended last, for the reason `grain`, `relief` and `tangents` each spell out:
|
|
283
|
+
* a 1.10 reader meets the seven arrays it knows in the seven places it expects them, stops before
|
|
284
|
+
* these, and draws the asset in bind pose — which is what a reader that has never heard of
|
|
285
|
+
* skinning should produce.
|
|
286
|
+
*/
|
|
287
|
+
export const ATTR_JOINTS = 1 << 7;
|
|
288
|
+
/** How much each of `ATTR_JOINTS`' four influences moves the vertex. See it for the whole story. */
|
|
289
|
+
export const ATTR_WEIGHTS = 1 << 8;
|
|
290
|
+
/** Everything this version defines, so a reader can spot bits from a later writer. */
|
|
291
|
+
export const ATTR_KNOWN = ATTR_SPECULAR |
|
|
292
|
+
ATTR_UVS |
|
|
293
|
+
ATTR_EMISSIVE_COLOR |
|
|
294
|
+
ATTR_ROUGHNESS |
|
|
295
|
+
ATTR_GRAIN |
|
|
296
|
+
ATTR_RELIEF |
|
|
297
|
+
ATTR_TANGENT |
|
|
298
|
+
ATTR_JOINTS |
|
|
299
|
+
ATTR_WEIGHTS;
|
|
300
|
+
/**
|
|
301
|
+
* How a `TEXS` payload is encoded.
|
|
302
|
+
*
|
|
303
|
+
* The compressed three are decoded by `createImageBitmap`, which every target browser has,
|
|
304
|
+
* so carrying them costs no dependency and no decoder of ours. `RAW` is uncompressed RGBA8
|
|
305
|
+
* for generated data and tests. `KTX2` is reserved rather than defined: it is the right
|
|
306
|
+
* long-term answer and it needs a transcoder of some 200 KB, so it becomes an additive
|
|
307
|
+
* minor version once a game needs it rather than a cost every asset pays now.
|
|
308
|
+
*/
|
|
309
|
+
export const CODEC_PNG = 1;
|
|
310
|
+
export const CODEC_JPEG = 2;
|
|
311
|
+
export const CODEC_WEBP = 3;
|
|
312
|
+
export const CODEC_RAW = 4;
|
|
313
|
+
/** What a codec is called, for a message naming what was not understood. */
|
|
314
|
+
export function codecName(codec) {
|
|
315
|
+
return codec === CODEC_PNG
|
|
316
|
+
? 'PNG'
|
|
317
|
+
: codec === CODEC_JPEG
|
|
318
|
+
? 'JPEG'
|
|
319
|
+
: codec === CODEC_WEBP
|
|
320
|
+
? 'WEBP'
|
|
321
|
+
: codec === CODEC_RAW
|
|
322
|
+
? 'RAW'
|
|
323
|
+
: `codec ${codec}`;
|
|
324
|
+
}
|
|
325
|
+
/** Floats in a `MATL` entry, then the signed texture indices that follow them. */
|
|
326
|
+
export const MATERIAL_FLOATS = 15;
|
|
327
|
+
/**
|
|
328
|
+
* Albedo, normal, ORM and emissive.
|
|
329
|
+
*
|
|
330
|
+
* **Four at once rather than one per feature, and the arithmetic is the argument.** Each appended
|
|
331
|
+
* field is a writer change, a reader change, a `FORMAT.md` table edit and a forged-old-file test,
|
|
332
|
+
* so taking them one at a time would spend three minor versions arriving where this arrives in
|
|
333
|
+
* one. A glTF material names all four of its maps as plainly as it names the first, so none of
|
|
334
|
+
* them is speculative — only unbound, and the plans that bind them need no second stride bump.
|
|
335
|
+
*/
|
|
336
|
+
export const MATERIAL_INDICES = 4;
|
|
337
|
+
export const MATERIAL_ENTRY_BYTES = MATERIAL_FLOATS * 4 + MATERIAL_INDICES * 4;
|
|
338
|
+
/**
|
|
339
|
+
* Thrown for any malformed or unreadable file, with the offset that failed.
|
|
340
|
+
*
|
|
341
|
+
* One error type rather than several, because every caller does the same thing with it:
|
|
342
|
+
* a `.drft` that cannot be read is an asset that cannot be shown, and the useful part is
|
|
343
|
+
* the message. Loading fails loudly at init; nothing partial is ever returned.
|
|
344
|
+
*/
|
|
345
|
+
export class DrftError extends Error {
|
|
346
|
+
constructor(message) {
|
|
347
|
+
super(`drft: ${message}`);
|
|
348
|
+
this.name = 'DrftError';
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a `.drft`.
|
|
3
|
+
*
|
|
4
|
+
* Two rules govern everything here, and they pull in opposite directions on purpose:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Never misread.** Every offset and length is checked against the buffer before it
|
|
7
|
+
* is used. A file that is wrong is refused with the byte that failed, never
|
|
8
|
+
* approximated and never half-returned. A partial asset shows up later as a rendering
|
|
9
|
+
* artefact with no route back to its cause, which is the failure mode this whole
|
|
10
|
+
* format was designed after.
|
|
11
|
+
* 2. **Never copy.** Vertex data is viewed in place — `new Float32Array(buffer, offset,
|
|
12
|
+
* count)` — so loading a mesh costs the bounds checks and nothing else.
|
|
13
|
+
*
|
|
14
|
+
* Compatibility lives here too. See docs/FORMAT.md §4.4: old files open forever, unknown
|
|
15
|
+
* optional chunks are skipped in silence, unknown *required* chunks are a refusal.
|
|
16
|
+
*/
|
|
17
|
+
import type { AnimationClip, DrftSkin } from './animationData.ts';
|
|
18
|
+
import type { DrftNode } from './drftSkin.ts';
|
|
19
|
+
import type { MeshData } from './meshData.ts';
|
|
20
|
+
import type { DrftChunk, DrftHead, DrftMaterial, DrftSplatBlock } from './drftFormat.ts';
|
|
21
|
+
/** An embedded image, still compressed. Decoding is the consumer's, through `createImageBitmap`. */
|
|
22
|
+
export interface DrftTexture {
|
|
23
|
+
/** What the source called this image. Empty for a file written before names existed. */
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly codec: number;
|
|
26
|
+
readonly width: number;
|
|
27
|
+
readonly height: number;
|
|
28
|
+
/**
|
|
29
|
+
* A view over the fetched buffer, not a copy, exactly like the vertex arrays.
|
|
30
|
+
*
|
|
31
|
+
* It therefore keeps the whole file alive for as long as it is held. That is the intended
|
|
32
|
+
* trade for a load that allocates nothing, and it is why a consumer decoding these should
|
|
33
|
+
* drop its reference to the asset afterwards rather than keep it beside the GPU textures.
|
|
34
|
+
*/
|
|
35
|
+
readonly bytes: Uint8Array;
|
|
36
|
+
}
|
|
37
|
+
/** A decoded asset. See docs/FORMAT.md for what each chunk carries. */
|
|
38
|
+
export interface DrftAsset {
|
|
39
|
+
readonly head: DrftHead;
|
|
40
|
+
readonly meshes: readonly MeshData[];
|
|
41
|
+
/**
|
|
42
|
+
* Coarse whole-asset levels of detail, coarsest first, or empty for a file carrying none.
|
|
43
|
+
*
|
|
44
|
+
* Separate from `meshes` because they are not parts of the model: each one *is* the model,
|
|
45
|
+
* at a resolution that arrives sooner. A consumer reading a whole file at once has no use
|
|
46
|
+
* for them and ignores them; the streaming loader draws the best one it has until the real
|
|
47
|
+
* geometry is complete. See `DrftLoader` and docs/FORMAT.md §4.6.
|
|
48
|
+
*/
|
|
49
|
+
readonly lods: readonly MeshData[];
|
|
50
|
+
/**
|
|
51
|
+
* Discrete levels of detail, finest first, each a complete `.drft` still in its bytes.
|
|
52
|
+
*
|
|
53
|
+
* **Handed over unparsed on purpose.** A level is a whole model, and a consumer that picks one
|
|
54
|
+
* by distance wants exactly one of them — parsing all four to hand back three that will not be
|
|
55
|
+
* drawn is work nobody asked for. Pass the one you want to `readDrft` again.
|
|
56
|
+
*/
|
|
57
|
+
readonly levels: readonly Uint8Array[];
|
|
58
|
+
/**
|
|
59
|
+
* The whole capture, assembled from every `SPLT` block in ordinal order, or null for a file
|
|
60
|
+
* carrying none.
|
|
61
|
+
*
|
|
62
|
+
* **Assembled rather than handed over as blocks**, because a caller reading a whole file at
|
|
63
|
+
* once has no use for the split: it exists so a *stream* can put a sparse capture on screen
|
|
64
|
+
* early, and `DrftStream.onSplats` is where that matters. The records still arrive in the
|
|
65
|
+
* file's coarse-first order, so a consumer that draws a prefix of them draws a sparse whole.
|
|
66
|
+
*/
|
|
67
|
+
readonly splats: DrftSplatBlock | null;
|
|
68
|
+
/**
|
|
69
|
+
* The asset's own hierarchy, or empty for a file carrying no `NODE`.
|
|
70
|
+
*
|
|
71
|
+
* Empty means what it always meant — one mesh at the origin — so a caller written before 1.6
|
|
72
|
+
* behaves identically against a file with a hierarchy it ignores.
|
|
73
|
+
*/
|
|
74
|
+
readonly nodes: readonly DrftNode[];
|
|
75
|
+
/** Skins, in the order their chunks appear. Empty for a file that deforms nothing. */
|
|
76
|
+
readonly skins: readonly DrftSkin[];
|
|
77
|
+
/** Clips, in the order their chunks appear. Empty for a file that animates nothing. */
|
|
78
|
+
readonly clips: readonly AnimationClip[];
|
|
79
|
+
/** One per mesh by ordinal, or empty when the file names no materials. */
|
|
80
|
+
readonly materials: readonly DrftMaterial[];
|
|
81
|
+
readonly textures: readonly DrftTexture[];
|
|
82
|
+
/**
|
|
83
|
+
* Which substance each material is made of, or empty where the file labels none.
|
|
84
|
+
*
|
|
85
|
+
* The `SUBS` chunk, added at 1.8. Hand these to `installChemistry().match` — which matches
|
|
86
|
+
* **exactly** and reports what it could not, rather than guessing.
|
|
87
|
+
*/
|
|
88
|
+
readonly substances: readonly {
|
|
89
|
+
readonly material: number;
|
|
90
|
+
readonly substance: string;
|
|
91
|
+
}[];
|
|
92
|
+
/**
|
|
93
|
+
* The convex hulls this asset collides as, or empty for a file carrying no `COLL`.
|
|
94
|
+
*
|
|
95
|
+
* Views over the fetched buffer, xyz-packed, in the asset's own space. Turning them into shapes is
|
|
96
|
+
* one line and belongs to whoever has a physics package:
|
|
97
|
+
*
|
|
98
|
+
* ```ts
|
|
99
|
+
* const shapes = asset.colliders.map((points) => hullShape(points));
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
readonly colliders: readonly Float32Array[];
|
|
103
|
+
/** Optional chunks this reader did not understand, in file order. Diagnostics only. */
|
|
104
|
+
readonly skipped: readonly string[];
|
|
105
|
+
readonly versionMajor: number;
|
|
106
|
+
readonly versionMinor: number;
|
|
107
|
+
}
|
|
108
|
+
export declare function readHead(buffer: ArrayBuffer, chunk: DrftChunk): DrftHead;
|
|
109
|
+
export declare function readMesh(buffer: ArrayBuffer, chunk: DrftChunk): MeshData;
|
|
110
|
+
/** Decode a `.drft` from a buffer. Throws `DrftError` on anything it cannot read. */
|
|
111
|
+
export declare function readDrft(buffer: ArrayBuffer): DrftAsset;
|
|
112
|
+
/**
|
|
113
|
+
* One `SPLT` block, viewed in place.
|
|
114
|
+
*
|
|
115
|
+
* **Zero-copy, like every other payload here.** The records are a `Uint32Array` over the file's
|
|
116
|
+
* own bytes, which is what the format's four-byte alignment exists for and what lets a capture of
|
|
117
|
+
* tens of megabytes reach the GPU without being duplicated on the way.
|
|
118
|
+
*/
|
|
119
|
+
export declare function readSplatBlock(buffer: ArrayBuffer, chunk: DrftChunk): DrftSplatBlock;
|
|
120
|
+
export declare function readTexture(buffer: ArrayBuffer, chunk: DrftChunk): DrftTexture;
|
|
121
|
+
export declare function readMaterials(buffer: ArrayBuffer, chunk: DrftChunk): DrftMaterial[];
|