AnimBlendLinear: Untested implementation of sync flag.

Move accumulateTime into AnimUtil.
This commit is contained in:
Anthony J. Thibault 2015-10-15 17:57:06 -07:00
parent 3716800b98
commit 2b4788929f
9 changed files with 141 additions and 56 deletions

View file

@ -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": [

View file

@ -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<float> offsets(_children.size(), 0.0f);
std::vector<float> 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<AnimClip>(_children[i]);
assert(clipNode);
if (clipNode) {
lengthSum += clipNode->getEndFrame() - clipNode->getStartFrame();
}
}
float averageLength = lengthSum / (float)_children.size();
auto prevClipNode = std::dynamic_pointer_cast<AnimClip>(_children[prevPoseIndex]);
float prevTimeScale = (prevClipNode->getEndFrame() - prevClipNode->getStartFrame()) / averageLength;
float prevOffset = prevClipNode->getStartFrame();
prevClipNode->setCurrentFrame(prevOffset + prevTimeScale / _syncFrame);
auto nextClipNode = std::dynamic_pointer_cast<AnimClip>(_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;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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<AnimBlendLinear>(id, alpha);
auto node = std::make_shared<AnimBlendLinear>(id, alpha, sync);
if (!alphaVar.isEmpty()) {
node->setAlphaVar(alphaVar);

View file

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

View file

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