@driftengine/drft 4.8.3 → 4.8.4
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/dist/drftBc.d.ts +23 -0
- package/dist/drftBc.js +116 -0
- package/dist/drftFormat.d.ts +37 -1
- package/dist/drftFormat.js +40 -3
- package/dist/drftQuantised.js +3 -1
- package/dist/drftRead.js +6 -1
- package/dist/drftWrite.js +10 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -1
- package/dist/meshData.d.ts +19 -3
- package/dist/meshData.js +11 -0
- package/package.json +4 -4
- package/src/drftBc.ts +151 -0
- package/src/drftFormat.ts +54 -3
- package/src/drftQuantised.ts +7 -1
- package/src/drftRead.ts +8 -0
- package/src/drftWrite.ts +7 -0
- package/src/index.ts +12 -1
- package/src/meshData.ts +34 -3
package/dist/drftBc.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { BcFormat } from './drftFormat.ts';
|
|
2
|
+
/** A block-compressed image, as a `CODEC_BC` payload holds it. */
|
|
3
|
+
export interface BcImage {
|
|
4
|
+
readonly format: BcFormat;
|
|
5
|
+
/**
|
|
6
|
+
* What the author's header said of its texels. How a material slot samples them is still the
|
|
7
|
+
* renderer's decision, as for every other codec: a colour map is read as sRGB and a normal map as
|
|
8
|
+
* linear whatever its file claimed, so this is the author's statement rather than an instruction.
|
|
9
|
+
*/
|
|
10
|
+
readonly srgb: boolean;
|
|
11
|
+
readonly width: number;
|
|
12
|
+
readonly height: number;
|
|
13
|
+
/** Level 0 first. Views over the payload when read, never copies. */
|
|
14
|
+
readonly levels: readonly Uint8Array[];
|
|
15
|
+
}
|
|
16
|
+
/** How many levels a full chain from `width` by `height` down to 1x1 has. */
|
|
17
|
+
export declare function bcChainLength(width: number, height: number): number;
|
|
18
|
+
/** The bytes level `level` of a `width` by `height` surface takes in `format`. */
|
|
19
|
+
export declare function bcLevelBytes(format: BcFormat, width: number, height: number, level: number): number;
|
|
20
|
+
/** The payload for `image`. Refuses a level whose size is not the one its position requires. */
|
|
21
|
+
export declare function writeBcPayload(image: BcImage): Uint8Array;
|
|
22
|
+
/** A payload read back, its levels as views over `bytes`. */
|
|
23
|
+
export declare function readBcPayload(width: number, height: number, bytes: Uint8Array): BcImage;
|
package/dist/drftBc.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `CODEC_BC` texture payload: which block format, what colour space its author declared, and
|
|
3
|
+
* every stored mip level, largest first.
|
|
4
|
+
*
|
|
5
|
+
* **The layout** (FORMAT.md §4.5, 1.24), little-endian after the `TEXS` prefix that already carries
|
|
6
|
+
* the codec, width and height:
|
|
7
|
+
*
|
|
8
|
+
* u32 format the format's own digit: 1, 2, 3, 4, 5 or 7 for BC1 to BC7
|
|
9
|
+
* u32 flags bit 0: the author declared the texels sRGB
|
|
10
|
+
* u32 levels how many mip levels follow, 1 to the full chain
|
|
11
|
+
* u32 0 reserved
|
|
12
|
+
* ... each level's blocks, back to back, largest first
|
|
13
|
+
*
|
|
14
|
+
* **A level's size is derived, never stored.** Level `i` is `max(1, width >> i)` by
|
|
15
|
+
* `max(1, height >> i)` texels, rounded up to whole 4x4 blocks — so a level smaller than a block
|
|
16
|
+
* still costs one, which is how every DDS writer and every GPU lays them out. Storing the sizes
|
|
17
|
+
* would be a second statement of the same fact, free to disagree with the first.
|
|
18
|
+
*
|
|
19
|
+
* **What it gives up**: a chain is whole levels from the top. A file that wanted to drop its top
|
|
20
|
+
* level to save bytes has to say a smaller width and height, which is also what it then is.
|
|
21
|
+
*
|
|
22
|
+
* Unknown flag bits are ignored, which is rule 4 of the compatibility contract; a format number
|
|
23
|
+
* this reader does not know is refused by name. A signed or HDR format would be a new number rather
|
|
24
|
+
* than a flag, because a decoder that ignored it would produce wrong values rather than none.
|
|
25
|
+
*/
|
|
26
|
+
import { BC_BLOCK_BYTES, DrftError } from './drftFormat.js';
|
|
27
|
+
const HEADER_BYTES = 16;
|
|
28
|
+
const FLAG_SRGB = 1;
|
|
29
|
+
const FORMAT_NUMBER = {
|
|
30
|
+
bc1: 1,
|
|
31
|
+
bc2: 2,
|
|
32
|
+
bc3: 3,
|
|
33
|
+
bc4: 4,
|
|
34
|
+
bc5: 5,
|
|
35
|
+
bc7: 7,
|
|
36
|
+
};
|
|
37
|
+
const FORMAT_OF = {
|
|
38
|
+
1: 'bc1',
|
|
39
|
+
2: 'bc2',
|
|
40
|
+
3: 'bc3',
|
|
41
|
+
4: 'bc4',
|
|
42
|
+
5: 'bc5',
|
|
43
|
+
7: 'bc7',
|
|
44
|
+
};
|
|
45
|
+
/** How many levels a full chain from `width` by `height` down to 1x1 has. */
|
|
46
|
+
export function bcChainLength(width, height) {
|
|
47
|
+
return Math.floor(Math.log2(Math.max(1, width, height))) + 1;
|
|
48
|
+
}
|
|
49
|
+
/** The bytes level `level` of a `width` by `height` surface takes in `format`. */
|
|
50
|
+
export function bcLevelBytes(format, width, height, level) {
|
|
51
|
+
const w = Math.max(1, width >> level);
|
|
52
|
+
const h = Math.max(1, height >> level);
|
|
53
|
+
return Math.ceil(w / 4) * Math.ceil(h / 4) * BC_BLOCK_BYTES[format];
|
|
54
|
+
}
|
|
55
|
+
/** The payload for `image`. Refuses a level whose size is not the one its position requires. */
|
|
56
|
+
export function writeBcPayload(image) {
|
|
57
|
+
const { format, width, height, levels } = image;
|
|
58
|
+
checkChain(levels.length, width, height);
|
|
59
|
+
let total = HEADER_BYTES;
|
|
60
|
+
for (let i = 0; i < levels.length; i++) {
|
|
61
|
+
const want = bcLevelBytes(format, width, height, i);
|
|
62
|
+
const level = levels[i];
|
|
63
|
+
if (level.length !== want) {
|
|
64
|
+
throw new DrftError(`bc: level ${i} of a ${width}x${height} ${format.toUpperCase()} image is ${want} bytes, and ` +
|
|
65
|
+
`${level.length} were given`);
|
|
66
|
+
}
|
|
67
|
+
total += want;
|
|
68
|
+
}
|
|
69
|
+
const out = new Uint8Array(total);
|
|
70
|
+
const view = new DataView(out.buffer);
|
|
71
|
+
view.setUint32(0, FORMAT_NUMBER[format], true);
|
|
72
|
+
view.setUint32(4, image.srgb ? FLAG_SRGB : 0, true);
|
|
73
|
+
view.setUint32(8, levels.length, true);
|
|
74
|
+
let at = HEADER_BYTES;
|
|
75
|
+
for (const level of levels) {
|
|
76
|
+
out.set(level, at);
|
|
77
|
+
at += level.length;
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
/** A payload read back, its levels as views over `bytes`. */
|
|
82
|
+
export function readBcPayload(width, height, bytes) {
|
|
83
|
+
if (bytes.length < HEADER_BYTES) {
|
|
84
|
+
throw new DrftError(`bc: a payload of ${bytes.length} bytes is shorter than its header`);
|
|
85
|
+
}
|
|
86
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
87
|
+
const number = view.getUint32(0, true);
|
|
88
|
+
const format = FORMAT_OF[number];
|
|
89
|
+
if (format === undefined) {
|
|
90
|
+
throw new DrftError(`bc: block format ${number} is not one this reader knows; it reads 1, 2, 3, 4, 5 and 7 ` +
|
|
91
|
+
'(BC1 to BC5 and BC7)');
|
|
92
|
+
}
|
|
93
|
+
const count = view.getUint32(8, true);
|
|
94
|
+
checkChain(count, width, height);
|
|
95
|
+
let needed = HEADER_BYTES;
|
|
96
|
+
for (let i = 0; i < count; i++)
|
|
97
|
+
needed += bcLevelBytes(format, width, height, i);
|
|
98
|
+
if (bytes.length < needed) {
|
|
99
|
+
throw new DrftError(`bc: ${count} levels of a ${width}x${height} ${format.toUpperCase()} image needs ` +
|
|
100
|
+
`${needed} bytes and the payload is ${bytes.length}`);
|
|
101
|
+
}
|
|
102
|
+
const levels = [];
|
|
103
|
+
let at = HEADER_BYTES;
|
|
104
|
+
for (let i = 0; i < count; i++) {
|
|
105
|
+
const size = bcLevelBytes(format, width, height, i);
|
|
106
|
+
levels.push(bytes.subarray(at, at + size));
|
|
107
|
+
at += size;
|
|
108
|
+
}
|
|
109
|
+
return { format, srgb: (view.getUint32(4, true) & FLAG_SRGB) !== 0, width, height, levels };
|
|
110
|
+
}
|
|
111
|
+
function checkChain(count, width, height) {
|
|
112
|
+
const most = bcChainLength(width, height);
|
|
113
|
+
if (width < 1 || height < 1 || count < 1 || count > most) {
|
|
114
|
+
throw new DrftError(`bc: ${count} levels for a ${width}x${height} image, which has at least 1 and at most ${most}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
package/dist/drftFormat.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare const DRFT_VERSION_MAJOR = 1;
|
|
|
20
20
|
* Additive revisions within a generation: new optional chunks, new attribute bits, new
|
|
21
21
|
* enum values with a defined fallback. Never a changed meaning.
|
|
22
22
|
*/
|
|
23
|
-
export declare const DRFT_VERSION_MINOR =
|
|
23
|
+
export declare const DRFT_VERSION_MINOR = 24;
|
|
24
24
|
/** Bytes before the chunk table. */
|
|
25
25
|
export declare const HEADER_BYTES = 32;
|
|
26
26
|
/** Bytes per chunk table entry. */
|
|
@@ -365,6 +365,20 @@ export declare const ATTR_LAYERS: number;
|
|
|
365
365
|
* before this one, and draws the mesh planted, sunlit and opaque — the absent lane's own meaning.
|
|
366
366
|
*/
|
|
367
367
|
export declare const ATTR_CHANNEL: number;
|
|
368
|
+
/**
|
|
369
|
+
* Added in 1.24: a vertex's fifth to eighth influences, which joints and how much. See
|
|
370
|
+
* `MeshData.joints2`.
|
|
371
|
+
*
|
|
372
|
+
* Two bits for two arrays, both-or-neither like the first pair, and neither without the first pair.
|
|
373
|
+
* Appended after the channel, as every attribute since 1.20 has been, and that has a consequence
|
|
374
|
+
* worth stating rather than implying: indices follow the last array, so **a 1.23 reader does not
|
|
375
|
+
* skip these** — it reads their floats where it expects indices, finds indices far past the vertex
|
|
376
|
+
* count, and refuses the mesh by name. A loud refusal rather than a mesh drawn with four of its
|
|
377
|
+
* eight influences, which is the same answer `layers` and the channel give an older reader.
|
|
378
|
+
*/
|
|
379
|
+
export declare const ATTR_JOINTS2: number;
|
|
380
|
+
/** How much each of `ATTR_JOINTS2`' four influences moves the vertex. */
|
|
381
|
+
export declare const ATTR_WEIGHTS2: number;
|
|
368
382
|
/** Everything this version defines, so a reader can spot bits from a later writer. */
|
|
369
383
|
export declare const ATTR_KNOWN: number;
|
|
370
384
|
/**
|
|
@@ -380,6 +394,28 @@ export declare const CODEC_PNG = 1;
|
|
|
380
394
|
export declare const CODEC_JPEG = 2;
|
|
381
395
|
export declare const CODEC_WEBP = 3;
|
|
382
396
|
export declare const CODEC_RAW = 4;
|
|
397
|
+
/**
|
|
398
|
+
* Added in 1.24: block-compressed images, kept as the blocks they were authored in.
|
|
399
|
+
*
|
|
400
|
+
* **Uploaded as they are where the device can sample them**, so a texture authored as BC7 costs a
|
|
401
|
+
* byte a texel on the GPU rather than four, and the mip chain the author built rather than one the
|
|
402
|
+
* engine generates. Where the device cannot — most phones, which sample ASTC and ETC2 instead — the
|
|
403
|
+
* loader decodes them to RGBA, which is the cost every texture paid before this. The payload — block
|
|
404
|
+
* format, colour space and the stored mip chain — is `drftBc.ts`'s.
|
|
405
|
+
*/
|
|
406
|
+
export declare const CODEC_BC = 5;
|
|
407
|
+
/**
|
|
408
|
+
* The block formats a `CODEC_BC` payload may hold: what one 4x4 block of texels encodes.
|
|
409
|
+
*
|
|
410
|
+
* BC1 is colour with punch-through alpha in eight bytes; BC2 and BC3 colour with explicit and
|
|
411
|
+
* interpolated alpha in sixteen; BC4 one channel and BC5 two (a normal map) in eight and sixteen;
|
|
412
|
+
* BC7 colour and alpha in sixteen, with eight modes chosen per block. BC6H, signed BC4/BC5 and the
|
|
413
|
+
* other families are not among them: an HDR or signed texture is a different contract, refused by
|
|
414
|
+
* name rather than mis-decoded.
|
|
415
|
+
*/
|
|
416
|
+
export type BcFormat = 'bc1' | 'bc2' | 'bc3' | 'bc4' | 'bc5' | 'bc7';
|
|
417
|
+
/** Bytes per 4x4 block of each format. */
|
|
418
|
+
export declare const BC_BLOCK_BYTES: Readonly<Record<BcFormat, number>>;
|
|
383
419
|
/** What a codec is called, for a message naming what was not understood. */
|
|
384
420
|
export declare function codecName(codec: number): string;
|
|
385
421
|
/**
|
package/dist/drftFormat.js
CHANGED
|
@@ -20,7 +20,7 @@ export const DRFT_VERSION_MAJOR = 1;
|
|
|
20
20
|
* Additive revisions within a generation: new optional chunks, new attribute bits, new
|
|
21
21
|
* enum values with a defined fallback. Never a changed meaning.
|
|
22
22
|
*/
|
|
23
|
-
export const DRFT_VERSION_MINOR =
|
|
23
|
+
export const DRFT_VERSION_MINOR = 24;
|
|
24
24
|
/** Bytes before the chunk table. */
|
|
25
25
|
export const HEADER_BYTES = 32;
|
|
26
26
|
/** Bytes per chunk table entry. */
|
|
@@ -406,6 +406,20 @@ export const ATTR_LAYERS = 1 << 9;
|
|
|
406
406
|
* before this one, and draws the mesh planted, sunlit and opaque — the absent lane's own meaning.
|
|
407
407
|
*/
|
|
408
408
|
export const ATTR_CHANNEL = 1 << 10;
|
|
409
|
+
/**
|
|
410
|
+
* Added in 1.24: a vertex's fifth to eighth influences, which joints and how much. See
|
|
411
|
+
* `MeshData.joints2`.
|
|
412
|
+
*
|
|
413
|
+
* Two bits for two arrays, both-or-neither like the first pair, and neither without the first pair.
|
|
414
|
+
* Appended after the channel, as every attribute since 1.20 has been, and that has a consequence
|
|
415
|
+
* worth stating rather than implying: indices follow the last array, so **a 1.23 reader does not
|
|
416
|
+
* skip these** — it reads their floats where it expects indices, finds indices far past the vertex
|
|
417
|
+
* count, and refuses the mesh by name. A loud refusal rather than a mesh drawn with four of its
|
|
418
|
+
* eight influences, which is the same answer `layers` and the channel give an older reader.
|
|
419
|
+
*/
|
|
420
|
+
export const ATTR_JOINTS2 = 1 << 11;
|
|
421
|
+
/** How much each of `ATTR_JOINTS2`' four influences moves the vertex. */
|
|
422
|
+
export const ATTR_WEIGHTS2 = 1 << 12;
|
|
409
423
|
/** Everything this version defines, so a reader can spot bits from a later writer. */
|
|
410
424
|
export const ATTR_KNOWN = ATTR_SPECULAR |
|
|
411
425
|
ATTR_UVS |
|
|
@@ -417,7 +431,9 @@ export const ATTR_KNOWN = ATTR_SPECULAR |
|
|
|
417
431
|
ATTR_JOINTS |
|
|
418
432
|
ATTR_WEIGHTS |
|
|
419
433
|
ATTR_LAYERS |
|
|
420
|
-
ATTR_CHANNEL
|
|
434
|
+
ATTR_CHANNEL |
|
|
435
|
+
ATTR_JOINTS2 |
|
|
436
|
+
ATTR_WEIGHTS2;
|
|
421
437
|
/**
|
|
422
438
|
* How a `TEXS` payload is encoded.
|
|
423
439
|
*
|
|
@@ -431,6 +447,25 @@ export const CODEC_PNG = 1;
|
|
|
431
447
|
export const CODEC_JPEG = 2;
|
|
432
448
|
export const CODEC_WEBP = 3;
|
|
433
449
|
export const CODEC_RAW = 4;
|
|
450
|
+
/**
|
|
451
|
+
* Added in 1.24: block-compressed images, kept as the blocks they were authored in.
|
|
452
|
+
*
|
|
453
|
+
* **Uploaded as they are where the device can sample them**, so a texture authored as BC7 costs a
|
|
454
|
+
* byte a texel on the GPU rather than four, and the mip chain the author built rather than one the
|
|
455
|
+
* engine generates. Where the device cannot — most phones, which sample ASTC and ETC2 instead — the
|
|
456
|
+
* loader decodes them to RGBA, which is the cost every texture paid before this. The payload — block
|
|
457
|
+
* format, colour space and the stored mip chain — is `drftBc.ts`'s.
|
|
458
|
+
*/
|
|
459
|
+
export const CODEC_BC = 5;
|
|
460
|
+
/** Bytes per 4x4 block of each format. */
|
|
461
|
+
export const BC_BLOCK_BYTES = Object.freeze({
|
|
462
|
+
bc1: 8,
|
|
463
|
+
bc2: 16,
|
|
464
|
+
bc3: 16,
|
|
465
|
+
bc4: 8,
|
|
466
|
+
bc5: 16,
|
|
467
|
+
bc7: 16,
|
|
468
|
+
});
|
|
434
469
|
/** What a codec is called, for a message naming what was not understood. */
|
|
435
470
|
export function codecName(codec) {
|
|
436
471
|
return codec === CODEC_PNG
|
|
@@ -441,7 +476,9 @@ export function codecName(codec) {
|
|
|
441
476
|
? 'WEBP'
|
|
442
477
|
: codec === CODEC_RAW
|
|
443
478
|
? 'RAW'
|
|
444
|
-
:
|
|
479
|
+
: codec === CODEC_BC
|
|
480
|
+
? 'BC'
|
|
481
|
+
: `codec ${codec}`;
|
|
445
482
|
}
|
|
446
483
|
/** Floats in a `MATL` entry, then the signed texture indices that follow them. */
|
|
447
484
|
export const MATERIAL_FLOATS = 20;
|
package/dist/drftQuantised.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* compares vertices bit for bit, or one that cannot afford the decode's copy; `writeDrft`'s
|
|
24
24
|
* `quantise: false` writes `MESH` as before.
|
|
25
25
|
*/
|
|
26
|
-
import { ATTR_EMISSIVE_COLOR, ATTR_GRAIN, ATTR_JOINTS, ATTR_RELIEF, ATTR_ROUGHNESS, ATTR_SPECULAR, ATTR_TANGENT, ATTR_UVS, ATTR_WEIGHTS, ATTR_LAYERS, ATTR_CHANNEL, DrftError, align, } from './drftFormat.js';
|
|
26
|
+
import { ATTR_EMISSIVE_COLOR, ATTR_GRAIN, ATTR_JOINTS, ATTR_JOINTS2, ATTR_WEIGHTS2, ATTR_RELIEF, ATTR_ROUGHNESS, ATTR_SPECULAR, ATTR_TANGENT, ATTR_UVS, ATTR_WEIGHTS, ATTR_LAYERS, ATTR_CHANNEL, DrftError, align, } from './drftFormat.js';
|
|
27
27
|
const RAW = 0;
|
|
28
28
|
const CONSTANT = 1;
|
|
29
29
|
const RANGE16 = 2;
|
|
@@ -49,6 +49,8 @@ const ORDER = [
|
|
|
49
49
|
/* Whole numbers naming an image, so exact: a layer quantised onto its neighbour is another picture. */
|
|
50
50
|
{ name: 'layers', width: 1, bit: ATTR_LAYERS, kind: 'exact' },
|
|
51
51
|
{ name: 'channel', width: 4, bit: ATTR_CHANNEL, kind: 'value' },
|
|
52
|
+
{ name: 'joints2', width: 4, bit: ATTR_JOINTS2, kind: 'exact' },
|
|
53
|
+
{ name: 'weights2', width: 4, bit: ATTR_WEIGHTS2, kind: 'value' },
|
|
52
54
|
];
|
|
53
55
|
/** A growing little-endian byte list, for the writer: offline, so it may allocate. */
|
|
54
56
|
class Bytes {
|
package/dist/drftRead.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { readClip, readMorph, readNodes, readSkin } from './drftSkin.js';
|
|
18
18
|
import { validateMeshData } from './meshData.js';
|
|
19
|
-
import { ATTR_EMISSIVE_COLOR, ATTR_GRAIN, ATTR_JOINTS, ATTR_RELIEF, ATTR_TANGENT, ATTR_ROUGHNESS, ATTR_SPECULAR, ATTR_UVS, ATTR_WEIGHTS, ATTR_LAYERS, ATTR_CHANNEL, CHUNK_ENTRY_BYTES, CHUNK_HEAD, CHUNK_LODM, CHUNK_LODF, CHUNK_DTEX, CHUNK_ENTS, CHUNK_NAVM, CHUNK_SPLT, SPLAT_BLOCK_PREFIX, CHUNK_MESH, CHUNK_REQUIRED, DRFT_MAGIC, DRFT_VERSION_MAJOR, DrftError, HEADER_BYTES, CHUNK_MATL, CHUNK_TEXS, KNOWN_CHUNKS, align, fourCCName, CHUNK_ANIM, CHUNK_NODE, CHUNK_INST, CHUNK_REGN, CHUNK_LVOL, CHUNK_LITE, CHUNK_MSHQ, CHUNK_KITS, CHUNK_MSHC, CHUNK_SKIN, CHUNK_MORP, CHUNK_NNET, CHUNK_NGRF, CHUNK_SDFV, CHUNK_SUBS, CHUNK_COLL, } from './drftFormat.js';
|
|
19
|
+
import { ATTR_EMISSIVE_COLOR, ATTR_GRAIN, ATTR_JOINTS, ATTR_RELIEF, ATTR_TANGENT, ATTR_ROUGHNESS, ATTR_SPECULAR, ATTR_UVS, ATTR_WEIGHTS, ATTR_LAYERS, ATTR_CHANNEL, ATTR_JOINTS2, ATTR_WEIGHTS2, CHUNK_ENTRY_BYTES, CHUNK_HEAD, CHUNK_LODM, CHUNK_LODF, CHUNK_DTEX, CHUNK_ENTS, CHUNK_NAVM, CHUNK_SPLT, SPLAT_BLOCK_PREFIX, CHUNK_MESH, CHUNK_REQUIRED, DRFT_MAGIC, DRFT_VERSION_MAJOR, DrftError, HEADER_BYTES, CHUNK_MATL, CHUNK_TEXS, KNOWN_CHUNKS, align, fourCCName, CHUNK_ANIM, CHUNK_NODE, CHUNK_INST, CHUNK_REGN, CHUNK_LVOL, CHUNK_LITE, CHUNK_MSHQ, CHUNK_KITS, CHUNK_MSHC, CHUNK_SKIN, CHUNK_MORP, CHUNK_NNET, CHUNK_NGRF, CHUNK_SDFV, CHUNK_SUBS, CHUNK_COLL, } from './drftFormat.js';
|
|
20
20
|
import { readSubs } from './drftSubs.js';
|
|
21
21
|
import { readSdfv } from './sdfv.js';
|
|
22
22
|
import { readDtex } from './dtex.js';
|
|
@@ -104,6 +104,9 @@ export function readMesh(buffer, chunk) {
|
|
|
104
104
|
const layers = (attributes & ATTR_LAYERS) !== 0 ? floats(vertices, 'layers') : undefined;
|
|
105
105
|
/* Sway, sky and opacity, 1.23, four floats a vertex and last. See ATTR_CHANNEL. */
|
|
106
106
|
const channel = (attributes & ATTR_CHANNEL) !== 0 ? floats(vertices * 4, 'channel') : undefined;
|
|
107
|
+
/* The fifth to eighth influences, 1.24, last. See ATTR_JOINTS2. */
|
|
108
|
+
const joints2 = (attributes & ATTR_JOINTS2) !== 0 ? floats(vertices * 4, 'joints2') : undefined;
|
|
109
|
+
const weights2 = (attributes & ATTR_WEIGHTS2) !== 0 ? floats(vertices * 4, 'weights2') : undefined;
|
|
107
110
|
const indexBytes = indexCount * 4;
|
|
108
111
|
if (at + indexBytes > limit) {
|
|
109
112
|
throw new DrftError(`indices need ${indexBytes} bytes but only ${limit - at} remain at ${at}`);
|
|
@@ -126,6 +129,8 @@ export function readMesh(buffer, chunk) {
|
|
|
126
129
|
...(weights === undefined ? {} : { weights }),
|
|
127
130
|
...(layers === undefined ? {} : { layers }),
|
|
128
131
|
...(channel === undefined ? {} : { channel }),
|
|
132
|
+
...(joints2 === undefined ? {} : { joints2 }),
|
|
133
|
+
...(weights2 === undefined ? {} : { weights2 }),
|
|
129
134
|
};
|
|
130
135
|
/*
|
|
131
136
|
* Checked again on the way in, even though the writer checked on the way out. The file
|
package/dist/drftWrite.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { buildClip, buildMorph, buildNodes, buildSkin } from './drftSkin.js';
|
|
12
12
|
import { validateMeshData } from './meshData.js';
|
|
13
|
-
import { ALIGNMENT, ATTR_EMISSIVE_COLOR, ATTR_GRAIN, ATTR_JOINTS, ATTR_RELIEF, ATTR_TANGENT, ATTR_ROUGHNESS, ATTR_SPECULAR, ATTR_UVS, ATTR_WEIGHTS, ATTR_LAYERS, ATTR_CHANNEL, CHUNK_ENTRY_BYTES, CHUNK_HEAD, CHUNK_ANIM, CHUNK_DTEX, CHUNK_ENTS, CHUNK_NAVM, CHUNK_LODM, CHUNK_LODF, CHUNK_MORP, CHUNK_NODE, CHUNK_INST, CHUNK_REGN, CHUNK_LVOL, CHUNK_LITE, CHUNK_MSHQ, CHUNK_KITS, CHUNK_MSHC, CHUNK_SKIN, CHUNK_SPLT, SPLAT_BLOCK_PREFIX, CHUNK_MATL, CHUNK_MESH, CHUNK_REQUIRED, CHUNK_COLL, CHUNK_NNET, CHUNK_NGRF, CHUNK_SDFV, CHUNK_SUBS, CHUNK_TEXS, MATERIAL_ENTRY_BYTES, DRFT_MAGIC, DRFT_VERSION_MAJOR, DRFT_VERSION_MINOR, DrftError, FLAG_LITTLE_ENDIAN, HEADER_BYTES, align, } from './drftFormat.js';
|
|
13
|
+
import { ALIGNMENT, ATTR_EMISSIVE_COLOR, ATTR_GRAIN, ATTR_JOINTS, ATTR_RELIEF, ATTR_TANGENT, ATTR_ROUGHNESS, ATTR_SPECULAR, ATTR_UVS, ATTR_WEIGHTS, ATTR_LAYERS, ATTR_CHANNEL, ATTR_JOINTS2, ATTR_WEIGHTS2, CHUNK_ENTRY_BYTES, CHUNK_HEAD, CHUNK_ANIM, CHUNK_DTEX, CHUNK_ENTS, CHUNK_NAVM, CHUNK_LODM, CHUNK_LODF, CHUNK_MORP, CHUNK_NODE, CHUNK_INST, CHUNK_REGN, CHUNK_LVOL, CHUNK_LITE, CHUNK_MSHQ, CHUNK_KITS, CHUNK_MSHC, CHUNK_SKIN, CHUNK_SPLT, SPLAT_BLOCK_PREFIX, CHUNK_MATL, CHUNK_MESH, CHUNK_REQUIRED, CHUNK_COLL, CHUNK_NNET, CHUNK_NGRF, CHUNK_SDFV, CHUNK_SUBS, CHUNK_TEXS, MATERIAL_ENTRY_BYTES, DRFT_MAGIC, DRFT_VERSION_MAJOR, DRFT_VERSION_MINOR, DrftError, FLAG_LITTLE_ENDIAN, HEADER_BYTES, align, } from './drftFormat.js';
|
|
14
14
|
import { coarseFirstOrder } from './coarseFirst.js';
|
|
15
15
|
import { buildSubs } from './drftSubs.js';
|
|
16
16
|
import { buildSdfv } from './sdfv.js';
|
|
@@ -280,6 +280,10 @@ function buildMesh(mesh, code = CHUNK_MESH, flags = CHUNK_REQUIRED) {
|
|
|
280
280
|
attributes |= ATTR_LAYERS;
|
|
281
281
|
if (mesh.channel !== undefined)
|
|
282
282
|
attributes |= ATTR_CHANNEL;
|
|
283
|
+
if (mesh.joints2 !== undefined)
|
|
284
|
+
attributes |= ATTR_JOINTS2;
|
|
285
|
+
if (mesh.weights2 !== undefined)
|
|
286
|
+
attributes |= ATTR_WEIGHTS2;
|
|
283
287
|
/* Order is frozen: mandatory arrays, then optional ones by ascending bit. */
|
|
284
288
|
const arrays = [
|
|
285
289
|
mesh.positions,
|
|
@@ -313,6 +317,11 @@ function buildMesh(mesh, code = CHUNK_MESH, flags = CHUNK_REQUIRED) {
|
|
|
313
317
|
/* And newer still, 1.23. See ATTR_CHANNEL. */
|
|
314
318
|
if (mesh.channel !== undefined)
|
|
315
319
|
arrays.push(mesh.channel);
|
|
320
|
+
/* And 1.24's second four influences, last. See ATTR_JOINTS2. */
|
|
321
|
+
if (mesh.joints2 !== undefined)
|
|
322
|
+
arrays.push(mesh.joints2);
|
|
323
|
+
if (mesh.weights2 !== undefined)
|
|
324
|
+
arrays.push(mesh.weights2);
|
|
316
325
|
arrays.push(mesh.indices);
|
|
317
326
|
const PREFIX = 16;
|
|
318
327
|
let total = PREFIX;
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,10 @@ export type { DrftManifest, DrftStreamHandlers, DrftStreamPacing } from './drftS
|
|
|
46
46
|
export { writeDrft } from './drftWrite.ts';
|
|
47
47
|
export type { DrftSource, DrftTextureSource } from './drftWrite.ts';
|
|
48
48
|
export { DrftError, DRFT_VERSION_MAJOR, DRFT_VERSION_MINOR } from './drftFormat.ts';
|
|
49
|
-
export { CODEC_PNG, CODEC_JPEG, CODEC_WEBP, CODEC_RAW, codecName } from './drftFormat.ts';
|
|
49
|
+
export { CODEC_PNG, CODEC_JPEG, CODEC_WEBP, CODEC_RAW, CODEC_BC, BC_BLOCK_BYTES, codecName, } from './drftFormat.ts';
|
|
50
|
+
export type { BcFormat } from './drftFormat.ts';
|
|
51
|
+
export { bcChainLength, bcLevelBytes, readBcPayload, writeBcPayload } from './drftBc.ts';
|
|
52
|
+
export type { BcImage } from './drftBc.ts';
|
|
50
53
|
export type { DrftHead, DrftMaterial, DrftSplatBlock, DrftSplats } from './drftFormat.ts';
|
|
51
54
|
export { coarseFirstOrder } from './coarseFirst.ts';
|
|
52
55
|
export { MAX_CLUSTERS, buildMeshlets, readMeshlets } from './drftMeshlets.ts';
|
package/dist/index.js
CHANGED
|
@@ -54,7 +54,8 @@ export { DEFAULT_STREAM_SLICE_MS, DrftStream, streamDrft } from './drftStream.js
|
|
|
54
54
|
export { writeDrft } from './drftWrite.js';
|
|
55
55
|
export { DrftError, DRFT_VERSION_MAJOR, DRFT_VERSION_MINOR } from './drftFormat.js';
|
|
56
56
|
/* A consumer decoding an embedded texture has to know which codec it is holding. */
|
|
57
|
-
export { CODEC_PNG, CODEC_JPEG, CODEC_WEBP, CODEC_RAW, codecName } from './drftFormat.js';
|
|
57
|
+
export { CODEC_PNG, CODEC_JPEG, CODEC_WEBP, CODEC_RAW, CODEC_BC, BC_BLOCK_BYTES, codecName, } from './drftFormat.js';
|
|
58
|
+
export { bcChainLength, bcLevelBytes, readBcPayload, writeBcPayload } from './drftBc.js';
|
|
58
59
|
/* The order a capture is written in, so a baker outside this package can lay one out the same
|
|
59
60
|
way — it is the whole reason a capture streams into a recognisable place. */
|
|
60
61
|
export { coarseFirstOrder } from './coarseFirst.js';
|
package/dist/meshData.d.ts
CHANGED
|
@@ -163,9 +163,12 @@ export interface MeshData {
|
|
|
163
163
|
* nothing and uploads nothing. Absent means unskinned, and an unskinned mesh takes exactly the
|
|
164
164
|
* draw path it took before skinning existed.
|
|
165
165
|
*
|
|
166
|
-
* **Four
|
|
167
|
-
*
|
|
168
|
-
*
|
|
166
|
+
* **Four here, and four more in `joints2` for a mesh that has them.** Four is what glTF's
|
|
167
|
+
* `JOINTS_0` carries and what most exports write, and it fits one attribute; a character authored
|
|
168
|
+
* for eight — a face, a shoulder — loses the shape of its deformation when the four lightest are
|
|
169
|
+
* dropped, so 4.8.4 added a second set rather than widening this one. A mesh without it pays
|
|
170
|
+
* nothing for it, and importers sort heaviest first, so this set always holds the four that
|
|
171
|
+
* matter most.
|
|
169
172
|
*
|
|
170
173
|
* **`Float32Array` and not `Uint8Array`, which costs twelve bytes a vertex.** Every attribute in
|
|
171
174
|
* this format is float32 and the absent-attribute mechanism depends on it: `mesh.ts` attaches
|
|
@@ -188,6 +191,19 @@ export interface MeshData {
|
|
|
188
191
|
* is refused below rather than drawn.
|
|
189
192
|
*/
|
|
190
193
|
weights?: Float32Array;
|
|
194
|
+
/**
|
|
195
|
+
* Four floats per vertex: the fifth to eighth joints that move this vertex, for a rig authored with
|
|
196
|
+
* eight influences. The same encoding as `joints`, and the same both-or-neither with `weights2`.
|
|
197
|
+
*
|
|
198
|
+
* **Present only with `joints`**, because the importers sort a vertex's influences heaviest first:
|
|
199
|
+
* the first set is always the four that matter most, and a second set alone would be four of the
|
|
200
|
+
* lightest with the heaviest missing. What it costs is thirty-two bytes a vertex on a mesh that
|
|
201
|
+
* carries it, and nothing on one that does not — a renderer draws such a mesh through a vertex
|
|
202
|
+
* variant that reads two more attributes, and every other mesh through the variant it always had.
|
|
203
|
+
*/
|
|
204
|
+
joints2?: Float32Array;
|
|
205
|
+
/** Four floats per vertex: how much each of `joints2`' influences moves the vertex. */
|
|
206
|
+
weights2?: Float32Array;
|
|
191
207
|
/**
|
|
192
208
|
* Morph target position deltas: `morphTargetCount` targets, three floats a vertex each.
|
|
193
209
|
*
|
package/dist/meshData.js
CHANGED
|
@@ -73,6 +73,17 @@ export function validateMeshData(data) {
|
|
|
73
73
|
check('tangents', data.tangents, 4);
|
|
74
74
|
check('joints', data.joints, 4);
|
|
75
75
|
check('weights', data.weights, 4);
|
|
76
|
+
check('joints2', data.joints2, 4);
|
|
77
|
+
check('weights2', data.weights2, 4);
|
|
78
|
+
if ((data.joints2 === undefined) !== (data.weights2 === undefined)) {
|
|
79
|
+
throw new Error(data.joints2 === undefined
|
|
80
|
+
? 'Mesh: weights2 without joints2 — the second four influences need both'
|
|
81
|
+
: 'Mesh: joints2 without weights2 — the second four influences need both');
|
|
82
|
+
}
|
|
83
|
+
if (data.joints2 !== undefined && data.joints === undefined) {
|
|
84
|
+
throw new Error('Mesh: a second set of influences without the first — importers put the four heaviest in ' +
|
|
85
|
+
'`joints`, so `joints2` alone would be the lightest four with the heaviest missing');
|
|
86
|
+
}
|
|
76
87
|
/*
|
|
77
88
|
* Both or neither. The shader multiplies four matrices by four weights, so a missing half means
|
|
78
89
|
* either every weight or every index is the absent-attribute constant — and both collapse the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driftengine/drft",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.4",
|
|
4
4
|
"description": "The .drft container: a streaming format for baked 3D scenes",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
],
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@driftengine/core": "4.8.
|
|
31
|
-
"@driftengine/entities": "4.8.
|
|
32
|
-
"@driftengine/texture": "4.8.
|
|
30
|
+
"@driftengine/core": "4.8.4",
|
|
31
|
+
"@driftengine/entities": "4.8.4",
|
|
32
|
+
"@driftengine/texture": "4.8.4"
|
|
33
33
|
},
|
|
34
34
|
"author": "Drift Technologies",
|
|
35
35
|
"repository": {
|
package/src/drftBc.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `CODEC_BC` texture payload: which block format, what colour space its author declared, and
|
|
3
|
+
* every stored mip level, largest first.
|
|
4
|
+
*
|
|
5
|
+
* **The layout** (FORMAT.md §4.5, 1.24), little-endian after the `TEXS` prefix that already carries
|
|
6
|
+
* the codec, width and height:
|
|
7
|
+
*
|
|
8
|
+
* u32 format the format's own digit: 1, 2, 3, 4, 5 or 7 for BC1 to BC7
|
|
9
|
+
* u32 flags bit 0: the author declared the texels sRGB
|
|
10
|
+
* u32 levels how many mip levels follow, 1 to the full chain
|
|
11
|
+
* u32 0 reserved
|
|
12
|
+
* ... each level's blocks, back to back, largest first
|
|
13
|
+
*
|
|
14
|
+
* **A level's size is derived, never stored.** Level `i` is `max(1, width >> i)` by
|
|
15
|
+
* `max(1, height >> i)` texels, rounded up to whole 4x4 blocks — so a level smaller than a block
|
|
16
|
+
* still costs one, which is how every DDS writer and every GPU lays them out. Storing the sizes
|
|
17
|
+
* would be a second statement of the same fact, free to disagree with the first.
|
|
18
|
+
*
|
|
19
|
+
* **What it gives up**: a chain is whole levels from the top. A file that wanted to drop its top
|
|
20
|
+
* level to save bytes has to say a smaller width and height, which is also what it then is.
|
|
21
|
+
*
|
|
22
|
+
* Unknown flag bits are ignored, which is rule 4 of the compatibility contract; a format number
|
|
23
|
+
* this reader does not know is refused by name. A signed or HDR format would be a new number rather
|
|
24
|
+
* than a flag, because a decoder that ignored it would produce wrong values rather than none.
|
|
25
|
+
*/
|
|
26
|
+
import { BC_BLOCK_BYTES, DrftError } from './drftFormat.ts';
|
|
27
|
+
import type { BcFormat } from './drftFormat.ts';
|
|
28
|
+
|
|
29
|
+
/** A block-compressed image, as a `CODEC_BC` payload holds it. */
|
|
30
|
+
export interface BcImage {
|
|
31
|
+
readonly format: BcFormat;
|
|
32
|
+
/**
|
|
33
|
+
* What the author's header said of its texels. How a material slot samples them is still the
|
|
34
|
+
* renderer's decision, as for every other codec: a colour map is read as sRGB and a normal map as
|
|
35
|
+
* linear whatever its file claimed, so this is the author's statement rather than an instruction.
|
|
36
|
+
*/
|
|
37
|
+
readonly srgb: boolean;
|
|
38
|
+
readonly width: number;
|
|
39
|
+
readonly height: number;
|
|
40
|
+
/** Level 0 first. Views over the payload when read, never copies. */
|
|
41
|
+
readonly levels: readonly Uint8Array[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const HEADER_BYTES = 16;
|
|
45
|
+
const FLAG_SRGB = 1;
|
|
46
|
+
|
|
47
|
+
const FORMAT_NUMBER: Readonly<Record<BcFormat, number>> = {
|
|
48
|
+
bc1: 1,
|
|
49
|
+
bc2: 2,
|
|
50
|
+
bc3: 3,
|
|
51
|
+
bc4: 4,
|
|
52
|
+
bc5: 5,
|
|
53
|
+
bc7: 7,
|
|
54
|
+
};
|
|
55
|
+
const FORMAT_OF: Readonly<Record<number, BcFormat>> = {
|
|
56
|
+
1: 'bc1',
|
|
57
|
+
2: 'bc2',
|
|
58
|
+
3: 'bc3',
|
|
59
|
+
4: 'bc4',
|
|
60
|
+
5: 'bc5',
|
|
61
|
+
7: 'bc7',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** How many levels a full chain from `width` by `height` down to 1x1 has. */
|
|
65
|
+
export function bcChainLength(width: number, height: number): number {
|
|
66
|
+
return Math.floor(Math.log2(Math.max(1, width, height))) + 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The bytes level `level` of a `width` by `height` surface takes in `format`. */
|
|
70
|
+
export function bcLevelBytes(
|
|
71
|
+
format: BcFormat,
|
|
72
|
+
width: number,
|
|
73
|
+
height: number,
|
|
74
|
+
level: number,
|
|
75
|
+
): number {
|
|
76
|
+
const w = Math.max(1, width >> level);
|
|
77
|
+
const h = Math.max(1, height >> level);
|
|
78
|
+
return Math.ceil(w / 4) * Math.ceil(h / 4) * BC_BLOCK_BYTES[format];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The payload for `image`. Refuses a level whose size is not the one its position requires. */
|
|
82
|
+
export function writeBcPayload(image: BcImage): Uint8Array {
|
|
83
|
+
const { format, width, height, levels } = image;
|
|
84
|
+
checkChain(levels.length, width, height);
|
|
85
|
+
let total = HEADER_BYTES;
|
|
86
|
+
for (let i = 0; i < levels.length; i++) {
|
|
87
|
+
const want = bcLevelBytes(format, width, height, i);
|
|
88
|
+
const level = levels[i] as Uint8Array;
|
|
89
|
+
if (level.length !== want) {
|
|
90
|
+
throw new DrftError(
|
|
91
|
+
`bc: level ${i} of a ${width}x${height} ${format.toUpperCase()} image is ${want} bytes, and ` +
|
|
92
|
+
`${level.length} were given`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
total += want;
|
|
96
|
+
}
|
|
97
|
+
const out = new Uint8Array(total);
|
|
98
|
+
const view = new DataView(out.buffer);
|
|
99
|
+
view.setUint32(0, FORMAT_NUMBER[format], true);
|
|
100
|
+
view.setUint32(4, image.srgb ? FLAG_SRGB : 0, true);
|
|
101
|
+
view.setUint32(8, levels.length, true);
|
|
102
|
+
let at = HEADER_BYTES;
|
|
103
|
+
for (const level of levels) {
|
|
104
|
+
out.set(level, at);
|
|
105
|
+
at += level.length;
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** A payload read back, its levels as views over `bytes`. */
|
|
111
|
+
export function readBcPayload(width: number, height: number, bytes: Uint8Array): BcImage {
|
|
112
|
+
if (bytes.length < HEADER_BYTES) {
|
|
113
|
+
throw new DrftError(`bc: a payload of ${bytes.length} bytes is shorter than its header`);
|
|
114
|
+
}
|
|
115
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
116
|
+
const number = view.getUint32(0, true);
|
|
117
|
+
const format = FORMAT_OF[number];
|
|
118
|
+
if (format === undefined) {
|
|
119
|
+
throw new DrftError(
|
|
120
|
+
`bc: block format ${number} is not one this reader knows; it reads 1, 2, 3, 4, 5 and 7 ` +
|
|
121
|
+
'(BC1 to BC5 and BC7)',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const count = view.getUint32(8, true);
|
|
125
|
+
checkChain(count, width, height);
|
|
126
|
+
let needed = HEADER_BYTES;
|
|
127
|
+
for (let i = 0; i < count; i++) needed += bcLevelBytes(format, width, height, i);
|
|
128
|
+
if (bytes.length < needed) {
|
|
129
|
+
throw new DrftError(
|
|
130
|
+
`bc: ${count} levels of a ${width}x${height} ${format.toUpperCase()} image needs ` +
|
|
131
|
+
`${needed} bytes and the payload is ${bytes.length}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const levels: Uint8Array[] = [];
|
|
135
|
+
let at = HEADER_BYTES;
|
|
136
|
+
for (let i = 0; i < count; i++) {
|
|
137
|
+
const size = bcLevelBytes(format, width, height, i);
|
|
138
|
+
levels.push(bytes.subarray(at, at + size));
|
|
139
|
+
at += size;
|
|
140
|
+
}
|
|
141
|
+
return { format, srgb: (view.getUint32(4, true) & FLAG_SRGB) !== 0, width, height, levels };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function checkChain(count: number, width: number, height: number): void {
|
|
145
|
+
const most = bcChainLength(width, height);
|
|
146
|
+
if (width < 1 || height < 1 || count < 1 || count > most) {
|
|
147
|
+
throw new DrftError(
|
|
148
|
+
`bc: ${count} levels for a ${width}x${height} image, which has at least 1 and at most ${most}`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/drftFormat.ts
CHANGED
|
@@ -23,7 +23,7 @@ export const DRFT_VERSION_MAJOR = 1;
|
|
|
23
23
|
* Additive revisions within a generation: new optional chunks, new attribute bits, new
|
|
24
24
|
* enum values with a defined fallback. Never a changed meaning.
|
|
25
25
|
*/
|
|
26
|
-
export const DRFT_VERSION_MINOR =
|
|
26
|
+
export const DRFT_VERSION_MINOR = 24;
|
|
27
27
|
|
|
28
28
|
/** Bytes before the chunk table. */
|
|
29
29
|
export const HEADER_BYTES = 32;
|
|
@@ -448,6 +448,21 @@ export const ATTR_LAYERS = 1 << 9;
|
|
|
448
448
|
*/
|
|
449
449
|
export const ATTR_CHANNEL = 1 << 10;
|
|
450
450
|
|
|
451
|
+
/**
|
|
452
|
+
* Added in 1.24: a vertex's fifth to eighth influences, which joints and how much. See
|
|
453
|
+
* `MeshData.joints2`.
|
|
454
|
+
*
|
|
455
|
+
* Two bits for two arrays, both-or-neither like the first pair, and neither without the first pair.
|
|
456
|
+
* Appended after the channel, as every attribute since 1.20 has been, and that has a consequence
|
|
457
|
+
* worth stating rather than implying: indices follow the last array, so **a 1.23 reader does not
|
|
458
|
+
* skip these** — it reads their floats where it expects indices, finds indices far past the vertex
|
|
459
|
+
* count, and refuses the mesh by name. A loud refusal rather than a mesh drawn with four of its
|
|
460
|
+
* eight influences, which is the same answer `layers` and the channel give an older reader.
|
|
461
|
+
*/
|
|
462
|
+
export const ATTR_JOINTS2 = 1 << 11;
|
|
463
|
+
/** How much each of `ATTR_JOINTS2`' four influences moves the vertex. */
|
|
464
|
+
export const ATTR_WEIGHTS2 = 1 << 12;
|
|
465
|
+
|
|
451
466
|
/** Everything this version defines, so a reader can spot bits from a later writer. */
|
|
452
467
|
export const ATTR_KNOWN =
|
|
453
468
|
ATTR_SPECULAR |
|
|
@@ -460,7 +475,9 @@ export const ATTR_KNOWN =
|
|
|
460
475
|
ATTR_JOINTS |
|
|
461
476
|
ATTR_WEIGHTS |
|
|
462
477
|
ATTR_LAYERS |
|
|
463
|
-
ATTR_CHANNEL
|
|
478
|
+
ATTR_CHANNEL |
|
|
479
|
+
ATTR_JOINTS2 |
|
|
480
|
+
ATTR_WEIGHTS2;
|
|
464
481
|
|
|
465
482
|
/**
|
|
466
483
|
* How a `TEXS` payload is encoded.
|
|
@@ -476,6 +493,38 @@ export const CODEC_JPEG = 2;
|
|
|
476
493
|
export const CODEC_WEBP = 3;
|
|
477
494
|
export const CODEC_RAW = 4;
|
|
478
495
|
|
|
496
|
+
/**
|
|
497
|
+
* Added in 1.24: block-compressed images, kept as the blocks they were authored in.
|
|
498
|
+
*
|
|
499
|
+
* **Uploaded as they are where the device can sample them**, so a texture authored as BC7 costs a
|
|
500
|
+
* byte a texel on the GPU rather than four, and the mip chain the author built rather than one the
|
|
501
|
+
* engine generates. Where the device cannot — most phones, which sample ASTC and ETC2 instead — the
|
|
502
|
+
* loader decodes them to RGBA, which is the cost every texture paid before this. The payload — block
|
|
503
|
+
* format, colour space and the stored mip chain — is `drftBc.ts`'s.
|
|
504
|
+
*/
|
|
505
|
+
export const CODEC_BC = 5;
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* The block formats a `CODEC_BC` payload may hold: what one 4x4 block of texels encodes.
|
|
509
|
+
*
|
|
510
|
+
* BC1 is colour with punch-through alpha in eight bytes; BC2 and BC3 colour with explicit and
|
|
511
|
+
* interpolated alpha in sixteen; BC4 one channel and BC5 two (a normal map) in eight and sixteen;
|
|
512
|
+
* BC7 colour and alpha in sixteen, with eight modes chosen per block. BC6H, signed BC4/BC5 and the
|
|
513
|
+
* other families are not among them: an HDR or signed texture is a different contract, refused by
|
|
514
|
+
* name rather than mis-decoded.
|
|
515
|
+
*/
|
|
516
|
+
export type BcFormat = 'bc1' | 'bc2' | 'bc3' | 'bc4' | 'bc5' | 'bc7';
|
|
517
|
+
|
|
518
|
+
/** Bytes per 4x4 block of each format. */
|
|
519
|
+
export const BC_BLOCK_BYTES: Readonly<Record<BcFormat, number>> = Object.freeze({
|
|
520
|
+
bc1: 8,
|
|
521
|
+
bc2: 16,
|
|
522
|
+
bc3: 16,
|
|
523
|
+
bc4: 8,
|
|
524
|
+
bc5: 16,
|
|
525
|
+
bc7: 16,
|
|
526
|
+
});
|
|
527
|
+
|
|
479
528
|
/** What a codec is called, for a message naming what was not understood. */
|
|
480
529
|
export function codecName(codec: number): string {
|
|
481
530
|
return codec === CODEC_PNG
|
|
@@ -486,7 +535,9 @@ export function codecName(codec: number): string {
|
|
|
486
535
|
? 'WEBP'
|
|
487
536
|
: codec === CODEC_RAW
|
|
488
537
|
? 'RAW'
|
|
489
|
-
:
|
|
538
|
+
: codec === CODEC_BC
|
|
539
|
+
? 'BC'
|
|
540
|
+
: `codec ${codec}`;
|
|
490
541
|
}
|
|
491
542
|
|
|
492
543
|
/**
|
package/src/drftQuantised.ts
CHANGED
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
ATTR_EMISSIVE_COLOR,
|
|
28
28
|
ATTR_GRAIN,
|
|
29
29
|
ATTR_JOINTS,
|
|
30
|
+
ATTR_JOINTS2,
|
|
31
|
+
ATTR_WEIGHTS2,
|
|
30
32
|
ATTR_RELIEF,
|
|
31
33
|
ATTR_ROUGHNESS,
|
|
32
34
|
ATTR_SPECULAR,
|
|
@@ -64,7 +66,9 @@ type Name =
|
|
|
64
66
|
| 'joints'
|
|
65
67
|
| 'weights'
|
|
66
68
|
| 'layers'
|
|
67
|
-
| 'channel'
|
|
69
|
+
| 'channel'
|
|
70
|
+
| 'joints2'
|
|
71
|
+
| 'weights2';
|
|
68
72
|
|
|
69
73
|
/** The frozen order `MESH` uses, with each optional array's bit: `FORMAT.md` §4.3. */
|
|
70
74
|
const ORDER: readonly {
|
|
@@ -89,6 +93,8 @@ const ORDER: readonly {
|
|
|
89
93
|
/* Whole numbers naming an image, so exact: a layer quantised onto its neighbour is another picture. */
|
|
90
94
|
{ name: 'layers', width: 1, bit: ATTR_LAYERS, kind: 'exact' },
|
|
91
95
|
{ name: 'channel', width: 4, bit: ATTR_CHANNEL, kind: 'value' },
|
|
96
|
+
{ name: 'joints2', width: 4, bit: ATTR_JOINTS2, kind: 'exact' },
|
|
97
|
+
{ name: 'weights2', width: 4, bit: ATTR_WEIGHTS2, kind: 'value' },
|
|
92
98
|
];
|
|
93
99
|
|
|
94
100
|
/** A growing little-endian byte list, for the writer: offline, so it may allocate. */
|
package/src/drftRead.ts
CHANGED
|
@@ -33,6 +33,8 @@ import {
|
|
|
33
33
|
ATTR_WEIGHTS,
|
|
34
34
|
ATTR_LAYERS,
|
|
35
35
|
ATTR_CHANNEL,
|
|
36
|
+
ATTR_JOINTS2,
|
|
37
|
+
ATTR_WEIGHTS2,
|
|
36
38
|
CHUNK_ENTRY_BYTES,
|
|
37
39
|
CHUNK_HEAD,
|
|
38
40
|
CHUNK_LODM,
|
|
@@ -324,6 +326,10 @@ export function readMesh(buffer: ArrayBuffer, chunk: DrftChunk): MeshData {
|
|
|
324
326
|
const layers = (attributes & ATTR_LAYERS) !== 0 ? floats(vertices, 'layers') : undefined;
|
|
325
327
|
/* Sway, sky and opacity, 1.23, four floats a vertex and last. See ATTR_CHANNEL. */
|
|
326
328
|
const channel = (attributes & ATTR_CHANNEL) !== 0 ? floats(vertices * 4, 'channel') : undefined;
|
|
329
|
+
/* The fifth to eighth influences, 1.24, last. See ATTR_JOINTS2. */
|
|
330
|
+
const joints2 = (attributes & ATTR_JOINTS2) !== 0 ? floats(vertices * 4, 'joints2') : undefined;
|
|
331
|
+
const weights2 =
|
|
332
|
+
(attributes & ATTR_WEIGHTS2) !== 0 ? floats(vertices * 4, 'weights2') : undefined;
|
|
327
333
|
|
|
328
334
|
const indexBytes = indexCount * 4;
|
|
329
335
|
if (at + indexBytes > limit) {
|
|
@@ -348,6 +354,8 @@ export function readMesh(buffer: ArrayBuffer, chunk: DrftChunk): MeshData {
|
|
|
348
354
|
...(weights === undefined ? {} : { weights }),
|
|
349
355
|
...(layers === undefined ? {} : { layers }),
|
|
350
356
|
...(channel === undefined ? {} : { channel }),
|
|
357
|
+
...(joints2 === undefined ? {} : { joints2 }),
|
|
358
|
+
...(weights2 === undefined ? {} : { weights2 }),
|
|
351
359
|
};
|
|
352
360
|
|
|
353
361
|
/*
|
package/src/drftWrite.ts
CHANGED
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
ATTR_WEIGHTS,
|
|
28
28
|
ATTR_LAYERS,
|
|
29
29
|
ATTR_CHANNEL,
|
|
30
|
+
ATTR_JOINTS2,
|
|
31
|
+
ATTR_WEIGHTS2,
|
|
30
32
|
CHUNK_ENTRY_BYTES,
|
|
31
33
|
CHUNK_HEAD,
|
|
32
34
|
CHUNK_ANIM,
|
|
@@ -523,6 +525,8 @@ function buildMesh(mesh: MeshData, code = CHUNK_MESH, flags = CHUNK_REQUIRED): P
|
|
|
523
525
|
if (mesh.weights !== undefined) attributes |= ATTR_WEIGHTS;
|
|
524
526
|
if (mesh.layers !== undefined) attributes |= ATTR_LAYERS;
|
|
525
527
|
if (mesh.channel !== undefined) attributes |= ATTR_CHANNEL;
|
|
528
|
+
if (mesh.joints2 !== undefined) attributes |= ATTR_JOINTS2;
|
|
529
|
+
if (mesh.weights2 !== undefined) attributes |= ATTR_WEIGHTS2;
|
|
526
530
|
|
|
527
531
|
/* Order is frozen: mandatory arrays, then optional ones by ascending bit. */
|
|
528
532
|
const arrays: (Float32Array | Uint32Array)[] = [
|
|
@@ -546,6 +550,9 @@ function buildMesh(mesh: MeshData, code = CHUNK_MESH, flags = CHUNK_REQUIRED): P
|
|
|
546
550
|
if (mesh.layers !== undefined) arrays.push(mesh.layers);
|
|
547
551
|
/* And newer still, 1.23. See ATTR_CHANNEL. */
|
|
548
552
|
if (mesh.channel !== undefined) arrays.push(mesh.channel);
|
|
553
|
+
/* And 1.24's second four influences, last. See ATTR_JOINTS2. */
|
|
554
|
+
if (mesh.joints2 !== undefined) arrays.push(mesh.joints2);
|
|
555
|
+
if (mesh.weights2 !== undefined) arrays.push(mesh.weights2);
|
|
549
556
|
arrays.push(mesh.indices);
|
|
550
557
|
|
|
551
558
|
const PREFIX = 16;
|
package/src/index.ts
CHANGED
|
@@ -140,7 +140,18 @@ export { writeDrft } from './drftWrite.ts';
|
|
|
140
140
|
export type { DrftSource, DrftTextureSource } from './drftWrite.ts';
|
|
141
141
|
export { DrftError, DRFT_VERSION_MAJOR, DRFT_VERSION_MINOR } from './drftFormat.ts';
|
|
142
142
|
/* A consumer decoding an embedded texture has to know which codec it is holding. */
|
|
143
|
-
export {
|
|
143
|
+
export {
|
|
144
|
+
CODEC_PNG,
|
|
145
|
+
CODEC_JPEG,
|
|
146
|
+
CODEC_WEBP,
|
|
147
|
+
CODEC_RAW,
|
|
148
|
+
CODEC_BC,
|
|
149
|
+
BC_BLOCK_BYTES,
|
|
150
|
+
codecName,
|
|
151
|
+
} from './drftFormat.ts';
|
|
152
|
+
export type { BcFormat } from './drftFormat.ts';
|
|
153
|
+
export { bcChainLength, bcLevelBytes, readBcPayload, writeBcPayload } from './drftBc.ts';
|
|
154
|
+
export type { BcImage } from './drftBc.ts';
|
|
144
155
|
export type { DrftHead, DrftMaterial, DrftSplatBlock, DrftSplats } from './drftFormat.ts';
|
|
145
156
|
/* The order a capture is written in, so a baker outside this package can lay one out the same
|
|
146
157
|
way — it is the whole reason a capture streams into a recognisable place. */
|
package/src/meshData.ts
CHANGED
|
@@ -164,9 +164,12 @@ export interface MeshData {
|
|
|
164
164
|
* nothing and uploads nothing. Absent means unskinned, and an unskinned mesh takes exactly the
|
|
165
165
|
* draw path it took before skinning existed.
|
|
166
166
|
*
|
|
167
|
-
* **Four
|
|
168
|
-
*
|
|
169
|
-
*
|
|
167
|
+
* **Four here, and four more in `joints2` for a mesh that has them.** Four is what glTF's
|
|
168
|
+
* `JOINTS_0` carries and what most exports write, and it fits one attribute; a character authored
|
|
169
|
+
* for eight — a face, a shoulder — loses the shape of its deformation when the four lightest are
|
|
170
|
+
* dropped, so 4.8.4 added a second set rather than widening this one. A mesh without it pays
|
|
171
|
+
* nothing for it, and importers sort heaviest first, so this set always holds the four that
|
|
172
|
+
* matter most.
|
|
170
173
|
*
|
|
171
174
|
* **`Float32Array` and not `Uint8Array`, which costs twelve bytes a vertex.** Every attribute in
|
|
172
175
|
* this format is float32 and the absent-attribute mechanism depends on it: `mesh.ts` attaches
|
|
@@ -189,6 +192,19 @@ export interface MeshData {
|
|
|
189
192
|
* is refused below rather than drawn.
|
|
190
193
|
*/
|
|
191
194
|
weights?: Float32Array;
|
|
195
|
+
/**
|
|
196
|
+
* Four floats per vertex: the fifth to eighth joints that move this vertex, for a rig authored with
|
|
197
|
+
* eight influences. The same encoding as `joints`, and the same both-or-neither with `weights2`.
|
|
198
|
+
*
|
|
199
|
+
* **Present only with `joints`**, because the importers sort a vertex's influences heaviest first:
|
|
200
|
+
* the first set is always the four that matter most, and a second set alone would be four of the
|
|
201
|
+
* lightest with the heaviest missing. What it costs is thirty-two bytes a vertex on a mesh that
|
|
202
|
+
* carries it, and nothing on one that does not — a renderer draws such a mesh through a vertex
|
|
203
|
+
* variant that reads two more attributes, and every other mesh through the variant it always had.
|
|
204
|
+
*/
|
|
205
|
+
joints2?: Float32Array;
|
|
206
|
+
/** Four floats per vertex: how much each of `joints2`' influences moves the vertex. */
|
|
207
|
+
weights2?: Float32Array;
|
|
192
208
|
/**
|
|
193
209
|
* Morph target position deltas: `morphTargetCount` targets, three floats a vertex each.
|
|
194
210
|
*
|
|
@@ -281,6 +297,21 @@ export function validateMeshData(data: MeshData): void {
|
|
|
281
297
|
check('tangents', data.tangents, 4);
|
|
282
298
|
check('joints', data.joints, 4);
|
|
283
299
|
check('weights', data.weights, 4);
|
|
300
|
+
check('joints2', data.joints2, 4);
|
|
301
|
+
check('weights2', data.weights2, 4);
|
|
302
|
+
if ((data.joints2 === undefined) !== (data.weights2 === undefined)) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
data.joints2 === undefined
|
|
305
|
+
? 'Mesh: weights2 without joints2 — the second four influences need both'
|
|
306
|
+
: 'Mesh: joints2 without weights2 — the second four influences need both',
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
if (data.joints2 !== undefined && data.joints === undefined) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
'Mesh: a second set of influences without the first — importers put the four heaviest in ' +
|
|
312
|
+
'`joints`, so `joints2` alone would be the lightest four with the heaviest missing',
|
|
313
|
+
);
|
|
314
|
+
}
|
|
284
315
|
|
|
285
316
|
/*
|
|
286
317
|
* Both or neither. The shader multiplies four matrices by four weights, so a missing half means
|