| #include "rive/animation/state_transition.hpp" | |
| #include "rive/importers/import_stack.hpp" | |
| #include "rive/importers/layer_state_importer.hpp" | |
| #include "rive/animation/layer_state.hpp" | |
| #include "rive/animation/transition_condition.hpp" | |
| #include "rive/animation/animation_state.hpp" | |
| #include "rive/animation/linear_animation.hpp" | |
| #include "rive/animation/state_machine_input_instance.hpp" | |
| #include "rive/animation/state_machine_trigger.hpp" | |
| #include "rive/animation/animation_state_instance.hpp" | |
| #include "rive/animation/transition_trigger_condition.hpp" | |
| using namespace rive; | |
| StateTransition::~StateTransition() | |
| { | |
| for (auto condition : m_Conditions) | |
| { | |
| delete condition; | |
| } | |
| } | |
| StatusCode StateTransition::onAddedDirty(CoreContext* context) | |
| { | |
| StatusCode code; | |
| for (auto condition : m_Conditions) | |
| { | |
| if ((code = condition->onAddedDirty(context)) != StatusCode::Ok) | |
| { | |
| return code; | |
| } | |
| } | |
| return StatusCode::Ok; | |
| } | |
| StatusCode StateTransition::onAddedClean(CoreContext* context) | |
| { | |
| StatusCode code; | |
| for (auto condition : m_Conditions) | |
| { | |
| if ((code = condition->onAddedClean(context)) != StatusCode::Ok) | |
| { | |
| return code; | |
| } | |
| } | |
| return StatusCode::Ok; | |
| } | |
| StatusCode StateTransition::import(ImportStack& importStack) | |
| { | |
| auto stateImporter = importStack.latest<LayerStateImporter>(LayerState::typeKey); | |
| if (stateImporter == nullptr) | |
| { | |
| return StatusCode::MissingObject; | |
| } | |
| stateImporter->addTransition(this); | |
| return Super::import(importStack); | |
| } | |
| void StateTransition::addCondition(TransitionCondition* condition) | |
| { | |
| m_Conditions.push_back(condition); | |
| } | |
| float StateTransition::mixTime(const LayerState* stateFrom) const | |
| { | |
| if (duration() == 0) | |
| { | |
| return 0; | |
| } | |
| if ((transitionFlags() & StateTransitionFlags::DurationIsPercentage) == | |
| StateTransitionFlags::DurationIsPercentage) | |
| { | |
| float animationDuration = 0.0f; | |
| if (stateFrom->is<AnimationState>()) | |
| { | |
| auto animation = stateFrom->as<AnimationState>()->animation(); | |
| if (animation != nullptr) | |
| { | |
| animationDuration = animation->durationSeconds(); | |
| } | |
| } | |
| return duration() / 100.0f * animationDuration; | |
| } | |
| else | |
| { | |
| return duration() / 1000.0f; | |
| } | |
| } | |
| float StateTransition::exitTimeSeconds(const LayerState* stateFrom, bool absolute) const | |
| { | |
| if ((transitionFlags() & StateTransitionFlags::ExitTimeIsPercentage) == | |
| StateTransitionFlags::ExitTimeIsPercentage) | |
| { | |
| float animationDuration = 0.0f; | |
| float start = 0.0f; | |
| auto exitAnimation = exitTimeAnimation(stateFrom); | |
| if (exitAnimation != nullptr) | |
| { | |
| start = absolute ? exitAnimation->startSeconds() : 0.0f; | |
| animationDuration = exitAnimation->durationSeconds(); | |
| } | |
| return start + exitTime() / 100.0f * animationDuration; | |
| } | |
| return exitTime() / 1000.0f; | |
| } | |
| const LinearAnimationInstance* | |
| StateTransition::exitTimeAnimationInstance(const StateInstance* from) const | |
| { | |
| return from != nullptr && from->state()->is<AnimationState>() | |
| ? static_cast<const AnimationStateInstance*>(from)->animationInstance() | |
| : nullptr; | |
| } | |
| const LinearAnimation* StateTransition::exitTimeAnimation(const LayerState* from) const | |
| { | |
| return from != nullptr && from->is<AnimationState>() ? from->as<AnimationState>()->animation() | |
| : nullptr; | |
| } | |
| AllowTransition StateTransition::allowed(StateInstance* stateFrom, | |
| Span<SMIInput*> inputs, | |
| bool ignoreTriggers) const | |
| { | |
| if (isDisabled()) | |
| { | |
| return AllowTransition::no; | |
| } | |
| for (auto condition : m_Conditions) | |
| { | |
| // N.B. state machine instance sanitizes these for us... | |
| auto input = inputs[condition->inputId()]; | |
| if ((ignoreTriggers && condition->is<TransitionTriggerCondition>()) || | |
| !condition->evaluate(input)) | |
| { | |
| return AllowTransition::no; | |
| } | |
| } | |
| if (enableExitTime()) | |
| { | |
| auto exitAnimation = exitTimeAnimationInstance(stateFrom); | |
| if (exitAnimation != nullptr) | |
| { | |
| // Exit time is specified in a value less than a single loop, so we | |
| // want to allow exiting regardless of which loop we're on. To do | |
| // that we bring the exit time up to the loop our lastTime is at. | |
| auto lastTime = exitAnimation->lastTotalTime(); | |
| auto time = exitAnimation->totalTime(); | |
| auto exitTime = exitTimeSeconds(stateFrom->state()); | |
| auto animationFrom = exitAnimation->animation(); | |
| auto duration = animationFrom->durationSeconds(); | |
| // TODO: there are some considerations to have when exit time is | |
| // combined with another condition (like trigger) | |
| // - not sure how to get this to make sense with pingPing | |
| // animations | |
| // - also if exit time is, say 50% on a loop, this will be happy | |
| // to fire | |
| // - when time is anywhere in 50%-100%, 150%-200%. as opposed | |
| // to just at 50% | |
| // .... makes you wonder if we need some kind of exit | |
| // after/exit before time | |
| // .... but i suspect that will introduce some more issues? | |
| // There's only one iteration in oneShot, | |
| if (exitTime <= duration && animationFrom->loop() != Loop::oneShot) | |
| { | |
| // Get exit time relative to the loop lastTime was in. | |
| exitTime += std::floor(lastTime / duration) * duration; | |
| } | |
| if (time < exitTime) | |
| { | |
| return AllowTransition::waitingForExit; | |
| } | |
| } | |
| } | |
| return AllowTransition::yes; | |
| } | |
| bool StateTransition::applyExitCondition(StateInstance* from) const | |
| { | |
| // Hold exit time when the user has set to pauseOnExit on this condition | |
| // (only valid when exiting from an Animation). | |
| bool useExitTime = enableExitTime() && (from != nullptr && from->state()->is<AnimationState>()); | |
| if (pauseOnExit() && useExitTime) | |
| { | |
| static_cast<AnimationStateInstance*>(from)->animationInstance()->time( | |
| exitTimeSeconds(from->state(), true)); | |
| return true; | |
| } | |
| return useExitTime; | |
| } |