driving_physics 0.0.0.3 → 0.0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +31 -1
- data/Rakefile +84 -0
- data/VERSION +1 -1
- data/demo/car.rb +178 -0
- data/demo/disk.rb +83 -0
- data/demo/gearbox.rb +53 -0
- data/demo/motor.rb +140 -0
- data/demo/mruby/disk.rb +81 -0
- data/demo/mruby/motor.rb +137 -0
- data/demo/powertrain.rb +47 -0
- data/demo/scalar_force.rb +41 -15
- data/demo/tire.rb +87 -0
- data/demo/vector_force.rb +46 -16
- data/lib/driving_physics/car.rb +123 -0
- data/lib/driving_physics/cli.rb +51 -0
- data/lib/driving_physics/disk.rb +185 -0
- data/lib/driving_physics/gearbox.rb +109 -0
- data/lib/driving_physics/imperial.rb +6 -0
- data/lib/driving_physics/motor.rb +150 -0
- data/lib/driving_physics/mruby.rb +45 -0
- data/lib/driving_physics/power.rb +20 -0
- data/lib/driving_physics/powertrain.rb +50 -0
- data/lib/driving_physics/scalar_force.rb +6 -3
- data/lib/driving_physics/tire.rb +120 -0
- data/lib/driving_physics/vector_force.rb +15 -2
- data/lib/driving_physics.rb +2 -0
- data/test/disk.rb +132 -0
- data/test/scalar_force.rb +7 -5
- data/test/{wheel.rb → tire.rb} +65 -55
- data/test/vector_force.rb +7 -1
- metadata +20 -4
- data/demo/wheel.rb +0 -84
- data/lib/driving_physics/wheel.rb +0 -191
data/demo/mruby/motor.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
|
2
|
+
# next fun idea: PID controller
|
3
|
+
|
4
|
+
include DrivingPhysics
|
5
|
+
|
6
|
+
env = Environment.new
|
7
|
+
motor = Motor.new(env)
|
8
|
+
puts env
|
9
|
+
puts motor
|
10
|
+
puts
|
11
|
+
|
12
|
+
puts "Rev it up!"
|
13
|
+
motor.throttle = 1.0
|
14
|
+
[:torque, :power].each { |run|
|
15
|
+
puts
|
16
|
+
puts run.to_s.upcase + ':'
|
17
|
+
|
18
|
+
800.upto(7000) { |rpm|
|
19
|
+
next unless rpm % 200 == 0
|
20
|
+
omega = DrivingPhysics.omega(rpm)
|
21
|
+
torque = motor.torque(rpm)
|
22
|
+
case run
|
23
|
+
when :torque
|
24
|
+
count = (torque.to_f / 10).round
|
25
|
+
char = 'T'
|
26
|
+
val = torque.round.to_s.rjust(5, ' ')
|
27
|
+
fmt = "%s Nm"
|
28
|
+
when :power
|
29
|
+
power = DrivingPhysics.power(torque, omega)
|
30
|
+
kw = power.to_f / 1000
|
31
|
+
count = (kw / 5).round
|
32
|
+
char = 'P'
|
33
|
+
val = kw.round(1).to_s.rjust(5, ' ')
|
34
|
+
fmt = "%s kW"
|
35
|
+
else
|
36
|
+
raise "unknown"
|
37
|
+
end
|
38
|
+
puts format("%s RPM: #{fmt}\t%s",
|
39
|
+
rpm.to_s.rjust(4, ' '),
|
40
|
+
val,
|
41
|
+
char * count)
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
puts
|
46
|
+
puts "Now, the simulation begins..."
|
47
|
+
puts "---"
|
48
|
+
puts "* Spin the motor up to #{motor.idle_rpm} RPM with the starter motor."
|
49
|
+
puts "* Rev it up with the throttle."
|
50
|
+
puts "* Let it die."
|
51
|
+
CLI.pause
|
52
|
+
|
53
|
+
alpha = 0.0
|
54
|
+
omega = 0.0
|
55
|
+
theta = 0.0
|
56
|
+
|
57
|
+
duration = 60
|
58
|
+
|
59
|
+
status = :ignition
|
60
|
+
rpm = 0
|
61
|
+
|
62
|
+
paused = 0.0
|
63
|
+
num_ticks = duration * env.hz + 1
|
64
|
+
start = Timer.now
|
65
|
+
|
66
|
+
num_ticks.times { |i|
|
67
|
+
# this is an input torque; alpha is determined after inertia and friction
|
68
|
+
torque = case status
|
69
|
+
when :ignition
|
70
|
+
motor.starter_torque
|
71
|
+
when :running, :off_throttle, :idling
|
72
|
+
motor.torque(rpm)
|
73
|
+
else
|
74
|
+
0
|
75
|
+
end
|
76
|
+
|
77
|
+
# Motor#alpha incorporates inertia and friction
|
78
|
+
alpha = motor.alpha(torque, omega: omega)
|
79
|
+
omega += alpha * env.tick
|
80
|
+
theta += omega * env.tick
|
81
|
+
|
82
|
+
# prevent silly oscillations due to ticks or tiny floating point errors
|
83
|
+
omega = 0 if omega < 0.0001
|
84
|
+
|
85
|
+
net_torque = motor.implied_torque(alpha)
|
86
|
+
rpm = DrivingPhysics.rpm(omega)
|
87
|
+
power = DrivingPhysics.power(net_torque, omega)
|
88
|
+
|
89
|
+
if rpm > motor.idle_rpm and status == :ignition
|
90
|
+
status = :running
|
91
|
+
flag = true
|
92
|
+
end
|
93
|
+
|
94
|
+
if rpm > 7000 and status == :running
|
95
|
+
status = :off_throttle
|
96
|
+
motor.throttle = 0.0
|
97
|
+
flag = true
|
98
|
+
end
|
99
|
+
|
100
|
+
if rpm < 1000 and status == :off_throttle
|
101
|
+
status = :idling
|
102
|
+
motor.throttle = 0.06
|
103
|
+
flag = true
|
104
|
+
end
|
105
|
+
|
106
|
+
if status == :idling
|
107
|
+
if rpm < 999
|
108
|
+
motor.throttle += (1.0 - motor.throttle) * 0.005
|
109
|
+
elsif rpm > 1001
|
110
|
+
motor.throttle -= motor.throttle * 0.005
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
if flag or
|
115
|
+
(i < 10) or
|
116
|
+
(i < 100 and i % 10 == 0) or
|
117
|
+
(i < 1000 and i % 100 == 0) or
|
118
|
+
(i < 10_000 and i % 500 == 0) or
|
119
|
+
(i % 5000 == 0) or
|
120
|
+
(status == :idling and i % 100 == 0)
|
121
|
+
puts Timer.display(ms: i)
|
122
|
+
puts format("Throttle: %.1f%%", motor.throttle * 100)
|
123
|
+
puts format("%d RPM %.1f Nm (%d Nm) %.1f kW Friction: %.1f Nm",
|
124
|
+
DrivingPhysics.rpm(omega),
|
125
|
+
net_torque,
|
126
|
+
torque,
|
127
|
+
power / 1000,
|
128
|
+
motor.spinner.rotating_friction(omega))
|
129
|
+
puts format("%.3f rad/s/s %.2f rad/s %.1f rad", alpha, omega, theta)
|
130
|
+
puts
|
131
|
+
|
132
|
+
paused += CLI.pause if flag
|
133
|
+
flag = false
|
134
|
+
end
|
135
|
+
}
|
136
|
+
|
137
|
+
puts Timer.summary(start, num_ticks, paused)
|
data/demo/powertrain.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'driving_physics/powertrain'
|
2
|
+
require 'driving_physics/imperial'
|
3
|
+
require 'driving_physics/cli'
|
4
|
+
require 'driving_physics/power'
|
5
|
+
|
6
|
+
include DrivingPhysics
|
7
|
+
|
8
|
+
env = Environment.new
|
9
|
+
motor = Motor.new(env)
|
10
|
+
gearbox = Gearbox.new(env)
|
11
|
+
powertrain = Powertrain.new(motor, gearbox)
|
12
|
+
motor.throttle = 1.0
|
13
|
+
puts env
|
14
|
+
puts powertrain
|
15
|
+
CLI.pause
|
16
|
+
|
17
|
+
crank_alpha = 0.0
|
18
|
+
crank_omega = 0.0
|
19
|
+
|
20
|
+
axle_alpha = 0.0
|
21
|
+
axle_omega = 0.0
|
22
|
+
|
23
|
+
# Run through the gears
|
24
|
+
1.upto(6) { |gear|
|
25
|
+
gearbox.gear = gear
|
26
|
+
|
27
|
+
puts <<EOF
|
28
|
+
|
29
|
+
# GEAR #{gear} (#{gearbox.ratio})
|
30
|
+
#
|
31
|
+
EOF
|
32
|
+
|
33
|
+
800.upto(7000) { |rpm|
|
34
|
+
next unless rpm % 200 == 0
|
35
|
+
|
36
|
+
power, axle_torque, axle_omega = powertrain.output(rpm)
|
37
|
+
kw = power / 1000.to_f
|
38
|
+
mph = Imperial.mph(Disk.tangential(axle_omega, 0.32))
|
39
|
+
ps = Imperial.ps(kw)
|
40
|
+
puts format("%s RPM: %s Nm %s kW %s PS\t%s mph",
|
41
|
+
rpm.round.to_s.rjust(4, ' '),
|
42
|
+
axle_torque.round(1).to_s.rjust(5, ' '),
|
43
|
+
kw.round(1).to_s.rjust(5, ' '),
|
44
|
+
ps.round.to_s.rjust(4, ' '),
|
45
|
+
mph.round.to_s.rjust(3, ' '))
|
46
|
+
}
|
47
|
+
}
|
data/demo/scalar_force.rb
CHANGED
@@ -1,26 +1,52 @@
|
|
1
1
|
require 'driving_physics/scalar_force'
|
2
|
+
require 'driving_physics/environment'
|
3
|
+
require 'driving_physics/cli'
|
2
4
|
|
3
|
-
|
5
|
+
include DrivingPhysics
|
6
|
+
|
7
|
+
env = Environment.new
|
8
|
+
puts env
|
4
9
|
|
5
|
-
pos = 0 # m
|
6
|
-
spd = 0 # m/s
|
7
10
|
mass = 1000 # kg
|
8
|
-
weight = mass *
|
11
|
+
weight = mass * env.g # N
|
9
12
|
drive_force = 7000 # N
|
10
13
|
duration = 100 # seconds
|
11
|
-
tick = 1.0 / DP::HZ
|
12
14
|
|
13
|
-
(
|
14
|
-
|
15
|
+
puts format("Force: %d N Mass: %d kg for %d seconds",
|
16
|
+
drive_force, mass, duration)
|
17
|
+
CLI.pause
|
18
|
+
|
19
|
+
spd = 0.0 # m/s
|
20
|
+
pos = 0.0 # m
|
21
|
+
num_ticks = duration * env.hz + 1
|
22
|
+
|
23
|
+
flag = false
|
24
|
+
phase = :accelerate
|
25
|
+
paused = 0.0
|
26
|
+
start = Timer.now
|
27
|
+
|
28
|
+
num_ticks.times { |i|
|
29
|
+
# TODO: make the resistance force negative
|
30
|
+
net_force = drive_force + ScalarForce.all_resistance(spd, nf_mag: weight)
|
15
31
|
|
16
|
-
|
17
|
-
spd
|
18
|
-
pos
|
32
|
+
acc = DrivingPhysics.acc(net_force, mass)
|
33
|
+
spd += acc * env.tick
|
34
|
+
pos += spd * env.tick
|
19
35
|
|
20
|
-
if
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
36
|
+
if phase == :accelerate and spd.magnitude > 100
|
37
|
+
flag = true
|
38
|
+
phase = :coast
|
39
|
+
drive_force = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
if flag or (i % 1000 == 0)
|
43
|
+
puts format("%d %.3f m/s/s %.2f m/s %.1f m",
|
44
|
+
i.to_f / env.hz, acc, spd, pos)
|
45
|
+
if flag
|
46
|
+
paused += CLI.pause
|
47
|
+
flag = false
|
48
|
+
end
|
25
49
|
end
|
26
50
|
}
|
51
|
+
|
52
|
+
puts Timer.summary(start, num_ticks, paused)
|
data/demo/tire.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'driving_physics/tire'
|
2
|
+
require 'driving_physics/imperial'
|
3
|
+
require 'driving_physics/cli'
|
4
|
+
|
5
|
+
include DrivingPhysics
|
6
|
+
|
7
|
+
env = Environment.new
|
8
|
+
tire = Tire.new(env)
|
9
|
+
puts env
|
10
|
+
puts tire
|
11
|
+
puts
|
12
|
+
|
13
|
+
duration = 100 # sec
|
14
|
+
axle_torque = 500 # N*m
|
15
|
+
supported_mass = 1500 # kg
|
16
|
+
|
17
|
+
puts "Given:"
|
18
|
+
puts "* #{axle_torque} Nm axle torque"
|
19
|
+
puts "* #{supported_mass} kg supported mass"
|
20
|
+
puts "* 4 tires"
|
21
|
+
puts "* #{duration} seconds"
|
22
|
+
puts
|
23
|
+
|
24
|
+
total_mass = supported_mass + 4 * tire.mass
|
25
|
+
corner_mass = Rational(total_mass) / 4
|
26
|
+
normal_force = corner_mass * env.g
|
27
|
+
|
28
|
+
puts "Therefore:"
|
29
|
+
puts format("* %.1f kg total mass", total_mass)
|
30
|
+
puts format("* %.1f kg per corner", corner_mass)
|
31
|
+
puts format("* %.1f N normal force", normal_force)
|
32
|
+
puts
|
33
|
+
|
34
|
+
traction = tire.traction(normal_force)
|
35
|
+
drive_force = tire.force(axle_torque)
|
36
|
+
|
37
|
+
puts "Tires:"
|
38
|
+
puts format("* %.1f N traction", traction)
|
39
|
+
puts format("* %.1f N drive force", drive_force)
|
40
|
+
|
41
|
+
CLI.pause
|
42
|
+
|
43
|
+
acc = 0.0 # meters/s/s
|
44
|
+
speed = 0.0 # meters/s
|
45
|
+
dist = 0.0 # meters
|
46
|
+
|
47
|
+
alpha = 0.0 # radians/s/s
|
48
|
+
omega = 0.0 # radians/s
|
49
|
+
theta = 0.0 # radians
|
50
|
+
|
51
|
+
start = Timer.now
|
52
|
+
paused = 0.0
|
53
|
+
num_ticks = duration * env.hz + 1
|
54
|
+
|
55
|
+
num_ticks.times { |i|
|
56
|
+
torque = tire.net_tractable_torque(axle_torque,
|
57
|
+
mass: total_mass,
|
58
|
+
omega: omega,
|
59
|
+
normal_force: normal_force)
|
60
|
+
force = tire.force(torque)
|
61
|
+
|
62
|
+
# translational kinematics
|
63
|
+
acc = DrivingPhysics.acc(force, total_mass)
|
64
|
+
speed += acc * env.tick
|
65
|
+
dist += speed * env.tick
|
66
|
+
mph = Imperial.mph(speed)
|
67
|
+
|
68
|
+
# rotational kinematics
|
69
|
+
alpha = acc / tire.radius
|
70
|
+
omega += alpha * env.tick
|
71
|
+
theta += omega * env.tick
|
72
|
+
|
73
|
+
if i < 10 or
|
74
|
+
(i < 20_000 and i%1000 == 0) or
|
75
|
+
(i % 10_000 == 0)
|
76
|
+
|
77
|
+
puts DrivingPhysics.elapsed_display(i)
|
78
|
+
puts format(" Tire: %.1f r %.2f r/s %.3f r/s^2", theta, omega, alpha)
|
79
|
+
puts format(" Car: %.1f m %.2f m/s %.3f m/s^2 (%d mph)",
|
80
|
+
dist, speed, acc, mph)
|
81
|
+
puts format("Torque: %.1f Nm (%d N) Loss: %.1f%%",
|
82
|
+
torque, force, (1.0 - torque / axle_torque) * 100)
|
83
|
+
puts
|
84
|
+
end
|
85
|
+
}
|
86
|
+
|
87
|
+
puts Timer.summary(start, num_ticks, paused)
|
data/demo/vector_force.rb
CHANGED
@@ -1,26 +1,56 @@
|
|
1
1
|
require 'driving_physics/vector_force'
|
2
|
+
require 'driving_physics/environment'
|
3
|
+
require 'driving_physics/cli'
|
2
4
|
|
3
|
-
|
5
|
+
include DrivingPhysics
|
6
|
+
|
7
|
+
env = Environment.new
|
8
|
+
puts env
|
4
9
|
|
5
|
-
p = Vector[0, 0] # m
|
6
|
-
v = Vector[0, 0] # m/s
|
7
10
|
mass = 1000 # kg
|
8
|
-
weight = mass *
|
11
|
+
weight = mass * env.g # N
|
12
|
+
drive_force = DrivingPhysics.random_unit_vector * 7000 # N
|
9
13
|
duration = 100 # seconds
|
10
|
-
drive_force = DP.random_unit_vector * 7000 # N
|
11
|
-
tick = 1.0 / DP::HZ
|
12
14
|
|
13
|
-
(
|
14
|
-
|
15
|
+
puts format("Force: %d N Dir: %s",
|
16
|
+
drive_force.magnitude,
|
17
|
+
DrivingPhysics.compass_dir(drive_force.normalize))
|
18
|
+
puts format("Mass: %d kg for %d seconds", mass, duration)
|
19
|
+
CLI.pause
|
20
|
+
|
21
|
+
acc = Vector[0, 0] # m/s/s
|
22
|
+
vel = Vector[0, 0] # m/s
|
23
|
+
pos = Vector[0, 0] # m
|
24
|
+
|
25
|
+
num_ticks = duration * env.hz + 1
|
26
|
+
|
27
|
+
flag = false
|
28
|
+
phase = :accelerate
|
29
|
+
paused = 0.0
|
30
|
+
start = Timer.now
|
15
31
|
|
16
|
-
|
17
|
-
|
18
|
-
|
32
|
+
num_ticks.times { |i|
|
33
|
+
net_force = drive_force +
|
34
|
+
VectorForce.all_resistance(vel, dir: vel, nf_mag: weight)
|
19
35
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
36
|
+
acc = DrivingPhysics.acc(net_force, mass)
|
37
|
+
vel += acc * env.tick
|
38
|
+
pos += vel * env.tick
|
39
|
+
|
40
|
+
if phase == :accelerate and vel.magnitude > 100
|
41
|
+
flag = true
|
42
|
+
phase = :coast
|
43
|
+
drive_force = Vector[0, 0]
|
44
|
+
end
|
45
|
+
|
46
|
+
if flag or (i % 1000 == 0)
|
47
|
+
puts format("%d %.3f m/s/s %.2f m/s %.1f m",
|
48
|
+
i.to_f / env.hz, acc.magnitude, vel.magnitude, pos.magnitude)
|
49
|
+
if flag
|
50
|
+
paused = CLI.pause
|
51
|
+
flag = false
|
52
|
+
end
|
25
53
|
end
|
26
54
|
}
|
55
|
+
|
56
|
+
puts Timer.summary(start, num_ticks, paused)
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'driving_physics/tire'
|
2
|
+
require 'driving_physics/powertrain'
|
3
|
+
|
4
|
+
module DrivingPhysics
|
5
|
+
class Car
|
6
|
+
attr_reader :tire, :powertrain, :env
|
7
|
+
attr_accessor :num_tires, :body_mass, :frontal_area, :cd
|
8
|
+
|
9
|
+
def initialize(tire:, powertrain:)
|
10
|
+
@num_tires = 4
|
11
|
+
@tire = tire
|
12
|
+
@env = @tire.env
|
13
|
+
@powertrain = powertrain
|
14
|
+
@body_mass = 1000.0
|
15
|
+
@frontal_area = DrivingPhysics::FRONTAL_AREA
|
16
|
+
@cd = DrivingPhysics::DRAG_COF
|
17
|
+
|
18
|
+
yield self if block_given?
|
19
|
+
end
|
20
|
+
|
21
|
+
def throttle
|
22
|
+
@powertrain.motor.throttle
|
23
|
+
end
|
24
|
+
|
25
|
+
def throttle=(val)
|
26
|
+
@powertrain.motor.throttle = val
|
27
|
+
end
|
28
|
+
|
29
|
+
def gear
|
30
|
+
@powertrain.gearbox.gear
|
31
|
+
end
|
32
|
+
|
33
|
+
def gear=(val)
|
34
|
+
@powertrain.gearbox.gear = val
|
35
|
+
end
|
36
|
+
|
37
|
+
def top_gear
|
38
|
+
@powertrain.gearbox.top_gear
|
39
|
+
end
|
40
|
+
|
41
|
+
# force opposing speed
|
42
|
+
def air_force(speed)
|
43
|
+
-0.5 * @frontal_area * @cd * @env.air_density * speed ** 2
|
44
|
+
end
|
45
|
+
|
46
|
+
# force of opposite sign to omega
|
47
|
+
def tire_rolling_force(omega)
|
48
|
+
@num_tires *
|
49
|
+
@tire.rolling_friction(omega, normal_force: self.normal_force) /
|
50
|
+
@tire.radius
|
51
|
+
end
|
52
|
+
|
53
|
+
# force of opposite sign to omega
|
54
|
+
def tire_rotational_force(omega)
|
55
|
+
@num_tires *
|
56
|
+
@tire.rotating_friction(omega, normal_force: self.normal_force) /
|
57
|
+
@tire.radius
|
58
|
+
end
|
59
|
+
|
60
|
+
# force of opposite sign to force
|
61
|
+
def tire_inertial_force(force)
|
62
|
+
mag = force.abs
|
63
|
+
sign = force / mag
|
64
|
+
force_loss = 0
|
65
|
+
5.times {
|
66
|
+
# use magnitude, so we are subtracting only positive numbers
|
67
|
+
acc = DrivingPhysics.acc(mag - force_loss, self.total_mass)
|
68
|
+
alpha = acc / @tire.radius
|
69
|
+
# this will be a positive number
|
70
|
+
force_loss = @num_tires * @tire.implied_torque(alpha) /
|
71
|
+
@tire.radius
|
72
|
+
}
|
73
|
+
# oppose initial force
|
74
|
+
-1 * sign * force_loss
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
[[format("Mass: %.1f kg", self.total_mass),
|
79
|
+
format("Fr.A: %.2f m^2", @frontal_area),
|
80
|
+
format("cD: %.2f", @cd),
|
81
|
+
].join(' | '),
|
82
|
+
format("POWERTRAIN:\n%s", @powertrain),
|
83
|
+
format("Tires: %s", @tire),
|
84
|
+
format("Corner mass: %.1f kg | Normal force: %.1f N",
|
85
|
+
self.corner_mass, self.normal_force),
|
86
|
+
].join("\n")
|
87
|
+
end
|
88
|
+
|
89
|
+
def drive_force(rpm)
|
90
|
+
@tire.force(@powertrain.axle_torque(rpm))
|
91
|
+
end
|
92
|
+
|
93
|
+
def tire_speed(rpm)
|
94
|
+
@tire.tangential(@powertrain.axle_omega(rpm))
|
95
|
+
end
|
96
|
+
|
97
|
+
def motor_rpm(tire_speed)
|
98
|
+
@powertrain.gearbox.crank_rpm(tire_speed / @tire_radius)
|
99
|
+
end
|
100
|
+
|
101
|
+
def total_mass
|
102
|
+
@body_mass + @powertrain.mass + @tire.mass * @num_tires
|
103
|
+
end
|
104
|
+
|
105
|
+
def corner_mass
|
106
|
+
self.total_mass / @num_tires
|
107
|
+
end
|
108
|
+
|
109
|
+
# per tire
|
110
|
+
def normal_force
|
111
|
+
self.corner_mass * @env.g
|
112
|
+
end
|
113
|
+
|
114
|
+
# per tire
|
115
|
+
def tire_traction
|
116
|
+
@tire.traction(self.normal_force)
|
117
|
+
end
|
118
|
+
|
119
|
+
def total_normal_force
|
120
|
+
self.total_mass * env.g
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module DrivingPhysics
|
2
|
+
module CLI
|
3
|
+
# returns user input as a string
|
4
|
+
def self.prompt(msg = '')
|
5
|
+
print msg + ' ' unless msg.empty?
|
6
|
+
print '> '
|
7
|
+
$stdin.gets.chomp
|
8
|
+
end
|
9
|
+
|
10
|
+
# press Enter to continue, ignore input, return elapsed time
|
11
|
+
def self.pause(msg = '')
|
12
|
+
t = Timer.now
|
13
|
+
puts msg unless msg.empty?
|
14
|
+
puts ' [ Press Enter ]'
|
15
|
+
$stdin.gets
|
16
|
+
Timer.since(t)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Timer
|
21
|
+
if defined? Process::CLOCK_MONOTONIC
|
22
|
+
def self.now
|
23
|
+
Process.clock_gettime Process::CLOCK_MONOTONIC
|
24
|
+
end
|
25
|
+
else
|
26
|
+
def self.now
|
27
|
+
Time.now
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.since(t)
|
32
|
+
self.now - t
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.elapsed(&work)
|
36
|
+
t = self.now
|
37
|
+
return yield, self.since(t)
|
38
|
+
end
|
39
|
+
|
40
|
+
# HH:MM:SS.mmm
|
41
|
+
def self.display(seconds: 0, ms: 0)
|
42
|
+
ms += (seconds * 1000).round if seconds > 0
|
43
|
+
DrivingPhysics.elapsed_display(ms)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.summary(start, num_ticks, paused = 0)
|
47
|
+
elapsed = self.since(start) - paused
|
48
|
+
format("%.3f s (%d ticks/s)", elapsed, num_ticks.to_f / elapsed)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|