Value Graph Export + Runtime Support!! - Renamed CubicInterpolator to CubicEaseInterpolator so that CubicInterpolator can now be the base class for both CubicEaseInterpolator and CubicValueInterpolator. - Added CubicValueInterpolator to cpp and Flutter runtimes. - Test in cpp runtime for the new cubic value interpolation. Diffs= 1e80ad08f Value Graph Export + Runtime Support!! (#4524)
diff --git a/.rive_head b/.rive_head index 3f05dc3..f5c51d2 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -c532f865810d80481428a13447a775c06dcdd507 +1e80ad08ff12ac794ad0abe65022ba0d10df0c5b
diff --git a/dev/defs/animation/cubic_ease_interpolator.json b/dev/defs/animation/cubic_ease_interpolator.json new file mode 100644 index 0000000..4ffa30b --- /dev/null +++ b/dev/defs/animation/cubic_ease_interpolator.json
@@ -0,0 +1,9 @@ +{ + "name": "CubicEaseInterpolator", + "key": { + "int": 28, + "string": "cubicEase" + }, + "exportsWithContext": true, + "extends": "animation/cubic_interpolator.json" +} \ No newline at end of file
diff --git a/dev/defs/animation/cubic_interpolator.json b/dev/defs/animation/cubic_interpolator.json index 8d2e74f..a810f29 100644 --- a/dev/defs/animation/cubic_interpolator.json +++ b/dev/defs/animation/cubic_interpolator.json
@@ -1,9 +1,10 @@ { "name": "CubicInterpolator", "key": { - "int": 28, + "int": 139, "string": "cubicinterpolator" }, + "abstract": true, "exportsWithContext": true, "properties": { "x1": {
diff --git a/dev/defs/animation/cubic_value_interpolator.json b/dev/defs/animation/cubic_value_interpolator.json new file mode 100644 index 0000000..6372c24 --- /dev/null +++ b/dev/defs/animation/cubic_value_interpolator.json
@@ -0,0 +1,9 @@ +{ + "name": "CubicValueInterpolator", + "key": { + "int": 138, + "string": "cubicvalueinterpolator" + }, + "exportsWithContext": true, + "extends": "animation/cubic_interpolator.json" +} \ No newline at end of file
diff --git a/include/rive/animation/cubic_ease_interpolator.hpp b/include/rive/animation/cubic_ease_interpolator.hpp new file mode 100644 index 0000000..ce68f53 --- /dev/null +++ b/include/rive/animation/cubic_ease_interpolator.hpp
@@ -0,0 +1,15 @@ +#ifndef _RIVE_CUBIC_EASE_INTERPOLATOR_HPP_ +#define _RIVE_CUBIC_EASE_INTERPOLATOR_HPP_ +#include "rive/generated/animation/cubic_ease_interpolator_base.hpp" +#include <stdio.h> +namespace rive +{ +class CubicEaseInterpolator : public CubicEaseInterpolatorBase +{ +public: + float transformValue(float valueFrom, float valueTo, float factor) override; + float transform(float factor) const override; +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/animation/cubic_interpolator.hpp b/include/rive/animation/cubic_interpolator.hpp index 95cb660..112d298 100644 --- a/include/rive/animation/cubic_interpolator.hpp +++ b/include/rive/animation/cubic_interpolator.hpp
@@ -10,15 +10,21 @@ static constexpr float SampleStepSize = 1.0f / (SplineTableSize - 1.0f); float m_Values[SplineTableSize]; +protected: float getT(float x) const; public: StatusCode onAddedDirty(CoreContext* context) override; + /// Convert a linear interpolation value to an eased one. + virtual float transformValue(float valueFrom, float valueTo, float factor) = 0; + /// Convert a linear interpolation factor to an eased one. - float transform(float value) const; + virtual float transform(float factor) const = 0; StatusCode import(ImportStack& importStack) override; + + static float calcBezier(float aT, float aA1, float aA2); }; } // namespace rive
diff --git a/include/rive/animation/cubic_value_interpolator.hpp b/include/rive/animation/cubic_value_interpolator.hpp new file mode 100644 index 0000000..a7aafc1 --- /dev/null +++ b/include/rive/animation/cubic_value_interpolator.hpp
@@ -0,0 +1,25 @@ +#ifndef _RIVE_CUBIC_VALUE_INTERPOLATOR_HPP_ +#define _RIVE_CUBIC_VALUE_INTERPOLATOR_HPP_ +#include "rive/generated/animation/cubic_value_interpolator_base.hpp" +#include <stdio.h> +namespace rive +{ +class CubicValueInterpolator : public CubicValueInterpolatorBase +{ +private: + float m_A; + float m_B; + float m_C; + float m_D; + float m_ValueTo; + + void computeParameters(); + +public: + CubicValueInterpolator(); + float transformValue(float valueFrom, float valueTo, float factor) override; + float transform(float factor) const override; +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/generated/animation/cubic_ease_interpolator_base.hpp b/include/rive/generated/animation/cubic_ease_interpolator_base.hpp new file mode 100644 index 0000000..b650744 --- /dev/null +++ b/include/rive/generated/animation/cubic_ease_interpolator_base.hpp
@@ -0,0 +1,36 @@ +#ifndef _RIVE_CUBIC_EASE_INTERPOLATOR_BASE_HPP_ +#define _RIVE_CUBIC_EASE_INTERPOLATOR_BASE_HPP_ +#include "rive/animation/cubic_interpolator.hpp" +namespace rive +{ +class CubicEaseInterpolatorBase : public CubicInterpolator +{ +protected: + typedef CubicInterpolator Super; + +public: + static const uint16_t typeKey = 28; + + /// Helper to quickly determine if a core object extends another without RTTI + /// at runtime. + bool isTypeOf(uint16_t typeKey) const override + { + switch (typeKey) + { + case CubicEaseInterpolatorBase::typeKey: + case CubicInterpolatorBase::typeKey: + return true; + default: + return false; + } + } + + uint16_t coreType() const override { return typeKey; } + + Core* clone() const override; + +protected: +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/generated/animation/cubic_interpolator_base.hpp b/include/rive/generated/animation/cubic_interpolator_base.hpp index 57abc03..4506353 100644 --- a/include/rive/generated/animation/cubic_interpolator_base.hpp +++ b/include/rive/generated/animation/cubic_interpolator_base.hpp
@@ -10,7 +10,7 @@ typedef Core Super; public: - static const uint16_t typeKey = 28; + static const uint16_t typeKey = 139; /// Helper to quickly determine if a core object extends another without RTTI /// at runtime. @@ -83,7 +83,6 @@ y2Changed(); } - Core* clone() const override; void copy(const CubicInterpolatorBase& object) { m_X1 = object.m_X1;
diff --git a/include/rive/generated/animation/cubic_value_interpolator_base.hpp b/include/rive/generated/animation/cubic_value_interpolator_base.hpp new file mode 100644 index 0000000..7dedcce --- /dev/null +++ b/include/rive/generated/animation/cubic_value_interpolator_base.hpp
@@ -0,0 +1,36 @@ +#ifndef _RIVE_CUBIC_VALUE_INTERPOLATOR_BASE_HPP_ +#define _RIVE_CUBIC_VALUE_INTERPOLATOR_BASE_HPP_ +#include "rive/animation/cubic_interpolator.hpp" +namespace rive +{ +class CubicValueInterpolatorBase : public CubicInterpolator +{ +protected: + typedef CubicInterpolator Super; + +public: + static const uint16_t typeKey = 138; + + /// Helper to quickly determine if a core object extends another without RTTI + /// at runtime. + bool isTypeOf(uint16_t typeKey) const override + { + switch (typeKey) + { + case CubicValueInterpolatorBase::typeKey: + case CubicInterpolatorBase::typeKey: + return true; + default: + return false; + } + } + + uint16_t coreType() const override { return typeKey; } + + Core* clone() const override; + +protected: +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp index 73f965a..5b0cfc7 100644 --- a/include/rive/generated/core_registry.hpp +++ b/include/rive/generated/core_registry.hpp
@@ -10,7 +10,9 @@ #include "rive/animation/blend_state_1d.hpp" #include "rive/animation/blend_state_direct.hpp" #include "rive/animation/blend_state_transition.hpp" +#include "rive/animation/cubic_ease_interpolator.hpp" #include "rive/animation/cubic_interpolator.hpp" +#include "rive/animation/cubic_value_interpolator.hpp" #include "rive/animation/entry_state.hpp" #include "rive/animation/exit_state.hpp" #include "rive/animation/keyed_object.hpp" @@ -155,6 +157,8 @@ return new BlendAnimationDirect(); case StateMachineNumberBase::typeKey: return new StateMachineNumber(); + case CubicValueInterpolatorBase::typeKey: + return new CubicValueInterpolator(); case TransitionTriggerConditionBase::typeKey: return new TransitionTriggerCondition(); case KeyedPropertyBase::typeKey: @@ -179,8 +183,8 @@ return new Animation(); case ListenerNumberChangeBase::typeKey: return new ListenerNumberChange(); - case CubicInterpolatorBase::typeKey: - return new CubicInterpolator(); + case CubicEaseInterpolatorBase::typeKey: + return new CubicEaseInterpolator(); case StateTransitionBase::typeKey: return new StateTransition(); case NestedBoolBase::typeKey: @@ -551,12 +555,6 @@ case StateMachineNumberBase::valuePropertyKey: object->as<StateMachineNumberBase>()->value(value); break; - case TransitionNumberConditionBase::valuePropertyKey: - object->as<TransitionNumberConditionBase>()->value(value); - break; - case ListenerNumberChangeBase::valuePropertyKey: - object->as<ListenerNumberChangeBase>()->value(value); - break; case CubicInterpolatorBase::x1PropertyKey: object->as<CubicInterpolatorBase>()->x1(value); break; @@ -569,6 +567,12 @@ case CubicInterpolatorBase::y2PropertyKey: object->as<CubicInterpolatorBase>()->y2(value); break; + case TransitionNumberConditionBase::valuePropertyKey: + object->as<TransitionNumberConditionBase>()->value(value); + break; + case ListenerNumberChangeBase::valuePropertyKey: + object->as<ListenerNumberChangeBase>()->value(value); + break; case KeyFrameDoubleBase::valuePropertyKey: object->as<KeyFrameDoubleBase>()->value(value); break; @@ -1015,10 +1019,6 @@ return object->as<NestedSimpleAnimationBase>()->speed(); case StateMachineNumberBase::valuePropertyKey: return object->as<StateMachineNumberBase>()->value(); - case TransitionNumberConditionBase::valuePropertyKey: - return object->as<TransitionNumberConditionBase>()->value(); - case ListenerNumberChangeBase::valuePropertyKey: - return object->as<ListenerNumberChangeBase>()->value(); case CubicInterpolatorBase::x1PropertyKey: return object->as<CubicInterpolatorBase>()->x1(); case CubicInterpolatorBase::y1PropertyKey: @@ -1027,6 +1027,10 @@ return object->as<CubicInterpolatorBase>()->x2(); case CubicInterpolatorBase::y2PropertyKey: return object->as<CubicInterpolatorBase>()->y2(); + case TransitionNumberConditionBase::valuePropertyKey: + return object->as<TransitionNumberConditionBase>()->value(); + case ListenerNumberChangeBase::valuePropertyKey: + return object->as<ListenerNumberChangeBase>()->value(); case KeyFrameDoubleBase::valuePropertyKey: return object->as<KeyFrameDoubleBase>()->value(); case LinearAnimationBase::speedPropertyKey: @@ -1299,12 +1303,12 @@ case NestedLinearAnimationBase::mixPropertyKey: case NestedSimpleAnimationBase::speedPropertyKey: case StateMachineNumberBase::valuePropertyKey: - case TransitionNumberConditionBase::valuePropertyKey: - case ListenerNumberChangeBase::valuePropertyKey: case CubicInterpolatorBase::x1PropertyKey: case CubicInterpolatorBase::y1PropertyKey: case CubicInterpolatorBase::x2PropertyKey: case CubicInterpolatorBase::y2PropertyKey: + case TransitionNumberConditionBase::valuePropertyKey: + case ListenerNumberChangeBase::valuePropertyKey: case KeyFrameDoubleBase::valuePropertyKey: case LinearAnimationBase::speedPropertyKey: case NestedNumberBase::nestedValuePropertyKey:
diff --git a/src/animation/cubic_ease_interpolator.cpp b/src/animation/cubic_ease_interpolator.cpp new file mode 100644 index 0000000..3b88125 --- /dev/null +++ b/src/animation/cubic_ease_interpolator.cpp
@@ -0,0 +1,13 @@ +#include "rive/animation/cubic_ease_interpolator.hpp" + +using namespace rive; + +float CubicEaseInterpolator::transformValue(float valueFrom, float valueTo, float factor) +{ + return valueFrom + (valueTo - valueFrom) * calcBezier(getT(factor), y1(), y2()); +} + +float CubicEaseInterpolator::transform(float factor) const +{ + return calcBezier(getT(factor), y1(), y2()); +} \ No newline at end of file
diff --git a/src/animation/cubic_interpolator.cpp b/src/animation/cubic_interpolator.cpp index ed655c8..a4f6edf 100644 --- a/src/animation/cubic_interpolator.cpp +++ b/src/animation/cubic_interpolator.cpp
@@ -12,7 +12,7 @@ const int SubdivisionMaxIterations = 10; // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. -static float calcBezier(float aT, float aA1, float aA2) +float CubicInterpolator::calcBezier(float aT, float aA1, float aA2) { return (((1.0f - 3.0f * aA2 + 3.0f * aA1) * aT + (3.0f * aA2 - 6.0f * aA1)) * aT + (3.0f * aA1)) * @@ -95,8 +95,6 @@ } } -float CubicInterpolator::transform(float mix) const { return calcBezier(getT(mix), y1(), y2()); } - StatusCode CubicInterpolator::import(ImportStack& importStack) { auto artboardImporter = importStack.latest<ArtboardImporter>(ArtboardBase::typeKey);
diff --git a/src/animation/cubic_value_interpolator.cpp b/src/animation/cubic_value_interpolator.cpp new file mode 100644 index 0000000..790a630 --- /dev/null +++ b/src/animation/cubic_value_interpolator.cpp
@@ -0,0 +1,38 @@ +#include "rive/animation/cubic_value_interpolator.hpp" + +using namespace rive; + +CubicValueInterpolator::CubicValueInterpolator() : m_D(0.0f), m_ValueTo(0.0f) +{ + computeParameters(); +} +void CubicValueInterpolator::computeParameters() +{ + float y1 = m_D; + float y2 = CubicValueInterpolator::y1(); + float y3 = CubicValueInterpolator::y2(); + float y4 = m_ValueTo; + + m_A = y4 + 3 * (y2 - y3) - y1; + m_B = 3 * (y3 - y2 * 2 + y1); + m_C = 3 * (y2 - y1); + // m_D = y1; +} + +float CubicValueInterpolator::transformValue(float valueFrom, float valueTo, float factor) +{ + if (m_D != valueFrom || m_ValueTo != valueTo) + { + m_D = valueFrom; + m_ValueTo = valueTo; + computeParameters(); + } + float t = getT(factor); + return ((m_A * t + m_B) * t + m_C) * t + m_D; +} + +float CubicValueInterpolator::transform(float factor) const +{ + assert(false); + return factor; +} \ No newline at end of file
diff --git a/src/animation/keyframe_double.cpp b/src/animation/keyframe_double.cpp index d6f63c2..ab5bea9 100644 --- a/src/animation/keyframe_double.cpp +++ b/src/animation/keyframe_double.cpp
@@ -38,10 +38,15 @@ const KeyFrameDouble& nextDouble = *kfd; float f = (currentTime - seconds()) / (nextDouble.seconds() - seconds()); + float frameValue; if (CubicInterpolator* cubic = interpolator()) { - f = cubic->transform(f); + frameValue = cubic->transformValue(value(), nextDouble.value(), f); + } + else + { + frameValue = value() + (nextDouble.value() - value()) * f; } - applyDouble(object, propertyKey, mix, value() + (nextDouble.value() - value()) * f); + applyDouble(object, propertyKey, mix, frameValue); } \ No newline at end of file
diff --git a/src/generated/animation/cubic_ease_interpolator_base.cpp b/src/generated/animation/cubic_ease_interpolator_base.cpp new file mode 100644 index 0000000..8dbda91 --- /dev/null +++ b/src/generated/animation/cubic_ease_interpolator_base.cpp
@@ -0,0 +1,11 @@ +#include "rive/generated/animation/cubic_ease_interpolator_base.hpp" +#include "rive/animation/cubic_ease_interpolator.hpp" + +using namespace rive; + +Core* CubicEaseInterpolatorBase::clone() const +{ + auto cloned = new CubicEaseInterpolator(); + cloned->copy(*this); + return cloned; +}
diff --git a/src/generated/animation/cubic_interpolator_base.cpp b/src/generated/animation/cubic_interpolator_base.cpp index 24eb158..1745e60 100644 --- a/src/generated/animation/cubic_interpolator_base.cpp +++ b/src/generated/animation/cubic_interpolator_base.cpp
@@ -1,11 +1,4 @@ #include "rive/generated/animation/cubic_interpolator_base.hpp" #include "rive/animation/cubic_interpolator.hpp" -using namespace rive; - -Core* CubicInterpolatorBase::clone() const -{ - auto cloned = new CubicInterpolator(); - cloned->copy(*this); - return cloned; -} +using namespace rive; \ No newline at end of file
diff --git a/src/generated/animation/cubic_value_interpolator_base.cpp b/src/generated/animation/cubic_value_interpolator_base.cpp new file mode 100644 index 0000000..3f64039 --- /dev/null +++ b/src/generated/animation/cubic_value_interpolator_base.cpp
@@ -0,0 +1,11 @@ +#include "rive/generated/animation/cubic_value_interpolator_base.hpp" +#include "rive/animation/cubic_value_interpolator.hpp" + +using namespace rive; + +Core* CubicValueInterpolatorBase::clone() const +{ + auto cloned = new CubicValueInterpolator(); + cloned->copy(*this); + return cloned; +}
diff --git a/test/assets/cubic_value_test.riv b/test/assets/cubic_value_test.riv new file mode 100644 index 0000000..891a47b --- /dev/null +++ b/test/assets/cubic_value_test.riv Binary files differ
diff --git a/test/cubic_value_test.cpp b/test/cubic_value_test.cpp new file mode 100644 index 0000000..05e1f3b --- /dev/null +++ b/test/cubic_value_test.cpp
@@ -0,0 +1,38 @@ +#include <rive/file.hpp> +#include <rive/node.hpp> +#include <rive/animation/cubic_value_interpolator.hpp> +#include "catch.hpp" +#include "rive_file_reader.hpp" +#include <cstdio> + +TEST_CASE("test cubic value load and interpolate properly", "[file]") +{ + auto file = ReadRiveFile("../../test/assets/cubic_value_test.riv"); + + auto artboard = file->artboard(); + REQUIRE(artboard != nullptr); + + auto greyRect = artboard->find<rive::Node>("grey_rectangle"); + REQUIRE(greyRect != nullptr); + + int interpolatorCount = 0; + for (auto object : artboard->objects()) + { + if (object->coreType() == rive::CubicValueInterpolatorBase::typeKey) + { + interpolatorCount++; + } + } + + REQUIRE(interpolatorCount == 3); + + auto animation = artboard->animation("Timeline 1"); + REQUIRE(animation != nullptr); + // Go to frame 15. + animation->apply(artboard, 15.0f / animation->fps(), 1.0f); + REQUIRE(greyRect->x() == Approx(290.71f)); + + // Go to frame 11. + animation->apply(artboard, 11.0f / animation->fps(), 1.0f); + REQUIRE(greyRect->x() == Approx(363.01f)); +}