Bug 1026023 - Part 2: Implement Physically Based Movement Model. r=mwoodrow

- Implemented the AxisPhysicsModel class, which encapsulates interpolation and
  integration of a 1-dimensional physics model in a frame-rate independent
  and stable manner.
- Implemented the AxisPhysicsMSDModel class, which models a generic
  1-dimensional Mass-Spring-Damper simulation.
- This physical movement simulation code has been implemented separately from
  the existing momentum code in Axis.cpp so that it can be utilized by
  both the compositor (AsyncPanZoomController) and main thread
  (nsGfxScrollFrame).
This commit is contained in:
Kearwood (Kip) Gilbert 2014-07-09 10:02:29 -07:00
parent 17ee816c8e
commit e17dcc92bc
5 changed files with 442 additions and 0 deletions

View File

@ -0,0 +1,100 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AxisPhysicsMSDModel.h"
#include <math.h> // for sqrt and fabs
namespace mozilla {
namespace layers {
/**
* Constructs an AxisPhysicsMSDModel with initial values for state.
*
* @param aInitialPosition sets the initial position of the simulated spring,
* in AppUnits.
* @param aInitialDestination sets the resting position of the simulated spring,
* in AppUnits.
* @param aInitialVelocity sets the initial velocity of the simulated spring,
* in AppUnits / second. Critically-damped and over-damped systems are
* guaranteed not to overshoot aInitialDestination if this is set to 0;
* however, it is possible to overshoot and oscillate if not set to 0 or
* the system is under-damped.
* @param aSpringConstant sets the strength of the simulated spring. Greater
* values of mSpringConstant result in a stiffer / stronger spring.
* @param aDampingRatio controls the amount of dampening force and determines
* if the system is under-damped, critically-damped, or over-damped.
*/
AxisPhysicsMSDModel::AxisPhysicsMSDModel(double aInitialPosition,
double aInitialDestination,
double aInitialVelocity,
double aSpringConstant,
double aDampingRatio)
: AxisPhysicsModel(aInitialPosition, aInitialVelocity)
, mDestination(aInitialDestination)
, mSpringConstant(aSpringConstant)
, mSpringConstantSqrtXTwo(sqrt(mSpringConstant) * 2.0)
, mDampingRatio(aDampingRatio)
{
}
AxisPhysicsMSDModel::~AxisPhysicsMSDModel()
{
}
double
AxisPhysicsMSDModel::Acceleration(const State &aState)
{
// Simulate a Mass-Damper-Spring Model; assume a unit mass
// Hookes Law: http://en.wikipedia.org/wiki/Hooke%27s_law
double spring_force = (mDestination - aState.p) * mSpringConstant;
double damp_force = -aState.v * mDampingRatio * mSpringConstantSqrtXTwo;
return spring_force + damp_force;
}
double
AxisPhysicsMSDModel::GetDestination()
{
return mDestination;
}
void
AxisPhysicsMSDModel::SetDestination(double aDestination)
{
mDestination = aDestination;
}
bool
AxisPhysicsMSDModel::IsFinished()
{
// In order to satisfy the condition of reaching the destination, the distance
// between the simulation position and the destination must be less than
// kFinishDistance while the speed is simultaneously less than
// kFinishVelocity. This enables an under-damped system to overshoot the
// destination when desired without prematurely triggering the finished state.
// As the number of app units per css pixel is 60 and retina / HiDPI displays
// may display two pixels for every css pixel, setting kFinishDistance to 30.0
// ensures that there will be no perceptable shift in position at the end
// of the animation.
const double kFinishDistance = 30.0;
// If kFinishVelocity is set too low, the animation may end long after
// oscillation has finished, resulting in unnecessary processing.
// If set too high, the animation may prematurely terminate when expected
// to overshoot the destination in an under-damped system.
// 60.0 was selected through experimentation that revealed that a
// critically damped system will terminate within 100ms.
const double kFinishVelocity = 60.0;
return fabs(mDestination - GetPosition ()) < kFinishDistance
&& fabs(GetVelocity()) <= kFinishVelocity;
}
}
}

View File

