From a8e092272c6a7c9e401562c991dba25f9ffda734 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 4 Feb 2016 10:32:58 -0800 Subject: [PATCH] AnimStateMachine: added new State parameter interpType interpType defines how the interpolation between two states is performed. * 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. --- libraries/animation/src/AnimNodeLoader.cpp | 26 ++++++++++- libraries/animation/src/AnimStateMachine.cpp | 45 +++++++++++++++++--- libraries/animation/src/AnimStateMachine.h | 23 +++++++++- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 568da8dd63..723d4deb29 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -67,6 +67,16 @@ static AnimNode::Type stringToAnimNodeType(const QString& str) { return AnimNode::Type::NumTypes; } +static AnimStateMachine::InterpType stringToInterpType(const QString& str) { + if (str == "snapshotBoth") { + return AnimStateMachine::InterpType::SnapshotBoth; + } else if (str == "snapshotPrev") { + return AnimStateMachine::InterpType::SnapshotPrev; + } else { + return AnimStateMachine::InterpType::NumTypes; + } +} + static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) { switch (type) { case AnimManipulator::JointVar::Type::AbsoluteRotation: return "absoluteRotation"; @@ -465,9 +475,11 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, 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_OPTIONAL_STRING(interpTargetVar, stateObj); READ_OPTIONAL_STRING(interpDurationVar, stateObj); + READ_OPTIONAL_STRING(interpTypeVar, stateObj); auto iter = childMap.find(id); if (iter == childMap.end()) { @@ -475,7 +487,16 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, return false; } - auto statePtr = std::make_shared(id, iter->second, interpTarget, interpDuration); + AnimStateMachine::InterpType interpTypeEnum = AnimStateMachine::InterpType::SnapshotBoth; // default value + if (!interpType.isEmpty()) { + interpTypeEnum = stringToInterpType(interpType); + if (interpTypeEnum == AnimStateMachine::InterpType::NumTypes) { + qCCritical(animation) << "AnimNodeLoader, bad interpType on stateMachine state, nodeId = " << nodeId << "stateId =" << id << "url = " << jsonUrl.toDisplayString(); + return false; + } + } + + auto statePtr = std::make_shared(id, iter->second, interpTarget, interpDuration, interpTypeEnum); assert(statePtr); if (!interpTargetVar.isEmpty()) { @@ -484,6 +505,9 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, if (!interpDurationVar.isEmpty()) { statePtr->setInterpDurationVar(interpDurationVar); } + if (!interpTypeVar.isEmpty()) { + statePtr->setInterpTypeVar(interpTypeVar); + } smNode->addState(statePtr); stateMap.insert(StateMap::value_type(statePtr->getID(), statePtr)); diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 3f5a81a861..e8f9c944b7 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -52,8 +52,25 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl if (_duringInterp) { _alpha += _alphaVel * dt; if (_alpha < 1.0f) { - if (_poses.size() > 0 && _nextPoses.size() > 0 && _prevPoses.size() > 0) { - ::blend(_poses.size(), &_prevPoses[0], &_nextPoses[0], _alpha, &_poses[0]); + 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, 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]); } } else { _duringInterp = false; @@ -86,16 +103,32 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointe _alpha = 0.0f; float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration)); _alphaVel = FRAMES_PER_SECOND / duration; - _prevPoses = _poses; - nextStateNode->setCurrentFrame(desiredState->_interpTarget); + _interpType = (InterpType)animVars.lookup(desiredState->_interpTypeVar, (int)desiredState->_interpType); // because dt is 0, we should not encounter any triggers const float dt = 0.0f; Triggers triggers; - _nextPoses = nextStateNode->evaluate(animVars, dt, triggers); + + if (_interpType == InterpType::SnapshotBoth) { + // snapshot previous pose. + _prevPoses = _poses; + // snapshot next pose at the target frame. + nextStateNode->setCurrentFrame(desiredState->_interpTarget); + _nextPoses = nextStateNode->evaluate(animVars, 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. + nextStateNode->setCurrentFrame(desiredState->_interpTarget - duration); + } else { + assert(false); + } + #if WANT_DEBUG - qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget; + qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget << "interpType = " << (int)_interpType; #endif + _currentState = desiredState; } diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index dda56235d5..6a28ef1825 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -31,13 +31,27 @@ // 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. +// * 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 AnimStateMachine : public AnimNode { public: friend class AnimNodeLoader; friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); + enum class InterpType { + SnapshotBoth = 0, + SnapshotPrev, + NumTypes + }; + protected: + class State { public: friend AnimStateMachine; @@ -55,14 +69,16 @@ protected: State::Pointer _state; }; - State(const QString& id, int childIndex, float interpTarget, float interpDuration) : + State(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType) : _id(id), _childIndex(childIndex), _interpTarget(interpTarget), - _interpDuration(interpDuration) {} + _interpDuration(interpDuration), + _interpType(interpType) {} 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; } const QString& getID() const { return _id; } @@ -78,9 +94,11 @@ protected: int _childIndex; float _interpTarget; // frames float _interpDuration; // frames + InterpType _interpType; QString _interpTargetVar; QString _interpDurationVar; + QString _interpTypeVar; std::vector _transitions; @@ -115,6 +133,7 @@ protected: // interpolation state bool _duringInterp = false; + InterpType _interpType { InterpType::SnapshotBoth }; float _alphaVel = 0.0f; float _alpha = 0.0f; AnimPoseVec _prevPoses;