diff --git a/libraries/animation/src/AnimContext.cpp b/libraries/animation/src/AnimContext.cpp index c8efd83318..3c14ded862 100644 --- a/libraries/animation/src/AnimContext.cpp +++ b/libraries/animation/src/AnimContext.cpp @@ -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) { } diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index e3ab5d9788..23878a0413 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -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; diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 1a12bb8ddb..31e10ca2d5 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -43,6 +43,7 @@ public: friend class AnimDebugDraw; friend void buildChildMap(std::map& map, Pointer node); friend class AnimStateMachine; + friend class AnimRandomSwitch; AnimNode(Type type, const QString& id) : _type(type), _id(id) {} virtual ~AnimNode() {} diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index b637d131f8..61468c0ce7 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -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(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(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 childMap; + buildChildMap(childMap, node); + + // first pass parse all the states and build up the state and transition map. + using StringPair = std::pair; + using TransitionMap = std::multimap; + TransitionMap transitionMap; + + using RandomStateMap = std::map; + 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(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) { diff --git a/libraries/animation/src/AnimRandomSwitch.cpp b/libraries/animation/src/AnimRandomSwitch.cpp new file mode 100644 index 0000000000..46c8e4fccf --- /dev/null +++ b/libraries/animation/src/AnimRandomSwitch.cpp @@ -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; +} diff --git a/libraries/animation/src/AnimRandomSwitch.h b/libraries/animation/src/AnimRandomSwitch.h new file mode 100644 index 0000000000..ff363b4154 --- /dev/null +++ b/libraries/animation/src/AnimRandomSwitch.h @@ -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 +#include +#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; + using ConstPointer = std::shared_ptr; + + 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 _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 _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 diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index a4c57025be..6d4b0b12a3 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -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; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index b9a7f73117..796be368fe 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -418,6 +418,7 @@ protected: HandAnimState _rightHandAnimState; HandAnimState _leftHandAnimState; std::map _roleAnimStates; + int _framesAnimatedThisSession { 0 }; float _leftHandOverlayAlpha { 0.0f }; float _rightHandOverlayAlpha { 0.0f }; diff --git a/tests/animation/src/AnimInverseKinematicsTests.cpp b/tests/animation/src/AnimInverseKinematicsTests.cpp index 708b1b2d2a..ad4a648070 100644 --- a/tests/animation/src/AnimInverseKinematicsTests.cpp +++ b/tests/animation/src/AnimInverseKinematicsTests.cpp @@ -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); diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index a14ffcf967..8a30ba8f56 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -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;