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;