diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 4131009324..006f90f70b 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -29,6 +29,7 @@ #include "AnimTwoBoneIK.h" #include "AnimSplineIK.h" #include "AnimPoleVectorConstraint.h" +#include "AnimUtil.h" using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); @@ -91,6 +92,8 @@ static AnimStateMachine::InterpType stringToInterpType(const QString& str) { return AnimStateMachine::InterpType::SnapshotBoth; } else if (str == "snapshotPrev") { return AnimStateMachine::InterpType::SnapshotPrev; + } else if (str == "evaluateBoth") { + return AnimStateMachine::InterpType::EvaluateBoth; } else { return AnimStateMachine::InterpType::NumTypes; } @@ -101,11 +104,61 @@ static AnimRandomSwitch::InterpType stringToRandomInterpType(const QString& str) return AnimRandomSwitch::InterpType::SnapshotBoth; } else if (str == "snapshotPrev") { return AnimRandomSwitch::InterpType::SnapshotPrev; + } else if (str == "evaluateBoth") { + return AnimRandomSwitch::InterpType::EvaluateBoth; } else { return AnimRandomSwitch::InterpType::NumTypes; } } +static EasingType stringToEasingType(const QString& str) { + if (str == "easeInSine") { + return EasingType_EaseInSine; + } else if (str == "easeOutSine") { + return EasingType_EaseOutSine; + } else if (str == "easeInOutSine") { + return EasingType_EaseInOutSine; + } else if (str == "easeInQuad") { + return EasingType_EaseInQuad; + } else if (str == "easeOutQuad") { + return EasingType_EaseOutQuad; + } else if (str == "easeInOutQuad") { + return EasingType_EaseInOutQuad; + } else if (str == "easeInCubic") { + return EasingType_EaseInCubic; + } else if (str == "easeOutCubic") { + return EasingType_EaseOutCubic; + } else if (str == "easeInOutCubic") { + return EasingType_EaseInOutCubic; + } else if (str == "easeInQuart") { + return EasingType_EaseInQuart; + } else if (str == "easeOutQuart") { + return EasingType_EaseOutQuart; + } else if (str == "easeInOutQuart") { + return EasingType_EaseInOutQuart; + } else if (str == "easeInQuint") { + return EasingType_EaseInQuint; + } else if (str == "easeOutQuint") { + return EasingType_EaseOutQuint; + } else if (str == "easeInOutQuint") { + return EasingType_EaseInOutQuint; + } else if (str == "easeInExpo") { + return EasingType_EaseInExpo; + } else if (str == "easeOutExpo") { + return EasingType_EaseOutExpo; + } else if (str == "easeInOutExpo") { + return EasingType_EaseInOutExpo; + } else if (str == "easeInCirc") { + return EasingType_EaseInCirc; + } else if (str == "easeOutCirc") { + return EasingType_EaseOutCirc; + } else if (str == "easeInOutCirc") { + return EasingType_EaseInOutCirc; + } else { + return EasingType_NumTypes; + } +} + static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) { switch (type) { case AnimManipulator::JointVar::Type::Absolute: return "absolute"; @@ -723,6 +776,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false); READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false); READ_OPTIONAL_STRING(interpType, stateObj); + READ_OPTIONAL_STRING(easingType, stateObj); READ_OPTIONAL_STRING(interpTargetVar, stateObj); READ_OPTIONAL_STRING(interpDurationVar, stateObj); @@ -743,7 +797,16 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, } } - auto statePtr = std::make_shared(id, iter->second, interpTarget, interpDuration, interpTypeEnum); + EasingType easingTypeEnum = EasingType_Linear; // default value + if (!easingType.isEmpty()) { + easingTypeEnum = stringToEasingType(easingType); + if (easingTypeEnum == EasingType_NumTypes) { + qCCritical(animation) << "AnimNodeLoader, bad easingType on stateMachine state, nodeId = " << nodeId << "stateId =" << id; + return false; + } + } + + auto statePtr = std::make_shared(id, iter->second, interpTarget, interpDuration, interpTypeEnum, easingTypeEnum); assert(statePtr); if (!interpTargetVar.isEmpty()) { @@ -845,6 +908,7 @@ bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObje READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false); READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false); READ_OPTIONAL_STRING(interpType, stateObj); + READ_OPTIONAL_STRING(easingType, stateObj); READ_FLOAT(priority, stateObj, nodeId, jsonUrl, false); READ_BOOL(resume, stateObj, nodeId, jsonUrl, false); @@ -867,7 +931,16 @@ bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObje } } - auto randomStatePtr = std::make_shared(id, iter->second, interpTarget, interpDuration, interpTypeEnum, priority, resume); + EasingType easingTypeEnum = EasingType_Linear; // default value + if (!easingType.isEmpty()) { + easingTypeEnum = stringToEasingType(easingType); + if (easingTypeEnum == EasingType_NumTypes) { + qCCritical(animation) << "AnimNodeLoader, bad easingType on randomSwitch state, nodeId = " << nodeId << "stateId =" << id; + return false; + } + } + + auto randomStatePtr = std::make_shared(id, iter->second, interpTarget, interpDuration, interpTypeEnum, easingTypeEnum, priority, resume); if (priority > 0.0f) { smNode->addToPrioritySum(priority); } diff --git a/libraries/animation/src/AnimRandomSwitch.cpp b/libraries/animation/src/AnimRandomSwitch.cpp index 2549a50062..edc8c8dd96 100644 --- a/libraries/animation/src/AnimRandomSwitch.cpp +++ b/libraries/animation/src/AnimRandomSwitch.cpp @@ -89,6 +89,7 @@ const AnimPoseVec& AnimRandomSwitch::evaluate(const AnimVariantMap& animVars, co assert(_currentState); auto currentStateNode = _children[_currentState->getChildIndex()]; + auto previousStateNode = _children[_previousState->getChildIndex()]; assert(currentStateNode); if (_duringInterp) { @@ -97,6 +98,7 @@ const AnimPoseVec& AnimRandomSwitch::evaluate(const AnimVariantMap& animVars, co AnimPoseVec* nextPoses = nullptr; AnimPoseVec* prevPoses = nullptr; AnimPoseVec localNextPoses; + AnimPoseVec localPrevPoses; if (_interpType == InterpType::SnapshotBoth) { // interp between both snapshots prevPoses = &_prevPoses; @@ -107,13 +109,18 @@ const AnimPoseVec& AnimRandomSwitch::evaluate(const AnimVariantMap& animVars, co localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut); prevPoses = &_prevPoses; nextPoses = &localNextPoses; + } else if (_interpType == InterpType::EvaluateBoth) { + localPrevPoses = previousStateNode->evaluate(animVars, context, dt, triggersOut); + localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut); + prevPoses = &localPrevPoses; + 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]); + ::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), easingFunc(_alpha, _easingType), &_poses[0]); } - context.setDebugAlpha(_currentState->getID(), _alpha * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); + context.setDebugAlpha(_currentState->getID(), easingFunc(_alpha, _easingType) * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); } else { _duringInterp = false; _prevPoses.clear(); @@ -160,6 +167,7 @@ void AnimRandomSwitch::switchRandomState(const AnimVariantMap& animVars, const A 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); + _easingType = desiredState->_easingType; // because dt is 0, we should not encounter any triggers const float dt = 0.0f; diff --git a/libraries/animation/src/AnimRandomSwitch.h b/libraries/animation/src/AnimRandomSwitch.h index 7a750cd89f..7c185dd7cb 100644 --- a/libraries/animation/src/AnimRandomSwitch.h +++ b/libraries/animation/src/AnimRandomSwitch.h @@ -14,6 +14,7 @@ #include #include #include "AnimNode.h" +#include "AnimUtil.h" // Random Switch State Machine for random transitioning between children AnimNodes // @@ -51,6 +52,7 @@ public: enum class InterpType { SnapshotBoth = 0, SnapshotPrev, + EvaluateBoth, NumTypes }; @@ -73,12 +75,13 @@ protected: RandomSwitchState::Pointer _randomSwitchState; }; - RandomSwitchState(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType, float priority, bool resume) : + RandomSwitchState(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType, EasingType easingType, float priority, bool resume) : _id(id), _childIndex(childIndex), _interpTarget(interpTarget), _interpDuration(interpDuration), _interpType(interpType), + _easingType(easingType), _priority(priority), _resume(resume){ } @@ -106,6 +109,7 @@ protected: float _interpTarget; // frames float _interpDuration; // frames InterpType _interpType; + EasingType _easingType; float _priority {0.0f}; bool _resume {false}; @@ -154,7 +158,8 @@ protected: int _randomSwitchEvaluationCount { 0 }; // interpolation state bool _duringInterp = false; - InterpType _interpType{ InterpType::SnapshotPrev }; + InterpType _interpType { InterpType::SnapshotPrev }; + EasingType _easingType { EasingType_Linear }; float _alphaVel = 0.0f; float _alpha = 0.0f; AnimPoseVec _prevPoses; diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 22a362ee1c..b2d993abb8 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -48,6 +48,7 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co assert(_currentState); auto currentStateNode = _children[_currentState->getChildIndex()]; + auto previousStateNode = _children[_previousState->getChildIndex()]; assert(currentStateNode); if (_duringInterp) { @@ -56,6 +57,8 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co AnimPoseVec* nextPoses = nullptr; AnimPoseVec* prevPoses = nullptr; AnimPoseVec localNextPoses; + AnimPoseVec localPrevPoses; + if (_interpType == InterpType::SnapshotBoth) { // interp between both snapshots prevPoses = &_prevPoses; @@ -66,13 +69,18 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut); prevPoses = &_prevPoses; nextPoses = &localNextPoses; + } else if (_interpType == InterpType::EvaluateBoth) { + localPrevPoses = previousStateNode->evaluate(animVars, context, dt, triggersOut); + localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut); + prevPoses = &localPrevPoses; + 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]); + ::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), easingFunc(_alpha, _easingType), &_poses[0]); } - context.setDebugAlpha(_currentState->getID(), _alpha * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); + context.setDebugAlpha(_currentState->getID(), easingFunc(_alpha, _easingType) * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); } else { _duringInterp = false; _prevPoses.clear(); @@ -125,6 +133,7 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, const AnimCon 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); + _easingType = desiredState->_easingType; // because dt is 0, we should not encounter any triggers const float dt = 0.0f; diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index aee7ba65e1..3998f7e9ef 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -14,6 +14,7 @@ #include #include #include "AnimNode.h" +#include "AnimUtil.h" // State Machine for transitioning between children AnimNodes // @@ -47,6 +48,7 @@ public: enum class InterpType { SnapshotBoth = 0, SnapshotPrev, + EvaluateBoth, NumTypes }; @@ -69,12 +71,13 @@ protected: State::Pointer _state; }; - State(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType) : + State(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType, EasingType easingType) : _id(id), _childIndex(childIndex), _interpTarget(interpTarget), _interpDuration(interpDuration), - _interpType(interpType) {} + _interpType(interpType), + _easingType(easingType) {} void setInterpTargetVar(const QString& interpTargetVar) { _interpTargetVar = interpTargetVar; } void setInterpDurationVar(const QString& interpDurationVar) { _interpDurationVar = interpDurationVar; } @@ -95,6 +98,7 @@ protected: float _interpTarget; // frames float _interpDuration; // frames InterpType _interpType; + EasingType _easingType; QString _interpTargetVar; QString _interpDurationVar; @@ -135,6 +139,7 @@ protected: // interpolation state bool _duringInterp = false; InterpType _interpType { InterpType::SnapshotPrev }; + EasingType _easingType { EasingType_Linear }; float _alphaVel = 0.0f; float _alpha = 0.0f; AnimPoseVec _prevPoses; diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index 5fca2b4f88..e8537b8972 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -211,3 +211,95 @@ bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose return true; } } + +// See https://easings.net/en# for a graphical visualiztion of easing types. +float easingFunc(float alpha, EasingType type) { + switch (type) { + case EasingType_Linear: + return alpha; + case EasingType_EaseInSine: + return sinf((alpha - 1.0f) * PI_OVER_TWO) + 1.0f; + case EasingType_EaseOutSine: + return sinf(alpha * PI_OVER_TWO); + case EasingType_EaseInOutSine: + return 0.5f * (1.0f - cos(alpha * PI)); + case EasingType_EaseInQuad: + return alpha * alpha; + case EasingType_EaseOutQuad: + return -(alpha * (alpha - 2.0f)); + case EasingType_EaseInOutQuad: + return (alpha < 0.5f) ? (2.0f * alpha * alpha) : ((-2.0f * alpha * alpha) + (4.0f * alpha) - 1.0f); + case EasingType_EaseInCubic: + return alpha * alpha * alpha; + case EasingType_EaseOutCubic: + { + float temp = alpha - 1.0f; + return temp * temp * temp + 1.0f; + } + case EasingType_EaseInOutCubic: + if (alpha < 0.5f) { + return 4.0f * alpha * alpha * alpha; + } else { + float temp = ((2.0f * alpha) - 2.0f); + return 0.5f * temp * temp * temp + 1.0f; + } + break; + case EasingType_EaseInQuart: + return alpha * alpha * alpha * alpha; + case EasingType_EaseOutQuart: + { + float temp = alpha - 1.0f; + return temp * temp * temp * (1.0f - alpha) + 1.0f; + } + break; + case EasingType_EaseInOutQuart: + if (alpha < 0.5f) { + return 8.0f * alpha * alpha * alpha * alpha; + } else { + float temp = alpha - 1.0f; + return -8.0f * temp * temp * temp * temp + 1.0f; + } + break; + case EasingType_EaseInQuint: + return alpha * alpha * alpha * alpha * alpha; + case EasingType_EaseOutQuint: + { + float temp = (alpha - 1.0f); + return temp * temp * temp * temp * temp + 1.0f; + } + case EasingType_EaseInOutQuint: + if (alpha < 0.5f) { + return 16.0f * alpha * alpha * alpha * alpha * alpha; + } else { + float temp = ((2.0f * alpha) - 2.0f); + return 0.5f * temp * temp * temp * temp * temp + 1.0f; + } + break; + case EasingType_EaseInExpo: + return (alpha == 0.0f) ? alpha : powf(2.0f, 10.0f * (alpha - 1.0f)); + case EasingType_EaseOutExpo: + return (alpha == 1.0f) ? alpha : 1.0f - powf(2.0f, -10.0f * alpha); + case EasingType_EaseInOutExpo: + if (alpha == 0.0f || alpha == 1.0f) + return alpha; + else if (alpha < 0.5) { + return 0.5f * powf(2.0f, (20.0f * alpha) - 10.0f); + } else { + return -0.5f * powf(2.0f, (-20.0f * alpha) + 10.0f) + 1.0f; + } + break; + case EasingType_EaseInCirc: + return 1.0f - sqrtf(1.0f - (alpha * alpha)); + case EasingType_EaseOutCirc: + return sqrtf((2.0f - alpha) * alpha); + case EasingType_EaseInOutCirc: + if (alpha < 0.5f) { + return 0.5f * (1.0f - sqrtf(1.0f - 4.0f * (alpha * alpha))); + } else { + return 0.5f * (sqrtf(-((2.0f * alpha) - 3.0f) * ((2.0f * alpha) - 1.0f)) + 1.0f); + } + break; + default: + return alpha; + } +} diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index c2925e31e8..26dc19da06 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -128,10 +128,37 @@ protected: bool _snapshotValid { false }; }; - // returns true if the given point lies inside of the k-dop, specified by shapeInfo & shapePose. // if the given point does lie within the k-dop, it also returns the amount of displacement necessary to push that point outward // such that it lies on the surface of the kdop. bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const HFMJointShapeInfo& shapeInfo, glm::vec3& displacementOut); +enum EasingType { + EasingType_Linear, + EasingType_EaseInSine, + EasingType_EaseOutSine, + EasingType_EaseInOutSine, + EasingType_EaseInQuad, + EasingType_EaseOutQuad, + EasingType_EaseInOutQuad, + EasingType_EaseInCubic, + EasingType_EaseOutCubic, + EasingType_EaseInOutCubic, + EasingType_EaseInQuart, + EasingType_EaseOutQuart, + EasingType_EaseInOutQuart, + EasingType_EaseInQuint, + EasingType_EaseOutQuint, + EasingType_EaseInOutQuint, + EasingType_EaseInExpo, + EasingType_EaseOutExpo, + EasingType_EaseInOutExpo, + EasingType_EaseInCirc, + EasingType_EaseOutCirc, + EasingType_EaseInOutCirc, + EasingType_NumTypes +}; + +float easingFunc(float alpha, EasingType type); + #endif