First pass integration of new anim system into rig.

This commit is contained in:
Anthony J. Thibault 2015-09-01 17:57:01 -07:00
parent 2401e6cc4b
commit 7b4cb8655c
11 changed files with 198 additions and 158 deletions

View file

@ -433,6 +433,8 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableRigAnimations, 0, false,
avatar, SLOT(setEnableRigAnimations(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAnimGraph, 0, false,
avatar, SLOT(setEnableAnimGraph(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu,
MenuOption::Connexion,

View file

@ -187,6 +187,7 @@ namespace MenuOption {
const QString EchoServerAudio = "Echo Server Audio";
const QString EditEntitiesHelp = "Edit Entities Help...";
const QString Enable3DTVMode = "Enable 3DTV Mode";
const QString EnableAnimGraph = "Enable Anim Graph";
const QString EnableCharacterController = "Enable avatar collisions";
const QString EnableRigAnimations = "Enable Rig Animations";
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";

View file

@ -36,11 +36,6 @@
#include "devices/Faceshift.h"
#include "AnimDebugDraw.h"
#include "AnimSkeleton.h"
#include "AnimClip.h"
#include "AnimBlendLinear.h"
#include "AnimOverlay.h"
#include "Application.h"
#include "AvatarManager.h"
#include "Environment.h"
@ -163,27 +158,6 @@ void MyAvatar::update(float deltaTime) {
_goToPending = false;
}
if (_animNode) {
static float t = 0.0f;
_animVars.set("sine", 0.5f * sin(t) + 0.5f);
if (glm::length(getVelocity()) > 0.07f) {
_animVars.set("isMoving", true);
_animVars.set("isNotMoving", false);
} else {
_animVars.set("isMoving", false);
_animVars.set("isNotMoving", true);
}
t += deltaTime;
AnimNode::Triggers triggers;
_animNode->evaluate(_animVars, deltaTime, triggers);
_animVars.clearTriggers();
for (auto& trigger : triggers) {
_animVars.setTrigger(trigger);
}
}
if (_referential) {
_referential->update();
}
@ -737,6 +711,13 @@ void MyAvatar::setEnableRigAnimations(bool isEnabled) {
}
}
void MyAvatar::setEnableAnimGraph(bool isEnabled) {
_rig->setEnableAnimGraph(isEnabled);
if (!isEnabled) {
// AJT: TODO: FIXME: currently setEnableAnimGraph menu item only works on startup
}
}
void MyAvatar::loadData() {
Settings settings;
settings.beginGroup("Avatar");
@ -1035,7 +1016,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_billboardValid = false;
_skeletonModel.setVisibleInScene(true, scene);
_headBoneSet.clear();
teardownNewAnimationSystem();
}
void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) {
@ -1232,48 +1212,6 @@ void MyAvatar::initHeadBones() {
}
}
void MyAvatar::setupNewAnimationSystem() {
// create a skeleton
auto geom = _skeletonModel.getGeometry()->getFBXGeometry();
std::vector<FBXJoint> joints;
for (auto& joint : geom.joints) {
joints.push_back(joint);
}
AnimPose geometryOffset(_skeletonModel.getGeometry()->getFBXGeometry().offset);
_animSkeleton = make_shared<AnimSkeleton>(joints, geometryOffset);
// add skeleton to the debug renderer, so we can see it.
AnimDebugDraw::getInstance().addSkeleton("my-avatar-skeleton", _animSkeleton, AnimPose::identity);
//_animSkeleton->dump();
qCDebug(interfaceapp) << "geomOffset =" << geometryOffset;
// load the anim graph
// https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9
// python2 -m SimpleHTTPServer&
auto graphUrl = QUrl("http://localhost:8000/avatar.json");
//auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/403651948de088ca4dcdda4f873e225b091c779a/avatar.json");
_animLoader.reset(new AnimNodeLoader(graphUrl));
connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) {
_animNode = nodeIn;
_animNode->setSkeleton(_animSkeleton);
AnimPose xform(glm::vec3(1), glm::quat(), glm::vec3(0, 0, -1));
AnimDebugDraw::getInstance().addAnimNode("my-avatar-animation", _animNode, xform);
});
connect(_animLoader.get(), &AnimNodeLoader::error, [this, graphUrl](int error, QString str) {
qCCritical(interfaceapp) << "Error loading" << graphUrl << "code = " << error << "str =" << str;
});
}
void MyAvatar::teardownNewAnimationSystem() {
AnimDebugDraw::getInstance().removeSkeleton("my-avatar-skeleton");
AnimDebugDraw::getInstance().removeAnimNode("my-avatar-animation");
_animSkeleton = nullptr;
_animLoader = nullptr;
_animNode = nullptr;
}
void MyAvatar::preRender(RenderArgs* renderArgs) {
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
@ -1281,11 +1219,15 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
if (_skeletonModel.initWhenReady(scene)) {
initHeadBones();
_skeletonModel.setCauterizeBoneSet(_headBoneSet);
// AJT: SETUP DEBUG RENDERING OF NEW ANIMATION SYSTEM
// https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9
// python2 -m SimpleHTTPServer&
auto graphUrl = QUrl("http://localhost:8000/avatar.json");
//auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/403651948de088ca4dcdda4f873e225b091c779a/avatar.json");
setupNewAnimationSystem();
_skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry());
}
if (shouldDrawHead != _prevShouldDrawHead) {

View file

@ -16,9 +16,6 @@
#include <DynamicCharacterController.h>
#include <Rig.h>
#include "AnimNode.h"
#include "AnimNodeLoader.h"
#include "Avatar.h"
class ModelItemID;
@ -194,6 +191,7 @@ public slots:
virtual void rebuildSkeletonBody();
void setEnableRigAnimations(bool isEnabled);
void setEnableAnimGraph(bool isEnabled);
signals:
void transformChanged();
@ -201,8 +199,6 @@ signals:
void collisionWithEntity(const Collision& collision);
private:
void setupNewAnimationSystem();
void teardownNewAnimationSystem();
glm::vec3 getWorldBodyPosition() const;
glm::quat getWorldBodyOrientation() const;
@ -317,11 +313,6 @@ private:
std::unordered_set<int> _headBoneSet;
RigPointer _rig;
bool _prevShouldDrawHead;
std::shared_ptr<AnimNode> _animNode;
std::shared_ptr<AnimSkeleton> _animSkeleton;
std::unique_ptr<AnimNodeLoader> _animLoader;
AnimVariantMap _animVars;
};
#endif // hifi_MyAvatar_h

View file

@ -571,3 +571,7 @@ bool SkeletonModel::hasSkeleton() {
void SkeletonModel::onInvalidate() {
}
void SkeletonModel::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) {
_rig->initAnimGraph(url, fbxGeometry);
}

View file

@ -106,6 +106,8 @@ public:
virtual void onInvalidate() override;
void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry);
signals:
void skeletonLoaded();

