From 2b4788929f6cb7e8c5b5944e69415a0b9d73ac42 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 15 Oct 2015 17:57:06 -0700 Subject: [PATCH] AnimBlendLinear: Untested implementation of sync flag. Move accumulateTime into AnimUtil. --- .../defaultAvatar_full/avatar-animation.json | 2 + libraries/animation/src/AnimBlendLinear.cpp | 86 ++++++++++++++++--- libraries/animation/src/AnimBlendLinear.h | 15 +++- libraries/animation/src/AnimClip.cpp | 38 +------- libraries/animation/src/AnimClip.h | 4 +- libraries/animation/src/AnimNode.h | 4 +- libraries/animation/src/AnimNodeLoader.cpp | 3 +- libraries/animation/src/AnimUtil.cpp | 39 +++++++++ libraries/animation/src/AnimUtil.h | 6 +- 9 files changed, 141 insertions(+), 56 deletions(-) diff --git a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json index 682e0be1bf..5b042a09b1 100644 --- a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json +++ b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json @@ -190,6 +190,7 @@ "type": "blendLinear", "data": { "alpha": 0.0, + "sync": false, "alphaVar": "rightHandGrabBlend" }, "children": [ @@ -339,6 +340,7 @@ "type": "blendLinear", "data": { "alpha": 0.0, + "sync": false, "alphaVar": "leftHandGrabBlend" }, "children": [ diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index bc95565f6f..1e65ba2b36 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -12,10 +12,12 @@ #include "GLMHelpers.h" #include "AnimationLogging.h" #include "AnimUtil.h" +#include "AnimClip.h" -AnimBlendLinear::AnimBlendLinear(const QString& id, float alpha) : +AnimBlendLinear::AnimBlendLinear(const QString& id, float alpha, bool sync) : AnimNode(AnimNode::Type::BlendLinear, id), - _alpha(alpha) { + _alpha(alpha), + _sync(sync) { } @@ -34,24 +36,19 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, flo } else if (_children.size() == 1) { _poses = _children[0]->evaluate(animVars, dt, triggersOut); } else { + float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); size_t prevPoseIndex = glm::floor(clampedAlpha); size_t nextPoseIndex = glm::ceil(clampedAlpha); float alpha = glm::fract(clampedAlpha); - if (prevPoseIndex == nextPoseIndex) { - // this can happen if alpha is on an integer boundary - _poses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut); - } else { - // need to eval and blend between two children. - auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut); - auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, dt, triggersOut); - if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) { - _poses.resize(prevPoses.size()); - - ::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]); - } + float prevPoseDeltaTime = dt; + float nextPoseDeltaTime = dt; + if (_sync) { + setSyncFrameAndComputeDeltaTime(dt, prevPoseIndex, nextPoseIndex, &prevPoseDeltaTime, &nextPoseDeltaTime, triggersOut); } + + evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevPoseDeltaTime, nextPoseDeltaTime); } return _poses; } @@ -60,3 +57,64 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, flo const AnimPoseVec& AnimBlendLinear::getPosesInternal() const { return _poses; } + +void AnimBlendLinear::evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha, + size_t prevPoseIndex, size_t nextPoseIndex, + float prevPoseDeltaTime, float nextPoseDeltaTime) { + if (prevPoseIndex == nextPoseIndex) { + // this can happen if alpha is on an integer boundary + _poses = _children[prevPoseIndex]->evaluate(animVars, prevPoseDeltaTime, triggersOut); + } else { + // need to eval and blend between two children. + auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, prevPoseDeltaTime, triggersOut); + auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, nextPoseDeltaTime, triggersOut); + + if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) { + _poses.resize(prevPoses.size()); + + ::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]); + } + } +} + +void AnimBlendLinear::setSyncFrameAndComputeDeltaTime(float dt, size_t prevPoseIndex, size_t nextPoseIndex, + float* prevPoseDeltaTime, float* nextPoseDeltaTime, + Triggers& triggersOut) { + std::vector offsets(_children.size(), 0.0f); + std::vector timeScales(_children.size(), 1.0f); + + float lengthSum = 0.0f; + for (size_t i = 0; i < _children.size(); i++) { + // abort if we find a child that is NOT a clipNode. + if (_children[i]->getType() != AnimNode::Type::Clip) { + // TODO: FIXME: make sync this work for other node types. + *prevPoseDeltaTime = dt; + *nextPoseDeltaTime = dt; + return; + } + auto clipNode = std::dynamic_pointer_cast(_children[i]); + assert(clipNode); + if (clipNode) { + lengthSum += clipNode->getEndFrame() - clipNode->getStartFrame(); + } + } + + float averageLength = lengthSum / (float)_children.size(); + + auto prevClipNode = std::dynamic_pointer_cast(_children[prevPoseIndex]); + float prevTimeScale = (prevClipNode->getEndFrame() - prevClipNode->getStartFrame()) / averageLength; + float prevOffset = prevClipNode->getStartFrame(); + prevClipNode->setCurrentFrame(prevOffset + prevTimeScale / _syncFrame); + + auto nextClipNode = std::dynamic_pointer_cast(_children[nextPoseIndex]); + float nextTimeScale = (nextClipNode->getEndFrame() - nextClipNode->getStartFrame()) / averageLength; + float nextOffset = nextClipNode->getStartFrame(); + nextClipNode->setCurrentFrame(nextOffset + nextTimeScale / _syncFrame); + + const bool LOOP_FLAG = true; + _syncFrame = ::accumulateTime(0.0f, averageLength, _timeScale, _syncFrame, dt, LOOP_FLAG, _id, triggersOut); + + *prevPoseDeltaTime = prevTimeScale; + *nextPoseDeltaTime = nextTimeScale; +} + diff --git a/libraries/animation/src/AnimBlendLinear.h b/libraries/animation/src/AnimBlendLinear.h index 56acd5c2f7..d8b4f52fb6 100644 --- a/libraries/animation/src/AnimBlendLinear.h +++ b/libraries/animation/src/AnimBlendLinear.h @@ -22,12 +22,15 @@ // between 0 and n - 1. This alpha can be used to linearly interpolate between // the closest two children poses. This can be used to sweep through a series // of animation poses. +// +// The sync flag is used to synchronize between child animations of different lengths. +// Typically used to synchronize blending between walk and run cycles. class AnimBlendLinear : public AnimNode { public: friend class AnimTests; - AnimBlendLinear(const QString& id, float alpha); + AnimBlendLinear(const QString& id, float alpha, bool sync); virtual ~AnimBlendLinear() override; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override; @@ -38,9 +41,19 @@ protected: // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; + void evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha, + size_t prevPoseIndex, size_t nextPoseIndex, + float prevPoseDeltaTime, float nextPoseDeltaTime); + void setSyncFrameAndComputeDeltaTime(float dt, size_t prevPoseIndex, size_t nextPoseIndex, + float* prevPoseDeltaTime, float* nextPoseDeltaTime, + Triggers& triggersOut); + AnimPoseVec _poses; float _alpha; + bool _sync; + float _syncFrame = 0.0f; + float _timeScale = 1.0f; // TODO: HOOK THIS UP TO AN ANIMVAR. QString _alphaVar; diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 251cb0047a..97f46a1b68 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -35,7 +35,9 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt, _endFrame = animVars.lookup(_endFrameVar, _endFrame); _timeScale = animVars.lookup(_timeScaleVar, _timeScale); _loopFlag = animVars.lookup(_loopFlagVar, _loopFlag); - _frame = accumulateTime(animVars.lookup(_frameVar, _frame), dt, triggersOut); + float frame = animVars.lookup(_frameVar, _frame); + + _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame, dt, _loopFlag, _id, triggersOut); // poll network anim to see if it's finished loading yet. if (_networkAnim && _networkAnim->isLoaded() && _skeleton) { @@ -78,39 +80,7 @@ void AnimClip::setCurrentFrameInternal(float frame) { // because dt is 0, we should not encounter any triggers const float dt = 0.0f; Triggers triggers; - _frame = accumulateTime(frame * _timeScale, dt, triggers); -} - -float AnimClip::accumulateTime(float frame, float dt, Triggers& triggersOut) const { - const float startFrame = std::min(_startFrame, _endFrame); - if (startFrame == _endFrame) { - // when startFrame >= endFrame - frame = _endFrame; - } else if (_timeScale > 0.0f) { - // accumulate time, keeping track of loops and end of animation events. - const float FRAMES_PER_SECOND = 30.0f; - float framesRemaining = (dt * _timeScale) * FRAMES_PER_SECOND; - while (framesRemaining > 0.0f) { - float framesTillEnd = _endFrame - _frame; - if (framesRemaining >= framesTillEnd) { - if (_loopFlag) { - // anim loop - triggersOut.push_back(_id + "OnLoop"); - framesRemaining -= framesTillEnd; - frame = startFrame; - } else { - // anim end - triggersOut.push_back(_id + "OnDone"); - frame = _endFrame; - framesRemaining = 0.0f; - } - } else { - frame += framesRemaining; - framesRemaining = 0.0f; - } - } - } - return frame; + _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame * _timeScale, dt, _loopFlag, _id, triggers); } void AnimClip::copyFromNetworkAnim() { diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 3a76870c98..b7cd2861fa 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -36,12 +36,14 @@ public: void setLoopFlagVar(const QString& loopFlagVar) { _loopFlagVar = loopFlagVar; } void setFrameVar(const QString& frameVar) { _frameVar = frameVar; } + float getStartFrame() const { return _startFrame; } + float getEndFrame() const { return _endFrame; } + protected: void loadURL(const QString& url); virtual void setCurrentFrameInternal(float frame) override; - float accumulateTime(float frame, float dt, Triggers& triggersOut) const; void copyFromNetworkAnim(); // for AnimDebugDraw rendering diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 9a21526409..315ee357dd 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -75,10 +75,10 @@ public: return evaluate(animVars, dt, triggersOut); } -protected: - void setCurrentFrame(float frame); +protected: + virtual void setCurrentFrameInternal(float frame) {} virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { _skeleton = skeleton; } diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 147025f1cf..2acbeed734 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -221,10 +221,11 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); + READ_BOOL(sync, jsonObj, id, jsonUrl, nullptr); READ_OPTIONAL_STRING(alphaVar, jsonObj); - auto node = std::make_shared(id, alpha); + auto node = std::make_shared(id, alpha, sync); if (!alphaVar.isEmpty()) { node->setAlphaVar(alphaVar); diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index 81b294e66c..c8efa85df9 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -11,6 +11,9 @@ #include "AnimUtil.h" #include "GLMHelpers.h" +// TODO: use restrict keyword +// TODO: excellent candidate for simd vectorization. + void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result) { for (size_t i = 0; i < numPoses; i++) { const AnimPose& aPose = a[i]; @@ -20,3 +23,39 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A result[i].trans = lerp(aPose.trans, bPose.trans, alpha); } } + +float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, + const QString& id, AnimNode::Triggers& triggersOut) { + + float frame = currentFrame; + const float clampedStartFrame = std::min(startFrame, endFrame); + if (clampedStartFrame == endFrame) { + // when clampedStartFrame >= endFrame + frame = endFrame; + } else if (timeScale > 0.0f) { + // accumulate time, keeping track of loops and end of animation events. + const float FRAMES_PER_SECOND = 30.0f; + float framesRemaining = (dt * timeScale) * FRAMES_PER_SECOND; + while (framesRemaining > 0.0f) { + float framesTillEnd = endFrame - frame; + if (framesRemaining >= framesTillEnd) { + if (loopFlag) { + // anim loop + triggersOut.push_back(id + "OnLoop"); + framesRemaining -= framesTillEnd; + frame = clampedStartFrame; + } else { + // anim end + triggersOut.push_back(id + "OnDone"); + frame = endFrame; + framesRemaining = 0.0f; + } + } else { + frame += framesRemaining; + framesRemaining = 0.0f; + } + } + } + return frame; +} + diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index 23c02b6183..6d394be882 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -13,12 +13,12 @@ #include "AnimNode.h" -// TODO: use restrict keyword -// TODO: excellent candidate for simd vectorization. - // this is where the magic happens void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result); +float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, + const QString& id, AnimNode::Triggers& triggersOut); + #endif