Merge pull request #15445 from amantley/AnimRandomSwitchSquash

AnimRandomSwitch class for random idle implementation
This commit is contained in:
Anthony Thibault 2019-04-26 10:06:37 -07:00 committed by GitHub
commit 93b03a588f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1362 additions and 392 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -11,11 +11,12 @@
#include "AnimContext.h"
AnimContext::AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains,
const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix) :
const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix, int evaluationCount) :
_enableDebugDrawIKTargets(enableDebugDrawIKTargets),
_enableDebugDrawIKConstraints(enableDebugDrawIKConstraints),
_enableDebugDrawIKChains(enableDebugDrawIKChains),
_geometryToRigMatrix(geometryToRigMatrix),
_rigToWorldMatrix(rigToWorldMatrix)
_rigToWorldMatrix(rigToWorldMatrix),
_evaluationCount(evaluationCount)
{
}

View file

@ -24,6 +24,7 @@ enum class AnimNodeType {
BlendLinearMove,
Overlay,
StateMachine,
RandomSwitchStateMachine,
Manipulator,
InverseKinematics,
DefaultPose,
@ -37,13 +38,14 @@ class AnimContext {
public:
AnimContext() {}
AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains,
const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix);
const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix, int evaluationCount);
bool getEnableDebugDrawIKTargets() const { return _enableDebugDrawIKTargets; }
bool getEnableDebugDrawIKConstraints() const { return _enableDebugDrawIKConstraints; }
bool getEnableDebugDrawIKChains() const { return _enableDebugDrawIKChains; }
const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; }
const glm::mat4& getRigToWorldMatrix() const { return _rigToWorldMatrix; }
int getEvaluationCount() const { return _evaluationCount; }
float getDebugAlpha(const QString& key) const {
auto it = _debugAlphaMap.find(key);
@ -85,6 +87,7 @@ protected:
bool _enableDebugDrawIKChains { false };
glm::mat4 _geometryToRigMatrix;
glm::mat4 _rigToWorldMatrix;
int _evaluationCount{ 0 };
// used for debugging internal state of animation system.
mutable DebugAlphaMap _debugAlphaMap;

View file

@ -43,6 +43,7 @@ public:
friend class AnimDebugDraw;
friend void buildChildMap(std::map<QString, Pointer>& map, Pointer node);
friend class AnimStateMachine;
friend class AnimRandomSwitch;
AnimNode(Type type, const QString& id) : _type(type), _id(id) {}
virtual ~AnimNode() {}

View file

@ -22,6 +22,7 @@
#include "AnimationLogging.h"
#include "AnimOverlay.h"
#include "AnimStateMachine.h"
#include "AnimRandomSwitch.h"
#include "AnimManipulator.h"
#include "AnimInverseKinematics.h"
#include "AnimDefaultPose.h"
@ -38,6 +39,7 @@ static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const Q
static AnimNode::Pointer loadBlendLinearMoveNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadRandomSwitchStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
@ -51,6 +53,7 @@ static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f;
// returns node on success, nullptr on failure.
static bool processDoNothing(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static const char* animNodeTypeToString(AnimNode::Type type) {
switch (type) {
@ -59,6 +62,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) {
case AnimNode::Type::BlendLinearMove: return "blendLinearMove";
case AnimNode::Type::Overlay: return "overlay";
case AnimNode::Type::StateMachine: return "stateMachine";
case AnimNode::Type::RandomSwitchStateMachine: return "randomSwitchStateMachine";
case AnimNode::Type::Manipulator: return "manipulator";
case AnimNode::Type::InverseKinematics: return "inverseKinematics";
case AnimNode::Type::DefaultPose: return "defaultPose";
@ -92,6 +96,16 @@ static AnimStateMachine::InterpType stringToInterpType(const QString& str) {
}
}
static AnimRandomSwitch::InterpType stringToRandomInterpType(const QString& str) {
if (str == "snapshotBoth") {
return AnimRandomSwitch::InterpType::SnapshotBoth;
} else if (str == "snapshotPrev") {
return AnimRandomSwitch::InterpType::SnapshotPrev;
} else {
return AnimRandomSwitch::InterpType::NumTypes;
}
}
static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) {
switch (type) {
case AnimManipulator::JointVar::Type::Absolute: return "absolute";
@ -122,6 +136,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) {
case AnimNode::Type::BlendLinearMove: return loadBlendLinearMoveNode;
case AnimNode::Type::Overlay: return loadOverlayNode;
case AnimNode::Type::StateMachine: return loadStateMachineNode;
case AnimNode::Type::RandomSwitchStateMachine: return loadRandomSwitchStateMachineNode;
case AnimNode::Type::Manipulator: return loadManipulatorNode;
case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode;
case AnimNode::Type::DefaultPose: return loadDefaultPoseNode;
@ -140,6 +155,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
case AnimNode::Type::BlendLinearMove: return processDoNothing;
case AnimNode::Type::Overlay: return processDoNothing;
case AnimNode::Type::StateMachine: return processStateMachineNode;
case AnimNode::Type::RandomSwitchStateMachine: return processRandomSwitchStateMachineNode;
case AnimNode::Type::Manipulator: return processDoNothing;
case AnimNode::Type::InverseKinematics: return processDoNothing;
case AnimNode::Type::DefaultPose: return processDoNothing;
@ -463,6 +479,11 @@ static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const
return node;
}
static AnimNode::Pointer loadRandomSwitchStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
auto node = std::make_shared<AnimRandomSwitch>(id);
return node;
}
static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr);
@ -780,6 +801,141 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
return true;
}
bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) {
auto smNode = std::static_pointer_cast<AnimRandomSwitch>(node);
assert(smNode);
READ_STRING(currentState, jsonObj, nodeId, jsonUrl, false);
READ_OPTIONAL_FLOAT(randomSwitchTimeMin, jsonObj, -1.0f);
READ_OPTIONAL_FLOAT(randomSwitchTimeMax, jsonObj, -1.0f);
READ_OPTIONAL_STRING(triggerRandomSwitch, jsonObj);
READ_OPTIONAL_FLOAT(triggerTimeMin, jsonObj, -1.0f);
READ_OPTIONAL_FLOAT(triggerTimeMax, jsonObj, -1.0f);
READ_OPTIONAL_STRING(transitionVar, jsonObj);
auto statesValue = jsonObj.value("states");
if (!statesValue.isArray()) {
qCCritical(animation) << "AnimNodeLoader, bad array \"states\" in random switch state Machine node, id =" << nodeId;
return false;
}
// build a map for all children by name.
std::map<QString, int> childMap;
buildChildMap(childMap, node);
// first pass parse all the states and build up the state and transition map.
using StringPair = std::pair<QString, QString>;
using TransitionMap = std::multimap<AnimRandomSwitch::RandomSwitchState::Pointer, StringPair>;
TransitionMap transitionMap;
using RandomStateMap = std::map<QString, AnimRandomSwitch::RandomSwitchState::Pointer>;
RandomStateMap randomStateMap;
auto randomStatesArray = statesValue.toArray();
for (const auto& randomStateValue : randomStatesArray) {
if (!randomStateValue.isObject()) {
qCCritical(animation) << "AnimNodeLoader, bad state object in \"random states\", id =" << nodeId;
return false;
}
auto stateObj = randomStateValue.toObject();
READ_STRING(id, stateObj, nodeId, jsonUrl, false);
READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false);
READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false);
READ_OPTIONAL_STRING(interpType, stateObj);
READ_FLOAT(priority, stateObj, nodeId, jsonUrl, false);
READ_BOOL(resume, stateObj, nodeId, jsonUrl, false);
READ_OPTIONAL_STRING(interpTargetVar, stateObj);
READ_OPTIONAL_STRING(interpDurationVar, stateObj);
READ_OPTIONAL_STRING(interpTypeVar, stateObj);
auto iter = childMap.find(id);
if (iter == childMap.end()) {
qCCritical(animation) << "AnimNodeLoader, could not find random stateMachine child (state) with nodeId =" << nodeId << "random stateId =" << id;
return false;
}
AnimRandomSwitch::InterpType interpTypeEnum = AnimRandomSwitch::InterpType::SnapshotPrev; // default value
if (!interpType.isEmpty()) {
interpTypeEnum = stringToRandomInterpType(interpType);
if (interpTypeEnum == AnimRandomSwitch::InterpType::NumTypes) {
qCCritical(animation) << "AnimNodeLoader, bad interpType on random state Machine state, nodeId = " << nodeId << "random stateId =" << id;
return false;
}
}
auto randomStatePtr = std::make_shared<AnimRandomSwitch::RandomSwitchState>(id, iter->second, interpTarget, interpDuration, interpTypeEnum, priority, resume);
if (priority > 0.0f) {
smNode->addToPrioritySum(priority);
}
assert(randomStatePtr);
if (!interpTargetVar.isEmpty()) {
randomStatePtr->setInterpTargetVar(interpTargetVar);
}
if (!interpDurationVar.isEmpty()) {
randomStatePtr->setInterpDurationVar(interpDurationVar);
}
if (!interpTypeVar.isEmpty()) {
randomStatePtr->setInterpTypeVar(interpTypeVar);
}
smNode->addState(randomStatePtr);
randomStateMap.insert(RandomStateMap::value_type(randomStatePtr->getID(), randomStatePtr));
auto transitionsValue = stateObj.value("transitions");
if (!transitionsValue.isArray()) {
qCritical(animation) << "AnimNodeLoader, bad array \"transitions\" in random state Machine node, stateId =" << id << "nodeId =" << nodeId;
return false;
}
auto transitionsArray = transitionsValue.toArray();
for (const auto& transitionValue : transitionsArray) {
if (!transitionValue.isObject()) {
qCritical(animation) << "AnimNodeLoader, bad transition object in \"transitions\", random stateId =" << id << "nodeId =" << nodeId;
return false;
}
auto transitionObj = transitionValue.toObject();
READ_STRING(var, transitionObj, nodeId, jsonUrl, false);
READ_STRING(randomSwitchState, transitionObj, nodeId, jsonUrl, false);
transitionMap.insert(TransitionMap::value_type(randomStatePtr, StringPair(var, randomSwitchState)));
}
}
// second pass: now iterate thru all transitions and add them to the appropriate states.
for (auto& transition : transitionMap) {
AnimRandomSwitch::RandomSwitchState::Pointer srcState = transition.first;
auto iter = randomStateMap.find(transition.second.second);
if (iter != randomStateMap.end()) {
srcState->addTransition(AnimRandomSwitch::RandomSwitchState::Transition(transition.second.first, iter->second));
} else {
qCCritical(animation) << "AnimNodeLoader, bad random state machine transition from srcState =" << srcState->_id << "dstState =" << transition.second.second << "nodeId =" << nodeId;
return false;
}
}
auto iter = randomStateMap.find(currentState);
if (iter == randomStateMap.end()) {
qCCritical(animation) << "AnimNodeLoader, bad currentState =" << currentState << "could not find child node" << "id =" << nodeId;
}
smNode->setCurrentState(iter->second);
smNode->setRandomSwitchTimeMin(randomSwitchTimeMin);
smNode->setRandomSwitchTimeMax(randomSwitchTimeMax);
smNode->setTriggerRandomSwitchVar(triggerRandomSwitch);
smNode->setTriggerTimeMin(triggerTimeMin);
smNode->setTriggerTimeMax(triggerTimeMax);
smNode->setTransitionVar(transitionVar);
return true;
}
AnimNodeLoader::AnimNodeLoader(const QUrl& url) :
_url(url)
{

View file

@ -0,0 +1,212 @@
//
// 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() {
}
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 (abs(_randomSwitchEvaluationCount - context.getEvaluationCount()) > 1 || animVars.lookup(_triggerRandomSwitchVar, false)) {
// get a random number and decide which motion to choose.
bool currentStateHasPriority = false;
float dice = randFloatInRange(0.0f, 1.0f);
float lowerBound = 0.0f;
for (const RandomSwitchState::Pointer& randState : _randomStates) {
if (randState->getPriority() > 0.0f) {
float upperBound = lowerBound + (randState->getPriority() / _totalPriorities);
if ((dice > lowerBound) && (dice < upperBound)) {
desiredState = randState;
}
lowerBound = upperBound;
// 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);
}
}
if (abs(_randomSwitchEvaluationCount - context.getEvaluationCount()) > 1) {
_duringInterp = false;
switchRandomState(animVars, context, desiredState, _duringInterp);
} else {
// firing a random switch, be sure that we aren't completing a previously triggered transition
if (currentStateHasPriority) {
if (desiredState->getID() != _currentState->getID()) {
_duringInterp = true;
switchRandomState(animVars, context, desiredState, _duringInterp);
} 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) {
_duringInterp = true;
switchRandomState(animVars, context, transitionState, _duringInterp);
_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()];
assert(currentStateNode);
if (_duringInterp) {
_alpha += _alphaVel * dt;
if (_alpha < 1.0f) {
AnimPoseVec* nextPoses = nullptr;
AnimPoseVec* prevPoses = nullptr;
AnimPoseVec localNextPoses;
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 {
assert(false);
}
if (_poses.size() > 0 && nextPoses && prevPoses && nextPoses->size() > 0 && prevPoses->size() > 0) {
::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), _alpha, &_poses[0]);
}
context.setDebugAlpha(_currentState->getID(), _alpha * 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);
}
_randomSwitchEvaluationCount = context.getEvaluationCount();
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 nextStateNode = _children[desiredState->getChildIndex()];
if (shouldInterp) {
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);
// 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 previoius 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 {
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;
}

