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));
+}