// Copyright 2016 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. import 'dart:math' as math; import 'simulation.dart'; import 'utils.dart'; enum SpringType { unknown, criticallyDamped, underDamped, overDamped } abstract class _SpringSolution { 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); if (cmk > 0.0) return new _OverdampedSolution(desc, initialPosition, initialVelocity); return new _UnderdampedSolution(desc, initialPosition, initialVelocity); } double x(double time); double dx(double time); SpringType get type; } class _CriticalSolution implements _SpringSolution { 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); } _CriticalSolution.withArgs(double r, double c1, double c2) : _r = r, _c1 = c1, _c2 = c2; final double _r, _c1, _c2; double x(double time) { return (_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; } SpringType get type => SpringType.criticallyDamped; } class _OverdampedSolution implements _SpringSolution { 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; final double _r1, _r2, _c1, _c2; double x(double time) { return _c1 * math.pow(math.E, _r1 * time) + _c2 * math.pow(math.E, _r2 * time); } double dx(double time) { return _c1 * _r1 * math.pow(math.E, _r1 * time) + _c2 * _r2 * math.pow(math.E, _r2 * time); } SpringType get type => SpringType.overDamped; } class _UnderdampedSolution implements _SpringSolution { 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; final double _w, _r, _c1, _c2; double x(double time) { return 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); } SpringType get type => SpringType.underDamped; } class SpringDescription { SpringDescription({ this.mass, this.springConstant, 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); /// The mass of the spring (m) final double mass; /// The spring constant (k) final double springConstant; /// The damping coefficient. /// Not to be confused with the damping ratio. Use the separate /// constructor provided for this purpose final double damping; } /// Creates a spring simulation. Depending on the spring description, a /// critically, under or overdamped spring will be created. class SpringSimulation extends Simulation { /// A spring description with the provided spring description, start distance, /// end distance and velocity. SpringSimulation( SpringDescription desc, double start, double end, double velocity ) : _endPosition = end, _solution = new _SpringSolution(desc, start - end, velocity); final double _endPosition; final _SpringSolution _solution; SpringType get type => _solution.type; double x(double time) => _endPosition + _solution.x(time); double dx(double time) => _solution.dx(time); bool isDone(double time) { return nearZero(_solution.x(time), tolerance.distance) && nearZero(_solution.dx(time), 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); double x(double time) => isDone(time) ? _endPosition : super.x(time); }