spring_simulation.dart 8.99 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:math' as math;

7 8
import 'package:flutter/foundation.dart';

9
import 'simulation.dart';
10
import 'tolerance.dart';
11 12
import 'utils.dart';

Ian Hickson's avatar
Ian Hickson committed
13 14 15 16
/// Structure that describes a spring's constants.
///
/// Used to configure a [SpringSimulation].
class SpringDescription {
17
  /// Creates a spring given the mass, stiffness, and the damping coefficient.
Ian Hickson's avatar
Ian Hickson committed
18
  ///
19
  /// See [mass], [stiffness], and [damping] for the units of the arguments.
Ian Hickson's avatar
Ian Hickson committed
20
  const SpringDescription({
21 22 23
    required this.mass,
    required this.stiffness,
    required this.damping,
Ian Hickson's avatar
Ian Hickson committed
24 25
  });

26
  /// Creates a spring given the mass (m), stiffness (k), and damping ratio (ζ).
27
  /// The damping ratio is especially useful trying to determining the type of
28 29
  /// 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.
Ian Hickson's avatar
Ian Hickson committed
30
  ///
31 32
  /// See [mass] and [stiffness] for the units for those arguments. The damping
  /// ratio is unitless.
Ian Hickson's avatar
Ian Hickson committed
33
  SpringDescription.withDampingRatio({
34 35
    required this.mass,
    required this.stiffness,
36
    double ratio = 1.0,
37
  }) : damping = ratio * 2.0 * math.sqrt(mass * stiffness);
Ian Hickson's avatar
Ian Hickson committed
38 39 40 41 42

  /// 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;

43 44 45 46
  /// 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;
Ian Hickson's avatar
Ian Hickson committed
47 48 49 50 51 52 53 54 55 56 57

  /// 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;
58 59

  @override
60
  String toString() => '${objectRuntimeType(this, 'SpringDescription')}(mass: ${mass.toStringAsFixed(1)}, stiffness: ${stiffness.toStringAsFixed(1)}, damping: ${damping.toStringAsFixed(1)})';
Ian Hickson's avatar
Ian Hickson committed
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
}

/// 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(
93
    SpringDescription spring,
Ian Hickson's avatar
Ian Hickson committed
94 95
    double start,
    double end,
96
    double velocity, {
97
    Tolerance tolerance = Tolerance.defaultTolerance,
98
  }) : _endPosition = end,
99 100
       _solution = _SpringSolution(spring, start - end, velocity),
       super(tolerance: tolerance);
Ian Hickson's avatar
Ian Hickson committed
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121

  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);
  }
122 123

  @override
124
  String toString() => '${objectRuntimeType(this, 'SpringSimulation')}(end: $_endPosition, $type)';
Ian Hickson's avatar
Ian Hickson committed
125 126
}

127 128
/// A [SpringSimulation] where the value of [x] is guaranteed to have exactly the
/// end value when the simulation [isDone].
Ian Hickson's avatar
Ian Hickson committed
129 130 131 132 133 134 135
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(
136
    SpringDescription spring,
Ian Hickson's avatar
Ian Hickson committed
137 138
    double start,
    double end,
139
    double velocity, {
140
    Tolerance tolerance = Tolerance.defaultTolerance,
141
  }) : super(spring, start, end, velocity, tolerance: tolerance);
Ian Hickson's avatar
Ian Hickson committed
142 143 144 145 146 147 148

  @override
  double x(double time) => isDone(time) ? _endPosition : super.x(time);
}


// SPRING IMPLEMENTATIONS
149

150 151
abstract class _SpringSolution {
  factory _SpringSolution(
152
    SpringDescription spring,
153
    double initialPosition,
154
    double initialVelocity,
155
  ) {
156 157
    assert(spring != null);
    assert(spring.mass != null);
158
    assert(spring.stiffness != null);
159
    assert(spring.damping != null);
Ian Hickson's avatar
Ian Hickson committed
160 161
    assert(initialPosition != null);
    assert(initialVelocity != null);
162
    final double cmk = spring.damping * spring.damping - 4 * spring.mass * spring.stiffness;
163
    if (cmk == 0.0)
164
      return _CriticalSolution(spring, initialPosition, initialVelocity);
165
    if (cmk > 0.0)
166 167
      return _OverdampedSolution(spring, initialPosition, initialVelocity);
    return _UnderdampedSolution(spring, initialPosition, initialVelocity);
168 169
  }

170 171
  double x(double time);
  double dx(double time);
172 173 174 175 176
  SpringType get type;
}

