@@ -3816,16 +3816,111 @@ class TalkingHead {
38163816 */
38173817 lookAtCamera ( t ) {
38183818
3819- if ( this . avatar . hasOwnProperty ( 'avatarIgnoreCamera' ) ) {
3820- if ( this . avatar . avatarIgnoreCamera ) {
3819+ // Calculate the target
3820+ let target ;
3821+ if ( this . speakTo ) {
3822+ target = new THREE . Vector3 ( ) ;
3823+ if ( this . speakTo . objectLeftEye ?. isObject3D ) {
3824+
3825+ // Target eyes
3826+ const o = this . speakTo . armature . objectHead ;
3827+ this . speakTo . objectLeftEye . updateMatrixWorld ( true ) ;
3828+ this . speakTo . objectRightEye . updateMatrixWorld ( true ) ;
3829+ v . setFromMatrixPosition ( this . speakTo . objectLeftEye . matrixWorld ) ;
3830+ w . setFromMatrixPosition ( this . speakTo . objectRightEye . matrixWorld ) ;
3831+ target . addVectors ( v , w ) . divideScalar ( 2 ) ;
3832+
3833+ } else if ( this . speakTo . isObject3D ) {
3834+ this . speakTo . getWorldPosition ( target ) ;
3835+ } else if ( this . speakTo . isVector3 ) {
3836+ target . set ( this . speakTo ) ;
3837+ } else if ( this . speakTo . x && this . speakTo . y && this . speakTo . z ) {
3838+ target . set ( this . speakTo . x , this . speakTo . y , this . speakTo . z ) ;
3839+ }
3840+ }
3841+
3842+ // If we don't have a target, look ahead or to the screen
3843+ if ( ! target ) {
3844+ if ( this . avatar . hasOwnProperty ( 'avatarIgnoreCamera' ) ) {
3845+ if ( this . avatar . avatarIgnoreCamera ) {
3846+ this . lookAhead ( t ) ;
3847+ return ;
3848+ }
3849+ } else if ( this . opt . avatarIgnoreCamera ) {
38213850 this . lookAhead ( t ) ;
3822- } else {
3823- this . lookAt ( null , null , t ) ;
3851+ return ;
38243852 }
3825- } else if ( this . opt . avatarIgnoreCamera ) {
3826- this . lookAhead ( t ) ;
3827- } else {
3828- this . lookAt ( null , null , t ) ;
3853+ this . lookAt ( null , null , t ) ;
3854+ return ;
3855+ }
3856+
3857+ // TODO: Improve the logic, if possible
3858+
3859+ // Eyes position and head world rotation
3860+ this . objectLeftEye . updateMatrixWorld ( true ) ;
3861+ this . objectRightEye . updateMatrixWorld ( true ) ;
3862+ v . setFromMatrixPosition ( this . objectLeftEye . matrixWorld ) ;
3863+ w . setFromMatrixPosition ( this . objectRightEye . matrixWorld ) ;
3864+ v . add ( w ) . divideScalar ( 2 ) ;
3865+ q . copy ( this . armature . quaternion ) ;
3866+ q . multiply ( this . poseTarget . props [ 'Hips.quaternion' ] ) ;
3867+ q . multiply ( this . poseTarget . props [ 'Spine.quaternion' ] ) ;
3868+ q . multiply ( this . poseTarget . props [ 'Spine1.quaternion' ] ) ;
3869+ q . multiply ( this . poseTarget . props [ 'Spine2.quaternion' ] ) ;
3870+ q . multiply ( this . poseTarget . props [ 'Neck.quaternion' ] ) ;
3871+ q . multiply ( this . poseTarget . props [ 'Head.quaternion' ] ) ;
3872+
3873+ // Direction from object to speakto target
3874+ const dir = new THREE . Vector3 ( ) . subVectors ( target , v ) . normalize ( ) ;
3875+
3876+ // Remove roll: compute yaw + pitch only
3877+ const yaw = Math . atan2 ( dir . x , dir . z ) ; // rotation around Y
3878+ const pitch = Math . asin ( - dir . y ) ; // rotation around X
3879+ const roll = 0 ; // force to 0
3880+
3881+ // Desired rotation with Z locked
3882+ e . set ( pitch , yaw , roll , 'YXZ' ) ;
3883+ const desiredQ = new THREE . Quaternion ( ) . setFromEuler ( e ) ;
3884+
3885+ // Rotation difference
3886+ const deltaQ = new THREE . Quaternion ( ) . copy ( desiredQ ) . multiply ( q . clone ( ) . invert ( ) ) ;
3887+
3888+ // Convert to Euler (Z will be ~0 by construction)
3889+ e . setFromQuaternion ( deltaQ , 'YXZ' ) ;
3890+ let rx = e . x / ( 40 / 24 ) + 0.2 ; // Refer to setValue(bodyRotateX)
3891+ let ry = e . y / ( 9 / 4 ) ; // Refer to setValue(bodyRotateY)
3892+ let rotx = Math . min ( 0.6 , Math . max ( - 0.3 , rx ) ) ;
3893+ let roty = Math . min ( 0.8 , Math . max ( - 0.8 , ry ) ) ;
3894+
3895+ // Randomize head/eyes ratio
3896+ let drotx = ( Math . random ( ) - 0.5 ) / 4 ;
3897+ let droty = ( Math . random ( ) - 0.5 ) / 4 ;
3898+
3899+ if ( t ) {
3900+
3901+ // Remove old, if any
3902+ let old = this . animQueue . findIndex ( y => y . template . name === 'lookat' ) ;
3903+ if ( old !== - 1 ) {
3904+ this . animQueue . splice ( old , 1 ) ;
3905+ }
3906+
3907+ // Add new anim
3908+ const templateLookAt = {
3909+ name : 'lookat' ,
3910+ dt : [ 750 , t ] ,
3911+ vs : {
3912+ bodyRotateX : [ rotx + drotx ] ,
3913+ bodyRotateY : [ roty + droty ] ,
3914+ eyesRotateX : [ - 3 * drotx + 0.1 ] ,
3915+ eyesRotateY : [ - 5 * droty ] ,
3916+ browInnerUp : [ [ 0 , 0.7 ] ] ,
3917+ mouthLeft : [ [ 0 , 0.7 ] ] ,
3918+ mouthRight : [ [ 0 , 0.7 ] ] ,
3919+ eyeContact : [ 0 ] ,
3920+ headMove : [ 0 ]
3921+ }
3922+ } ;
3923+ this . animQueue . push ( this . animFactory ( templateLookAt ) ) ;
38293924 }
38303925
38313926 }
0 commit comments