mirror of
https://github.com/overte-org/overte.git
synced 2025-04-19 13:43:49 +02:00
AnimBlendLinear: Untested implementation of sync flag.
Move accumulateTime into AnimUtil.
This commit is contained in:
parent
3716800b98
commit
2b4788929f
9 changed files with 141 additions and 56 deletions
|
@ -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": [
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue