@stowkit/three-loader 0.1.17 → 0.1.19
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/MeshParser.d.ts.map +1 -1
- package/dist/StowKitPack.d.ts.map +1 -1
- package/dist/stowkit-three-loader.esm.js +96 -62
- package/dist/stowkit-three-loader.esm.js.map +1 -1
- package/dist/stowkit-three-loader.js +96 -62
- package/dist/stowkit-three-loader.js.map +1 -1
- package/package.json +36 -36
- package/public/basis/basis_transcoder.wasm +0 -0
- package/public/draco/draco_decoder.wasm +0 -0
- package/scripts/postinstall.js +37 -37
|
@@ -29,13 +29,22 @@ class MeshParser {
|
|
|
29
29
|
* Parse mesh metadata from binary data
|
|
30
30
|
*/
|
|
31
31
|
static parseMeshMetadata(data) {
|
|
32
|
+
// NEW FORMAT: Skip past tag header
|
|
33
|
+
// Format: [4-byte tag_csv_length][CSV string][MeshMetadata struct]
|
|
32
34
|
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
35
|
+
const tagCsvLength = view.getUint32(0, true);
|
|
36
|
+
const metadataOffset = 4 + tagCsvLength;
|
|
37
|
+
if (metadataOffset >= data.length) {
|
|
38
|
+
throw new Error('Invalid metadata: tag header exceeds bounds');
|
|
39
|
+
}
|
|
40
|
+
const actualMetadata = data.slice(metadataOffset);
|
|
41
|
+
const metaView = new DataView(actualMetadata.buffer, actualMetadata.byteOffset, actualMetadata.byteLength);
|
|
33
42
|
// MeshMetadata structure (140 bytes total when packed)
|
|
34
43
|
const metadata = {
|
|
35
|
-
meshGeometryCount:
|
|
36
|
-
materialCount:
|
|
37
|
-
nodeCount:
|
|
38
|
-
stringId: this.readString(
|
|
44
|
+
meshGeometryCount: metaView.getUint32(0, true),
|
|
45
|
+
materialCount: metaView.getUint32(4, true),
|
|
46
|
+
nodeCount: metaView.getUint32(8, true),
|
|
47
|
+
stringId: this.readString(actualMetadata.slice(12, 140)) // 128 bytes for string
|
|
39
48
|
};
|
|
40
49
|
return metadata;
|
|
41
50
|
}
|
|
@@ -43,21 +52,24 @@ class MeshParser {
|
|
|
43
52
|
* Parse complete mesh data including geometry, materials, and nodes
|
|
44
53
|
*/
|
|
45
54
|
static parseMeshData(metadataBlob, dataBlob) {
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const metadata = this.parseMeshMetadata(metadataBlob);
|
|
51
|
-
// Check if the metadata blob contains
|
|
52
|
-
|
|
55
|
+
// NEW FORMAT: Account for tag header at front
|
|
56
|
+
const view = new DataView(metadataBlob.buffer, metadataBlob.byteOffset, metadataBlob.byteLength);
|
|
57
|
+
const tagCsvLength = view.getUint32(0, true);
|
|
58
|
+
const tagHeaderSize = 4 + tagCsvLength;
|
|
59
|
+
const metadata = this.parseMeshMetadata(metadataBlob); // This already handles tag header
|
|
60
|
+
// Check if the metadata blob contains all the mesh info (shifted by tag header)
|
|
61
|
+
// Total expected size = tagHeaderSize + 140 (MeshMetadata) + geometries + materials + nodes
|
|
62
|
+
const expectedMetadataSize = tagHeaderSize + 140 +
|
|
53
63
|
(metadata.meshGeometryCount * 40) + // Draco compressed (40 bytes each)
|
|
54
64
|
(metadata.materialCount * 196) + // MaterialData header
|
|
55
65
|
(metadata.nodeCount * 116);
|
|
56
66
|
let hasExtendedMetadata = metadataBlob.length >= expectedMetadataSize;
|
|
57
|
-
// If metadata
|
|
67
|
+
// If metadata has everything, read from metadataBlob (accounting for tag header)
|
|
68
|
+
// Otherwise, read from dataBlob (no tag header there)
|
|
58
69
|
const sourceBlob = hasExtendedMetadata ? metadataBlob : dataBlob;
|
|
59
70
|
const metaView = new DataView(sourceBlob.buffer, sourceBlob.byteOffset, sourceBlob.byteLength);
|
|
60
|
-
|
|
71
|
+
// Offset: skip tag header + MeshMetadata struct if reading from metadata blob
|
|
72
|
+
let offset = hasExtendedMetadata ? (tagHeaderSize + 140) : 0;
|
|
61
73
|
// Parse geometry infos (now Draco compressed!)
|
|
62
74
|
const geometries = [];
|
|
63
75
|
for (let i = 0; i < metadata.meshGeometryCount; i++) {
|
|
@@ -176,10 +188,16 @@ class MeshParser {
|
|
|
176
188
|
});
|
|
177
189
|
// Apply properties
|
|
178
190
|
for (const prop of matData.properties) {
|
|
179
|
-
|
|
180
|
-
|
|
191
|
+
const fieldName = prop.fieldName.toLowerCase();
|
|
192
|
+
if (fieldName === 'tint' || fieldName.includes('color')) {
|
|
181
193
|
mat.color = new THREE__namespace.Color(prop.value[0], prop.value[1], prop.value[2]);
|
|
182
194
|
}
|
|
195
|
+
else if (fieldName === 'alphatest') {
|
|
196
|
+
if (prop.value[0] > 0) {
|
|
197
|
+
mat.alphaTest = 0.5;
|
|
198
|
+
mat.transparent = true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
183
201
|
}
|
|
184
202
|
materials.push(mat);
|
|
185
203
|
}
|
|
@@ -347,15 +365,20 @@ class StowKitPack {
|
|
|
347
365
|
* Parse skinned mesh from binary data
|
|
348
366
|
*/
|
|
349
367
|
async parseSkinnedMesh(metadata, data) {
|
|
350
|
-
//
|
|
368
|
+
// NEW FORMAT: Skip past tag header
|
|
351
369
|
const view = new DataView(metadata.buffer, metadata.byteOffset, metadata.byteLength);
|
|
370
|
+
const tagCsvLength = view.getUint32(0, true);
|
|
371
|
+
const tagHeaderSize = 4 + tagCsvLength;
|
|
372
|
+
// Skip to actual metadata
|
|
373
|
+
const actualMetadata = metadata.slice(tagHeaderSize);
|
|
374
|
+
const metaView = new DataView(actualMetadata.buffer, actualMetadata.byteOffset, actualMetadata.byteLength);
|
|
352
375
|
const decoder = new TextDecoder();
|
|
353
376
|
// SkinnedMeshMetadata: 4 uint32s (16 bytes) THEN string_id[128] = 144 bytes total
|
|
354
|
-
const meshGeometryCount =
|
|
355
|
-
const materialCount =
|
|
356
|
-
const nodeCount =
|
|
357
|
-
const boneCount =
|
|
358
|
-
const stringIdBytes =
|
|
377
|
+
const meshGeometryCount = metaView.getUint32(0, true);
|
|
378
|
+
const materialCount = metaView.getUint32(4, true);
|
|
379
|
+
const nodeCount = metaView.getUint32(8, true);
|
|
380
|
+
const boneCount = metaView.getUint32(12, true);
|
|
381
|
+
const stringIdBytes = actualMetadata.slice(16, 144);
|
|
359
382
|
const stringIdNullIndex = stringIdBytes.indexOf(0);
|
|
360
383
|
const stringId = decoder
|
|
361
384
|
.decode(stringIdBytes.slice(0, stringIdNullIndex >= 0 ? stringIdNullIndex : 128))
|
|
@@ -368,21 +391,21 @@ class StowKitPack {
|
|
|
368
391
|
// vertex_buffer_offset, vertex_buffer_size, index_buffer_offset, index_buffer_size, weights_offset, weights_size (48 bytes)
|
|
369
392
|
// material_index, _padding (8 bytes)
|
|
370
393
|
for (let g = 0; g < meshGeometryCount; g++) {
|
|
371
|
-
if (offset + 72 >
|
|
394
|
+
if (offset + 72 > actualMetadata.length) {
|
|
372
395
|
throw new Error(`Metadata too small for geometry ${g}`);
|
|
373
396
|
}
|
|
374
397
|
geometryInfos.push({
|
|
375
|
-
vertexCount:
|
|
376
|
-
indexCount:
|
|
377
|
-
hasNormals:
|
|
378
|
-
hasUVs:
|
|
379
|
-
vertexBufferOffset: Number(
|
|
380
|
-
vertexBufferSize: Number(
|
|
381
|
-
indexBufferOffset: Number(
|
|
382
|
-
indexBufferSize: Number(
|
|
383
|
-
weightsOffset: Number(
|
|
384
|
-
weightsSize: Number(
|
|
385
|
-
materialIndex:
|
|
398
|
+
vertexCount: metaView.getUint32(offset, true),
|
|
399
|
+
indexCount: metaView.getUint32(offset + 4, true),
|
|
400
|
+
hasNormals: metaView.getUint32(offset + 8, true),
|
|
401
|
+
hasUVs: metaView.getUint32(offset + 12, true),
|
|
402
|
+
vertexBufferOffset: Number(metaView.getBigUint64(offset + 16, true)),
|
|
403
|
+
vertexBufferSize: Number(metaView.getBigUint64(offset + 24, true)),
|
|
404
|
+
indexBufferOffset: Number(metaView.getBigUint64(offset + 32, true)),
|
|
405
|
+
indexBufferSize: Number(metaView.getBigUint64(offset + 40, true)),
|
|
406
|
+
weightsOffset: Number(metaView.getBigUint64(offset + 48, true)),
|
|
407
|
+
weightsSize: Number(metaView.getBigUint64(offset + 56, true)),
|
|
408
|
+
materialIndex: metaView.getUint32(offset + 64, true)
|
|
386
409
|
// _padding at offset + 68 (ignored)
|
|
387
410
|
});
|
|
388
411
|
offset += 72;
|
|
@@ -395,15 +418,15 @@ class StowKitPack {
|
|
|
395
418
|
const materialData = [];
|
|
396
419
|
// Parse MaterialData headers
|
|
397
420
|
for (let i = 0; i < materialCount; i++) {
|
|
398
|
-
if (metadataOffset + 196 >
|
|
421
|
+
if (metadataOffset + 196 > actualMetadata.length) {
|
|
399
422
|
console.warn('Skinned mesh metadata truncated while reading material headers');
|
|
400
423
|
break;
|
|
401
424
|
}
|
|
402
|
-
const nameBytes =
|
|
403
|
-
const schemaIdBytes =
|
|
425
|
+
const nameBytes = actualMetadata.slice(metadataOffset, metadataOffset + 64);
|
|
426
|
+
const schemaIdBytes = actualMetadata.slice(metadataOffset + 64, metadataOffset + 192);
|
|
404
427
|
const name = decoder.decode(nameBytes.slice(0, nameBytes.indexOf(0) || 64));
|
|
405
428
|
const schemaId = decoder.decode(schemaIdBytes.slice(0, schemaIdBytes.indexOf(0) || 128));
|
|
406
|
-
const propertyCount =
|
|
429
|
+
const propertyCount = metaView.getUint32(metadataOffset + 192, true);
|
|
407
430
|
materialData.push({ name, schemaId, propertyCount, properties: [] });
|
|
408
431
|
metadataOffset += 196;
|
|
409
432
|
}
|
|
@@ -418,20 +441,20 @@ class StowKitPack {
|
|
|
418
441
|
const mat = materialData[i];
|
|
419
442
|
for (let p = 0; p < mat.propertyCount; p++) {
|
|
420
443
|
try {
|
|
421
|
-
if (metadataOffset + PROPERTY_STRIDE >
|
|
444
|
+
if (metadataOffset + PROPERTY_STRIDE > actualMetadata.length) {
|
|
422
445
|
break;
|
|
423
446
|
}
|
|
424
|
-
const fieldNameBytes =
|
|
447
|
+
const fieldNameBytes = actualMetadata.slice(metadataOffset, metadataOffset + 64);
|
|
425
448
|
const fieldName = decoder
|
|
426
449
|
.decode(fieldNameBytes.slice(0, fieldNameBytes.indexOf(0) || 64))
|
|
427
450
|
.trim();
|
|
428
451
|
const value = [
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
452
|
+
metaView.getFloat32(metadataOffset + 64, true),
|
|
453
|
+
metaView.getFloat32(metadataOffset + 68, true),
|
|
454
|
+
metaView.getFloat32(metadataOffset + 72, true),
|
|
455
|
+
metaView.getFloat32(metadataOffset + 76, true)
|
|
433
456
|
];
|
|
434
|
-
const textureIdBytes =
|
|
457
|
+
const textureIdBytes = actualMetadata.slice(metadataOffset + 80, metadataOffset + 144);
|
|
435
458
|
const textureIdNullIndex = textureIdBytes.indexOf(0);
|
|
436
459
|
const textureId = (textureIdNullIndex >= 0
|
|
437
460
|
? decoder.decode(textureIdBytes.slice(0, textureIdNullIndex))
|
|
@@ -453,17 +476,17 @@ class StowKitPack {
|
|
|
453
476
|
const boneOriginalNames = [];
|
|
454
477
|
const bindPoses = [];
|
|
455
478
|
for (let i = 0; i < boneCount; i++) {
|
|
456
|
-
if (metadataOffset + BONE_STRIDE >
|
|
479
|
+
if (metadataOffset + BONE_STRIDE > actualMetadata.length) {
|
|
457
480
|
throw new Error(`Skinned mesh metadata truncated while reading bone ${i}`);
|
|
458
481
|
}
|
|
459
|
-
const nameBytes = new Uint8Array(
|
|
482
|
+
const nameBytes = new Uint8Array(actualMetadata.buffer, actualMetadata.byteOffset + metadataOffset, 64);
|
|
460
483
|
const nullIndex = nameBytes.indexOf(0);
|
|
461
484
|
const rawName = decoder.decode(nameBytes.subarray(0, nullIndex >= 0 ? nullIndex : 64)).trim();
|
|
462
|
-
const parentIndex =
|
|
485
|
+
const parentIndex = metaView.getInt32(metadataOffset + 64, true);
|
|
463
486
|
const offsetMatrix = new THREE__namespace.Matrix4();
|
|
464
487
|
const matrixElements = new Float32Array(16);
|
|
465
488
|
for (let j = 0; j < 16; j++) {
|
|
466
|
-
matrixElements[j] =
|
|
489
|
+
matrixElements[j] = metaView.getFloat32(metadataOffset + 68 + j * 4, true);
|
|
467
490
|
}
|
|
468
491
|
offsetMatrix.fromArray(matrixElements);
|
|
469
492
|
const sanitizedName = this.sanitizeTrackName(rawName);
|
|
@@ -524,7 +547,6 @@ class StowKitPack {
|
|
|
524
547
|
// Apply non-texture properties
|
|
525
548
|
for (const prop of mat.properties) {
|
|
526
549
|
const fieldName = prop.fieldName.toLowerCase();
|
|
527
|
-
// Handle color properties
|
|
528
550
|
if (fieldName.includes('color') || fieldName.includes('tint')) {
|
|
529
551
|
material.color.setRGB(prop.value[0], prop.value[1], prop.value[2]);
|
|
530
552
|
}
|
|
@@ -534,6 +556,12 @@ class StowKitPack {
|
|
|
534
556
|
else if (fieldName === 'roughness') {
|
|
535
557
|
material.roughness = prop.value[0];
|
|
536
558
|
}
|
|
559
|
+
else if (fieldName === 'alphatest') {
|
|
560
|
+
if (prop.value[0] > 0) {
|
|
561
|
+
material.alphaTest = 0.5;
|
|
562
|
+
material.transparent = true;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
537
565
|
}
|
|
538
566
|
materials.push(material);
|
|
539
567
|
}
|
|
@@ -853,32 +881,38 @@ class StowKitPack {
|
|
|
853
881
|
* Parse animation clip from binary data
|
|
854
882
|
*/
|
|
855
883
|
parseAnimationClip(metadata, data) {
|
|
884
|
+
// NEW FORMAT: Skip past tag header
|
|
856
885
|
const view = new DataView(metadata.buffer, metadata.byteOffset, metadata.byteLength);
|
|
886
|
+
const tagCsvLength = view.getUint32(0, true);
|
|
887
|
+
const tagHeaderSize = 4 + tagCsvLength;
|
|
888
|
+
// Skip to actual metadata
|
|
889
|
+
const actualMetadata = metadata.slice(tagHeaderSize);
|
|
890
|
+
const metaView = new DataView(actualMetadata.buffer, actualMetadata.byteOffset, actualMetadata.byteLength);
|
|
857
891
|
const decoder = new TextDecoder();
|
|
858
892
|
// Parse AnimationClipMetadata (272 bytes total)
|
|
859
|
-
const stringIdBytes =
|
|
893
|
+
const stringIdBytes = actualMetadata.slice(0, 128);
|
|
860
894
|
const stringIdNullIndex = stringIdBytes.indexOf(0);
|
|
861
895
|
const stringId = decoder.decode(stringIdBytes.slice(0, stringIdNullIndex >= 0 ? stringIdNullIndex : 128));
|
|
862
|
-
const targetMeshIdBytes =
|
|
896
|
+
const targetMeshIdBytes = actualMetadata.slice(128, 256);
|
|
863
897
|
const targetMeshIdNullIndex = targetMeshIdBytes.indexOf(0);
|
|
864
898
|
decoder
|
|
865
899
|
.decode(targetMeshIdBytes.slice(0, targetMeshIdNullIndex >= 0 ? targetMeshIdNullIndex : 128))
|
|
866
900
|
.trim();
|
|
867
|
-
const durationSeconds =
|
|
868
|
-
|
|
869
|
-
const channelCount =
|
|
870
|
-
const boneCount =
|
|
901
|
+
const durationSeconds = metaView.getFloat32(256, true); // Duration in seconds (per C header)
|
|
902
|
+
metaView.getFloat32(260, true); // For custom playback rate if needed
|
|
903
|
+
const channelCount = metaView.getUint32(264, true);
|
|
904
|
+
const boneCount = metaView.getUint32(268, true);
|
|
871
905
|
// Parse AnimationChannel[] (starts at offset 272, each is 28 bytes)
|
|
872
906
|
const channelsOffset = 272;
|
|
873
907
|
const channels = [];
|
|
874
908
|
for (let i = 0; i < channelCount; i++) {
|
|
875
909
|
const chOffset = channelsOffset + i * 28;
|
|
876
910
|
channels.push({
|
|
877
|
-
boneIndex:
|
|
878
|
-
keyframeType:
|
|
879
|
-
keyframeCount:
|
|
880
|
-
keyframeTimesOffset: Number(
|
|
881
|
-
keyframeValuesOffset: Number(
|
|
911
|
+
boneIndex: metaView.getUint32(chOffset, true),
|
|
912
|
+
keyframeType: metaView.getUint32(chOffset + 4, true),
|
|
913
|
+
keyframeCount: metaView.getUint32(chOffset + 8, true),
|
|
914
|
+
keyframeTimesOffset: Number(metaView.getBigUint64(chOffset + 12, true)),
|
|
915
|
+
keyframeValuesOffset: Number(metaView.getBigUint64(chOffset + 20, true))
|
|
882
916
|
});
|
|
883
917
|
}
|
|
884
918
|
// Parse AnimationBoneInfo[] (starts after all channels, each is 68 bytes)
|
|
@@ -886,13 +920,13 @@ class StowKitPack {
|
|
|
886
920
|
const bones = [];
|
|
887
921
|
for (let i = 0; i < boneCount; i++) {
|
|
888
922
|
const boneOffset = bonesOffset + i * 68;
|
|
889
|
-
const nameBytes =
|
|
923
|
+
const nameBytes = actualMetadata.slice(boneOffset, boneOffset + 64);
|
|
890
924
|
const nullIndex = nameBytes.indexOf(0);
|
|
891
925
|
const rawName = decoder
|
|
892
926
|
.decode(nameBytes.slice(0, nullIndex >= 0 ? nullIndex : 64))
|
|
893
927
|
.trim();
|
|
894
928
|
const sanitizedName = this.sanitizeTrackName(rawName);
|
|
895
|
-
const parentIndex =
|
|
929
|
+
const parentIndex = metaView.getInt32(boneOffset + 64, true);
|
|
896
930
|
bones.push({ name: rawName, parentIndex, sanitizedName });
|
|
897
931
|
}
|
|
898
932
|
// Create DataView for keyframe data
|