@ -0,0 +1,86 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_layers_AxisPhysicsMSDModel_h
#define mozilla_layers_AxisPhysicsMSDModel_h
#include "AxisPhysicsModel.h"
namespace mozilla {
namespace layers {
/**
* AxisPhysicsMSDModel encapsulates a 1-dimensional MSD (Mass-Spring-Damper)
* model. A unit mass is assumed.
*/
class AxisPhysicsMSDModel : public AxisPhysicsModel {
public:
AxisPhysicsMSDModel(double aInitialPosition, double aInitialDestination,
double aInitialVelocity, double aSpringConstant,
double aDampingRatio);
~AxisPhysicsMSDModel();
/**
* Gets the raw destination of this axis at this moment.
*/
double GetDestination();
/**
* Sets the raw destination of this axis at this moment.
*/
void SetDestination(double aDestination);
/**
* Returns true when the position is close to the destination and the
* velocity is low.
*/
bool IsFinished();
protected:
virtual double Acceleration(const State &aState);
private:
/**
* mDestination represents the target position and the resting position of
* the simulated spring.
*/
double mDestination;
/**
* Greater values of mSpringConstant result in a stiffer / stronger spring.
*/
double mSpringConstant;
/**
* mSpringConstantSqrtTimesTwo is calculated from mSpringConstant to reduce
* calculations performed in the inner loop.
*/
double mSpringConstantSqrtXTwo;
/**
* Damping Ratio: http://en.wikipedia.org/wiki/Damping_ratio
*
* When mDampingRatio < 1.0, this is an under damped system.
* - Overshoots destination and oscillates with the amplitude gradually
* decreasing to zero.
*
* When mDampingRatio == 1.0, this is a critically damped system.
* - Reaches destination as quickly as possible without oscillating.
*
* When mDampingRatio > 1.0, this is an over damped system.
* - Reaches destination (exponentially decays) without oscillating.
*/
double mDampingRatio;
};
}
}
#endif

View File

@ -0,0 +1,121 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AxisPhysicsModel.h"
namespace mozilla {
namespace layers {
/**
* The simulation is advanced forward in time with a fixed time step to ensure
* that it remains deterministic given variable framerates. To determine the
* position at any variable time, two samples are interpolated.
*
* kFixedtimestep is set to 120hz in order to ensure that every frame in a
* common 60hz refresh rate display will have at least one physics simulation
* sample. More accuracy can be obtained by reducing kFixedTimestep to smaller
* intervals, such as 240hz or 1000hz, at the cost of more CPU cycles. If
* kFixedTimestep is increased to much longer intervals, interpolation will
* become less effective at reducing temporal jitter and the simulation will
* lose accuracy.
*/
const double AxisPhysicsModel::kFixedTimestep = 1.0 / 120.0; // 120hz
/**
* Constructs an AxisPhysicsModel with initial values for state.
*
* @param aInitialPosition sets the initial position of the simulation,
* in AppUnits.
* @param aInitialVelocity sets the initial velocity of the simulation,
* in AppUnits / second.
*/
AxisPhysicsModel::AxisPhysicsModel(double aInitialPosition,
double aInitialVelocity)
: mProgress(1.0)
, mPrevState(aInitialPosition, aInitialVelocity)
, mNextState(aInitialPosition, aInitialVelocity)
{
}
AxisPhysicsModel::~AxisPhysicsModel()
{
}
double
AxisPhysicsModel::GetVelocity()
{
return LinearInterpolate(mPrevState.v, mNextState.v, mProgress);
}
double
AxisPhysicsModel::GetPosition()
{
return LinearInterpolate(mPrevState.p, mNextState.p, mProgress);
}
void
AxisPhysicsModel::SetVelocity(double aVelocity)
{
mNextState.v = aVelocity;
mNextState.p = GetPosition();
mProgress = 1.0;
}
void
AxisPhysicsModel::SetPosition(double aPosition)
{
mNextState.v = GetVelocity();
mNextState.p = aPosition;
mProgress = 1.0;
}
void
AxisPhysicsModel::Simulate(const TimeDuration& aDeltaTime)
{
for(mProgress += aDeltaTime.ToSeconds() / kFixedTimestep;
mProgress > 1.0; mProgress -= 1.0) {
Integrate(kFixedTimestep);
}
}
void
AxisPhysicsModel::Integrate(double aDeltaTime)
{
mPrevState = mNextState;
// RK4 (Runge-Kutta method) Integration
// http://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods
Derivative a = Evaluate( mNextState, 0.0, Derivative() );
Derivative b = Evaluate( mNextState, aDeltaTime * 0.5, a );
Derivative c = Evaluate( mNextState, aDeltaTime * 0.5, b );
Derivative d = Evaluate( mNextState, aDeltaTime, c );
double dpdt = 1.0 / 6.0 * (a.dp + 2.0 * (b.dp + c.dp) + d.dp);
double dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv);
mNextState.p += dpdt * aDeltaTime;
mNextState.v += dvdt * aDeltaTime;
}
AxisPhysicsModel::Derivative
AxisPhysicsModel::Evaluate(const State &aInitState, double aDeltaTime,
const Derivative &aDerivative)
{
State state( aInitState.p + aDerivative.dp*aDeltaTime, aInitState.v + aDerivative.dv*aDeltaTime );
return Derivative( state.v, Acceleration(state) );
}
double
AxisPhysicsModel::LinearInterpolate(double aV1, double aV2, double aBlend)
{
return aV1 * (1.0 - aBlend) + aV2 * aBlend;
}
}
}

