Xxxx transitions with base virtual animation Diffs= f9b1e8ec2 Xxxx transitions with base virtual animation (#7157) Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head index a74cdfb..853c915 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -e5db5a652fedb8dd8c9708f02536e23ab12210d9 +f9b1e8ec2ff7960990f87ef855c669f6a4a86b1d
diff --git a/include/rive/animation/animation_reset.hpp b/include/rive/animation/animation_reset.hpp new file mode 100644 index 0000000..6be0c99 --- /dev/null +++ b/include/rive/animation/animation_reset.hpp
@@ -0,0 +1,32 @@ +#ifndef _RIVE_ANIMATION_RESET_HPP_ +#define _RIVE_ANIMATION_RESET_HPP_ + +#include <string> +#include "rive/artboard.hpp" +#include <rive/animation/animation_reset.hpp> +#include "rive/core/binary_writer.hpp" +#include "rive/core/vector_binary_writer.hpp" +#include "rive/core/binary_data_reader.hpp" + +namespace rive +{ + +class AnimationReset +{ +private: + VectorBinaryWriter m_binaryWriter; + BinaryDataReader m_binaryReader; + std::vector<uint8_t> m_WriteBuffer; + +public: + AnimationReset(); + void writeObjectId(uint32_t objectId); + void writeTotalProperties(uint8_t value); + void writePropertyKey(uint8_t value); + void writePropertyValue(float value); + void apply(Artboard* artboard); + void complete(); + void clear(); +}; +} // namespace rive +#endif \ No newline at end of file
diff --git a/include/rive/animation/animation_reset_factory.hpp b/include/rive/animation/animation_reset_factory.hpp new file mode 100644 index 0000000..8a4db56 --- /dev/null +++ b/include/rive/animation/animation_reset_factory.hpp
@@ -0,0 +1,40 @@ +#ifndef _RIVE_ANIMATION_RESET_FACTORY_HPP_ +#define _RIVE_ANIMATION_RESET_FACTORY_HPP_ + +#include <string> +#include <mutex> +#include "rive/animation/animation_reset.hpp" +#include "rive/animation/state_instance.hpp" +#include "rive/animation/linear_animation.hpp" +#include "rive/artboard.hpp" + +namespace rive +{ + +class AnimationResetFactory +{ + static std::vector<std::unique_ptr<AnimationReset>> m_resources; + static std::mutex m_mutex; + +private: + static void fromState(StateInstance* stateInstance, + std::vector<const LinearAnimation*>& animations); + +public: + static std::unique_ptr<AnimationReset> getInstance(); + static std::unique_ptr<AnimationReset> fromStates(StateInstance* stateFrom, + StateInstance* currentState, + ArtboardInstance* artboard); + static std::unique_ptr<AnimationReset> fromAnimations( + std::vector<const LinearAnimation*>& animations, + ArtboardInstance* artboard, + bool useFirstAsBaseline); + static void release(std::unique_ptr<AnimationReset> value); +#ifdef TESTING + // Used in testing to check pooled resources; + static int resourcesCount() { return m_resources.size(); }; + static void releaseResources() { m_resources.clear(); }; +#endif +}; +} // namespace rive +#endif \ No newline at end of file
diff --git a/include/rive/animation/animation_state_instance.hpp b/include/rive/animation/animation_state_instance.hpp index 0eb15cc..7e73343 100644 --- a/include/rive/animation/animation_state_instance.hpp +++ b/include/rive/animation/animation_state_instance.hpp
@@ -20,7 +20,7 @@ AnimationStateInstance(const AnimationState* animationState, ArtboardInstance* instance); void advance(float seconds, StateMachineInstance* stateMachineInstance) override; - void apply(float mix) override; + void apply(ArtboardInstance* instance, float mix) override; bool keepGoing() const override; void clearSpilledTime() override;
diff --git a/include/rive/animation/blend_state_1d_instance.hpp b/include/rive/animation/blend_state_1d_instance.hpp index d0c81a2..ed1df09 100644 --- a/include/rive/animation/blend_state_1d_instance.hpp +++ b/include/rive/animation/blend_state_1d_instance.hpp
@@ -4,6 +4,8 @@ #include "rive/animation/blend_state_instance.hpp" #include "rive/animation/blend_state_1d.hpp" #include "rive/animation/blend_animation_1d.hpp" +#include "rive/animation/animation_reset.hpp" +#include "rive/animation/animation_reset_factory.hpp" namespace rive { @@ -12,11 +14,14 @@ private: BlendStateAnimationInstance<BlendAnimation1D>* m_From = nullptr; BlendStateAnimationInstance<BlendAnimation1D>* m_To = nullptr; + std::unique_ptr<AnimationReset> m_AnimationReset; int animationIndex(float value); public: BlendState1DInstance(const BlendState1D* blendState, ArtboardInstance* instance); + ~BlendState1DInstance(); void advance(float seconds, StateMachineInstance* stateMachineInstance) override; + void apply(ArtboardInstance* instance, float mix) override; }; } // namespace rive #endif \ No newline at end of file
diff --git a/include/rive/animation/blend_state_instance.hpp b/include/rive/animation/blend_state_instance.hpp index fb52f12..bf34466 100644 --- a/include/rive/animation/blend_state_instance.hpp +++ b/include/rive/animation/blend_state_instance.hpp
@@ -5,8 +5,11 @@ #include <vector> #include "rive/animation/state_instance.hpp" #include "rive/animation/blend_state.hpp" +#include "rive/animation/layer_state_flags.hpp" #include "rive/animation/linear_animation_instance.hpp" #include "rive/animation/state_machine_instance.hpp" +#include "rive/animation/animation_reset.hpp" +#include "rive/animation/animation_reset_factory.hpp" namespace rive { @@ -49,6 +52,15 @@ m_AnimationInstances.emplace_back( BlendStateAnimationInstance<T>(static_cast<T*>(blendAnimation), instance)); } + if ((static_cast<LayerStateFlags>(blendState->flags()) & LayerStateFlags::Reset) == + LayerStateFlags::Reset) + { + auto animations = std::vector<const LinearAnimation*>(); + for (auto blendAnimation : blendState->animations()) + { + animations.push_back(blendAnimation->animation()); + } + } } bool keepGoing() const override { return m_KeepGoing; } @@ -71,7 +83,7 @@ } } - void apply(float mix) override + void apply(ArtboardInstance* instance, float mix) override { for (auto& animation : m_AnimationInstances) {
diff --git a/include/rive/animation/keyed_object.hpp b/include/rive/animation/keyed_object.hpp index 860e11f..3aba7b9 100644 --- a/include/rive/animation/keyed_object.hpp +++ b/include/rive/animation/keyed_object.hpp
@@ -24,6 +24,20 @@ StatusCode import(ImportStack& importStack) override; + const KeyedProperty* getProperty(size_t index) const + { + if (index < m_keyedProperties.size()) + { + return m_keyedProperties[index].get(); + } + else + { + return nullptr; + } + } + + size_t numKeyedProperties() const { return m_keyedProperties.size(); } + private: std::vector<std::unique_ptr<KeyedProperty>> m_keyedProperties; };
diff --git a/include/rive/animation/keyed_property.hpp b/include/rive/animation/keyed_property.hpp index 1ef73ce..d9d0425 100644 --- a/include/rive/animation/keyed_property.hpp +++ b/include/rive/animation/keyed_property.hpp
@@ -26,6 +26,14 @@ void apply(Core* object, float time, float mix); StatusCode import(ImportStack& importStack) override; + KeyFrame* first() const + { + if (m_keyFrames.size() > 0) + { + return m_keyFrames.front().get(); + } + return nullptr; + } private: int closestFrameIndex(float seconds, int exactOffset = 0) const;
diff --git a/include/rive/animation/layer_state_flags.hpp b/include/rive/animation/layer_state_flags.hpp index 880bd35..c9d6134 100644 --- a/include/rive/animation/layer_state_flags.hpp +++ b/include/rive/animation/layer_state_flags.hpp
@@ -12,6 +12,8 @@ /// Whether the transition is disabled. Random = 1 << 0, + /// Whether the blend should include an instance to reset values on apply + Reset = 1 << 1, }; inline constexpr LayerStateFlags operator&(LayerStateFlags lhs, LayerStateFlags rhs)
diff --git a/include/rive/animation/linear_animation.hpp b/include/rive/animation/linear_animation.hpp index f7aa60e..01f9205 100644 --- a/include/rive/animation/linear_animation.hpp +++ b/include/rive/animation/linear_animation.hpp
@@ -42,8 +42,21 @@ /// work area start/end, speed, looping). float globalToLocalSeconds(float seconds) const; + const KeyedObject* getObject(size_t index) const + { + if (index < m_KeyedObjects.size()) + { + return m_KeyedObjects[index].get(); + } + else + { + return nullptr; + } + } + + size_t numKeyedObjects() const { return m_KeyedObjects.size(); } + #ifdef TESTING - size_t numKeyedObjects() { return m_KeyedObjects.size(); } // Used in testing to check how many animations gets deleted. static int deleteCount; #endif
diff --git a/include/rive/animation/state_instance.hpp b/include/rive/animation/state_instance.hpp index 401b9f0..1dffd9d 100644 --- a/include/rive/animation/state_instance.hpp +++ b/include/rive/animation/state_instance.hpp
@@ -22,7 +22,7 @@ StateInstance(const LayerState* layerState); virtual ~StateInstance(); virtual void advance(float seconds, StateMachineInstance* stateMachineInstance) = 0; - virtual void apply(float mix) = 0; + virtual void apply(ArtboardInstance* instance, float mix) = 0; /// Returns true when the State Machine needs to keep advancing this /// state.
diff --git a/include/rive/animation/system_state_instance.hpp b/include/rive/animation/system_state_instance.hpp index b439da8..49f64b0 100644 --- a/include/rive/animation/system_state_instance.hpp +++ b/include/rive/animation/system_state_instance.hpp
@@ -17,7 +17,7 @@ SystemStateInstance(const LayerState* layerState, ArtboardInstance* instance); void advance(float seconds, StateMachineInstance* stateMachineInstance) override; - void apply(float mix) override; + void apply(ArtboardInstance* artboard, float mix) override; bool keepGoing() const override; };
diff --git a/include/rive/core/binary_data_reader.hpp b/include/rive/core/binary_data_reader.hpp new file mode 100644 index 0000000..beae449 --- /dev/null +++ b/include/rive/core/binary_data_reader.hpp
@@ -0,0 +1,38 @@ +#ifndef _RIVE_CORE_BINARY_DATA_READER_HPP_ +#define _RIVE_CORE_BINARY_DATA_READER_HPP_ + +#include <string> +#include <stdint.h> + +namespace rive +{ +class BinaryDataReader +{ +private: + uint8_t* m_Position; + uint8_t* m_End; + bool m_Overflowed; + size_t m_Length; + + void overflow(); + +public: + BinaryDataReader(uint8_t* bytes, size_t length); + bool didOverflow() const; + bool isEOF() const { return m_Position >= m_End; } + const uint8_t* position() const { return m_Position; } + + size_t lengthInBytes() const; + + uint64_t readVarUint(); + uint32_t readVarUint32(); + double readFloat64(); + float readFloat32(); + uint8_t readByte(); + uint32_t readUint32(); + void complete(uint8_t* bytes, size_t length); + void reset(uint8_t* bytes); +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/core/binary_reader.hpp b/include/rive/core/binary_reader.hpp index c0c2c3b..00687da 100644 --- a/include/rive/core/binary_reader.hpp +++ b/include/rive/core/binary_reader.hpp
@@ -49,6 +49,7 @@ } return static_cast<T>(value); } + void reset(); }; } // namespace rive
diff --git a/include/rive/core/binary_stream.hpp b/include/rive/core/binary_stream.hpp new file mode 100644 index 0000000..f582ccf --- /dev/null +++ b/include/rive/core/binary_stream.hpp
@@ -0,0 +1,20 @@ +#ifndef _RIVE_CORE_BINARY_STREAM_HPP_ +#define _RIVE_CORE_BINARY_STREAM_HPP_ + +#include <cstdint> +#include <cstddef> + +namespace rive +{ +// Used to write binary chunks to an underlying stream, makes no assumptions +// regarding storage/streaming it can flush the contents as it needs. +class BinaryStream +{ +public: + virtual void write(const uint8_t* bytes, std::size_t length) = 0; + virtual void flush() = 0; + virtual void clear() = 0; +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/core/binary_writer.hpp b/include/rive/core/binary_writer.hpp new file mode 100644 index 0000000..213e295 --- /dev/null +++ b/include/rive/core/binary_writer.hpp
@@ -0,0 +1,31 @@ + +#ifndef _RIVE_CORE_BINARY_WRITER_HPP_ +#define _RIVE_CORE_BINARY_WRITER_HPP_ + +#include <cstddef> +#include <cstdint> + +namespace rive +{ +class BinaryStream; +class BinaryWriter +{ +private: + BinaryStream* m_Stream; + +public: + BinaryWriter(BinaryStream* stream); + ~BinaryWriter(); + void write(float value); + void writeFloat(float value); + void write(double value); + void writeVarUint(uint64_t value); + void writeVarUint(uint32_t value); + void write(const uint8_t* bytes, std::size_t length); + void write(uint8_t value); + void writeDouble(double value); + void clear(); +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/core/reader.h b/include/rive/core/reader.h index f6969ba..45c8ffd 100644 --- a/include/rive/core/reader.h +++ b/include/rive/core/reader.h
@@ -35,6 +35,29 @@ return p - buf; } +/* Decode an unsigned int LEB128 at buf into r, returning the nr of bytes read. + */ +inline size_t decode_uint_leb32(const uint8_t* buf, const uint8_t* buf_end, uint32_t* r) +{ + const uint8_t* p = buf; + uint8_t shift = 0; + uint32_t result = 0; + uint8_t byte; + + do + { + if (p >= buf_end) + { + return 0; + } + byte = *p++; + result |= ((uint32_t)(byte & 0x7f)) << shift; + shift += 7; + } while ((byte & 0x80) != 0); + *r = result; + return p - buf; +} + /* Decodes a string */ inline uint64_t decode_string(uint64_t str_len, @@ -57,6 +80,27 @@ return str_len; } +/* Decodes a double (8 bytes) + */ +inline size_t decode_double(const uint8_t* buf, const uint8_t* buf_end, double* r) +{ + // Return zero bytes read on buffer overflow + if (buf_end - buf < sizeof(double)) + { + return 0; + } + if (is_big_endian()) + { + uint8_t inverted[8] = {buf[7], buf[6], buf[5], buf[4], buf[3], buf[2], buf[1], buf[0]}; + memcpy(r, inverted, sizeof(double)); + } + else + { + memcpy(r, buf, sizeof(double)); + } + return sizeof(double); +} + /* Decodes a float (4 bytes) */ inline size_t decode_float(const uint8_t* buf, const uint8_t* buf_end, float* r) @@ -110,4 +154,4 @@ memcpy(r, buf, sizeof(uint32_t)); } return sizeof(uint32_t); -} +} \ No newline at end of file
diff --git a/include/rive/core/vector_binary_writer.hpp b/include/rive/core/vector_binary_writer.hpp new file mode 100644 index 0000000..34ba649 --- /dev/null +++ b/include/rive/core/vector_binary_writer.hpp
@@ -0,0 +1,43 @@ +#ifndef _RIVE_CORE_VECTOR_BINARY_WRITER_HPP_ +#define _RIVE_CORE_VECTOR_BINARY_WRITER_HPP_ + +#include "rive/core/binary_stream.hpp" +#include "rive/core/binary_writer.hpp" +#include <cstring> + +namespace rive +{ +class VectorBinaryWriter : public BinaryStream, public BinaryWriter +{ +private: + std::vector<uint8_t>* m_WriteBuffer; + std::size_t m_Start; + size_t m_pos = 0; + +public: + VectorBinaryWriter(std::vector<uint8_t>* buffer) : + BinaryWriter(this), m_WriteBuffer(buffer), m_Start(m_WriteBuffer->size()) + {} + + uint8_t* buffer() const { return &(*m_WriteBuffer)[m_Start]; } + std::size_t bufferSize() const { return m_WriteBuffer->size() - m_Start; } + + std::size_t start() const { return m_Start; } + size_t size() const { return m_pos; } + + using BinaryWriter::write; + void write(const uint8_t* bytes, std::size_t length) override + { + auto end = m_pos; + if (m_WriteBuffer->size() < end + length) + { + m_WriteBuffer->resize(end + length); + } + std::memcpy(&((*m_WriteBuffer)[end]), bytes, length); + m_pos += length; + } + void flush() override {} + void clear() override { m_pos = 0; } +}; +} // namespace rive +#endif \ No newline at end of file
diff --git a/src/animation/animation_reset.cpp b/src/animation/animation_reset.cpp new file mode 100644 index 0000000..87b906a --- /dev/null +++ b/src/animation/animation_reset.cpp
@@ -0,0 +1,49 @@ +#include "rive/animation/animation_reset.hpp" +#include "rive/core/vector_binary_writer.hpp" +#include "rive/generated/core_registry.hpp" + +using namespace rive; + +AnimationReset::AnimationReset() : m_binaryWriter(&m_WriteBuffer), m_binaryReader(nullptr, 0) {} + +void AnimationReset::writeObjectId(uint32_t objectId) { m_binaryWriter.writeVarUint(objectId); } + +void AnimationReset::writeTotalProperties(uint8_t value) { m_binaryWriter.write(value); } + +void AnimationReset::writePropertyKey(uint8_t value) { m_binaryWriter.write(value); } + +void AnimationReset::writePropertyValue(float value) { m_binaryWriter.writeFloat(value); } + +void AnimationReset::clear() { m_binaryWriter.clear(); } + +void AnimationReset::complete() +{ + m_binaryReader.complete(&m_WriteBuffer.front(), m_binaryWriter.size()); +} + +void AnimationReset::apply(Artboard* artboard) +{ + m_binaryReader.reset(&m_WriteBuffer.front()); + while (!m_binaryReader.isEOF()) + { + auto objectId = m_binaryReader.readVarUint32(); + auto object = artboard->resolve(objectId); + auto totalProperties = m_binaryReader.readByte(); + auto currentPropertyIndex = 0; + while (currentPropertyIndex < totalProperties) + { + auto propertyKey = m_binaryReader.readByte(); + auto propertyValue = m_binaryReader.readFloat32(); + switch (CoreRegistry::propertyFieldId(propertyKey)) + { + case CoreDoubleType::id: + CoreRegistry::setDouble(object, propertyKey, propertyValue); + break; + case CoreColorType::id: + CoreRegistry::setColor(object, propertyKey, propertyValue); + break; + } + currentPropertyIndex++; + } + } +}
diff --git a/src/animation/animation_reset_factory.cpp b/src/animation/animation_reset_factory.cpp new file mode 100644 index 0000000..a08f35f --- /dev/null +++ b/src/animation/animation_reset_factory.cpp
@@ -0,0 +1,210 @@ +#include "rive/animation/animation_reset_factory.hpp" +#include "rive/animation/linear_animation.hpp" +#include "rive/animation/animation_state.hpp" +#include "rive/animation/keyed_object.hpp" +#include "rive/animation/keyed_property.hpp" +#include "rive/generated/core_registry.hpp" +#include <map> +#include <set> + +using namespace rive; + +class KeyedPropertyData +{ +public: + const KeyedProperty* keyedProperty; + bool isBaseline; + KeyedPropertyData(const KeyedProperty* value, bool baselineValue) : + keyedProperty(value), isBaseline(baselineValue) + {} +}; + +class KeyedObjectData +{ +public: + std::vector<KeyedPropertyData> keyedPropertiesData; + std::set<int> keyedPropertiesSet; + uint32_t objectId; + KeyedObjectData(const uint32_t value) { objectId = value; } + void addProperties(const KeyedObject* keyedObject, bool isBaseline) + { + size_t index = 0; + while (index < keyedObject->numKeyedProperties()) + { + auto keyedProperty = keyedObject->getProperty(index); + auto pos = keyedPropertiesSet.find(keyedProperty->propertyKey()); + if (pos == keyedPropertiesSet.end()) + { + keyedPropertiesSet.insert(keyedProperty->propertyKey()); + keyedPropertiesData.push_back(KeyedPropertyData(keyedProperty, isBaseline)); + } + index++; + } + } +}; + +class AnimationsData +{ + +private: + std::vector<std::unique_ptr<KeyedObjectData>> keyedObjectsData; + KeyedObjectData* getKeyedObjectData(const KeyedObject* keyedObject) + { + for (auto& keyedObjectData : keyedObjectsData) + { + if (keyedObjectData->objectId == keyedObject->objectId()) + { + return keyedObjectData.get(); + } + } + + auto keyedObjectData = rivestd::make_unique<KeyedObjectData>(keyedObject->objectId()); + auto ref = keyedObjectData.get(); + keyedObjectsData.push_back(std::move(keyedObjectData)); + return ref; + } + + void findKeyedObjects(const LinearAnimation* animation, bool isFirstAnimation) + { + + size_t index = 0; + while (index < animation->numKeyedObjects()) + { + auto keyedObject = animation->getObject(index); + auto keyedObjectData = getKeyedObjectData(keyedObject); + + keyedObjectData->addProperties(keyedObject, isFirstAnimation); + index++; + } + } + +public: + AnimationsData(std::vector<const LinearAnimation*>& animations, bool useFirstAsBaseline) + { + bool isFirstAnimation = useFirstAsBaseline; + for (auto animation : animations) + { + findKeyedObjects(animation, isFirstAnimation); + isFirstAnimation = false; + } + } + + void writeObjects(AnimationReset* animationReset, ArtboardInstance* artboard) + { + for (auto& keyedObjectData : keyedObjectsData) + { + auto object = artboard->resolve(keyedObjectData->objectId)->as<Component>(); + auto propertiesData = keyedObjectData->keyedPropertiesData; + if (propertiesData.size() > 0) + { + animationReset->writeObjectId(keyedObjectData->objectId); + animationReset->writeTotalProperties(propertiesData.size()); + for (auto keyedPropertyData : propertiesData) + { + auto keyedProperty = keyedPropertyData.keyedProperty; + auto propertyKey = keyedProperty->propertyKey(); + switch (CoreRegistry::propertyFieldId(propertyKey)) + { + case CoreDoubleType::id: + animationReset->writePropertyKey(propertyKey); + if (keyedPropertyData.isBaseline) + { + auto firstKeyframe = keyedProperty->first(); + if (firstKeyframe != nullptr) + { + auto value = + keyedProperty->first()->as<KeyFrameDouble>()->value(); + animationReset->writePropertyValue(value); + } + } + else + { + animationReset->writePropertyValue( + CoreRegistry::getDouble(object, propertyKey)); + } + break; + case CoreColorType::id: + + animationReset->writePropertyKey(propertyKey); + if (keyedPropertyData.isBaseline) + { + auto firstKeyframe = keyedProperty->first(); + if (firstKeyframe != nullptr) + { + auto value = + keyedProperty->first()->as<KeyFrameColor>()->value(); + animationReset->writePropertyValue(value); + } + } + else + { + animationReset->writePropertyValue( + CoreRegistry::getColor(object, propertyKey)); + } + break; + } + } + } + } + animationReset->complete(); + } +}; + +std::unique_ptr<AnimationReset> AnimationResetFactory::getInstance() +{ + std::unique_lock<std::mutex> lock(m_mutex); + if (m_resources.size() > 0) + { + auto instance = std::move(m_resources.back()); + m_resources.pop_back(); + return instance; + } + auto instance = rivestd::make_unique<AnimationReset>(); + return instance; +} + +void AnimationResetFactory::fromState(StateInstance* stateInstance, + std::vector<const LinearAnimation*>& animations) +{ + if (stateInstance != nullptr) + { + auto state = stateInstance->state(); + if (state->is<AnimationState>()) + { + animations.push_back(state->as<AnimationState>()->animation()); + } + } +} + +std::unique_ptr<AnimationReset> AnimationResetFactory::fromStates(StateInstance* stateFrom, + StateInstance* currentState, + ArtboardInstance* artboard) +{ + std::vector<const LinearAnimation*> animations; + fromState(stateFrom, animations); + fromState(currentState, animations); + return fromAnimations(animations, artboard, false); +} + +std::unique_ptr<AnimationReset> AnimationResetFactory::fromAnimations( + std::vector<const LinearAnimation*>& animations, + ArtboardInstance* artboard, + bool useFirstAsBaseline) +{ + auto animationsData = new AnimationsData(animations, useFirstAsBaseline); + auto animationReset = AnimationResetFactory::getInstance(); + animationsData->writeObjects(animationReset.get(), artboard); + delete animationsData; + return animationReset; +} + +std::vector<std::unique_ptr<AnimationReset>> AnimationResetFactory::m_resources; + +std::mutex AnimationResetFactory::m_mutex; + +void AnimationResetFactory::release(std::unique_ptr<AnimationReset> value) +{ + std::unique_lock<std::mutex> lock(m_mutex); + value->clear(); + m_resources.push_back(std::move(value)); +}
diff --git a/src/animation/animation_state_instance.cpp b/src/animation/animation_state_instance.cpp index 0739550..5cae3e8 100644 --- a/src/animation/animation_state_instance.cpp +++ b/src/animation/animation_state_instance.cpp
@@ -30,7 +30,10 @@ stateMachineInstance); } -void AnimationStateInstance::apply(float mix) { m_AnimationInstance.apply(mix); } +void AnimationStateInstance::apply(ArtboardInstance* instance, float mix) +{ + m_AnimationInstance.apply(mix); +} bool AnimationStateInstance::keepGoing() const { return m_KeepGoing; } void AnimationStateInstance::clearSpilledTime() { m_AnimationInstance.clearSpilledTime(); } \ No newline at end of file
diff --git a/src/animation/blend_state_1d_instance.cpp b/src/animation/blend_state_1d_instance.cpp index 6902273..f1049ba 100644 --- a/src/animation/blend_state_1d_instance.cpp +++ b/src/animation/blend_state_1d_instance.cpp
@@ -1,12 +1,33 @@ #include "rive/animation/blend_state_1d_instance.hpp" #include "rive/animation/state_machine_input_instance.hpp" +#include "rive/animation/layer_state_flags.hpp" using namespace rive; BlendState1DInstance::BlendState1DInstance(const BlendState1D* blendState, ArtboardInstance* instance) : BlendStateInstance<BlendState1D, BlendAnimation1D>(blendState, instance) -{} +{ + + if ((static_cast<LayerStateFlags>(blendState->flags()) & LayerStateFlags::Reset) == + LayerStateFlags::Reset) + { + auto animations = std::vector<const LinearAnimation*>(); + for (auto blendAnimation : blendState->animations()) + { + animations.push_back(blendAnimation->animation()); + } + m_AnimationReset = AnimationResetFactory::fromAnimations(animations, instance, true); + } +} + +BlendState1DInstance::~BlendState1DInstance() +{ + if (m_AnimationReset != nullptr) + { + AnimationResetFactory::release(std::move(m_AnimationReset)); + } +} int BlendState1DInstance::animationIndex(float value) { @@ -39,6 +60,15 @@ return idx; } +void BlendState1DInstance::apply(ArtboardInstance* instance, float mix) +{ + if (m_AnimationReset != nullptr) + { + m_AnimationReset->apply(instance); + } + BlendStateInstance::apply(instance, mix); +} + void BlendState1DInstance::advance(float seconds, StateMachineInstance* stateMachineInstance) { BlendStateInstance<BlendState1D, BlendAnimation1D>::advance(seconds, stateMachineInstance);
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp index e70d4f4..51f01f1 100644 --- a/src/animation/state_machine_instance.cpp +++ b/src/animation/state_machine_instance.cpp
@@ -1,3 +1,5 @@ +#include "rive/animation/animation_reset.hpp" +#include "rive/animation/animation_reset_factory.hpp" #include "rive/animation/animation_state_instance.hpp" #include "rive/animation/animation_state.hpp" #include "rive/animation/any_state.hpp" @@ -70,6 +72,7 @@ if (m_mix == 1.0f && !m_transitionCompleted) { m_transitionCompleted = true; + clearAnimationReset(); fireEvents(StateMachineFireOccurance::atEnd, m_transition->events()); } } @@ -256,6 +259,21 @@ return nullptr; } + void buildAnimationResetForTransition() + { + m_animationReset = + AnimationResetFactory::fromStates(m_stateFrom, m_currentState, m_artboardInstance); + } + + void clearAnimationReset() + { + if (m_animationReset != nullptr) + { + AnimationResetFactory::release(std::move(m_animationReset)); + m_animationReset = nullptr; + } + } + bool tryChangeState(StateInstance* stateFromInstance, bool ignoreTriggers) { if (stateFromInstance == nullptr) @@ -266,6 +284,7 @@ auto transition = findAllowedTransition(stateFromInstance, ignoreTriggers); if (transition != nullptr) { + clearAnimationReset(); changeState(transition->stateTo()); m_stateMachineChangedOnAdvance = true; // state actually has changed @@ -288,6 +307,11 @@ } m_stateFrom = outState; + if (!m_transitionCompleted) + { + buildAnimationResetForTransition(); + } + // 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. @@ -327,6 +351,10 @@ void apply(/*Artboard* artboard*/) { + if (m_animationReset != nullptr) + { + m_animationReset->apply(m_artboardInstance); + } if (m_holdAnimation != nullptr) { m_holdAnimation->apply(m_artboardInstance, m_holdTime, m_mixFrom); @@ -342,12 +370,12 @@ if (m_stateFrom != nullptr && m_mix < 1.0f) { auto fromMix = cubic != nullptr ? cubic->transform(m_mixFrom) : m_mixFrom; - m_stateFrom->apply(fromMix); + m_stateFrom->apply(m_artboardInstance, fromMix); } if (m_currentState != nullptr) { auto mix = cubic != nullptr ? cubic->transform(m_mix) : m_mix; - m_currentState->apply(mix); + m_currentState->apply(m_artboardInstance, mix); } } @@ -378,6 +406,7 @@ StateInstance* m_stateFrom = nullptr; const StateTransition* m_transition = nullptr; + std::unique_ptr<AnimationReset> m_animationReset = nullptr; bool m_transitionCompleted = false; bool m_holdAnimationFrom = false;
diff --git a/src/animation/system_state_instance.cpp b/src/animation/system_state_instance.cpp index 92e28de..4eb489a 100644 --- a/src/animation/system_state_instance.cpp +++ b/src/animation/system_state_instance.cpp
@@ -6,6 +6,6 @@ {} void SystemStateInstance::advance(float seconds, StateMachineInstance* stateMachineInstance) {} -void SystemStateInstance::apply(float mix) {} +void SystemStateInstance::apply(ArtboardInstance* artboard, float mix) {} bool SystemStateInstance::keepGoing() const { return false; } \ No newline at end of file
diff --git a/src/core/binary_data_reader.cpp b/src/core/binary_data_reader.cpp new file mode 100644 index 0000000..1fa7f33 --- /dev/null +++ b/src/core/binary_data_reader.cpp
@@ -0,0 +1,102 @@ +#include "rive/core/binary_data_reader.hpp" +#include "rive/core/reader.h" + +using namespace rive; + +BinaryDataReader::BinaryDataReader(uint8_t* bytes, size_t length) : + m_Position(bytes), m_End(bytes + length), m_Overflowed(false), m_Length(length) +{} + +size_t BinaryDataReader::lengthInBytes() const { return m_Length; } + +bool BinaryDataReader::didOverflow() const { return m_Overflowed; } + +void BinaryDataReader::overflow() +{ + m_Overflowed = true; + m_Position = m_End; +} + +uint64_t BinaryDataReader::readVarUint() +{ + uint64_t value; + auto readBytes = decode_uint_leb(m_Position, m_End, &value); + if (readBytes == 0) + { + overflow(); + return 0; + } + m_Position += readBytes; + return value; +} + +uint32_t BinaryDataReader::readVarUint32() +{ + uint32_t value; + auto readBytes = decode_uint_leb32(m_Position, m_End, &value); + if (readBytes == 0) + { + overflow(); + return 0; + } + m_Position += readBytes; + return value; +} + +double BinaryDataReader::readFloat64() +{ + double value; + auto readBytes = decode_double(m_Position, m_End, &value); + if (readBytes == 0) + { + overflow(); + return 0.0; + } + m_Position += readBytes; + return value; +} + +float BinaryDataReader::readFloat32() +{ + float value; + auto readBytes = decode_float(m_Position, m_End, &value); + if (readBytes == 0) + { + overflow(); + return 0.0f; + } + m_Position += readBytes; + return value; +} + +uint8_t BinaryDataReader::readByte() +{ + if (m_End - m_Position < 1) + { + overflow(); + return 0; + } + return *m_Position++; +} + +uint32_t BinaryDataReader::readUint32() +{ + uint32_t value; + auto readBytes = decode_uint_32(m_Position, m_End, &value); + if (readBytes == 0) + { + overflow(); + return 0; + } + m_Position += readBytes; + return value; +} + +void BinaryDataReader::complete(uint8_t* bytes, size_t length) +{ + m_Position = bytes; + m_End = bytes + length; + m_Length = length; +} + +void BinaryDataReader::reset(uint8_t* bytes) { m_Position = bytes; }
diff --git a/src/core/binary_reader.cpp b/src/core/binary_reader.cpp index d00413f..ee3ca01 100644 --- a/src/core/binary_reader.cpp +++ b/src/core/binary_reader.cpp
@@ -113,3 +113,5 @@ m_Position += readBytes; return value; } + +void BinaryReader::reset() { m_Position = m_Bytes.begin(); }
diff --git a/src/core/binary_writer.cpp b/src/core/binary_writer.cpp new file mode 100644 index 0000000..ef0fd68 --- /dev/null +++ b/src/core/binary_writer.cpp
@@ -0,0 +1,120 @@ +#include "rive/core/binary_writer.hpp" +#include "rive/core/binary_stream.hpp" +#include "rive/core/reader.h" +#include <stdio.h> + +using namespace rive; + +BinaryWriter::BinaryWriter(BinaryStream* stream) : m_Stream(stream) {} +BinaryWriter::~BinaryWriter() { m_Stream->flush(); } + +void BinaryWriter::write(float value) +{ + auto bytes = reinterpret_cast<uint8_t*>(&value); + if (is_big_endian()) + { + uint8_t backwards[4] = {bytes[3], bytes[2], bytes[1], bytes[0]}; + m_Stream->write(backwards, 4); + } + else + { + m_Stream->write(bytes, 4); + } +} + +void BinaryWriter::writeFloat(float value) +{ + auto bytes = reinterpret_cast<uint8_t*>(&value); + if (is_big_endian()) + { + uint8_t backwards[4] = {bytes[3], bytes[2], bytes[1], bytes[0]}; + m_Stream->write(backwards, 4); + } + else + { + m_Stream->write(bytes, 4); + } +} + +void BinaryWriter::write(double value) +{ + auto bytes = reinterpret_cast<uint8_t*>(&value); + if (is_big_endian()) + { + uint8_t backwards[8] = + {bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]}; + m_Stream->write(backwards, 8); + } + else + { + m_Stream->write(bytes, 8); + } +} + +void BinaryWriter::writeVarUint(uint64_t value) +{ + uint8_t buffer[16]; + int index = 0; + do + { + uint8_t byte = value & 0x7f; + value >>= 7; + + if (value != 0) + { + // more bytes follow + byte |= 0x80; + } + + buffer[index++] = byte; + } while (value != 0); + m_Stream->write(buffer, index); +} + +void BinaryWriter::writeVarUint(uint32_t value) +{ + uint8_t buffer[16]; + int index = 0; + do + { + uint8_t byte = value & 0x7f; + value >>= 7; + + if (value != 0) + { + // more bytes follow + byte |= 0x80; + } + + buffer[index++] = byte; + } while (value != 0); + m_Stream->write(buffer, index); +} + +void BinaryWriter::write(const uint8_t* bytes, size_t length) +{ + if (length == 0) + { + return; + } + m_Stream->write(bytes, length); +} + +void BinaryWriter::write(uint8_t value) { m_Stream->write(&value, 1); } + +void BinaryWriter::writeDouble(double value) +{ + auto bytes = reinterpret_cast<uint8_t*>(&value); + if (is_big_endian()) + { + uint8_t backwards[8] = + {bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]}; + m_Stream->write(backwards, 8); + } + else + { + m_Stream->write(bytes, 8); + } +} + +void BinaryWriter::clear() { m_Stream->clear(); } \ No newline at end of file
diff --git a/test/assets/animation_reset_cases.riv b/test/assets/animation_reset_cases.riv new file mode 100644 index 0000000..09e544f --- /dev/null +++ b/test/assets/animation_reset_cases.riv Binary files differ
diff --git a/test/state_machine_test.cpp b/test/state_machine_test.cpp index e3dd9cf..6f25ae8 100644 --- a/test/state_machine_test.cpp +++ b/test/state_machine_test.cpp
@@ -10,6 +10,7 @@ #include <rive/animation/blend_animation_1d.hpp> #include <rive/animation/blend_state_direct.hpp> #include <rive/animation/blend_state_transition.hpp> +#include <rive/animation/animation_reset_factory.hpp> #include <rive/shapes/paint/solid_color.hpp> #include <rive/shapes/paint/stroke.hpp> #include <rive/shapes/shape.hpp> @@ -242,3 +243,174 @@ delete stateMachineInstance; } + +TEST_CASE("Blend state animations with reset applied to them.", "[file]") +{ + auto file = ReadRiveFile("../../test/assets/animation_reset_cases.riv"); + + auto artboard = file->artboard(); + auto stateMachine = artboard->stateMachine("blend-states-state-machine"); + + // We empty all factory reset resources to start the test clean + rive::AnimationResetFactory::releaseResources(); + REQUIRE(rive::AnimationResetFactory::resourcesCount() == 0); + + REQUIRE(artboard != nullptr); + REQUIRE(artboard->animationCount() == 9); + REQUIRE(artboard->stateMachineCount() == 1); + + auto abi = artboard->instance(); + rive::StateMachineInstance* stateMachineInstance = + new rive::StateMachineInstance(stateMachine, abi.get()); + stateMachineInstance->advanceAndApply(0.1f); + abi->advance(0.1f); + + auto blendValueNumber = stateMachineInstance->getNumber("blend-value"); + REQUIRE(blendValueNumber != nullptr); + REQUIRE(blendValueNumber->value() == 0); + blendValueNumber->value(50); + REQUIRE(blendValueNumber->value() == 50); + stateMachineInstance->advanceAndApply(0.1f); + abi->advance(0.1f); + + auto rect1 = abi->children()[9]->as<rive::Shape>(); + REQUIRE(rect1->name() == "rect1"); + + auto rect2 = abi->children()[7]->as<rive::Shape>(); + REQUIRE(rect2->name() == "rect2"); + + auto triangle = abi->children()[5]->as<rive::Shape>(); + REQUIRE(triangle->name() == "triangle"); + + // This blend rotates 2 * Pi. At 50% it should have rotated 1 * Pi + REQUIRE(rect1->rotation() == Approx(3.141592f)); + + auto state2Bool = stateMachineInstance->getBool("state-2"); + REQUIRE(state2Bool != nullptr); + REQUIRE(state2Bool->value() == false); + state2Bool->value(true); + blendValueNumber->value(50); + stateMachineInstance->advanceAndApply(0.1f); + abi->advance(0.1f); + stateMachineInstance->advanceAndApply(0.1f); + REQUIRE(rive::AnimationResetFactory::resourcesCount() == 0); + REQUIRE(state2Bool->value() == true); + // X and Y interpolation in this state ranges are [75; 425] and [50; 450] + // so at 50% they should be at 250, 250 + REQUIRE(rect1->x() == 250.0f); + REQUIRE(rect1->y() == 250.0f); + // rect2 rotation range is [0; -360] + // so at 50% it should be at -180 + REQUIRE(rect2->rotation() == Approx(-3.141592f)); + + auto state3Bool = stateMachineInstance->getBool("state-3"); + REQUIRE(state3Bool != nullptr); + REQUIRE(state3Bool->value() == false); + state3Bool->value(true); + REQUIRE(rive::AnimationResetFactory::resourcesCount() == 0); + stateMachineInstance->advanceAndApply(0.1f); + abi->advance(0.1f); + stateMachineInstance->advanceAndApply(0.1f); + blendValueNumber->value(100); + abi->advance(0.1f); + stateMachineInstance->advanceAndApply(0.1f); + REQUIRE(state3Bool->value() == true); + REQUIRE(triangle->y() == Approx(43.13281f)); + + REQUIRE(rive::AnimationResetFactory::resourcesCount() == 1); + + auto state4Bool = stateMachineInstance->getBool("state-4"); + REQUIRE(state4Bool != nullptr); + REQUIRE(state4Bool->value() == false); + state4Bool->value(true); + stateMachineInstance->advanceAndApply(0.1f); + abi->advance(0.1f); + REQUIRE(state4Bool->value() == true); + REQUIRE(rive::AnimationResetFactory::resourcesCount() == 2); + + state4Bool->value(false); + stateMachineInstance->advanceAndApply(0.1f); + abi->advance(0.1f); + stateMachineInstance->advanceAndApply(0.1f); + REQUIRE(state4Bool->value() == false); + // After switching states mutiple times resources stay at 2 because they are released + // and retrieved from the pool + REQUIRE(rive::AnimationResetFactory::resourcesCount() == 2); + delete stateMachineInstance; +} + +TEST_CASE("Transitions with reset applied to them.", "[file]") +{ + auto file = ReadRiveFile("../../test/assets/animation_reset_cases.riv"); + + auto artboard = file->artboard("transitions"); + auto stateMachine = artboard->stateMachine("transitions-state-machine"); + + // We empty all factory reset resources to start the test clean + rive::AnimationResetFactory::releaseResources(); + REQUIRE(rive::AnimationResetFactory::resourcesCount() == 0); + + REQUIRE(artboard != nullptr); + REQUIRE(artboard->animationCount() == 5); + REQUIRE(artboard->stateMachineCount() == 1); + + auto abi = artboard->instance(); + rive::StateMachineInstance* stateMachineInstance = + new rive::StateMachineInstance(stateMachine, abi.get()); + stateMachineInstance->advanceAndApply(0.1f); + abi->advance(0.1f); + + auto rect = abi->children()[7]->as<rive::Shape>(); + REQUIRE(rect->name() == "rectangle"); + REQUIRE(rect->x() == 50); + + auto ellipse = abi->children()[5]->as<rive::Shape>(); + REQUIRE(ellipse->name() == "ellipse"); + REQUIRE(ellipse->x() == Approx(440.31241)); + + auto stateNumber = stateMachineInstance->getNumber("Number 1"); + REQUIRE(stateNumber != nullptr); + REQUIRE(stateNumber->value() == 0); + stateNumber->value(1); + REQUIRE(stateNumber->value() == 1); + stateMachineInstance->advanceAndApply(0.1f); + abi->advance(0.1f); + stateMachineInstance->advanceAndApply(1.25f); + + // rect transitions in 2.5 secs from x->50 to x->433 + // so if the translation is linear, after 1.25s it should have + // traversed half the path + REQUIRE(rect->x() == 241.5f); + + stateNumber->value(2); + REQUIRE(stateNumber->value() == 2); + stateMachineInstance->advanceAndApply(0.1f); + abi->advance(0.1f); + stateMachineInstance->advanceAndApply(1.25f); + // range is [440.31241; 42.69] + // half if the path is 42.69 + (440.21241 - 42.60) = 241.4962 + REQUIRE(ellipse->x() == Approx(241.49992f)); + + // Transitions release their instance immediately so it's available for the next instance to use + REQUIRE(rive::AnimationResetFactory::resourcesCount() == 0); + + stateNumber->value(3); + REQUIRE(stateNumber->value() == 3); + stateMachineInstance->advanceAndApply(0.1f); + abi->advance(0.1f); + stateMachineInstance->advanceAndApply(1.25f); + + REQUIRE(rive::AnimationResetFactory::resourcesCount() == 0); + + stateNumber->value(4); + REQUIRE(stateNumber->value() == 4); + stateMachineInstance->advanceAndApply(0.1f); + abi->advance(0.1f); + stateMachineInstance->advanceAndApply(1.25f); + + // The last two states don't have a transition with duration set so the instance is released + // and available + REQUIRE(rive::AnimationResetFactory::resourcesCount() == 1); + + delete stateMachineInstance; +}