@stowkit/three-loader 0.1.34 → 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.
|
@@ -610,11 +610,19 @@ class StowKitPack {
|
|
|
610
610
|
materialData.push({ name, schemaId, propertyCount, properties: [] });
|
|
611
611
|
metadataOffset += 196;
|
|
612
612
|
}
|
|
613
|
-
//
|
|
614
|
-
// Node struct:
|
|
613
|
+
// Parse Node array to calculate total mesh indices
|
|
614
|
+
// Node struct: name[64] + parent_index[4] + position[12] + rotation[16] + scale[12] + mesh_count[4] + first_mesh_index[4] = 116 bytes
|
|
615
615
|
const NODE_STRIDE = 116;
|
|
616
|
+
let totalMeshIndices = 0;
|
|
617
|
+
for (let n = 0; n < nodeCount; n++) {
|
|
618
|
+
const nodeOffset = metadataOffset + n * NODE_STRIDE;
|
|
619
|
+
// mesh_count is at offset 64 + 4 + 12 + 16 + 12 = 108 within Node struct
|
|
620
|
+
const meshCount = metaView.getUint32(nodeOffset + 108, true);
|
|
621
|
+
totalMeshIndices += meshCount;
|
|
622
|
+
}
|
|
616
623
|
metadataOffset += nodeCount * NODE_STRIDE;
|
|
617
|
-
|
|
624
|
+
// Skip node_mesh_indices array (totalMeshIndices entries, not nodeCount!)
|
|
625
|
+
metadataOffset += totalMeshIndices * 4;
|
|
618
626
|
// Parse material properties
|
|
619
627
|
const PROPERTY_STRIDE = 144;
|
|
620
628
|
for (let i = 0; i < materialCount; i++) {
|
|
@@ -692,7 +700,8 @@ class StowKitPack {
|
|
|
692
700
|
const bone = bones[i];
|
|
693
701
|
const bindPose = bindPoses[i];
|
|
694
702
|
const parentIndex = parentIndices[i];
|
|
695
|
-
if (parentIndex < 0) {
|
|
703
|
+
if (parentIndex < 0 || parentIndex >= bindPoses.length) {
|
|
704
|
+
// Root bone or invalid parent - use bind pose directly
|
|
696
705
|
const pos = new THREE__namespace.Vector3();
|
|
697
706
|
const quat = new THREE__namespace.Quaternion();
|
|
698
707
|
const scale = new THREE__namespace.Vector3();
|
|
@@ -722,7 +731,9 @@ class StowKitPack {
|
|
|
722
731
|
const material = new THREE__namespace.MeshStandardMaterial({
|
|
723
732
|
side: THREE__namespace.DoubleSide,
|
|
724
733
|
name: mat.name || `Material_${i}`,
|
|
725
|
-
color: new THREE__namespace.Color(0.8, 0.8, 0.8) // Default gray
|
|
734
|
+
color: new THREE__namespace.Color(0.8, 0.8, 0.8), // Default gray
|
|
735
|
+
metalness: 0.5,
|
|
736
|
+
roughness: 0.5
|
|
726
737
|
});
|
|
727
738
|
// Apply non-texture properties
|
|
728
739
|
for (const prop of mat.properties) {
|
|
@@ -789,6 +800,8 @@ class StowKitPack {
|
|
|
789
800
|
skinnedMesh.bind(skeleton, skinnedMesh.matrixWorld);
|
|
790
801
|
skinnedMesh.normalizeSkinWeights();
|
|
791
802
|
skinnedMesh.updateMatrixWorld(true);
|
|
803
|
+
// Ensure frustum culling doesn't hide the mesh if bounds are wrong
|
|
804
|
+
skinnedMesh.frustumCulled = false;
|
|
792
805
|
const container = new THREE__namespace.Group();
|
|
793
806
|
container.name = stringId || 'SkinnedMesh';
|
|
794
807
|
container.add(boneContainer);
|
|
@@ -995,22 +1008,99 @@ class StowKitPack {
|
|
|
995
1008
|
*/
|
|
996
1009
|
async loadAnimation(skinnedMeshGroup, animationPath) {
|
|
997
1010
|
const clip = await this.loadAnimationClip(animationPath);
|
|
1011
|
+
const retargetedClip = this.retargetAnimationClip(clip, skinnedMeshGroup);
|
|
998
1012
|
const mixer = new THREE__namespace.AnimationMixer(skinnedMeshGroup);
|
|
999
|
-
const action = mixer.clipAction(
|
|
1013
|
+
const action = mixer.clipAction(retargetedClip);
|
|
1000
1014
|
action.setLoop(THREE__namespace.LoopRepeat, Infinity);
|
|
1001
1015
|
action.play();
|
|
1002
|
-
return { mixer, action, clip };
|
|
1016
|
+
return { mixer, action, clip: retargetedClip };
|
|
1003
1017
|
}
|
|
1004
1018
|
/**
|
|
1005
1019
|
* Load and play animation by index
|
|
1006
1020
|
*/
|
|
1007
1021
|
async loadAnimationByIndex(skinnedMeshGroup, animationIndex) {
|
|
1008
1022
|
const clip = await this.loadAnimationClipByIndex(animationIndex);
|
|
1023
|
+
const retargetedClip = this.retargetAnimationClip(clip, skinnedMeshGroup);
|
|
1009
1024
|
const mixer = new THREE__namespace.AnimationMixer(skinnedMeshGroup);
|
|
1010
|
-
const action = mixer.clipAction(
|
|
1025
|
+
const action = mixer.clipAction(retargetedClip);
|
|
1011
1026
|
action.setLoop(THREE__namespace.LoopRepeat, Infinity);
|
|
1012
1027
|
action.play();
|
|
1013
|
-
return { mixer, action, clip };
|
|
1028
|
+
return { mixer, action, clip: retargetedClip };
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Retarget animation clip to match skeleton bone names
|
|
1032
|
+
*/
|
|
1033
|
+
retargetAnimationClip(clip, skinnedMeshGroup) {
|
|
1034
|
+
// Collect all bone names from the skeleton
|
|
1035
|
+
const skeletonBoneNames = new Set();
|
|
1036
|
+
skinnedMeshGroup.traverse((child) => {
|
|
1037
|
+
if (child.isBone) {
|
|
1038
|
+
skeletonBoneNames.add(child.name);
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
// Build a mapping from animation bone names to skeleton bone names
|
|
1042
|
+
// Try to match by suffix (e.g., "CharacterRig_MainHipJ" -> "MainHipJ")
|
|
1043
|
+
const boneNameMap = new Map();
|
|
1044
|
+
for (const track of clip.tracks) {
|
|
1045
|
+
const trackParts = track.name.split('.');
|
|
1046
|
+
const animBoneName = trackParts[0];
|
|
1047
|
+
if (skeletonBoneNames.has(animBoneName)) {
|
|
1048
|
+
// Exact match - no remapping needed
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
// Try to find a match by suffix
|
|
1052
|
+
for (const skelBoneName of skeletonBoneNames) {
|
|
1053
|
+
// Check if animation bone name ends with skeleton bone name
|
|
1054
|
+
// e.g., "CharacterRig_MainHipJ" contains "MainHipJ"
|
|
1055
|
+
if (animBoneName.endsWith(skelBoneName) ||
|
|
1056
|
+
animBoneName.endsWith('_' + skelBoneName) ||
|
|
1057
|
+
skelBoneName.endsWith(animBoneName) ||
|
|
1058
|
+
skelBoneName.endsWith('_' + animBoneName)) {
|
|
1059
|
+
boneNameMap.set(animBoneName, skelBoneName);
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
// Also try matching without common prefixes
|
|
1063
|
+
const animSuffix = animBoneName.replace(/^.*_/, '');
|
|
1064
|
+
const skelSuffix = skelBoneName.replace(/^.*_/, '');
|
|
1065
|
+
if (animSuffix === skelSuffix && animSuffix.length > 2) {
|
|
1066
|
+
boneNameMap.set(animBoneName, skelBoneName);
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
// If no remapping needed, return original clip
|
|
1072
|
+
if (boneNameMap.size === 0) {
|
|
1073
|
+
return clip;
|
|
1074
|
+
}
|
|
1075
|
+
console.log(`[StowKit] Retargeting animation: ${boneNameMap.size} bones remapped`);
|
|
1076
|
+
// Create new tracks with remapped names
|
|
1077
|
+
const newTracks = [];
|
|
1078
|
+
for (const track of clip.tracks) {
|
|
1079
|
+
const trackParts = track.name.split('.');
|
|
1080
|
+
const animBoneName = trackParts[0];
|
|
1081
|
+
const property = trackParts.slice(1).join('.');
|
|
1082
|
+
const mappedBoneName = boneNameMap.get(animBoneName) || animBoneName;
|
|
1083
|
+
const newTrackName = `${mappedBoneName}.${property}`;
|
|
1084
|
+
// Clone the track with the new name
|
|
1085
|
+
// Use generic KeyframeTrack constructor to avoid type issues with specific track types
|
|
1086
|
+
const times = track.times;
|
|
1087
|
+
const values = track.values;
|
|
1088
|
+
let newTrack;
|
|
1089
|
+
if (track instanceof THREE__namespace.VectorKeyframeTrack) {
|
|
1090
|
+
newTrack = new THREE__namespace.VectorKeyframeTrack(newTrackName, times, values);
|
|
1091
|
+
}
|
|
1092
|
+
else if (track instanceof THREE__namespace.QuaternionKeyframeTrack) {
|
|
1093
|
+
newTrack = new THREE__namespace.QuaternionKeyframeTrack(newTrackName, times, values);
|
|
1094
|
+
}
|
|
1095
|
+
else if (track instanceof THREE__namespace.NumberKeyframeTrack) {
|
|
1096
|
+
newTrack = new THREE__namespace.NumberKeyframeTrack(newTrackName, times, values);
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
newTrack = new THREE__namespace.KeyframeTrack(newTrackName, times, values);
|
|
1100
|
+
}
|
|
1101
|
+
newTracks.push(newTrack);
|
|
1102
|
+
}
|
|
1103
|
+
return new THREE__namespace.AnimationClip(clip.name, clip.duration, newTracks);
|
|
1014
1104
|
}
|
|
1015
1105
|
/**
|
|
1016
1106
|
* Load animation clip by index
|
|
@@ -1192,6 +1282,8 @@ class StowKitPack {
|
|
|
1192
1282
|
URL.revokeObjectURL(url);
|
|
1193
1283
|
reader.PerfLogger.log(`[Perf] ✓ Texture "${textureName}": 0ms (IndexedDB cache)`);
|
|
1194
1284
|
const texture = new THREE__namespace.CompressedTexture([{ data: cached.data, width: cached.width, height: cached.height }], cached.width, cached.height, cached.format, THREE__namespace.UnsignedByteType);
|
|
1285
|
+
// All textures default to sRGB. Data textures are set to linear in applyTextureToMaterial.
|
|
1286
|
+
texture.colorSpace = THREE__namespace.SRGBColorSpace;
|
|
1195
1287
|
texture.needsUpdate = true;
|
|
1196
1288
|
return texture;
|
|
1197
1289
|
}
|
|
@@ -1199,6 +1291,9 @@ class StowKitPack {
|
|
|
1199
1291
|
const texture = await new Promise((resolve, reject) => {
|
|
1200
1292
|
this.ktx2Loader.load(url, (texture) => {
|
|
1201
1293
|
URL.revokeObjectURL(url);
|
|
1294
|
+
// All textures default to sRGB. Data textures (normal, metallic, roughness)
|
|
1295
|
+
// are set back to linear in applyTextureToMaterial.
|
|
1296
|
+
texture.colorSpace = THREE__namespace.SRGBColorSpace;
|
|
1202
1297
|
texture.needsUpdate = true;
|
|
1203
1298
|
resolve(texture);
|
|
1204
1299
|
}, undefined, (error) => {
|
|
@@ -1232,18 +1327,26 @@ class StowKitPack {
|
|
|
1232
1327
|
const propNameLower = propertyName.toLowerCase();
|
|
1233
1328
|
if (propNameLower === 'maintex' || propNameLower.includes('diffuse') ||
|
|
1234
1329
|
propNameLower.includes('albedo') || propNameLower.includes('base')) {
|
|
1330
|
+
// Color texture - keep sRGB (default)
|
|
1235
1331
|
material.map = texture;
|
|
1236
1332
|
}
|
|
1237
1333
|
else if (propNameLower.includes('normal')) {
|
|
1334
|
+
// Data texture - use linear
|
|
1335
|
+
texture.colorSpace = THREE__namespace.LinearSRGBColorSpace;
|
|
1238
1336
|
material.normalMap = texture;
|
|
1239
1337
|
}
|
|
1240
1338
|
else if (propNameLower.includes('metallic')) {
|
|
1339
|
+
// Data texture - use linear
|
|
1340
|
+
texture.colorSpace = THREE__namespace.LinearSRGBColorSpace;
|
|
1241
1341
|
material.metalnessMap = texture;
|
|
1242
1342
|
}
|
|
1243
1343
|
else if (propNameLower.includes('roughness')) {
|
|
1344
|
+
// Data texture - use linear
|
|
1345
|
+
texture.colorSpace = THREE__namespace.LinearSRGBColorSpace;
|
|
1244
1346
|
material.roughnessMap = texture;
|
|
1245
1347
|
}
|
|
1246
1348
|
else {
|
|
1349
|
+
// Default to color texture - keep sRGB
|
|
1247
1350
|
material.map = texture;
|
|
1248
1351
|
}
|
|
1249
1352
|
material.needsUpdate = true;
|