@stowkit/three-loader 0.1.35 → 0.1.39

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.
@@ -101,6 +101,10 @@ export declare class StowKitPack {
101
101
  action: THREE.AnimationAction;
102
102
  clip: THREE.AnimationClip;
103
103
  }>;
104
+ /**
105
+ * Retarget animation clip to match skeleton bone names
106
+ */
107
+ private retargetAnimationClip;
104
108
  /**
105
109
  * Load animation clip by index
106
110
  */
@@ -1 +1 @@
1
- {"version":3,"file":"StowKitPack.d.ts","sourceRoot":"","sources":["../src/StowKitPack.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"StowKitPack.d.ts","sourceRoot":"","sources":["../src/StowKitPack.ts"],"names":[],"mappings":"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"}
@@ -590,11 +590,19 @@ class StowKitPack {
590
590
  materialData.push({ name, schemaId, propertyCount, properties: [] });
591
591
  metadataOffset += 196;
592
592
  }
593
- // Skip Node array and node_mesh_indices (not needed for rendering)
594
- // Node struct: transform[16 floats] + name[64 bytes] + mesh_index[4 bytes] + parent_index[4 bytes] = 116 bytes
593
+ // Parse Node array to calculate total mesh indices
594
+ // Node struct: name[64] + parent_index[4] + position[12] + rotation[16] + scale[12] + mesh_count[4] + first_mesh_index[4] = 116 bytes
595
595
  const NODE_STRIDE = 116;
596
+ let totalMeshIndices = 0;
597
+ for (let n = 0; n < nodeCount; n++) {
598
+ const nodeOffset = metadataOffset + n * NODE_STRIDE;
599
+ // mesh_count is at offset 64 + 4 + 12 + 16 + 12 = 108 within Node struct
600
+ const meshCount = metaView.getUint32(nodeOffset + 108, true);
601
+ totalMeshIndices += meshCount;
602
+ }
596
603
  metadataOffset += nodeCount * NODE_STRIDE;
597
- metadataOffset += nodeCount * 4;
604
+ // Skip node_mesh_indices array (totalMeshIndices entries, not nodeCount!)
605
+ metadataOffset += totalMeshIndices * 4;
598
606
  // Parse material properties
599
607
  const PROPERTY_STRIDE = 144;
600
608
  for (let i = 0; i < materialCount; i++) {
@@ -672,7 +680,8 @@ class StowKitPack {
672
680
  const bone = bones[i];
673
681
  const bindPose = bindPoses[i];
674
682
  const parentIndex = parentIndices[i];
675
- if (parentIndex < 0) {
683
+ if (parentIndex < 0 || parentIndex >= bindPoses.length) {
684
+ // Root bone or invalid parent - use bind pose directly
676
685
  const pos = new THREE.Vector3();
677
686
  const quat = new THREE.Quaternion();
678
687
  const scale = new THREE.Vector3();
@@ -702,7 +711,9 @@ class StowKitPack {
702
711
  const material = new THREE.MeshStandardMaterial({
703
712
  side: THREE.DoubleSide,
704
713
  name: mat.name || `Material_${i}`,
705
- color: new THREE.Color(0.8, 0.8, 0.8) // Default gray
714
+ color: new THREE.Color(0.8, 0.8, 0.8), // Default gray
715
+ metalness: 0.5,
716
+ roughness: 0.5
706
717
  });
707
718
  // Apply non-texture properties
708
719
  for (const prop of mat.properties) {
@@ -769,6 +780,8 @@ class StowKitPack {
769
780
  skinnedMesh.bind(skeleton, skinnedMesh.matrixWorld);
770
781
  skinnedMesh.normalizeSkinWeights();
771
782
  skinnedMesh.updateMatrixWorld(true);
783
+ // Ensure frustum culling doesn't hide the mesh if bounds are wrong
784
+ skinnedMesh.frustumCulled = false;
772
785
  const container = new THREE.Group();
773
786
  container.name = stringId || 'SkinnedMesh';
774
787
  container.add(boneContainer);
@@ -975,22 +988,99 @@ class StowKitPack {
975
988
  */
976
989
  async loadAnimation(skinnedMeshGroup, animationPath) {
977
990
  const clip = await this.loadAnimationClip(animationPath);
991
+ const retargetedClip = this.retargetAnimationClip(clip, skinnedMeshGroup);
978
992
  const mixer = new THREE.AnimationMixer(skinnedMeshGroup);
979
- const action = mixer.clipAction(clip);
993
+ const action = mixer.clipAction(retargetedClip);
980
994
  action.setLoop(THREE.LoopRepeat, Infinity);
981
995
  action.play();
982
- return { mixer, action, clip };
996
+ return { mixer, action, clip: retargetedClip };
983
997
  }
984
998
  /**
985
999
  * Load and play animation by index
986
1000
  */
987
1001
  async loadAnimationByIndex(skinnedMeshGroup, animationIndex) {
988
1002
  const clip = await this.loadAnimationClipByIndex(animationIndex);
1003
+ const retargetedClip = this.retargetAnimationClip(clip, skinnedMeshGroup);
989
1004
  const mixer = new THREE.AnimationMixer(skinnedMeshGroup);
990
- const action = mixer.clipAction(clip);
1005
+ const action = mixer.clipAction(retargetedClip);
991
1006
  action.setLoop(THREE.LoopRepeat, Infinity);
992
1007
  action.play();
993
- return { mixer, action, clip };
1008
+ return { mixer, action, clip: retargetedClip };
1009
+ }
1010
+ /**
1011
+ * Retarget animation clip to match skeleton bone names
1012
+ */
1013
+ retargetAnimationClip(clip, skinnedMeshGroup) {
1014
+ // Collect all bone names from the skeleton
1015
+ const skeletonBoneNames = new Set();
1016
+ skinnedMeshGroup.traverse((child) => {
1017
+ if (child.isBone) {
1018
+ skeletonBoneNames.add(child.name);
1019
+ }
1020
+ });
1021
+ // Build a mapping from animation bone names to skeleton bone names
1022
+ // Try to match by suffix (e.g., "CharacterRig_MainHipJ" -> "MainHipJ")
1023
+ const boneNameMap = new Map();
1024
+ for (const track of clip.tracks) {
1025
+ const trackParts = track.name.split('.');
1026
+ const animBoneName = trackParts[0];
1027
+ if (skeletonBoneNames.has(animBoneName)) {
1028
+ // Exact match - no remapping needed
1029
+ continue;
1030
+ }
1031
+ // Try to find a match by suffix
1032
+ for (const skelBoneName of skeletonBoneNames) {
1033
+ // Check if animation bone name ends with skeleton bone name
1034
+ // e.g., "CharacterRig_MainHipJ" contains "MainHipJ"
1035
+ if (animBoneName.endsWith(skelBoneName) ||
1036
+ animBoneName.endsWith('_' + skelBoneName) ||
1037
+ skelBoneName.endsWith(animBoneName) ||
1038
+ skelBoneName.endsWith('_' + animBoneName)) {
1039
+ boneNameMap.set(animBoneName, skelBoneName);
1040
+ break;
1041
+ }
1042
+ // Also try matching without common prefixes
1043
+ const animSuffix = animBoneName.replace(/^.*_/, '');
1044
+ const skelSuffix = skelBoneName.replace(/^.*_/, '');
1045
+ if (animSuffix === skelSuffix && animSuffix.length > 2) {
1046
+ boneNameMap.set(animBoneName, skelBoneName);
1047
+ break;
1048
+ }
1049
+ }
1050
+ }
1051
+ // If no remapping needed, return original clip
1052
+ if (boneNameMap.size === 0) {
1053
+ return clip;
1054
+ }
1055
+ console.log(`[StowKit] Retargeting animation: ${boneNameMap.size} bones remapped`);
1056
+ // Create new tracks with remapped names
1057
+ const newTracks = [];
1058
+ for (const track of clip.tracks) {
1059
+ const trackParts = track.name.split('.');
1060
+ const animBoneName = trackParts[0];
1061
+ const property = trackParts.slice(1).join('.');
1062
+ const mappedBoneName = boneNameMap.get(animBoneName) || animBoneName;
1063
+ const newTrackName = `${mappedBoneName}.${property}`;
1064
+ // Clone the track with the new name
1065
+ // Use generic KeyframeTrack constructor to avoid type issues with specific track types
1066
+ const times = track.times;
1067
+ const values = track.values;
1068
+ let newTrack;
1069
+ if (track instanceof THREE.VectorKeyframeTrack) {
1070
+ newTrack = new THREE.VectorKeyframeTrack(newTrackName, times, values);
1071
+ }
1072
+ else if (track instanceof THREE.QuaternionKeyframeTrack) {
1073
+ newTrack = new THREE.QuaternionKeyframeTrack(newTrackName, times, values);
1074
+ }
1075
+ else if (track instanceof THREE.NumberKeyframeTrack) {
1076
+ newTrack = new THREE.NumberKeyframeTrack(newTrackName, times, values);
1077
+ }
1078
+ else {
1079
+ newTrack = new THREE.KeyframeTrack(newTrackName, times, values);
1080
+ }
1081
+ newTracks.push(newTrack);
1082
+ }
1083
+ return new THREE.AnimationClip(clip.name, clip.duration, newTracks);
994
1084
  }
995
1085
  /**
996
1086
  * Load animation clip by index
@@ -1172,6 +1262,8 @@ class StowKitPack {
1172
1262
  URL.revokeObjectURL(url);
1173
1263
  PerfLogger.log(`[Perf] ✓ Texture "${textureName}": 0ms (IndexedDB cache)`);
1174
1264
  const texture = new THREE.CompressedTexture([{ data: cached.data, width: cached.width, height: cached.height }], cached.width, cached.height, cached.format, THREE.UnsignedByteType);
1265
+ // All textures default to sRGB. Data textures are set to linear in applyTextureToMaterial.
1266
+ texture.colorSpace = THREE.SRGBColorSpace;
1175
1267
  texture.needsUpdate = true;
1176
1268
  return texture;
1177
1269
  }
@@ -1179,6 +1271,9 @@ class StowKitPack {
1179
1271
  const texture = await new Promise((resolve, reject) => {
1180
1272
  this.ktx2Loader.load(url, (texture) => {
1181
1273
  URL.revokeObjectURL(url);
1274
+ // All textures default to sRGB. Data textures (normal, metallic, roughness)
1275
+ // are set back to linear in applyTextureToMaterial.
1276
+ texture.colorSpace = THREE.SRGBColorSpace;
1182
1277
  texture.needsUpdate = true;
1183
1278
  resolve(texture);
1184
1279
  }, undefined, (error) => {
@@ -1212,18 +1307,26 @@ class StowKitPack {
1212
1307
  const propNameLower = propertyName.toLowerCase();
1213
1308
  if (propNameLower === 'maintex' || propNameLower.includes('diffuse') ||
1214
1309
  propNameLower.includes('albedo') || propNameLower.includes('base')) {
1310
+ // Color texture - keep sRGB (default)
1215
1311
  material.map = texture;
1216
1312
  }
1217
1313
  else if (propNameLower.includes('normal')) {
1314
+ // Data texture - use linear
1315
+ texture.colorSpace = THREE.LinearSRGBColorSpace;
1218
1316
  material.normalMap = texture;
1219
1317
  }
1220
1318
  else if (propNameLower.includes('metallic')) {
1319
+ // Data texture - use linear
1320
+ texture.colorSpace = THREE.LinearSRGBColorSpace;
1221
1321
  material.metalnessMap = texture;
1222
1322
  }
1223
1323
  else if (propNameLower.includes('roughness')) {
1324
+ // Data texture - use linear
1325
+ texture.colorSpace = THREE.LinearSRGBColorSpace;
1224
1326
  material.roughnessMap = texture;
1225
1327
  }
1226
1328
  else {
1329
+ // Default to color texture - keep sRGB
1227
1330
  material.map = texture;
1228
1331
  }
1229
1332
  material.needsUpdate = true;