View file

@ -0,0 +1,184 @@
//
// AnimRandomSwitch.h
//
// Created by Angus Antley on 4/8/19.
// 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
//
#ifndef hifi_AnimRandomSwitch_h
#define hifi_AnimRandomSwitch_h
#include <string>
#include <vector>
#include "AnimNode.h"
// Random Switch State Machine for random transitioning between children AnimNodes
//
// This is mechanisim for choosing and playing a random animation and smoothly interpolating/fading
// between them. A RandomSwitch has a set of States, which typically reference
// child AnimNodes. Each Random Switch State has a list of Transitions, which are evaluated
// to determine when we should switch to a new State. Parameters for the smooth
// interpolation/fading are read from the Random Switch State that you are transitioning to.
//
// The currentState can be set directly via the setCurrentStateVar() and will override
// any State transitions.
//
// Each Random Switch State has two parameters that can be changed via AnimVars,
// * interpTarget - (frames) The destination frame of the interpolation. i.e. the first frame of the animation that will
// visible after interpolation is complete.
// * interpDuration - (frames) The total length of time it will take to interp between the current pose and the
// interpTarget frame.
// * interpType - How the interpolation is performed.
// * priority - this number represents how likely this Random Switch State will be chosen.
// the priority for each Random Switch State will be normalized, so their relative size is what is important
// * resume - if resume is false then if this state is chosen twice in a row it will remember what frame it was playing on.
// * SnapshotBoth: Stores two snapshots, the previous animation before interpolation begins and the target state at the
// interTarget frame. Then during the interpolation period the two snapshots are interpolated to produce smooth motion between them.
// * SnapshotPrev: Stores a snapshot of the previous animation before interpolation begins. However the target state is
// evaluated dynamically. During the interpolation period the previous snapshot is interpolated with the target pose
// to produce smooth motion between them. This mode is useful for interping into a blended animation where the actual
// blend factor is not known at the start of the interp or is might change dramatically during the interp.
//
class AnimRandomSwitch : public AnimNode {
public:
friend class AnimNodeLoader;
friend bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
enum class InterpType {
SnapshotBoth = 0,
SnapshotPrev,
NumTypes
};
protected:
class RandomSwitchState {
public:
friend AnimRandomSwitch;
friend bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
using Pointer = std::shared_ptr<RandomSwitchState>;
using ConstPointer = std::shared_ptr<const RandomSwitchState>;
class Transition {
public:
friend AnimRandomSwitch;
Transition(const QString& var, RandomSwitchState::Pointer randomState) : _var(var), _randomSwitchState(randomState) {}
protected:
QString _var;
RandomSwitchState::Pointer _randomSwitchState;
};
RandomSwitchState(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType, float priority, bool resume) :
_id(id),
_childIndex(childIndex),
_interpTarget(interpTarget),
_interpDuration(interpDuration),
_interpType(interpType),
_priority(priority),
_resume(resume){
}
void setInterpTargetVar(const QString& interpTargetVar) { _interpTargetVar = interpTargetVar; }
void setInterpDurationVar(const QString& interpDurationVar) { _interpDurationVar = interpDurationVar; }
void setInterpTypeVar(const QString& interpTypeVar) { _interpTypeVar = interpTypeVar; }
int getChildIndex() const { return _childIndex; }
float getPriority() const { return _priority; }
bool getResume() const { return _resume; }
const QString& getID() const { return _id; }
protected:
void setInterpTarget(float interpTarget) { _interpTarget = interpTarget; }
void setInterpDuration(float interpDuration) { _interpDuration = interpDuration; }
void setPriority(float priority) { _priority = priority; }
void setResumeFlag(bool resume) { _resume = resume; }
void addTransition(const Transition& transition) { _transitions.push_back(transition); }
QString _id;
int _childIndex;
float _interpTarget; // frames
float _interpDuration; // frames
InterpType _interpType;
float _priority {0.0f};
bool _resume {false};
QString _interpTargetVar;
QString _interpDurationVar;
QString _interpTypeVar;
std::vector<Transition> _transitions;
private:
// no copies
RandomSwitchState(const RandomSwitchState&) = delete;
RandomSwitchState& operator=(const RandomSwitchState&) = delete;
};
public:
explicit AnimRandomSwitch(const QString& id);
virtual ~AnimRandomSwitch() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
void setCurrentStateVar(QString& currentStateVar) { _currentStateVar = currentStateVar; }
protected:
void setCurrentState(RandomSwitchState::Pointer randomState);
void setTriggerRandomSwitchVar(const QString& triggerRandomSwitchVar) { _triggerRandomSwitchVar = triggerRandomSwitchVar; }
void setRandomSwitchTimeMin(float randomSwitchTimeMin) { _randomSwitchTimeMin = randomSwitchTimeMin; }
void setRandomSwitchTimeMax(float randomSwitchTimeMax) { _randomSwitchTimeMax = randomSwitchTimeMax; }
void setTransitionVar(const QString& transitionVar) { _transitionVar = transitionVar; }
void setTriggerTimeMin(float triggerTimeMin) { _triggerTimeMin = triggerTimeMin; }
void setTriggerTimeMax(float triggerTimeMax) { _triggerTimeMax = triggerTimeMax; }
void addToPrioritySum(float priority) { _totalPriorities += priority; }
void addState(RandomSwitchState::Pointer randomState);
void switchRandomState(const AnimVariantMap& animVars, const AnimContext& context, RandomSwitchState::Pointer desiredState, bool shouldInterp);
RandomSwitchState::Pointer evaluateTransitions(const AnimVariantMap& animVars) const;
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override;
AnimPoseVec _poses;
int _randomSwitchEvaluationCount { 0 };
// interpolation state
bool _duringInterp = false;
InterpType _interpType{ InterpType::SnapshotPrev };
float _alphaVel = 0.0f;
float _alpha = 0.0f;
AnimPoseVec _prevPoses;
AnimPoseVec _nextPoses;
float _totalPriorities { 0.0f };
RandomSwitchState::Pointer _currentState;
RandomSwitchState::Pointer _previousState;
std::vector<RandomSwitchState::Pointer> _randomStates;
QString _currentStateVar;
QString _triggerRandomSwitchVar;
QString _transitionVar;
float _triggerTimeMin { 10.0f };
float _triggerTimeMax { 20.0f };
float _triggerTime { 0.0f };
float _randomSwitchTimeMin { 10.0f };
float _randomSwitchTimeMax { 20.0f };
float _randomSwitchTime { 0.0f };
private:
// no copies
AnimRandomSwitch(const AnimRandomSwitch&) = delete;
AnimRandomSwitch& operator=(const AnimRandomSwitch&) = delete;
};
#endif // hifi_AnimRandomSwitch_h

