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 |