Avatar Developer Menu: added animation debug options

This options are for for developers only and might help debug animation related issues.

* Enable Inverse Kinematics: this can be toggled to disable IK for the avatar.
* Enable Anim Pre and Post Rotations: this option can be used to use FBX pre-rotations from source avatar animations, instead of the current default, which is to use them from the source model.
  This only effects FBX files loaded by the animation system, it does not affect changing model orientations via JavaScript.
This commit is contained in:
Anthony J. Thibault 2016-01-25 18:32:12 -08:00
parent 1bdeeeceeb
commit 3cee3cbb5a
8 changed files with 72 additions and 23 deletions

View file

@ -479,6 +479,10 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ComfortMode, 0, true); 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, addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::KeyboardMotorControl,
Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehaviorFromMenu()), Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehaviorFromMenu()),

View file

@ -208,6 +208,7 @@ namespace MenuOption {
const QString EchoServerAudio = "Echo Server Audio"; const QString EchoServerAudio = "Echo Server Audio";
const QString Enable3DTVMode = "Enable 3DTV Mode"; const QString Enable3DTVMode = "Enable 3DTV Mode";
const QString EnableCharacterController = "Enable avatar collisions"; const QString EnableCharacterController = "Enable avatar collisions";
const QString EnableInverseKinematics = "Enable Inverse Kinematics";
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
const QString ExpandMyAvatarTiming = "Expand /myAvatar"; const QString ExpandMyAvatarTiming = "Expand /myAvatar";
const QString ExpandOtherAvatarTiming = "Expand /otherAvatar"; const QString ExpandOtherAvatarTiming = "Expand /otherAvatar";
@ -302,6 +303,7 @@ namespace MenuOption {
const QString UploadAsset = "Upload File to Asset Server"; const QString UploadAsset = "Upload File to Asset Server";
const QString UseAudioForMouth = "Use Audio for Mouth"; const QString UseAudioForMouth = "Use Audio for Mouth";
const QString UseCamera = "Use Camera"; const QString UseCamera = "Use Camera";
const QString UseAnimPreAndPostRotations = "Use Anim Pre and Post Rotations";
const QString VelocityFilter = "Velocity Filter"; const QString VelocityFilter = "Velocity Filter";
const QString VisibleToEveryone = "Everyone"; const QString VisibleToEveryone = "Everyone";
const QString VisibleToFriends = "Friends"; const QString VisibleToFriends = "Friends";

View file

@ -34,6 +34,7 @@
#include <TextRenderer3D.h> #include <TextRenderer3D.h>
#include <UserActivityLogger.h> #include <UserActivityLogger.h>
#include <AnimDebugDraw.h> #include <AnimDebugDraw.h>
#include <AnimClip.h>
#include <recording/Deck.h> #include <recording/Deck.h>
#include <recording/Recorder.h> #include <recording/Recorder.h>
#include <recording/Clip.h> #include <recording/Clip.h>
@ -647,6 +648,15 @@ void MyAvatar::setEnableMeshVisible(bool isEnabled) {
_skeletonModel.setVisibleInScene(isEnabled, scene); _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() { void MyAvatar::loadData() {
Settings settings; Settings settings;
settings.beginGroup("Avatar"); settings.beginGroup("Avatar");

View file

@ -260,6 +260,8 @@ public slots:
void setEnableDebugDrawPosition(bool isEnabled); void setEnableDebugDrawPosition(bool isEnabled);
bool getEnableMeshVisible() const { return _skeletonModel.isVisible(); } bool getEnableMeshVisible() const { return _skeletonModel.isVisible(); }
void setEnableMeshVisible(bool isEnabled); void setEnableMeshVisible(bool isEnabled);
void setUseAnimPreAndPostRotations(bool isEnabled);
void setEnableInverseKinematics(bool isEnabled);
Q_INVOKABLE void setAnimGraphUrl(const QUrl& url); Q_INVOKABLE void setAnimGraphUrl(const QUrl& url);
glm::vec3 getPositionForAudio(); glm::vec3 getPositionForAudio();

View file

@ -13,6 +13,8 @@
#include "AnimationLogging.h" #include "AnimationLogging.h"
#include "AnimUtil.h" #include "AnimUtil.h"
bool AnimClip::usePreAndPostPoseFromAnim = false;
AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag) : AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag) :
AnimNode(AnimNode::Type::Clip, id), AnimNode(AnimNode::Type::Clip, id),
_startFrame(startFrame), _startFrame(startFrame),
@ -109,6 +111,8 @@ void AnimClip::copyFromNetworkAnim() {
for (int frame = 0; frame < frameCount; frame++) { for (int frame = 0; frame < frameCount; frame++) {
const FBXAnimationFrame& fbxAnimFrame = geom.animationFrames[frame];
// init all joints in animation to default pose // 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. // this will give us a resonable result for bones in the model skeleton but not in the animation.
_anim[frame].reserve(skeletonJointCount); _anim[frame].reserve(skeletonJointCount);
@ -119,36 +123,46 @@ void AnimClip::copyFromNetworkAnim() {
for (int animJoint = 0; animJoint < animJointCount; animJoint++) { for (int animJoint = 0; animJoint < animJointCount; animJoint++) {
int skeletonJoint = jointMap[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. // skip joints that are in the animation but not in the skeleton.
if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) { if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) {
const glm::vec3& fbxZeroTrans = geom.animationFrames[0].translations[animJoint]; AnimPose preRot, postRot;
#ifdef USE_PRE_ROT_FROM_ANIM if (usePreAndPostPoseFromAnim) {
// TODO: This is the correct way to apply the pre rotations from maya, however preRot = animSkeleton.getPreRotationPose(animJoint);
// the current animation set has incorrect preRotations for the left wrist and thumb postRot = animSkeleton.getPostRotationPose(animJoint);
// so it looks wrong if we enable this code. } else {
glm::quat preRotation = animSkeleton.getPreRotation(animJoint); // In order to support Blender, which does not have preRotation FBX support, we use the models defaultPose as the reference frame for the animations.
#else preRot = AnimPose(glm::vec3(1.0f), _skeleton->getRelativeBindPose(skeletonJoint).rot, glm::vec3());
// TODO: This is the legacy approach, this does not work when animations and models do not postRot = AnimPose::identity;
// 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);
// 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. // 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]; AnimPose trans;
const FBXAnimationFrame& fbxAnimFrame = geom.animationFrames[frame]; if (usePreAndPostPoseFromAnim) {
trans = AnimPose(glm::vec3(1.0f), glm::quat(), boneLengthScale * (fbxAnimTrans + (fbxAnimTrans - fbxZeroTrans)));
} else {
trans = AnimPose(glm::vec3(1.0f), glm::quat(), relDefaultPose.trans + boneLengthScale * (fbxAnimTrans - fbxZeroTrans));
}
// rotation in fbxAnimationFrame is a delta from its preRotation. _anim[frame][skeletonJoint] = trans * preRot * rot * postRot;
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);
} }
} }
} }

View file

@ -25,6 +25,9 @@ class AnimClip : public AnimNode {
public: public:
friend class AnimTests; friend class AnimTests;
static bool usePreAndPostPoseFromAnim;
static bool useTransFromAnim;
AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag); AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag);
virtual ~AnimClip() override; virtual ~AnimClip() override;

View file

@ -464,6 +464,10 @@ void Rig::computeEyesInRootFrame(const AnimPoseVec& poses) {
} }
} }
void Rig::setEnableInverseKinematics(bool enable) {
_enableInverseKinematics = enable;
}
AnimPose Rig::getAbsoluteDefaultPose(int index) const { AnimPose Rig::getAbsoluteDefaultPose(int index) const {
if (_animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints()) { if (_animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints()) {
return _absoluteDefaultPoses[index]; return _absoluteDefaultPoses[index];
@ -705,6 +709,12 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
} }
t += deltaTime; t += deltaTime;
if (_enableInverseKinematics) {
_animVars.set("ikOverlayAlpha", 1.0f);
} else {
_animVars.set("ikOverlayAlpha", 0.0f);
}
} }
_lastFront = front; _lastFront = front;

View file

@ -208,6 +208,8 @@ public:
void computeAvatarBoundingCapsule(const FBXGeometry& geometry, float& radiusOut, float& heightOut, glm::vec3& offsetOut) const; void computeAvatarBoundingCapsule(const FBXGeometry& geometry, float& radiusOut, float& heightOut, glm::vec3& offsetOut) const;
void setEnableInverseKinematics(bool enable);
protected: protected:
bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); } bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); }
void updateAnimationStateHandlers(); void updateAnimationStateHandlers();
@ -290,6 +292,8 @@ public:
std::map<QString, AnimNode::Pointer> _origRoleAnimations; std::map<QString, AnimNode::Pointer> _origRoleAnimations;
std::vector<AnimNode::Pointer> _prefetchedAnimations; std::vector<AnimNode::Pointer> _prefetchedAnimations;
bool _enableInverseKinematics { true };
private: private:
QMap<int, StateHandler> _stateHandlers; QMap<int, StateHandler> _stateHandlers;
int _nextStateHandlerId { 0 }; int _nextStateHandlerId { 0 };