@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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const
|
|
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.
|
|
756
|
-
|
|
757
|
-
|
|
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
|
-
|
|
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
|
/**
|