@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
package/src/drftWrite.ts
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writing a `.drft`.
|
|
3
|
+
*
|
|
4
|
+
* The writer is the smaller half and the stricter one: it validates what it is given
|
|
5
|
+
* before anything reaches a file, because a malformed asset caught here costs a failed
|
|
6
|
+
* bake, and the same asset caught later costs somebody a night on a device they cannot
|
|
7
|
+
* attach a debugger to. That is not hypothetical — see `validateMeshData`.
|
|
8
|
+
*
|
|
9
|
+
* Runs offline in the baker, so it may allocate freely. Nothing here is on a frame path.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { AnimationClip, DrftSkin } from './animationData.ts';
|
|
13
|
+
import type { DrftNode } from './drftSkin.ts';
|
|
14
|
+
import { buildClip, buildMorph, buildNodes, buildSkin } from './drftSkin.ts';
|
|
15
|
+
import type { MeshData } from './meshData.ts';
|
|
16
|
+
import { validateMeshData } from './meshData.ts';
|
|
17
|
+
import {
|
|
18
|
+
ALIGNMENT,
|
|
19
|
+
ATTR_EMISSIVE_COLOR,
|
|
20
|
+
ATTR_GRAIN,
|
|
21
|
+
ATTR_JOINTS,
|
|
22
|
+
ATTR_RELIEF,
|
|
23
|
+
ATTR_TANGENT,
|
|
24
|
+
ATTR_ROUGHNESS,
|
|
25
|
+
ATTR_SPECULAR,
|
|
26
|
+
ATTR_UVS,
|
|
27
|
+
ATTR_WEIGHTS,
|
|
28
|
+
CHUNK_ENTRY_BYTES,
|
|
29
|
+
CHUNK_HEAD,
|
|
30
|
+
CHUNK_ANIM,
|
|
31
|
+
CHUNK_LODM,
|
|
32
|
+
CHUNK_LODF,
|
|
33
|
+
CHUNK_MORP,
|
|
34
|
+
CHUNK_NODE,
|
|
35
|
+
CHUNK_SKIN,
|
|
36
|
+
CHUNK_SPLT,
|
|
37
|
+
SPLAT_BLOCK_PREFIX,
|
|
38
|
+
CHUNK_MATL,
|
|
39
|
+
CHUNK_MESH,
|
|
40
|
+
CHUNK_REQUIRED,
|
|
41
|
+
CHUNK_COLL,
|
|
42
|
+
CHUNK_SUBS,
|
|
43
|
+
CHUNK_TEXS,
|
|
44
|
+
MATERIAL_ENTRY_BYTES,
|
|
45
|
+
DRFT_MAGIC,
|
|
46
|
+
DRFT_VERSION_MAJOR,
|
|
47
|
+
DRFT_VERSION_MINOR,
|
|
48
|
+
DrftError,
|
|
49
|
+
FLAG_LITTLE_ENDIAN,
|
|
50
|
+
HEADER_BYTES,
|
|
51
|
+
align,
|
|
52
|
+
} from './drftFormat.ts';
|
|
53
|
+
import type { DrftHead, DrftMaterial, DrftSplats } from './drftFormat.ts';
|
|
54
|
+
import { coarseFirstOrder } from './coarseFirst.ts';
|
|
55
|
+
import { type DrftSubstanceEntry, buildSubs } from './drftSubs.ts';
|
|
56
|
+
import { buildColliders } from './drftColliders.ts';
|
|
57
|
+
|
|
58
|
+
/** An image to embed, already compressed, with what its own header said about it. */
|
|
59
|
+
export interface DrftTextureSource {
|
|
60
|
+
/**
|
|
61
|
+
* What the source model called this image, normally its declared relative path.
|
|
62
|
+
*
|
|
63
|
+
* Carried so a texture can be addressed by name instead of by an ordinal. An ordinal is a
|
|
64
|
+
* position in whatever order a reader happened to meet its records, which is not something
|
|
65
|
+
* a consumer should have to know or a format should ask it to depend on.
|
|
66
|
+
*/
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly codec: number;
|
|
69
|
+
readonly width: number;
|
|
70
|
+
readonly height: number;
|
|
71
|
+
readonly bytes: Uint8Array;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** What a caller hands over to be baked. */
|
|
75
|
+
export interface DrftSource {
|
|
76
|
+
readonly head?: Partial<DrftHead>;
|
|
77
|
+
readonly meshes: readonly MeshData[];
|
|
78
|
+
/**
|
|
79
|
+
* One material per mesh, by ordinal, or absent for an asset that names none.
|
|
80
|
+
*
|
|
81
|
+
* All or nothing: a partial list would make material *n* mean a different mesh depending
|
|
82
|
+
* on how many came before it, which is the kind of off-by-one that shows up as one wrong
|
|
83
|
+
* surface in a scene and is looked for everywhere except here.
|
|
84
|
+
*/
|
|
85
|
+
readonly materials?: readonly DrftMaterial[];
|
|
86
|
+
readonly textures?: readonly DrftTextureSource[];
|
|
87
|
+
/**
|
|
88
|
+
* Which substance each material is made of, or absent for a file that labels none.
|
|
89
|
+
*
|
|
90
|
+
* Written as one `SUBS` chunk at 1.8. It pairs by the material ordinal carried per entry rather
|
|
91
|
+
* than by position, so a file labelling only its fourth material says so.
|
|
92
|
+
*/
|
|
93
|
+
readonly substances?: readonly DrftSubstanceEntry[];
|
|
94
|
+
/**
|
|
95
|
+
* The convex hulls this asset collides as, or absent for a file that carries none.
|
|
96
|
+
*
|
|
97
|
+
* Each entry is xyz-packed points in the asset's own space, at most 64 of them, which is what
|
|
98
|
+
* `hullShape` takes. Written as one `COLL` chunk at 1.12. See `drftColliders.ts` for why the
|
|
99
|
+
* format carries points rather than shapes.
|
|
100
|
+
*/
|
|
101
|
+
readonly colliders?: readonly Float32Array[];
|
|
102
|
+
/**
|
|
103
|
+
* Coarse whole-asset levels of detail, **coarsest first**, or absent for a file with none.
|
|
104
|
+
*
|
|
105
|
+
* Each one stands in for the entire model rather than for one part of it, which is the
|
|
106
|
+
* argument docs/FORMAT.md §4.6 makes: a single decimated body reads as a car, and 187 coarse
|
|
107
|
+
* fragments read as a mess. They are written first so a sequential fetch has an outline on
|
|
108
|
+
* screen within the first few kilobytes, and they carry no material entry because they carry
|
|
109
|
+
* their colour per vertex — `MATL` pairs with `MESH` by ordinal and nothing else.
|
|
110
|
+
*/
|
|
111
|
+
readonly lods?: readonly MeshData[];
|
|
112
|
+
/**
|
|
113
|
+
* Discrete levels of detail, **finest first**, each a complete `.drft` of its own.
|
|
114
|
+
*
|
|
115
|
+
* Distinct from `lods` above in every way that matters: those are one merged outline per level
|
|
116
|
+
* for a progressive load, these are whole alternative models a source authored by hand, each
|
|
117
|
+
* with its own materials and hierarchy. See `CHUNK_LODF`.
|
|
118
|
+
*/
|
|
119
|
+
readonly levels?: readonly Uint8Array[];
|
|
120
|
+
/**
|
|
121
|
+
* The asset's own hierarchy, or absent for one mesh at the origin.
|
|
122
|
+
*
|
|
123
|
+
* What `NODE` carries, and what rigid TRS animation drives. A reader that skips the chunk gets
|
|
124
|
+
* the meshes exactly as it always did, which is why this is additive.
|
|
125
|
+
*/
|
|
126
|
+
readonly nodes?: readonly DrftNode[];
|
|
127
|
+
/** Skins, one `SKIN` chunk each, or absent for an asset that deforms nothing. */
|
|
128
|
+
readonly skins?: readonly DrftSkin[];
|
|
129
|
+
/** Clips, one `ANIM` chunk each, or absent for an asset that animates nothing. */
|
|
130
|
+
readonly clips?: readonly AnimationClip[];
|
|
131
|
+
/**
|
|
132
|
+
* A Gaussian splat capture, or absent for a file carrying none.
|
|
133
|
+
*
|
|
134
|
+
* Written as several `SPLT` blocks, interleaved across the whole capture so any prefix of the
|
|
135
|
+
* file is a complete sparse version of it. See `coarseFirst.ts` for the ordering and
|
|
136
|
+
* `CHUNK_SPLT` for why it is several chunks rather than one.
|
|
137
|
+
*/
|
|
138
|
+
readonly splats?: DrftSplats;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The most blocks a capture is split into, and the fewest splats worth having in one.
|
|
143
|
+
*
|
|
144
|
+
* **A ceiling and a floor rather than a fixed count**, because both ends have a real cost. Every
|
|
145
|
+
* block is a sixteen-byte table entry and a forty-byte payload header, so a thousand blocks of
|
|
146
|
+
* two hundred splats is a table nobody wants; one block of a million splats is a capture that
|
|
147
|
+
* arrives all at once and does not stream at all. Sixteen blocks puts the first sixteenth of a
|
|
148
|
+
* capture on screen, which is what `coarseFirst.ts` is built to make worth looking at.
|
|
149
|
+
*
|
|
150
|
+
* **What would make these wrong** is a measurement on a real connection: a capture that reads as
|
|
151
|
+
* finished after two blocks wants fewer and larger, and one that stutters visibly between blocks
|
|
152
|
+
* wants more and smaller. `DrftSplats.blocks` is how a baker that has taken that measurement says
|
|
153
|
+
* so without waiting for these numbers to move.
|
|
154
|
+
*/
|
|
155
|
+
const SPLAT_MAX_BLOCKS = 16;
|
|
156
|
+
const SPLAT_MIN_BLOCK = 4096;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Split a capture into blocks, each a sparse sample of the whole thing.
|
|
160
|
+
*
|
|
161
|
+
* The records are re-ordered on the way out — the file's order is `coarseFirstOrder`'s and not the
|
|
162
|
+
* caller's, which is why nothing here preserves an index. A consumer that needed the original
|
|
163
|
+
* ordering would need it stored, and `CHUNK_SPLT` says so.
|
|
164
|
+
*/
|
|
165
|
+
function buildSplats(splats: DrftSplats): PendingChunk[] {
|
|
166
|
+
const { count, wordsPerSplat } = splats;
|
|
167
|
+
if (count <= 0) return [];
|
|
168
|
+
if (wordsPerSplat <= 0) {
|
|
169
|
+
throw new DrftError(`drft: a splat record is ${wordsPerSplat} words, which stores nothing`);
|
|
170
|
+
}
|
|
171
|
+
if (splats.records.length < count * wordsPerSplat) {
|
|
172
|
+
throw new DrftError(
|
|
173
|
+
`drft: ${count} splats of ${wordsPerSplat} words need ${count * wordsPerSplat} entries and ` +
|
|
174
|
+
`the records array has ${splats.records.length}`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (splats.positions.length < count * 3) {
|
|
178
|
+
throw new DrftError(
|
|
179
|
+
`drft: ${count} splats need ${count * 3} position entries and the array has ` +
|
|
180
|
+
`${splats.positions.length}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const asked = splats.blocks;
|
|
185
|
+
const blocks =
|
|
186
|
+
asked !== undefined && asked > 0
|
|
187
|
+
? Math.min(asked, count)
|
|
188
|
+
: Math.max(1, Math.min(SPLAT_MAX_BLOCKS, Math.ceil(count / SPLAT_MIN_BLOCK)));
|
|
189
|
+
const order = coarseFirstOrder(splats.positions, count);
|
|
190
|
+
/* Ceiling, so the last block is the short one and every earlier one is full. A reader takes
|
|
191
|
+
each block's own count from its header rather than deriving it, so a short block is ordinary. */
|
|
192
|
+
const perBlock = Math.ceil(count / blocks);
|
|
193
|
+
|
|
194
|
+
const out: PendingChunk[] = [];
|
|
195
|
+
for (let block = 0; block < blocks; block++) {
|
|
196
|
+
const from = block * perBlock;
|
|
197
|
+
if (from >= count) break;
|
|
198
|
+
const howMany = Math.min(perBlock, count - from);
|
|
199
|
+
const bytes = new Uint8Array(SPLAT_BLOCK_PREFIX + howMany * wordsPerSplat * 4);
|
|
200
|
+
const view = new DataView(bytes.buffer);
|
|
201
|
+
view.setUint32(0, howMany, true);
|
|
202
|
+
view.setUint32(4, count, true);
|
|
203
|
+
view.setUint32(8, wordsPerSplat, true);
|
|
204
|
+
view.setUint32(12, splats.sphericalHarmonics ?? 0, true);
|
|
205
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
206
|
+
view.setFloat32(16 + axis * 4, splats.boundsMin[axis] ?? 0, true);
|
|
207
|
+
view.setFloat32(28 + axis * 4, splats.boundsMax[axis] ?? 0, true);
|
|
208
|
+
}
|
|
209
|
+
const records = new Uint32Array(bytes.buffer, SPLAT_BLOCK_PREFIX, howMany * wordsPerSplat);
|
|
210
|
+
for (let slot = 0; slot < howMany; slot++) {
|
|
211
|
+
const splat = order[from + slot] ?? 0;
|
|
212
|
+
records.set(
|
|
213
|
+
splats.records.subarray(splat * wordsPerSplat, (splat + 1) * wordsPerSplat),
|
|
214
|
+
slot * wordsPerSplat,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
/* Optional, so a reader that predates 1.5 skips every one of these in silence. */
|
|
218
|
+
out.push({ code: CHUNK_SPLT, flags: 0, bytes });
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** A chunk built in memory, before the table is laid out. */
|
|
224
|
+
interface PendingChunk {
|
|
225
|
+
readonly code: number;
|
|
226
|
+
readonly flags: number;
|
|
227
|
+
readonly bytes: Uint8Array;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const encoder = new TextEncoder();
|
|
231
|
+
|
|
232
|
+
/** A length-prefixed UTF-8 string, padded so what follows it stays aligned. */
|
|
233
|
+
function encodeString(value: string): Uint8Array {
|
|
234
|
+
const text = encoder.encode(value);
|
|
235
|
+
const bytes = new Uint8Array(align(4 + text.length));
|
|
236
|
+
new DataView(bytes.buffer).setUint32(0, text.length, true);
|
|
237
|
+
bytes.set(text, 4);
|
|
238
|
+
return bytes;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function buildHead(head: Partial<DrftHead> | undefined, meshes: readonly MeshData[]): PendingChunk {
|
|
242
|
+
/*
|
|
243
|
+
* Bounds are computed rather than taken on trust. A caller that gets them wrong would
|
|
244
|
+
* produce an asset that culls itself out of frame, which looks like a rendering bug and
|
|
245
|
+
* is not one — and the writer is holding every vertex anyway.
|
|
246
|
+
*/
|
|
247
|
+
let minX = Infinity;
|
|
248
|
+
let minY = Infinity;
|
|
249
|
+
let minZ = Infinity;
|
|
250
|
+
let maxX = -Infinity;
|
|
251
|
+
let maxY = -Infinity;
|
|
252
|
+
let maxZ = -Infinity;
|
|
253
|
+
for (const mesh of meshes) {
|
|
254
|
+
for (let at = 0; at + 2 < mesh.positions.length; at += 3) {
|
|
255
|
+
const x = mesh.positions[at] as number;
|
|
256
|
+
const y = mesh.positions[at + 1] as number;
|
|
257
|
+
const z = mesh.positions[at + 2] as number;
|
|
258
|
+
if (x < minX) minX = x;
|
|
259
|
+
if (y < minY) minY = y;
|
|
260
|
+
if (z < minZ) minZ = z;
|
|
261
|
+
if (x > maxX) maxX = x;
|
|
262
|
+
if (y > maxY) maxY = y;
|
|
263
|
+
if (z > maxZ) maxZ = z;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (!Number.isFinite(minX)) {
|
|
267
|
+
minX = 0;
|
|
268
|
+
minY = 0;
|
|
269
|
+
minZ = 0;
|
|
270
|
+
maxX = 0;
|
|
271
|
+
maxY = 0;
|
|
272
|
+
maxZ = 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const name = encodeString(head?.name ?? '');
|
|
276
|
+
const generator = encodeString(head?.generator ?? 'drft');
|
|
277
|
+
const numbers = new Float32Array([head?.unitScale ?? 1, minX, minY, minZ, maxX, maxY, maxZ]);
|
|
278
|
+
const bytes = new Uint8Array(numbers.byteLength + name.length + generator.length);
|
|
279
|
+
bytes.set(new Uint8Array(numbers.buffer), 0);
|
|
280
|
+
bytes.set(name, numbers.byteLength);
|
|
281
|
+
bytes.set(generator, numbers.byteLength + name.length);
|
|
282
|
+
return { code: CHUNK_HEAD, flags: CHUNK_REQUIRED, bytes };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* One mesh payload, under whichever FourCC is carrying it.
|
|
287
|
+
*
|
|
288
|
+
* `MESH` and `LODM` are the same bytes on purpose: a coarse level *is* a mesh, and giving it
|
|
289
|
+
* its own layout would be a second thing to keep in step with the frozen attribute order for
|
|
290
|
+
* no gain. What differs is the code and the flag — a level of detail is optional, so a reader
|
|
291
|
+
* that has never heard of one skips it and draws the model, per rule 2 of §4.4.
|
|
292
|
+
*/
|
|
293
|
+
function buildMesh(mesh: MeshData, code = CHUNK_MESH, flags = CHUNK_REQUIRED): PendingChunk {
|
|
294
|
+
/*
|
|
295
|
+
* The check that this whole format exists downstream of. An attribute array not
|
|
296
|
+
* covering every vertex is drawn by some drivers and causes others to drop the draw
|
|
297
|
+
* outright, with no GL error on either path — so it is caught here, where the message
|
|
298
|
+
* can name the array, rather than on somebody's phone where it cannot.
|
|
299
|
+
*/
|
|
300
|
+
validateMeshData(mesh);
|
|
301
|
+
|
|
302
|
+
const vertices = mesh.positions.length / 3;
|
|
303
|
+
let attributes = 0;
|
|
304
|
+
if (mesh.specular !== undefined) attributes |= ATTR_SPECULAR;
|
|
305
|
+
if (mesh.uvs !== undefined) attributes |= ATTR_UVS;
|
|
306
|
+
if (mesh.emissiveColor !== undefined) attributes |= ATTR_EMISSIVE_COLOR;
|
|
307
|
+
if (mesh.roughness !== undefined) attributes |= ATTR_ROUGHNESS;
|
|
308
|
+
if (mesh.grain !== undefined) attributes |= ATTR_GRAIN;
|
|
309
|
+
if (mesh.relief !== undefined) attributes |= ATTR_RELIEF;
|
|
310
|
+
if (mesh.tangents !== undefined) attributes |= ATTR_TANGENT;
|
|
311
|
+
/* Both or neither: `validateMeshData` above has already refused one without the other, so
|
|
312
|
+
these two bits are always set together and the reader may rely on it. */
|
|
313
|
+
if (mesh.joints !== undefined) attributes |= ATTR_JOINTS;
|
|
314
|
+
if (mesh.weights !== undefined) attributes |= ATTR_WEIGHTS;
|
|
315
|
+
|
|
316
|
+
/* Order is frozen: mandatory arrays, then optional ones by ascending bit. */
|
|
317
|
+
const arrays: (Float32Array | Uint32Array)[] = [
|
|
318
|
+
mesh.positions,
|
|
319
|
+
mesh.normals,
|
|
320
|
+
mesh.colors,
|
|
321
|
+
mesh.emissive,
|
|
322
|
+
];
|
|
323
|
+
if (mesh.specular !== undefined) arrays.push(mesh.specular);
|
|
324
|
+
if (mesh.uvs !== undefined) arrays.push(mesh.uvs);
|
|
325
|
+
if (mesh.emissiveColor !== undefined) arrays.push(mesh.emissiveColor);
|
|
326
|
+
if (mesh.roughness !== undefined) arrays.push(mesh.roughness);
|
|
327
|
+
if (mesh.grain !== undefined) arrays.push(mesh.grain);
|
|
328
|
+
if (mesh.relief !== undefined) arrays.push(mesh.relief);
|
|
329
|
+
if (mesh.tangents !== undefined) arrays.push(mesh.tangents);
|
|
330
|
+
/* Last, because the order is frozen and these are the newest bits. See ATTR_JOINTS: the
|
|
331
|
+
container validated this pair for four minor versions while writing neither of them. */
|
|
332
|
+
if (mesh.joints !== undefined) arrays.push(mesh.joints);
|
|
333
|
+
if (mesh.weights !== undefined) arrays.push(mesh.weights);
|
|
334
|
+
arrays.push(mesh.indices);
|
|
335
|
+
|
|
336
|
+
const PREFIX = 16;
|
|
337
|
+
let total = PREFIX;
|
|
338
|
+
for (const array of arrays) total += array.byteLength;
|
|
339
|
+
|
|
340
|
+
const bytes = new Uint8Array(total);
|
|
341
|
+
const view = new DataView(bytes.buffer);
|
|
342
|
+
view.setUint32(0, vertices, true);
|
|
343
|
+
view.setUint32(4, mesh.indices.length, true);
|
|
344
|
+
view.setUint32(8, attributes, true);
|
|
345
|
+
view.setUint32(12, 0, true);
|
|
346
|
+
|
|
347
|
+
let at = PREFIX;
|
|
348
|
+
for (const array of arrays) {
|
|
349
|
+
bytes.set(new Uint8Array(array.buffer, array.byteOffset, array.byteLength), at);
|
|
350
|
+
at += array.byteLength;
|
|
351
|
+
}
|
|
352
|
+
return { code, flags, bytes };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* One embedded image: its codec and size, then the compressed bytes untouched.
|
|
357
|
+
*
|
|
358
|
+
* The payload is not re-encoded. A JPEG the artist exported is already the size it is going
|
|
359
|
+
* to be, and decoding it here to compress it again would cost quality for nothing.
|
|
360
|
+
*/
|
|
361
|
+
function buildTexture(texture: DrftTextureSource): PendingChunk {
|
|
362
|
+
const PREFIX = 16;
|
|
363
|
+
/* The name follows the payload rather than preceding it, so the image still starts at a
|
|
364
|
+
fixed offset and a reader that predates the name simply stops before it. */
|
|
365
|
+
const name = encodeString(texture.name);
|
|
366
|
+
const nameAt = align(PREFIX + texture.bytes.length);
|
|
367
|
+
const bytes = new Uint8Array(nameAt + name.length);
|
|
368
|
+
const view = new DataView(bytes.buffer);
|
|
369
|
+
view.setUint32(0, texture.codec, true);
|
|
370
|
+
view.setUint32(4, texture.width, true);
|
|
371
|
+
view.setUint32(8, texture.height, true);
|
|
372
|
+
view.setUint32(12, texture.bytes.length, true);
|
|
373
|
+
bytes.set(texture.bytes, PREFIX);
|
|
374
|
+
bytes.set(name, nameAt);
|
|
375
|
+
return { code: CHUNK_TEXS, flags: 0, bytes };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Every material in one chunk: they are small, and the loader wants them all before it uploads.
|
|
380
|
+
*
|
|
381
|
+
* **The entry stride is written down rather than implied**, which is what makes a new material
|
|
382
|
+
* field additive instead of a break. A reader takes the fields it knows and steps by the
|
|
383
|
+
* stride it was told, so a file carrying an eleventh float opens in a reader that knows ten.
|
|
384
|
+
* Without it, appending one field changes the meaning of every byte after the first entry,
|
|
385
|
+
* which is precisely what rule 4 of §4.4 forbids, and the failure is silent: every material
|
|
386
|
+
* after the first would describe the wrong surface.
|
|
387
|
+
*
|
|
388
|
+
* Names follow the entries as a length-prefixed block, because they are the one variable
|
|
389
|
+
* length thing here and putting them inline would cost the fixed stride that buys the above.
|
|
390
|
+
*/
|
|
391
|
+
function buildMaterials(materials: readonly DrftMaterial[]): PendingChunk {
|
|
392
|
+
const names = materials.map((material) => encodeString(material.name));
|
|
393
|
+
let nameBytes = 0;
|
|
394
|
+
for (const name of names) nameBytes += name.length;
|
|
395
|
+
|
|
396
|
+
const bytes = new Uint8Array(align(8 + materials.length * MATERIAL_ENTRY_BYTES + nameBytes));
|
|
397
|
+
const view = new DataView(bytes.buffer);
|
|
398
|
+
view.setUint32(0, materials.length, true);
|
|
399
|
+
view.setUint32(4, MATERIAL_ENTRY_BYTES, true);
|
|
400
|
+
let namesAt = 8 + materials.length * MATERIAL_ENTRY_BYTES;
|
|
401
|
+
for (const name of names) {
|
|
402
|
+
bytes.set(name, namesAt);
|
|
403
|
+
namesAt += name.length;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let at = 8;
|
|
407
|
+
for (const material of materials) {
|
|
408
|
+
view.setFloat32(at, material.color[0], true);
|
|
409
|
+
view.setFloat32(at + 4, material.color[1], true);
|
|
410
|
+
view.setFloat32(at + 8, material.color[2], true);
|
|
411
|
+
view.setFloat32(at + 12, material.specular, true);
|
|
412
|
+
view.setFloat32(at + 16, material.roughness, true);
|
|
413
|
+
view.setFloat32(at + 20, material.emissive, true);
|
|
414
|
+
view.setFloat32(at + 24, material.emissiveColor[0], true);
|
|
415
|
+
view.setFloat32(at + 28, material.emissiveColor[1], true);
|
|
416
|
+
view.setFloat32(at + 32, material.emissiveColor[2], true);
|
|
417
|
+
view.setFloat32(at + 36, material.opacity, true);
|
|
418
|
+
view.setInt32(at + 40, material.albedo, true);
|
|
419
|
+
view.setFloat32(at + 44, material.reflectivity, true);
|
|
420
|
+
view.setInt32(at + 48, material.normalMap, true);
|
|
421
|
+
view.setInt32(at + 52, material.ormMap, true);
|
|
422
|
+
view.setInt32(at + 56, material.emissiveMap, true);
|
|
423
|
+
view.setFloat32(at + 60, material.roughnessScale, true);
|
|
424
|
+
view.setFloat32(at + 64, material.metallicScale, true);
|
|
425
|
+
view.setFloat32(at + 68, material.occlusionStrength, true);
|
|
426
|
+
view.setFloat32(at + 72, material.cutout, true);
|
|
427
|
+
at += MATERIAL_ENTRY_BYTES;
|
|
428
|
+
}
|
|
429
|
+
return { code: CHUNK_MATL, flags: 0, bytes };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Bake a `.drft` into one `ArrayBuffer`.
|
|
434
|
+
*
|
|
435
|
+
* Every payload lands 4-byte aligned, which is what lets the reader view the vertex data
|
|
436
|
+
* in place rather than copying it out.
|
|
437
|
+
*/
|
|
438
|
+
export function writeDrft(source: DrftSource): ArrayBuffer {
|
|
439
|
+
/*
|
|
440
|
+
* **Geometry *or* a capture, since 1.5.** This said "at least one mesh" while a mesh was the
|
|
441
|
+
* only thing an asset could be made of; a file holding one Gaussian splat capture is an
|
|
442
|
+
* ordinary asset and the reader accepts it, so refusing to write one would be the two halves of
|
|
443
|
+
* the format disagreeing about what a file may contain.
|
|
444
|
+
*/
|
|
445
|
+
if (source.meshes.length === 0 && (source.splats === undefined || source.splats.count === 0)) {
|
|
446
|
+
throw new DrftError('an asset must contain at least one mesh or a capture with splats in it');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const materials = source.materials;
|
|
450
|
+
if (materials !== undefined && materials.length !== source.meshes.length) {
|
|
451
|
+
throw new DrftError(
|
|
452
|
+
`${materials.length} materials for ${source.meshes.length} meshes; they pair by ordinal, so the counts must match`,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/*
|
|
457
|
+
* **Laid out in the order a viewer needs it, which is docs/FORMAT.md §4.6 and costs nothing.**
|
|
458
|
+
*
|
|
459
|
+
* Offsets are absolute, so where a payload sits is a bake-time choice no reader pays for, and
|
|
460
|
+
* this order is what makes a *plain sequential fetch* refine the model as bytes arrive: no
|
|
461
|
+
* range requests, no server support beyond serving a file. See `drftStream.ts`.
|
|
462
|
+
*
|
|
463
|
+
* `MATL` before the geometry is the part worth explaining. It is about 8 KB for a 187-mesh
|
|
464
|
+
* car against 74 MB of file, and it is what turns arriving parts from grey shapes into a
|
|
465
|
+
* correctly painted model. Paint costs almost nothing and buys the largest single jump in how
|
|
466
|
+
* finished the thing looks, so it goes early; textures are megabytes and buy the smallest, so
|
|
467
|
+
* they go last.
|
|
468
|
+
*
|
|
469
|
+
* `MATL` is optional and therefore skipped in silence by a reader that predates it, per rule 2
|
|
470
|
+
* in §4.4: an older reader draws the asset with its vertex colours and no maps, which is the
|
|
471
|
+
* same asset less its textures rather than a refusal. Moving it earlier in the file changes
|
|
472
|
+
* nothing about that, because a chunk is found through the table rather than by position.
|
|
473
|
+
*/
|
|
474
|
+
const chunks: PendingChunk[] = [buildHead(source.head, source.meshes)];
|
|
475
|
+
/*
|
|
476
|
+
* The outline first, ahead of even the paint. It is the one thing that puts a recognisable
|
|
477
|
+
* whole object on screen, it is a few hundred kilobytes against tens of megabytes, and the
|
|
478
|
+
* order it is written in is the order a plain sequential fetch delivers it.
|
|
479
|
+
*/
|
|
480
|
+
for (const lod of source.lods ?? []) chunks.push(buildMesh(lod, CHUNK_LODM, 0));
|
|
481
|
+
/*
|
|
482
|
+
* Discrete levels, finest first — the opposite of `lods` above, and deliberately. `LODM` is
|
|
483
|
+
* coarsest first because it serves a progressive load, where drawing *something* early is the
|
|
484
|
+
* point; a discrete chain serves distance selection, where level 0 should be the full-detail
|
|
485
|
+
* model the consumer already holds.
|
|
486
|
+
*/
|
|
487
|
+
for (const level of source.levels ?? [])
|
|
488
|
+
chunks.push({ code: CHUNK_LODF, flags: 0, bytes: level });
|
|
489
|
+
if (materials !== undefined && materials.length > 0) chunks.push(buildMaterials(materials));
|
|
490
|
+
/*
|
|
491
|
+
* After the paint and before the parts. Paint is a few kilobytes and turns everything after it
|
|
492
|
+
* from grey into a finished surface; a capture is the place the parts stand in, and its first
|
|
493
|
+
* block is a recognisable whole. Both come before the meshes for the same reason the outline
|
|
494
|
+
* does: what puts something worth looking at on screen goes early.
|
|
495
|
+
*/
|
|
496
|
+
if (source.splats !== undefined) chunks.push(...buildSplats(source.splats));
|
|
497
|
+
/*
|
|
498
|
+
* The rig before the geometry it deforms. A streaming consumer that has the skeleton and the
|
|
499
|
+
* clips can start a character the moment the first mesh lands, where the reverse order makes it
|
|
500
|
+
* wait for both; and all three are small beside the parts, so nothing is delayed by much.
|
|
501
|
+
*/
|
|
502
|
+
for (const node of source.nodes === undefined ? [] : [source.nodes]) {
|
|
503
|
+
chunks.push({ code: CHUNK_NODE, flags: 0, bytes: buildNodes(node) });
|
|
504
|
+
}
|
|
505
|
+
for (const skin of source.skins ?? []) {
|
|
506
|
+
chunks.push({ code: CHUNK_SKIN, flags: 0, bytes: buildSkin(skin) });
|
|
507
|
+
}
|
|
508
|
+
for (const clip of source.clips ?? []) {
|
|
509
|
+
chunks.push({ code: CHUNK_ANIM, flags: 0, bytes: buildClip(clip) });
|
|
510
|
+
}
|
|
511
|
+
source.meshes.forEach((mesh, ordinal) => {
|
|
512
|
+
/*
|
|
513
|
+
* **Its deltas immediately before it, not after.** A streaming consumer uploads a mesh the
|
|
514
|
+
* moment its chunk lands, so deltas arriving afterwards are deltas for geometry already on the
|
|
515
|
+
* GPU — and a stream cannot rewrite a mesh it has handed over. Written first, the consumer has
|
|
516
|
+
* them in hand when it builds. The mesh ordinal in the payload still does the pairing, so a
|
|
517
|
+
* reader meeting them in another order is unaffected.
|
|
518
|
+
*/
|
|
519
|
+
if (mesh.morphTargets !== undefined && mesh.morphTargetCount !== undefined) {
|
|
520
|
+
chunks.push({
|
|
521
|
+
code: CHUNK_MORP,
|
|
522
|
+
flags: 0,
|
|
523
|
+
bytes: buildMorph({
|
|
524
|
+
mesh: ordinal,
|
|
525
|
+
targetCount: mesh.morphTargetCount,
|
|
526
|
+
deltas: mesh.morphTargets,
|
|
527
|
+
}),
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
chunks.push(buildMesh(mesh));
|
|
531
|
+
});
|
|
532
|
+
for (const texture of source.textures ?? []) chunks.push(buildTexture(texture));
|
|
533
|
+
/* One `SUBS` for the file, and only where something was labelled — an empty chunk would cost
|
|
534
|
+
sixteen bytes to say nothing, and rule 2 already covers its absence. */
|
|
535
|
+
if ((source.substances?.length ?? 0) > 0) {
|
|
536
|
+
chunks.push({
|
|
537
|
+
code: CHUNK_SUBS,
|
|
538
|
+
flags: 0,
|
|
539
|
+
bytes: buildSubs({ entries: source.substances as readonly DrftSubstanceEntry[] }),
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/*
|
|
544
|
+
* `COLL` last among the small chunks, because collision is the one thing a viewer never needs and
|
|
545
|
+
* a consumer that simulates has already waited for the whole file by the time it builds a world.
|
|
546
|
+
*/
|
|
547
|
+
if ((source.colliders?.length ?? 0) > 0) {
|
|
548
|
+
chunks.push({
|
|
549
|
+
code: CHUNK_COLL,
|
|
550
|
+
flags: 0,
|
|
551
|
+
bytes: buildColliders(source.colliders as readonly Float32Array[]),
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const tableBytes = chunks.length * CHUNK_ENTRY_BYTES;
|
|
556
|
+
let cursor = align(HEADER_BYTES + tableBytes);
|
|
557
|
+
const offsets: number[] = [];
|
|
558
|
+
for (const chunk of chunks) {
|
|
559
|
+
offsets.push(cursor);
|
|
560
|
+
cursor = align(cursor + chunk.bytes.length);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const total = cursor;
|
|
564
|
+
const buffer = new ArrayBuffer(total);
|
|
565
|
+
const bytes = new Uint8Array(buffer);
|
|
566
|
+
const view = new DataView(buffer);
|
|
567
|
+
|
|
568
|
+
view.setUint32(0, DRFT_MAGIC, true);
|
|
569
|
+
view.setUint16(4, DRFT_VERSION_MAJOR, true);
|
|
570
|
+
view.setUint16(6, DRFT_VERSION_MINOR, true);
|
|
571
|
+
/*
|
|
572
|
+
* The oldest reader that may open this. Equal to the current major because nothing here
|
|
573
|
+
* needs a newer one — a writer raises it only when it emits something a previous
|
|
574
|
+
* generation would misread, which is exactly what a major bump means.
|
|
575
|
+
*/
|
|
576
|
+
view.setUint16(8, DRFT_VERSION_MAJOR, true);
|
|
577
|
+
view.setUint16(10, FLAG_LITTLE_ENDIAN, true);
|
|
578
|
+
view.setUint32(12, chunks.length, true);
|
|
579
|
+
view.setUint32(16, total, true);
|
|
580
|
+
/* Bytes 20..31 stay zero: reserved, and readers must ignore them so they can become
|
|
581
|
+
fields later without a major bump. */
|
|
582
|
+
|
|
583
|
+
/*
|
|
584
|
+
* `index` is the ordinal *within a FourCC*, not the chunk's position in the table.
|
|
585
|
+
*
|
|
586
|
+
* It used to be written as the table position, which happened to be right for `MESH`
|
|
587
|
+
* because the meshes come first, and was wrong for everything after them: the six `TEXS`
|
|
588
|
+
* chunks in a 195 chunk file were numbered 188 to 193. Nothing here noticed, because this
|
|
589
|
+
* reader takes textures in the order it meets them rather than by the number they carry.
|
|
590
|
+
* A second implementation reading §4.3 would have used the number, and indexed six
|
|
591
|
+
* textures at 188.
|
|
592
|
+
*/
|
|
593
|
+
const ordinals = new Map<number, number>();
|
|
594
|
+
let entry = HEADER_BYTES;
|
|
595
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
596
|
+
const chunk = chunks[i] as PendingChunk;
|
|
597
|
+
const offset = offsets[i] as number;
|
|
598
|
+
const ordinal = ordinals.get(chunk.code) ?? 0;
|
|
599
|
+
ordinals.set(chunk.code, ordinal + 1);
|
|
600
|
+
view.setUint32(entry, chunk.code, true);
|
|
601
|
+
view.setUint32(entry + 4, offset, true);
|
|
602
|
+
view.setUint32(entry + 8, chunk.bytes.length, true);
|
|
603
|
+
view.setUint16(entry + 12, chunk.flags, true);
|
|
604
|
+
view.setUint16(entry + 14, ordinal, true);
|
|
605
|
+
entry += CHUNK_ENTRY_BYTES;
|
|
606
|
+
bytes.set(chunk.bytes, offset);
|
|
607
|
+
if (offset % ALIGNMENT !== 0) {
|
|
608
|
+
throw new DrftError(`internal: chunk ${i} landed unaligned at ${offset}`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return buffer;
|
|
613
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `.drft` written by version 1.0, kept byte for byte.
|
|
3
|
+
*
|
|
4
|
+
* **Never regenerate this file.** It exists so today's reader can be tested against
|
|
5
|
+
* genuinely old bytes; rewriting it with a newer writer would only prove that the writer
|
|
6
|
+
* and the reader agree, which they always do, and would silently retire the guarantee it
|
|
7
|
+
* is here to defend.
|
|
8
|
+
*/
|
|
9
|
+
export const FIXTURE_V1_0 =
|
|
10
|
+
'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ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+ZmZmP83MzD3NzEw+AACAPgAAgD4AAIA+AACAPgAAgD4AAIA+AACAPgAAgD4AAIA+AACAPgAAgD4AAIA+AACAPgAAgD4AAIA+AACAPgAAgD4AAIA+AACAPgAAgD4AAIA+AACAPgAAgD4AAIA+zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPc3MzD3NzMw9zczMPs3MzD7NzMw+zczMPs3MzD7NzMw+zczMPs3MzD7NzMw+zczMPs3MzD7NzMw+zczMPs3MzD7NzMw+zczMPs3MzD7NzMw+zczMPs3MzD7NzMw+zczMPs3MzD7NzMw+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/zcxMPs3MzD5mZmY/mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+mpmZPpqZmT6amZk+AAAAAAEAAAACAAAAAAAAAAIAAAADAAAABAAAAAUAAAAGAAAABAAAAAYAAAAHAAAACAAAAAkAAAAKAAAACAAAAAoAAAALAAAADAAAAA0AAAAOAAAADAAAAA4AAAAPAAAAEAAAABEAAAASAAAAEAAAABIAAAATAAAAFAAAABUAAAAWAAAAFAAAABYAAAAXAAAAGAAAACMAAAAZAAAAGQAAACMAAAAkAAAAGQAAACQAAAAaAAAAGgAAACQAAAAlAAAAGgAAACUAAAAbAAAAGwAAACUAAAAmAAAAGwAAACYAAAAcAAAAHAAAACYAAAAnAAAAHAAAACcAAAAdAAAAHQAAACcAAAAoAAAAHQAAACgAAAAeAAAAHgAAACgAAAApAAAAHgAAACkAAAAfAAAAHwAAACkAAAAqAAAAHwAAACoAAAAgAAAAIAAAACoAAAArAAAAIAAAACsAAAAhAAAAIQAAACsAAAAsAAAAIQAAACwAAAAiAAAAIgAAACwAAAAtAAAAIwAAAC4AAAAkAAAAJAAAAC4AAAAvAAAAJAAAAC8AAAAlAAAAJQAAAC8AAAAwAAAAJQAAADAAAAAmAAAAJgAAADAAAAAxAAAAJgAAADEAAAAnAAAAJwAAADEAAAAyAAAAJwAAADIAAAAoAAAAKAAAADIAAAAzAAAAKAAAADMAAAApAAAAKQAAADMAAAA0AAAAKQAAADQAAAAqAAAAKgAAADQAAAA1AAAAKgAAADUAAAArAAAAKwAAADUAAAA2AAAAKwAAADYAAAAsAAAALAAAADYAAAA3AAAALAAAADcAAAAtAAAALQAAADcAAAA4AAAALgAAADkAAAAvAAAALwAAADkAAAA6AAAALwAAADoAAAAwAAAAMAAAADoAAAA7AAAAMAAAADsAAAAxAAAAMQAAADsAAAA8AAAAMQAAADwAAAAyAAAAMgAAADwAAAA9AAAAMgAAAD0AAAAzAAAAMwAAAD0AAAA+AAAAMwAAAD4AAAA0AAAANAAAAD4AAAA/AAAANAAAAD8AAAA1AAAANQAAAD8AAABAAAAANQAAAEAAAAA2AAAANgAAAEAAAABBAAAANgAAAEEAAAA3AAAANwAAAEEAAABCAAAANwAAAEIAAAA4AAAAOAAAAEIAAABDAAAAOQAAAEQAAAA6AAAAOgAAAEQAAABFAAAAOgAAAEUAAAA7AAAAOwAAAEUAAABGAAAAOwAAAEYAAAA8AAAAPAAAAEYAAABHAAAAPAAAAEcAAAA9AAAAPQAAAEcAAABIAAAAPQAAAEgAAAA+AAAAPgAAAEgAAABJAAAAPgAAAEkAAAA/AAAAPwAAAEkAAABKAAAAPwAAAEoAAABAAAAAQAAAAEoAAABLAAAAQAAAAEsAAABBAAAAQQAAAEsAAABMAAAAQQAAAEwAAABCAAAAQgAAAEwAAABNAAAAQgAAAE0AAABDAAAAQwAAAE0AAABOAAAARAAAAE8AAABFAAAARQAAAE8AAABQAAAARQAAAFAAAABGAAAARgAAAFAAAABRAAAARgAAAFEAAABHAAAARwAAAFEAAABSAAAARwAAAFIAAABIAAAASAAAAFIAAABTAAAASAAAAFMAAABJAAAASQAAAFMAAABUAAAASQAAAFQAAABKAAAASgAAAFQAAABVAAAASgAAAFUAAABLAAAASwAAAFUAAABWAAAASwAAAFYAAABMAAAATAAAAFYAAABXAAAATAAAAFcAAABNAAAATQAAAFcAAABYAAAATQAAAFgAAABOAAAATgAAAFgAAABZAAAATwAAAFoAAABQAAAAUAAAAFoAAABbAAAAUAAAAFsAAABRAAAAUQAAAFsAAABcAAAAUQAAAFwAAABSAAAAUgAAAFwAAABdAAAAUgAAAF0AAABTAAAAUwAAAF0AAABeAAAAUwAAAF4AAABUAAAAVAAAAF4AAABfAAAAVAAAAF8AAABVAAAAVQAAAF8AAABgAAAAVQAAAGAAAABWAAAAVgAAAGAAAABhAAAAVgAAAGEAAABXAAAAVwAAAGEAAABiAAAAVwAAAGIAAABYAAAAWAAAAGIAAABjAAAAWAAAAGMAAABZAAAAWQAAAGMAAABkAAAAWgAAAGUAAABbAAAAWwAAAGUAAABmAAAAWwAAAGYAAABcAAAAXAAAAGYAAABnAAAAXAAAAGcAAABdAAAAXQAAAGcAAABoAAAAXQAAAGgAAABeAAAAXgAAAGgAAABpAAAAXgAAAGkAAABfAAAAXwAAAGkAAABqAAAAXwAAAGoAAABgAAAAYAAAAGoAAABrAAAAYAAAAGsAAABhAAAAYQAAAGsAAABsAAAAYQAAAGwAAABiAAAAYgAAAGwAAABtAAAAYgAAAG0AAABjAAAAYwAAAG0AAABuAAAAYwAAAG4AAABkAAAAZAAAAG4AAABvAAAAZQAAAHAAAABmAAAAZgAAAHAAAABxAAAAZgAAAHEAAABnAAAAZwAAAHEAAAByAAAAZwAAAHIAAABoAAAAaAAAAHIAAABzAAAAaAAAAHMAAABpAAAAaQAAAHMAAAB0AAAAaQAAAHQAAABqAAAAagAAAHQAAAB1AAAAagAAAHUAAABrAAAAawAAAHUAAAB2AAAAawAAAHYAAABsAAAAbAAAAHYAAAB3AAAAbAAAAHcAAABtAAAAbQAAAHcAAAB4AAAAbQAAAHgAAABuAAAAbgAAAHgAAAB5AAAAbgAAAHkAAABvAAAAbwAAAHkAAAB6AAAAcAAAAHsAAABxAAAAcQAAAHsAAAB8AAAAcQAAAHwAAAByAAAAcgAAAHwAAAB9AAAAcgAAAH0AAABzAAAAcwAAAH0AAAB+AAAAcwAAAH4AAAB0AAAAdAAAAH4AAAB/AAAAdAAAAH8AAAB1AAAAdQAAAH8AAACAAAAAdQAAAIAAAAB2AAAAdgAAAIAAAACBAAAAdgAAAIEAAAB3AAAAdwAAAIEAAACCAAAAdwAAAIIAAAB4AAAAeAAAAIIAAACDAAAAeAAAAIMAAAB5AAAAeQAAAIMAAACEAAAAeQAAAIQAAAB6AAAAegAAAIQAAACFAAAAewAAAIYAAAB8AAAAfAAAAIYAAACHAAAAfAAAAIcAAAB9AAAAfQAAAIcAAACIAAAAfQAAAIgAAAB+AAAAfgAAAIgAAACJAAAAfgAAAIkAAAB/AAAAfwAAAIkAAACKAAAAfwAAAIoAAACAAAAAgAAAAIoAAACLAAAAgAAAAIsAAACBAAAAgQAAAIsAAACMAAAAgQAAAIwAAACCAAAAggAAAIwAAACNAAAAggAAAI0AAACDAAAAgwAAAI0AAACOAAAAgwAAAI4AAACEAAAAhAAAAI4AAACPAAAAhAAAAI8AAACFAAAAhQAAAI8AAACQAAAAhgAAAJEAAACHAAAAhwAAAJEAAACSAAAAhwAAAJIAAACIAAAAiAAAAJIAAACTAAAAiAAAAJMAAACJAAAAiQAAAJMAAACUAAAAiQAAAJQAAACKAAAAigAAAJQAAACVAAAAigAAAJUAAACLAAAAiwAAAJUAAACWAAAAiwAAAJYAAACMAAAAjAAAAJYAAACXAAAAjAAAAJcAAACNAAAAjQAAAJcAAACYAAAAjQAAAJgAAACOAAAAjgAAAJgAAACZAAAAjgAAAJkAAACPAAAAjwAAAJkAAACaAAAAjwAAAJoAAACQAAAAkAAAAJoAAACbAAAAGAAAACQAAAAAAAAAAAAAAAAAGEEAAAC/AAAAvwAAGEEAAAA/AAAAvwAAGEEAAAA/AAAAPwAAGEEAAAC/AAAAPwAACEEAAAC/AAAAPwAACEEAAAA/AAAAPwAACEEAAAA/AAAAvwAACEEAAAC/AAAAvwAACEEAAAA/AAAAvwAACEEAAAA/AAAAPwAAGEEAAAA/AAAAPwAAGEEAAAA/AAAAvwAAGEEAAAC/AAAAvwAAGEEAAAC/AAAAPwAACEEAAAC/AAAAPwAACEEAAAC/AAAAvwAACEEAAAC/AAAAPwAAGEEAAAC/AAAAPwAAGEEAAAA/AAAAPwAACEEAAAA/AAAAPwAACEEAAAA/AAAAvwAAGEEAAAA/AAAAvwAAGEEAAAC/AAAAvwAACEEAAAC/AAAAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAv83MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPs3MzD3NzEw+mpmZPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAgAAAAAAAAACAAAAAwAAAAQAAAAFAAAABgAAAAQAAAAGAAAABwAAAAgAAAAJAAAACgAAAAgAAAAKAAAACwAAAAwAAAANAAAADgAAAAwAAAAOAAAADwAAABAAAAARAAAAEgAAABAAAAASAAAAEwAAABQAAAAVAAAAFgAAABQAAAAWAAAAFwAAAA==';
|