View File

@ -0,0 +1,131 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_layers_AxisPhysicsModel_h
#define mozilla_layers_AxisPhysicsModel_h
#include "AxisPhysicsModel.h"
#include <sys/types.h> // for int32_t
#include "mozilla/TimeStamp.h" // for TimeDuration
namespace mozilla {
namespace layers {
/**
* AxisPhysicsModel encapsulates a generic 1-dimensional physically-based motion
* model.
*
* It performs frame-rate independent interpolation and RK4 integration for
* smooth animation with stable, deterministic behavior.
* Implementations are expected to subclass and override the Acceleration()
* method.
*/
class AxisPhysicsModel {
public:
AxisPhysicsModel(double aInitialPosition, double aInitialVelocity);
~AxisPhysicsModel();
/**
* Advance the physics simulation.
* |aDelta| is the time since the last sample.
*/
void Simulate(const TimeDuration& aDeltaTime);
/**
* Gets the raw velocity of this axis at this moment.
*/
double GetVelocity();
/**
* Sets the raw velocity of this axis at this moment.
*/
void SetVelocity(double aVelocity);
/**
* Gets the raw position of this axis at this moment.
*/
double GetPosition();
/**
* Sets the raw position of this axis at this moment.
*/
void SetPosition(double aPosition);
protected:
struct State
{
State(double ap, double av) : p(ap), v(av) {};
double p; // Position
double v; // Velocity
};
struct Derivative
{
Derivative() : dp(0.0), dv(0.0) {};
Derivative(double aDp, double aDv) : dp(aDp), dv(aDv) {};
double dp; // dp / delta time = Position
double dv; // dv / delta time = Velocity
};
/**
* Acceleration must be overridden and return the number of
* axis-position-units / second that should be added or removed from the
* velocity.
*/
virtual double Acceleration(const State &aState) = 0;
private:
/**
* Duration of fixed delta time step (seconds)
*/
static const double kFixedTimestep;
/**
* 0.0 - 1.0 value indicating progress between current and next simulation
* sample. Normalized to units of kFixedTimestep duration.
*/
double mProgress;
/**
* Sample of simulation state as it existed
* (1.0 - mProgress) * kFixedTimestep seconds in the past.
*/
State mPrevState;
/**
* Sample of simulation state as it will be in mProgress * kFixedTimestep
* seconds in the future.
*/
State mNextState;
/**
* Perform RK4 (Runge-Kutta method) Integration to calculate the next
* simulation sample.
*/
void Integrate(double aDeltaTime);
/**
* Apply delta velocity and position represented by aDerivative over
* aDeltaTime seconds, calculate new acceleration, and return new deltas.
*/
Derivative Evaluate(const State &aInitState, double aDeltaTime,
const Derivative &aDerivative);
/**
* Helper function for performing linear interpolation (lerp) of double's
*/
static double LinearInterpolate(double aV1, double aV2, double aBlend);
};
}
}
#endif

View File

@ -115,6 +115,8 @@ EXPORTS.mozilla.layers += [
'apz/util/ActiveElementManager.h',
'apz/util/APZCCallbackHelper.h',
'AtomicRefCountedWithFinalize.h',
'AxisPhysicsModel.h',
'AxisPhysicsMSDModel.h',
'basic/BasicCompositor.h',
'basic/MacIOSurfaceTextureHostBasic.h',
'basic/TextureHostBasic.h',
@ -241,6 +243,8 @@ UNIFIED_SOURCES += [
'apz/testutil/APZTestData.cpp',
'apz/util/ActiveElementManager.cpp',
'apz/util/APZCCallbackHelper.cpp',
'AxisPhysicsModel.cpp',
'AxisPhysicsMSDModel.cpp',
'basic/BasicCanvasLayer.cpp',
'basic/BasicColorLayer.cpp',
'basic/BasicCompositor.cpp',