-
Notifications
You must be signed in to change notification settings - Fork 30.7k
Expand file tree
/
Copy pathfriction_simulation.dart
More file actions
201 lines (181 loc) · 7.08 KB
/
Copy pathfriction_simulation.dart
File metadata and controls
201 lines (181 loc) · 7.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Copyright 2014 The Flutter 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 'package:flutter/foundation.dart';
import 'simulation.dart';
export 'tolerance.dart' show Tolerance;
/// Numerically determine the input value which produces output value [target]
/// for a function [f], given its first-derivative [df].
double _newtonsMethod({
required double initialGuess,
required double target,
required double Function(double) f,
required double Function(double) df,
required int iterations,
}) {
var guess = initialGuess;
for (var i = 0; i < iterations; i++) {
guess = guess - (f(guess) - target) / df(guess);
}
return guess;
}
/// 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 _cₓ_, a unitless value; the initial position _x₀_, in the same
/// length units as used for [x]; and the initial velocity _dx₀_, in the same
/// velocity units as used for [dx].
FrictionSimulation(
double drag,
double position,
double velocity, {
super.tolerance,
double constantDeceleration = 0,
}) : _drag = drag,
_dragLog = math.log(drag),
_x = position,
_v = velocity,
_constantDeceleration = constantDeceleration * velocity.sign {
_finalTime = _newtonsMethod(
initialGuess: 0,
target: 0,
f: dx,
df: (double time) => (_v * math.pow(_drag, time) * _dragLog) - _constantDeceleration,
iterations: 10,
);
}
/// Creates a new friction simulation with its fluid drag coefficient (_cₓ_) 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 FrictionSimulation(
_dragFor(startPosition, endPosition, startVelocity, endVelocity),
startPosition,
startVelocity,
tolerance: Tolerance(velocity: endVelocity.abs()),
);
}
final double _drag;
final double _dragLog;
final double _x;
final double _v;
final double _constantDeceleration;
// The time at which the simulation should be stopped.
// This is needed when constantDeceleration is not zero (on Desktop), when
// using the pure friction simulation, acceleration naturally reduces to zero
// and creates a stopping point.
double _finalTime =
double.infinity; // needs to be infinity for newtonsMethod call in constructor.
// 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))
as double;
}
@override
double x(double time) {
if (time > _finalTime) {
return finalX;
}
return _x +
_v * math.pow(_drag, time) / _dragLog -
_v / _dragLog -
((_constantDeceleration / 2) * time * time);
}
@override
double dx(double time) {
if (time > _finalTime) {
return 0;
}
return _v * math.pow(_drag, time) - _constantDeceleration * time;
}
/// The value of [x] at `double.infinity`.
double get finalX {
if (_constantDeceleration == 0) {
return _x - _v / _dragLog;
}
return x(_finalTime);
}
/// 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 _newtonsMethod(target: x, initialGuess: 0, f: this.x, df: dx, iterations: 10);
}
@override
bool isDone(double time) {
return dx(time).abs() < tolerance.velocity;
}
@override
String toString() =>
'${objectRuntimeType(this, 'FrictionSimulation')}(cₓ: ${_drag.toStringAsFixed(1)}, x₀: ${_x.toStringAsFixed(1)}, dx₀: ${_v.toStringAsFixed(1)})';
}
/// A [FrictionSimulation] that clamps the modeled particle to a specific range
/// of values.
///
/// Only the position is clamped. The velocity [dx] will continue to report
/// unbounded simulated velocities once the particle has reached the bounds.
class BoundedFrictionSimulation extends FrictionSimulation {
/// Creates a [BoundedFrictionSimulation] with the given arguments, namely:
/// the fluid drag coefficient _cₓ_, a unitless value; the initial position _x₀_, in the
/// same length units as used for [x]; the initial velocity _dx₀_, 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(super.drag, super.position, super.velocity, this._minX, this._maxX)
: assert(clampDouble(position, _minX, _maxX) == position);
final double _minX;
final double _maxX;
@override
double x(double time) {
return clampDouble(super.x(time), _minX, _maxX);
}
@override
bool isDone(double time) {
return super.isDone(time) ||
(x(time) - _minX).abs() < tolerance.distance ||
(x(time) - _maxX).abs() < tolerance.distance;
}
@override
String toString() =>
'${objectRuntimeType(this, 'BoundedFrictionSimulation')}(cₓ: ${_drag.toStringAsFixed(1)}, x₀: ${_x.toStringAsFixed(1)}, dx₀: ${_v.toStringAsFixed(1)}, x: ${_minX.toStringAsFixed(1)}..${_maxX.toStringAsFixed(1)})';
}