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/. */
7 #include "ScrollAnimationMSDPhysics.h"
8 #include "mozilla/Logging.h"
9 #include "mozilla/StaticPrefs_general.h"
10 #include "mozilla/ToString.h"
12 static mozilla::LazyLogModule
sApzMsdLog("apz.msd");
13 #define MSD_LOG(...) MOZ_LOG(sApzMsdLog, LogLevel::Debug, (__VA_ARGS__))
15 using namespace mozilla
;
17 ScrollAnimationMSDPhysics::ScrollAnimationMSDPhysics(const nsPoint
& aStartPos
)
18 : mStartPos(aStartPos
),
21 StaticPrefs::general_smoothScroll_msdPhysics_regularSpringConstant(),
25 StaticPrefs::general_smoothScroll_msdPhysics_regularSpringConstant(),
27 mIsFirstIteration(true) {}
29 void ScrollAnimationMSDPhysics::Update(const TimeStamp
& aTime
,
30 const nsPoint
& aDestination
,
31 const nsSize
& aCurrentVelocity
) {
32 double springConstant
= ComputeSpringConstant(aTime
);
34 // mLastSimulatedTime is the most recent time that this animation has been
35 // "observed" at. We don't want to update back to a state in the past, so we
36 // set mStartTime to the more recent of mLastSimulatedTime and aTime.
37 // aTime can be in the past if we're processing an input event whose internal
38 // timestamp is in the past.
39 if (mLastSimulatedTime
&& aTime
< mLastSimulatedTime
) {
40 mStartTime
= mLastSimulatedTime
;
45 if (!mIsFirstIteration
) {
46 mStartPos
= PositionAt(mStartTime
);
49 mLastSimulatedTime
= mStartTime
;
50 mDestination
= aDestination
;
51 mModelX
= NonOscillatingAxisPhysicsMSDModel(
52 mStartPos
.x
, aDestination
.x
, aCurrentVelocity
.width
, springConstant
, 1);
53 mModelY
= NonOscillatingAxisPhysicsMSDModel(
54 mStartPos
.y
, aDestination
.y
, aCurrentVelocity
.height
, springConstant
, 1);
55 mIsFirstIteration
= false;
58 void ScrollAnimationMSDPhysics::ApplyContentShift(const CSSPoint
& aShiftDelta
) {
59 nsPoint shiftDelta
= CSSPoint::ToAppUnits(aShiftDelta
);
60 mStartPos
+= shiftDelta
;
61 mDestination
+= shiftDelta
;
62 TimeStamp currentTime
= mLastSimulatedTime
;
63 nsPoint currentPosition
= PositionAt(currentTime
) + shiftDelta
;
64 nsSize currentVelocity
= VelocityAt(currentTime
);
65 double springConstant
= ComputeSpringConstant(currentTime
);
66 mModelX
= NonOscillatingAxisPhysicsMSDModel(currentPosition
.x
, mDestination
.x
,
67 currentVelocity
.width
,
69 mModelY
= NonOscillatingAxisPhysicsMSDModel(currentPosition
.y
, mDestination
.y
,
70 currentVelocity
.height
,
74 double ScrollAnimationMSDPhysics::ComputeSpringConstant(
75 const TimeStamp
& aTime
) {
76 if (!mPreviousEventTime
) {
77 mPreviousEventTime
= aTime
;
78 mPreviousDelta
= TimeDuration();
80 general_smoothScroll_msdPhysics_motionBeginSpringConstant();
83 TimeDuration delta
= aTime
- mPreviousEventTime
;
84 TimeDuration previousDelta
= mPreviousDelta
;
86 mPreviousEventTime
= aTime
;
87 mPreviousDelta
= delta
;
89 double deltaMS
= delta
.ToMilliseconds();
92 general_smoothScroll_msdPhysics_continuousMotionMaxDeltaMS()) {
94 general_smoothScroll_msdPhysics_motionBeginSpringConstant();
99 StaticPrefs::general_smoothScroll_msdPhysics_slowdownMinDeltaMS() &&
101 previousDelta
.ToMilliseconds() *
103 general_smoothScroll_msdPhysics_slowdownMinDeltaRatio()) {
104 // The rate of events has slowed (the time delta between events has
105 // increased) enough that we think that the current scroll motion is coming
106 // to a stop. Use a stiffer spring in order to reach the destination more
109 general_smoothScroll_msdPhysics_slowdownSpringConstant();
112 return StaticPrefs::general_smoothScroll_msdPhysics_regularSpringConstant();
115 void ScrollAnimationMSDPhysics::SimulateUntil(const TimeStamp
& aTime
) {
116 if (!mLastSimulatedTime
|| aTime
<= mLastSimulatedTime
) {
119 TimeDuration delta
= aTime
- mLastSimulatedTime
;
120 mModelX
.Simulate(delta
);
121 mModelY
.Simulate(delta
);
122 mLastSimulatedTime
= aTime
;
123 MSD_LOG("Simulated for duration %f, finished %d position %s velocity %s\n",
124 delta
.ToMilliseconds(), IsFinished(aTime
),
125 ToString(CSSPoint::FromAppUnits(PositionAt(aTime
))).c_str(),
126 ToString(CSSPoint::FromAppUnits(VelocityAt(aTime
))).c_str());
129 nsPoint
ScrollAnimationMSDPhysics::PositionAt(const TimeStamp
& aTime
) {
130 SimulateUntil(aTime
);
131 return nsPoint(NSToCoordRound(mModelX
.GetPosition()),
132 NSToCoordRound(mModelY
.GetPosition()));
135 nsSize
ScrollAnimationMSDPhysics::VelocityAt(const TimeStamp
& aTime
) {
136 SimulateUntil(aTime
);
137 return nsSize(NSToCoordRound(mModelX
.GetVelocity()),
138 NSToCoordRound(mModelY
.GetVelocity()));
141 static double ClampVelocityToMaximum(double aVelocity
, double aInitialPosition
,
143 double aSpringConstant
) {
144 // Clamp velocity to the maximum value it could obtain if we started at this
145 // position with zero velocity (see bug 1866904 comment 3). With a damping
146 // ratio >= 1.0, this should be low enough to avoid overshooting the
148 double velocityLimit
=
149 sqrt(aSpringConstant
) * abs(aDestination
- aInitialPosition
);
150 return clamped(aVelocity
, -velocityLimit
, velocityLimit
);
153 ScrollAnimationMSDPhysics::NonOscillatingAxisPhysicsMSDModel::
154 NonOscillatingAxisPhysicsMSDModel(double aInitialPosition
,
155 double aInitialDestination
,
156 double aInitialVelocity
,
157 double aSpringConstant
,
158 double aDampingRatio
)
159 : AxisPhysicsMSDModel(
160 aInitialPosition
, aInitialDestination
,
161 ClampVelocityToMaximum(aInitialVelocity
, aInitialPosition
,
162 aInitialDestination
, aSpringConstant
),
163 aSpringConstant
, aDampingRatio
) {
164 MSD_LOG("Constructing axis physics model with parameters %f %f %f %f %f\n",
165 aInitialPosition
, aInitialDestination
, aInitialVelocity
,
166 aSpringConstant
, aDampingRatio
);
167 MOZ_ASSERT(aDampingRatio
>= 1.0,
168 "Damping ratio must be >= 1.0 to avoid oscillation");