spring_simulation.dart 8.45 KB
Newer Older
1
// Copyright 2016 The Chromium 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 7 8 9
import 'dart:math' as math;

import 'simulation.dart';
import 'utils.dart';

Ian Hickson's avatar
Ian Hickson committed
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
/// Structure that describes a spring's constants.
///
/// Used to configure a [SpringSimulation].
class SpringDescription {
  /// Creates a spring given the mass, spring constant and the damping coefficient.
  ///
  /// See [mass], [springConstant], and [damping] for the units of the arguments.
  const SpringDescription({
    this.mass,
    this.springConstant,
    this.damping
  });

  /// Creates a spring given the mass (m), spring constant (k), and 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.
  ///
  /// See [mass] and [springConstant] for the units for those arguments. The
  /// damping ratio is unitless.
  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). 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 the spring constant 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 springConstant;

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

/// 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 desc,
    double start,
    double end,
    double velocity
  ) : _endPosition = end,
      _solution = new _SpringSolution(desc, start - end, velocity);

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

/// 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 desc,
    double start,
    double end,
    double velocity
  ) : super(desc, start, end, velocity);

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


// SPRING IMPLEMENTATIONS
139

140 141 142 143 144 145
abstract class _SpringSolution {
  factory _SpringSolution(
    SpringDescription desc,
    double initialPosition,
    double initialVelocity
  ) {
Ian Hickson's avatar
Ian Hickson committed
146 147 148 149 150 151
    assert(desc != null);
    assert(desc.mass != null);
    assert(desc.springConstant != null);
    assert(desc.damping != null);
    assert(initialPosition != null);
    assert(initialVelocity != null);
152 153
    double cmk = desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
    if (cmk == 0.0)
154
      return new _CriticalSolution(desc, initialPosition, initialVelocity);
155
    if (cmk > 0.0)
156
      return new _OverdampedSolution(desc, initialPosition, initialVelocity);
157
    return new _UnderdampedSolution(desc, initialPosition, initialVelocity);
158 159
  }

160 161
  double x(double time);
  double dx(double time);
162 163 164 165 166
  SpringType get type;
}

class _CriticalSolution implements _SpringSolution {
  factory _CriticalSolution(
167 168 169 170
    SpringDescription desc,
    double distance,
    double velocity
  ) {
171 172 173 174 175 176 177
    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)
178 179 180
    : _r = r,
      _c1 = c1,
      _c2 = c2;
181

182 183
  final double _r, _c1, _c2;

184
  @override
185 186 187
  double x(double time) {
    return (_c1 + _c2 * time) * math.pow(math.E, _r * time);
  }
188

189
  @override
190 191 192 193
  double dx(double time) {
    final double power = math.pow(math.E, _r * time);
    return _r * (_c1 + _c2 * time) * power + _c2 * power;
  }
194

195
  @override
196
  SpringType get type => SpringType.criticallyDamped;
197 198 199 200
}

class _OverdampedSolution implements _SpringSolution {
  factory _OverdampedSolution(
201 202 203 204 205
    SpringDescription desc,
    double distance,
    double velocity
  ) {
    final double cmk = desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
206 207 208 209 210 211 212 213
    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)
214 215 216 217
    : _r1 = r1,
      _r2 = r2,
      _c1 = c1,
      _c2 = c2;
218

219
  final double _r1, _r2, _c1, _c2;
220

221
  @override
222 223 224 225 226
  double x(double time) {
    return _c1 * math.pow(math.E, _r1 * time) +
           _c2 * math.pow(math.E, _r2 * time);
  }

227
  @override
228 229 230 231
  double dx(double time) {
    return _c1 * _r1 * math.pow(math.E, _r1 * time) +
           _c2 * _r2 * math.pow(math.E, _r2 * time);
  }
232

233
  @override
234
  SpringType get type => SpringType.overDamped;
235 236 237 238
}

class _UnderdampedSolution implements _SpringSolution {
  factory _UnderdampedSolution(
239 240 241 242
    SpringDescription desc,
    double distance,
    double velocity
  ) {
243
    final double w = math.sqrt(4.0 * desc.mass * desc.springConstant -
244
                     desc.damping * desc.damping) / (2.0 * desc.mass);
245 246 247 248 249 250 251
    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)
252 253 254 255
    : _w = w,
      _r = r,
      _c1 = c1,
      _c2 = c2;
256

257
  final double _w, _r, _c1, _c2;
258

259
  @override
260 261 262 263
  double x(double time) {
    return math.pow(math.E, _r * time) *
           (_c1 * math.cos(_w * time) + _c2 * math.sin(_w * time));
  }
264

265
  @override
266 267 268 269
  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);
270 271
    return      power * (_c2 * _w * cosine - _c1 * _w * sine) +
           _r * power * (_c2 *      sine   + _c1 *      cosine);
272
  }
273

274
  @override
275
  SpringType get type => SpringType.underDamped;
276
}