ScrollAnimationMSDPhysics.cpp (8021B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ScrollAnimationMSDPhysics.h" 8 9 #include "mozilla/Logging.h" 10 #include "mozilla/StaticPrefs_general.h" 11 #include "mozilla/StaticPrefs_layout.h" 12 #include "mozilla/ToString.h" 13 14 static mozilla::LazyLogModule sApzMsdLog("apz.msd"); 15 #define MSD_LOG(...) MOZ_LOG(sApzMsdLog, LogLevel::Debug, (__VA_ARGS__)) 16 17 using namespace mozilla; 18 19 ScrollAnimationMSDPhysics::ScrollAnimationMSDPhysics( 20 ScrollAnimationKind aAnimationKind, const nsPoint& aStartPos, 21 nscoord aSmallestVisibleIncrement) 22 : mAnimationKind(aAnimationKind), 23 mSmallestVisibleIncrement(aSmallestVisibleIncrement), 24 mStartPos(aStartPos), 25 mModelX( 26 0, 0, 0, 27 StaticPrefs::general_smoothScroll_msdPhysics_regularSpringConstant(), 28 1), 29 mModelY( 30 0, 0, 0, 31 StaticPrefs::general_smoothScroll_msdPhysics_regularSpringConstant(), 32 1), 33 mIsFirstIteration(true) {} 34 35 void ScrollAnimationMSDPhysics::Update(const TimeStamp& aTime, 36 const nsPoint& aDestination, 37 const nsSize& aCurrentVelocity) { 38 double springConstant = ComputeSpringConstant(aTime); 39 double dampingRatio = GetDampingRatio(); 40 41 // mLastSimulatedTime is the most recent time that this animation has been 42 // "observed" at. We don't want to update back to a state in the past, so we 43 // set mStartTime to the more recent of mLastSimulatedTime and aTime. 44 // aTime can be in the past if we're processing an input event whose internal 45 // timestamp is in the past. 46 if (mLastSimulatedTime && aTime < mLastSimulatedTime) { 47 mStartTime = mLastSimulatedTime; 48 } else { 49 mStartTime = aTime; 50 } 51 52 if (!mIsFirstIteration) { 53 mStartPos = PositionAt(mStartTime); 54 } 55 56 mLastSimulatedTime = mStartTime; 57 mDestination = aDestination; 58 mModelX = NonOscillatingAxisPhysicsMSDModel(mStartPos.x, aDestination.x, 59 aCurrentVelocity.width, 60 springConstant, dampingRatio); 61 mModelY = NonOscillatingAxisPhysicsMSDModel(mStartPos.y, aDestination.y, 62 aCurrentVelocity.height, 63 springConstant, dampingRatio); 64 mIsFirstIteration = false; 65 } 66 67 void ScrollAnimationMSDPhysics::ApplyContentShift(const CSSPoint& aShiftDelta) { 68 nsPoint shiftDelta = CSSPoint::ToAppUnits(aShiftDelta); 69 mStartPos += shiftDelta; 70 mDestination += shiftDelta; 71 TimeStamp currentTime = mLastSimulatedTime; 72 nsPoint currentPosition = PositionAt(currentTime) + shiftDelta; 73 nsSize currentVelocity = VelocityAt(currentTime); 74 double springConstant = ComputeSpringConstant(currentTime); 75 double dampingRatio = GetDampingRatio(); 76 mModelX = NonOscillatingAxisPhysicsMSDModel(currentPosition.x, mDestination.x, 77 currentVelocity.width, 78 springConstant, dampingRatio); 79 mModelY = NonOscillatingAxisPhysicsMSDModel(currentPosition.y, mDestination.y, 80 currentVelocity.height, 81 springConstant, dampingRatio); 82 } 83 84 double ScrollAnimationMSDPhysics::GetDampingRatio() const { 85 if (mAnimationKind == ScrollAnimationKind::SmoothMsd) { 86 return StaticPrefs::layout_css_scroll_snap_damping_ratio(); 87 } 88 return 1.0; 89 } 90 91 double ScrollAnimationMSDPhysics::ComputeSpringConstant( 92 const TimeStamp& aTime) { 93 if (mAnimationKind == ScrollAnimationKind::SmoothMsd) { 94 return StaticPrefs::layout_css_scroll_snap_spring_constant(); 95 } 96 97 if (!mPreviousEventTime) { 98 mPreviousEventTime = aTime; 99 mPreviousDelta = TimeDuration(); 100 return StaticPrefs:: 101 general_smoothScroll_msdPhysics_motionBeginSpringConstant(); 102 } 103 104 TimeDuration delta = aTime - mPreviousEventTime; 105 TimeDuration previousDelta = mPreviousDelta; 106 107 mPreviousEventTime = aTime; 108 mPreviousDelta = delta; 109 110 double deltaMS = delta.ToMilliseconds(); 111 if (deltaMS >= 112 StaticPrefs:: 113 general_smoothScroll_msdPhysics_continuousMotionMaxDeltaMS()) { 114 return StaticPrefs:: 115 general_smoothScroll_msdPhysics_motionBeginSpringConstant(); 116 } 117 118 if (previousDelta && 119 deltaMS >= 120 StaticPrefs::general_smoothScroll_msdPhysics_slowdownMinDeltaMS() && 121 deltaMS >= 122 previousDelta.ToMilliseconds() * 123 StaticPrefs:: 124 general_smoothScroll_msdPhysics_slowdownMinDeltaRatio()) { 125 // The rate of events has slowed (the time delta between events has 126 // increased) enough that we think that the current scroll motion is coming 127 // to a stop. Use a stiffer spring in order to reach the destination more 128 // quickly. 129 return StaticPrefs:: 130 general_smoothScroll_msdPhysics_slowdownSpringConstant(); 131 } 132 133 return StaticPrefs::general_smoothScroll_msdPhysics_regularSpringConstant(); 134 } 135 136 void ScrollAnimationMSDPhysics::SimulateUntil(const TimeStamp& aTime) { 137 if (!mLastSimulatedTime || aTime <= mLastSimulatedTime) { 138 return; 139 } 140 TimeDuration delta = aTime - mLastSimulatedTime; 141 mModelX.Simulate(delta); 142 mModelY.Simulate(delta); 143 mLastSimulatedTime = aTime; 144 MSD_LOG("Simulated for duration %f, finished %d position %s velocity %s\n", 145 delta.ToMilliseconds(), IsFinished(aTime), 146 ToString(CSSPoint::FromAppUnits(PositionAt(aTime))).c_str(), 147 ToString(CSSPoint::FromAppUnits(VelocityAt(aTime))).c_str()); 148 } 149 150 bool mozilla::ScrollAnimationMSDPhysics::IsFinished(const TimeStamp& aTime) { 151 SimulateUntil(aTime); 152 return mModelX.IsFinished(mSmallestVisibleIncrement) && 153 mModelY.IsFinished(mSmallestVisibleIncrement); 154 } 155 156 nsPoint ScrollAnimationMSDPhysics::PositionAt(const TimeStamp& aTime) { 157 SimulateUntil(aTime); 158 return nsPoint(NSToCoordRound(mModelX.GetPosition()), 159 NSToCoordRound(mModelY.GetPosition())); 160 } 161 162 nsSize ScrollAnimationMSDPhysics::VelocityAt(const TimeStamp& aTime) { 163 SimulateUntil(aTime); 164 return nsSize(NSToCoordRound(mModelX.GetVelocity()), 165 NSToCoordRound(mModelY.GetVelocity())); 166 } 167 168 static double ClampVelocityToMaximum(double aVelocity, double aInitialPosition, 169 double aDestination, 170 double aSpringConstant) { 171 // Clamp velocity to the maximum value it could obtain if we started at this 172 // position with zero velocity (see bug 1866904 comment 3). With a damping 173 // ratio >= 1.0, this should be low enough to avoid overshooting the 174 // destination. 175 double velocityLimit = 176 sqrt(aSpringConstant) * abs(aDestination - aInitialPosition); 177 return std::clamp(aVelocity, -velocityLimit, velocityLimit); 178 } 179 180 ScrollAnimationMSDPhysics::NonOscillatingAxisPhysicsMSDModel:: 181 NonOscillatingAxisPhysicsMSDModel(double aInitialPosition, 182 double aInitialDestination, 183 double aInitialVelocity, 184 double aSpringConstant, 185 double aDampingRatio) 186 : AxisPhysicsMSDModel( 187 aInitialPosition, aInitialDestination, 188 ClampVelocityToMaximum(aInitialVelocity, aInitialPosition, 189 aInitialDestination, aSpringConstant), 190 aSpringConstant, aDampingRatio) { 191 MSD_LOG("Constructing axis physics model with parameters %f %f %f %f %f\n", 192 aInitialPosition, aInitialDestination, aInitialVelocity, 193 aSpringConstant, aDampingRatio); 194 MOZ_ASSERT(aDampingRatio >= 1.0, 195 "Damping ratio must be >= 1.0 to avoid oscillation"); 196 }