@stowkit/three-loader 0.1.17 → 0.1.18

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.
@@ -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: view.getUint32(0, true),
36
- materialCount: view.getUint32(4, true),
37
- nodeCount: view.getUint32(8, true),
38
- stringId: this.readString(data.slice(12, 140)) // 128 bytes for string
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
- // Ensure we have at least the minimum metadata size
47
- if (metadataBlob.length < 140) {
48
- throw new Error(`Metadata blob too small: ${metadataBlob.length} bytes (expected at least 140)`);
49
- }
50
- const metadata = this.parseMeshMetadata(metadataBlob);
51
- // Check if the metadata blob contains just the header or all the mesh info
52
- const expectedMetadataSize = 140 +
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 only has the header, the mesh info might be at the start of the data blob
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
- let offset = hasExtendedMetadata ? 140 : 0; // Start after MeshMetadata if in metadata blob, or at beginning of data blob
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
- if (prop.fieldName.toLowerCase() === 'tint' || prop.fieldName.toLowerCase().includes('color')) {
180
- // Apply color/tint
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
- // Parse skinned mesh metadata
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 = view.getUint32(0, true);
355
- const materialCount = view.getUint32(4, true);
356
- const nodeCount = view.getUint32(8, true);
357
- const boneCount = view.getUint32(12, true);
358
- const stringIdBytes = metadata.slice(16, 144);
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 > metadata.length) {
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: view.getUint32(offset, true),
376
- indexCount: view.getUint32(offset + 4, true),
377
- hasNormals: view.getUint32(offset + 8, true),
378
- hasUVs: view.getUint32(offset + 12, true),
379
- vertexBufferOffset: Number(view.getBigUint64(offset + 16, true)),
380
- vertexBufferSize: Number(view.getBigUint64(offset + 24, true)),
381
- indexBufferOffset: Number(view.getBigUint64(offset + 32, true)),
382
- indexBufferSize: Number(view.getBigUint64(offset + 40, true)),
383
- weightsOffset: Number(view.getBigUint64(offset + 48, true)),
384
- weightsSize: Number(view.getBigUint64(offset + 56, true)),
385
- materialIndex: view.getUint32(offset + 64, true)
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 > metadata.length) {
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 = metadata.slice(metadataOffset, metadataOffset + 64);
403
- const schemaIdBytes = metadata.slice(metadataOffset + 64, metadataOffset + 192);
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 = view.getUint32(metadataOffset + 192, true);
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 > metadata.length) {
444
+ if (metadataOffset + PROPERTY_STRIDE > actualMetadata.length) {
422
445
  break;
423
446
  }
424
- const fieldNameBytes = metadata.slice(metadataOffset, metadataOffset + 64);
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
- view.getFloat32(metadataOffset + 64, true),
430
- view.getFloat32(metadataOffset + 68, true),
431
- view.getFloat32(metadataOffset + 72, true),
432
- view.getFloat32(metadataOffset + 76, true)
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 = metadata.slice(metadataOffset + 80, metadataOffset + 144);
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 > metadata.length) {
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(metadata.buffer, metadata.byteOffset + metadataOffset, 64);
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 = view.getInt32(metadataOffset + 64, true);
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] = view.getFloat32(metadataOffset + 68 + j * 4, true);
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 = metadata.slice(0, 128);
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 = metadata.slice(128, 256);
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 = view.getFloat32(256, true); // Duration in seconds (per C header)
868
- view.getFloat32(260, true); // For custom playback rate if needed
869
- const channelCount = view.getUint32(264, true);
870
- const boneCount = view.getUint32(268, true);
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: view.getUint32(chOffset, true),
878
- keyframeType: view.getUint32(chOffset + 4, true),
879
- keyframeCount: view.getUint32(chOffset + 8, true),
880
- keyframeTimesOffset: Number(view.getBigUint64(chOffset + 12, true)),
881
- keyframeValuesOffset: Number(view.getBigUint64(chOffset + 20, true))
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 = metadata.slice(boneOffset, boneOffset + 64);
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 = view.getInt32(boneOffset + 64, true);
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