ScrollingMomentumCalculator.cpp [plain text]
#include "config.h"
#include "ScrollingMomentumCalculator.h"
#include "FloatPoint.h"
#include "FloatSize.h"
namespace WebCore {
static const Seconds scrollSnapAnimationDuration = 1_s;
static inline float projectedInertialScrollDistance(float initialWheelDelta)
{
const static double inertialScrollPredictionFactor = 16.7;
return inertialScrollPredictionFactor * initialWheelDelta;
}
ScrollingMomentumCalculator::ScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
: m_initialDelta(initialDelta)
, m_initialVelocity(initialVelocity)
, m_initialScrollOffset(initialOffset.x(), initialOffset.y())
, m_viewportSize(viewportSize)
, m_contentSize(contentSize)
{
}
void ScrollingMomentumCalculator::setRetargetedScrollOffset(const FloatSize& target)
{
if (m_retargetedScrollOffset && m_retargetedScrollOffset == target)
return;
m_retargetedScrollOffset = target;
retargetedScrollOffsetDidChange();
}
FloatSize ScrollingMomentumCalculator::predictedDestinationOffset()
{
float initialOffsetX = clampTo<float>(m_initialScrollOffset.width() + projectedInertialScrollDistance(m_initialDelta.width()), 0, m_contentSize.width() - m_viewportSize.width());
float initialOffsetY = clampTo<float>(m_initialScrollOffset.height() + projectedInertialScrollDistance(m_initialDelta.height()), 0, m_contentSize.height() - m_viewportSize.height());
return { initialOffsetX, initialOffsetY };
}
#if !PLATFORM(MAC)
std::unique_ptr<ScrollingMomentumCalculator> ScrollingMomentumCalculator::create(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
{
return std::make_unique<BasicScrollingMomentumCalculator>(viewportSize, contentSize, initialOffset, initialDelta, initialVelocity);
}
void ScrollingMomentumCalculator::setPlatformMomentumScrollingPredictionEnabled(bool)
{
}
#endif
BasicScrollingMomentumCalculator::BasicScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
: ScrollingMomentumCalculator(viewportSize, contentSize, initialOffset, initialDelta, initialVelocity)
{
}
FloatSize BasicScrollingMomentumCalculator::linearlyInterpolatedOffsetAtProgress(float progress)
{
return m_initialScrollOffset + progress * (retargetedScrollOffset() - m_initialScrollOffset);
}
FloatSize BasicScrollingMomentumCalculator::cubicallyInterpolatedOffsetAtProgress(float progress) const
{
ASSERT(!m_forceLinearAnimationCurve);
FloatSize interpolatedPoint;
for (int i = 0; i < 4; ++i)
interpolatedPoint += std::pow(progress, i) * m_snapAnimationCurveCoefficients[i];
return interpolatedPoint;
}
FloatPoint BasicScrollingMomentumCalculator::scrollOffsetAfterElapsedTime(Seconds elapsedTime)
{
if (m_momentumCalculatorRequiresInitialization) {
initializeSnapProgressCurve();
initializeInterpolationCoefficientsIfNecessary();
m_momentumCalculatorRequiresInitialization = false;
}
float progress = animationProgressAfterElapsedTime(elapsedTime);
auto offsetAsSize = m_forceLinearAnimationCurve ? linearlyInterpolatedOffsetAtProgress(progress) : cubicallyInterpolatedOffsetAtProgress(progress);
return FloatPoint(offsetAsSize.width(), offsetAsSize.height());
}
Seconds BasicScrollingMomentumCalculator::animationDuration()
{
return scrollSnapAnimationDuration;
}
void BasicScrollingMomentumCalculator::initializeInterpolationCoefficientsIfNecessary()
{
m_forceLinearAnimationCurve = true;
float initialDeltaMagnitude = m_initialDelta.diagonalLength();
if (initialDeltaMagnitude < 1) {
return;
}
FloatSize startToEndVector = retargetedScrollOffset() - m_initialScrollOffset;
float startToEndDistance = startToEndVector.diagonalLength();
if (!startToEndDistance) {
return;
}
float cosTheta = (m_initialDelta.width() * startToEndVector.width() + m_initialDelta.height() * startToEndVector.height()) / (initialDeltaMagnitude * startToEndDistance);
if (cosTheta <= 0) {
return;
}
float sideLength = startToEndDistance / (2.0f * cosTheta + 1.0f);
FloatSize controlVector1 = m_initialScrollOffset + sideLength * m_initialDelta / initialDeltaMagnitude;
FloatSize controlVector2 = controlVector1 + (sideLength * startToEndVector / startToEndDistance);
m_snapAnimationCurveCoefficients[0] = m_initialScrollOffset;
m_snapAnimationCurveCoefficients[1] = 3 * (controlVector1 - m_initialScrollOffset);
m_snapAnimationCurveCoefficients[2] = 3 * (m_initialScrollOffset - 2 * controlVector1 + controlVector2);
m_snapAnimationCurveCoefficients[3] = 3 * (controlVector1 - controlVector2) - m_initialScrollOffset + retargetedScrollOffset();
m_forceLinearAnimationCurve = false;
}
static const float framesPerSecond = 60.0f;
void BasicScrollingMomentumCalculator::initializeSnapProgressCurve()
{
static const int maxNumScrollSnapParameterEstimationIterations = 10;
static const float scrollSnapDecayFactorConvergenceThreshold = 0.001;
static const float initialScrollSnapCurveMagnitude = 1.1;
static const float minScrollSnapInitialProgress = 0.1;
static const float maxScrollSnapInitialProgress = 0.5;
FloatSize alignmentVector = m_initialDelta * (retargetedScrollOffset() - m_initialScrollOffset);
float initialProgress;
if (alignmentVector.width() + alignmentVector.height() > 0)
initialProgress = clampTo(m_initialDelta.diagonalLength() / (retargetedScrollOffset() - m_initialScrollOffset).diagonalLength(), minScrollSnapInitialProgress, maxScrollSnapInitialProgress);
else
initialProgress = minScrollSnapInitialProgress;
float previousDecayFactor = 1.0f;
m_snapAnimationCurveMagnitude = initialScrollSnapCurveMagnitude;
for (int i = 0; i < maxNumScrollSnapParameterEstimationIterations; ++i) {
m_snapAnimationDecayFactor = m_snapAnimationCurveMagnitude / (m_snapAnimationCurveMagnitude - initialProgress);
m_snapAnimationCurveMagnitude = 1.0f / (1.0f - std::pow(m_snapAnimationDecayFactor, -framesPerSecond * scrollSnapAnimationDuration.value()));
if (std::abs(m_snapAnimationDecayFactor - previousDecayFactor) < scrollSnapDecayFactorConvergenceThreshold)
break;
previousDecayFactor = m_snapAnimationDecayFactor;
}
}
float BasicScrollingMomentumCalculator::animationProgressAfterElapsedTime(Seconds elapsedTime) const
{
float timeProgress = clampTo<float>(elapsedTime / scrollSnapAnimationDuration, 0, 1);
return std::min(1.0, m_snapAnimationCurveMagnitude * (1.0 - std::pow(m_snapAnimationDecayFactor, -framesPerSecond * scrollSnapAnimationDuration.value() * timeProgress)));
}
};