diff options
Diffstat (limited to 'indra/llcharacter/llheadrotmotion.cpp')
-rw-r--r-- | indra/llcharacter/llheadrotmotion.cpp | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/indra/llcharacter/llheadrotmotion.cpp b/indra/llcharacter/llheadrotmotion.cpp new file mode 100644 index 0000000000..4ff7e6b582 --- /dev/null +++ b/indra/llcharacter/llheadrotmotion.cpp @@ -0,0 +1,505 @@ +/** + * @file llheadrotmotion.cpp + * @brief Implementation of LLHeadRotMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llheadrotmotion.h" +#include "llcharacter.h" +#include "llrand.h" +#include "m3math.h" +#include "v3dmath.h" +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +const F32 TORSO_LAG = 0.35f; // torso rotation factor +const F32 NECK_LAG = 0.5f; // neck rotation factor +const F32 HEAD_LOOKAT_LAG_HALF_LIFE = 0.15f; // half-life of lookat targeting for head +const F32 TORSO_LOOKAT_LAG_HALF_LIFE = 0.27f; // half-life of lookat targeting for torso +const F32 EYE_LOOKAT_LAG_HALF_LIFE = 0.06f; // half-life of lookat targeting for eye +const F32 HEAD_ROTATION_CONSTRAINT = F_PI_BY_TWO * 0.8f; // limit angle for head rotation + +const F32 MIN_HEAD_LOOKAT_DISTANCE = 0.3f; // minimum distance from head before we turn to look at it +const F32 MAX_TIME_DELTA = 2.f; //max two seconds a frame for calculating interpolation +const F32 EYE_JITTER_MIN_TIME = 0.3f; // min amount of time between eye "jitter" motions +const F32 EYE_JITTER_MAX_TIME = 2.5f; // max amount of time between eye "jitter" motions +const F32 EYE_JITTER_MAX_YAW = 0.08f; // max yaw of eye jitter motion +const F32 EYE_JITTER_MAX_PITCH = 0.015f; // max pitch of eye jitter motion +const F32 EYE_LOOK_AWAY_MIN_TIME = 5.f; // min amount of time between eye "look away" motions +const F32 EYE_LOOK_AWAY_MAX_TIME = 15.f; // max amount of time between eye "look away" motions +const F32 EYE_LOOK_BACK_MIN_TIME = 1.f; // min amount of time before looking back after looking away +const F32 EYE_LOOK_BACK_MAX_TIME = 5.f; // max amount of time before looking back after looking away +const F32 EYE_LOOK_AWAY_MAX_YAW = 0.15f; // max yaw of eye look away motion +const F32 EYE_LOOK_AWAY_MAX_PITCH = 0.12f; // max pitch of look away motion +const F32 EYE_ROT_LIMIT_ANGLE = F_PI_BY_TWO * 0.3f; //max angle in radians for eye rotation + +const F32 EYE_BLINK_MIN_TIME = 0.5f; // minimum amount of time between blinks +const F32 EYE_BLINK_MAX_TIME = 8.f; // maximum amount of time between blinks +const F32 EYE_BLINK_CLOSE_TIME = 0.03f; // how long the eye stays closed in a blink +const F32 EYE_BLINK_SPEED = 0.015f; // seconds it takes for a eye open/close movement +const F32 EYE_BLINK_TIME_DELTA = 0.005f; // time between one eye starting a blink and the other following + +//----------------------------------------------------------------------------- +// LLHeadRotMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLHeadRotMotion::LLHeadRotMotion(const LLUUID &id) : + LLMotion(id), + mCharacter(NULL), + mTorsoJoint(NULL), + mHeadJoint(NULL) +{ + mName = "head_rot"; +} + + +//----------------------------------------------------------------------------- +// ~LLHeadRotMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLHeadRotMotion::~LLHeadRotMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLHeadRotMotion::onInitialize(LLCharacter *character) +{ + if (!character) + return STATUS_FAILURE; + mCharacter = character; + + mPelvisJoint = character->getJoint("mPelvis"); + if ( ! mPelvisJoint ) + { + llinfos << getName() << ": Can't get pelvis joint." << llendl; + return STATUS_FAILURE; + } + + mRootJoint = character->getJoint("mRoot"); + if ( ! mRootJoint ) + { + llinfos << getName() << ": Can't get root joint." << llendl; + return STATUS_FAILURE; + } + + mTorsoJoint = character->getJoint("mTorso"); + if ( ! mTorsoJoint ) + { + llinfos << getName() << ": Can't get torso joint." << llendl; + return STATUS_FAILURE; + } + + mHeadJoint = character->getJoint("mHead"); + if ( ! mHeadJoint ) + { + llinfos << getName() << ": Can't get head joint." << llendl; + return STATUS_FAILURE; + } + + mTorsoState.setJoint( character->getJoint("mTorso") ); + if ( ! mTorsoState.getJoint() ) + { + llinfos << getName() << ": Can't get torso joint." << llendl; + return STATUS_FAILURE; + } + + mNeckState.setJoint( character->getJoint("mNeck") ); + if ( ! mNeckState.getJoint() ) + { + llinfos << getName() << ": Can't get neck joint." << llendl; + return STATUS_FAILURE; + } + + mHeadState.setJoint( character->getJoint("mHead") ); + if ( ! mHeadState.getJoint() ) + { + llinfos << getName() << ": Can't get head joint." << llendl; + return STATUS_FAILURE; + } + + mTorsoState.setUsage(LLJointState::ROT); + mNeckState.setUsage(LLJointState::ROT); + mHeadState.setUsage(LLJointState::ROT); + + addJointState( &mTorsoState ); + addJointState( &mNeckState ); + addJointState( &mHeadState ); + + mLastHeadRot.loadIdentity(); + + return STATUS_SUCCESS; +} + + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLHeadRotMotion::onActivate() +{ + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLHeadRotMotion::onUpdate(F32 time, U8* joint_mask) +{ + LLQuaternion targetHeadRotWorld; + LLQuaternion currentRootRotWorld = mRootJoint->getWorldRotation(); + LLQuaternion currentInvRootRotWorld = ~currentRootRotWorld; + + F32 head_slerp_amt = LLCriticalDamp::getInterpolant(HEAD_LOOKAT_LAG_HALF_LIFE); + F32 torso_slerp_amt = LLCriticalDamp::getInterpolant(TORSO_LOOKAT_LAG_HALF_LIFE); + + LLVector3* targetPos = (LLVector3*)mCharacter->getAnimationData("LookAtPoint"); + + if (targetPos) + { + LLVector3 headLookAt = *targetPos; + +// llinfos << "Look At: " << headLookAt + mHeadJoint->getWorldPosition() << llendl; + + F32 lookatDistance = headLookAt.normVec(); + + if (lookatDistance < MIN_HEAD_LOOKAT_DISTANCE) + { + targetHeadRotWorld = mPelvisJoint->getWorldRotation(); + } + else + { + LLVector3 root_up = LLVector3(0.f, 0.f, 1.f) * currentRootRotWorld; + LLVector3 left(root_up % headLookAt); + // if look_at has zero length, fail + // if look_at and skyward are parallel, fail + // + // Test both of these conditions with a cross product. + + if (left.magVecSquared() < 0.15f) + { + LLVector3 root_at = LLVector3(1.f, 0.f, 0.f) * currentRootRotWorld; + root_at.mV[VZ] = 0.f; + root_at.normVec(); + + headLookAt = lerp(headLookAt, root_at, 0.4f); + headLookAt.normVec(); + + left = root_up % headLookAt; + } + + // Make sure look_at and skyward and not parallel + // and neither are zero length + LLVector3 up(headLookAt % left); + + targetHeadRotWorld = LLQuaternion(headLookAt, left, up); + } + } + else + { + targetHeadRotWorld = currentRootRotWorld; + } + + LLQuaternion head_rot_local = targetHeadRotWorld * currentInvRootRotWorld; + head_rot_local.constrain(HEAD_ROTATION_CONSTRAINT); + + // set final torso rotation + // Set torso target rotation such that it lags behind the head rotation + // by a fixed amount. + LLQuaternion torso_rot_local = nlerp(TORSO_LAG, LLQuaternion::DEFAULT, head_rot_local ); + mTorsoState.setRotation( nlerp(torso_slerp_amt, mTorsoState.getRotation(), torso_rot_local) ); + + head_rot_local = nlerp(head_slerp_amt, mLastHeadRot, head_rot_local); + mLastHeadRot = head_rot_local; + + // Set the head rotation. + LLQuaternion torsoRotLocal = mNeckState.getJoint()->getParent()->getWorldRotation() * currentInvRootRotWorld; + head_rot_local = head_rot_local * ~torsoRotLocal; + mNeckState.setRotation( nlerp(NECK_LAG, LLQuaternion::DEFAULT, head_rot_local) ); + mHeadState.setRotation( nlerp(1.f - NECK_LAG, LLQuaternion::DEFAULT, head_rot_local)); + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLHeadRotMotion::onDeactivate() +{ +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLEyeMotion::LLEyeMotion(const LLUUID &id) : LLMotion(id) +{ + mCharacter = NULL; + mEyeJitterTime = 0.f; + mEyeJitterYaw = 0.f; + mEyeJitterPitch = 0.f; + + mEyeLookAwayTime = 0.f; + mEyeLookAwayYaw = 0.f; + mEyeLookAwayPitch = 0.f; + + mEyeBlinkTime = 0.f; + mEyesClosed = FALSE; + + mHeadJoint = NULL; + + mName = "eye_rot"; +} + + +//----------------------------------------------------------------------------- +// ~LLEyeMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLEyeMotion::~LLEyeMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLEyeMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLEyeMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + mHeadJoint = character->getJoint("mHead"); + if ( ! mHeadJoint ) + { + llinfos << getName() << ": Can't get head joint." << llendl; + return STATUS_FAILURE; + } + + mLeftEyeState.setJoint( character->getJoint("mEyeLeft") ); + if ( ! mLeftEyeState.getJoint() ) + { + llinfos << getName() << ": Can't get left eyeball joint." << llendl; + return STATUS_FAILURE; + } + + mRightEyeState.setJoint( character->getJoint("mEyeRight") ); + if ( ! mRightEyeState.getJoint() ) + { + llinfos << getName() << ": Can't get Right eyeball joint." << llendl; + return STATUS_FAILURE; + } + + mLeftEyeState.setUsage(LLJointState::ROT); + mRightEyeState.setUsage(LLJointState::ROT); + + addJointState( &mLeftEyeState ); + addJointState( &mRightEyeState ); + + return STATUS_SUCCESS; +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLEyeMotion::onActivate() +{ + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLEyeMotion::onUpdate(F32 time, U8* joint_mask) +{ + // Compute eye rotation. + LLQuaternion target_eye_rot; + LLVector3 eye_look_at; + F32 vergence; + + //calculate jitter + if (mEyeJitterTimer.getElapsedTimeF32() > mEyeJitterTime) + { + mEyeJitterTime = EYE_JITTER_MIN_TIME + frand(EYE_JITTER_MAX_TIME - EYE_JITTER_MIN_TIME); + mEyeJitterYaw = (frand(2.f) - 1.f) * EYE_JITTER_MAX_YAW; + mEyeJitterPitch = (frand(2.f) - 1.f) * EYE_JITTER_MAX_PITCH; + // make sure lookaway time count gets updated, because we're resetting the timer + mEyeLookAwayTime -= llmax(0.f, mEyeJitterTimer.getElapsedTimeF32()); + mEyeJitterTimer.reset(); + } + else if (mEyeJitterTimer.getElapsedTimeF32() > mEyeLookAwayTime) + { + if (frand(1.f) > 0.1f) + { + // blink while moving eyes some percentage of the time + mEyeBlinkTime = mEyeBlinkTimer.getElapsedTimeF32(); + } + if (mEyeLookAwayYaw == 0.f && mEyeLookAwayPitch == 0.f) + { + mEyeLookAwayYaw = (frand(2.f) - 1.f) * EYE_LOOK_AWAY_MAX_YAW; + mEyeLookAwayPitch = (frand(2.f) - 1.f) * EYE_LOOK_AWAY_MAX_PITCH; + mEyeLookAwayTime = EYE_LOOK_BACK_MIN_TIME + frand(EYE_LOOK_BACK_MAX_TIME - EYE_LOOK_BACK_MIN_TIME); + } + else + { + mEyeLookAwayYaw = 0.f; + mEyeLookAwayPitch = 0.f; + mEyeLookAwayTime = EYE_LOOK_AWAY_MIN_TIME + frand(EYE_LOOK_AWAY_MAX_TIME - EYE_LOOK_AWAY_MIN_TIME); + } + } + + // do blinking + if (!mEyesClosed && mEyeBlinkTimer.getElapsedTimeF32() >= mEyeBlinkTime) + { + F32 leftEyeBlinkMorph = mEyeBlinkTimer.getElapsedTimeF32() - mEyeBlinkTime; + F32 rightEyeBlinkMorph = leftEyeBlinkMorph - EYE_BLINK_TIME_DELTA; + + leftEyeBlinkMorph = llclamp(leftEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + rightEyeBlinkMorph = llclamp(rightEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + mCharacter->setVisualParamWeight("Blink_Left", leftEyeBlinkMorph); + mCharacter->setVisualParamWeight("Blink_Right", rightEyeBlinkMorph); + mCharacter->updateVisualParams(); + + if (rightEyeBlinkMorph == 1.f) + { + mEyesClosed = TRUE; + mEyeBlinkTime = EYE_BLINK_CLOSE_TIME; + mEyeBlinkTimer.reset(); + } + } + else if (mEyesClosed) + { + if (mEyeBlinkTimer.getElapsedTimeF32() >= mEyeBlinkTime) + { + F32 leftEyeBlinkMorph = mEyeBlinkTimer.getElapsedTimeF32() - mEyeBlinkTime; + F32 rightEyeBlinkMorph = leftEyeBlinkMorph - EYE_BLINK_TIME_DELTA; + + leftEyeBlinkMorph = 1.f - llclamp(leftEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + rightEyeBlinkMorph = 1.f - llclamp(rightEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + mCharacter->setVisualParamWeight("Blink_Left", leftEyeBlinkMorph); + mCharacter->setVisualParamWeight("Blink_Right", rightEyeBlinkMorph); + mCharacter->updateVisualParams(); + + if (rightEyeBlinkMorph == 0.f) + { + mEyesClosed = FALSE; + mEyeBlinkTime = EYE_BLINK_MIN_TIME + frand(EYE_BLINK_MAX_TIME - EYE_BLINK_MIN_TIME); + mEyeBlinkTimer.reset(); + } + } + } + + BOOL has_eye_target = FALSE; + LLVector3* targetPos = (LLVector3*)mCharacter->getAnimationData("LookAtPoint"); + + if (targetPos) + { + LLVector3 skyward(0.f, 0.f, 1.f); + LLVector3 left; + LLVector3 up; + + eye_look_at = *targetPos; + F32 lookAtDistance = eye_look_at.normVec(); + + left.setVec(skyward % eye_look_at); + up.setVec(eye_look_at % left); + + target_eye_rot = LLQuaternion(eye_look_at, left, up); + + // calculate vergence + F32 interocular_dist = (mLeftEyeState.getJoint()->getWorldPosition() - mRightEyeState.getJoint()->getWorldPosition()).magVec(); + vergence = -atan2((interocular_dist / 2.f), lookAtDistance); + llclamp(vergence, -F_PI_BY_TWO, 0.f); + } + else + { + target_eye_rot = mHeadJoint->getWorldRotation(); + vergence = 0.f; + } + + //RN: subtract 4 degrees to account for foveal angular offset relative to pupil + vergence += 4.f * DEG_TO_RAD; + + // calculate eye jitter + LLQuaternion eye_jitter_rot; + + // vergence not too high... + if (vergence > -0.05f) + { + //...go ahead and jitter + eye_jitter_rot.setQuat(0.f, mEyeJitterPitch + mEyeLookAwayPitch, mEyeJitterYaw + mEyeLookAwayYaw); + } + else + { + //...or don't + eye_jitter_rot.loadIdentity(); + } + + // calculate vergence of eyes as an object gets closer to the avatar's head + LLQuaternion vergence_quat; + + if (has_eye_target) + { + vergence_quat.setQuat(vergence, LLVector3(0.f, 0.f, 1.f)); + } + else + { + vergence_quat.loadIdentity(); + } + + // calculate eye rotations + LLQuaternion left_eye_rot = target_eye_rot; + left_eye_rot = vergence_quat * eye_jitter_rot * left_eye_rot; + + LLQuaternion right_eye_rot = target_eye_rot; + vergence_quat.transQuat(); + right_eye_rot = vergence_quat * eye_jitter_rot * right_eye_rot; + + //set final eye rotations + // start with left + LLQuaternion tQw = mLeftEyeState.getJoint()->getParent()->getWorldRotation(); + LLQuaternion tQh = left_eye_rot * ~tQw; + tQh.constrain(EYE_ROT_LIMIT_ANGLE); + mLeftEyeState.setRotation( tQh ); + + // now do right eye + tQw = mRightEyeState.getJoint()->getParent()->getWorldRotation(); + tQh = right_eye_rot * ~tQw; + tQh.constrain(EYE_ROT_LIMIT_ANGLE); + mRightEyeState.setRotation( tQh ); + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLEyeMotion::onDeactivate() +{ + LLJoint* joint = mLeftEyeState.getJoint(); + if (joint) + { + joint->setRotation(LLQuaternion::DEFAULT); + } + + joint = mRightEyeState.getJoint(); + if (joint) + { + joint->setRotation(LLQuaternion::DEFAULT); + } +} + +// End |