diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 14c91dbda8..720d8993ab 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -482,6 +482,10 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ComfortMode, 0, true); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, false, + avatar, SLOT(setUseAnimPreAndPostRotations(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableInverseKinematics, 0, true, + avatar, SLOT(setEnableInverseKinematics(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::KeyboardMotorControl, Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehaviorFromMenu()), diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 408ab15d5e..7b9cca2c63 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -208,6 +208,7 @@ namespace MenuOption { const QString EchoServerAudio = "Echo Server Audio"; const QString Enable3DTVMode = "Enable 3DTV Mode"; const QString EnableCharacterController = "Enable avatar collisions"; + const QString EnableInverseKinematics = "Enable Inverse Kinematics"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; const QString ExpandMyAvatarTiming = "Expand /myAvatar"; const QString ExpandOtherAvatarTiming = "Expand /otherAvatar"; @@ -302,6 +303,7 @@ namespace MenuOption { const QString UploadAsset = "Upload File to Asset Server"; const QString UseAudioForMouth = "Use Audio for Mouth"; const QString UseCamera = "Use Camera"; + const QString UseAnimPreAndPostRotations = "Use Anim Pre and Post Rotations"; const QString VelocityFilter = "Velocity Filter"; const QString VisibleToEveryone = "Everyone"; const QString VisibleToFriends = "Friends"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e06dce2324..756dc6adea 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -647,6 +648,15 @@ void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel.setVisibleInScene(isEnabled, scene); } +void MyAvatar::setUseAnimPreAndPostRotations(bool isEnabled) { + AnimClip::usePreAndPostPoseFromAnim = isEnabled; + reset(true); +} + +void MyAvatar::setEnableInverseKinematics(bool isEnabled) { + _rig->setEnableInverseKinematics(isEnabled); +} + void MyAvatar::loadData() { Settings settings; settings.beginGroup("Avatar"); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 10ce962821..ed6c3cb883 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -260,6 +260,8 @@ public slots: void setEnableDebugDrawPosition(bool isEnabled); bool getEnableMeshVisible() const { return _skeletonModel.isVisible(); } void setEnableMeshVisible(bool isEnabled); + void setUseAnimPreAndPostRotations(bool isEnabled); + void setEnableInverseKinematics(bool isEnabled); Q_INVOKABLE void setAnimGraphUrl(const QUrl& url); glm::vec3 getPositionForAudio(); diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 986bb0a30e..90cd85e727 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -13,6 +13,8 @@ #include "AnimationLogging.h" #include "AnimUtil.h" +bool AnimClip::usePreAndPostPoseFromAnim = false; + AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag) : AnimNode(AnimNode::Type::Clip, id), _startFrame(startFrame), @@ -109,6 +111,8 @@ void AnimClip::copyFromNetworkAnim() { for (int frame = 0; frame < frameCount; frame++) { + const FBXAnimationFrame& fbxAnimFrame = geom.animationFrames[frame]; + // init all joints in animation to default pose // this will give us a resonable result for bones in the model skeleton but not in the animation. _anim[frame].reserve(skeletonJointCount); @@ -119,36 +123,41 @@ void AnimClip::copyFromNetworkAnim() { for (int animJoint = 0; animJoint < animJointCount; animJoint++) { int skeletonJoint = jointMap[animJoint]; + const glm::vec3& fbxAnimTrans = fbxAnimFrame.translations[animJoint]; + const glm::quat& fbxAnimRot = fbxAnimFrame.rotations[animJoint]; + // skip joints that are in the animation but not in the skeleton. if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) { - const glm::vec3& fbxZeroTrans = geom.animationFrames[0].translations[animJoint]; -#ifdef USE_PRE_ROT_FROM_ANIM - // TODO: This is the correct way to apply the pre rotations from maya, however - // the current animation set has incorrect preRotations for the left wrist and thumb - // so it looks wrong if we enable this code. - glm::quat preRotation = animSkeleton.getPreRotation(animJoint); -#else - // TODO: This is the legacy approach, this does not work when animations and models do not - // have the same set of pre rotations. For example when mixing maya models with blender animations. - glm::quat preRotation = _skeleton->getRelativeBindPose(skeletonJoint).rot; -#endif - const AnimPose& relDefaultPose = _skeleton->getRelativeDefaultPose(skeletonJoint); + AnimPose preRot, postRot; + if (usePreAndPostPoseFromAnim) { + preRot = animSkeleton.getPreRotationPose(animJoint); + postRot = animSkeleton.getPostRotationPose(animJoint); + } else { + // In order to support Blender, which does not have preRotation FBX support, we use the models defaultPose as the reference frame for the animations. + preRot = AnimPose(glm::vec3(1.0f), _skeleton->getRelativeBindPose(skeletonJoint).rot, glm::vec3()); + postRot = AnimPose::identity; + } - // used to adjust translation offsets, so large translation animatons on the reference skeleton + // cancel out scale + preRot.scale = glm::vec3(1.0f); + postRot.scale = glm::vec3(1.0f); + + AnimPose rot(glm::vec3(1.0f), fbxAnimRot, glm::vec3()); + + // adjust translation offsets, so large translation animatons on the reference skeleton // will be adjusted when played on a skeleton with short limbs. - float limbLengthScale = fabsf(glm::length(fbxZeroTrans)) <= 0.0001f ? 1.0f : (glm::length(relDefaultPose.trans) / glm::length(fbxZeroTrans)); + const glm::vec3& fbxZeroTrans = geom.animationFrames[0].translations[animJoint]; + const AnimPose& relDefaultPose = _skeleton->getRelativeDefaultPose(skeletonJoint); + float boneLengthScale = 1.0f; + const float EPSILON = 0.0001f; + if (fabsf(glm::length(fbxZeroTrans)) > EPSILON) { + boneLengthScale = glm::length(relDefaultPose.trans) / glm::length(fbxZeroTrans); + } - AnimPose& pose = _anim[frame][skeletonJoint]; - const FBXAnimationFrame& fbxAnimFrame = geom.animationFrames[frame]; + AnimPose trans = AnimPose(glm::vec3(1.0f), glm::quat(), relDefaultPose.trans + boneLengthScale * (fbxAnimTrans - fbxZeroTrans)); - // rotation in fbxAnimationFrame is a delta from its preRotation. - pose.rot = preRotation * fbxAnimFrame.rotations[animJoint]; - - // translation in fbxAnimationFrame is not a delta. - // convert it into a delta by subtracting from the first frame. - const glm::vec3& fbxTrans = fbxAnimFrame.translations[animJoint]; - pose.trans = relDefaultPose.trans + limbLengthScale * (fbxTrans - fbxZeroTrans); + _anim[frame][skeletonJoint] = trans * preRot * rot * postRot; } } } diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 934f3f3ed8..7d58ae4f9a 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -25,6 +25,8 @@ class AnimClip : public AnimNode { public: friend class AnimTests; + static bool usePreAndPostPoseFromAnim; + AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag); virtual ~AnimClip() override; diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 8e3d716aac..8f45b785d1 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -59,8 +59,14 @@ const AnimPose& AnimSkeleton::getAbsoluteDefaultPose(int jointIndex) const { return _absoluteDefaultPoses[jointIndex]; } -const glm::quat AnimSkeleton::getPreRotation(int jointIndex) const { - return _joints[jointIndex].preRotation; +// get pre multiplied transform which should include FBX pre potations +const AnimPose& AnimSkeleton::getPreRotationPose(int jointIndex) const { + return _relativePreRotationPoses[jointIndex]; +} + +// get post multiplied transform which might include FBX offset transformations +const AnimPose& AnimSkeleton::getPostRotationPose(int jointIndex) const { + return _relativePostRotationPoses[jointIndex]; } int AnimSkeleton::getParentIndex(int jointIndex) const { @@ -99,13 +105,20 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) // build a chache of default poses _absoluteDefaultPoses.reserve(joints.size()); _relativeDefaultPoses.reserve(joints.size()); + _relativePreRotationPoses.reserve(joints.size()); + _relativePostRotationPoses.reserve(joints.size()); // iterate over FBXJoints and extract the bind pose information. for (int i = 0; i < (int)joints.size(); i++) { + // build pre and post transforms + glm::mat4 preRotationTransform = _joints[i].preTransform * glm::mat4_cast(_joints[i].preRotation); + glm::mat4 postRotationTransform = glm::mat4_cast(_joints[i].postRotation) * _joints[i].postTransform; + _relativePreRotationPoses.push_back(AnimPose(preRotationTransform)); + _relativePostRotationPoses.push_back(AnimPose(postRotationTransform)); + // build relative and absolute default poses - glm::mat4 rotTransform = glm::mat4_cast(_joints[i].preRotation * _joints[i].rotation * _joints[i].postRotation); - glm::mat4 relDefaultMat = glm::translate(_joints[i].translation) * _joints[i].preTransform * rotTransform * _joints[i].postTransform; + glm::mat4 relDefaultMat = glm::translate(_joints[i].translation) * preRotationTransform * glm::mat4_cast(_joints[i].rotation) * postRotationTransform; AnimPose relDefaultPose(relDefaultMat); _relativeDefaultPoses.push_back(relDefaultPose); int parentIndex = getParentIndex(i); diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 7312fea6b1..757f4e5c3e 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -42,8 +42,11 @@ public: const AnimPose& getAbsoluteDefaultPose(int jointIndex) const; const AnimPoseVec& getAbsoluteDefaultPoses() const { return _absoluteDefaultPoses; } - // get pre-rotation aka Maya's joint orientation. - const glm::quat getPreRotation(int jointIndex) const; + // get pre transform which should include FBX pre potations + const AnimPose& getPreRotationPose(int jointIndex) const; + + // get post transform which might include FBX offset transformations + const AnimPose& getPostRotationPose(int jointIndex) const; int getParentIndex(int jointIndex) const; @@ -64,6 +67,8 @@ protected: AnimPoseVec _relativeBindPoses; AnimPoseVec _relativeDefaultPoses; AnimPoseVec _absoluteDefaultPoses; + AnimPoseVec _relativePreRotationPoses; + AnimPoseVec _relativePostRotationPoses; // no copies AnimSkeleton(const AnimSkeleton&) = delete; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index df92d7d912..4d72bfbaea 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -464,6 +464,10 @@ void Rig::computeEyesInRootFrame(const AnimPoseVec& poses) { } } +void Rig::setEnableInverseKinematics(bool enable) { + _enableInverseKinematics = enable; +} + AnimPose Rig::getAbsoluteDefaultPose(int index) const { if (_animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints()) { return _absoluteDefaultPoses[index]; @@ -705,6 +709,12 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } t += deltaTime; + + if (_enableInverseKinematics) { + _animVars.set("ikOverlayAlpha", 1.0f); + } else { + _animVars.set("ikOverlayAlpha", 0.0f); + } } _lastFront = front; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index d26be83d36..cf96c6dc16 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -208,6 +208,8 @@ public: void computeAvatarBoundingCapsule(const FBXGeometry& geometry, float& radiusOut, float& heightOut, glm::vec3& offsetOut) const; + void setEnableInverseKinematics(bool enable); + protected: bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); } void updateAnimationStateHandlers(); @@ -290,6 +292,8 @@ public: std::map _origRoleAnimations; std::vector _prefetchedAnimations; + bool _enableInverseKinematics { true }; + private: QMap _stateHandlers; int _nextStateHandlerId { 0 };