@sage-rsc/talking-head-react 1.4.0 → 1.4.2
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 +1 -1
- package/dist/index.js +3179 -1355
- package/package.json +1 -1
- package/src/lib/talkinghead.mjs +59 -24
- package/src/lib/talkinghead.mjs.new +2 -2
package/package.json
CHANGED
package/src/lib/talkinghead.mjs
CHANGED
|
@@ -1128,7 +1128,7 @@ class TalkingHead {
|
|
|
1128
1128
|
});
|
|
1129
1129
|
} else {
|
|
1130
1130
|
if (obj.material.map) obj.material.map.dispose();
|
|
1131
|
-
|
|
1131
|
+
obj.material.dispose();
|
|
1132
1132
|
}
|
|
1133
1133
|
}
|
|
1134
1134
|
}
|
|
@@ -1244,7 +1244,7 @@ class TalkingHead {
|
|
|
1244
1244
|
this.lockedPosition = null;
|
|
1245
1245
|
this.originalPosition = null;
|
|
1246
1246
|
this.positionWasLocked = false;
|
|
1247
|
-
|
|
1247
|
+
|
|
1248
1248
|
// Initialize FBX animation loader
|
|
1249
1249
|
this.fbxAnimationLoader = null;
|
|
1250
1250
|
|
|
@@ -1256,7 +1256,7 @@ class TalkingHead {
|
|
|
1256
1256
|
this.mixer.removeEventListener('finished', this._mixerHandler);
|
|
1257
1257
|
this.mixer.stopAllAction();
|
|
1258
1258
|
this.mixer.uncacheRoot(this.armature);
|
|
1259
|
-
|
|
1259
|
+
this.mixer = null;
|
|
1260
1260
|
this._mixerHandler = null;
|
|
1261
1261
|
}
|
|
1262
1262
|
if ( this.isAvatarOnly ) {
|
|
@@ -1512,6 +1512,11 @@ class TalkingHead {
|
|
|
1512
1512
|
this.controlsEnd = new THREE.Vector3(x, y, 0);
|
|
1513
1513
|
this.cameraEnd = new THREE.Vector3(x, y, z).applyEuler( new THREE.Euler( cameraRotateX, cameraRotateY, 0 ) );
|
|
1514
1514
|
|
|
1515
|
+
// Guard against null controls (e.g., in avatarOnly mode or before initialization)
|
|
1516
|
+
if ( !this.controls ) {
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1515
1520
|
if ( this.cameraClock === null ) {
|
|
1516
1521
|
this.controls.target.copy( this.controlsEnd );
|
|
1517
1522
|
this.camera.position.copy( this.cameraEnd );
|
|
@@ -1585,7 +1590,9 @@ class TalkingHead {
|
|
|
1585
1590
|
this.camera.aspect = this.nodeAvatar.clientWidth / this.nodeAvatar.clientHeight;
|
|
1586
1591
|
this.camera.updateProjectionMatrix();
|
|
1587
1592
|
this.renderer.setSize( this.nodeAvatar.clientWidth, this.nodeAvatar.clientHeight );
|
|
1593
|
+
if ( this.controls ) {
|
|
1588
1594
|
this.controls.update();
|
|
1595
|
+
}
|
|
1589
1596
|
this.render();
|
|
1590
1597
|
}
|
|
1591
1598
|
}
|
|
@@ -2040,6 +2047,11 @@ class TalkingHead {
|
|
|
2040
2047
|
*/
|
|
2041
2048
|
setPoseFromTemplate(template, ms=2000) {
|
|
2042
2049
|
|
|
2050
|
+
// Guard against disposal: check if required objects exist
|
|
2051
|
+
if (!this.poseFactory || !this.poseTemplates) {
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2043
2055
|
// Special cases
|
|
2044
2056
|
const isIntermediate = template && this.poseTarget && this.poseTarget.template && ((this.poseTarget.template.standing && template.lying) || (this.poseTarget.template.lying && template.standing));
|
|
2045
2057
|
const isSameTemplate = template && (template === this.poseCurrentTemplate);
|
|
@@ -2053,13 +2065,18 @@ class TalkingHead {
|
|
|
2053
2065
|
this.setPoseFromTemplate(template,ms);
|
|
2054
2066
|
}, duration);
|
|
2055
2067
|
} else {
|
|
2056
|
-
this.poseCurrentTemplate = template ||
|
|
2068
|
+
this.poseCurrentTemplate = template || this.poseCurrentTemplate;
|
|
2057
2069
|
}
|
|
2058
2070
|
|
|
2059
2071
|
// Set target
|
|
2060
2072
|
this.poseTarget = this.poseFactory(this.poseCurrentTemplate, duration);
|
|
2061
2073
|
this.poseWeightOnLeft = true;
|
|
2062
2074
|
|
|
2075
|
+
// Guard: ensure poseTarget was created successfully
|
|
2076
|
+
if (!this.poseTarget || !this.poseTarget.props) {
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2063
2080
|
// Mirror properties, if necessary
|
|
2064
2081
|
if ( (!isSameTemplate && !isWeightOnLeft) || (isSameTemplate && isWeightOnLeft ) ) {
|
|
2065
2082
|
this.poseTarget.props = this.mirrorPose(this.poseTarget.props);
|
|
@@ -2078,13 +2095,16 @@ class TalkingHead {
|
|
|
2078
2095
|
}
|
|
2079
2096
|
|
|
2080
2097
|
// Make sure deltas are included in the target
|
|
2098
|
+
// Guard against disposal: check if poseBase and its props exist
|
|
2099
|
+
if (this.poseBase && this.poseBase.props && this.poseDelta && this.poseDelta.props) {
|
|
2081
2100
|
Object.keys(this.poseDelta.props).forEach( key => {
|
|
2082
|
-
|
|
2101
|
+
if ( !this.poseTarget.props.hasOwnProperty(key) && this.poseBase.props[key] ) {
|
|
2083
2102
|
this.poseTarget.props[key] = this.poseBase.props[key].clone();
|
|
2084
2103
|
this.poseTarget.props[key].t = this.animClock;
|
|
2085
2104
|
this.poseTarget.props[key].d = duration;
|
|
2086
2105
|
}
|
|
2087
2106
|
});
|
|
2107
|
+
}
|
|
2088
2108
|
|
|
2089
2109
|
}
|
|
2090
2110
|
|
|
@@ -2816,7 +2836,7 @@ class TalkingHead {
|
|
|
2816
2836
|
} else {
|
|
2817
2837
|
|
|
2818
2838
|
// Camera
|
|
2819
|
-
if ( this.cameraClock !== null && this.cameraClock < 1000 ) {
|
|
2839
|
+
if ( this.controls && this.cameraClock !== null && this.cameraClock < 1000 ) {
|
|
2820
2840
|
this.cameraClock += dt;
|
|
2821
2841
|
if ( this.cameraClock > 1000 ) this.cameraClock = 1000;
|
|
2822
2842
|
let s = new THREE.Spherical().setFromVector3(this.cameraStart);
|
|
@@ -2841,7 +2861,7 @@ class TalkingHead {
|
|
|
2841
2861
|
}
|
|
2842
2862
|
|
|
2843
2863
|
// Autorotate
|
|
2844
|
-
if ( this.controls.autoRotate ) this.controls.update();
|
|
2864
|
+
if ( this.controls && this.controls.autoRotate ) this.controls.update();
|
|
2845
2865
|
|
|
2846
2866
|
// Statistics end
|
|
2847
2867
|
if ( this.stats ) {
|
|
@@ -2868,17 +2888,24 @@ class TalkingHead {
|
|
|
2868
2888
|
}
|
|
2869
2889
|
|
|
2870
2890
|
/**
|
|
2871
|
-
* Get lip-sync processor based on language.
|
|
2891
|
+
* Get lip-sync processor based on language. Use statically imported modules.
|
|
2872
2892
|
* @param {string} lang Language
|
|
2873
|
-
* @param {string} [path="./"] Module path
|
|
2893
|
+
* @param {string} [path="./"] Module path (ignored, using static imports)
|
|
2874
2894
|
*/
|
|
2875
2895
|
lipsyncGetProcessor(lang, path="./") {
|
|
2876
2896
|
if ( !this.lipsync.hasOwnProperty(lang) ) {
|
|
2877
|
-
const
|
|
2897
|
+
const langLower = lang.toLowerCase();
|
|
2898
|
+
// Use statically imported modules from LIPSYNC_MODULES
|
|
2899
|
+
if (LIPSYNC_MODULES[langLower]) {
|
|
2878
2900
|
const className = 'Lipsync' + lang.charAt(0).toUpperCase() + lang.slice(1);
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2901
|
+
if (LIPSYNC_MODULES[langLower][className]) {
|
|
2902
|
+
this.lipsync[lang] = new LIPSYNC_MODULES[langLower][className];
|
|
2903
|
+
} else {
|
|
2904
|
+
console.warn(`Lipsync class ${className} not found in module for language ${lang}`);
|
|
2905
|
+
}
|
|
2906
|
+
} else {
|
|
2907
|
+
console.warn(`Lipsync module for language ${lang} not found in static imports`);
|
|
2908
|
+
}
|
|
2882
2909
|
}
|
|
2883
2910
|
}
|
|
2884
2911
|
|
|
@@ -4326,10 +4353,10 @@ class TalkingHead {
|
|
|
4326
4353
|
setSlowdownRate(k) {
|
|
4327
4354
|
this.animSlowdownRate = k;
|
|
4328
4355
|
if ( this.audioSpeechSource ) {
|
|
4329
|
-
|
|
4356
|
+
this.audioSpeechSource.playbackRate.value = 1 / this.animSlowdownRate;
|
|
4330
4357
|
}
|
|
4331
4358
|
if ( this.audioBackgroundSource ) {
|
|
4332
|
-
|
|
4359
|
+
this.audioBackgroundSource.playbackRate.value = 1 / this.animSlowdownRate;
|
|
4333
4360
|
}
|
|
4334
4361
|
}
|
|
4335
4362
|
|
|
@@ -4338,7 +4365,7 @@ class TalkingHead {
|
|
|
4338
4365
|
* @return {numeric} Autorotate speed.
|
|
4339
4366
|
*/
|
|
4340
4367
|
getAutoRotateSpeed(k) {
|
|
4341
|
-
return this.controls.autoRotateSpeed;
|
|
4368
|
+
return this.controls ? this.controls.autoRotateSpeed : 0;
|
|
4342
4369
|
}
|
|
4343
4370
|
|
|
4344
4371
|
/**
|
|
@@ -4346,8 +4373,10 @@ class TalkingHead {
|
|
|
4346
4373
|
* @param {numeric} speed Autorotate speed, e.g. value 2 = 30 secs per orbit at 60fps.
|
|
4347
4374
|
*/
|
|
4348
4375
|
setAutoRotateSpeed(speed) {
|
|
4349
|
-
this.controls
|
|
4350
|
-
|
|
4376
|
+
if ( this.controls ) {
|
|
4377
|
+
this.controls.autoRotateSpeed = speed;
|
|
4378
|
+
this.controls.autoRotate = (speed > 0);
|
|
4379
|
+
}
|
|
4351
4380
|
}
|
|
4352
4381
|
|
|
4353
4382
|
/**
|
|
@@ -4737,7 +4766,7 @@ class TalkingHead {
|
|
|
4737
4766
|
this.mixer = null;
|
|
4738
4767
|
this._mixerHandler = null;
|
|
4739
4768
|
}
|
|
4740
|
-
|
|
4769
|
+
|
|
4741
4770
|
// Unlock position if it was locked
|
|
4742
4771
|
if (this.positionWasLocked) {
|
|
4743
4772
|
this.unlockAvatarPosition();
|
|
@@ -4747,7 +4776,7 @@ class TalkingHead {
|
|
|
4747
4776
|
}
|
|
4748
4777
|
|
|
4749
4778
|
// Restart gesture
|
|
4750
|
-
if ( this.gesture ) {
|
|
4779
|
+
if ( this.gesture && this.poseTarget && this.poseTarget.props ) {
|
|
4751
4780
|
for( let [p,v] of Object.entries(this.gesture) ) {
|
|
4752
4781
|
v.t = this.animClock;
|
|
4753
4782
|
v.d = 1000;
|
|
@@ -4760,11 +4789,17 @@ class TalkingHead {
|
|
|
4760
4789
|
}
|
|
4761
4790
|
|
|
4762
4791
|
// Restart pose animation
|
|
4792
|
+
if ( this.animQueue ) {
|
|
4763
4793
|
let anim = this.animQueue.find( x => x.template.name === 'pose' );
|
|
4764
4794
|
if ( anim ) {
|
|
4765
4795
|
anim.ts[0] = this.animClock;
|
|
4766
4796
|
}
|
|
4797
|
+
}
|
|
4798
|
+
|
|
4799
|
+
// Only call setPoseFromTemplate if poseFactory exists (not disposed)
|
|
4800
|
+
if ( this.poseFactory ) {
|
|
4767
4801
|
this.setPoseFromTemplate( null );
|
|
4802
|
+
}
|
|
4768
4803
|
|
|
4769
4804
|
}
|
|
4770
4805
|
|
|
@@ -4799,7 +4834,7 @@ class TalkingHead {
|
|
|
4799
4834
|
this.mixer.removeEventListener('finished', this._mixerHandler);
|
|
4800
4835
|
this.mixer.stopAllAction();
|
|
4801
4836
|
this.mixer.uncacheRoot(this.armature);
|
|
4802
|
-
|
|
4837
|
+
this.mixer = null;
|
|
4803
4838
|
this._mixerHandler = null;
|
|
4804
4839
|
}
|
|
4805
4840
|
let anim = this.animQueue.find( x => x.template.name === 'pose' );
|
|
@@ -5514,7 +5549,7 @@ class TalkingHead {
|
|
|
5514
5549
|
this.clearThree(this.scene);
|
|
5515
5550
|
this.resizeobserver.disconnect();
|
|
5516
5551
|
this.resizeobserver = null;
|
|
5517
|
-
|
|
5552
|
+
|
|
5518
5553
|
if ( this.renderer ) {
|
|
5519
5554
|
this.renderer.dispose();
|
|
5520
5555
|
const gl = this.renderer.getContext();
|
|
@@ -5527,12 +5562,12 @@ class TalkingHead {
|
|
|
5527
5562
|
if ( this.controls ) {
|
|
5528
5563
|
this.controls.dispose();
|
|
5529
5564
|
this.controls = null;
|
|
5530
|
-
|
|
5565
|
+
}
|
|
5531
5566
|
}
|
|
5532
5567
|
|
|
5533
5568
|
this.clearThree( this.ikMesh );
|
|
5534
5569
|
this.dynamicbones.dispose();
|
|
5535
|
-
|
|
5570
|
+
|
|
5536
5571
|
// Clean up FBX animation loader
|
|
5537
5572
|
if (this.fbxAnimationLoader) {
|
|
5538
5573
|
this.fbxAnimationLoader.stopCurrentAnimation();
|
|
@@ -4448,7 +4448,7 @@ class TalkingHead {
|
|
|
4448
4448
|
|
|
4449
4449
|
// Use existing mixer or create new one if none exists
|
|
4450
4450
|
if (!this.mixer) {
|
|
4451
|
-
|
|
4451
|
+
this.mixer = new THREE.AnimationMixer(this.armature);
|
|
4452
4452
|
console.log('Created new mixer for FBX animation');
|
|
4453
4453
|
} else {
|
|
4454
4454
|
console.log('Using existing mixer for FBX animation, preserving morph targets');
|
|
@@ -4465,7 +4465,7 @@ class TalkingHead {
|
|
|
4465
4465
|
this.currentFBXAction = action;
|
|
4466
4466
|
|
|
4467
4467
|
try {
|
|
4468
|
-
|
|
4468
|
+
action.fadeIn(0.5).play();
|
|
4469
4469
|
console.log('FBX animation started successfully:', url);
|
|
4470
4470
|
} catch (error) {
|
|
4471
4471
|
console.warn('FBX animation failed to start:', error);
|