//// AnimRandomSwitch.cpp//// Created by Angus Antley on 4/8/2019.// Copyright (c) 2019 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 "AnimRandomSwitch.h"#include "AnimUtil.h"#include "AnimationLogging.h" AnimRandomSwitch::AnimRandomSwitch(const QString& id) : AnimNode(AnimNode::Type::RandomSwitchStateMachine, id) { } AnimRandomSwitch::~AnimRandomSwitch() { } void AnimRandomSwitch::setActiveInternal(bool active) { _active = active; _triggerNewRandomState = active;} const AnimPoseVec& AnimRandomSwitch::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { float parentDebugAlpha = context.getDebugAlpha(_id); AnimRandomSwitch::RandomSwitchState::Pointer desiredState = _currentState; if (_triggerNewRandomState || animVars.lookup(_triggerRandomSwitchVar, false)) { // filter states different to the last random state and with priorities. bool currentStateHasPriority = false; std::vector<RandomSwitchState::Pointer> randomStatesToConsider; randomStatesToConsider.reserve(_randomStates.size()); float totalPriorities = 0.0f; for (size_t i = 0; i < _randomStates.size(); i++) { auto randState = _randomStates[i]; if (randState->getPriority() > 0.0f) { bool isRepeatingClip = _children[randState->getChildIndex()]->getID() == _lastPlayedState; if (!isRepeatingClip) { randomStatesToConsider.push_back(randState); totalPriorities += randState->getPriority(); } // this indicates if the curent state is one that can be selected randomly, or is one that was transitioned to by the random duration timer. currentStateHasPriority = currentStateHasPriority || (_currentState == randState); } } // get a random number and decide which motion to choose. float dice = randFloatInRange(0.0f, 1.0f); float lowerBound = 0.0f; for (size_t i = 0; i < randomStatesToConsider.size(); i++) { auto randState = randomStatesToConsider[i]; float upperBound = lowerBound + (randState->getPriority() / totalPriorities); if ((dice > lowerBound) && (dice < upperBound)) { desiredState = randState; break; } lowerBound = upperBound; } if (_triggerNewRandomState) { switchRandomState(animVars, context, desiredState, false); _triggerNewRandomState = false; } else { // firing a random switch, be sure that we aren't completing a previously triggered transition if (currentStateHasPriority) { if (desiredState->getID() != _currentState->getID()) { switchRandomState(animVars, context, desiredState, true); } else { _duringInterp = false; } } } _triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax); _randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax); } else { // here we are checking to see if we want a temporary movement // evaluate currentState transitions auto transitionState = evaluateTransitions(animVars); if (transitionState != _currentState) { switchRandomState(animVars, context, transitionState, true); _triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax); _randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax); } } _triggerTime -= dt; if ((_triggerTime < 0.0f) && (_triggerTimeMin > 0.0f) && (_triggerTimeMax > 0.0f)) { _triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax); triggersOut.setTrigger(_transitionVar); } _randomSwitchTime -= dt; if ((_randomSwitchTime < 0.0f) && (_randomSwitchTimeMin > 0.0f) && (_randomSwitchTimeMax > 0.0f)) { _randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax); // restart the trigger timer if it is also enabled _triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax); triggersOut.setTrigger(_triggerRandomSwitchVar); } assert(_currentState); auto currentStateNode = _children[_currentState->getChildIndex()]; auto previousStateNode = _children[_previousState->getChildIndex()]; assert(currentStateNode); if (_duringInterp) { _alpha += _alphaVel * dt; if (_alpha < 1.0f) { AnimPoseVec* nextPoses = nullptr; AnimPoseVec* prevPoses = nullptr; AnimPoseVec localNextPoses; AnimPoseVec localPrevPoses; if (_interpType == InterpType::SnapshotBoth) { // interp between both snapshots prevPoses = &_prevPoses; nextPoses = &_nextPoses; } else if (_interpType == InterpType::SnapshotPrev) { // interp between the prev snapshot and evaluated next target. // this is useful for interping into a blend localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut); prevPoses = &_prevPoses; nextPoses = &localNextPoses; } else if (_interpType == InterpType::EvaluateBoth) { localPrevPoses = previousStateNode->evaluate(animVars, context, dt, triggersOut); localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut); prevPoses = &localPrevPoses; nextPoses = &localNextPoses; } else { assert(false); } if (_poses.size() > 0 && nextPoses && prevPoses && nextPoses->size() > 0 && prevPoses->size() > 0) { ::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), easingFunc(_alpha, _easingType), &_poses[0]); } context.setDebugAlpha(_currentState->getID(), easingFunc(_alpha, _easingType) * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); } else { _duringInterp = false; _prevPoses.clear(); _nextPoses.clear(); } } if (!_duringInterp){ context.setDebugAlpha(_currentState->getID(), parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); _poses = currentStateNode->evaluate(animVars, context, dt, triggersOut); } processOutputJoints(triggersOut); context.addStateMachineInfo(_id, _currentState->getID(), _previousState->getID(), _duringInterp, _alpha); if (_duringInterp) { // hack: add previoius state to debug alpha map, with parens around it's name. context.setDebugAlpha(QString("(%1)").arg(_previousState->getID()), 1.0f - _alpha, AnimNodeType::Clip); } return _poses;} void AnimRandomSwitch::setCurrentState(RandomSwitchState::Pointer randomState) { _previousState = _currentState ? _currentState : randomState; _currentState = randomState;} void AnimRandomSwitch::addState(RandomSwitchState::Pointer randomState) { _randomStates.push_back(randomState);} void AnimRandomSwitch::switchRandomState(const AnimVariantMap& animVars, const AnimContext& context, RandomSwitchState::Pointer desiredState, bool shouldInterp) { auto prevStateNode = _children[_currentState->getChildIndex()]; auto nextStateNode = _children[desiredState->getChildIndex()]; // activate/deactivate states prevStateNode->setActive(false); nextStateNode->setActive(true); _lastPlayedState = nextStateNode->getID(); if (shouldInterp) { bool interpActive = _duringInterp; _duringInterp = true; const float FRAMES_PER_SECOND = 30.0f; auto prevStateNode = _children[_currentState->getChildIndex()]; _alpha = 0.0f; float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration)); _alphaVel = FRAMES_PER_SECOND / duration; _interpType = (InterpType)animVars.lookup(desiredState->_interpTypeVar, (int)desiredState->_interpType); _easingType = desiredState->_easingType; // because dt is 0, we should not encounter any triggers const float dt = 0.0f; AnimVariantMap triggers; if (_interpType == InterpType::SnapshotBoth) { // snapshot previous pose. _prevPoses = _poses; // snapshot next pose at the target frame. if (!desiredState->getResume()) { nextStateNode->setCurrentFrame(desiredState->_interpTarget); } _nextPoses = nextStateNode->evaluate(animVars, context, dt, triggers); } else if (_interpType == InterpType::SnapshotPrev) { // snapshot previous pose _prevPoses = _poses; // no need to evaluate _nextPoses we will do it dynamically during the interp, // however we need to set the current frame. if (!desiredState->getResume()) { nextStateNode->setCurrentFrame(desiredState->_interpTarget - duration); } } else if (_interpType == InterpType::EvaluateBoth) { // need to set current frame in destination branch. nextStateNode->setCurrentFrame(desiredState->_interpTarget - duration); if (interpActive) { // snapshot previous pose _prevPoses = _poses; _interpType = InterpType::SnapshotPrev; } } else { assert(false); } } else { if (!desiredState->getResume()) { nextStateNode->setCurrentFrame(desiredState->_interpTarget); } } #ifdef WANT_DEBUG qCDebug(animation) << "AnimRandomSwitch::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget << "interpType = " << (int)_interpType;#endif setCurrentState(desiredState);} AnimRandomSwitch::RandomSwitchState::Pointer AnimRandomSwitch::evaluateTransitions(const AnimVariantMap& animVars) const { assert(_currentState); for (auto& transition : _currentState->_transitions) { if (animVars.lookup(transition._var, false)) { return transition._randomSwitchState; } } return _currentState;} const AnimPoseVec& AnimRandomSwitch::getPosesInternal() const { return _poses;}
//
// AnimRandomSwitch.cpp
//
// Created by Angus Antley on 4/8/2019.
// Copyright (c) 2019 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 "AnimRandomSwitch.h"
#include "AnimUtil.h"
#include "AnimationLogging.h"
AnimRandomSwitch::AnimRandomSwitch(const QString& id) :
AnimNode(AnimNode::Type::RandomSwitchStateMachine, id) {
}
AnimRandomSwitch::~AnimRandomSwitch() {
}
void AnimRandomSwitch::setActiveInternal(bool active) {
_active = active;
_triggerNewRandomState = active;
}
const AnimPoseVec& AnimRandomSwitch::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
float parentDebugAlpha = context.getDebugAlpha(_id);
AnimRandomSwitch::RandomSwitchState::Pointer desiredState = _currentState;
if (_triggerNewRandomState || animVars.lookup(_triggerRandomSwitchVar, false)) {
// filter states different to the last random state and with priorities.
bool currentStateHasPriority = false;
std::vector<RandomSwitchState::Pointer> randomStatesToConsider;
randomStatesToConsider.reserve(_randomStates.size());
float totalPriorities = 0.0f;
for (size_t i = 0; i < _randomStates.size(); i++) {
auto randState = _randomStates[i];
if (randState->getPriority() > 0.0f) {
bool isRepeatingClip = _children[randState->getChildIndex()]->getID() == _lastPlayedState;
if (!isRepeatingClip) {
randomStatesToConsider.push_back(randState);
totalPriorities += randState->getPriority();
}
// this indicates if the curent state is one that can be selected randomly, or is one that was transitioned to by the random duration timer.
currentStateHasPriority = currentStateHasPriority || (_currentState == randState);
}
}
// get a random number and decide which motion to choose.
float dice = randFloatInRange(0.0f, 1.0f);
float lowerBound = 0.0f;
for (size_t i = 0; i < randomStatesToConsider.size(); i++) {
auto randState = randomStatesToConsider[i];
float upperBound = lowerBound + (randState->getPriority() / totalPriorities);
if ((dice > lowerBound) && (dice < upperBound)) {
desiredState = randState;
break;
}
lowerBound = upperBound;
}
if (_triggerNewRandomState) {
switchRandomState(animVars, context, desiredState, false);
_triggerNewRandomState = false;
} else {
// firing a random switch, be sure that we aren't completing a previously triggered transition
if (currentStateHasPriority) {
if (desiredState->getID() != _currentState->getID()) {
switchRandomState(animVars, context, desiredState, true);
} else {
_duringInterp = false;
}
}
}
_triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax);
_randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax);
} else {
// here we are checking to see if we want a temporary movement
// evaluate currentState transitions
auto transitionState = evaluateTransitions(animVars);
if (transitionState != _currentState) {
switchRandomState(animVars, context, transitionState, true);
_triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax);
_randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax);
}
}
_triggerTime -= dt;
if ((_triggerTime < 0.0f) && (_triggerTimeMin > 0.0f) && (_triggerTimeMax > 0.0f)) {
_triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax);
triggersOut.setTrigger(_transitionVar);
}
_randomSwitchTime -= dt;
if ((_randomSwitchTime < 0.0f) && (_randomSwitchTimeMin > 0.0f) && (_randomSwitchTimeMax > 0.0f)) {
_randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax);
// restart the trigger timer if it is also enabled
_triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax);
triggersOut.setTrigger(_triggerRandomSwitchVar);
}
assert(_currentState);
auto currentStateNode = _children[_currentState->getChildIndex()];
auto previousStateNode = _children[_previousState->getChildIndex()];
assert(currentStateNode);
if (_duringInterp) {
_alpha += _alphaVel * dt;
if (_alpha < 1.0f) {
AnimPoseVec* nextPoses = nullptr;
AnimPoseVec* prevPoses = nullptr;
AnimPoseVec localNextPoses;
AnimPoseVec localPrevPoses;
if (_interpType == InterpType::SnapshotBoth) {
// interp between both snapshots
prevPoses = &_prevPoses;
nextPoses = &_nextPoses;
} else if (_interpType == InterpType::SnapshotPrev) {
// interp between the prev snapshot and evaluated next target.
// this is useful for interping into a blend
localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
prevPoses = &_prevPoses;
nextPoses = &localNextPoses;
} else if (_interpType == InterpType::EvaluateBoth) {
localPrevPoses = previousStateNode->evaluate(animVars, context, dt, triggersOut);
localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
prevPoses = &localPrevPoses;
nextPoses = &localNextPoses;
} else {
assert(false);
}
if (_poses.size() > 0 && nextPoses && prevPoses && nextPoses->size() > 0 && prevPoses->size() > 0) {
::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), easingFunc(_alpha, _easingType), &_poses[0]);
}
context.setDebugAlpha(_currentState->getID(), easingFunc(_alpha, _easingType) * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType());
} else {
_duringInterp = false;
_prevPoses.clear();
_nextPoses.clear();
}
}
if (!_duringInterp){
context.setDebugAlpha(_currentState->getID(), parentDebugAlpha, _children[_currentState->getChildIndex()]->getType());
_poses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
}
processOutputJoints(triggersOut);
context.addStateMachineInfo(_id, _currentState->getID(), _previousState->getID(), _duringInterp, _alpha);
if (_duringInterp) {
// hack: add previoius state to debug alpha map, with parens around it's name.
context.setDebugAlpha(QString("(%1)").arg(_previousState->getID()), 1.0f - _alpha, AnimNodeType::Clip);
}
return _poses;
}
void AnimRandomSwitch::setCurrentState(RandomSwitchState::Pointer randomState) {
_previousState = _currentState ? _currentState : randomState;
_currentState = randomState;
}
void AnimRandomSwitch::addState(RandomSwitchState::Pointer randomState) {
_randomStates.push_back(randomState);
}
void AnimRandomSwitch::switchRandomState(const AnimVariantMap& animVars, const AnimContext& context, RandomSwitchState::Pointer desiredState, bool shouldInterp) {
auto prevStateNode = _children[_currentState->getChildIndex()];
auto nextStateNode = _children[desiredState->getChildIndex()];
// activate/deactivate states
prevStateNode->setActive(false);
nextStateNode->setActive(true);
_lastPlayedState = nextStateNode->getID();
if (shouldInterp) {
bool interpActive = _duringInterp;
_duringInterp = true;
const float FRAMES_PER_SECOND = 30.0f;
auto prevStateNode = _children[_currentState->getChildIndex()];
_alpha = 0.0f;
float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration));
_alphaVel = FRAMES_PER_SECOND / duration;
_interpType = (InterpType)animVars.lookup(desiredState->_interpTypeVar, (int)desiredState->_interpType);
_easingType = desiredState->_easingType;
// because dt is 0, we should not encounter any triggers
const float dt = 0.0f;
AnimVariantMap triggers;
if (_interpType == InterpType::SnapshotBoth) {
// snapshot previous pose.
_prevPoses = _poses;
// snapshot next pose at the target frame.
if (!desiredState->getResume()) {
nextStateNode->setCurrentFrame(desiredState->_interpTarget);
}
_nextPoses = nextStateNode->evaluate(animVars, context, dt, triggers);
} else if (_interpType == InterpType::SnapshotPrev) {
// snapshot previous pose
_prevPoses = _poses;
// no need to evaluate _nextPoses we will do it dynamically during the interp,
// however we need to set the current frame.
if (!desiredState->getResume()) {
nextStateNode->setCurrentFrame(desiredState->_interpTarget - duration);
}
} else if (_interpType == InterpType::EvaluateBoth) {
// need to set current frame in destination branch.
nextStateNode->setCurrentFrame(desiredState->_interpTarget - duration);
if (interpActive) {
// snapshot previous pose
_prevPoses = _poses;
_interpType = InterpType::SnapshotPrev;
}
} else {
assert(false);
}
} else {
if (!desiredState->getResume()) {
nextStateNode->setCurrentFrame(desiredState->_interpTarget);
}
}
#ifdef WANT_DEBUG
qCDebug(animation) << "AnimRandomSwitch::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget << "interpType = " << (int)_interpType;
#endif
setCurrentState(desiredState);
}
AnimRandomSwitch::RandomSwitchState::Pointer AnimRandomSwitch::evaluateTransitions(const AnimVariantMap& animVars) const {
assert(_currentState);
for (auto& transition : _currentState->_transitions) {
if (animVars.lookup(transition._var, false)) {
return transition._randomSwitchState;
}
}
return _currentState;
}
const AnimPoseVec& AnimRandomSwitch::getPosesInternal() const {
return _poses;
}