| #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; |
| } |
| } |