driving_physics 0.0.2.1 → 0.0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +17 -14
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/demo/car.rb +83 -73
- data/demo/motor.rb +7 -4
- data/demo/mruby/car.rb +184 -0
- data/demo/mruby/motor.rb +7 -4
- data/demo/pid_controller.rb +143 -0
- data/demo/powertrain.rb +1 -1
- data/driving_physics.gemspec +2 -1
- data/lib/driving_physics/car.rb +13 -4
- data/lib/driving_physics/cli.rb +7 -35
- data/lib/driving_physics/cockpit.rb +167 -0
- data/lib/driving_physics/disk.rb +5 -25
- data/lib/driving_physics/environment.rb +2 -0
- data/lib/driving_physics/gearbox.rb +60 -30
- data/lib/driving_physics/motor.rb +166 -58
- data/lib/driving_physics/mruby.rb +3 -0
- data/lib/driving_physics/pid_controller.rb +111 -0
- data/lib/driving_physics/powertrain.rb +8 -13
- data/lib/driving_physics/scalar_force.rb +2 -1
- data/lib/driving_physics/timer.rb +34 -0
- data/lib/driving_physics/vector_force.rb +23 -0
- data/lib/driving_physics.rb +28 -22
- data/test/disk.rb +23 -62
- data/test/gearbox.rb +36 -0
- data/test/motor.rb +236 -0
- data/test/powertrain.rb +21 -0
- data/test/scalar_force.rb +3 -6
- data/test/tire.rb +25 -59
- data/test/vector_force.rb +76 -37
- metadata +26 -5
- data/demo/mruby/disk.rb +0 -81
@@ -1,96 +1,204 @@
|
|
1
1
|
require 'driving_physics/disk'
|
2
2
|
|
3
3
|
module DrivingPhysics
|
4
|
+
def self.interpolate(x, xs:, ys:)
|
5
|
+
raise("Xs size #{xs.size}; Ys size #{ys.size}") unless xs.size == ys.size
|
6
|
+
raise("#{x} out of range") if x < xs.first or x > xs.last
|
7
|
+
xs.each.with_index { |xi, i|
|
8
|
+
return ys[i] if x == xi
|
9
|
+
if i > 0
|
10
|
+
last_x, last_y = xs[i-1], ys[i-1]
|
11
|
+
raise("xs out of order (#{xi} <= #{last_x})") unless xi > last_x
|
12
|
+
if x <= xi
|
13
|
+
proportion = Rational(x - last_x) / (xi - last_x)
|
14
|
+
return last_y + (ys[i] - last_y) * proportion
|
15
|
+
end
|
16
|
+
end
|
17
|
+
}
|
18
|
+
raise("couldn't find #{x} in #{xs.inspect}") # sanity check
|
19
|
+
end
|
20
|
+
|
21
|
+
class TorqueCurve
|
22
|
+
RPMS = [500, 1000, 1500, 2000, 2500, 3500, 5000, 6000, 7000, 7100]
|
23
|
+
TORQUES = [ 0, 70, 130, 200, 250, 320, 330, 320, 260, 0]
|
24
|
+
RPM_IDX = {
|
25
|
+
min: 0,
|
26
|
+
idle: 1,
|
27
|
+
redline: -2,
|
28
|
+
max: -1,
|
29
|
+
}
|
30
|
+
|
31
|
+
def self.validate_rpms!(rpms)
|
32
|
+
raise("rpms should be positive") if rpms.any? { |r| r < 0 }
|
33
|
+
rpms.each.with_index { |r, i|
|
34
|
+
if i > 0 and r <= rpms[i-1]
|
35
|
+
raise("rpms #{rpms.inspect} should increase")
|
36
|
+
end
|
37
|
+
}
|
38
|
+
rpms
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.validate_torques!(torques)
|
42
|
+
raise("first torque should be zero") unless torques.first == 0
|
43
|
+
raise("last torque should be zero") unless torques.last == 0
|
44
|
+
raise("torques should be positive") if torques.any? { |t| t < 0 }
|
45
|
+
torques
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(rpms: RPMS, torques: TORQUES)
|
49
|
+
if rpms.size != torques.size
|
50
|
+
raise("RPMs size #{rpms.size}; Torques size #{torques.size}")
|
51
|
+
end
|
52
|
+
@rpms = self.class.validate_rpms! rpms
|
53
|
+
@torques = self.class.validate_torques! torques
|
54
|
+
peak_torque = 0
|
55
|
+
idx = 0
|
56
|
+
@torques.each.with_index { |t, i|
|
57
|
+
if t > peak_torque
|
58
|
+
peak_torque = t
|
59
|
+
idx = i
|
60
|
+
end
|
61
|
+
}
|
62
|
+
@peak = idx
|
63
|
+
end
|
64
|
+
|
65
|
+
def peak
|
66
|
+
[@rpms[@peak], @torques[@peak]]
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
@rpms.map.with_index { |r, i|
|
71
|
+
format("%s RPM %s Nm",
|
72
|
+
r.to_s.rjust(5, ' '),
|
73
|
+
@torques[i].round(1).to_s.rjust(4, ' '))
|
74
|
+
}.join("\n")
|
75
|
+
end
|
76
|
+
|
77
|
+
RPM_IDX.each { |name, idx| define_method(name) do @rpms[idx] end }
|
78
|
+
|
79
|
+
# interpolate based on torque curve points
|
80
|
+
def torque(rpm)
|
81
|
+
DrivingPhysics.interpolate(rpm, xs: @rpms, ys: @torques)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# represent all rotating mass as one big flywheel
|
4
86
|
class Motor
|
5
87
|
class Stall < RuntimeError; end
|
6
88
|
class OverRev < RuntimeError; end
|
7
|
-
class SanityCheck < RuntimeError; end
|
8
89
|
|
9
|
-
|
10
|
-
RPMS = [500, 1000, 1500, 2000, 2500, 3500, 5000, 6000, 7000, 7100]
|
11
|
-
ENGINE_BRAKING = 0.2 # 20% of the torque at a given RPM
|
90
|
+
CLOSED_THROTTLE = 0.01 # threshold for engine braking
|
12
91
|
|
13
|
-
|
14
|
-
|
15
|
-
|
92
|
+
# What percentage of the nominal torque at a given RPM would slow the
|
93
|
+
# motor when off throttle? 0% at 0 RPM; 50% at 7000 RPM
|
94
|
+
def self.engine_braking(rpm)
|
95
|
+
(rpm / 14_000.0).clamp(0, 0.5)
|
96
|
+
end
|
16
97
|
|
17
|
-
|
18
|
-
|
98
|
+
attr_reader :env, :torque_curve, :throttle
|
99
|
+
attr_accessor :fixed_mass, :spinner, :starter_torque
|
19
100
|
|
20
|
-
|
21
|
-
|
22
|
-
|
101
|
+
# Originally, torque_curve was a kwarg; but mruby currently has a bug
|
102
|
+
# where block_given? returns true in the presence of an unset default
|
103
|
+
# kwarg, or something like that.
|
104
|
+
# https://github.com/mruby/mruby/issues/5741
|
105
|
+
#
|
106
|
+
def initialize(env, torque_curve = nil)
|
107
|
+
@env = env
|
108
|
+
@torque_curve = torque_curve.nil? ? TorqueCurve.new : torque_curve
|
109
|
+
@throttle = 0.0 # 0.0 - 1.0 (0% - 100%)
|
23
110
|
|
24
|
-
|
25
|
-
@
|
26
|
-
fly.
|
27
|
-
fly.
|
28
|
-
fly.base_friction
|
29
|
-
fly.omega_friction =
|
111
|
+
@fixed_mass = 125
|
112
|
+
@flywheel = Disk.new(@env) { |fly|
|
113
|
+
fly.mass = 12
|
114
|
+
fly.radius = 0.20
|
115
|
+
fly.base_friction = 1.0 / 1_000
|
116
|
+
fly.omega_friction = 5.0 / 10_000
|
117
|
+
}
|
118
|
+
@crankshaft = Disk.new(@env) { |crank|
|
119
|
+
crank.mass = 25
|
120
|
+
crank.radius = 0.04
|
121
|
+
crank.base_friction = 1.0 / 1_000
|
122
|
+
crank.omega_friction = 5.0 / 10_000
|
30
123
|
}
|
124
|
+
|
31
125
|
@starter_torque = 500 # Nm
|
32
|
-
|
33
|
-
|
126
|
+
|
127
|
+
yield self if block_given?
|
128
|
+
end
|
129
|
+
|
130
|
+
def redline
|
131
|
+
@torque_curve.redline
|
132
|
+
end
|
133
|
+
|
134
|
+
def idle
|
135
|
+
@torque_curve.idle
|
34
136
|
end
|
35
137
|
|
36
138
|
def to_s
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
139
|
+
peak_rpm, peak_tq = *@torque_curve.peak
|
140
|
+
[format("Peak Torque: %d Nm @ %d RPM Redline: %d",
|
141
|
+
peak_tq, peak_rpm, @torque_curve.redline),
|
142
|
+
format(" Throttle: %s Mass: %.1f kg (%d kg fixed)",
|
143
|
+
self.throttle_pct, self.mass, @fixed_mass),
|
144
|
+
format(" Crankshaft: %s", @crankshaft),
|
145
|
+
format(" Flywheel: %s", @flywheel),
|
146
|
+
].join("\n")
|
147
|
+
end
|
148
|
+
|
149
|
+
def inertia
|
150
|
+
@crankshaft.rotational_inertia + @flywheel.rotational_inertia
|
151
|
+
end
|
152
|
+
|
153
|
+
def energy(omega)
|
154
|
+
@crankshaft.energy(omega) + @flywheel.energy(omega)
|
47
155
|
end
|
48
156
|
|
49
|
-
def
|
50
|
-
@
|
157
|
+
def friction(omega)
|
158
|
+
@crankshaft.rotating_friction(omega) +
|
159
|
+
@flywheel.rotating_friction(omega)
|
51
160
|
end
|
52
161
|
|
53
162
|
def mass
|
54
|
-
@
|
163
|
+
@fixed_mass + self.rotating_mass
|
164
|
+
end
|
165
|
+
|
166
|
+
def rotating_mass
|
167
|
+
@crankshaft.mass + @flywheel.mass
|
55
168
|
end
|
56
169
|
|
57
170
|
def throttle=(val)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
171
|
+
@throttle = DrivingPhysics.unit_interval! val
|
172
|
+
end
|
173
|
+
|
174
|
+
def throttle_pct(places = 1)
|
175
|
+
format("%.#{places}f%%", @throttle * 100)
|
62
176
|
end
|
63
177
|
|
64
|
-
# given torque, determine crank alpha
|
178
|
+
# given torque, determine crank alpha considering inertia and friction
|
65
179
|
def alpha(torque, omega: 0)
|
66
|
-
|
180
|
+
(torque + self.friction(omega)) / self.inertia
|
67
181
|
end
|
68
182
|
|
69
183
|
def implied_torque(alpha)
|
70
|
-
|
184
|
+
alpha * self.inertia
|
71
185
|
end
|
72
186
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
raise(OverRev, "RPM #{rpm}") if rpm > @rpms.last
|
187
|
+
def output_torque(rpm)
|
188
|
+
self.torque(rpm) + self.friction(DrivingPhysics.omega(rpm))
|
189
|
+
end
|
77
190
|
|
78
|
-
|
191
|
+
# this is our "input torque" and it depends on @throttle
|
192
|
+
# here is where engine braking is implemented
|
193
|
+
def torque(rpm)
|
194
|
+
raise(Stall, "RPM #{rpm}") if rpm < @torque_curve.min
|
195
|
+
raise(OverRev, "RPM #{rpm}") if rpm > @torque_curve.max
|
79
196
|
|
80
|
-
|
81
|
-
|
82
|
-
if last_rpm <= rpm and rpm <= r
|
83
|
-
proportion = Rational(rpm - last_rpm) / (r - last_rpm)
|
84
|
-
torque = last_tq + (tq - last_tq) * proportion
|
85
|
-
break
|
86
|
-
end
|
87
|
-
last_rpm, last_tq = r, tq
|
88
|
-
}
|
89
|
-
raise(SanityCheck, "RPM #{rpm}") if torque.nil?
|
197
|
+
# interpolate based on torque curve points
|
198
|
+
torque = @torque_curve.torque(rpm)
|
90
199
|
|
91
|
-
if (@throttle <=
|
92
|
-
|
93
|
-
-1 * torque * ENGINE_BRAKING
|
200
|
+
if (@throttle <= CLOSED_THROTTLE)
|
201
|
+
-1 * torque * self.class.engine_braking(rpm)
|
94
202
|
else
|
95
203
|
torque * @throttle
|
96
204
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module DrivingPhysics
|
2
|
+
# we will have a control loop
|
3
|
+
# SP - setpoint, this is the desired position
|
4
|
+
# PV(t) - process variable, this is the sensed position, varying over time
|
5
|
+
# e(t) - error, SP - PV
|
6
|
+
# CV(t) - control variable: the controller output
|
7
|
+
|
8
|
+
# for example, where to set the throttle to maintain 1000 RPM
|
9
|
+
# SP - 1000 RPM
|
10
|
+
# PV - current RPM
|
11
|
+
# CV - throttle position
|
12
|
+
|
13
|
+
class PIDController
|
14
|
+
HZ = 1000
|
15
|
+
TICK = Rational(1) / HZ
|
16
|
+
|
17
|
+
# Ziegler-Nichols method for tuning PID gain knobs
|
18
|
+
ZN = {
|
19
|
+
# Kp Ti Td Ki Kd
|
20
|
+
# Var: Ku Tu Tu Ku/Tu Ku*Tu
|
21
|
+
'P' => [0.500],
|
22
|
+
'PI' => [0.450, 0.800, nil, 0.540],
|
23
|
+
'PD' => [0.800, nil, 0.125, nil, 0.100],
|
24
|
+
'PID' => [0.600, 0.500, 0.125, 1.200, 0.075],
|
25
|
+
'PIR' => [0.700, 0.400, 0.150, 1.750, 0.105],
|
26
|
+
# less overshoot than standard PID below
|
27
|
+
'some' => [0.333, 0.500, 0.333, 0.666, 0.111],
|
28
|
+
'none' => [0.200, 0.500, 0.333, 0.400, 0.066],
|
29
|
+
}
|
30
|
+
|
31
|
+
# ultimate gain, oscillation
|
32
|
+
def self.tune(type, ku, tu)
|
33
|
+
record = ZN[type.downcase] || ZN[type.upcase] || ZN.fetch(type)
|
34
|
+
kp, ti, td, ki, kd = *record
|
35
|
+
kp *= ku if kp
|
36
|
+
ti *= tu if ti
|
37
|
+
td *= tu if td
|
38
|
+
ki *= (ku / tu) if ki
|
39
|
+
kd *= (ku * tu) if kd
|
40
|
+
{ kp: kp, ti: ti, td: td, ki: ki, kd: kd }
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_accessor :kp, :ki, :kd, :dt, :setpoint,
|
44
|
+
:p_range, :i_range, :d_range, :o_range
|
45
|
+
attr_reader :measure, :error, :last_error, :sum_error
|
46
|
+
|
47
|
+
def initialize(setpoint, dt: TICK)
|
48
|
+
@setpoint, @dt, @measure = setpoint, dt, 0.0
|
49
|
+
|
50
|
+
# track error over time for integral and derivative
|
51
|
+
@error, @last_error, @sum_error = 0.0, 0.0, 0.0
|
52
|
+
|
53
|
+
# gain / multipliers for PID; tunables
|
54
|
+
@kp, @ki, @kd = 1.0, 1.0, 1.0
|
55
|
+
|
56
|
+
# optional clamps for PID terms and output
|
57
|
+
@p_range = (-Float::INFINITY..Float::INFINITY)
|
58
|
+
@i_range = (-Float::INFINITY..Float::INFINITY)
|
59
|
+
@d_range = (-Float::INFINITY..Float::INFINITY)
|
60
|
+
@o_range = (-Float::INFINITY..Float::INFINITY)
|
61
|
+
|
62
|
+
yield self if block_given?
|
63
|
+
end
|
64
|
+
|
65
|
+
def update(measure)
|
66
|
+
self.measure = measure
|
67
|
+
self.output
|
68
|
+
end
|
69
|
+
|
70
|
+
def measure=(val)
|
71
|
+
@measure = val
|
72
|
+
@last_error = @error
|
73
|
+
@error = @setpoint - @measure
|
74
|
+
if @error * @last_error <= 0 # zero crossing; reset the accumulated error
|
75
|
+
@sum_error = @error
|
76
|
+
else
|
77
|
+
@sum_error += @error
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def output
|
82
|
+
(self.proportion +
|
83
|
+
self.integral +
|
84
|
+
self.derivative).clamp(@o_range.begin, @o_range.end)
|
85
|
+
end
|
86
|
+
|
87
|
+
def proportion
|
88
|
+
(@kp * @error).clamp(@p_range.begin, @p_range.end)
|
89
|
+
end
|
90
|
+
|
91
|
+
def integral
|
92
|
+
(@ki * @sum_error * @dt).clamp(@i_range.begin, @i_range.end)
|
93
|
+
end
|
94
|
+
|
95
|
+
def derivative
|
96
|
+
(@kd * (@error - @last_error) / @dt).clamp(@d_range.begin, @d_range.end)
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_s
|
100
|
+
[format("Setpoint: %.3f Measure: %.3f",
|
101
|
+
@setpoint, @measure),
|
102
|
+
format("Error: %+.3f\tLast: %+.3f\tSum: %+.3f",
|
103
|
+
@error, @last_error, @sum_error),
|
104
|
+
format(" Gain:\t%.3f\t%.3f\t%.3f",
|
105
|
+
@kp, @ki, @kd),
|
106
|
+
format(" PID:\t%+.3f\t%+.3f\t%+.3f\t= %.5f",
|
107
|
+
self.proportion, self.integral, self.derivative, self.output),
|
108
|
+
].join("\n")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -10,7 +10,7 @@ module DrivingPhysics
|
|
10
10
|
class Powertrain
|
11
11
|
attr_reader :motor, :gearbox
|
12
12
|
|
13
|
-
def initialize(motor
|
13
|
+
def initialize(motor:, gearbox:)
|
14
14
|
@motor = motor
|
15
15
|
@gearbox = gearbox
|
16
16
|
end
|
@@ -29,22 +29,17 @@ module DrivingPhysics
|
|
29
29
|
[t * o, t, o]
|
30
30
|
end
|
31
31
|
|
32
|
-
def axle_torque(rpm)
|
33
|
-
|
34
|
-
|
35
|
-
crank_torque = @motor.implied_torque(crank_alpha)
|
36
|
-
|
37
|
-
axle_alpha = @gearbox.alpha(@gearbox.axle_torque(crank_torque),
|
38
|
-
omega: @gearbox.axle_omega(rpm))
|
39
|
-
@gearbox.implied_torque(axle_alpha)
|
32
|
+
def axle_torque(rpm, axle_omega: nil)
|
33
|
+
@gearbox.output_torque(@motor.output_torque(rpm), rpm,
|
34
|
+
axle_omega: axle_omega)
|
40
35
|
end
|
41
36
|
|
42
|
-
def axle_omega(rpm)
|
43
|
-
@gearbox.axle_omega(rpm)
|
37
|
+
def axle_omega(rpm, axle_omega: nil)
|
38
|
+
@gearbox.axle_omega(rpm, axle_omega: axle_omega)
|
44
39
|
end
|
45
40
|
|
46
|
-
def crank_rpm(axle_omega)
|
47
|
-
@gearbox.crank_rpm(axle_omega)
|
41
|
+
def crank_rpm(axle_omega, crank_rpm: nil)
|
42
|
+
@gearbox.crank_rpm(axle_omega, crank_rpm: crank_rpm)
|
48
43
|
end
|
49
44
|
end
|
50
45
|
end
|
@@ -23,7 +23,8 @@ module DrivingPhysics
|
|
23
23
|
frontal_area: FRONTAL_AREA,
|
24
24
|
drag_cof: DRAG_COF,
|
25
25
|
air_density: AIR_DENSITY)
|
26
|
-
|
26
|
+
(speed ** 2) * (speed > 0 ? -1 : 1) *
|
27
|
+
0.5 * frontal_area * drag_cof * air_density
|
27
28
|
end
|
28
29
|
|
29
30
|
# opposes the direction of speed
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module DrivingPhysics
|
2
|
+
module Timer
|
3
|
+
# don't use `defined?` with mruby
|
4
|
+
if (Process::CLOCK_MONOTONIC rescue false)
|
5
|
+
def self.now
|
6
|
+
Process.clock_gettime Process::CLOCK_MONOTONIC
|
7
|
+
end
|
8
|
+
else
|
9
|
+
def self.now
|
10
|
+
Time.now
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.since(t)
|
15
|
+
self.now - t
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.elapsed(&work)
|
19
|
+
t = self.now
|
20
|
+
return yield, self.since(t)
|
21
|
+
end
|
22
|
+
|
23
|
+
# HH:MM:SS.mmm
|
24
|
+
def self.display(seconds: 0, ms: 0)
|
25
|
+
ms += (seconds * 1000).round if seconds > 0
|
26
|
+
DrivingPhysics.elapsed_display(ms)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.summary(start, num_ticks, paused = 0)
|
30
|
+
elapsed = self.since(start) - paused
|
31
|
+
format("%.3f s (%d ticks/s)", elapsed, num_ticks.to_f / elapsed)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -64,6 +64,29 @@ module DrivingPhysics
|
|
64
64
|
dir
|
65
65
|
end
|
66
66
|
|
67
|
+
def self.torque_vector(force, radius)
|
68
|
+
if !force.is_a?(Vector) or force.size != 2
|
69
|
+
raise(ArgumentError, "force must be a 2D vector")
|
70
|
+
end
|
71
|
+
if !radius.is_a?(Vector) or radius.size != 2
|
72
|
+
raise(ArgumentError, "radius must be a 2D vector")
|
73
|
+
end
|
74
|
+
force = Vector[force[0], force[1], 0]
|
75
|
+
radius = Vector[radius[0], radius[1], 0]
|
76
|
+
force.cross(radius)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.force_vector(torque, radius)
|
80
|
+
if !torque.is_a?(Vector) or torque.size != 3
|
81
|
+
raise(ArgumentError, "torque must be a 3D vector")
|
82
|
+
end
|
83
|
+
if !radius.is_a?(Vector) or radius.size != 2
|
84
|
+
raise(ArgumentError, "radius must be a 2D vector")
|
85
|
+
end
|
86
|
+
radius = Vector[radius[0], radius[1], 0]
|
87
|
+
radius.cross(torque) / radius.dot(radius)
|
88
|
+
end
|
89
|
+
|
67
90
|
module VectorForce
|
68
91
|
#
|
69
92
|
# Resistance Forces
|
data/lib/driving_physics.rb
CHANGED
@@ -1,37 +1,36 @@
|
|
1
|
+
#
|
2
|
+
# Units: metric
|
3
|
+
#
|
4
|
+
# distance: meter
|
5
|
+
# velocity: meter / sec
|
6
|
+
# acceleration: meter / sec^2
|
7
|
+
# volume: Liter
|
8
|
+
# temperature: Celsius
|
9
|
+
#
|
1
10
|
module DrivingPhysics
|
2
|
-
#
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# acceleration: meter / sec^2
|
8
|
-
# volume: Liter
|
9
|
-
# temperature: Celsius
|
10
|
-
#
|
11
|
+
# runtime check; this returns false by default
|
12
|
+
# Vector is not currently/easily available in mruby
|
13
|
+
def self.has_vector?
|
14
|
+
Vector rescue false
|
15
|
+
end
|
11
16
|
|
12
|
-
#
|
13
17
|
# environmental defaults
|
14
|
-
#
|
15
18
|
HZ = 1000
|
16
19
|
TICK = Rational(1) / HZ
|
17
20
|
G = 9.8 # m/s^2
|
18
21
|
AIR_TEMP = 25 # deg c
|
19
22
|
AIR_DENSITY = 1.29 # kg / m^3
|
20
|
-
PETROL_DENSITY = 0.71 # kg / L
|
23
|
+
PETROL_DENSITY = 0.71 # kg / L
|
21
24
|
|
22
|
-
#
|
23
25
|
# defaults for resistance forces
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
ROLL_COF = 0.01 # roughly: street tires on concrete
|
26
|
+
FRONTAL_AREA = 2.2 # m^2, based roughly on 2000s-era Chevrolet Corvette
|
27
|
+
DRAG_COF = 0.3 # based roughly on 2000s-era Chevrolet Corvette
|
28
|
+
DRAG = 0.4257 # air_resistance at 1 m/s given above numbers
|
29
|
+
ROT_COF = 12.771 # if rot matches air at 30 m/s
|
30
|
+
ROT_CONST = 0.05 # N opposing drive force / torque
|
31
|
+
ROLL_COF = 0.01 # roughly: street tires on concrete
|
31
32
|
|
32
|
-
#
|
33
33
|
# constants
|
34
|
-
#
|
35
34
|
SECS_PER_MIN = 60
|
36
35
|
MINS_PER_HOUR = 60
|
37
36
|
SECS_PER_HOUR = SECS_PER_MIN * MINS_PER_HOUR
|
@@ -70,4 +69,11 @@ module DrivingPhysics
|
|
70
69
|
alias_method(:omega, :accum)
|
71
70
|
alias_method(:theta, :accum)
|
72
71
|
end
|
72
|
+
|
73
|
+
def self.unit_interval!(val)
|
74
|
+
if val < 0.0 or val > 1.0
|
75
|
+
raise(ArgumentError, "val #{val.inspect} should be between 0 and 1")
|
76
|
+
end
|
77
|
+
val
|
78
|
+
end
|
73
79
|
end
|