View file

@ -161,4 +161,20 @@ void AnimSkeleton::dump() const {
}
qCDebug(animation) << "]";
}
void AnimSkeleton::dump(const AnimPoseVec& poses) const {
qCDebug(animation) << "[";
for (int i = 0; i < getNumJoints(); i++) {
qCDebug(animation) << " {";
qCDebug(animation) << " name =" << getJointName(i);
qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i);
qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i);
qCDebug(animation) << " pose =" << poses[i];
if (getParentIndex(i) >= 0) {
qCDebug(animation) << " parent =" << getJointName(getParentIndex(i));
}
qCDebug(animation) << " },";
}
qCDebug(animation) << "]";
}
#endif

View file

@ -61,6 +61,7 @@ public:
#ifndef NDEBUG
void dump() const;
void dump(const AnimPoseVec& poses) const;
#endif
protected:

View file

@ -14,6 +14,9 @@
#include "AnimationHandle.h"
#include "AnimationLogging.h"
#include "AnimSkeleton.h"
#include "Rig.h"
void Rig::HeadParameters::dump() const {
@ -183,6 +186,12 @@ void Rig::deleteAnimations() {
removeAnimationHandle(animation);
}
_animationHandles.clear();
if (_enableAnimGraph) {
_animSkeleton = nullptr;
_animLoader = nullptr;
_animNode = nullptr;
}
}
void Rig::initJointStates(QVector<JointState> states, glm::mat4 parentTransform,
@ -406,6 +415,22 @@ glm::mat4 Rig::getJointVisibleTransform(int jointIndex) const {
}
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) {
if (_enableAnimGraph) {
static float t = 0.0f;
_animVars.set("sine", 0.5f * sin(t) + 0.5f);
if (glm::length(worldVelocity) > 0.07f) {
_animVars.set("isMoving", true);
_animVars.set("isNotMoving", false);
} else {
_animVars.set("isMoving", false);
_animVars.set("isNotMoving", true);
}
t += deltaTime;
}
if (!_enableRig) {
return;
}
@ -442,55 +467,85 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
}
void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
// First normalize the fades so that they sum to 1.0.
// update the fade data in each animation (not normalized as they are an independent propert of animation)
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
float fadePerSecond = handle->getFadePerSecond();
float fade = handle->getFade();
if (fadePerSecond != 0.0f) {
fade += fadePerSecond * deltaTime;
if ((0.0f >= fade) || (fade >= 1.0f)) {
fade = glm::clamp(fade, 0.0f, 1.0f);
handle->setFadePerSecond(0.0f);
if (_enableAnimGraph) {
if (!_animNode) {
return;
}
// evaluate the animation
AnimNode::Triggers triggersOut;
AnimPoseVec poses = _animNode->evaluate(_animVars, deltaTime, triggersOut);
_animVars.clearTriggers();
for (auto& trigger : triggersOut) {
_animVars.setTrigger(trigger);
}
// copy poses into jointStates
const float PRIORITY = 1.0f;
const float MIX = 1.0f;
for (size_t i = 0; i < poses.size(); i++) {
setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, false);
}
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i, parentTransform);
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].resetTransformChanged();
}
} else {
// First normalize the fades so that they sum to 1.0.
// update the fade data in each animation (not normalized as they are an independent propert of animation)
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
float fadePerSecond = handle->getFadePerSecond();
float fade = handle->getFade();
if (fadePerSecond != 0.0f) {
fade += fadePerSecond * deltaTime;
if ((0.0f >= fade) || (fade >= 1.0f)) {
fade = glm::clamp(fade, 0.0f, 1.0f);
handle->setFadePerSecond(0.0f);
}
handle->setFade(fade);
if (fade <= 0.0f) { // stop any finished animations now
handle->setRunning(false, false); // but do not restore joints as it causes a flicker
}
}
handle->setFade(fade);
if (fade <= 0.0f) { // stop any finished animations now
handle->setRunning(false, false); // but do not restore joints as it causes a flicker
}
}
}
// sum the remaining fade data
float fadeTotal = 0.0f;
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
fadeTotal += handle->getFade();
}
float fadeSumSoFar = 0.0f;
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
handle->setPriority(1.0f);
// if no fadeTotal, everyone's (typically just one running) is starting at zero. In that case, blend equally.
float normalizedFade = (fadeTotal != 0.0f) ? (handle->getFade() / fadeTotal) : (1.0f / _runningAnimations.count());
assert(normalizedFade != 0.0f);
// simulate() will blend each animation result into the result so far, based on the pairwise mix at at each step.
// i.e., slerp the 'mix' distance from the result so far towards this iteration's animation result.
// The formula here for mix is based on the idea that, at each step:
// fadeSum is to normalizedFade, as (1 - mix) is to mix
// i.e., fadeSumSoFar/normalizedFade = (1 - mix)/mix
// Then we solve for mix.
// Sanity check: For the first animation, fadeSum = 0, and the mix will always be 1.
// Sanity check: For equal blending, the formula is equivalent to mix = 1 / nAnimationsSoFar++
float mix = 1.0f / ((fadeSumSoFar / normalizedFade) + 1.0f);
assert((0.0f <= mix) && (mix <= 1.0f));
fadeSumSoFar += normalizedFade;
handle->setMix(mix);
handle->simulate(deltaTime);
}
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i, parentTransform);
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].resetTransformChanged();
}
// sum the remaining fade data
float fadeTotal = 0.0f;
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
fadeTotal += handle->getFade();
}
float fadeSumSoFar = 0.0f;
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
handle->setPriority(1.0f);
// if no fadeTotal, everyone's (typically just one running) is starting at zero. In that case, blend equally.
float normalizedFade = (fadeTotal != 0.0f) ? (handle->getFade() / fadeTotal) : (1.0f / _runningAnimations.count());
assert(normalizedFade != 0.0f);
// simulate() will blend each animation result into the result so far, based on the pairwise mix at at each step.
// i.e., slerp the 'mix' distance from the result so far towards this iteration's animation result.
// The formula here for mix is based on the idea that, at each step:
// fadeSum is to normalizedFade, as (1 - mix) is to mix
// i.e., fadeSumSoFar/normalizedFade = (1 - mix)/mix
// Then we solve for mix.
// Sanity check: For the first animation, fadeSum = 0, and the mix will always be 1.
// Sanity check: For equal blending, the formula is equivalent to mix = 1 / nAnimationsSoFar++
float mix = 1.0f / ((fadeSumSoFar / normalizedFade) + 1.0f);
assert((0.0f <= mix) && (mix <= 1.0f));
fadeSumSoFar += normalizedFade;
handle->setMix(mix);
handle->simulate(deltaTime);
}
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i, parentTransform);
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].resetTransformChanged();
}
}
}
@ -824,6 +879,7 @@ void Rig::updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3&
updateEyeJoint(leftEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade);
updateEyeJoint(rightEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade);
}
void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) {
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
auto& state = _jointStates[index];
@ -842,3 +898,38 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
state.getDefaultRotation(), DEFAULT_PRIORITY);
}
}
void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) {
if (!_enableAnimGraph) {
return;
}
// convert to std::vector of joints
std::vector<FBXJoint> joints;
joints.reserve(fbxGeometry.joints.size());
for (auto& joint : fbxGeometry.joints) {
joints.push_back(joint);
}
// create skeleton
AnimPose geometryOffset(fbxGeometry.offset);
_animSkeleton = std::make_shared<AnimSkeleton>(joints, geometryOffset);
// add skeleton to the debug renderer, so we can see it.
// AnimDebugDraw::getInstance().addSkeleton("my-avatar-skeleton", _animSkeleton, AnimPose::identity);
// _animSkeleton->dump();
// load the anim graph
_animLoader.reset(new AnimNodeLoader(url));
connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) {
_animNode = nodeIn;
_animNode->setSkeleton(_animSkeleton);
// add node to debug renderer, for debugging
// AnimPose xform(glm::vec3(1), glm::quat(), glm::vec3(0, 0, -1));
// AnimDebugDraw::getInstance().addAnimNode("my-avatar-animation", _animNode, xform);
});
connect(_animLoader.get(), &AnimNodeLoader::error, [this, url](int error, QString str) {
qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;
});
}

View file

@ -40,6 +40,9 @@
#include "JointState.h" // We might want to change this (later) to something that doesn't depend on gpu, fbx and model. -HRS
#include "AnimNode.h"
#include "AnimNodeLoader.h"
class AnimationHandle;
typedef std::shared_ptr<AnimationHandle> AnimationHandlePointer;
@ -155,6 +158,7 @@ public:
virtual void updateJointState(int index, glm::mat4 parentTransform) = 0;
void setEnableRig(bool isEnabled) { _enableRig = isEnabled; }
void setEnableAnimGraph(bool isEnabled) { _enableAnimGraph = isEnabled; }
void updateFromHeadParameters(const HeadParameters& params);
void updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation,
@ -163,6 +167,8 @@ public:
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
float scale, float priority) = 0;
void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry);
protected:
void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist);
@ -184,8 +190,14 @@ public:
QList<AnimationHandlePointer> _runningAnimations;
bool _enableRig;
bool _enableAnimGraph;
glm::vec3 _lastFront;
glm::vec3 _lastPosition;
std::shared_ptr<AnimNode> _animNode;
std::shared_ptr<AnimSkeleton> _animSkeleton;
std::unique_ptr<AnimNodeLoader> _animLoader;
AnimVariantMap _animVars;
};
#endif /* defined(__hifi__Rig__) */

View file

@ -27,37 +27,15 @@
"children": [
{
"id": "idle",
"type": "blendLinear",
"type": "clip",
"data": {
"alpha": 0.5,
"alphaVar": "sine"
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
"startFrame": 0.0,
"endFrame": 90.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": [
{
"id": "normalIdle",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
"startFrame": 0.0,
"endFrame": 90.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "otherIdle",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
"startFrame": 20.0,
"endFrame": 90.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
}
]
"children": []
},
{
"id": "walkFwd",