@sage-rsc/talking-head-react 1.0.84 → 1.1.1
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.
- package/dist/index.cjs +2 -2
- package/dist/index.js +641 -624
- package/package.json +1 -1
- package/src/lib/talkinghead.mjs +93 -45
package/package.json
CHANGED
package/src/lib/talkinghead.mjs
CHANGED
|
@@ -2214,38 +2214,35 @@ class TalkingHead {
|
|
|
2214
2214
|
console.log('Original position stored:', this.originalPosition);
|
|
2215
2215
|
}
|
|
2216
2216
|
|
|
2217
|
-
//
|
|
2218
|
-
// Set a higher base position so FBX animations land in the center
|
|
2219
|
-
const upwardOffset = 2.0; // Even more upward movement
|
|
2220
|
-
|
|
2217
|
+
// Lock the avatar at its CURRENT position (don't move it)
|
|
2221
2218
|
this.lockedPosition = {
|
|
2222
|
-
x:
|
|
2223
|
-
y:
|
|
2224
|
-
z:
|
|
2219
|
+
x: this.armature.position.x,
|
|
2220
|
+
y: this.armature.position.y,
|
|
2221
|
+
z: this.armature.position.z
|
|
2225
2222
|
};
|
|
2226
2223
|
|
|
2227
|
-
|
|
2228
|
-
this.armature.position.set(
|
|
2229
|
-
this.lockedPosition.x,
|
|
2230
|
-
this.lockedPosition.y,
|
|
2231
|
-
this.lockedPosition.z
|
|
2232
|
-
);
|
|
2233
|
-
|
|
2234
|
-
console.log('BEFORE: Avatar position was:', this.armature.position.x, this.armature.position.y, this.armature.position.z);
|
|
2235
|
-
console.log('AFTER: Avatar position moved up and locked at:', this.lockedPosition);
|
|
2236
|
-
console.log('Current view:', this.viewName);
|
|
2224
|
+
console.log('Avatar position locked at current position:', this.lockedPosition);
|
|
2237
2225
|
}
|
|
2238
2226
|
|
|
2239
2227
|
/**
|
|
2240
|
-
* Unlock avatar position and
|
|
2228
|
+
* Unlock avatar position and restore original position.
|
|
2241
2229
|
*/
|
|
2242
2230
|
unlockAvatarPosition() {
|
|
2243
|
-
if (this.armature) {
|
|
2244
|
-
//
|
|
2231
|
+
if (this.armature && this.originalPosition) {
|
|
2232
|
+
// Restore avatar to its original position before locking
|
|
2233
|
+
this.armature.position.set(
|
|
2234
|
+
this.originalPosition.x,
|
|
2235
|
+
this.originalPosition.y,
|
|
2236
|
+
this.originalPosition.z
|
|
2237
|
+
);
|
|
2238
|
+
console.log('Avatar position restored to original:', this.originalPosition);
|
|
2239
|
+
} else if (this.armature) {
|
|
2240
|
+
// Fallback: reset to center if no original position was stored
|
|
2245
2241
|
this.armature.position.set(0, 0, 0);
|
|
2246
2242
|
console.log('Avatar position reset to center (0,0,0)');
|
|
2247
2243
|
}
|
|
2248
2244
|
this.lockedPosition = null;
|
|
2245
|
+
this.originalPosition = null; // Clear original position after unlock
|
|
2249
2246
|
console.log('Avatar position unlocked');
|
|
2250
2247
|
}
|
|
2251
2248
|
|
|
@@ -2254,31 +2251,13 @@ class TalkingHead {
|
|
|
2254
2251
|
*/
|
|
2255
2252
|
maintainLockedPosition() {
|
|
2256
2253
|
if (this.lockedPosition && this.armature) {
|
|
2257
|
-
//
|
|
2258
|
-
// This
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
// If moved too far down, bring it back up
|
|
2265
|
-
this.armature.position.set(
|
|
2266
|
-
this.lockedPosition.x,
|
|
2267
|
-
minY,
|
|
2268
|
-
this.lockedPosition.z
|
|
2269
|
-
);
|
|
2270
|
-
} else if (currentY > maxY) {
|
|
2271
|
-
// If moved too far up, bring it back down
|
|
2272
|
-
this.armature.position.set(
|
|
2273
|
-
this.lockedPosition.x,
|
|
2274
|
-
maxY,
|
|
2275
|
-
this.lockedPosition.z
|
|
2276
|
-
);
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
// Always maintain X and Z position
|
|
2280
|
-
this.armature.position.x = this.lockedPosition.x;
|
|
2281
|
-
this.armature.position.z = this.lockedPosition.z;
|
|
2254
|
+
// Enforce the locked position - keep avatar exactly where it was locked
|
|
2255
|
+
// This prevents FBX animations from moving the avatar
|
|
2256
|
+
this.armature.position.set(
|
|
2257
|
+
this.lockedPosition.x,
|
|
2258
|
+
this.lockedPosition.y,
|
|
2259
|
+
this.lockedPosition.z
|
|
2260
|
+
);
|
|
2282
2261
|
}
|
|
2283
2262
|
}
|
|
2284
2263
|
|
|
@@ -5344,6 +5323,61 @@ class TalkingHead {
|
|
|
5344
5323
|
* @param {number} [ndx=0] Index of the clip
|
|
5345
5324
|
* @param {number} [scale=0.01] Position scale factor
|
|
5346
5325
|
*/
|
|
5326
|
+
/**
|
|
5327
|
+
* Get all bone names from the avatar's armature
|
|
5328
|
+
* @returns {Set<string>} Set of bone names
|
|
5329
|
+
*/
|
|
5330
|
+
getAvailableBoneNames() {
|
|
5331
|
+
const boneNames = new Set();
|
|
5332
|
+
if (!this.armature) return boneNames;
|
|
5333
|
+
|
|
5334
|
+
this.armature.traverse((child) => {
|
|
5335
|
+
if (child.isBone || child.type === 'Bone') {
|
|
5336
|
+
boneNames.add(child.name);
|
|
5337
|
+
}
|
|
5338
|
+
});
|
|
5339
|
+
|
|
5340
|
+
return boneNames;
|
|
5341
|
+
}
|
|
5342
|
+
|
|
5343
|
+
/**
|
|
5344
|
+
* Filter animation tracks to only include bones that exist in the avatar
|
|
5345
|
+
* @param {THREE.AnimationClip} clip - Animation clip to filter
|
|
5346
|
+
* @param {Set<string>} availableBones - Set of available bone names
|
|
5347
|
+
* @returns {THREE.AnimationClip} Filtered animation clip
|
|
5348
|
+
*/
|
|
5349
|
+
filterAnimationTracks(clip, availableBones) {
|
|
5350
|
+
const validTracks = [];
|
|
5351
|
+
const missingBones = new Set();
|
|
5352
|
+
|
|
5353
|
+
clip.tracks.forEach(track => {
|
|
5354
|
+
// Extract bone name from track name (e.g., "CC_Base_R_Index3.position" -> "CC_Base_R_Index3")
|
|
5355
|
+
const trackNameParts = track.name.split('.');
|
|
5356
|
+
const boneName = trackNameParts[0];
|
|
5357
|
+
|
|
5358
|
+
if (availableBones.has(boneName)) {
|
|
5359
|
+
validTracks.push(track);
|
|
5360
|
+
} else {
|
|
5361
|
+
missingBones.add(boneName);
|
|
5362
|
+
}
|
|
5363
|
+
});
|
|
5364
|
+
|
|
5365
|
+
if (missingBones.size > 0) {
|
|
5366
|
+
console.warn(`FBX animation "${clip.name}" contains tracks for ${missingBones.size} bone(s) not found in avatar skeleton:`, Array.from(missingBones).slice(0, 10).join(', '), missingBones.size > 10 ? '...' : '');
|
|
5367
|
+
console.info(`Filtered ${clip.tracks.length} tracks down to ${validTracks.length} valid tracks`);
|
|
5368
|
+
} else if (validTracks.length > 0) {
|
|
5369
|
+
console.info(`FBX animation "${clip.name}" is fully compatible: all ${validTracks.length} tracks match avatar skeleton`);
|
|
5370
|
+
}
|
|
5371
|
+
|
|
5372
|
+
// Create a new clip with only valid tracks
|
|
5373
|
+
if (validTracks.length === 0) {
|
|
5374
|
+
console.error(`No valid tracks found for animation "${clip.name}". All bones are missing from avatar skeleton.`);
|
|
5375
|
+
return null;
|
|
5376
|
+
}
|
|
5377
|
+
|
|
5378
|
+
return new THREE.AnimationClip(clip.name, clip.duration, validTracks);
|
|
5379
|
+
}
|
|
5380
|
+
|
|
5347
5381
|
async playAnimation(url, onprogress=null, dur=10, ndx=0, scale=0.01, disablePositionLock=false) {
|
|
5348
5382
|
if ( !this.armature ) return;
|
|
5349
5383
|
|
|
@@ -5491,6 +5525,20 @@ class TalkingHead {
|
|
|
5491
5525
|
if ( fbx && fbx.animations && fbx.animations[ndx] ) {
|
|
5492
5526
|
let anim = fbx.animations[ndx];
|
|
5493
5527
|
|
|
5528
|
+
// Get available bone names from avatar skeleton
|
|
5529
|
+
const availableBones = this.getAvailableBoneNames();
|
|
5530
|
+
|
|
5531
|
+
// Filter animation tracks to only include bones that exist
|
|
5532
|
+
const filteredAnim = this.filterAnimationTracks(anim, availableBones);
|
|
5533
|
+
|
|
5534
|
+
if (!filteredAnim) {
|
|
5535
|
+
console.error(`Cannot play FBX animation "${url}": No compatible bones found.`);
|
|
5536
|
+
return;
|
|
5537
|
+
}
|
|
5538
|
+
|
|
5539
|
+
// Use the filtered animation instead of the original
|
|
5540
|
+
anim = filteredAnim;
|
|
5541
|
+
|
|
5494
5542
|
// Rename and scale Mixamo tracks, create a pose
|
|
5495
5543
|
const props = {};
|
|
5496
5544
|
anim.tracks.forEach( t => {
|