// 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'; /// A simulation that applies a drag to slow a particle down. /// /// Models a particle affected by fluid drag, e.g. air resistance. /// /// The simulation ends when the velocity of the particle drops to zero (within /// the current velocity [tolerance]). class FrictionSimulation extends Simulation { /// Creates a [FrictionSimulation] with the given arguments, namely: the fluid /// drag coefficient, a unitless value; the initial position, in the same /// length units as used for [x]; and the initial velocity, in the same /// velocity units as used for [dx]. FrictionSimulation(double drag, double position, double velocity, { Tolerance tolerance: Tolerance.defaultTolerance, }) : _drag = drag, _dragLog = math.log(drag), _x = position, _v = velocity, super(tolerance: tolerance); /// Creates a new friction simulation with its fluid drag coefficient set so /// as to ensure that the simulation starts and ends at the specified /// positions and velocities. /// /// The positions must use the same units as expected from [x], and the /// velocities must use the same units as expected from [dx]. /// /// The sign of the start and end velocities must be the same, the magnitude /// of the start velocity must be greater than the magnitude of the end /// velocity, and the velocities must be in the direction appropriate for the /// particle to start from the start position and reach the end position. factory FrictionSimulation.through(double startPosition, double endPosition, double startVelocity, double endVelocity) { assert(startVelocity == 0.0 || endVelocity == 0.0 || startVelocity.sign == endVelocity.sign); assert(startVelocity.abs() >= endVelocity.abs()); assert((endPosition - startPosition).sign == startVelocity.sign); return new FrictionSimulation( _dragFor(startPosition, endPosition, startVelocity, endVelocity), startPosition, startVelocity, tolerance: new Tolerance(velocity: endVelocity.abs()), ); } final double _drag; final double _dragLog; final double _x; final double _v; // 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)); } @override double x(double time) => _x + _v * math.pow(_drag, time) / _dragLog - _v / _dragLog; @override double dx(double time) => _v * math.pow(_drag, time); /// The value of [x] at `double.INFINITY`. double get finalX => _x - _v / _dragLog; /// The time at which the value of `x(time)` will equal [x]. /// /// Returns `double.INFINITY` if the simulation will never reach [x]. double timeAtX(double x) { if (x == _x) return 0.0; if (_v == 0.0 || (_v > 0 ? (x < _x || x > finalX) : (x > _x || x < finalX))) return double.INFINITY; return math.log(_dragLog * (x - _x) / _v + 1.0) / _dragLog; } @override bool isDone(double time) => dx(time).abs() < tolerance.velocity; } /// A [FrictionSimulation] that clamps the modeled particle to a specific range /// of values. class BoundedFrictionSimulation extends FrictionSimulation { /// Creates a [BoundedFrictionSimulation] with the given arguments, namely: /// the fluid drag coefficient, a unitless value; the initial position, in the /// same length units as used for [x]; the initial velocity, in the same /// velocity units as used for [dx], the minimum value for the position, and /// the maximum value for the position. The minimum and maximum values must be /// in the same units as the initial position, and the initial position must /// be within the given range. BoundedFrictionSimulation( double drag, double position, double velocity, this._minX, this._maxX ) : assert(position.clamp(_minX, _maxX) == position), super(drag, position, velocity); final double _minX; final double _maxX; @override double x(double time) { return super.x(time).clamp(_minX, _maxX); } @override bool isDone(double time) { return super.isDone(time) || (x(time) - _minX).abs() < tolerance.distance || (x(time) - _maxX).abs() < tolerance.distance; } }