mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 14:47:41 +02:00
Merge branch 'tony/anim-sync-blend' into test
This commit is contained in:
commit
41cce4320a
18 changed files with 474 additions and 155 deletions
|
@ -4,7 +4,7 @@
|
||||||
"id": "ikOverlay",
|
"id": "ikOverlay",
|
||||||
"type": "overlay",
|
"type": "overlay",
|
||||||
"data": {
|
"data": {
|
||||||
"alpha": 1.0,
|
"alpha": 0.0,
|
||||||
"boneSet": "fullBody"
|
"boneSet": "fullBody"
|
||||||
},
|
},
|
||||||
"children": [
|
"children": [
|
||||||
|
@ -190,6 +190,8 @@
|
||||||
"type": "blendLinear",
|
"type": "blendLinear",
|
||||||
"data": {
|
"data": {
|
||||||
"alpha": 0.0,
|
"alpha": 0.0,
|
||||||
|
"sync": false,
|
||||||
|
"timeScale": 1.0,
|
||||||
"alphaVar": "rightHandGrabBlend"
|
"alphaVar": "rightHandGrabBlend"
|
||||||
},
|
},
|
||||||
"children": [
|
"children": [
|
||||||
|
@ -339,6 +341,8 @@
|
||||||
"type": "blendLinear",
|
"type": "blendLinear",
|
||||||
"data": {
|
"data": {
|
||||||
"alpha": 0.0,
|
"alpha": 0.0,
|
||||||
|
"sync": false,
|
||||||
|
"timeScale": 1.0,
|
||||||
"alphaVar": "leftHandGrabBlend"
|
"alphaVar": "leftHandGrabBlend"
|
||||||
},
|
},
|
||||||
"children": [
|
"children": [
|
||||||
|
@ -523,16 +527,52 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "walkFwd",
|
"id": "walkFwd",
|
||||||
"type": "clip",
|
"type": "blendLinear",
|
||||||
"data": {
|
"data": {
|
||||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx",
|
"alpha": 0.0,
|
||||||
"startFrame": 0.0,
|
"sync": true,
|
||||||
"endFrame": 35.0,
|
|
||||||
"timeScale": 1.0,
|
"timeScale": 1.0,
|
||||||
"loopFlag": true,
|
"timeScaleVar": "walkTimeScale",
|
||||||
"timeScaleVar": "walkTimeScale"
|
"alphaVar": "walkAlpha"
|
||||||
},
|
},
|
||||||
"children": []
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "walkFwdShort",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_short_fwd.fbx",
|
||||||
|
"startFrame": 0.0,
|
||||||
|
"endFrame": 39.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": true
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "walkFwdNormal",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx",
|
||||||
|
"startFrame": 0.0,
|
||||||
|
"endFrame": 35.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": true
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "walkFwdRun",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/run_fwd.fbx",
|
||||||
|
"startFrame": 0.0,
|
||||||
|
"endFrame": 21.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": true
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "walkBwd",
|
"id": "walkBwd",
|
||||||
|
@ -577,7 +617,7 @@
|
||||||
"data": {
|
"data": {
|
||||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_left.fbx",
|
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_left.fbx",
|
||||||
"startFrame": 0.0,
|
"startFrame": 0.0,
|
||||||
"endFrame": 31.0,
|
"endFrame": 30.0,
|
||||||
"timeScale": 1.0,
|
"timeScale": 1.0,
|
||||||
"loopFlag": true
|
"loopFlag": true
|
||||||
},
|
},
|
||||||
|
@ -589,7 +629,7 @@
|
||||||
"data": {
|
"data": {
|
||||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_right.fbx",
|
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_right.fbx",
|
||||||
"startFrame": 0.0,
|
"startFrame": 0.0,
|
||||||
"endFrame": 31.0,
|
"endFrame": 30.0,
|
||||||
"timeScale": 1.0,
|
"timeScale": 1.0,
|
||||||
"loopFlag": true
|
"loopFlag": true
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,10 +12,13 @@
|
||||||
#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, float timeScale) :
|
||||||
AnimNode(AnimNode::Type::BlendLinear, id),
|
AnimNode(AnimNode::Type::BlendLinear, id),
|
||||||
_alpha(alpha) {
|
_alpha(alpha),
|
||||||
|
_sync(sync),
|
||||||
|
_timeScale(timeScale) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +29,7 @@ AnimBlendLinear::~AnimBlendLinear() {
|
||||||
const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||||
|
|
||||||
_alpha = animVars.lookup(_alphaVar, _alpha);
|
_alpha = animVars.lookup(_alphaVar, _alpha);
|
||||||
|
_timeScale = animVars.lookup(_timeScaleVar, _timeScale);
|
||||||
|
|
||||||
if (_children.size() == 0) {
|
if (_children.size() == 0) {
|
||||||
for (auto&& pose : _poses) {
|
for (auto&& pose : _poses) {
|
||||||
|
@ -34,24 +38,17 @@ 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()) {
|
if (_sync) {
|
||||||
_poses.resize(prevPoses.size());
|
setSyncAndAccumulateTime(dt, prevPoseIndex, nextPoseIndex, triggersOut);
|
||||||
|
|
||||||
::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, dt);
|
||||||
}
|
}
|
||||||
return _poses;
|
return _poses;
|
||||||
}
|
}
|
||||||
|
@ -60,3 +57,73 @@ 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 dt) {
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimBlendLinear::setSyncAndAccumulateTime(float dt, size_t prevPoseIndex, size_t nextPoseIndex, 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.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto clipNode = std::dynamic_pointer_cast<AnimClip>(_children[i]);
|
||||||
|
assert(clipNode);
|
||||||
|
if (clipNode) {
|
||||||
|
lengthSum += (clipNode->getEndFrame() - clipNode->getStartFrame()) + 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_averageLength = lengthSum / (float)_children.size();
|
||||||
|
|
||||||
|
float progress = (_syncFrame / _averageLength);
|
||||||
|
|
||||||
|
auto prevClipNode = std::dynamic_pointer_cast<AnimClip>(_children[prevPoseIndex]);
|
||||||
|
float prevLength = (prevClipNode->getEndFrame() - prevClipNode->getStartFrame()) + 1.0f;
|
||||||
|
float prevOffset = prevClipNode->getStartFrame();
|
||||||
|
float prevFrame = prevOffset + (progress * prevLength);
|
||||||
|
float prevTimeScale = _timeScale * (_averageLength / prevLength);
|
||||||
|
prevClipNode->setTimeScale(prevTimeScale);
|
||||||
|
prevClipNode->setCurrentFrame(prevFrame);
|
||||||
|
|
||||||
|
auto nextClipNode = std::dynamic_pointer_cast<AnimClip>(_children[nextPoseIndex]);
|
||||||
|
float nextLength = (nextClipNode->getEndFrame() - nextClipNode->getStartFrame()) + 1.0f;
|
||||||
|
float nextOffset = nextClipNode->getStartFrame();
|
||||||
|
float nextFrame = nextOffset + (progress * nextLength);
|
||||||
|
float nextTimeScale = _timeScale * (_averageLength / nextLength);
|
||||||
|
nextClipNode->setTimeScale(nextTimeScale);
|
||||||
|
nextClipNode->setCurrentFrame(nextFrame);
|
||||||
|
|
||||||
|
const float START_FRAME = 0.0f;
|
||||||
|
const bool LOOP_FLAG = true;
|
||||||
|
_syncFrame = ::accumulateTime(START_FRAME, _averageLength, _timeScale, _syncFrame, dt, LOOP_FLAG, _id, triggersOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimBlendLinear::setCurrentFrameInternal(float frame) {
|
||||||
|
// because dt is 0, we should not encounter any triggers
|
||||||
|
const float dt = 0.0f;
|
||||||
|
Triggers triggers;
|
||||||
|
const float START_FRAME = 0.0f;
|
||||||
|
const bool LOOP_FLAG = true;
|
||||||
|
_syncFrame = ::accumulateTime(START_FRAME, _averageLength, _timeScale, frame, dt, LOOP_FLAG, _id, triggers);
|
||||||
|
}
|
||||||
|
|
|
@ -22,27 +22,43 @@
|
||||||
// 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, float timeScale);
|
||||||
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;
|
||||||
|
|
||||||
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
|
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
|
||||||
|
void setTimeScaleVar(const QString& timeScaleVar) { _timeScaleVar = timeScaleVar; }
|
||||||
|
|
||||||
protected:
|
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 dt);
|
||||||
|
void setSyncAndAccumulateTime(float dt, size_t prevPoseIndex, size_t nextPoseIndex, Triggers& triggersOut);
|
||||||
|
|
||||||
|
virtual void setCurrentFrameInternal(float frame) override;
|
||||||
|
|
||||||
AnimPoseVec _poses;
|
AnimPoseVec _poses;
|
||||||
|
|
||||||
float _alpha;
|
float _alpha;
|
||||||
|
bool _sync;
|
||||||
|
float _timeScale;
|
||||||
|
|
||||||
|
float _syncFrame = 0.0f;
|
||||||
|
float _averageLength = 0.0f; // average length of child animations in frames.
|
||||||
|
|
||||||
QString _alphaVar;
|
QString _alphaVar;
|
||||||
|
QString _timeScaleVar;
|
||||||
|
|
||||||
// no copies
|
// no copies
|
||||||
AnimBlendLinear(const AnimBlendLinear&) = delete;
|
AnimBlendLinear(const AnimBlendLinear&) = delete;
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -45,16 +47,17 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_anim.size()) {
|
if (_anim.size()) {
|
||||||
int frameCount = _anim.size();
|
|
||||||
|
|
||||||
int prevIndex = (int)glm::floor(_frame);
|
int prevIndex = (int)glm::floor(_frame);
|
||||||
int nextIndex = (int)glm::ceil(_frame);
|
int nextIndex;
|
||||||
if (_loopFlag && nextIndex >= frameCount) {
|
if (_loopFlag && _frame >= _endFrame) {
|
||||||
nextIndex = 0;
|
nextIndex = (int)glm::ceil(_startFrame);
|
||||||
|
} else {
|
||||||
|
nextIndex = (int)glm::ceil(_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// It can be quite possible for the user to set _startFrame and _endFrame to
|
// It can be quite possible for the user to set _startFrame and _endFrame to
|
||||||
// values before or past valid ranges. We clamp the frames here.
|
// values before or past valid ranges. We clamp the frames here.
|
||||||
|
int frameCount = _anim.size();
|
||||||
prevIndex = std::min(std::max(0, prevIndex), frameCount - 1);
|
prevIndex = std::min(std::max(0, prevIndex), frameCount - 1);
|
||||||
nextIndex = std::min(std::max(0, nextIndex), frameCount - 1);
|
nextIndex = std::min(std::max(0, nextIndex), frameCount - 1);
|
||||||
|
|
||||||
|
@ -78,39 +81,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, 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,17 @@ 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; }
|
||||||
|
|
||||||
|
void setTimeScale(float timeScale) { _timeScale = timeScale; }
|
||||||
|
float getTimeScale() const { return _timeScale; }
|
||||||
|
|
||||||
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; }
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,9 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr
|
||||||
|
|
||||||
assert((int)type >= 0 && type < AnimNode::Type::NumTypes);
|
assert((int)type >= 0 && type < AnimNode::Type::NumTypes);
|
||||||
auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl);
|
auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl);
|
||||||
|
if (!node) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
auto childrenValue = jsonObj.value("children");
|
auto childrenValue = jsonObj.value("children");
|
||||||
if (!childrenValue.isArray()) {
|
if (!childrenValue.isArray()) {
|
||||||
|
@ -221,15 +224,22 @@ 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_FLOAT(timeScale, jsonObj, id, jsonUrl, nullptr);
|
||||||
|
|
||||||
READ_OPTIONAL_STRING(alphaVar, jsonObj);
|
READ_OPTIONAL_STRING(alphaVar, jsonObj);
|
||||||
|
READ_OPTIONAL_STRING(timeScaleVar, jsonObj);
|
||||||
|
|
||||||
auto node = std::make_shared<AnimBlendLinear>(id, alpha);
|
auto node = std::make_shared<AnimBlendLinear>(id, alpha, sync, timeScale);
|
||||||
|
|
||||||
if (!alphaVar.isEmpty()) {
|
if (!alphaVar.isEmpty()) {
|
||||||
node->setAlphaVar(alphaVar);
|
node->setAlphaVar(alphaVar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!timeScaleVar.isEmpty()) {
|
||||||
|
node->setTimeScaleVar(timeScaleVar);
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
56
libraries/animation/src/AnimPose.cpp
Normal file
56
libraries/animation/src/AnimPose.cpp
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
//
|
||||||
|
// AnimPose.cpp
|
||||||
|
//
|
||||||
|
// Created by Anthony J. Thibault on 10/14/15.
|
||||||
|
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "AnimPose.h"
|
||||||
|
#include "GLMHelpers.h"
|
||||||
|
|
||||||
|
const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f),
|
||||||
|
glm::quat(),
|
||||||
|
glm::vec3(0.0f));
|
||||||
|
|
||||||
|
AnimPose::AnimPose(const glm::mat4& mat) {
|
||||||
|
scale = extractScale(mat);
|
||||||
|
rot = glm::normalize(glm::quat_cast(mat));
|
||||||
|
trans = extractTranslation(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 AnimPose::operator*(const glm::vec3& rhs) const {
|
||||||
|
return trans + (rot * (scale * rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 AnimPose::xformPoint(const glm::vec3& rhs) const {
|
||||||
|
return *this * rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// really slow
|
||||||
|
glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const {
|
||||||
|
glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f);
|
||||||
|
glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f);
|
||||||
|
glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z);
|
||||||
|
glm::mat3 mat(xAxis, yAxis, zAxis);
|
||||||
|
glm::mat3 transInvMat = glm::inverse(glm::transpose(mat));
|
||||||
|
return transInvMat * rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimPose AnimPose::operator*(const AnimPose& rhs) const {
|
||||||
|
return AnimPose(static_cast<glm::mat4>(*this) * static_cast<glm::mat4>(rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimPose AnimPose::inverse() const {
|
||||||
|
return AnimPose(glm::inverse(static_cast<glm::mat4>(*this)));
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimPose::operator glm::mat4() const {
|
||||||
|
glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f);
|
||||||
|
glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f);
|
||||||
|
glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z);
|
||||||
|
return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f),
|
||||||
|
glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f));
|
||||||
|
}
|
47
libraries/animation/src/AnimPose.h
Normal file
47
libraries/animation/src/AnimPose.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// AnimPose.h
|
||||||
|
//
|
||||||
|
// Created by Anthony J. Thibault on 10/14/15.
|
||||||
|
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_AnimPose
|
||||||
|
#define hifi_AnimPose
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <vector>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
|
struct AnimPose {
|
||||||
|
AnimPose() {}
|
||||||
|
explicit AnimPose(const glm::mat4& mat);
|
||||||
|
AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : scale(scaleIn), rot(rotIn), trans(transIn) {}
|
||||||
|
static const AnimPose identity;
|
||||||
|
|
||||||
|
glm::vec3 xformPoint(const glm::vec3& rhs) const;
|
||||||
|
glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow
|
||||||
|
|
||||||
|
glm::vec3 operator*(const glm::vec3& rhs) const; // same as xformPoint
|
||||||
|
AnimPose operator*(const AnimPose& rhs) const;
|
||||||
|
|
||||||
|
AnimPose inverse() const;
|
||||||
|
operator glm::mat4() const;
|
||||||
|
|
||||||
|
glm::vec3 scale;
|
||||||
|
glm::quat rot;
|
||||||
|
glm::vec3 trans;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline QDebug operator<<(QDebug debug, const AnimPose& pose) {
|
||||||
|
debug << "AnimPose, trans = (" << pose.trans.x << pose.trans.y << pose.trans.z << "), rot = (" << pose.rot.x << pose.rot.y << pose.rot.z << pose.rot.w << "), scale = (" << pose.scale.x << pose.scale.y << pose.scale.z << ")";
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
using AnimPoseVec = std::vector<AnimPose>;
|
||||||
|
|
||||||
|
#endif
|
|
@ -16,50 +16,6 @@
|
||||||
|
|
||||||
#include "AnimationLogging.h"
|
#include "AnimationLogging.h"
|
||||||
|
|
||||||
const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f),
|
|
||||||
glm::quat(),
|
|
||||||
glm::vec3(0.0f));
|
|
||||||
|
|
||||||
AnimPose::AnimPose(const glm::mat4& mat) {
|
|
||||||
scale = extractScale(mat);
|
|
||||||
rot = glm::normalize(glm::quat_cast(mat));
|
|
||||||
trans = extractTranslation(mat);
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 AnimPose::operator*(const glm::vec3& rhs) const {
|
|
||||||
return trans + (rot * (scale * rhs));
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 AnimPose::xformPoint(const glm::vec3& rhs) const {
|
|
||||||
return *this * rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// really slow
|
|
||||||
glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const {
|
|
||||||
glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f);
|
|
||||||
glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f);
|
|
||||||
glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z);
|
|
||||||
glm::mat3 mat(xAxis, yAxis, zAxis);
|
|
||||||
glm::mat3 transInvMat = glm::inverse(glm::transpose(mat));
|
|
||||||
return transInvMat * rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimPose AnimPose::operator*(const AnimPose& rhs) const {
|
|
||||||
return AnimPose(static_cast<glm::mat4>(*this) * static_cast<glm::mat4>(rhs));
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimPose AnimPose::inverse() const {
|
|
||||||
return AnimPose(glm::inverse(static_cast<glm::mat4>(*this)));
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimPose::operator glm::mat4() const {
|
|
||||||
glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f);
|
|
||||||
glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f);
|
|
||||||
glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z);
|
|
||||||
return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f),
|
|
||||||
glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimSkeleton::AnimSkeleton(const FBXGeometry& fbxGeometry) {
|
AnimSkeleton::AnimSkeleton(const FBXGeometry& fbxGeometry) {
|
||||||
// convert to std::vector of joints
|
// convert to std::vector of joints
|
||||||
std::vector<FBXJoint> joints;
|
std::vector<FBXJoint> joints;
|
||||||
|
|
|
@ -16,33 +16,7 @@
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
#include <FBXReader.h>
|
#include <FBXReader.h>
|
||||||
|
#include "AnimPose.h"
|
||||||
struct AnimPose {
|
|
||||||
AnimPose() {}
|
|
||||||
explicit AnimPose(const glm::mat4& mat);
|
|
||||||
AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : scale(scaleIn), rot(rotIn), trans(transIn) {}
|
|
||||||
static const AnimPose identity;
|
|
||||||
|
|
||||||
glm::vec3 xformPoint(const glm::vec3& rhs) const;
|
|
||||||
glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow
|
|
||||||
|
|
||||||
glm::vec3 operator*(const glm::vec3& rhs) const; // same as xformPoint
|
|
||||||
AnimPose operator*(const AnimPose& rhs) const;
|
|
||||||
|
|
||||||
AnimPose inverse() const;
|
|
||||||
operator glm::mat4() const;
|
|
||||||
|
|
||||||
glm::vec3 scale;
|
|
||||||
glm::quat rot;
|
|
||||||
glm::vec3 trans;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline QDebug operator<<(QDebug debug, const AnimPose& pose) {
|
|
||||||
debug << "AnimPose, trans = (" << pose.trans.x << pose.trans.y << pose.trans.z << "), rot = (" << pose.rot.x << pose.rot.y << pose.rot.z << pose.rot.w << "), scale = (" << pose.scale.x << pose.scale.y << pose.scale.z << ")";
|
|
||||||
return debug;
|
|
||||||
}
|
|
||||||
|
|
||||||
using AnimPoseVec = std::vector<AnimPose>;
|
|
||||||
|
|
||||||
class AnimSkeleton {
|
class AnimSkeleton {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -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,42 @@ 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 (fabsf(clampedStartFrame - endFrame) < 1.0f) {
|
||||||
|
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;
|
||||||
|
// when looping, add one frame between start and end.
|
||||||
|
if (loopFlag) {
|
||||||
|
framesTillEnd += 1.0f;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -408,6 +408,41 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const {
|
||||||
return _jointStates[jointIndex].getTransform();
|
return _jointStates[jointIndex].getTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Rig::calcWalkForwardAlphaAndTimeScale(float speed, float* alphaOut, float* timeScaleOut) {
|
||||||
|
|
||||||
|
// filter speed using a moving average.
|
||||||
|
_averageForwardSpeed.updateAverage(speed);
|
||||||
|
speed = _averageForwardSpeed.getAverage();
|
||||||
|
|
||||||
|
const int NUM_FWD_SPEEDS = 3;
|
||||||
|
float FWD_SPEEDS[NUM_FWD_SPEEDS] = { 0.3f, 1.4f, 2.7f }; // m/s
|
||||||
|
|
||||||
|
// first calculate alpha by lerping between speeds.
|
||||||
|
float alpha = 0.0f;
|
||||||
|
if (speed <= FWD_SPEEDS[0]) {
|
||||||
|
alpha = 0.0f;
|
||||||
|
} else if (speed > FWD_SPEEDS[NUM_FWD_SPEEDS - 1]) {
|
||||||
|
alpha = (float)(NUM_FWD_SPEEDS - 1);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < NUM_FWD_SPEEDS - 1; i++) {
|
||||||
|
if (FWD_SPEEDS[i] < speed && speed < FWD_SPEEDS[i + 1]) {
|
||||||
|
alpha = (float)i + ((speed - FWD_SPEEDS[i]) / (FWD_SPEEDS[i + 1] - FWD_SPEEDS[i]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now keeping the alpha fixed compute the timeScale.
|
||||||
|
// NOTE: This makes the assumption that the velocity of a linear blend between two animations is also linear.
|
||||||
|
int prevIndex = glm::floor(alpha);
|
||||||
|
int nextIndex = glm::ceil(alpha);
|
||||||
|
float animSpeed = lerp(FWD_SPEEDS[prevIndex], FWD_SPEEDS[nextIndex], (float)glm::fract(alpha));
|
||||||
|
float timeScale = glm::clamp(0.5f, 2.0f, speed / animSpeed);
|
||||||
|
|
||||||
|
*alphaOut = alpha;
|
||||||
|
*timeScaleOut = timeScale;
|
||||||
|
}
|
||||||
|
|
||||||
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) {
|
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) {
|
||||||
|
|
||||||
glm::vec3 front = worldRotation * IDENTITY_FRONT;
|
glm::vec3 front = worldRotation * IDENTITY_FRONT;
|
||||||
|
@ -435,10 +470,13 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
||||||
|
|
||||||
// sine wave LFO var for testing.
|
// sine wave LFO var for testing.
|
||||||
static float t = 0.0f;
|
static float t = 0.0f;
|
||||||
_animVars.set("sine", static_cast<float>(0.5 * sin(t) + 0.5));
|
_animVars.set("sine", 2.0f * static_cast<float>(0.5 * sin(t) + 0.5));
|
||||||
|
|
||||||
const float ANIM_WALK_SPEED = 1.4f; // m/s
|
float walkAlpha, walkTimeScale;
|
||||||
_animVars.set("walkTimeScale", glm::clamp(0.5f, 2.0f, glm::length(localVel) / ANIM_WALK_SPEED));
|
calcWalkForwardAlphaAndTimeScale(glm::length(localVel), &walkAlpha, &walkTimeScale);
|
||||||
|
|
||||||
|
_animVars.set("walkTimeScale", walkTimeScale);
|
||||||
|
_animVars.set("walkAlpha", walkAlpha);
|
||||||
|
|
||||||
const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec
|
const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec
|
||||||
const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec
|
const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
|
|
||||||
#include "AnimNode.h"
|
#include "AnimNode.h"
|
||||||
#include "AnimNodeLoader.h"
|
#include "AnimNodeLoader.h"
|
||||||
|
#include "SimpleMovingAverage.h"
|
||||||
|
|
||||||
class AnimationHandle;
|
class AnimationHandle;
|
||||||
typedef std::shared_ptr<AnimationHandle> AnimationHandlePointer;
|
typedef std::shared_ptr<AnimationHandle> AnimationHandlePointer;
|
||||||
|
@ -207,6 +208,7 @@ public:
|
||||||
void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist);
|
void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist);
|
||||||
void updateNeckJoint(int index, const HeadParameters& params);
|
void updateNeckJoint(int index, const HeadParameters& params);
|
||||||
void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade);
|
void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade);
|
||||||
|
void calcWalkForwardAlphaAndTimeScale(float speed, float* alphaOut, float* timeScaleOut);
|
||||||
|
|
||||||
QVector<JointState> _jointStates;
|
QVector<JointState> _jointStates;
|
||||||
int _rootJointIndex = -1;
|
int _rootJointIndex = -1;
|
||||||
|
@ -241,6 +243,8 @@ public:
|
||||||
float _desiredStateAge = 0.0f;
|
float _desiredStateAge = 0.0f;
|
||||||
float _leftHandOverlayAlpha = 0.0f;
|
float _leftHandOverlayAlpha = 0.0f;
|
||||||
float _rightHandOverlayAlpha = 0.0f;
|
float _rightHandOverlayAlpha = 0.0f;
|
||||||
|
|
||||||
|
SimpleMovingAverage _averageForwardSpeed{ 25 };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__Rig__) */
|
#endif /* defined(__hifi__Rig__) */
|
||||||
|
|
|
@ -183,6 +183,11 @@ T toNormalizedDeviceScale(const T& value, const T& size) {
|
||||||
#define PITCH(euler) euler.x
|
#define PITCH(euler) euler.x
|
||||||
#define ROLL(euler) euler.z
|
#define ROLL(euler) euler.z
|
||||||
|
|
||||||
|
// float - linear interpolate
|
||||||
|
inline float lerp(float x, float y, float a) {
|
||||||
|
return x * (1.0f - a) + (y * a);
|
||||||
|
}
|
||||||
|
|
||||||
// vec2 lerp - linear interpolate
|
// vec2 lerp - linear interpolate
|
||||||
template<typename T, glm::precision P>
|
template<typename T, glm::precision P>
|
||||||
glm::detail::tvec2<T, P> lerp(const glm::detail::tvec2<T, P>& x, const glm::detail::tvec2<T, P>& y, T a) {
|
glm::detail::tvec2<T, P> lerp(const glm::detail::tvec2<T, P>& x, const glm::detail::tvec2<T, P>& y, T a) {
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "AnimBlendLinear.h"
|
#include "AnimBlendLinear.h"
|
||||||
#include "AnimationLogging.h"
|
#include "AnimationLogging.h"
|
||||||
#include "AnimVariant.h"
|
#include "AnimVariant.h"
|
||||||
|
#include "AnimUtil.h"
|
||||||
|
|
||||||
#include <../QTestExtensions.h>
|
#include <../QTestExtensions.h>
|
||||||
|
|
||||||
|
@ -30,8 +31,8 @@ void AnimTests::cleanupTestCase() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimTests::testClipInternalState() {
|
void AnimTests::testClipInternalState() {
|
||||||
std::string id = "my anim clip";
|
QString id = "my anim clip";
|
||||||
std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
|
QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
|
||||||
float startFrame = 2.0f;
|
float startFrame = 2.0f;
|
||||||
float endFrame = 20.0f;
|
float endFrame = 20.0f;
|
||||||
float timeScale = 1.1f;
|
float timeScale = 1.1f;
|
||||||
|
@ -55,8 +56,8 @@ static float framesToSec(float secs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimTests::testClipEvaulate() {
|
void AnimTests::testClipEvaulate() {
|
||||||
std::string id = "myClipNode";
|
QString id = "myClipNode";
|
||||||
std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
|
QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
|
||||||
float startFrame = 2.0f;
|
float startFrame = 2.0f;
|
||||||
float endFrame = 22.0f;
|
float endFrame = 22.0f;
|
||||||
float timeScale = 1.0f;
|
float timeScale = 1.0f;
|
||||||
|
@ -73,8 +74,8 @@ void AnimTests::testClipEvaulate() {
|
||||||
|
|
||||||
// does it loop?
|
// does it loop?
|
||||||
triggers.clear();
|
triggers.clear();
|
||||||
clip.evaluate(vars, framesToSec(11.0f), triggers);
|
clip.evaluate(vars, framesToSec(12.0f), triggers);
|
||||||
QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, EPSILON);
|
QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, EPSILON); // Note: frame 3 and not 4, because extra frame between start and end.
|
||||||
|
|
||||||
// did we receive a loop trigger?
|
// did we receive a loop trigger?
|
||||||
QVERIFY(std::find(triggers.begin(), triggers.end(), "myClipNodeOnLoop") != triggers.end());
|
QVERIFY(std::find(triggers.begin(), triggers.end(), "myClipNodeOnLoop") != triggers.end());
|
||||||
|
@ -90,8 +91,8 @@ void AnimTests::testClipEvaulate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimTests::testClipEvaulateWithVars() {
|
void AnimTests::testClipEvaulateWithVars() {
|
||||||
std::string id = "myClipNode";
|
QString id = "myClipNode";
|
||||||
std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
|
QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
|
||||||
float startFrame = 2.0f;
|
float startFrame = 2.0f;
|
||||||
float endFrame = 22.0f;
|
float endFrame = 22.0f;
|
||||||
float timeScale = 1.0f;
|
float timeScale = 1.0f;
|
||||||
|
@ -126,9 +127,9 @@ void AnimTests::testClipEvaulateWithVars() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimTests::testLoader() {
|
void AnimTests::testLoader() {
|
||||||
auto url = QUrl("https://gist.githubusercontent.com/hyperlogic/857129fe04567cbe670f/raw/8ba57a8f0a76f88b39a11f77f8d9df04af9cec95/test.json");
|
auto url = QUrl("https://gist.githubusercontent.com/hyperlogic/857129fe04567cbe670f/raw/0c54500f480fd7314a5aeb147c45a8a707edcc2e/test.json");
|
||||||
// NOTE: This will warn about missing "test01.fbx", "test02.fbx", etc. if the resource loading code doesn't handle relative pathnames!
|
// NOTE: This will warn about missing "test01.fbx", "test02.fbx", etc. if the resource loading code doesn't handle relative pathnames!
|
||||||
// However, the test will proceed.
|
// However, the test will proceed.
|
||||||
AnimNodeLoader loader(url);
|
AnimNodeLoader loader(url);
|
||||||
|
|
||||||
const int timeout = 1000;
|
const int timeout = 1000;
|
||||||
|
@ -238,3 +239,87 @@ void AnimTests::testVariant() {
|
||||||
QVERIFY(m[1].z == -7.0f);
|
QVERIFY(m[1].z == -7.0f);
|
||||||
QVERIFY(m[3].w == 16.0f);
|
QVERIFY(m[3].w == 16.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnimTests::testAccumulateTime() {
|
||||||
|
|
||||||
|
float startFrame = 0.0f;
|
||||||
|
float endFrame = 10.0f;
|
||||||
|
float timeScale = 1.0f;
|
||||||
|
testAccumulateTimeWithParameters(startFrame, endFrame, timeScale);
|
||||||
|
|
||||||
|
startFrame = 5.0f;
|
||||||
|
endFrame = 15.0f;
|
||||||
|
timeScale = 1.0f;
|
||||||
|
testAccumulateTimeWithParameters(startFrame, endFrame, timeScale);
|
||||||
|
|
||||||
|
startFrame = 0.0f;
|
||||||
|
endFrame = 10.0f;
|
||||||
|
timeScale = 0.5f;
|
||||||
|
testAccumulateTimeWithParameters(startFrame, endFrame, timeScale);
|
||||||
|
|
||||||
|
startFrame = 5.0f;
|
||||||
|
endFrame = 15.0f;
|
||||||
|
timeScale = 2.0f;
|
||||||
|
testAccumulateTimeWithParameters(startFrame, endFrame, timeScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFrame, float timeScale) const {
|
||||||
|
|
||||||
|
float dt = (1.0f / 30.0f) / timeScale; // sec
|
||||||
|
QString id = "testNode";
|
||||||
|
AnimNode::Triggers triggers;
|
||||||
|
bool loopFlag = false;
|
||||||
|
|
||||||
|
float resultFrame = accumulateTime(startFrame, endFrame, timeScale, startFrame, dt, loopFlag, id, triggers);
|
||||||
|
QVERIFY(resultFrame == startFrame + 1.0f);
|
||||||
|
QVERIFY(triggers.empty());
|
||||||
|
triggers.clear();
|
||||||
|
|
||||||
|
resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers);
|
||||||
|
QVERIFY(resultFrame == startFrame + 2.0f);
|
||||||
|
QVERIFY(triggers.empty());
|
||||||
|
triggers.clear();
|
||||||
|
|
||||||
|
resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers);
|
||||||
|
QVERIFY(resultFrame == startFrame + 3.0f);
|
||||||
|
QVERIFY(triggers.empty());
|
||||||
|
triggers.clear();
|
||||||
|
|
||||||
|
// test onDone trigger and frame clamping.
|
||||||
|
resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 1.0f, dt, loopFlag, id, triggers);
|
||||||
|
QVERIFY(resultFrame == endFrame);
|
||||||
|
QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnDone");
|
||||||
|
triggers.clear();
|
||||||
|
|
||||||
|
resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 0.5f, dt, loopFlag, id, triggers);
|
||||||
|
QVERIFY(resultFrame == endFrame);
|
||||||
|
QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnDone");
|
||||||
|
triggers.clear();
|
||||||
|
|
||||||
|
// test onLoop trigger and looping frame logic
|
||||||
|
loopFlag = true;
|
||||||
|
|
||||||
|
// should NOT trigger loop even though we stop at last frame, because there is an extra frame between end and start frames.
|
||||||
|
resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 1.0f, dt, loopFlag, id, triggers);
|
||||||
|
QVERIFY(resultFrame == endFrame);
|
||||||
|
QVERIFY(triggers.empty());
|
||||||
|
triggers.clear();
|
||||||
|
|
||||||
|
// now we should hit loop trigger
|
||||||
|
resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers);
|
||||||
|
QVERIFY(resultFrame == startFrame);
|
||||||
|
QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop");
|
||||||
|
triggers.clear();
|
||||||
|
|
||||||
|
// should NOT trigger loop, even though we move past the end frame, because of extra frame between end and start.
|
||||||
|
resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 0.5f, dt, loopFlag, id, triggers);
|
||||||
|
QVERIFY(resultFrame == endFrame + 0.5f);
|
||||||
|
QVERIFY(triggers.empty());
|
||||||
|
triggers.clear();
|
||||||
|
|
||||||
|
// now we should hit loop trigger
|
||||||
|
resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers);
|
||||||
|
QVERIFY(resultFrame == startFrame + 0.5f);
|
||||||
|
QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop");
|
||||||
|
triggers.clear();
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
class AnimTests : public QObject {
|
class AnimTests : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
void testAccumulateTimeWithParameters(float startFrame, float endFrame, float timeScale) const;
|
||||||
private slots:
|
private slots:
|
||||||
void initTestCase();
|
void initTestCase();
|
||||||
void cleanupTestCase();
|
void cleanupTestCase();
|
||||||
|
@ -23,6 +25,7 @@ private slots:
|
||||||
void testClipEvaulateWithVars();
|
void testClipEvaulateWithVars();
|
||||||
void testLoader();
|
void testLoader();
|
||||||
void testVariant();
|
void testVariant();
|
||||||
|
void testAccumulateTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AnimTests_h
|
#endif // hifi_AnimTests_h
|
||||||
|
|
Loading…
Reference in a new issue