From 7b4cb8655c7909220f597dac0cd65d5b2ed9e5d9 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 1 Sep 2015 17:57:01 -0700 Subject: [PATCH] First pass integration of new anim system into rig. --- interface/src/Menu.cpp | 2 + interface/src/Menu.h | 1 + interface/src/avatar/MyAvatar.cpp | 84 ++-------- interface/src/avatar/MyAvatar.h | 11 +- interface/src/avatar/SkeletonModel.cpp | 4 + interface/src/avatar/SkeletonModel.h | 2 + libraries/animation/src/AnimSkeleton.cpp | 16 ++ libraries/animation/src/AnimSkeleton.h | 1 + libraries/animation/src/Rig.cpp | 187 +++++++++++++++++------ libraries/animation/src/Rig.h | 12 ++ tests/animation/src/data/avatar.json | 36 +---- 11 files changed, 198 insertions(+), 158 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4696463181..854f6fefe9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -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, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 94e49abcc7..c050e0b7a3 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -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"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 45c22dc35c..b72dd5c813 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -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 joints; - for (auto& joint : geom.joints) { - joints.push_back(joint); - } - AnimPose geometryOffset(_skeletonModel.getGeometry()->getFBXGeometry().offset); - _animSkeleton = make_shared(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) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b48964d02c..0283310e71 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -16,9 +16,6 @@ #include #include -#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 _headBoneSet; RigPointer _rig; bool _prevShouldDrawHead; - - std::shared_ptr _animNode; - std::shared_ptr _animSkeleton; - std::unique_ptr _animLoader; - AnimVariantMap _animVars; }; #endif // hifi_MyAvatar_h diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index acd2f038f4..0e29986bd1 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -571,3 +571,7 @@ bool SkeletonModel::hasSkeleton() { void SkeletonModel::onInvalidate() { } + +void SkeletonModel::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) { + _rig->initAnimGraph(url, fbxGeometry); +} diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 4ae615eadd..6b04d36de0 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -106,6 +106,8 @@ public: virtual void onInvalidate() override; + void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry); + signals: void skeletonLoaded(); diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index d650c0b964..d8efe55f5d 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -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 diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index cc0d0b1c70..f266fe04b3 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -61,6 +61,7 @@ public: #ifndef NDEBUG void dump() const; + void dump(const AnimPoseVec& poses) const; #endif protected: diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 8adb53024c..356afe6620 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -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 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 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(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; + }); +} diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index a574807eb4..1b7bf72e88 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -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 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 _runningAnimations; bool _enableRig; + bool _enableAnimGraph; glm::vec3 _lastFront; glm::vec3 _lastPosition; + + std::shared_ptr _animNode; + std::shared_ptr _animSkeleton; + std::unique_ptr _animLoader; + AnimVariantMap _animVars; }; #endif /* defined(__hifi__Rig__) */ diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index 5647f74a7f..0988e40ca8 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -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",