class _CriticalSolution implements _SpringSolution {
  factory _CriticalSolution(
177
    SpringDescription spring,
178
    double distance,
179
    double velocity,
180
  ) {
181
    final double r = -spring.damping / (2.0 * spring.mass);
182 183
    final double c1 = distance;
    final double c2 = velocity / (r * distance);
184
    return _CriticalSolution.withArgs(r, c1, c2);
185 186 187
  }

  _CriticalSolution.withArgs(double r, double c1, double c2)
188 189 190
    : _r = r,
      _c1 = c1,
      _c2 = c2;
191

192 193
  final double _r, _c1, _c2;

194
  @override
195
  double x(double time) {
196
    return (_c1 + _c2 * time) * math.pow(math.e, _r * time);
197
  }
198

199
  @override
200
  double dx(double time) {
201
    final double power = math.pow(math.e, _r * time) as double;
202 203
    return _r * (_c1 + _c2 * time) * power + _c2 * power;
  }
204

205
  @override
206
  SpringType get type => SpringType.criticallyDamped;
207 208 209 210
}

class _OverdampedSolution implements _SpringSolution {
  factory _OverdampedSolution(
211
    SpringDescription spring,
212
    double distance,
213
    double velocity,
214
  ) {
215
    final double cmk = spring.damping * spring.damping - 4 * spring.mass * spring.stiffness;
216 217
    final double r1 = (-spring.damping - math.sqrt(cmk)) / (2.0 * spring.mass);
    final double r2 = (-spring.damping + math.sqrt(cmk)) / (2.0 * spring.mass);
218 219
    final double c2 = (velocity - r1 * distance) / (r2 - r1);
    final double c1 = distance - c2;
220
    return _OverdampedSolution.withArgs(r1, r2, c1, c2);
221 222 223
  }

  _OverdampedSolution.withArgs(double r1, double r2, double c1, double c2)
224 225 226 227
    : _r1 = r1,
      _r2 = r2,
      _c1 = c1,
      _c2 = c2;
228

229
  final double _r1, _r2, _c1, _c2;
230

231
  @override
232
  double x(double time) {
233 234
    return _c1 * math.pow(math.e, _r1 * time) +
           _c2 * math.pow(math.e, _r2 * time);
235 236
  }

237
  @override
238
  double dx(double time) {
239 240
    return _c1 * _r1 * math.pow(math.e, _r1 * time) +
           _c2 * _r2 * math.pow(math.e, _r2 * time);
241
  }
242

243
  @override
244
  SpringType get type => SpringType.overDamped;
245 246 247 248
}

class _UnderdampedSolution implements _SpringSolution {
  factory _UnderdampedSolution(
249
    SpringDescription spring,
250
    double distance,
251
    double velocity,
252
  ) {
253
    final double w = math.sqrt(4.0 * spring.mass * spring.stiffness -
254 255
                     spring.damping * spring.damping) / (2.0 * spring.mass);
    final double r = -(spring.damping / 2.0 * spring.mass);
256 257
    final double c1 = distance;
    final double c2 = (velocity - r * distance) / w;
258
    return _UnderdampedSolution.withArgs(w, r, c1, c2);
259 260 261
  }

  _UnderdampedSolution.withArgs(double w, double r, double c1, double c2)
262 263 264 265
    : _w = w,
      _r = r,
      _c1 = c1,
      _c2 = c2;
266

267
  final double _w, _r, _c1, _c2;
268

269
  @override
270
  double x(double time) {
271
    return (math.pow(math.e, _r * time) as double) *
272 273
           (_c1 * math.cos(_w * time) + _c2 * math.sin(_w * time));
  }
274

275
  @override
276
  double dx(double time) {
277
    final double power = math.pow(math.e, _r * time) as double;
278 279
    final double cosine = math.cos(_w * time);
    final double sine = math.sin(_w * time);
280
    return power * (_c2 * _w * cosine - _c1 * _w * sine) +
281
           _r * power * (_c2 *      sine   + _c1 *      cosine);
282
  }
283

284
  @override
285
  SpringType get type => SpringType.underDamped;
286
}