Bug 639179 Part 5: Use exponential approximation for kinetic friction r=mbrubeck

This commit is contained in:
Benjamin Stover 2011-04-09 12:38:26 -07:00
parent 92031e035e
commit a5b826efe5
2 changed files with 50 additions and 49 deletions

View File

@ -395,8 +395,9 @@ pref("dom.max_script_run_time", 20);
pref("devtools.errorconsole.enabled", false);
// kinetic tweakables
pref("browser.ui.kinetic.updateInterval", 30);
pref("browser.ui.kinetic.decelerationRate", 20);
pref("browser.ui.kinetic.updateInterval", 16);
pref("browser.ui.kinetic.exponentialC", 1400);
pref("browser.ui.kinetic.polynomialC", 100);
pref("browser.ui.kinetic.swipeLength", 160);
// zooming

View File

@ -834,8 +834,9 @@ function KineticController(aPanBy, aEndCallback) {
// How often do we change the position of the scroll pane? Too often and panning may jerk near
// the end. Too little and panning will be choppy. In milliseconds.
this._updateInterval = Services.prefs.getIntPref("browser.ui.kinetic.updateInterval");
// "Friction" of the scroll pane. The lower, the less friction and the further distance traveled.
this._decelerationRate = Services.prefs.getIntPref("browser.ui.kinetic.decelerationRate") / 10000;
// Constants that affect the "friction" of the scroll pane.
this._exponentialC = Services.prefs.getIntPref("browser.ui.kinetic.exponentialC");
this._polynomialC = Services.prefs.getIntPref("browser.ui.kinetic.polynomialC") / 1000000;
// Number of milliseconds that can contain a swipe. Movements earlier than this are disregarded.
this._swipeLength = Services.prefs.getIntPref("browser.ui.kinetic.swipeLength");
@ -855,35 +856,29 @@ KineticController.prototype = {
},
_startTimer: function _startTimer() {
// Use closed form of a parabola to calculate each position for panning.
// x(t) = v0*t + .5*t^2*a
// where: v0 is initial velocity
// a is acceleration
// t is time elapsed
//
// x(t)
// ^
// | |
// |
// | |
// | ....^^^^....
// | ...^^ | ^^...
// | ...^ ^...
// |.. | ..
// -----------------------------------> t
// t0 tf=-v0/a
//
// Using this formula, distance moved is independent of the time between each frame, unlike time
// step approaches. Once the time is up, set the position to x(tf) and stop the timer.
let self = this;
let lastx = this._position; // track last position vector because pan takes differences
let lastp = this._position; // track last position vector because pan takes deltas
let v0 = this._velocity; // initial velocity
let a = this._acceleration; // acceleration
let c = this._exponentialC;
let p = new Point(0, 0);
let dx, dy, t, realt;
// Temporary "bins" so that we don't create new objects during pan.
let aBin = new Point(0, 0);
let v0Bin = new Point(0, 0);
let self = this;
function calcP(v0, a, t) {
// Important traits for this function:
// p(t=0) is 0
// p'(t=0) is v0
//
// We use exponential to get a smoother stop, but by itself exponential
// is too smooth at the end. Adding a polynomial with the appropriate
// weight helps to balance
return v0 * Math.exp(-t / c) * -c + a * t * t + v0 * c;
}
this._calcV = function(v0, a, t) {
return v0 * Math.exp(-t / c) + 2 * a * t;
}
let callback = {
onBeforePaint: function kineticHandleEvent(timeStamp) {
@ -895,29 +890,27 @@ KineticController.prototype = {
// To make animation end fast enough but to keep smoothness, average the ideal
// time frame (smooth animation) with the actual time lapse (end fast enough).
// Animation will never take longer than 2 times the ideal length of time.
let realt = timeStamp - self._initialTime;
realt = timeStamp - self._initialTime;
self._time += self._updateInterval;
let t = (self._time + realt) / 2;
t = (self._time + realt) / 2;
// Calculate new position using x(t) formula.
let x = v0Bin.set(v0).scale(t).add(aBin.set(a).scale(0.5 * t * t));
let dx = Math.round(x.x - lastx.x);
let dy = Math.round(x.y - lastx.y);
// Calculate new position.
p.x = calcP(v0.x, a.x, t);
p.y = calcP(v0.y, a.y, t);
dx = Math.round(p.x - lastp.x);
dy = Math.round(p.y - lastp.y);
// Test to see if movement is finished for each component. As seen in graph, we want the
// final position to be at tf.
if (t >= -v0.x / a.x) {
// Plug in t=-v0/a into x(t) to get final position.
dx = Math.round(-v0.x * v0.x / 2 / a.x - lastx.x);
// Reset components. Next frame: a's component will be 0 and t >= NaN will be false.
lastx.x = 0;
// Test to see if movement is finished for each component.
if (dx * a.x > 0) {
dx = 0;
lastp.x = 0;
v0.x = 0;
a.x = 0;
}
// Symmetric to above case.
if (t >= -v0.y / a.y) {
dy = Math.round(-v0.y * v0.y / 2 / a.y - lastx.y);
lastx.y = 0;
if (dy * a.y > 0) {
dy = 0;
lastp.y = 0;
v0.y = 0;
a.y = 0;
}
@ -928,7 +921,7 @@ KineticController.prototype = {
let panStop = false;
if (dx != 0 || dy != 0) {
try { panStop = !self._panBy(-dx, -dy, true); } catch (e) {}
lastx.add(dx, dy);
lastp.add(dx, dy);
}
if (panStop)
@ -949,6 +942,12 @@ KineticController.prototype = {
return x ? ((x > 0) ? 1 : -1) : 0;
}
function clampFromZero(x, closerToZero, furtherFromZero) {
if (x >= 0)
return Math.max(closerToZero, Math.min(furtherFromZero, x));
return Math.min(-closerToZero, Math.max(-furtherFromZero, x));
}
let mb = this.momentumBuffer;
let mblen = this.momentumBuffer.length;
@ -971,9 +970,10 @@ KineticController.prototype = {
let currentVelocityY = 0;
if (this.isActive()) {
// If active, then we expect this._calcV to be defined.
let currentTime = Date.now() - this._initialTime;
currentVelocityX = Util.clamp(this._velocity.x + this._acceleration.x * currentTime, -kMaxVelocity, kMaxVelocity);
currentVelocityY = Util.clamp(this._velocity.y + this._acceleration.y * currentTime, -kMaxVelocity, kMaxVelocity);
currentVelocityX = Util.clamp(this._calcV(this._velocity.x, this._acceleration.x, currentTime), -kMaxVelocity, kMaxVelocity);
currentVelocityY = Util.clamp(this._calcV(this._velocity.y, this._acceleration.y, currentTime), -kMaxVelocity, kMaxVelocity);
}
if (currentVelocityX * this._velocity.x <= 0)
@ -986,7 +986,7 @@ KineticController.prototype = {
this._velocity.y = clampFromZero((distanceY / swipeTime) + currentVelocityY, Math.abs(currentVelocityY), 6);
// Set acceleration vector to opposite signs of velocity
this._acceleration.set(this._velocity.clone().map(sign).scale(-this._decelerationRate));
this._acceleration.set(this._velocity.clone().map(sign).scale(-this._polynomialC));
this._position.set(0, 0);
this._initialTime = mozAnimationStartTime;