From 6d9e4188eb8d0248a3fad6c50efa708192918c2c Mon Sep 17 00:00:00 2001
From: Angus Antley <amantley@googlemail.com>
Date: Mon, 8 Apr 2019 22:11:45 -0700
Subject: [PATCH] added AnimRandomSwitch class and got it integrated with a new
 avatar-animation.json that includes random idles and random fidgets and
 random talk

---
 libraries/animation/src/AnimContext.cpp       |   5 +-
 libraries/animation/src/AnimContext.h         |   5 +-
 libraries/animation/src/AnimNode.h            |   1 +
 libraries/animation/src/AnimNodeLoader.cpp    | 151 ++++++++++++++
 libraries/animation/src/AnimRandomSwitch.cpp  | 194 ++++++++++++++++++
 libraries/animation/src/AnimRandomSwitch.h    | 178 ++++++++++++++++
 libraries/animation/src/Rig.cpp               |  11 +-
 libraries/animation/src/Rig.h                 |   1 +
 .../src/AnimInverseKinematicsTests.cpp        |   2 +-
 tests/animation/src/AnimTests.cpp             |   4 +-
 10 files changed, 543 insertions(+), 9 deletions(-)
 create mode 100644 libraries/animation/src/AnimRandomSwitch.cpp
 create mode 100644 libraries/animation/src/AnimRandomSwitch.h

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<QString, Pointer>& 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<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)
 {
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 <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
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<QString, RoleAnimState> _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;