blob: e94e84836ff5ab9aec19605976514bf6841c64c3 [file] [log] [blame]
#include "rive/constraints/scrolling/elastic_scroll_physics.hpp"
#include "math.h"
using namespace rive;
ElasticScrollPhysics::~ElasticScrollPhysics()
{
delete m_physicsX;
delete m_physicsY;
}
Vec2D ElasticScrollPhysics::advance(float elapsedSeconds)
{
float advanceX =
m_physicsX != nullptr ? m_physicsX->advance(elapsedSeconds) : 0.0f;
float advanceY =
m_physicsY != nullptr ? m_physicsY->advance(elapsedSeconds) : 0.0f;
bool isRunningX = m_physicsX != nullptr && m_physicsX->isRunning();
bool isRunningY = m_physicsY != nullptr && m_physicsY->isRunning();
if (!isRunningX && !isRunningY)
{
reset();
}
return Vec2D(advanceX, advanceY);
}
Vec2D ElasticScrollPhysics::clamp(Vec2D rangeMin, Vec2D rangeMax, Vec2D value)
{
float clampX = m_physicsX != nullptr
? m_physicsX->clamp(rangeMin.x, rangeMax.x, value.x)
: 0.0f;
float clampY = m_physicsY != nullptr
? m_physicsY->clamp(rangeMin.y, rangeMax.y, value.y)
: 0.0f;
return Vec2D(clampX, clampY);
}
void ElasticScrollPhysics::run(Vec2D rangeMin,
Vec2D rangeMax,
Vec2D value,
std::vector<Vec2D> snappingPoints,
float contentSize)
{
Super::run(rangeMin, rangeMax, value, snappingPoints, contentSize);
std::vector<float> xPoints;
std::vector<float> yPoints;
for (auto pt : snappingPoints)
{
xPoints.push_back(pt.x);
yPoints.push_back(pt.y);
}
if (m_physicsX != nullptr)
{
m_physicsX->run(m_acceleration.x,
rangeMin.x,
rangeMax.x,
value.x,
xPoints,
contentSize);
}
if (m_physicsY != nullptr)
{
m_physicsY->run(m_acceleration.y,
rangeMin.y,
rangeMax.y,
value.y,
yPoints,
contentSize);
}
}
void ElasticScrollPhysics::prepare(DraggableConstraintDirection dir)
{
Super::prepare(dir);
if (dir == DraggableConstraintDirection::horizontal ||
dir == DraggableConstraintDirection::all)
{
m_physicsX = new ElasticScrollPhysicsHelper(friction(),
speedMultiplier(),
elasticFactor());
}
if (dir == DraggableConstraintDirection::vertical ||
dir == DraggableConstraintDirection::all)
{
m_physicsY = new ElasticScrollPhysicsHelper(friction(),
speedMultiplier(),
elasticFactor());
}
}
void ElasticScrollPhysics::reset()
{
Super::reset();
m_physicsX = nullptr;
m_physicsY = nullptr;
}
float ElasticScrollPhysicsHelper::advance(float elapsedSeconds)
{
if (m_speed != 0)
{
m_current += m_speed * elapsedSeconds;
if (m_current < m_runRangeMin)
{
m_friction *= 4;
}
else if (m_current > m_runRangeMax)
{
m_friction *= 4;
}
m_speed += -m_speed * std::min(1.0f, elapsedSeconds * m_friction);
if (abs(m_speed) < 5)
{
m_speed = 0;
if (m_current < m_runRangeMin)
{
m_target = m_runRangeMin;
}
else if (m_current > m_runRangeMax)
{
m_target = m_runRangeMax;
}
else
{
m_target = m_current;
}
}
return m_current;
}
auto diff = m_target - m_current;
if (abs(diff) < 0.1)
{
m_current = std::isnan(m_snapTarget) ? m_target : m_snapTarget;
m_isRunning = false;
}
else
{
m_current += diff * std::min(1.0f, elapsedSeconds * 15.0f);
}
return m_current;
}
float ElasticScrollPhysicsHelper::clamp(float rangeMin,
float rangeMax,
float value)
{
if (value < rangeMin)
{
return rangeMin - pow(-(value - rangeMin), m_elasticFactor);
}
else if (value > rangeMax)
{
return rangeMax + pow(value + rangeMax, m_elasticFactor);
}
return value;
}
void ElasticScrollPhysicsHelper::run(float acceleration,
float rangeMin,
float rangeMax,
float value,
std::vector<float> snappingPoints,
float contentSize)
{
m_isRunning = true;
m_runRangeMin = rangeMin;
m_runRangeMax = rangeMax;
if (abs(acceleration) > 100)
{
m_speed = acceleration * 0.16f * 0.16f * 0.1f * m_speedMultiplier;
}
else
{
m_speed = 0;
}
if (value < rangeMin)
{
m_target = rangeMin;
}
else if (value > rangeMax)
{
m_target = rangeMax;
}
else
{
m_target = value;
}
m_current = value;
if (!snappingPoints.empty())
{
float endTarget = -(m_current + m_speed / m_friction);
float sectionSize = contentSize != 0 ? contentSize : 1;
int multiple = rangeMax == std::numeric_limits<float>::infinity()
? std::floor(endTarget / sectionSize)
: 0;
float modEndTarget = rangeMax == std::numeric_limits<float>::infinity()
? std::fmod(endTarget, sectionSize)
: endTarget;
float closest = std::numeric_limits<float>::max();
float snapTarget = 0;
for (auto snap : snappingPoints)
{
float diff = std::abs(snap - modEndTarget);
if (diff < closest)
{
closest = diff;
snapTarget = snap + (multiple * sectionSize);
}
}
m_speed = -(snapTarget + m_current) * m_friction;
m_snapTarget = -snapTarget;
}
else
{
m_snapTarget = NAN;
}
}