View file

@ -1480,13 +1480,15 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
if (_animNode && _enabledAnimations) {
DETAILED_PERFORMANCE_TIMER("handleTriggers");
++_evaluationCount;
updateAnimationStateHandlers();
_animVars.setRigToGeometryTransform(_rigToGeometryTransform);
if (_networkNode) {
_networkVars.setRigToGeometryTransform(_rigToGeometryTransform);
}
AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains,
getGeometryToRigTransform(), rigToWorldTransform);
getGeometryToRigTransform(), rigToWorldTransform, _evaluationCount);
// evaluate the animation
AnimVariantMap triggersOut;
@ -2009,8 +2011,35 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
return;
}
_animVars.set("isTalking", params.isTalking);
_animVars.set("notIsTalking", !params.isTalking);
if (_previousIsTalking != params.isTalking) {
if (_talkIdleInterpTime < 1.0f) {
_talkIdleInterpTime = 1.0f - _talkIdleInterpTime;
} else {
_talkIdleInterpTime = 0.0f;
}
}
_previousIsTalking = params.isTalking;
const float TOTAL_EASE_IN_TIME = 0.75f;
const float TOTAL_EASE_OUT_TIME = 1.5f;
if (params.isTalking) {
if (_talkIdleInterpTime < 1.0f) {
_talkIdleInterpTime += dt / TOTAL_EASE_IN_TIME;
float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f;
_animVars.set("idleOverlayAlpha", easeOutInValue);
} else {
_animVars.set("idleOverlayAlpha", 1.0f);
}
} else {
if (_talkIdleInterpTime < 1.0f) {
_talkIdleInterpTime += dt / TOTAL_EASE_OUT_TIME;
float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f;
float talkAlpha = 1.0f - easeOutInValue;
_animVars.set("idleOverlayAlpha", talkAlpha);
} else {
_animVars.set("idleOverlayAlpha", 0.0f);
}
}
_headEnabled = params.primaryControllerFlags[PrimaryControllerType_Head] & (uint8_t)ControllerFlags::Enabled;
bool leftHandEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftHand] & (uint8_t)ControllerFlags::Enabled;

