@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.
Files changed (67) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +9 -0
  4. package/dist/animationData.d.ts +53 -0
  5. package/dist/animationData.js +13 -0
  6. package/dist/coarseFirst.d.ts +27 -0
  7. package/dist/coarseFirst.js +118 -0
  8. package/dist/drftColliders.d.ts +53 -0
  9. package/dist/drftColliders.js +137 -0
  10. package/dist/drftFormat.d.ts +454 -0
  11. package/dist/drftFormat.js +350 -0
  12. package/dist/drftRead.d.ts +121 -0
  13. package/dist/drftRead.js +487 -0
  14. package/dist/drftSkin.d.ts +40 -0
  15. package/dist/drftSkin.js +270 -0
  16. package/dist/drftStream.d.ts +170 -0
  17. package/dist/drftStream.js +315 -0
  18. package/dist/drftSubs.d.ts +18 -0
  19. package/dist/drftSubs.js +70 -0
  20. package/dist/drftWrite.d.ts +103 -0
  21. package/dist/drftWrite.js +478 -0
  22. package/dist/fixtures/v1-0.d.ts +9 -0
  23. package/dist/fixtures/v1-0.js +9 -0
  24. package/dist/fixtures/v1-1.d.ts +16 -0
  25. package/dist/fixtures/v1-1.js +16 -0
  26. package/dist/fixtures/v1-11.d.ts +14 -0
  27. package/dist/fixtures/v1-11.js +14 -0
  28. package/dist/fixtures/v1-2.d.ts +19 -0
  29. package/dist/fixtures/v1-2.js +19 -0
  30. package/dist/fixtures/v1-3.d.ts +18 -0
  31. package/dist/fixtures/v1-3.js +18 -0
  32. package/dist/fixtures/v1-4.d.ts +13 -0
  33. package/dist/fixtures/v1-4.js +13 -0
  34. package/dist/fixtures/v1-5.d.ts +14 -0
  35. package/dist/fixtures/v1-5.js +14 -0
  36. package/dist/fixtures/v1-6.d.ts +9 -0
  37. package/dist/fixtures/v1-6.js +9 -0
  38. package/dist/fixtures/v1-7.d.ts +9 -0
  39. package/dist/fixtures/v1-7.js +9 -0
  40. package/dist/fixtures/v1-9.d.ts +9 -0
  41. package/dist/fixtures/v1-9.js +9 -0
  42. package/dist/index.d.ts +26 -0
  43. package/dist/index.js +29 -0
  44. package/dist/meshData.d.ts +210 -0
  45. package/dist/meshData.js +105 -0
  46. package/package.json +57 -0
  47. package/src/animationData.ts +58 -0
  48. package/src/coarseFirst.ts +113 -0
  49. package/src/drftColliders.ts +153 -0
  50. package/src/drftFormat.ts +537 -0
  51. package/src/drftRead.ts +652 -0
  52. package/src/drftSkin.ts +326 -0
  53. package/src/drftStream.ts +436 -0
  54. package/src/drftSubs.ts +86 -0
  55. package/src/drftWrite.ts +613 -0
  56. package/src/fixtures/v1-0.ts +10 -0
  57. package/src/fixtures/v1-1.ts +17 -0
  58. package/src/fixtures/v1-11.ts +15 -0
  59. package/src/fixtures/v1-2.ts +20 -0
  60. package/src/fixtures/v1-3.ts +19 -0
  61. package/src/fixtures/v1-4.ts +14 -0
  62. package/src/fixtures/v1-5.ts +15 -0
  63. package/src/fixtures/v1-6.ts +10 -0
  64. package/src/fixtures/v1-7.ts +10 -0
  65. package/src/fixtures/v1-9.ts +10 -0
  66. package/src/index.ts +56 -0
  67. package/src/meshData.ts +301 -0
