From c2934bdb5d3fc8e1b1fac2d60c0eec1430ae8d06 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 29 Jul 2015 17:35:29 -0700 Subject: [PATCH] Fix avatar head, eye and torso twist. * Updated SkeletionModel::updateRig to explicitly pass a set of HeadParameters to the rig to do procedural animation. * Moved torso lean procedural animation from SkeletonModel into Rig. * Moved eye tracking procedural animation from HeadModel into Rig. * Moved neck procedural animation from HeadModel into Rig. --- interface/src/avatar/FaceModel.cpp | 54 --------------------- interface/src/avatar/FaceModel.h | 10 ++-- interface/src/avatar/SkeletonModel.cpp | 57 +++++++--------------- libraries/animation/src/AvatarRig.cpp | 18 ------- libraries/animation/src/AvatarRig.h | 1 - libraries/animation/src/EntityRig.h | 1 - libraries/animation/src/Rig.cpp | 65 ++++++++++++++++++++++++++ libraries/animation/src/Rig.h | 25 ++++++++-- 8 files changed, 108 insertions(+), 123 deletions(-) diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index e35c70a8ab..d5d4da8665 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -50,60 +50,6 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) { } } -void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, int index) { - // get the rotation axes in joint space and use them to adjust the rotation - glm::mat3 axes = glm::mat3_cast(glm::quat()); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * - glm::translate(_rig->getJointDefaultTranslationInConstrainedFrame(index)) * - joint.preTransform * glm::mat4_cast(joint.preRotation))); - glm::vec3 pitchYawRoll = safeEulerAngles(_owningHead->getFinalOrientationInLocalFrame()); - glm::vec3 lean = glm::radians(glm::vec3(_owningHead->getFinalLeanForward(), - _owningHead->getTorsoTwist(), - _owningHead->getFinalLeanSideways())); - pitchYawRoll -= lean; - _rig->setJointRotationInConstrainedFrame(index, - glm::angleAxis(-pitchYawRoll.z, glm::normalize(inverse * axes[2])) - * glm::angleAxis(pitchYawRoll.y, glm::normalize(inverse * axes[1])) - * glm::angleAxis(-pitchYawRoll.x, glm::normalize(inverse * axes[0])) - * joint.rotation, DEFAULT_PRIORITY); -} - -void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, int index) { - // likewise with the eye joints - // NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual. - glm::mat4 inverse = glm::inverse(glm::mat4_cast(model->getRotation()) * parentState.getTransform() * - glm::translate(_rig->getJointDefaultTranslationInConstrainedFrame(index)) * - joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)); - glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f)); - glm::vec3 lookAtDelta = _owningHead->getCorrectedLookAtPosition() - model->getTranslation(); - glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(lookAtDelta + glm::length(lookAtDelta) * _owningHead->getSaccade(), 1.0f)); - glm::quat between = rotationBetween(front, lookAt); - const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; - _rig->setJointRotationInConstrainedFrame(index, glm::angleAxis(glm::clamp(glm::angle(between), - -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * - joint.rotation, DEFAULT_PRIORITY); -} - -void FaceModel::updateJointState(int index) { - const JointState& state = _rig->getJointState(index); - const FBXJoint& joint = state.getFBXJoint(); - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - - // guard against out-of-bounds access to _jointStates - if (joint.parentIndex != -1 && joint.parentIndex >= 0 && joint.parentIndex < _rig->getJointStateCount()) { - const JointState& parentState = _rig->getJointState(joint.parentIndex); - if (index == geometry.neckJointIndex) { - maybeUpdateNeckRotation(parentState, joint, index); - - } else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) { - maybeUpdateEyeRotation(this, parentState, joint, index); - } - } - - glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; - _rig->updateFaceJointState(index, parentTransform); -} - bool FaceModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { if (!isActive()) { return false; diff --git a/interface/src/avatar/FaceModel.h b/interface/src/avatar/FaceModel.h index ce78c51e70..5a19a8ea29 100644 --- a/interface/src/avatar/FaceModel.h +++ b/interface/src/avatar/FaceModel.h @@ -19,23 +19,19 @@ class Head; /// A face formed from a linear mix of blendshapes according to a set of coefficients. class FaceModel : public Model { Q_OBJECT - + public: FaceModel(Head* owningHead, RigPointer rig); virtual void simulate(float deltaTime, bool fullUpdate = true); - - virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, int index); - virtual void maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, int index); - virtual void updateJointState(int index); /// Retrieve the positions of up to two eye meshes. /// \return whether or not both eye meshes were found bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; - + private: - + Head* _owningHead; }; diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 122559bedb..a766a80bab 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -118,6 +118,24 @@ void SkeletonModel::updateClusterMatrices() { void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { _rig->computeMotionAnimationState(deltaTime, _owningAvatar->getPosition(), _owningAvatar->getVelocity(), _owningAvatar->getOrientation()); Model::updateRig(deltaTime, parentTransform); + if (_owningAvatar->isMyAvatar()) { + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + + Rig::HeadParameters params; + params.leanSideways = _owningAvatar->getHead()->getFinalLeanSideways(); + params.leanForward = _owningAvatar->getHead()->getFinalLeanSideways(); + params.torsoTwist = _owningAvatar->getHead()->getTorsoTwist(); + params.localHeadOrientation = _owningAvatar->getHead()->getFinalOrientationInLocalFrame(); + params.worldHeadOrientation = _owningAvatar->getHead()->getFinalOrientationInWorldFrame(); + params.eyeLookAt = _owningAvatar->getHead()->getCorrectedLookAtPosition(); + params.eyeSaccade = _owningAvatar->getHead()->getSaccade(); + params.leanJointIndex = geometry.leanJointIndex; + params.neckJointIndex = geometry.neckJointIndex; + params.leftEyeJointIndex = geometry.leftEyeJointIndex; + params.rightEyeJointIndex = geometry.rightEyeJointIndex; + + _rig->updateFromHeadParameters(params); + } } void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { @@ -259,51 +277,12 @@ void SkeletonModel::updateJointState(int index) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; - const JointState joint = _rig->getJointState(index); - if (joint.getParentIndex() >= 0 && joint.getParentIndex() < _rig->getJointStateCount()) { - const JointState parentState = _rig->getJointState(joint.getParentIndex()); - if (index == geometry.leanJointIndex) { - maybeUpdateLeanRotation(parentState, index); - - } else if (index == geometry.neckJointIndex) { - maybeUpdateNeckRotation(parentState, joint.getFBXJoint(), index); - - } else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) { - maybeUpdateEyeRotation(parentState, joint.getFBXJoint(), index); - } - } - _rig->updateJointState(index, parentTransform); if (index == _geometry->getFBXGeometry().rootJointIndex) { _rig->clearJointTransformTranslation(index); } } - -void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, int index) { - if (!_owningAvatar->isMyAvatar()) { - return; - } - // get the rotation axes in joint space and use them to adjust the rotation - glm::vec3 xAxis(1.0f, 0.0f, 0.0f); - glm::vec3 yAxis(0.0f, 1.0f, 0.0f); - glm::vec3 zAxis(0.0f, 0.0f, 1.0f); - glm::quat inverse = glm::inverse(parentState.getRotation() * _rig->getJointDefaultRotationInParentFrame(index)); - _rig->setJointRotationInConstrainedFrame(index, - glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), inverse * zAxis) - * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), inverse * xAxis) - * glm::angleAxis(RADIANS_PER_DEGREE * _owningAvatar->getHead()->getTorsoTwist(), inverse * yAxis) - * _rig->getJointState(index).getFBXJoint().rotation, LEAN_PRIORITY); -} - -void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, int index) { - _owningAvatar->getHead()->getFaceModel().maybeUpdateNeckRotation(parentState, joint, index); -} - -void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, int index) { - _owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(this, parentState, joint, index); -} - void SkeletonModel::renderJointConstraints(gpu::Batch& batch, int jointIndex) { if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) { return; diff --git a/libraries/animation/src/AvatarRig.cpp b/libraries/animation/src/AvatarRig.cpp index cf05e61cdb..3b48b0814f 100644 --- a/libraries/animation/src/AvatarRig.cpp +++ b/libraries/animation/src/AvatarRig.cpp @@ -31,21 +31,3 @@ void AvatarRig::updateJointState(int index, glm::mat4 parentTransform) { } } } - - -void AvatarRig::updateFaceJointState(int index, glm::mat4 parentTransform) { - JointState& state = _jointStates[index]; - const FBXJoint& joint = state.getFBXJoint(); - - // compute model transforms - int parentIndex = joint.parentIndex; - if (parentIndex == -1) { - state.computeTransform(parentTransform); - } else { - // guard against out-of-bounds access to _jointStates - if (joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) { - const JointState& parentState = _jointStates.at(parentIndex); - state.computeTransform(parentState.getTransform(), parentState.getTransformChanged()); - } - } -} diff --git a/libraries/animation/src/AvatarRig.h b/libraries/animation/src/AvatarRig.h index dbffd8aa45..4a111a535b 100644 --- a/libraries/animation/src/AvatarRig.h +++ b/libraries/animation/src/AvatarRig.h @@ -22,7 +22,6 @@ class AvatarRig : public Rig { public: ~AvatarRig() {} virtual void updateJointState(int index, glm::mat4 parentTransform); - virtual void updateFaceJointState(int index, glm::mat4 parentTransform); }; #endif // hifi_AvatarRig_h diff --git a/libraries/animation/src/EntityRig.h b/libraries/animation/src/EntityRig.h index aa6a5fbd2b..e8e15a5a28 100644 --- a/libraries/animation/src/EntityRig.h +++ b/libraries/animation/src/EntityRig.h @@ -22,7 +22,6 @@ class EntityRig : public Rig { public: ~EntityRig() {} virtual void updateJointState(int index, glm::mat4 parentTransform); - virtual void updateFaceJointState(int index, glm::mat4 parentTransform) { } }; #endif // hifi_EntityRig_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index dc7b37129e..2ff6faa868 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -665,3 +665,68 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) { } return _jointStates[jointIndex].getDefaultRotationInParentFrame(); } + +void Rig::updateFromHeadParameters(const HeadParameters& params) { + updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist); + updateNeckJoint(params.neckJointIndex, params.localHeadOrientation, params.leanSideways, params.leanForward, params.torsoTwist); + updateEyeJoint(params.leftEyeJointIndex, params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade); + updateEyeJoint(params.rightEyeJointIndex, params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade); +} + +void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist) { + if (index > 0 && _jointStates[index].getParentIndex() > 0) { + auto& parentState = _jointStates[_jointStates[index].getParentIndex()]; + + // get the rotation axes in joint space and use them to adjust the rotation + glm::vec3 xAxis(1.0f, 0.0f, 0.0f); + glm::vec3 yAxis(0.0f, 1.0f, 0.0f); + glm::vec3 zAxis(0.0f, 0.0f, 1.0f); + glm::quat inverse = glm::inverse(parentState.getRotation() * getJointDefaultRotationInParentFrame(index)); + setJointRotationInConstrainedFrame(index, + glm::angleAxis(- RADIANS_PER_DEGREE * leanSideways, inverse * zAxis) * + glm::angleAxis(- RADIANS_PER_DEGREE * leanForward, inverse * xAxis) * + glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, inverse * yAxis) * + getJointState(index).getFBXJoint().rotation, DEFAULT_PRIORITY); + } +} + +void Rig::updateNeckJoint(int index, const glm::quat& localHeadOrientation, float leanSideways, float leanForward, float torsoTwist) { + if (index > 0 && _jointStates[index].getParentIndex() > 0) { + auto& parentState = _jointStates[_jointStates[index].getParentIndex()]; + auto joint = _jointStates[index].getFBXJoint(); + + // get the rotation axes in joint space and use them to adjust the rotation + glm::mat3 axes = glm::mat3_cast(glm::quat()); + glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * + glm::translate(getJointDefaultTranslationInConstrainedFrame(index)) * + joint.preTransform * glm::mat4_cast(joint.preRotation))); + glm::vec3 pitchYawRoll = safeEulerAngles(localHeadOrientation); + glm::vec3 lean = glm::radians(glm::vec3(leanForward, torsoTwist, leanSideways)); + pitchYawRoll -= lean; + setJointRotationInConstrainedFrame(index, + glm::angleAxis(-pitchYawRoll.z, glm::normalize(inverse * axes[2])) * + glm::angleAxis(pitchYawRoll.y, glm::normalize(inverse * axes[1])) * + glm::angleAxis(-pitchYawRoll.x, glm::normalize(inverse * axes[0])) * + joint.rotation, DEFAULT_PRIORITY); + } +} + +void Rig::updateEyeJoint(int index, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade) { + if ( index > 0 && _jointStates[index].getParentIndex() > 0) { + auto& parentState = _jointStates[_jointStates[index].getParentIndex()]; + auto joint = _jointStates[index].getFBXJoint(); + + // NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual. + glm::mat4 inverse = glm::inverse(parentState.getTransform() * + glm::translate(getJointDefaultTranslationInConstrainedFrame(index)) * + joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)); + glm::vec3 front = glm::vec3(inverse * glm::vec4(worldHeadOrientation * IDENTITY_FRONT, 0.0f)); + glm::vec3 lookAtDelta = lookAt; + glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(lookAtDelta + glm::length(lookAtDelta) * saccade, 1.0f)); + glm::quat between = rotationBetween(front, lookAt); + const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; + float angle = glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE); + glm::quat rot = glm::angleAxis(angle, glm::axis(between)); + setJointRotationInConstrainedFrame(index, rot * joint.rotation, DEFAULT_PRIORITY); + } +} diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index fe6bc82f35..2eb9d0e0b3 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -47,14 +47,27 @@ typedef std::shared_ptr AnimationHandlePointer; class Rig; typedef std::shared_ptr RigPointer; - class Rig : public QObject, public std::enable_shared_from_this { public: + struct HeadParameters { + float leanSideways = 0.0f; // degrees + float leanForward = 0.0f; // degrees + float torsoTwist = 0.0f; // degrees + glm::quat localHeadOrientation = glm::quat(); + glm::quat worldHeadOrientation = glm::quat(); + glm::vec3 eyeLookAt = glm::vec3(); // world space + glm::vec3 eyeSaccade = glm::vec3(); // world space + int leanJointIndex = -1; + int neckJointIndex = -1; + int leftEyeJointIndex = -1; + int rightEyeJointIndex = -1; + }; + virtual ~Rig() {} - RigPointer getRigPointer() { return shared_from_this(); } + RigPointer getRigPointer() { return shared_from_this(); } AnimationHandlePointer createAnimationHandle(); void removeAnimationHandle(const AnimationHandlePointer& handle); @@ -129,11 +142,17 @@ public: void updateVisibleJointStates(); virtual void updateJointState(int index, glm::mat4 parentTransform) = 0; - virtual void updateFaceJointState(int index, glm::mat4 parentTransform) = 0; void setEnableRig(bool isEnabled) { _enableRig = isEnabled; } + void updateFromHeadParameters(const HeadParameters& params); + protected: + + void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist); + void updateNeckJoint(int index, const glm::quat& localHeadOrientation, float leanSideways, float leanForward, float torsoTwist); + void updateEyeJoint(int index, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade); + QVector _jointStates; QList _animationHandles;