support randomizing transitions First version of randomization to get in UAT for validation. Diffs= edac19b06 support randomizing transitions (#7082) Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head index 929a551..58930e3 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -2828b7b013811f1f4b0b0e2ee0f6d9b23c73878b +edac19b0600a1ede64bb0b07e61556013a3e84fe
diff --git a/dev/defs/animation/layer_state.json b/dev/defs/animation/layer_state.json index ce57d57..b15b793 100644 --- a/dev/defs/animation/layer_state.json +++ b/dev/defs/animation/layer_state.json
@@ -34,6 +34,14 @@ "string": "y" }, "runtime": false + }, + "flags": { + "type": "uint", + "initialValue": "0", + "key": { + "int": 536, + "string": "flags" + } } } } \ No newline at end of file
diff --git a/dev/defs/animation/state_transition.json b/dev/defs/animation/state_transition.json index 072d10e..3ac429e 100644 --- a/dev/defs/animation/state_transition.json +++ b/dev/defs/animation/state_transition.json
@@ -82,6 +82,15 @@ "string": "interpolatorid" }, "description": "The id of the custom interpolator used when interpolation is Cubic." + }, + "randomWeight": { + "type": "uint", + "initialValue": "1", + "key": { + "int": 537, + "string": "randomweight" + }, + "description": "Weight of the transition in the overall random options" } } } \ No newline at end of file
diff --git a/include/rive/animation/layer_state_flags.hpp b/include/rive/animation/layer_state_flags.hpp new file mode 100644 index 0000000..880bd35 --- /dev/null +++ b/include/rive/animation/layer_state_flags.hpp
@@ -0,0 +1,24 @@ +#ifndef _RIVE_LAYER_STATE_FLAGS_HPP_ +#define _RIVE_LAYER_STATE_FLAGS_HPP_ + +#include <type_traits> + +namespace rive +{ +enum class LayerStateFlags : unsigned char +{ + None = 0, + + /// Whether the transition is disabled. + Random = 1 << 0, + +}; + +inline constexpr LayerStateFlags operator&(LayerStateFlags lhs, LayerStateFlags rhs) +{ + return static_cast<LayerStateFlags>( + static_cast<std::underlying_type<LayerStateFlags>::type>(lhs) & + static_cast<std::underlying_type<LayerStateFlags>::type>(rhs)); +} +} // namespace rive +#endif \ No newline at end of file
diff --git a/include/rive/animation/state_machine_instance.hpp b/include/rive/animation/state_machine_instance.hpp index 98a5693..1232b11 100644 --- a/include/rive/animation/state_machine_instance.hpp +++ b/include/rive/animation/state_machine_instance.hpp
@@ -43,6 +43,7 @@ friend class SMIInput; friend class KeyedProperty; friend class HitComponent; + friend class StateMachineLayerInstance; private: /// Provide a hitListener if you want to process a down or an up for the pointer position @@ -53,6 +54,7 @@ InstType* getNamedInput(const std::string& name) const; void notifyEventListeners(const std::vector<EventReport>& events, NestedArtboard* source); void sortHitComponents(); + double randomValue(); public: StateMachineInstance(const StateMachine* machine, ArtboardInstance* instance);
diff --git a/include/rive/animation/state_transition.hpp b/include/rive/animation/state_transition.hpp index 412ac06..c021c2c 100644 --- a/include/rive/animation/state_transition.hpp +++ b/include/rive/animation/state_transition.hpp
@@ -35,6 +35,7 @@ return static_cast<StateTransitionFlags>(flags()); } LayerState* m_StateTo = nullptr; + uint32_t m_EvaluatedRandomWeight = 1; CubicInterpolator* m_Interpolator = nullptr; std::vector<TransitionCondition*> m_Conditions; @@ -45,6 +46,9 @@ const LayerState* stateTo() const { return m_StateTo; } inline CubicInterpolator* interpolator() const { return m_Interpolator; } + inline uint32_t evaluatedRandomWeight() const { return m_EvaluatedRandomWeight; } + void evaluatedRandomWeight(uint32_t value) { m_EvaluatedRandomWeight = value; } + StatusCode onAddedDirty(CoreContext* context) override; StatusCode onAddedClean(CoreContext* context) override;
diff --git a/include/rive/generated/animation/layer_state_base.hpp b/include/rive/generated/animation/layer_state_base.hpp index a14df6c..f561138 100644 --- a/include/rive/generated/animation/layer_state_base.hpp +++ b/include/rive/generated/animation/layer_state_base.hpp
@@ -1,6 +1,7 @@ #ifndef _RIVE_LAYER_STATE_BASE_HPP_ #define _RIVE_LAYER_STATE_BASE_HPP_ #include "rive/animation/state_machine_layer_component.hpp" +#include "rive/core/field_types/core_uint_type.hpp" namespace rive { class LayerStateBase : public StateMachineLayerComponent @@ -27,7 +28,42 @@ uint16_t coreType() const override { return typeKey; } + static const uint16_t flagsPropertyKey = 535; + +private: + uint32_t m_Flags = 0; + +public: + inline uint32_t flags() const { return m_Flags; } + void flags(uint32_t value) + { + if (m_Flags == value) + { + return; + } + m_Flags = value; + flagsChanged(); + } + + void copy(const LayerStateBase& object) + { + m_Flags = object.m_Flags; + StateMachineLayerComponent::copy(object); + } + + bool deserialize(uint16_t propertyKey, BinaryReader& reader) override + { + switch (propertyKey) + { + case flagsPropertyKey: + m_Flags = CoreUintType::deserialize(reader); + return true; + } + return StateMachineLayerComponent::deserialize(propertyKey, reader); + } + protected: + virtual void flagsChanged() {} }; } // namespace rive
diff --git a/include/rive/generated/animation/state_transition_base.hpp b/include/rive/generated/animation/state_transition_base.hpp index 9b526ac..2deeb09 100644 --- a/include/rive/generated/animation/state_transition_base.hpp +++ b/include/rive/generated/animation/state_transition_base.hpp
@@ -34,6 +34,7 @@ static const uint16_t exitTimePropertyKey = 160; static const uint16_t interpolationTypePropertyKey = 349; static const uint16_t interpolatorIdPropertyKey = 350; + static const uint16_t randomWeightPropertyKey = 536; private: uint32_t m_StateToId = -1; @@ -42,6 +43,7 @@ uint32_t m_ExitTime = 0; uint32_t m_InterpolationType = 1; uint32_t m_InterpolatorId = -1; + uint32_t m_RandomWeight = 1; public: inline uint32_t stateToId() const { return m_StateToId; } @@ -110,6 +112,17 @@ interpolatorIdChanged(); } + inline uint32_t randomWeight() const { return m_RandomWeight; } + void randomWeight(uint32_t value) + { + if (m_RandomWeight == value) + { + return; + } + m_RandomWeight = value; + randomWeightChanged(); + } + Core* clone() const override; void copy(const StateTransitionBase& object) { @@ -119,6 +132,7 @@ m_ExitTime = object.m_ExitTime; m_InterpolationType = object.m_InterpolationType; m_InterpolatorId = object.m_InterpolatorId; + m_RandomWeight = object.m_RandomWeight; StateMachineLayerComponent::copy(object); } @@ -144,6 +158,9 @@ case interpolatorIdPropertyKey: m_InterpolatorId = CoreUintType::deserialize(reader); return true; + case randomWeightPropertyKey: + m_RandomWeight = CoreUintType::deserialize(reader); + return true; } return StateMachineLayerComponent::deserialize(propertyKey, reader); } @@ -155,6 +172,7 @@ virtual void exitTimeChanged() {} virtual void interpolationTypeChanged() {} virtual void interpolatorIdChanged() {} + virtual void randomWeightChanged() {} }; } // namespace rive
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp index a911f8c..0b077aa 100644 --- a/include/rive/generated/core_registry.hpp +++ b/include/rive/generated/core_registry.hpp
@@ -460,6 +460,9 @@ case ListenerFireEventBase::eventIdPropertyKey: object->as<ListenerFireEventBase>()->eventId(value); break; + case LayerStateBase::flagsPropertyKey: + object->as<LayerStateBase>()->flags(value); + break; case ListenerInputChangeBase::inputIdPropertyKey: object->as<ListenerInputChangeBase>()->inputId(value); break; @@ -538,6 +541,9 @@ case StateTransitionBase::interpolatorIdPropertyKey: object->as<StateTransitionBase>()->interpolatorId(value); break; + case StateTransitionBase::randomWeightPropertyKey: + object->as<StateTransitionBase>()->randomWeight(value); + break; case StateMachineFireEventBase::eventIdPropertyKey: object->as<StateMachineFireEventBase>()->eventId(value); break; @@ -1256,6 +1262,8 @@ return object->as<SoloBase>()->activeComponentId(); case ListenerFireEventBase::eventIdPropertyKey: return object->as<ListenerFireEventBase>()->eventId(); + case LayerStateBase::flagsPropertyKey: + return object->as<LayerStateBase>()->flags(); case ListenerInputChangeBase::inputIdPropertyKey: return object->as<ListenerInputChangeBase>()->inputId(); case ListenerInputChangeBase::nestedInputIdPropertyKey: @@ -1308,6 +1316,8 @@ return object->as<StateTransitionBase>()->interpolationType(); case StateTransitionBase::interpolatorIdPropertyKey: return object->as<StateTransitionBase>()->interpolatorId(); + case StateTransitionBase::randomWeightPropertyKey: + return object->as<StateTransitionBase>()->randomWeight(); case StateMachineFireEventBase::eventIdPropertyKey: return object->as<StateMachineFireEventBase>()->eventId(); case StateMachineFireEventBase::occursValuePropertyKey: @@ -1779,6 +1789,7 @@ case NestedAnimationBase::animationIdPropertyKey: case SoloBase::activeComponentIdPropertyKey: case ListenerFireEventBase::eventIdPropertyKey: + case LayerStateBase::flagsPropertyKey: case ListenerInputChangeBase::inputIdPropertyKey: case ListenerInputChangeBase::nestedInputIdPropertyKey: case AnimationStateBase::animationIdPropertyKey: @@ -1805,6 +1816,7 @@ case StateTransitionBase::exitTimePropertyKey: case StateTransitionBase::interpolationTypePropertyKey: case StateTransitionBase::interpolatorIdPropertyKey: + case StateTransitionBase::randomWeightPropertyKey: case StateMachineFireEventBase::eventIdPropertyKey: case StateMachineFireEventBase::occursValuePropertyKey: case LinearAnimationBase::fpsPropertyKey:
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp index b75642a..3617c64 100644 --- a/src/animation/state_machine_instance.cpp +++ b/src/animation/state_machine_instance.cpp
@@ -3,6 +3,7 @@ #include "rive/animation/any_state.hpp" #include "rive/animation/cubic_interpolator.hpp" #include "rive/animation/entry_state.hpp" +#include "rive/animation/layer_state_flags.hpp" #include "rive/animation/nested_state_machine.hpp" #include "rive/animation/state_instance.hpp" #include "rive/animation/state_machine_bool.hpp" @@ -140,6 +141,13 @@ } } + bool canChangeState(const LayerState* stateTo) + { + return !((m_currentState == nullptr ? nullptr : m_currentState->state()) == stateTo); + } + + double randomValue() { return ((double)rand() / (RAND_MAX)); } + bool changeState(const LayerState* stateTo) { if ((m_currentState == nullptr ? nullptr : m_currentState->state()) == stateTo) @@ -172,73 +180,105 @@ } auto stateFrom = stateFromInstance->state(); auto outState = m_currentState; + uint32_t totalWeight = 0; for (size_t i = 0, length = stateFrom->transitionCount(); i < length; i++) { auto transition = stateFrom->transition(i); auto allowed = transition->allowed(stateFromInstance, m_stateMachineInstance, ignoreTriggers); - if (allowed == AllowTransition::yes && changeState(transition->stateTo())) + if (allowed == AllowTransition::yes && canChangeState(transition->stateTo())) { - m_stateMachineChangedOnAdvance = true; - // state actually has changed - m_transition = transition; - fireEvents(StateMachineFireOccurance::atStart, transition->events()); - if (transition->duration() == 0) + transition->evaluatedRandomWeight(transition->randomWeight()); + totalWeight += transition->randomWeight(); + if ((static_cast<LayerStateFlags>(stateFromInstance->state()->flags()) & + LayerStateFlags::Random) != LayerStateFlags::Random) { - m_transitionCompleted = true; - fireEvents(StateMachineFireOccurance::atEnd, transition->events()); + break; } - else - { - m_transitionCompleted = false; - } - - if (m_stateFrom != m_anyStateInstance) - { - // Old state from is done. - delete m_stateFrom; - } - m_stateFrom = outState; - - // If we had an exit time and wanted to pause on exit, make - // sure to hold the exit time. Delegate this to the - // transition by telling it that it was completed. - if (outState != nullptr && transition->applyExitCondition(outState)) - { - // Make sure we apply this state. This only returns true - // when it's an animation state instance. - auto instance = - static_cast<AnimationStateInstance*>(m_stateFrom)->animationInstance(); - - m_holdAnimation = instance->animation(); - m_holdTime = instance->time(); - } - m_mixFrom = m_mix; - - // Keep mixing last animation that was mixed in. - if (m_mix != 0.0f) - { - m_holdAnimationFrom = transition->pauseOnExit(); - } - if (m_stateFrom != nullptr && m_stateFrom->state()->is<AnimationState>() && - m_currentState != nullptr) - { - auto instance = - static_cast<AnimationStateInstance*>(m_stateFrom)->animationInstance(); - - auto spilledTime = instance->spilledTime(); - m_currentState->advance(spilledTime, m_stateMachineInstance); - } - m_mix = 0.0f; - updateMix(0.0f); - m_waitingForExit = false; - return true; } - else if (allowed == AllowTransition::waitingForExit) + else { - m_waitingForExit = true; + transition->evaluatedRandomWeight(0); + if (allowed == AllowTransition::waitingForExit) + { + m_waitingForExit = true; + } } } + if (totalWeight > 0) + { + + double randomWeight = randomValue() * totalWeight * 1.0; + float currentWeight = 0; + size_t index = 0; + StateTransition* transition; + while (index < stateFrom->transitionCount()) + { + transition = stateFrom->transition(index); + auto transitionWeight = transition->evaluatedRandomWeight(); + if (currentWeight + transitionWeight > randomWeight) + { + break; + } + currentWeight += transitionWeight; + index++; + } + changeState(transition->stateTo()); + m_stateMachineChangedOnAdvance = true; + // state actually has changed + m_transition = transition; + fireEvents(StateMachineFireOccurance::atStart, transition->events()); + if (transition->duration() == 0) + { + m_transitionCompleted = true; + fireEvents(StateMachineFireOccurance::atEnd, transition->events()); + } + else + { + m_transitionCompleted = false; + } + + if (m_stateFrom != m_anyStateInstance) + { + // Old state from is done. + delete m_stateFrom; + } + m_stateFrom = outState; + + // If we had an exit time and wanted to pause on exit, make + // sure to hold the exit time. Delegate this to the + // transition by telling it that it was completed. + if (outState != nullptr && transition->applyExitCondition(outState)) + { + // Make sure we apply this state. This only returns true + // when it's an animation state instance. + auto instance = + static_cast<AnimationStateInstance*>(m_stateFrom)->animationInstance(); + + m_holdAnimation = instance->animation(); + m_holdTime = instance->time(); + } + m_mixFrom = m_mix; + + // Keep mixing last animation that was mixed in. + if (m_mix != 0.0f) + { + m_holdAnimationFrom = transition->pauseOnExit(); + } + if (m_stateFrom != nullptr && m_stateFrom->state()->is<AnimationState>() && + m_currentState != nullptr) + { + auto instance = + static_cast<AnimationStateInstance*>(m_stateFrom)->animationInstance(); + + auto spilledTime = instance->spilledTime(); + m_currentState->advance(spilledTime, m_stateMachineInstance); + } + m_mix = 0.0f; + updateMix(0.0f); + m_waitingForExit = false; + return true; + } return false; } @@ -837,4 +877,4 @@ } } } -} +} \ No newline at end of file