View file

@ -418,9 +418,12 @@ protected:
HandAnimState _rightHandAnimState;
HandAnimState _leftHandAnimState;
std::map<QString, RoleAnimState> _roleAnimStates;
int _evaluationCount{ 0 };
float _leftHandOverlayAlpha { 0.0f };
float _rightHandOverlayAlpha { 0.0f };
float _talkIdleInterpTime { 0.0f };
bool _previousIsTalking { false };
SimpleMovingAverage _averageForwardSpeed { 10 };
SimpleMovingAverage _averageLateralSpeed { 10 };

View file

@ -94,7 +94,7 @@ void makeTestFBXJoints(HFMModel& hfmModel) {
void AnimInverseKinematicsTests::testSingleChain() {
AnimContext context(false, false, false, glm::mat4(), glm::mat4());
AnimContext context(false, false, false, glm::mat4(), glm::mat4(), 0);
HFMModel hfmModel;
makeTestFBXJoints(hfmModel);

View file

@ -72,7 +72,7 @@ static float framesToSec(float secs) {
}
void AnimTests::testClipEvaulate() {
AnimContext context(false, false, false, glm::mat4(), glm::mat4());
AnimContext context(false, false, false, glm::mat4(), glm::mat4(), 0);
QString id = "myClipNode";
QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
float startFrame = 2.0f;
@ -109,7 +109,7 @@ void AnimTests::testClipEvaulate() {
}
void AnimTests::testClipEvaulateWithVars() {
AnimContext context(false, false, false, glm::mat4(), glm::mat4());
AnimContext context(false, false, false, glm::mat4(), glm::mat4(), 0);
QString id = "myClipNode";
QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
float startFrame = 2.0f;