| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 import 'dart:math' as math; | |
| 6 | |
| 7 const double kGravity = -0.980; // m^s-2 | |
| 8 | |
| 9 abstract class System { | |
| 10 void update(double deltaT); | |
| 11 } | |
| 12 | |
| 13 class Particle extends System { | |
| 14 final double mass; | |
| 15 double velocity; | |
| 16 double position; | |
| 17 | |
| 18 Particle({this.mass: 1.0, this.velocity: 0.0, this.position: 0.0}); | |
| 19 | |
| 20 void applyImpulse(double impulse) { | |
| 21 velocity += impulse / mass; | |
| 22 } | |
| 23 | |
| 24 void update(double deltaT) { | |
| 25 position += velocity * deltaT; | |
| 26 } | |
| 27 | |
| 28 void setVelocityFromEnergy({double energy, double direction}) { | |
| 29 assert(direction == -1.0 || direction == 1.0); | |
| 30 assert(energy >= 0.0); | |
| 31 velocity = math.sqrt(2.0 * energy / mass) * direction; | |
| 32 } | |
| 33 } | |
| 34 | |
| 35 abstract class Box { | |
| 36 void confine(Particle p); | |
| 37 } | |
| 38 | |
| 39 class ClosedBox extends Box { | |
| 40 final double min; // m | |
| 41 final double max; // m | |
| 42 | |
| 43 ClosedBox({this.min, this.max}) { | |
| 44 assert(min == null || max == null || min <= max); | |
| 45 } | |
| 46 | |
| 47 void confine(Particle p) { | |
| 48 if (min != null) { | |
| 49 p.position = math.max(min, p.position); | |
| 50 if (p.position == min) | |
| 51 p.velocity = math.max(0.0, p.velocity); | |
| 52 } | |
| 53 if (max != null) { | |
| 54 p.position = math.min(max, p.position); | |
| 55 if (p.position == max) | |
| 56 p.velocity = math.min(0.0, p.velocity); | |
| 57 } | |
| 58 } | |
| 59 } | |
| 60 | |
| 61 class GeofenceBox extends Box { | |
| 62 final double min; // m | |
| 63 final double max; // m | |
| 64 | |
| 65 final Function onEscape; | |
| 66 | |
| 67 GeofenceBox({this.min, this.max, this.onEscape}) { | |
| 68 assert(min == null || max == null || min <= max); | |
| 69 assert(onEscape != null); | |
| 70 } | |
| 71 | |
| 72 void confine(Particle p) { | |
| 73 if (((min != null) && (p.position < min)) || | |
| 74 ((max != null) && (p.position > max))) | |
| 75 onEscape(); | |
| 76 } | |
| 77 } | |
| 78 | |
| 79 class ParticleInBox extends System { | |
| 80 final Particle particle; | |
| 81 final Box box; | |
| 82 | |
| 83 ParticleInBox({this.particle, this.box}) { | |
| 84 box.confine(particle); | |
| 85 } | |
| 86 | |
| 87 void update(double deltaT) { | |
| 88 particle.update(deltaT); | |
| 89 box.confine(particle); | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 class ParticleInBoxWithFriction extends ParticleInBox { | |
| 94 final double friction; // unitless | |
| 95 final double _sign; | |
| 96 | |
| 97 final Function onStop; | |
| 98 | |
| 99 ParticleInBoxWithFriction({Particle particle, Box box, this.friction, this.onS
top}) | |
| 100 : super(particle: particle, box: box), | |
| 101 _sign = particle.velocity.sign; | |
| 102 | |
| 103 void update(double deltaT) { | |
| 104 double force = -_sign * friction * particle.mass * -kGravity; | |
| 105 particle.applyImpulse(force * deltaT); | |
| 106 if (particle.velocity.sign != _sign) { | |
| 107 particle.velocity = 0.0; | |
| 108 } | |
| 109 super.update(deltaT); | |
| 110 if ((particle.velocity == 0.0) && (onStop != null)) | |
| 111 onStop(); | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 class Spring { | |
| 116 final double k; | |
| 117 double displacement; | |
| 118 | |
| 119 Spring(this.k, {this.displacement: 0.0}); | |
| 120 | |
| 121 double get force => -k * displacement; | |
| 122 } | |
| 123 | |
| 124 class ParticleAndSpringInBox extends System { | |
| 125 final Particle particle; | |
| 126 final Spring spring; | |
| 127 final Box box; | |
| 128 | |
| 129 ParticleAndSpringInBox({this.particle, this.spring, this.box}) { | |
| 130 _applyInvariants(); | |
| 131 } | |
| 132 | |
| 133 void update(double deltaT) { | |
| 134 particle.applyImpulse(spring.force * deltaT); | |
| 135 particle.update(deltaT); | |
| 136 _applyInvariants(); | |
| 137 } | |
| 138 | |
| 139 void _applyInvariants() { | |
| 140 box.confine(particle); | |
| 141 spring.displacement = particle.position; | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 class ParticleClimbingRamp extends System { | |
| 146 | |
| 147 // This is technically the same as ParticleInBoxWithFriction. The | |
| 148 // difference is in how the system is set up. Here, we configure the | |
| 149 // system so as to stop by a certain distance after having been | |
| 150 // given an initial impulse from rest, whereas | |
| 151 // ParticleInBoxWithFriction is set up to stop with a consistent | |
| 152 // decelerating force assuming an initial velocity. The angle theta | |
| 153 // (0 < theta < π/2) is used to configure how much energy the | |
| 154 // particle is to start with; lower angles result in a gentler kick | |
| 155 // while higher angles result in a faster conclusion. | |
| 156 | |
| 157 final Particle particle; | |
| 158 final Box box; | |
| 159 final double theta; | |
| 160 final double _sinTheta; | |
| 161 | |
| 162 ParticleClimbingRamp({ | |
| 163 this.particle, | |
| 164 this.box, | |
| 165 double theta, // in radians | |
| 166 double targetPosition}) : this.theta = theta, this._sinTheta = math.sin(th
eta) { | |
| 167 assert(theta > 0.0); | |
| 168 assert(theta < math.PI / 2.0); | |
| 169 double deltaPosition = targetPosition - particle.position; | |
| 170 double tanTheta = math.tan(theta); | |
| 171 // We need to give the particle exactly as much (kinetic) energy | |
| 172 // as it needs to get to the top of the slope and stop with | |
| 173 // energy=0. This is exactly the same amount of energy as the | |
| 174 // potential energy at the top of the slope, which is g*h*m. | |
| 175 // If the slope's horizontal component is delta P long, then | |
| 176 // the height is delta P times tan theta. | |
| 177 particle.setVelocityFromEnergy( | |
| 178 energy: (kGravity * (deltaPosition * tanTheta) * particle.mass).abs(), | |
| 179 direction: deltaPosition > 0.0 ? 1.0 : -1.0 | |
| 180 ); | |
| 181 box.confine(particle); | |
| 182 } | |
| 183 | |
| 184 void update(double deltaT) { | |
| 185 particle.update(deltaT); | |
| 186 // Note that we apply the impulse from gravity after updating the particle's | |
| 187 // position so that we overestimate the distance traveled by the particle. | |
| 188 // That ensures that we actually hit the edge of the box and don't wind up | |
| 189 // reversing course. | |
| 190 particle.applyImpulse(particle.mass * kGravity * _sinTheta * deltaT); | |
| 191 box.confine(particle); | |
| 192 } | |
| 193 } | |
| 194 | |
| 195 class Multisystem extends System { | |
| 196 final Particle particle; | |
| 197 | |
| 198 System _currentSystem; | |
| 199 | |
| 200 Multisystem({ this.particle, System system }) { | |
| 201 assert(system != null); | |
| 202 _currentSystem = system; | |
| 203 } | |
| 204 | |
| 205 void update(double deltaT) { | |
| 206 _currentSystem.update(deltaT); | |
| 207 } | |
| 208 | |
| 209 void transitionToSystem(System system) { | |
| 210 assert(system != null); | |
| 211 _currentSystem = system; | |
| 212 } | |
| 213 } | |
| OLD | NEW |