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::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()),

View file

@ -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";

View file

@ -34,6 +34,7 @@
#include <TextRenderer3D.h>
#include <UserActivityLogger.h>
#include <AnimDebugDraw.h>
#include <AnimClip.h>
#include <recording/Deck.h>
#include <recording/Recorder.h>
#include <recording/Clip.h>
@ -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");

View file

@ -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();

View file

@ -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,46 @@ 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;
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.
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;
}
}
}

View file

@ -25,6 +25,9 @@ class AnimClip : public AnimNode {
public:
friend class AnimTests;
static bool usePreAndPostPoseFromAnim;
static bool useTransFromAnim;
AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag);
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 {
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;

View file

@ -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<QString, AnimNode::Pointer> _origRoleAnimations;
std::vector<AnimNode::Pointer> _prefetchedAnimations;
bool _enableInverseKinematics { true };
private:
QMap<int, StateHandler> _stateHandlers;
int _nextStateHandlerId { 0 };