mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 20:58:38 +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",
|
"type": "blendLinear",
|
||||||
"data": {
|
"data": {
|
||||||
"alpha": 0.0,
|
"alpha": 0.0,
|
||||||
|
"sync": false,
|
||||||
"alphaVar": "rightHandGrabBlend"
|
"alphaVar": "rightHandGrabBlend"
|
||||||
},
|
},
|
||||||
"children": [
|
"children": [
|
||||||
|
@ -339,6 +340,7 @@
|
||||||
"type": "blendLinear",
|
"type": "blendLinear",
|
||||||
"data": {
|
"data": {
|
||||||
"alpha": 0.0,
|
"alpha": 0.0,
|
||||||
|
"sync": false,
|
||||||
"alphaVar": "leftHandGrabBlend"
|
"alphaVar": "leftHandGrabBlend"
|
||||||
},
|
},
|
||||||
"children": [
|
"children": [
|
||||||
|
|
|
@ -12,10 +12,12 @@
|
||||||
#include "GLMHelpers.h"
|
#include "GLMHelpers.h"
|
||||||
#include "AnimationLogging.h"
|
#include "AnimationLogging.h"
|
||||||
#include "AnimUtil.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),
|
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) {
|
} else if (_children.size() == 1) {
|
||||||
_poses = _children[0]->evaluate(animVars, dt, triggersOut);
|
_poses = _children[0]->evaluate(animVars, dt, triggersOut);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1));
|
float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1));
|
||||||
size_t prevPoseIndex = glm::floor(clampedAlpha);
|
size_t prevPoseIndex = glm::floor(clampedAlpha);
|
||||||
size_t nextPoseIndex = glm::ceil(clampedAlpha);
|
size_t nextPoseIndex = glm::ceil(clampedAlpha);
|
||||||
float alpha = glm::fract(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()) {
|
float prevPoseDeltaTime = dt;
|
||||||
_poses.resize(prevPoses.size());
|
float nextPoseDeltaTime = dt;
|
||||||
|
if (_sync) {
|
||||||
::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]);
|
setSyncFrameAndComputeDeltaTime(dt, prevPoseIndex, nextPoseIndex, &prevPoseDeltaTime, &nextPoseDeltaTime, triggersOut);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevPoseDeltaTime, nextPoseDeltaTime);
|
||||||
}
|
}
|
||||||
return _poses;
|
return _poses;
|
||||||
}
|
}
|
||||||
|
@ -60,3 +57,64 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, flo
|
||||||
const AnimPoseVec& AnimBlendLinear::getPosesInternal() const {
|
const AnimPoseVec& AnimBlendLinear::getPosesInternal() const {
|
||||||
return _poses;
|
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
|
// 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
|
// the closest two children poses. This can be used to sweep through a series
|
||||||
// of animation poses.
|
// 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 {
|
class AnimBlendLinear : public AnimNode {
|
||||||
public:
|
public:
|
||||||
friend class AnimTests;
|
friend class AnimTests;
|
||||||
|
|
||||||
AnimBlendLinear(const QString& id, float alpha);
|
AnimBlendLinear(const QString& id, float alpha, bool sync);
|
||||||
virtual ~AnimBlendLinear() override;
|
virtual ~AnimBlendLinear() override;
|
||||||
|
|
||||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||||
|
@ -38,9 +41,19 @@ protected:
|
||||||
// for AnimDebugDraw rendering
|
// for AnimDebugDraw rendering
|
||||||
virtual const AnimPoseVec& getPosesInternal() const override;
|
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;
|
AnimPoseVec _poses;
|
||||||
|
|
||||||
float _alpha;
|
float _alpha;
|
||||||
|
bool _sync;
|
||||||
|
float _syncFrame = 0.0f;
|
||||||
|
float _timeScale = 1.0f; // TODO: HOOK THIS UP TO AN ANIMVAR.
|
||||||
|
|
||||||
QString _alphaVar;
|
QString _alphaVar;
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,9 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt,
|
||||||
_endFrame = animVars.lookup(_endFrameVar, _endFrame);
|
_endFrame = animVars.lookup(_endFrameVar, _endFrame);
|
||||||
_timeScale = animVars.lookup(_timeScaleVar, _timeScale);
|
_timeScale = animVars.lookup(_timeScaleVar, _timeScale);
|
||||||
_loopFlag = animVars.lookup(_loopFlagVar, _loopFlag);
|
_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.
|
// poll network anim to see if it's finished loading yet.
|
||||||
if (_networkAnim && _networkAnim->isLoaded() && _skeleton) {
|
if (_networkAnim && _networkAnim->isLoaded() && _skeleton) {
|
||||||
|
@ -78,39 +80,7 @@ void AnimClip::setCurrentFrameInternal(float frame) {
|
||||||
// because dt is 0, we should not encounter any triggers
|
// because dt is 0, we should not encounter any triggers
|
||||||
const float dt = 0.0f;
|
const float dt = 0.0f;
|
||||||
Triggers triggers;
|
Triggers triggers;
|
||||||
_frame = accumulateTime(frame * _timeScale, dt, triggers);
|
_frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame * _timeScale, dt, _loopFlag, _id, 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimClip::copyFromNetworkAnim() {
|
void AnimClip::copyFromNetworkAnim() {
|
||||||
|
|
|
@ -36,12 +36,14 @@ public:
|
||||||
void setLoopFlagVar(const QString& loopFlagVar) { _loopFlagVar = loopFlagVar; }
|
void setLoopFlagVar(const QString& loopFlagVar) { _loopFlagVar = loopFlagVar; }
|
||||||
void setFrameVar(const QString& frameVar) { _frameVar = frameVar; }
|
void setFrameVar(const QString& frameVar) { _frameVar = frameVar; }
|
||||||
|
|
||||||
|
float getStartFrame() const { return _startFrame; }
|
||||||
|
float getEndFrame() const { return _endFrame; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void loadURL(const QString& url);
|
void loadURL(const QString& url);
|
||||||
|
|
||||||
virtual void setCurrentFrameInternal(float frame) override;
|
virtual void setCurrentFrameInternal(float frame) override;
|
||||||
|
|
||||||
float accumulateTime(float frame, float dt, Triggers& triggersOut) const;
|
|
||||||
void copyFromNetworkAnim();
|
void copyFromNetworkAnim();
|
||||||
|
|
||||||
// for AnimDebugDraw rendering
|
// for AnimDebugDraw rendering
|
||||||
|
|
|
@ -75,10 +75,10 @@ public:
|
||||||
return evaluate(animVars, dt, triggersOut);
|
return evaluate(animVars, dt, triggersOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
void setCurrentFrame(float frame);
|
void setCurrentFrame(float frame);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
virtual void setCurrentFrameInternal(float frame) {}
|
virtual void setCurrentFrameInternal(float frame) {}
|
||||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { _skeleton = skeleton; }
|
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) {
|
static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
|
||||||
|
|
||||||
READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr);
|
READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr);
|
||||||
|
READ_BOOL(sync, jsonObj, id, jsonUrl, nullptr);
|
||||||
|
|
||||||
READ_OPTIONAL_STRING(alphaVar, jsonObj);
|
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()) {
|
if (!alphaVar.isEmpty()) {
|
||||||
node->setAlphaVar(alphaVar);
|
node->setAlphaVar(alphaVar);
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
#include "AnimUtil.h"
|
#include "AnimUtil.h"
|
||||||
#include "GLMHelpers.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) {
|
void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result) {
|
||||||
for (size_t i = 0; i < numPoses; i++) {
|
for (size_t i = 0; i < numPoses; i++) {
|
||||||
const AnimPose& aPose = a[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);
|
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"
|
#include "AnimNode.h"
|
||||||
|
|
||||||
// TODO: use restrict keyword
|
|
||||||
// TODO: excellent candidate for simd vectorization.
|
|
||||||
|
|
||||||
// this is where the magic happens
|
// this is where the magic happens
|
||||||
void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result);
|
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
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue