blob: ed655c8e1881f1b7a056e685dd33ad4db25ce527 [file] [log] [blame]
#include "rive/animation/cubic_interpolator.hpp"
#include "rive/artboard.hpp"
#include "rive/importers/artboard_importer.hpp"
#include "rive/importers/import_stack.hpp"
#include <cmath>
using namespace rive;
const int NewtonIterations = 4;
const float NewtonMinSlope = 0.001f;
const float SubdivisionPrecision = 0.0000001f;
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)
{
return (((1.0f - 3.0f * aA2 + 3.0f * aA1) * aT + (3.0f * aA2 - 6.0f * aA1)) * aT +
(3.0f * aA1)) *
aT;
}
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
static float getSlope(float aT, float aA1, float aA2)
{
return 3.0f * (1.0f - 3.0f * aA2 + 3.0f * aA1) * aT * aT +
2.0f * (3.0f * aA2 - 6.0f * aA1) * aT + (3.0f * aA1);
}
StatusCode CubicInterpolator::onAddedDirty(CoreContext* context)
{
for (int i = 0; i < SplineTableSize; ++i)
{
m_Values[i] = calcBezier(i * SampleStepSize, x1(), x2());
}
return StatusCode::Ok;
}
float CubicInterpolator::getT(float x) const
{
float intervalStart = 0.0f;
int currentSample = 1;
int lastSample = SplineTableSize - 1;
for (; currentSample != lastSample && m_Values[currentSample] <= x; ++currentSample)
{
intervalStart += SampleStepSize;
}
--currentSample;
// Interpolate to provide an initial guess for t
float dist =
(x - m_Values[currentSample]) / (m_Values[currentSample + 1] - m_Values[currentSample]);
float guessForT = intervalStart + dist * SampleStepSize;
float _x1 = x1(), _x2 = x2();
float initialSlope = getSlope(guessForT, _x1, _x2);
if (initialSlope >= NewtonMinSlope)
{
for (int i = 0; i < NewtonIterations; ++i)
{
float currentSlope = getSlope(guessForT, _x1, _x2);
if (currentSlope == 0.0f)
{
return guessForT;
}
float currentX = calcBezier(guessForT, _x1, _x2) - x;
guessForT -= currentX / currentSlope;
}
return guessForT;
}
else if (initialSlope == 0.0f)
{
return guessForT;
}
else
{
float aB = intervalStart + SampleStepSize;
float currentX, currentT;
int i = 0;
do
{
currentT = intervalStart + (aB - intervalStart) / 2.0f;
currentX = calcBezier(currentT, _x1, _x2) - x;
if (currentX > 0.0f)
{
aB = currentT;
}
else
{
intervalStart = currentT;
}
} while (std::abs(currentX) > SubdivisionPrecision && ++i < SubdivisionMaxIterations);
return currentT;
}
}
float CubicInterpolator::transform(float mix) const { return calcBezier(getT(mix), y1(), y2()); }
StatusCode CubicInterpolator::import(ImportStack& importStack)
{
auto artboardImporter = importStack.latest<ArtboardImporter>(ArtboardBase::typeKey);
if (artboardImporter == nullptr)
{
return StatusCode::MissingObject;
}
artboardImporter->addComponent(this);
return Super::import(importStack);
}