@@ -0,0 +1,454 @@
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 declare const DRFT_MAGIC = 1413894724;
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 declare 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 declare const DRFT_VERSION_MINOR = 12;
24
+ /** Bytes before the chunk table. */
25
+ export declare const HEADER_BYTES = 32;
26
+ /** Bytes per chunk table entry. */
27
+ export declare 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 declare const ALIGNMENT = 4;
34
+ /** Round up to the next aligned boundary. */
35
+ export declare function align(value: number): number;
36
+ /** A four-character code as a u32, so chunk identity is one integer compare. */
37
+ export declare function fourCC(code: string): number;
38
+ /** Back to text, for error messages that name what was not understood. */
39
+ export declare function fourCCName(code: number): string;
40
+ export declare const CHUNK_HEAD: number;
41
+ export declare const CHUNK_MESH: number;
42
+ /**
43
+ * The asset's own hierarchy: parent index, TRS, mesh index and name. Optional.
44
+ *
45
+ * **Specified in v1 and implemented in 1.6**, which is worth stating rather than quietly fixing.
46
+ * This constant and its `KNOWN_CHUNKS` entry have existed since the format did, and docs/FORMAT.md
47
+ * §4.3 described the payload — while no writer emitted one and no reader consumed one, for the
48
+ * whole of v1. Rigid TRS animation is the first thing that needs a hierarchy, so the chunk is
49
+ * finally built, and to the layout §4.3 already promised rather than to a new one.
50
+ */
51
+ export declare const CHUNK_NODE: number;
52
+ export declare const CHUNK_MATL: number;
53
+ export declare const CHUNK_TEXS: number;
54
+ /**
55
+ * Baked colliders: a set of convex hulls, as the points whose hulls they are.
56
+ *
57
+ * **Claimed in v1 and defined in 1.12.** The code and the `KNOWN_CHUNKS` entry existed from the
58
+ * start while no writer emitted one and no reader consumed one — the same history `CHUNK_NODE` and
59
+ * `CHUNK_ANIM` had. `drftColliders.ts` carries the payload and the reason it is points rather than
60
+ * shapes.
61
+ */
62
+ export declare const CHUNK_COLL: number;
63
+ /**
64
+ * A discrete level of detail, as a **complete nested `.drft`**. Added at 1.9.
65
+ *
66
+ * **Not `LODM`, and the difference is what the chunk exists for.** `LODM` is one merged,
67
+ * material-less outline standing in for a whole asset while it loads. A discrete level is a whole
68
+ * model in its own right — its own meshes, its own materials, its own hierarchy — which is what a
69
+ * source that ships four hand-authored levels actually has, and folding one into `LODM` would
70
+ * merge its parts into a single mesh and discard every material it carries.
71
+ *
72
+ * **A nested file rather than appended arrays, and that is what keeps it additive.** `MATL` is
73
+ * parallel to `MESH` by ordinal and `NODE.mesh` indexes `MESH`, so appending another level's
74
+ * meshes to the same arrays would leave an older reader drawing every level at once, on top of
75
+ * itself. Nesting means an older reader skips one unknown chunk under §4.4 rule 2 and opens the
76
+ * file as the full-detail model it also is, which is the degradation that rule promises.
77
+ *
78
+ * Read as bytes rather than parsed, so a consumer pays only for a level it decides to use.
79
+ */
80
+ export declare const CHUNK_LODF: number;
81
+ export declare const CHUNK_LITE: number;
82
+ /**
83
+ * A coarse whole-asset level of detail, added in 1.2. Its payload is a `MESH` payload.
84
+ *
85
+ * **A chunk of its own rather than a level field inside `MESH`**, which is what §4.6 of
86
+ * docs/FORMAT.md proposed and is the one part of that design that could not stand. Three
87
+ * things break if a coarse level arrives as a `MESH`, and every one of them is silent:
88
+ *
89
+ * - `MESH` is a *required* chunk kind, so rule 2's protection does not apply. A 1.1 reader
90
+ * would draw the coarse level as a real part, on top of the model it stands for.
91
+ * - `MATL` pairs with `MESH` **by ordinal** and both sides refuse a count mismatch, so a
92
+ * file with one extra `MESH` either fails to open or shifts every material by one.
93
+ * - The manifest counts `MESH` chunks, so "142 of 187 parts" would gain a part that is not
94
+ * one.
95
+ *
96
+ * Optional and skipped in silence, which is rule 2 doing exactly its job: a 1.1 reader opens
97
+ * a 1.2 file as the model without its outline, and the outline is not something a reader that
98
+ * has never heard of it should draw. The table's `index` is the **level ordinal, coarsest
99
+ * first**, which is the same "ordinal within a FourCC" every other chunk kind uses.
100
+ */
101
+ export declare const CHUNK_LODM: number;
102
+ /**
103
+ * One block of a Gaussian splat capture, added in 1.5. Optional, and there are usually several.
104
+ *
105
+ * **Several chunks rather than one, and that is the whole point of putting splats in this
106
+ * container at all.** A chunk is reported when its last byte lands, so a single chunk holding a
107
+ * million splats arrives all at once and streaming buys nothing. Split into blocks whose contents
108
+ * are interleaved across the *whole* capture — see `coarseFirst.ts` — the first block that lands
109
+ * is a complete sparse capture rather than a finished corner of one, and the load opens on a
110
+ * recognisable place that densifies. The table's `index` is the block ordinal.
111
+ *
112
+ * Optional, so rule 2 of docs/FORMAT.md §4.4 applies exactly as it does to `LODM`: a 1.4 reader
113
+ * skips every block in silence and opens the file as whatever else it holds. That is the right
114
+ * degradation — a reader that has never heard of a splat cannot draw one, and a capture is not a
115
+ * mesh it could approximate.
116
+ *
117
+ * **The payload is opaque to this format.** Its per-splat record is `wordsPerSplat` `u32`s whose
118
+ * meaning belongs to whoever packed them, which is `@driftengine/splats`. That keeps the container
119
+ * free of the shader's texel layout and lets a later minor version widen the record — for
120
+ * spherical harmonics, say — without this file learning anything new.
121
+ */
122
+ export declare const CHUNK_SPLT: number;
123
+ /**
124
+ * A skin: joints, their parents and their inverse bind matrices. Optional, added in 1.6.
125
+ *
126
+ * Written **parents-first**, and the reader refuses any other order. A palette is resolved in
127
+ * index order and reads each joint's parent as it goes, so a nearly-sorted rig is wrong in one
128
+ * limb — which reads as a bad animation rather than as a bad file. The importer is the layer that
129
+ * sorts, because it is also the one that can remap every index naming a joint.
130
+ */
131
+ export declare const CHUNK_SKIN: number;
132
+ /**
133
+ * One animation clip. Optional, one chunk per clip, added in 1.6.
134
+ *
135
+ * **This FourCC was claimed and undefined for the whole of v1** — docs/FORMAT.md §4.3 listed it as
136
+ * "Reserved. Not in v1", and §4.4's own example of a free additive change was "a v1.4 file carrying
137
+ * `ANIM` opens in a v1.0 reader as a v1.0 file would". That example stops being hypothetical here,
138
+ * and it holds: the chunk is optional, so rule 2 skips it in silence.
139
+ *
140
+ * The table's `index` is the clip ordinal, which is the same "ordinal within a FourCC" every other
141
+ * repeated chunk kind uses.
142
+ */
143
+ export declare const CHUNK_ANIM: number;
144
+ /**
145
+ * One mesh's morph target deltas. Optional, one chunk per mesh that has any, added in 1.7.
146
+ *
147
+ * **A chunk of its own rather than more attribute bits in `MESH`**, and the reason is the one
148
+ * `CHUNK_LODM` gives about not being a `MESH`: an attribute covers every vertex once, and morph
149
+ * deltas cover every vertex once *per target*. There is no attribute bit that can express a length
150
+ * that scales with a count stored elsewhere, and a reader that guessed would read the wrong number
151
+ * of floats and hand back geometry rather than fail.
152
+ *
153
+ * The table's `index` is the **mesh ordinal** it belongs to, which is how a reader pairs them — the
154
+ * same "ordinal within a FourCC" `MATL` uses, except that `MATL` pairs by position and this pairs
155
+ * by an index a mesh may not have. A mesh with no targets simply has no chunk.
156
+ */
157
+ export declare const CHUNK_MORP: number;
158
+ /**
159
+ * Which substance each material is made of. Optional, one chunk per file, added in 1.8.
160
+ *
161
+ * **`§16` of the chemistry design, and it exists so there is no code between an artist and a fire**:
162
+ * the baker reads `extras.substance` off a glTF material and writes it here, and a consumer hands
163
+ * the ids to `installChemistry().match`. Labelling the oak in Blender is the whole of what a
164
+ * consumer does to make a log burn like oak.
165
+ *
166
+ * **One chunk for the file, pairing by an ordinal in the payload.** `CHUNK_MORP`'s note records why:
167
+ * the table's `index` is the ordinal within a FourCC, so a file where only the fourth material is
168
+ * labelled would carry a chunk at index 0 and a reader pairing by it would set the wrong material on
169
+ * fire. See `drftSubs.ts` for the layout.
170
+ */
171
+ export declare const CHUNK_SUBS: number;
172
+ /** Bytes before the records in a `SPLT` payload. See `DrftSplatBlock` for the fields. */
173
+ export declare const SPLAT_BLOCK_PREFIX = 40;
174
+ /**
175
+ * Chunks this reader understands.
176
+ *
177
+ * The set matters because of rule 3 in docs/FORMAT.md §4.4: a *required* chunk that is not in
178
+ * here is a refusal. An optional one is skipped in silence, which is what makes adding a
179
+ * chunk in a minor version free.
180
+ */
181
+ export declare const KNOWN_CHUNKS: ReadonlySet<number>;
182
+ /** Chunk flags. */
183
+ export declare const CHUNK_REQUIRED: number;
184
+ /** File flags. Bit 0 is set on every file this engine writes. */
185
+ export declare const FLAG_LITTLE_ENDIAN: number;
186
+ /**
187
+ * Which optional attributes a `MESH` chunk carries.
188
+ *
189
+ * Positions, normals, colours and emissive are mandatory — every `MeshData` has them —
190
+ * so they take no bit. The four optional arrays do, in the order they appear in the
191
+ * payload, and the order is frozen: a new attribute takes the next free bit and appends
192
+ * to the payload, which is why an older reader can stop at the bits it knows.
193
+ */
194
+ export declare const ATTR_SPECULAR: number;
195
+ export declare const ATTR_UVS: number;
196
+ export declare const ATTR_EMISSIVE_COLOR: number;
197
+ export declare const ATTR_ROUGHNESS: number;
198
+ /**
199
+ * Added in 1.1, and the worked example of why the order is frozen rather than tidy.
200
+ *
201
+ * It takes the next free bit and appends to the end of the payload, so a 1.0 reader meets
202
+ * exactly the arrays it knows in exactly the places it expects them and stops before this
203
+ * one. The asset then draws without grain, which is what a reader that has never heard of
204
+ * grain should produce. Inserting it beside `roughness`, where it belongs conceptually,
205
+ * would have shifted every array after it and repainted the model with no error raised.
206
+ */
207
+ export declare const ATTR_GRAIN: number;
208
+ /**
209
+ * Added in 1.3, and it is the same story as `grain` one bit along.
210
+ *
211
+ * `MeshData.relief` and `MeshBuilder.setRelief` shipped in engine 0.23.0 and the container was
212
+ * never taught the bit, so a mesh carrying microscopic relief went through `writeDrft` and came
213
+ * back without it. Everything else survived, which is what made it invisible: a round trip gave
214
+ * the right shape with its surface texture gone, and that reads as a lighting problem rather
215
+ * than as a format dropping an array. Found from outside by a test that writes every optional
216
+ * attribute and reports *all* the losses rather than the first.
217
+ *
218
+ * Next free bit and appended last, for the reason spelled out above: a 1.2 reader meets the
219
+ * arrays it knows where it expects them, stops before this one, and draws the asset smooth,
220
+ * which is what a reader that has never heard of relief should produce.
221
+ */
222
+ export declare const ATTR_RELIEF: number;
223
+ /**
224
+ * Added in 1.4, and the first optional attribute wider than three floats a vertex.
225
+ *
226
+ * A tangent frame: `xyz` running with u, and the bitangent's handedness in `w`. Four floats
227
+ * rather than three because a UV layout can mirror, and a bitangent computed without the sign
228
+ * lights one side of a model inside out. See `generateTangents` for the derivation.
229
+ *
230
+ * Next free bit, appended last, exactly as `grain` and `relief` each explain at length: a 1.3
231
+ * reader meets the six arrays it knows in the six places it expects them, stops before this one,
232
+ * and draws the asset with no tangent frame — which is what a reader that has never heard of
233
+ * tangents should produce, and which is precisely what today's shaders do with one.
234
+ */
235
+ export declare const ATTR_TANGENT: number;
236
+ /**
237
+ * Added in 1.11, and it is `relief` again with more at stake.
238
+ *
239
+ * **Which joints move a vertex, four of them, as indices into a skinning palette.** `MeshData`
240
+ * declared `joints` and `weights` from the day skinning shipped and `validateMeshData` checked
241
+ * both — four floats a vertex, and neither present without the other — and this format wrote
242
+ * neither, because `buildMesh` listed the six optional arrays it knew and there was no bit to
243
+ * list. The `SKIN` chunk beside it was correct the whole time, so a file carried a skeleton, its
244
+ * bind pose and its clips, pointing at vertices that recorded no influence on any of them.
245
+ *
246
+ * **The failure was silent, which is what makes it the worse kind.** A baker handed the writer a
247
+ * correctly-validated skinned mesh, got no error, and produced a file nothing could skin — and
248
+ * `Mesh.isSkinned` reads `data.joints`, so what came back drew in bind pose for ever. Confine
249
+ * wrote its own binary for one human body rather than use this, about seventy lines across a
250
+ * baker and a loader, and that is the cost this closes.
251
+ *
252
+ * Two bits rather than one, because they are two arrays in the payload and the frozen order is a
253
+ * property of arrays. They are still both-or-neither: `validateMeshData` refuses one without the
254
+ * other on the way out and again on the way in, so the pair cannot half-arrive.
255
+ *
256
+ * Next free bits, appended last, for the reason `grain`, `relief` and `tangents` each spell out:
257
+ * a 1.10 reader meets the seven arrays it knows in the seven places it expects them, stops before
258
+ * these, and draws the asset in bind pose — which is what a reader that has never heard of
259
+ * skinning should produce.
260
+ */
261
+ export declare const ATTR_JOINTS: number;
262
+ /** How much each of `ATTR_JOINTS`' four influences moves the vertex. See it for the whole story. */
263
+ export declare const ATTR_WEIGHTS: number;
264
+ /** Everything this version defines, so a reader can spot bits from a later writer. */
265
+ export declare const ATTR_KNOWN: number;
266
+ /**
267
+ * How a `TEXS` payload is encoded.
268
+ *
269
+ * The compressed three are decoded by `createImageBitmap`, which every target browser has,
270
+ * so carrying them costs no dependency and no decoder of ours. `RAW` is uncompressed RGBA8
271
+ * for generated data and tests. `KTX2` is reserved rather than defined: it is the right
272
+ * long-term answer and it needs a transcoder of some 200 KB, so it becomes an additive
273
+ * minor version once a game needs it rather than a cost every asset pays now.
274
+ */
275
+ export declare const CODEC_PNG = 1;
276
+ export declare const CODEC_JPEG = 2;
277
+ export declare const CODEC_WEBP = 3;
278
+ export declare const CODEC_RAW = 4;
279
+ /** What a codec is called, for a message naming what was not understood. */
280
+ export declare function codecName(codec: number): string;
281
+ /**
282
+ * A capture, as the writer is handed it.
283
+ *
284
+ * **`positions` is read and never stored**, which is the one surprising field here. The writer
285
+ * needs them to decide the coarse-first block layout, and the reader gets them back out of the
286
+ * records — so writing them a second time would be twelve bytes a splat of duplicate in the one
287
+ * place this format is trying hardest to be small.
288
+ */
289
+ export interface DrftSplats {
290
+ readonly count: number;
291
+ /** Three per splat, in the capture's own space. Read for layout only. */
292
+ readonly positions: Float32Array;
293
+ /** `wordsPerSplat` `u32`s per splat, in `count` order. Opaque here. */
294
+ readonly records: Uint32Array;
295
+ readonly wordsPerSplat: number;
296
+ readonly boundsMin: Float32Array;
297
+ readonly boundsMax: Float32Array;
298
+ /** Coefficients the source carried and the packer did not read. Carried as a number. */
299
+ readonly sphericalHarmonics?: number;
300
+ /**
301
+ * How many blocks to split the capture into, or absent for a size this writer chooses.
302
+ *
303
+ * More blocks means a capture that opens sooner and a longer chunk table; each entry is
304
+ * sixteen bytes, so even thirty-two blocks is half a kilobyte against megabytes of records.
305
+ */
306
+ readonly blocks?: number;
307
+ }
308
+ /**
309
+ * One block of a capture, as the reader hands it back.
310
+ *
311
+ * Every block repeats the whole capture's count and bounds, which is twenty-eight bytes each and
312
+ * makes a block self-describing: a consumer that has only the first one can size its buffers and
313
+ * cull against the final extent, rather than waiting for a manifest it may already have passed.
314
+ */
315
+ export interface DrftSplatBlock {
316
+ /** Splats in this block. */
317
+ readonly count: number;
318
+ /** Splats in the whole capture, so a reader allocates once from the first block it meets. */
319
+ readonly totalCount: number;
320
+ readonly wordsPerSplat: number;
321
+ readonly sphericalHarmonics: number;
322
+ /** The whole capture's extent, not this block's. */
323
+ readonly boundsMin: Float32Array;
324
+ readonly boundsMax: Float32Array;
325
+ /** A view over the file's own bytes: `wordsPerSplat * count` words, no copy. */
326
+ readonly records: Uint32Array;
327
+ }
328
+ /**
329
+ * One surface, stored once per mesh rather than once per vertex.
330
+ *
331
+ * Parallel to the `MESH` chunks by ordinal: material *n* describes mesh *n*. A parallel
332
+ * array rather than a field inside `MESH` because a material is small and a mesh is
333
+ * megabytes, and the loader wants every material before it uploads anything, so it can
334
+ * create each texture once and share it between the meshes that name it.
335
+ */
336
+ export interface DrftMaterial {
337
+ /**
338
+ * What the source called this surface. Diagnostic, and more than diagnostic.
339
+ *
340
+ * A bought asset frequently carries **no material data worth the name** and yet names its
341
+ * materials perfectly well: the one this format was built against stores every surface as
342
+ * the same default grey with no transparency, while calling them `body`, `glass`,
343
+ * `chrome`, `tire_mat5` and `calipers`. The name is then the only thing in the file that
344
+ * says what a surface *is*, so it is carried rather than discarded, and a scene can dress
345
+ * an import without editing the import.
346
+ */
347
+ readonly name: string;
348
+ readonly color: readonly [number, number, number];
349
+ readonly specular: number;
350
+ readonly roughness: number;
351
+ readonly emissive: number;
352
+ readonly emissiveColor: readonly [number, number, number];
353
+ /** 1 is opaque. Below 1 the surface blends, and the loader draws it after the solid ones. */
354
+ readonly opacity: number;
355
+ /** Index into the asset's textures for the colour map, or -1 for an untextured surface. */
356
+ readonly albedo: number;
357
+ /**
358
+ * Index into the asset's textures for the surface-space normal map, or -1 for none.
359
+ *
360
+ * **Written by any baker that can derive it, and bound by nothing yet.** Normal maps ship in the
361
+ * renderer; carrying one through the container is the plan after ORM's. The field is defined
362
+ * here rather than appended later so that all four indices cost the format one minor version
363
+ * instead of three — see `MATERIAL_INDICES`.
364
+ */
365
+ readonly normalMap: number;
366
+ /** Occlusion in R, roughness in G, metallic in B, or -1 for none. glTF's packing. */
367
+ readonly ormMap: number;
368
+ /** Index into the asset's textures for the emissive map, or -1. Written, not yet bound. */
369
+ readonly emissiveMap: number;
370
+ /**
371
+ * glTF's `roughnessFactor` where an `ormMap` supplies the roughness, and 1 where it does not.
372
+ *
373
+ * **A factor that multiplies a texture is not a value**, which is the mistake this pair exists
374
+ * to stop repeating. `roughness` and `specular` above are the scalars a material states when it
375
+ * has no map; these are what the same numbers mean when it has one.
376
+ */
377
+ readonly roughnessScale: number;
378
+ /** glTF's `metallicFactor` where an `ormMap` supplies the metallic, and 1 where it does not. */
379
+ readonly metallicScale: number;
380
+ /**
381
+ * glTF's `occlusionTexture.strength`, or **0** where nothing establishes that the ORM map's R
382
+ * channel holds occlusion at all.
383
+ *
384
+ * glTF assigns G and B of a `metallicRoughnessTexture` and says nothing about R, so occlusion is
385
+ * only there when `occlusionTexture` names the same image — which is what the ORM convention is.
386
+ * Anywhere else this is 0, because reading R would be reading whatever the exporter left.
387
+ */
388
+ readonly occlusionStrength: number;
389
+ /**
390
+ * How much of the environment this surface mirrors, 0 to 1.
391
+ *
392
+ * Distinct from `specular`, which is the strength of a highlight from a *light*. A surface
393
+ * can take a sharp highlight and reflect nothing, which is most plastic, or reflect its
394
+ * surroundings strongly, which is what makes paint and chrome read as what they are.
395
+ */
396
+ readonly reflectivity: number;
397
+ /**
398
+ * Alpha below which a fragment is discarded, 0 for a surface that discards nothing.
399
+ *
400
+ * **A test, not an opacity, and the difference is the whole reason this field exists.** A cutout
401
+ * says which *texels* of a surface are there at all — the gaps in a grille, the space between
402
+ * leaves, the holes in a fence — and leaves everything it keeps at full strength. An opacity
403
+ * says how much of the light passes through the surface that is there. A format with only the
404
+ * second has to spend it on the first, and `SurfaceMaterial.cutout` had no way through the
405
+ * container until this: an alpha-tested surface arrived as a solid rectangle or as nothing.
406
+ *
407
+ * glTF states it exactly — `alphaMode: 'MASK'` with `alphaCutoff`, which defaults to 0.5 — and
408
+ * it is what a vehicle format's alpha-test reference is, on the materials that say they are
409
+ * tested.
410
+ */
411
+ readonly cutout: number;
412
+ }
413
+ /** Floats in a `MATL` entry, then the signed texture indices that follow them. */
414
+ export declare const MATERIAL_FLOATS = 15;
415
+ /**
416
+ * Albedo, normal, ORM and emissive.
417
+ *
418
+ * **Four at once rather than one per feature, and the arithmetic is the argument.** Each appended
419
+ * field is a writer change, a reader change, a `FORMAT.md` table edit and a forged-old-file test,
420
+ * so taking them one at a time would spend three minor versions arriving where this arrives in
421
+ * one. A glTF material names all four of its maps as plainly as it names the first, so none of
422
+ * them is speculative — only unbound, and the plans that bind them need no second stride bump.
423
+ */
424
+ export declare const MATERIAL_INDICES = 4;
425
+ export declare const MATERIAL_ENTRY_BYTES: number;
426
+ /** What `HEAD` says about the asset as a whole. */
427
+ export interface DrftHead {
428
+ /** The asset's own name, for diagnostics. Never load-bearing. */
429
+ readonly name: string;
430
+ /** What wrote it — tool and version — so a bad bake can be traced to its baker. */
431
+ readonly generator: string;
432
+ /** Metres per unit in the source. 1 for an asset already authored in metres. */
433
+ readonly unitScale: number;
434
+ /** Axis-aligned bounds of everything, in this file's units: min x/y/z then max x/y/z. */
435
+ readonly bounds: readonly number[];
436
+ }
437
+ /** A chunk as the table describes it, before its payload is touched. */
438
+ export interface DrftChunk {
439
+ readonly code: number;
440
+ readonly offset: number;
441
+ readonly byteLength: number;
442
+ readonly flags: number;
443
+ readonly index: number;
444
+ }
445
+ /**
446
+ * Thrown for any malformed or unreadable file, with the offset that failed.
447
+ *
448
+ * One error type rather than several, because every caller does the same thing with it:
449
+ * a `.drft` that cannot be read is an asset that cannot be shown, and the useful part is
450
+ * the message. Loading fails loudly at init; nothing partial is ever returned.
451
+ */
452
+ export declare class DrftError extends Error {
453
+ constructor(message: string);
454
+ }