Source code

Revision control

Other Tools

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 "AndroidFlingPhysics.h"
8
9
#include <cmath>
10
11
#include "mozilla/ClearOnShutdown.h"
12
#include "mozilla/StaticPrefs_apz.h"
13
#include "mozilla/StaticPtr.h"
14
15
namespace mozilla {
16
namespace layers {
17
18
// The fling physics calculations implemented here are adapted from
19
// Chrome's implementation of fling physics on Android:
21
22
static double ComputeDeceleration(float aDPI) {
23
const float kFriction = 0.84f;
24
const float kGravityEarth = 9.80665f;
25
return kGravityEarth // g (m/s^2)
26
* 39.37f // inch/meter
27
* aDPI // pixels/inch
28
* kFriction;
29
}
30
31
// == std::log(0.78f) / std::log(0.9f)
32
const float kDecelerationRate = 2.3582018f;
33
34
// Default friction constant in android.view.ViewConfiguration.
35
static float GetFlingFriction() {
36
return StaticPrefs::apz_android_chrome_fling_physics_friction();
37
}
38
39
// Tension lines cross at (GetInflexion(), 1).
40
static float GetInflexion() {
41
// Clamp the inflexion to the range [0,1]. Values outside of this range
42
// do not make sense in the physics model, and for negative values the
43
// approximation used to compute the spline curve does not converge.
44
const float inflexion =
45
StaticPrefs::apz_android_chrome_fling_physics_inflexion();
46
if (inflexion < 0.0f) {
47
return 0.0f;
48
}
49
if (inflexion > 1.0f) {
50
return 1.0f;
51
}
52
return inflexion;
53
}
54
55
// Fling scroll is stopped when the scroll position is |kThresholdForFlingEnd|
56
// pixels or closer from the end.
57
static float GetThresholdForFlingEnd() {
58
return StaticPrefs::apz_android_chrome_fling_physics_stop_threshold();
59
}
60
61
static double ComputeSplineDeceleration(ParentLayerCoord aVelocity,
62
double aTuningCoeff) {
63
float velocityPerSec = aVelocity * 1000.0f;
64
return std::log(GetInflexion() * velocityPerSec /
65
(GetFlingFriction() * aTuningCoeff));
66
}
67
68
static TimeDuration ComputeFlingDuration(ParentLayerCoord aVelocity,
69
double aTuningCoeff) {
70
const double splineDecel = ComputeSplineDeceleration(aVelocity, aTuningCoeff);
71
const double timeSeconds = std::exp(splineDecel / (kDecelerationRate - 1.0));
72
return TimeDuration::FromSeconds(timeSeconds);
73
}
74
75
static ParentLayerCoord ComputeFlingDistance(ParentLayerCoord aVelocity,
76
double aTuningCoeff) {
77
const double splineDecel = ComputeSplineDeceleration(aVelocity, aTuningCoeff);
78
return GetFlingFriction() * aTuningCoeff *
79
std::exp(kDecelerationRate / (kDecelerationRate - 1.0) * splineDecel);
80
}
81
82
struct SplineConstants {
83
public:
84
SplineConstants() {
85
const float kStartTension = 0.5f;
86
const float kEndTension = 1.0f;
87
const float kP1 = kStartTension * GetInflexion();
88
const float kP2 = 1.0f - kEndTension * (1.0f - GetInflexion());
89
90
float xMin = 0.0f;
91
for (int i = 0; i < kNumSamples; i++) {
92
const float alpha = static_cast<float>(i) / kNumSamples;
93
94
float xMax = 1.0f;
95
float x, tx, coef;
96
// While the inflexion can be overridden by the user, it's clamped to
97
// [0,1]. For values in this range, the approximation algorithm below
98
// should converge in < 20 iterations. For good measure, we impose an
99
// iteration limit as well.
100
static const int sIterationLimit = 100;
101
int iterations = 0;
102
while (iterations++ < sIterationLimit) {
103
x = xMin + (xMax - xMin) / 2.0f;
104
coef = 3.0f * x * (1.0f - x);
105
tx = coef * ((1.0f - x) * kP1 + x * kP2) + x * x * x;
106
if (FuzzyEqualsAdditive(tx, alpha)) {
107
break;
108
}
109
if (tx > alpha) {
110
xMax = x;
111
} else {
112
xMin = x;
113
}
114
}
115
mSplinePositions[i] = coef * ((1.0f - x) * kStartTension + x) + x * x * x;
116
}
117
mSplinePositions[kNumSamples] = 1.0f;
118
}
119
120
void CalculateCoefficients(float aTime, float* aOutDistanceCoef,
121
float* aOutVelocityCoef) {
122
*aOutDistanceCoef = 1.0f;
123
*aOutVelocityCoef = 0.0f;
124
const int index = static_cast<int>(kNumSamples * aTime);
125
if (index < kNumSamples) {
126
const float tInf = static_cast<float>(index) / kNumSamples;
127
const float dInf = mSplinePositions[index];
128
const float tSup = static_cast<float>(index + 1) / kNumSamples;
129
const float dSup = mSplinePositions[index + 1];
130
*aOutVelocityCoef = (dSup - dInf) / (tSup - tInf);
131
*aOutDistanceCoef = dInf + (aTime - tInf) * *aOutVelocityCoef;
132
}
133
}
134
135
private:
136
static const int kNumSamples = 100;
137
float mSplinePositions[kNumSamples + 1];
138
};
139
140
StaticAutoPtr<SplineConstants> gSplineConstants;
141
142
/* static */
143
void AndroidFlingPhysics::InitializeGlobalState() {
144
gSplineConstants = new SplineConstants();
145
ClearOnShutdown(&gSplineConstants);
146
}
147
148
void AndroidFlingPhysics::Init(const ParentLayerPoint& aStartingVelocity,
149
float aPLPPI) {
150
mVelocity = aStartingVelocity.Length();
151
// We should not have created a fling animation if there is no velocity.
152
MOZ_ASSERT(mVelocity != 0.0f);
153
const double tuningCoeff = ComputeDeceleration(aPLPPI);
154
mTargetDuration = ComputeFlingDuration(mVelocity, tuningCoeff);
155
MOZ_ASSERT(!mTargetDuration.IsZero());
156
mDurationSoFar = TimeDuration();
157
mLastPos = ParentLayerPoint();
158
mCurrentPos = ParentLayerPoint();
159
float coeffX = mVelocity == 0 ? 1.0f : aStartingVelocity.x / mVelocity;
160
float coeffY = mVelocity == 0 ? 1.0f : aStartingVelocity.y / mVelocity;
161
mTargetDistance = ComputeFlingDistance(mVelocity, tuningCoeff);
162
mTargetPos =
163
ParentLayerPoint(mTargetDistance * coeffX, mTargetDistance * coeffY);
164
const float hyp = mTargetPos.Length();
165
if (FuzzyEqualsAdditive(hyp, 0.0f)) {
166
mDeltaNorm = ParentLayerPoint(1, 1);
167
} else {
168
mDeltaNorm = ParentLayerPoint(mTargetPos.x / hyp, mTargetPos.y / hyp);
169
}
170
}
171
void AndroidFlingPhysics::Sample(const TimeDuration& aDelta,
172
ParentLayerPoint* aOutVelocity,
173
ParentLayerPoint* aOutOffset) {
174
float newVelocity;
175
if (SampleImpl(aDelta, &newVelocity)) {
176
*aOutOffset = (mCurrentPos - mLastPos);
177
*aOutVelocity = ParentLayerPoint(mDeltaNorm.x * newVelocity,
178
mDeltaNorm.y * newVelocity);
179
mLastPos = mCurrentPos;
180
} else {
181
*aOutOffset = (mTargetPos - mLastPos);
182
*aOutVelocity = ParentLayerPoint();
183
}
184
}
185
186
bool AndroidFlingPhysics::SampleImpl(const TimeDuration& aDelta,
187
float* aOutVelocity) {
188
mDurationSoFar += aDelta;
189
if (mDurationSoFar >= mTargetDuration) {
190
return false;
191
}
192
193
const float timeRatio =
194
mDurationSoFar.ToSeconds() / mTargetDuration.ToSeconds();
195
float distanceCoef = 1.0f;
196
float velocityCoef = 0.0f;
197
gSplineConstants->CalculateCoefficients(timeRatio, &distanceCoef,
198
&velocityCoef);
199
200
// The caller expects the velocity in pixels per _millisecond_.
201
*aOutVelocity =
202
velocityCoef * mTargetDistance / mTargetDuration.ToMilliseconds();
203
204
mCurrentPos = mTargetPos * distanceCoef;
205
206
ParentLayerPoint remainder = mTargetPos - mCurrentPos;
207
const float threshold = GetThresholdForFlingEnd();
208
if (fabsf(remainder.x) < threshold && fabsf(remainder.y) < threshold) {
209
return false;
210
}
211
212
return true;
213
}
214
215
} // namespace layers
216
} // namespace mozilla