@stowkit/three-loader 0.1.32 → 0.1.33

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.
@@ -521,6 +521,7 @@ class StowKitPack {
521
521
  * Load a skinned mesh by its string ID
522
522
  */
523
523
  async loadSkinnedMesh(stringId) {
524
+ const totalStart = performance.now();
524
525
  const assetIndex = this.reader.findAssetByPath(stringId);
525
526
  if (assetIndex < 0) {
526
527
  throw new Error(`Skinned mesh not found: ${stringId}`);
@@ -531,12 +532,16 @@ class StowKitPack {
531
532
  throw new Error(`Failed to read skinned mesh data: ${stringId}`);
532
533
  if (!metadata)
533
534
  throw new Error(`No metadata for skinned mesh: ${stringId}`);
534
- return await this.parseSkinnedMesh(metadata, data);
535
+ const result = await this.parseSkinnedMesh(metadata, data);
536
+ const totalTime = performance.now() - totalStart;
537
+ reader.PerfLogger.log(`[Perf] === Skinned Mesh "${stringId}": ${totalTime.toFixed(2)}ms total ===`);
538
+ return result;
535
539
  }
536
540
  /**
537
541
  * Parse skinned mesh from binary data
538
542
  */
539
543
  async parseSkinnedMesh(metadata, data) {
544
+ const parseStart = performance.now();
540
545
  // NEW FORMAT: Skip past tag header
541
546
  const view = new DataView(metadata.buffer, metadata.byteOffset, metadata.byteLength);
542
547
  const tagCsvLength = view.getUint32(0, true);
@@ -582,7 +587,7 @@ class StowKitPack {
582
587
  });
583
588
  offset += 72;
584
589
  }
585
- const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
590
+ new DataView(data.buffer, data.byteOffset, data.byteLength);
586
591
  // Sort geometry infos by vertex buffer offset to ensure sequential parsing
587
592
  const sortedGeometryInfos = geometryInfos.slice().sort((a, b) => a.vertexBufferOffset - b.vertexBufferOffset);
588
593
  // Parse materials from metadata (immediately after geometries)
@@ -737,88 +742,39 @@ class StowKitPack {
737
742
  }
738
743
  materials.push(material);
739
744
  }
745
+ const parseTime = performance.now() - parseStart;
746
+ reader.PerfLogger.log(`[Perf] Parse skinned metadata: ${parseTime.toFixed(2)}ms`);
747
+ const textureStart = performance.now();
740
748
  await this.loadMaterialTextures(materialData, materials);
749
+ const textureTime = performance.now() - textureStart;
750
+ if (materialData.length > 0) {
751
+ reader.PerfLogger.log(`[Perf] Load textures: ${textureTime.toFixed(2)}ms`);
752
+ }
741
753
  // Collect root bones (hierarchy already built earlier)
754
+ const buildStart = performance.now();
742
755
  const rootBones = bones.filter((_, index) => parentIndices[index] < 0);
743
756
  // Create shared skeleton
744
757
  const skeleton = new THREE__namespace.Skeleton(bones, boneMatrices);
745
- // Pre-allocate combined buffers
746
- const totalVertexCount = sortedGeometryInfos.reduce((sum, info) => sum + info.vertexCount, 0);
747
- const totalIndexCount = sortedGeometryInfos.reduce((sum, info) => sum + info.indexCount, 0);
748
- const combinedPositions = new Float32Array(totalVertexCount * 3);
749
- const combinedNormals = new Float32Array(totalVertexCount * 3);
750
- const combinedUVs = new Float32Array(totalVertexCount * 2);
751
- const combinedSkinIndices = new Uint16Array(totalVertexCount * 4);
752
- const combinedSkinWeights = new Float32Array(totalVertexCount * 4);
753
- const combinedIndices = new Uint32Array(totalIndexCount);
758
+ // Use WASM to parse geometry data (10-50x faster!)
759
+ const geomData = this.reader.parseSkinnedMeshGeometryFast(metadata, data);
760
+ if (!geomData) {
761
+ throw new Error('Failed to parse skinned mesh geometry');
762
+ }
763
+ // Convert Uint32Array skinIndices to Uint16Array for Three.js
764
+ const skinIndices16 = new Uint16Array(geomData.skinIndices);
754
765
  const combinedGeometry = new THREE__namespace.BufferGeometry();
755
- combinedGeometry.clearGroups();
756
- let vertexCursor = 0; // counts vertices
757
- let indexCursor = 0; // counts indices
766
+ combinedGeometry.setAttribute('position', new THREE__namespace.BufferAttribute(geomData.positions, 3));
767
+ combinedGeometry.setAttribute('normal', new THREE__namespace.BufferAttribute(geomData.normals, 3));
768
+ combinedGeometry.setAttribute('uv', new THREE__namespace.BufferAttribute(geomData.uvs, 2));
769
+ combinedGeometry.setAttribute('skinIndex', new THREE__namespace.Uint16BufferAttribute(skinIndices16, 4));
770
+ combinedGeometry.setAttribute('skinWeight', new THREE__namespace.BufferAttribute(geomData.skinWeights, 4));
771
+ combinedGeometry.setIndex(new THREE__namespace.BufferAttribute(geomData.indices, 1));
772
+ // Add geometry groups for materials
773
+ let indexCursor = 0;
758
774
  for (const geomInfo of sortedGeometryInfos) {
759
- const vertexStrideBytes = 32;
760
- const vertexBase = vertexCursor; // base vertex index for this submesh
761
- // Validate offsets before parsing
762
- const requiredVertexBytes = geomInfo.vertexBufferOffset + geomInfo.vertexCount * vertexStrideBytes;
763
- const requiredIndexBytes = geomInfo.indexBufferOffset + geomInfo.indexCount * 4;
764
- const requiredWeightBytes = geomInfo.weightsOffset + geomInfo.vertexCount * 32;
765
- if (requiredVertexBytes > data.byteLength) {
766
- throw new Error(`Vertex buffer out of bounds: need ${requiredVertexBytes}, have ${data.byteLength}`);
767
- }
768
- if (requiredIndexBytes > data.byteLength) {
769
- throw new Error(`Index buffer out of bounds: need ${requiredIndexBytes}, have ${data.byteLength}`);
770
- }
771
- if (requiredWeightBytes > data.byteLength) {
772
- throw new Error(`Weight buffer out of bounds: need ${requiredWeightBytes}, have ${data.byteLength}`);
773
- }
774
- // Parse vertices
775
- for (let v = 0; v < geomInfo.vertexCount; v++) {
776
- const vertexOffset = geomInfo.vertexBufferOffset + v * vertexStrideBytes;
777
- // positions
778
- combinedPositions[(vertexCursor + v) * 3 + 0] = dataView.getFloat32(vertexOffset + 0, true);
779
- combinedPositions[(vertexCursor + v) * 3 + 1] = dataView.getFloat32(vertexOffset + 4, true);
780
- combinedPositions[(vertexCursor + v) * 3 + 2] = dataView.getFloat32(vertexOffset + 8, true);
781
- // normals
782
- if (geomInfo.hasNormals) {
783
- combinedNormals[(vertexCursor + v) * 3 + 0] = dataView.getFloat32(vertexOffset + 12, true);
784
- combinedNormals[(vertexCursor + v) * 3 + 1] = dataView.getFloat32(vertexOffset + 16, true);
785
- combinedNormals[(vertexCursor + v) * 3 + 2] = dataView.getFloat32(vertexOffset + 20, true);
786
- }
787
- // uvs
788
- if (geomInfo.hasUVs) {
789
- combinedUVs[(vertexCursor + v) * 2 + 0] = dataView.getFloat32(vertexOffset + 24, true);
790
- combinedUVs[(vertexCursor + v) * 2 + 1] = dataView.getFloat32(vertexOffset + 28, true);
791
- }
792
- // bone indices & weights
793
- const weightOffset = geomInfo.weightsOffset + v * 32;
794
- for (let j = 0; j < 4; j++) {
795
- const boneIndex = dataView.getUint32(weightOffset + j * 4, true);
796
- combinedSkinIndices[(vertexCursor + v) * 4 + j] = boneIndex < boneCount ? boneIndex : 0;
797
- }
798
- for (let j = 0; j < 4; j++) {
799
- combinedSkinWeights[(vertexCursor + v) * 4 + j] = dataView.getFloat32(weightOffset + 16 + j * 4, true);
800
- }
801
- }
802
- // Parse indices
803
- const groupStart = indexCursor;
804
- for (let i = 0; i < geomInfo.indexCount; i++) {
805
- const indexValue = dataView.getUint32(geomInfo.indexBufferOffset + i * 4, true);
806
- combinedIndices[indexCursor + i] = indexValue + vertexBase;
807
- }
808
- combinedGeometry.addGroup(groupStart, geomInfo.indexCount, Math.min(geomInfo.materialIndex, materials.length - 1));
809
- vertexCursor += geomInfo.vertexCount;
775
+ combinedGeometry.addGroup(indexCursor, geomInfo.indexCount, Math.min(geomInfo.materialIndex, materials.length - 1));
810
776
  indexCursor += geomInfo.indexCount;
811
777
  }
812
- combinedGeometry.setAttribute('position', new THREE__namespace.BufferAttribute(combinedPositions, 3));
813
- if (geometryInfos.some(info => info.hasNormals)) {
814
- combinedGeometry.setAttribute('normal', new THREE__namespace.BufferAttribute(combinedNormals, 3));
815
- }
816
- if (geometryInfos.some(info => info.hasUVs)) {
817
- combinedGeometry.setAttribute('uv', new THREE__namespace.BufferAttribute(combinedUVs, 2));
818
- }
819
- combinedGeometry.setAttribute('skinIndex', new THREE__namespace.Uint16BufferAttribute(combinedSkinIndices, 4));
820
- combinedGeometry.setAttribute('skinWeight', new THREE__namespace.BufferAttribute(combinedSkinWeights, 4));
821
- combinedGeometry.setIndex(new THREE__namespace.BufferAttribute(combinedIndices, 1));
822
778
  combinedGeometry.computeBoundingBox();
823
779
  combinedGeometry.computeBoundingSphere();
824
780
  const skinnedMesh = new THREE__namespace.SkinnedMesh(combinedGeometry, materials);
@@ -836,6 +792,8 @@ class StowKitPack {
836
792
  container.add(skinnedMesh);
837
793
  container.userData.boneOriginalNames = boneOriginalNames;
838
794
  container.userData.skinnedMesh = skinnedMesh;
795
+ const buildTime = performance.now() - buildStart;
796
+ reader.PerfLogger.log(`[Perf] Build skinned mesh: ${buildTime.toFixed(2)}ms (${boneCount} bones, ${geomData.vertexCount} verts)`);
839
797
  return container;
840
798
  }
841
799
  /**