diff --git a/.rive_head b/.rive_head index 929a5513..58930e30 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 ce57d57b..b15b793b 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 072d10e0..3ac429eb 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 00000000..880bd35a --- /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 + +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( + static_cast::type>(lhs) & + static_cast::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 98a56935..1232b113 100644 --- a/include/rive/animation/state_machine_instance.hpp +++ b/include/rive/animation/state_machine_instance.hpp @@ -43,6 +43,7 @@ class StateMachineInstance : public Scene 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 @@ class StateMachineInstance : public Scene InstType* getNamedInput(const std::string& name) const; void notifyEventListeners(const std::vector& 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 412ac065..c021c2c5 100644 --- a/include/rive/animation/state_transition.hpp +++ b/include/rive/animation/state_transition.hpp @@ -35,6 +35,7 @@ class StateTransition : public StateTransitionBase return static_cast(flags()); } LayerState* m_StateTo = nullptr; + uint32_t m_EvaluatedRandomWeight = 1; CubicInterpolator* m_Interpolator = nullptr; std::vector m_Conditions; @@ -45,6 +46,9 @@ class StateTransition : public StateTransitionBase 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 a14df6c0..f5611383 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 @@ class LayerStateBase : public StateMachineLayerComponent 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 9b526ac0..2deeb09e 100644 --- a/include/rive/generated/animation/state_transition_base.hpp +++ b/include/rive/generated/animation/state_transition_base.hpp @@ -34,6 +34,7 @@ class StateTransitionBase : public StateMachineLayerComponent 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 @@ class StateTransitionBase : public StateMachineLayerComponent 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 @@ class StateTransitionBase : public StateMachineLayerComponent 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 @@ class StateTransitionBase : public StateMachineLayerComponent 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 @@ class StateTransitionBase : public StateMachineLayerComponent 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 @@ class StateTransitionBase : public StateMachineLayerComponent 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 a911f8cf..0b077aaa 100644 --- a/include/rive/generated/core_registry.hpp +++ b/include/rive/generated/core_registry.hpp @@ -460,6 +460,9 @@ class CoreRegistry case ListenerFireEventBase::eventIdPropertyKey: object->as()->eventId(value); break; + case LayerStateBase::flagsPropertyKey: + object->as()->flags(value); + break; case ListenerInputChangeBase::inputIdPropertyKey: object->as()->inputId(value); break; @@ -538,6 +541,9 @@ class CoreRegistry case StateTransitionBase::interpolatorIdPropertyKey: object->as()->interpolatorId(value); break; + case StateTransitionBase::randomWeightPropertyKey: + object->as()->randomWeight(value); + break; case StateMachineFireEventBase::eventIdPropertyKey: object->as()->eventId(value); break; @@ -1256,6 +1262,8 @@ class CoreRegistry return object->as()->activeComponentId(); case ListenerFireEventBase::eventIdPropertyKey: return object->as()->eventId(); + case LayerStateBase::flagsPropertyKey: + return object->as()->flags(); case ListenerInputChangeBase::inputIdPropertyKey: return object->as()->inputId(); case ListenerInputChangeBase::nestedInputIdPropertyKey: @@ -1308,6 +1316,8 @@ class CoreRegistry return object->as()->interpolationType(); case StateTransitionBase::interpolatorIdPropertyKey: return object->as()->interpolatorId(); + case StateTransitionBase::randomWeightPropertyKey: + return object->as()->randomWeight(); case StateMachineFireEventBase::eventIdPropertyKey: return object->as()->eventId(); case StateMachineFireEventBase::occursValuePropertyKey: @@ -1779,6 +1789,7 @@ class CoreRegistry 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 @@ class CoreRegistry 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 b75642a8..3617c648 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 @@ class StateMachineLayerInstance } } + 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,72 +180,104 @@ class StateMachineLayerInstance } 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(stateFromInstance->state()->flags()) & + LayerStateFlags::Random) != LayerStateFlags::Random) { - m_transitionCompleted = true; - fireEvents(StateMachineFireOccurance::atEnd, transition->events()); + break; } - else + } + else + { + transition->evaluatedRandomWeight(0); + if (allowed == AllowTransition::waitingForExit) { - m_transitionCompleted = false; + m_waitingForExit = true; } + } + } + if (totalWeight > 0) + { - if (m_stateFrom != m_anyStateInstance) + 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) { - // Old state from is done. - delete m_stateFrom; + break; } - m_stateFrom = outState; + 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 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(m_stateFrom)->animationInstance(); + if (m_stateFrom != m_anyStateInstance) + { + // Old state from is done. + delete m_stateFrom; + } + m_stateFrom = outState; - m_holdAnimation = instance->animation(); - m_holdTime = instance->time(); - } - m_mixFrom = m_mix; + // 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(m_stateFrom)->animationInstance(); - // 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() && - m_currentState != nullptr) - { - auto instance = - static_cast(m_stateFrom)->animationInstance(); + m_holdAnimation = instance->animation(); + m_holdTime = instance->time(); + } + m_mixFrom = m_mix; - auto spilledTime = instance->spilledTime(); - m_currentState->advance(spilledTime, m_stateMachineInstance); - } - m_mix = 0.0f; - updateMix(0.0f); - m_waitingForExit = false; - return true; + // Keep mixing last animation that was mixed in. + if (m_mix != 0.0f) + { + m_holdAnimationFrom = transition->pauseOnExit(); } - else if (allowed == AllowTransition::waitingForExit) + if (m_stateFrom != nullptr && m_stateFrom->state()->is() && + m_currentState != nullptr) { - m_waitingForExit = true; + auto instance = + static_cast(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 @@ void StateMachineInstance::notifyEventListeners(const std::vector& } } } -} +} \ No newline at end of file