// 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 'tolerance.dart'; import 'utils.dart'; /// Structure that describes a spring's constants. /// /// Used to configure a [SpringSimulation]. class SpringDescription { /// Creates a spring given the mass, stiffness, and the damping coefficient. /// /// See [mass], [stiffness], and [damping] for the units of the arguments. const SpringDescription({ this.mass, this.stiffness, this.damping, }); /// Creates a spring given the mass (m), stiffness (k), and damping ratio (ζ). /// The damping ratio is especially useful trying to determining 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. /// /// See [mass] and [stiffness] for the units for those arguments. The damping /// ratio is unitless. SpringDescription.withDampingRatio({ this.mass, this.stiffness, double ratio = 1.0, }) : damping = ratio * 2.0 * math.sqrt(mass * stiffness); /// The mass of the spring (m). The units are arbitrary, but all springs /// within a system should use the same mass units. final double mass; /// The spring constant (k). The units of stiffness are M/T², where M is the /// mass unit used for the value of the [mass] property, and T is the time /// unit used for driving the [SpringSimulation]. final double stiffness; /// The damping coefficient (c). /// /// Do not confuse the damping _coefficient_ (c) with the damping _ratio_ (ζ). /// To create a [SpringDescription] with a damping ratio, use the [new /// SpringDescription.withDampingRatio] constructor. /// /// The units of the damping coefficient are M/T, where M is the mass unit /// used for the value of the [mass] property, and T is the time unit used for /// driving the [SpringSimulation]. final double damping; @override String toString() => '$runtimeType(mass: ${mass.toStringAsFixed(1)}, stiffness: ${stiffness.toStringAsFixed(1)}, damping: ${damping.toStringAsFixed(1)})'; } /// The kind of spring solution that the [SpringSimulation] is using to simulate the spring. /// /// See [SpringSimulation.type]. enum SpringType { /// A spring that does not bounce and returns to its rest position in the /// shortest possible time. criticallyDamped, /// A spring that bounces. underDamped, /// A spring that does not bounce but takes longer to return to its rest /// position than a [criticallyDamped] one. overDamped, } /// A spring simulation. /// /// Models a particle attached to a spring that follows Hooke's law. class SpringSimulation extends Simulation { /// Creates a spring simulation from the provided spring description, start /// distance, end distance, and initial velocity. /// /// The units for the start and end distance arguments are arbitrary, but must /// be consistent with the units used for other lengths in the system. /// /// The units for the velocity are L/T, where L is the aforementioned /// arbitrary unit of length, and T is the time unit used for driving the /// [SpringSimulation]. SpringSimulation( SpringDescription spring, double start, double end, double velocity, { Tolerance tolerance = Tolerance.defaultTolerance, }) : _endPosition = end, _solution = _SpringSolution(spring, start - end, velocity), super(tolerance: tolerance); final double _endPosition; final _SpringSolution _solution; /// The kind of spring being simulated, for debugging purposes. /// /// This is derived from the [SpringDescription] provided to the [new /// SpringSimulation] constructor. SpringType get type => _solution.type; @override double x(double time) => _endPosition + _solution.x(time); @override double dx(double time) => _solution.dx(time); @override bool isDone(double time) { return nearZero(_solution.x(time), tolerance.distance) && nearZero(_solution.dx(time), tolerance.velocity); } @override String toString() => '$runtimeType(end: $_endPosition, $type)'; } /// A SpringSimulation where the value of [x] is guaranteed to have exactly the /// end value when the simulation isDone(). class ScrollSpringSimulation extends SpringSimulation { /// Creates a spring simulation from the provided spring description, start /// distance, end distance, and initial velocity. /// /// See the [new SpringSimulation] constructor on the superclass for a /// discussion of the arguments' units. ScrollSpringSimulation( SpringDescription spring, double start, double end, double velocity, { Tolerance tolerance = Tolerance.defaultTolerance, }) : super(spring, start, end, velocity, tolerance: tolerance); @override double x(double time) => isDone(time) ? _endPosition : super.x(time); } // SPRING IMPLEMENTATIONS abstract class _SpringSolution { factory _SpringSolution( SpringDescription spring, double initialPosition, double initialVelocity, ) { assert(spring != null); assert(spring.mass != null); assert(spring.stiffness != null); assert(spring.damping != null); assert(initialPosition != null); assert(initialVelocity != null); final double cmk = spring.damping * spring.damping - 4 * spring.mass * spring.stiffness; if (cmk == 0.0) return _CriticalSolution(spring, initialPosition, initialVelocity); if (cmk > 0.0) return _OverdampedSolution(spring, initialPosition, initialVelocity); return _UnderdampedSolution(spring, initialPosition, initialVelocity); } double x(double time); double dx(double time); SpringType get type; } class _CriticalSolution implements _SpringSolution { factory _CriticalSolution( SpringDescription spring, double distance, double velocity, ) { final double r = -spring.damping / (2.0 * spring.mass); final double c1 = distance; final double c2 = velocity / (r * distance); return _CriticalSolution.withArgs(r, c1, c2); } _CriticalSolution.withArgs(double r, double c1, double c2) : _r = r, _c1 = c1, _c2 = c2; final double _r, _c1, _c2; @override double x(double time) { return (_c1 + _c2 * time) * math.pow(math.e, _r * time); } @override double dx(double time) { final double power = math.pow(math.e, _r * time); return _r * (_c1 + _c2 * time) * power + _c2 * power; } @override SpringType get type => SpringType.criticallyDamped; } class _OverdampedSolution implements _SpringSolution { factory _OverdampedSolution( SpringDescription spring, double distance, double velocity, ) { final double cmk = spring.damping * spring.damping - 4 * spring.mass * spring.stiffness; final double r1 = (-spring.damping - math.sqrt(cmk)) / (2.0 * spring.mass); final double r2 = (-spring.damping + math.sqrt(cmk)) / (2.0 * spring.mass); final double c2 = (velocity - r1 * distance) / (r2 - r1); final double c1 = distance - c2; return _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; @override double x(double time) { return _c1 * math.pow(math.e, _r1 * time) + _c2 * math.pow(math.e, _r2 * time); } @override double dx(double time) { return _c1 * _r1 * math.pow(math.e, _r1 * time) + _c2 * _r2 * math.pow(math.e, _r2 * time); } @override SpringType get type => SpringType.overDamped; } class _UnderdampedSolution implements _SpringSolution { factory _UnderdampedSolution( SpringDescription spring, double distance, double velocity, ) { final double w = math.sqrt(4.0 * spring.mass * spring.stiffness - spring.damping * spring.damping) / (2.0 * spring.mass); final double r = -(spring.damping / 2.0 * spring.mass); final double c1 = distance; final double c2 = (velocity - r * distance) / w; return _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; @override double x(double time) { return math.pow(math.e, _r * time) * (_c1 * math.cos(_w * time) + _c2 * math.sin(_w * time)); } @override 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); } @override SpringType get type => SpringType.underDamped; }