mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
added AnimRandomSwitch class and got it integrated with a new avatar-animation.json that includes random idles and random fidgets and random talk
This commit is contained in:
parent
2a7ef5c5bb
commit
6d9e4188eb
10 changed files with 543 additions and 9 deletions
|
@ -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 framesAnimatedThisSession) :
|
||||
_enableDebugDrawIKTargets(enableDebugDrawIKTargets),
|
||||
_enableDebugDrawIKConstraints(enableDebugDrawIKConstraints),
|
||||
_enableDebugDrawIKChains(enableDebugDrawIKChains),
|
||||
_geometryToRigMatrix(geometryToRigMatrix),
|
||||
_rigToWorldMatrix(rigToWorldMatrix)
|
||||
_rigToWorldMatrix(rigToWorldMatrix),
|
||||
_framesAnimatedThisSession(framesAnimatedThisSession)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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 framesAnimatedThisSession);
|
||||
|
||||
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 getFramesAnimatedThisSession() const { return _framesAnimatedThisSession; }
|
||||
|
||||
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 _framesAnimatedThisSession { 0 };
|
||||
|
||||
// used for debugging internal state of animation system.
|
||||
mutable DebugAlphaMap _debugAlphaMap;
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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,136 @@ 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_STRING(triggerRandomSwitch, jsonObj, nodeId, jsonUrl, false);
|
||||
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->setTriggerRandomSwitchVar(triggerRandomSwitch);
|
||||
smNode->setTriggerTimeMin(triggerTimeMin);
|
||||
smNode->setTriggerTimeMax(triggerTimeMax);
|
||||
smNode->setTransitionVar(transitionVar);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
AnimNodeLoader::AnimNodeLoader(const QUrl& url) :
|
||||
_url(url)
|
||||
{
|
||||
|
|
194
libraries/animation/src/AnimRandomSwitch.cpp
Normal file
194
libraries/animation/src/AnimRandomSwitch.cpp
Normal file
|
@ -0,0 +1,194 @@
|
|||
//
|
||||
// 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(_framesActive - context.getFramesAnimatedThisSession()) > 1 || animVars.lookup(_triggerRandomSwitchVar, false)) {
|
||||
// get a random number and decide which motion to choose.
|
||||
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;
|
||||
break;
|
||||
} else {
|
||||
lowerBound = upperBound;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (abs(_framesActive - context.getFramesAnimatedThisSession()) > 1) {
|
||||
_duringInterp = false;
|
||||
switchRandomState(animVars, context, desiredState, _duringInterp);
|
||||
} else {
|
||||
if (desiredState->getID() != _currentState->getID()) {
|
||||
_duringInterp = true;
|
||||
switchRandomState(animVars, context, desiredState, _duringInterp);
|
||||
} else {
|
||||
_duringInterp = false;
|
||||
}
|
||||
}
|
||||
_triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax);
|
||||
|
||||
} else {
|
||||
|
||||
// here we are checking to see if we want a temporary movement
|
||||
// evaluate currentState transitions
|
||||
auto desiredState = evaluateTransitions(animVars);
|
||||
if (desiredState != _currentState) {
|
||||
_duringInterp = true;
|
||||
switchRandomState(animVars, context, desiredState, _duringInterp);
|
||||
_triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax);
|
||||
}
|
||||
}
|
||||
|
||||
_triggerTime -= dt;
|
||||
if (_triggerTime < 0.0f) {
|
||||
_triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax);
|
||||
triggersOut.setTrigger(_transitionVar);
|
||||
}
|
||||
|
||||
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 {
|
||||
AnimPoseVec checkNodes = currentStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
_duringInterp = false;
|
||||
_prevPoses.clear();
|
||||
_nextPoses.clear();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}else {
|
||||
context.setDebugAlpha(_currentState->getID(), parentDebugAlpha, _children[_currentState->getChildIndex()]->getType());
|
||||
_poses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
}
|
||||
|
||||
_framesActive = context.getFramesAnimatedThisSession();
|
||||
processOutputJoints(triggersOut);
|
||||
context.addStateMachineInfo(_id, _currentState->getID(), _previousState->getID(), _duringInterp, _alpha);
|
||||
|
||||
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;
|
||||
}
|
178
libraries/animation/src/AnimRandomSwitch.h
Normal file
178
libraries/animation/src/AnimRandomSwitch.h
Normal file
|
@ -0,0 +1,178 @@
|
|||
//
|
||||
// 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 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 _framesActive { 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 };
|
||||
|
||||
private:
|
||||
// no copies
|
||||
AnimRandomSwitch(const AnimRandomSwitch&) = delete;
|
||||
AnimRandomSwitch& operator=(const AnimRandomSwitch&) = delete;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimRandomSwitch_h
|
|
@ -1480,13 +1480,15 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
|
|||
if (_animNode && _enabledAnimations) {
|
||||
DETAILED_PERFORMANCE_TIMER("handleTriggers");
|
||||
|
||||
++_framesAnimatedThisSession;
|
||||
|
||||
updateAnimationStateHandlers();
|
||||
_animVars.setRigToGeometryTransform(_rigToGeometryTransform);
|
||||
if (_networkNode) {
|
||||
_networkVars.setRigToGeometryTransform(_rigToGeometryTransform);
|
||||
}
|
||||
AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains,
|
||||
getGeometryToRigTransform(), rigToWorldTransform);
|
||||
getGeometryToRigTransform(), rigToWorldTransform, _framesAnimatedThisSession);
|
||||
|
||||
// evaluate the animation
|
||||
AnimVariantMap triggersOut;
|
||||
|
@ -2009,8 +2011,11 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
|
|||
return;
|
||||
}
|
||||
|
||||
_animVars.set("isTalking", params.isTalking);
|
||||
_animVars.set("notIsTalking", !params.isTalking);
|
||||
if (params.isTalking) {
|
||||
_animVars.set("idleOverlayAlpha", 1.0f);
|
||||
} 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;
|
||||
|
|
|
@ -418,6 +418,7 @@ protected:
|
|||
HandAnimState _rightHandAnimState;
|
||||
HandAnimState _leftHandAnimState;
|
||||
std::map<QString, RoleAnimState> _roleAnimStates;
|
||||
int _framesAnimatedThisSession { 0 };
|
||||
|
||||
float _leftHandOverlayAlpha { 0.0f };
|
||||
float _rightHandOverlayAlpha { 0.0f };
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue