Merge branch 'newton'
diff --git a/packages/newton/README.md b/packages/newton/README.md
new file mode 100644
index 0000000..ccd16ea
--- /dev/null
+++ b/packages/newton/README.md
@@ -0,0 +1,9 @@
+# Newton
+[](https://travis-ci.org/flutter/newton)
+[](https://coveralls.io/r/domokit/newton?branch=master)
+
+Simple Physics Simulations for Dart. Springs, friction, gravity, etc.
+
+To run the tests:
+pub get
+dart test/newton_test.dart
diff --git a/packages/newton/lib/newton.dart b/packages/newton/lib/newton.dart
new file mode 100644
index 0000000..c70ca3c
--- /dev/null
+++ b/packages/newton/lib/newton.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library newton;
+
+import 'dart:math' as math;
+
+part 'src/simulation.dart';
+part 'src/simulation_group.dart';
+part 'src/tolerance.dart';
+part 'src/utils.dart';
+
+part 'src/friction_simulation.dart';
+part 'src/gravity_simulation.dart';
+part 'src/scroll_simulation.dart';
+part 'src/spring_simulation.dart';
+part 'src/spring_solution.dart';
diff --git a/packages/newton/lib/src/friction_simulation.dart b/packages/newton/lib/src/friction_simulation.dart
new file mode 100644
index 0000000..38671b9
--- /dev/null
+++ b/packages/newton/lib/src/friction_simulation.dart
@@ -0,0 +1,68 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of newton;
+
+class FrictionSimulation extends Simulation {
+ final double _drag;
+ final double _dragLog;
+ final double _x;
+ final double _v;
+
+ FrictionSimulation(double drag, double position, double velocity)
+ : _drag = drag,
+ _dragLog = math.log(drag),
+ _x = position,
+ _v = velocity;
+
+ // Return the drag value for a FrictionSimulation whose x() and dx() values pass
+ // through the specified start and end position/velocity values.
+ //
+ // Total time to reach endVelocity is just: (log(endVelocity) / log(startVelocity)) / log(_drag)
+ // or (log(v1) - log(v0)) / log(D), given v = v0 * D^t per the dx() function below.
+ // Solving for D given x(time) is trickier. Algebra courtesy of Wolfram Alpha:
+ // x1 = x0 + (v0 * D^((log(v1) - log(v0)) / log(D))) / log(D) - v0 / log(D), find D
+ static double _dragFor(double startPosition, double endPosition, double startVelocity, double endVelocity) {
+ return math.pow(math.E, (startVelocity - endVelocity) / (startPosition - endPosition));
+ }
+
+ // A friction simulation that starts and ends at the specified positions
+ // and velocities.
+ factory FrictionSimulation.through(double startPosition, double endPosition, double startVelocity, double endVelocity) {
+ return new FrictionSimulation(
+ _dragFor(startPosition, endPosition, startVelocity, endVelocity),
+ startPosition,
+ startVelocity)
+ .. tolerance = new Tolerance(velocity: endVelocity.abs());
+ }
+
+ double x(double time) => _x + _v * math.pow(_drag, time) / _dragLog - _v / _dragLog;
+
+ double dx(double time) => _v * math.pow(_drag, time);
+
+ @override
+ bool isDone(double time) => dx(time).abs() < this.tolerance.velocity;
+}
+
+class BoundedFrictionSimulation extends FrictionSimulation {
+ BoundedFrictionSimulation(
+ double drag,
+ double position,
+ double velocity,
+ double this._minX,
+ double this._maxX) : super(drag, position, velocity);
+
+ final double _minX;
+ final double _maxX;
+
+ double x(double time) {
+ return super.x(time).clamp(_minX, _maxX);
+ }
+
+ bool isDone(double time) {
+ return super.isDone(time) ||
+ (x(time) - _minX).abs() < tolerance.distance ||
+ (x(time) - _maxX).abs() < tolerance.distance;
+ }
+}
diff --git a/packages/newton/lib/src/gravity_simulation.dart b/packages/newton/lib/src/gravity_simulation.dart
new file mode 100644
index 0000000..2f002b7
--- /dev/null
+++ b/packages/newton/lib/src/gravity_simulation.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of newton;
+
+class GravitySimulation extends Simulation {
+ final double _x;
+ final double _v;
+ final double _a;
+ final double _end;
+
+ GravitySimulation(
+ double acceleration, double distance, double endDistance, double velocity)
+ : _a = acceleration,
+ _x = distance,
+ _v = velocity,
+ _end = endDistance;
+
+ double x(double time) => _x + _v * time + 0.5 * _a * time * time;
+
+ double dx(double time) => _v + time * _a;
+
+ @override
+ bool isDone(double time) => x(time).abs() >= _end;
+}
diff --git a/packages/newton/lib/src/scroll_simulation.dart b/packages/newton/lib/src/scroll_simulation.dart
new file mode 100644
index 0000000..64e0a45
--- /dev/null
+++ b/packages/newton/lib/src/scroll_simulation.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of newton;
+
+/// Simulates kinetic scrolling behavior between a leading and trailing
+/// boundary. Friction is applied within the extends and a spring action applied
+/// at the boundaries. This simulation can only step forward.
+class ScrollSimulation extends SimulationGroup {
+ ScrollSimulation(
+ double position,
+ double velocity,
+ this._leadingExtent,
+ this._trailingExtent,
+ this._spring,
+ this._drag) {
+ _chooseSimulation(position, velocity, 0.0);
+ }
+
+ final double _leadingExtent;
+ final double _trailingExtent;
+ final SpringDescription _spring;
+ final double _drag;
+
+ bool _isSpringing = false;
+ Simulation _currentSimulation;
+ double _offset = 0.0;
+
+ @override
+ bool step(double time) => _chooseSimulation(
+ _currentSimulation.x(time - _offset),
+ _currentSimulation.dx(time - _offset), time);
+
+ @override
+ Simulation get currentSimulation => _currentSimulation;
+
+ @override
+ double get currentIntervalOffset => _offset;
+
+ bool _chooseSimulation(double position, double velocity, double intervalOffset) {
+ if (_spring == null && (position > _trailingExtent || position < _leadingExtent))
+ return false;
+
+ /// This simulation can only step forward.
+ if (!_isSpringing) {
+ if (position > _trailingExtent) {
+ _isSpringing = true;
+ _offset = intervalOffset;
+ _currentSimulation = new ScrollSpringSimulation(_spring, position, _trailingExtent, velocity);
+ return true;
+ } else if (position < _leadingExtent) {
+ _isSpringing = true;
+ _offset = intervalOffset;
+ _currentSimulation = new ScrollSpringSimulation(_spring, position, _leadingExtent, velocity);
+ return true;
+ }
+ }
+
+ if (_currentSimulation == null) {
+ _currentSimulation = new FrictionSimulation(_drag, position, velocity);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/packages/newton/lib/src/simulation.dart b/packages/newton/lib/src/simulation.dart
new file mode 100644
index 0000000..e374c9d
--- /dev/null
+++ b/packages/newton/lib/src/simulation.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of newton;
+
+abstract class Simulatable {
+ /// The current position of the object in the simulation
+ double x(double time);
+
+ /// The current velocity of the object in the simulation
+ double dx(double time);
+}
+
+/// The base class for all simulations. The user is meant to instantiate an
+/// instance of a simulation and query the same for the position and velocity
+/// of the body at a given interval.
+abstract class Simulation implements Simulatable {
+ Tolerance tolerance = toleranceDefault;
+
+ /// Returns if the simulation is done at a given time
+ bool isDone(double time);
+}
diff --git a/packages/newton/lib/src/simulation_group.dart b/packages/newton/lib/src/simulation_group.dart
new file mode 100644
index 0000000..a7359da
--- /dev/null
+++ b/packages/newton/lib/src/simulation_group.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of newton;
+
+/// The abstract base class for all composite simulations. Concrete subclasses
+/// must implement the appropriate methods to select the appropriate simulation
+/// at a given time interval. The simulation group takes care to call the `step`
+/// method at appropriate intervals. If more fine grained control over the the
+/// step is necessary, subclasses may override `Simulatable` methods.
+abstract class SimulationGroup extends Simulation {
+
+ /// The currently active simulation
+ Simulation get currentSimulation;
+
+ /// The time offset applied to the currently active simulation;
+ double get currentIntervalOffset;
+
+ /// Called when a significant change in the interval is detected. Subclasses
+ /// must decide if the the current simulation must be switched (or updated).
+ /// The result is whether the simulation was switched in this step.
+ bool step(double time);
+
+ double x(double time) {
+ _stepIfNecessary(time);
+ return currentSimulation.x(time - currentIntervalOffset);
+ }
+
+ double dx(double time) {
+ _stepIfNecessary(time);
+ return currentSimulation.dx(time - currentIntervalOffset);
+ }
+
+ @override
+ void set tolerance(Tolerance t) {
+ this.currentSimulation.tolerance = t;
+ super.tolerance = t;
+ }
+
+ @override
+ bool isDone(double time) {
+ _stepIfNecessary(time);
+ return currentSimulation.isDone(time - currentIntervalOffset);
+ }
+
+ double _lastStep = -1.0;
+ void _stepIfNecessary(double time) {
+ if (_nearEqual(_lastStep, time, toleranceDefault.time)) {
+ return;
+ }
+
+ _lastStep = time;
+ if (step(time)) {
+ this.currentSimulation.tolerance = this.tolerance;
+ }
+ }
+}
diff --git a/packages/newton/lib/src/spring_simulation.dart b/packages/newton/lib/src/spring_simulation.dart
new file mode 100644
index 0000000..143f96c
--- /dev/null
+++ b/packages/newton/lib/src/spring_simulation.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of newton;
+
+class SpringDescription {
+ /// The mass of the spring (m)
+ final double mass;
+
+ /// The spring constant (k)
+ final double springConstant;
+
+ /// The damping coefficient.
+ /// Note: Not to be confused with the damping ratio. Use the separate
+ /// constructor provided for this purpose
+ final double damping;
+
+ SpringDescription(
+ {double this.mass, double this.springConstant, double this.damping}) {
+ assert(mass != null);
+ assert(springConstant != null);
+ assert(damping != null);
+ }
+
+ /// Create a spring given the mass, spring constant and the damping ratio. The
+ /// damping ratio is especially useful trying to determing the type of spring
+ /// to create. A ratio of 1.0 creates a critically damped spring, > 1.0
+ /// creates an overdamped spring and < 1.0 an underdamped one.
+ SpringDescription.withDampingRatio(
+ {double mass, double springConstant, double ratio: 1.0})
+ : mass = mass,
+ springConstant = springConstant,
+ damping = ratio * 2.0 * math.sqrt(mass * springConstant);
+}
+
+enum SpringType { unknown, criticallyDamped, underDamped, overDamped, }
+
+/// Creates a spring simulation. Depending on the spring description, a
+/// critically, under or overdamped spring will be created.
+class SpringSimulation extends Simulation {
+ final double _endPosition;
+
+ final _SpringSolution _solution;
+
+ /// A spring description with the provided spring description, start distance,
+ /// end distance and velocity.
+ SpringSimulation(
+ SpringDescription desc, double start, double end, double velocity)
+ : this._endPosition = end,
+ _solution = new _SpringSolution(desc, start - end, velocity);
+
+ SpringType get type => _solution.type;
+
+ double x(double time) => _endPosition + _solution.x(time);
+
+ double dx(double time) => _solution.dx(time);
+
+ @override
+ bool isDone(double time) =>
+ _nearEqual(x(time), _endPosition, this.tolerance.distance) &&
+ _nearZero(dx(time), this.tolerance.velocity);
+}
+
+/// A SpringSimulation where the value of x() is guaranteed to have exactly the
+/// end value when the simulation isDone().
+class ScrollSpringSimulation extends SpringSimulation {
+ ScrollSpringSimulation(SpringDescription desc, double start, double end, double velocity)
+ : super(desc, start, end, velocity);
+
+ bool _isDone(double position, double velocity) {
+ return _nearEqual(position, _endPosition, tolerance.distance) && _nearZero(velocity, tolerance.velocity);
+ }
+
+ @override
+ double x(double time) {
+ double xAtTime = super.x(time);
+ return _isDone(xAtTime, dx(time)) ? _endPosition : xAtTime;
+ }
+
+ @override
+ bool isDone(double time) => _isDone(x(time), dx(time));
+}
diff --git a/packages/newton/lib/src/spring_solution.dart b/packages/newton/lib/src/spring_solution.dart
new file mode 100644
index 0000000..3241311
--- /dev/null
+++ b/packages/newton/lib/src/spring_solution.dart
@@ -0,0 +1,118 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of newton;
+
+abstract class _SpringSolution implements Simulatable {
+ factory _SpringSolution(
+ SpringDescription desc, double initialPosition, double initialVelocity) {
+ double cmk =
+ desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
+
+ if (cmk == 0.0) {
+ return new _CriticalSolution(desc, initialPosition, initialVelocity);
+ } else if (cmk > 0.0) {
+ return new _OverdampedSolution(desc, initialPosition, initialVelocity);
+ } else {
+ return new _UnderdampedSolution(desc, initialPosition, initialVelocity);
+ }
+
+ return null;
+ }
+
+ SpringType get type;
+}
+
+class _CriticalSolution implements _SpringSolution {
+ final double _r, _c1, _c2;
+
+ factory _CriticalSolution(
+ SpringDescription desc, double distance, double velocity) {
+ final double r = -desc.damping / (2.0 * desc.mass);
+ final double c1 = distance;
+ final double c2 = velocity / (r * distance);
+ return new _CriticalSolution.withArgs(r, c1, c2);
+ }
+
+ SpringType get type => SpringType.criticallyDamped;
+
+ _CriticalSolution.withArgs(double r, double c1, double c2)
+ : _r = r,
+ _c1 = c1,
+ _c2 = c2;
+
+ double x(double time) => (_c1 + _c2 * time) * math.pow(math.E, _r * time);
+
+ double dx(double time) {
+ final double power = math.pow(math.E, _r * time);
+ return _r * (_c1 + _c2 * time) * power + _c2 * power;
+ }
+}
+
+class _OverdampedSolution implements _SpringSolution {
+ final double _r1, _r2, _c1, _c2;
+
+ factory _OverdampedSolution(
+ SpringDescription desc, double distance, double velocity) {
+ final double cmk =
+ desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
+
+ final double r1 = (-desc.damping - math.sqrt(cmk)) / (2.0 * desc.mass);
+ final double r2 = (-desc.damping + math.sqrt(cmk)) / (2.0 * desc.mass);
+ final double c2 = (velocity - r1 * distance) / (r2 - r1);
+ final double c1 = distance - c2;
+
+ return new _OverdampedSolution.withArgs(r1, r2, c1, c2);
+ }
+
+ _OverdampedSolution.withArgs(double r1, double r2, double c1, double c2)
+ : _r1 = r1,
+ _r2 = r2,
+ _c1 = c1,
+ _c2 = c2;
+
+ SpringType get type => SpringType.overDamped;
+
+ double x(double time) =>
+ (_c1 * math.pow(math.E, _r1 * time) + _c2 * math.pow(math.E, _r2 * time));
+
+ double dx(double time) => (_c1 * _r1 * math.pow(math.E, _r1 * time) +
+ _c2 * _r2 * math.pow(math.E, _r2 * time));
+}
+
+class _UnderdampedSolution implements _SpringSolution {
+ final double _w, _r, _c1, _c2;
+
+ factory _UnderdampedSolution(
+ SpringDescription desc, double distance, double velocity) {
+ final double w = math.sqrt(4.0 * desc.mass * desc.springConstant -
+ desc.damping * desc.damping) /
+ (2.0 * desc.mass);
+ final double r = -(desc.damping / 2.0 * desc.mass);
+ final double c1 = distance;
+ final double c2 = (velocity - r * distance) / w;
+
+ return new _UnderdampedSolution.withArgs(w, r, c1, c2);
+ }
+
+ _UnderdampedSolution.withArgs(double w, double r, double c1, double c2)
+ : _w = w,
+ _r = r,
+ _c1 = c1,
+ _c2 = c2;
+
+ SpringType get type => SpringType.underDamped;
+
+ double x(double time) => math.pow(math.E, _r * time) *
+ (_c1 * math.cos(_w * time) + _c2 * math.sin(_w * time));
+
+ double dx(double time) {
+ final double power = math.pow(math.E, _r * time);
+ final double cosine = math.cos(_w * time);
+ final double sine = math.sin(_w * time);
+
+ return power * (_c2 * _w * cosine - _c1 * _w * sine) +
+ _r * power * (_c2 * sine + _c1 * cosine);
+ }
+}
diff --git a/packages/newton/lib/src/tolerance.dart b/packages/newton/lib/src/tolerance.dart
new file mode 100644
index 0000000..b52d075
--- /dev/null
+++ b/packages/newton/lib/src/tolerance.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of newton;
+
+class Tolerance {
+ final double distance;
+ final double time;
+ final double velocity;
+
+ const Tolerance({this.distance: epsilonDefault, this.time: epsilonDefault,
+ this.velocity: epsilonDefault});
+}
+
+const double epsilonDefault = 1e-3;
+const Tolerance toleranceDefault = const Tolerance();
diff --git a/packages/newton/lib/src/utils.dart b/packages/newton/lib/src/utils.dart
new file mode 100644
index 0000000..75709a3
--- /dev/null
+++ b/packages/newton/lib/src/utils.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of newton;
+
+bool _nearEqual(double a, double b, double epsilon) =>
+ (a > (b - epsilon)) && (a < (b + epsilon));
+
+bool _nearZero(double a, double epsilon) => _nearEqual(a, 0.0, epsilon);
diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart
new file mode 100644
index 0000000..20efa21
--- /dev/null
+++ b/packages/newton/test/newton_test.dart
@@ -0,0 +1,252 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library simple_physics.test;
+
+import 'package:test/test.dart';
+
+import 'package:newton/newton.dart';
+
+void main() {
+ test('test_friction', () {
+ var friction = new FrictionSimulation(0.3, 100.0, 400.0);
+
+ friction.tolerance = const Tolerance(velocity: 1.0);
+
+ expect(friction.isDone(0.0), false);
+ expect(friction.x(0.0), 100);
+ expect(friction.dx(0.0), 400.0);
+
+ expect(friction.x(1.0) > 330 && friction.x(1.0) < 335, true);
+
+ expect(friction.dx(1.0), 120.0);
+ expect(friction.dx(2.0), 36.0);
+ expect(friction.dx(3.0), 10.8);
+ expect(friction.dx(4.0) < 3.5, true);
+
+ expect(friction.isDone(5.0), true);
+ expect(friction.x(5.0) > 431 && friction.x(5.0) < 432, true);
+ });
+
+ test('test_friction_through', () {
+ // Use a normal FrictionSimulation to generate start and end
+ // velocity and positions with drag = 0.025.
+ var startPosition = 10.0;
+ var startVelocity = 600.0;
+ var f = new FrictionSimulation(0.025, startPosition, startVelocity);
+ var endPosition = f.x(1.0);
+ var endVelocity = f.dx(1.0);
+ expect(endPosition, greaterThan(startPosition));
+ expect(endVelocity, lessThan(startVelocity));
+
+ // Verify that that the "through" FrictionSimulation ends up at
+ // endPosition and endVelocity; implies that it computed the right
+ // value for _drag.
+ var friction = new FrictionSimulation.through(
+ startPosition, endPosition, startVelocity, endVelocity);
+ expect(friction.isDone(0.0), false);
+ expect(friction.x(0.0), 10.0);
+ expect(friction.dx(0.0), 600.0);
+
+ double epsilon = 1e-4;
+ expect(friction.isDone(1.0 + epsilon), true);
+ expect(friction.x(1.0), closeTo(endPosition, epsilon));
+ expect(friction.dx(1.0), closeTo(endVelocity, epsilon));
+
+ // Same scenario as above except that the velocities are
+ // are negative.
+ startPosition = 1000.0;
+ startVelocity = -500.0;
+ f = new FrictionSimulation(0.025, 1000.0, -500.0);
+ endPosition = f.x(1.0);
+ endVelocity = f.dx(1.0);
+ expect(endPosition, lessThan(startPosition));
+ expect(endVelocity, greaterThan(startVelocity));
+
+ friction = new FrictionSimulation.through(
+ startPosition, endPosition, startVelocity, endVelocity);
+ expect(friction.isDone(1.0 + epsilon), true);
+ expect(friction.x(1.0), closeTo(endPosition, epsilon));
+ expect(friction.dx(1.0), closeTo(endVelocity, epsilon));
+ });
+
+ test('test_gravity', () {
+ var gravity = new GravitySimulation(200.0, 100.0, 600.0, 0.0);
+
+ expect(gravity.isDone(0.0), false);
+ expect(gravity.x(0.0), 100.0);
+ expect(gravity.dx(0.0), 0.0);
+
+ // Starts at 100
+ expect(gravity.x(0.25), 106.25);
+ expect(gravity.x(0.50), 125);
+ expect(gravity.x(0.75), 156.25);
+ expect(gravity.x(1.00), 200);
+ expect(gravity.x(1.25), 256.25);
+ expect(gravity.x(1.50), 325);
+ expect(gravity.x(1.75), 406.25);
+
+ // Starts at 0.0
+ expect(gravity.dx(0.25), 50.0);
+ expect(gravity.dx(0.50), 100);
+ expect(gravity.dx(0.75), 150.00);
+ expect(gravity.dx(1.00), 200.0);
+ expect(gravity.dx(1.25), 250.0);
+ expect(gravity.dx(1.50), 300);
+ expect(gravity.dx(1.75), 350);
+
+ expect(gravity.isDone(2.5), true);
+ expect(gravity.x(2.5), 725);
+ expect(gravity.dx(2.5), 500.0);
+ });
+
+ test('spring_types', () {
+ var crit = new SpringSimulation(new SpringDescription.withDampingRatio(
+ mass: 1.0, springConstant: 100.0), 0.0, 300.0, 0.0);
+ expect(crit.type, SpringType.criticallyDamped);
+
+ crit = new SpringSimulation(new SpringDescription.withDampingRatio(
+ mass: 1.0, springConstant: 100.0, ratio: 1.0), 0.0, 300.0, 0.0);
+ expect(crit.type, SpringType.criticallyDamped);
+
+ var under = new SpringSimulation(new SpringDescription.withDampingRatio(
+ mass: 1.0, springConstant: 100.0, ratio: 0.75), 0.0, 300.0, 0.0);
+ expect(under.type, SpringType.underDamped);
+
+ var over = new SpringSimulation(new SpringDescription.withDampingRatio(
+ mass: 1.0, springConstant: 100.0, ratio: 1.25), 0.0, 300.0, 0.0);
+ expect(over.type, SpringType.overDamped);
+
+ // Just so we don't forget how to create a desc without the ratio.
+ var other = new SpringSimulation(
+ new SpringDescription(mass: 1.0, springConstant: 100.0, damping: 20.0),
+ 0.0, 20.0, 20.0);
+ expect(other.type, SpringType.criticallyDamped);
+ });
+
+ test('crit_spring', () {
+ var crit = new SpringSimulation(new SpringDescription.withDampingRatio(
+ mass: 1.0, springConstant: 100.0, ratio: 1.0), 0.0, 500.0, 0.0);
+
+ crit.tolerance = const Tolerance(distance: 0.01, velocity: 0.01);
+
+ expect(crit.type, SpringType.criticallyDamped);
+
+ expect(crit.isDone(0.0), false);
+ expect(crit.x(0.0), 0.0);
+ expect(crit.dx(0.0), 5000.0);
+
+ expect(crit.x(0.25).floor(), 458.0);
+ expect(crit.x(0.50).floor(), 496.0);
+ expect(crit.x(0.75).floor(), 499.0);
+
+ expect(crit.dx(0.25).floor(), 410);
+ expect(crit.dx(0.50).floor(), 33);
+ expect(crit.dx(0.75).floor(), 2);
+
+ expect(crit.isDone(1.50), true);
+ expect(crit.x(1.5) > 499.0 && crit.x(1.5) < 501.0, true);
+ expect(crit.dx(1.5) < 0.1, true /* basically within tolerance */);
+ });
+
+ test('overdamped_spring', () {
+ var over = new SpringSimulation(new SpringDescription.withDampingRatio(
+ mass: 1.0, springConstant: 100.0, ratio: 1.25), 0.0, 500.0, 0.0);
+
+ over.tolerance = const Tolerance(distance: 0.01, velocity: 0.01);
+
+ expect(over.type, SpringType.overDamped);
+
+ expect(over.isDone(0.0), false);
+ expect(over.x(0.0), 0.0);
+
+ expect(over.x(0.5).floor(), 445.0);
+ expect(over.x(1.0).floor(), 495.0);
+ expect(over.x(1.5).floor(), 499.0);
+
+ expect(over.dx(0.5).floor(), 273.0);
+ expect(over.dx(1.0).floor(), 22.0);
+ expect(over.dx(1.5).floor(), 1.0);
+
+ expect(over.isDone(3.0), true);
+ });
+
+ test('underdamped_spring', () {
+ var under = new SpringSimulation(new SpringDescription.withDampingRatio(
+ mass: 1.0, springConstant: 100.0, ratio: 0.25), 0.0, 300.0, 0.0);
+ expect(under.type, SpringType.underDamped);
+
+ expect(under.isDone(0.0), false);
+
+ // Overshot with negative velocity
+ expect(under.x(1.0).floor(), 325);
+ expect(under.dx(1.0).floor(), -65);
+
+ expect(under.dx(6.0).floor(), 0.0);
+ expect(under.x(6.0).floor(), 299);
+
+ expect(under.isDone(6.0), true);
+ });
+
+ test('test_kinetic_scroll', () {
+ var spring = new SpringDescription.withDampingRatio(
+ mass: 1.0, springConstant: 50.0, ratio: 0.5);
+
+ var scroll = new ScrollSimulation(100.0, 800.0, 0.0, 300.0, spring, 0.3);
+ scroll.tolerance = const Tolerance(velocity: 0.5, distance: 0.1);
+ expect(scroll.isDone(0.0), false);
+ expect(scroll.isDone(0.5), false); // switch from friction to spring
+ expect(scroll.isDone(3.5), true);
+
+ var scroll2 = new ScrollSimulation(100.0, -800.0, 0.0, 300.0, spring, 0.3);
+ scroll2.tolerance = const Tolerance(velocity: 0.5, distance: 0.1);
+ expect(scroll2.isDone(0.0), false);
+ expect(scroll2.isDone(0.5), false); // switch from friction to spring
+ expect(scroll2.isDone(3.5), true);
+ });
+
+ test('scroll_with_inf_edge_ends', () {
+ var spring = new SpringDescription.withDampingRatio(
+ mass: 1.0, springConstant: 50.0, ratio: 0.5);
+
+ var scroll =
+ new ScrollSimulation(100.0, 400.0, 0.0, double.INFINITY, spring, 0.3);
+ scroll.tolerance = const Tolerance(velocity: 1.0);
+
+ expect(scroll.isDone(0.0), false);
+ expect(scroll.x(0.0), 100);
+ expect(scroll.dx(0.0), 400.0);
+
+ expect(scroll.x(1.0) > 330 && scroll.x(1.0) < 335, true);
+
+ expect(scroll.dx(1.0), 120.0);
+ expect(scroll.dx(2.0), 36.0);
+ expect(scroll.dx(3.0), 10.8);
+ expect(scroll.dx(4.0) < 3.5, true);
+
+ expect(scroll.isDone(5.0), true);
+ expect(scroll.x(5.0) > 431 && scroll.x(5.0) < 432, true);
+
+ // We should never switch
+ expect(scroll.currentIntervalOffset, 0.0);
+ });
+
+ test('over/under scroll spring', () {
+ var spring = new SpringDescription.withDampingRatio(mass: 1.0, springConstant: 170.0, ratio: 1.1);
+ var scroll = new ScrollSimulation(500.0, -7500.0, 0.0, 1000.0, spring, 0.025);
+ scroll.tolerance = new Tolerance(velocity: 45.0, distance: 1.5);
+
+ expect(scroll.isDone(0.0), false);
+ expect(scroll.x(0.0), closeTo(500.0, .0001));
+ expect(scroll.dx(0.0), closeTo(-7500.0, .0001));
+
+ expect(scroll.isDone(0.025), false);
+ expect(scroll.x(0.025), closeTo(320.0, 1.0));
+ expect(scroll.dx(0.25), closeTo(-2982, 1.0));
+
+ expect(scroll.isDone(2.0), true);
+ expect(scroll.x(2.0), 0.0);
+ expect(scroll.dx(2.0), closeTo(0.0, 45.0));
+ });
+}