-
Notifications
You must be signed in to change notification settings - Fork 0
/
A00_basic_engine2.txt
547 lines (457 loc) · 20.8 KB
/
A00_basic_engine2.txt
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
# Filename: A00_basic_engine.py
# Author: James D. Miller; Gustavus Adolphus College.
import time, sys, textwrap
from timeit import default_timer
def px_from_m( x_m):
return int(round( x_m * env['m_to_px']))
def move( car):
v_i = car['v_mps']
car['v_mps'] += car['a_mps2'] * dt_s
if env['exact_solution']:
#car['x_m'] += ((v_i + car['v_mps'])/2.0) * dt_s
car['x_m'] += v_i * dt_s + (car['a_mps2'] * (dt_s ** 2.0))/2.0
else:
# Normal Euler's method (using v at the beginning of the frame).
#car['x_m'] += v_i * dt_s
# Backward Euler's method (using v at the end of the frame).
car['x_m'] += car['v_mps'] * dt_s
def debug_print(name_string):
# Print out the values for a set of global names contained in
# a string and separated by commas.
names = name_string.split(",")
print_string = ''
for name in names:
print_string += name + ":" + str(eval(name)) + ", "
print print_string
def dp(variable, variable_name):
print variable_name + "=" + str(variable)
def x_corrected_exact( car, x_overlap):
# Inputs are conditions at time of collision detection. These
# values have signs. For example x_overlap is positive for penetration
# on the right end of the track.
if (env['stickiness_correction']):
x_coll_m = car['x_m']
v_coll_mps = car['v_mps']
# Determine the car state as it passes through the wall.
# v_coll_mps**2 = v_wall_mps**2 + 2*a*x_overlap
v_wall_mps = (v_coll_mps**2.0 - 2.0 * car['a_mps2'] * x_overlap)**0.5
if track['collision_state'] == 'left':
v_wall_mps = -1.0 * abs(v_wall_mps)
# Might have to consider the case where it reverses direction during the
# collision (at the top of the track).
# The time expended penetrating the wall.
# x_coll_m = x_wall_m + ((v_wall_mps + v_coll_mps)/2.0) * t_pen
t_pen = 2.0 * x_overlap/(v_wall_mps + v_coll_mps)
# The distance covered bouncing back from the wall in time t_pen. Change
# the sign (direction) of v_wall.
v_wall_afterbounce_mps = v_wall_mps * -1.0 * env['CR']
x_bounce_pen = v_wall_afterbounce_mps * t_pen + (car['a_mps2'] * t_pen**2.0)/2.0
if track['collision_state'] == 'left':
if x_bounce_pen < 0.0: x_bounce_pen = 0.0
else:
if x_bounce_pen > 0.0: x_bounce_pen = 0.0
# The corrected position, that is, where it would be if it had bounced off the wall.
car['x_m'] = (x_coll_m - x_overlap) + x_bounce_pen
# Also need to determine the velocity at the corrected position.
car['v_mps'] = v_wall_afterbounce_mps + t_pen * car['a_mps2']
if False:
dp(x_coll_m, "x_coll_m")
dp(v_coll_mps, "v_coll_mps")
dp(t_pen, "t_pen")
dp(v_wall_mps, "v_wall_mps")
dp(v_wall_afterbounce_mps, "v_wall_afterbounce_mps")
dp(x_bounce_pen, "x_bounce_pen")
dp(car['x_m'], "car['x_m']")
dp(car['v_mps'], "car['v_mps']")
else:
# If no position correction, simply reverse the direction of the car.
car['v_mps'] *= -1.0 * env['CR']
def check_for_wall_collisions( car):
# Check for a collision.
if (car['x_m'] < track['left_edge_m']):
x_overlap = car['x_m'] - track['left_edge_m']
track['collision_state'] = 'left'
elif (car['x_m'] > track['right_edge_m']):
x_overlap = car['x_m'] - track['right_edge_m']
track['collision_state'] = 'right'
else:
track['collision_state'] = 'none'
# Resolve the collision.
if track['collision_state'] != 'none':
track['collision_mark_px'] = px_from_m( car['x_m'])
if env['exact_solution']:
x_corrected_exact( car, x_overlap)
else:
if (env['stickiness_correction']):
if (env['correction_version_2']):
# Move the car back to the surface and then an additional
# equal amount but reduced by the CR coefficient.
car['x_m'] -= x_overlap * (1 + env['CR'])
else:
# Simple stickiness correction. Move it back by the amount of the overlap.
# This puts the car at the surface.
if track['collision_state'] == 'left':
car['x_m'] = track['left_edge_m']
else:
car['x_m'] = track['right_edge_m']
# loss of (1-CR)*100% on each bounce.
car['v_mps'] *= -1 * env['CR']
else:
track['collision_mark_px'] = -999
def build_airtrack_string( car):
left_edge_px = px_from_m( track['left_edge_m'])
right_edge_px = px_from_m( track['right_edge_m'])
display_width_px = 135
car_location_px = px_from_m( car['x_m'])
string = ''
for j in range(0, display_width_px + 1):
if (j == car_location_px):
string += '*'
elif (j == left_edge_px) or (j == right_edge_px):
string += '|'
elif (track['show_start_mark'] and (j == track['track_mark_px'])):
string += "."
elif (track['show_collision_mark'] and (j == track['collision_mark_px'])):
string += "0"
else:
string += ' '
return string
def render( car):
display_string = build_airtrack_string( car)
if cl['details']:
print display_string + 'x=' + "%6.3f" % car['x_m'] + ', v=' + "% .2f" % car['v_mps'] + ", F=" + "%3.0f" % fps_observed
else:
print display_string
def pos_avg_10( car):
x_list.append( car['x_m'])
if len(x_list) > 10: x_list.pop(0)
return sum(x_list)/float(len(x_list))
def pretty_paragraphs( text_string, n_blanklines):
paragraph_list = text_string.split('||')
for paragraph in paragraph_list:
dedented_text = textwrap.dedent( paragraph).strip()
print textwrap.fill(dedented_text, initial_indent=' ', subsequent_indent=' ')
print ""
for j in range( n_blanklines): print "\n"
def print_delay( string):
print string
time.sleep( 0.10)
def try_sleep( seconds):
# If you don't want to wait. Press control-c to break out of the sleep.
try:
time.sleep( seconds)
except KeyboardInterrupt:
print_delay(" * ")
print_delay(" * * ")
print_delay(" * * ")
print_delay(" * * ")
print_delay(" * ")
print "\n\n\n"
def print_header(car):
print "\n\n\n\n"
print " Example #" + str(cl['example_index'])
print " ---------------------"
print " Initial x = " + str(car['x_m'])
print " Initial v = " + str(car['v_mps'])
print " a = " + str(car['a_mps2'])
print " Coefficient of Restitution = " + str(env['CR'])
print ""
print " Stickiness correction = " + str(env['stickiness_correction'])
if env['stickiness_correction'] and not env['exact_solution']:
print " Correction (version 2) = " + str(env['correction_version_2'])
print " Exact solution = " + str(env['exact_solution'])
print " Use observed dt in next frame = " + str(env['use_observed_dt'])
print ""
print " Show starting mark (\".\") = " + str(track['show_start_mark'])
print " Show collision mark (\"0\") = " + str(track['show_collision_mark'])
print " Show physics-engine output = " + str(cl['details'])
print ""
print " FPS target = " + str(env['fps_target'])
print " Auto-Off = " + str(env['auto_off'])
print " Zoom (meters to px factor) = " + str(env['m_to_px'])
print " "
def modify( car, env):
if cl['example_index'] == 1:
car['x_m'] = 2.0; car['v_mps'] = 0.0; car['a_mps2'] = -1.5
track['show_collision_mark'] = False
env['CR'] = 0.7
print_header(car)
explaination = '''
This first example has the car (represented by a "*") starting from rest and accelerating to the left. Stickiness
correction is ON. There is energy loss (fractional reduction in v) after each wall collision.
||
Control-s pauses (and restarts) the run. Control-s can be used to give additional time (pause) for reading the descriptions
at the beginning. Control-c stops the run. Control-c can also be used to skip
the reading-wait at the beginning.
||
For starters, remember that this is 1-D motion! The history of this motion moves vertically, one step at a time,
as the program renders each new single-line snapshot. An effective way to view this 1-D motion (animation) is to focus
your attention at the bottom row. The YouTube video will provide some training wheels (an annotation rectangle) to help
you do this. Another approach
is to place a sheet of paper over everything on the screen except the bottom row.
||
If the "d" option is given at the command line, the details of the physics calculation are printed with each
frame. This outputs, position, velocity, and frame rate.
'''
pretty_paragraphs( explaination, 1)
try_sleep(5.0)
elif cl['example_index'] == 2:
car['x_m'] = 1.65; car['v_mps'] = 2.7; car['a_mps2'] = 0.0
env['stickiness_correction'] = False
track['show_collision_mark'] = False
env['CR'] = 1.0
print_header(car)
explaination = '''
Stickiness correction is turned off which allows the overlap (penetration) to be seen. These are
elastic collisions (CR=1), meaning this will run until a keyboard stop or the loop counter hits its limit.
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(3.0)
elif cl['example_index'] == 3:
car['x_m'] = 2.0; car['v_mps'] = 0.0; car['a_mps2'] = -1.5
env['stickiness_correction'] = False
track['show_collision_mark'] = False
env['auto_off'] = False
env['fps_target'] = 30
env['CR'] = 0.6
print_header(car)
explaination = '''
All parameters are identical to example 1 except that stickiness
correction is OFF.
||
Watch the wall collision. With stickiness correction turned off, the car will be allowed to render in the state of
collision (on the other side of the wall). But with the first bounce, due to
gravity and the collision-related energy losses, the car does not recover from the state of penetration (the car sticks to the wall),
that is, the ball does not bounce back
far enough to get back to the other side.
This leads to a state of perpetual collisions,
with gravity dragging the car to the left.
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(4.0)
elif cl['example_index'] == 4:
car['x_m'] = 0.2; car['v_mps'] = 10.0; car['a_mps2'] = -10.0
env['CR'] = 0.8
env['fps_target'] = 300
track['show_collision_mark'] = False
print_header(car)
explaination = '''
The target frame rate is set high to give an interesting display of the time-series tail. The car
loses speed from each
wall collision. Acceleration (set high) to the left (negative).
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(3.0)
elif cl['example_index'] == 5:
car['x_m'] = 2.0; car['v_mps'] = 3.0; car['a_mps2'] = 2.0
env['CR'] = 0.7
print_header(car)
explaination = '''
Acceleration is to the right (opposite direction from other examples).
||
A collision mark (0) is displayed at the original collision position (before stickiness correction).
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(3.0)
elif cl['example_index'] == 6:
car['x_m'] = 2.0; car['v_mps'] = 3.0; car['a_mps2'] = 2.0
env['CR'] = 0.7
env['m_to_px'] = 30.0
print_header(car)
explaination = '''
The scaling factor between the physics engine and the renderer has decreased from the level used in example 5.
This effectively zooms out the view of the
track and the car on it. The output from the physics engine is unchanged by this;
all characteristics of the movement is identical to example 5.
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(3.0)
elif cl['example_index'] == 7:
car['x_m'] = 2.0; car['v_mps'] = 0.0; car['a_mps2'] = -2.0
env['CR'] = 1.0
env['fps_target'] = 240
track['show_start_mark'] = True
print_header(car)
explaination = '''
The CR value of unity yields elastic collisions. The frame rate is set high so to give
the highest precision in the physics predictions. Note the car consistently returns to the initial starting point
as marked by the period symbol on the track.
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(3.0)
elif cl['example_index'] == 8:
car['x_m'] = 2.0; car['v_mps'] = 0.0; car['a_mps2'] = -2.0
env['CR'] = 1.0
env['fps_target'] = 10
track['show_start_mark'] = True
print_header(car)
explaination = '''
This is like the previous example (7), except the target frame rate is reduced to give
the lower precision in the physics predictions.
Note the car does NOT return to the initial starting point (as marked on the track by column of period symbols).
||
The "0"s are very visible in this example. As mentioned before, these are marks to indicate the
position of the car at the time of collision detection (before stickiness correction
is applied to resolve the state of overlap).
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(3.0)
elif cl['example_index'] == 9:
car['x_m'] = 2.0; car['v_mps'] = 0.0; car['a_mps2'] = -2.0
env['CR'] = 1.0
env['fps_target'] = 10
env['use_observed_dt'] = True
track['show_start_mark'] = True
print_header(car)
explaination = '''
The observed dt is used in the subsequent frame to calculate the
physics engine motions.
All other settings are identical to those in example 8.
Note the car, again, does NOT return to the initial starting point (as marked on the track by column of period symbols).
But here the return behavior is more variable.
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(3.0)
elif cl['example_index'] == 10:
car['x_m'] = 2.0; car['v_mps'] = 0.0; car['a_mps2'] = -2.0
env['CR'] = 1.0
env['fps_target'] = 10
track['show_start_mark'] = True
env['exact_solution'] = True
print_header(car)
explaination = '''
The Euler-method calculation has been replaced
with an exact calculation method in this example. The calculation uses physics kinematics equations
to model the motion between the ends of the track and also the motion in the collision frames. The frame rate is set low to give
the most severe test for this exact method. Note the car consistently returns to the initial starting point.
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(3.0)
elif cl['example_index'] == 11:
car['x_m'] = 2.0; car['v_mps'] = 0.0; car['a_mps2'] = -2.0
env['CR'] = 1.0
env['fps_target'] = 10
track['show_start_mark'] = True
env['exact_solution'] = True
env['use_observed_dt'] = True
print_header(car)
explaination = '''
The observed dt is used in subsequent frames. Note that in contrast to example 9, the car consistently returns to the initial starting point.
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(3.0)
elif cl['example_index'] == 12:
car['x_m'] = 2.0; car['v_mps'] = 0.0; car['a_mps2'] = -2.0
env['CR'] = 0.8
env['fps_target'] = 10
track['show_start_mark'] = True
env['exact_solution'] = True
env['use_observed_dt'] = True
print_header(car)
explaination = '''
Same as example 11 but with a CR of less than 1.0.
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(3.0)
else:
cl['example_index'] = "--> Defaults"
print_header(car)
explaination = '''
No command line arguments were supplied or there was no match for the mode value. Default parameters will be used.
||
Control-s pauses the run. Control-c stops the run.
'''
pretty_paragraphs( explaination, 1)
try_sleep(3.0)
def at_rest( car):
avg_car_position = pos_avg_10( car)
rest_tolerance = 0.001
if ( (abs(avg_car_position - track['left_edge_m']) < rest_tolerance) or
(abs(avg_car_position - track['right_edge_m']) < rest_tolerance) ):
return True
else:
return False
def cl_args_init():
cl['n_args'] = len(sys.argv) - 1
if cl['n_args'] >= 1:
cl['example_index'] = int(sys.argv[1])
cl['details'] = False
if cl['n_args'] == 2:
if sys.argv[2] == "d":
cl['details'] = True
def main():
global env, dt_s, track, fps_observed, x_list, cl
# A list to support calculating a running average of the position.
x_list = []
# Initialize the general parameters that control the environment.
env = {'stickiness_correction':True, 'correction_version_2':True ,
'm_to_px':55.0, 'CR':0.80, 'auto_off':True, 'fps_target':30,
'exact_solution':False, 'use_observed_dt':False}
# Characteristics of the air track (the 1-D range of space that the car moves along).
track = {'left_edge_m':0.25 , 'right_edge_m':2.2,
'show_start_mark':False,
'collision_state':'none',
'collision_mark_px':-999, 'show_collision_mark':True}
car = {'x_m':1.1, 'v_mps':0.0, 'a_mps2':0.0}
# Use the command-line arguments if provided. Put them in a dictionary.
cl = {'details':False, 'n_args':0}
cl_args_init()
# Modify the initial conditions for the car and environment if command line parameters
# were provided.
if (cl['n_args'] > 0):
modify( car, env)
fps_observed = env['fps_target']
dt_target_s = 1.0/env['fps_target']
dt_s = dt_target_s
dt_observed_s = dt_target_s
# A mark on the track where the car started at (optionally displayed)
track['track_mark_px'] = px_from_m(car['x_m'])
t_now_s = default_timer()
for j in range( 50000):
try:
t_previous_s = t_now_s
if (dt_observed_s < 0.15):
move( car)
check_for_wall_collisions( car)
render( car)
if env['auto_off']:
if at_rest( car):
break
time.sleep( dt_target_s)
t_now_s = default_timer()
dt_observed_s = t_now_s - t_previous_s
if env['use_observed_dt']:
dt_s = dt_observed_s
fps_observed = 1/dt_observed_s
except KeyboardInterrupt:
# You pressed control-c.
print "Stopped by keyboard."
break
except:
print "There is a problem in the TRY block above: \n"
# The following "raise" will print out the traceback.
raise
break
main()