driving_physics 0.0.1.2 → 0.0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef486c39982798a7331f49f601e648d520a65977161180631cc4c5d3340a9011
4
- data.tar.gz: 9e30b74ae34cb77a711d5d9631983b0c070872900123a1dde2d3dd03bdab854b
3
+ metadata.gz: 9b9159b499a0082d5af3ef926fceab925572552978b50d020b71bc51df7506fb
4
+ data.tar.gz: 183eaae3e07e24f38bb1417d96a6310eebfd288a1300d4e8bab96f008abbf3e5
5
5
  SHA512:
6
- metadata.gz: 46eaf512dad64ca39ec20451d7acce1b1819ecd64125ee858f92c26e5edd7b0ec0e7ef01af8f73daa0d37868e0ffd76bd3b29478093219cd906d8d9e38026bcd
7
- data.tar.gz: ae2e01ae8e7986c9d2e659ff5a0934ed5359e20cf2757d7724118bb59e0fb0199180249815f2f28f55f48a2336511a0b43f0d6fab042ed5d20a5ab6a897baef1
6
+ metadata.gz: 9a19ba4646abe21f30ca12c88f43e9ddc7083028e96504260f0277b167af5374364967b1617ab83744f824d6d2aa513ef38627d23ad5e9ccc8ff7e017b8a2ac8
7
+ data.tar.gz: 5357970cd547b12727bfc20c408df27a07d3aaac5d5b9042cbb10fc98c85484eb7bf054e140158f7e8695ef6c30eb18961fdd6979b4d0e3ab6c343f7767fc296
data/README.md CHANGED
@@ -10,14 +10,36 @@ using `Vector` class from `matrix.rb` in stdlib.
10
10
 
11
11
  This is very much a **Work In Progress**. Implemented so far:
12
12
 
13
+ ### Library Features
14
+
13
15
  * Spinning: rotational inertia and friction / hysteresis
14
- * Tires: traction force via friction and the normal force
16
+ * Tires: traction force via friction (static and kinetic) and the normal force
15
17
  * Vectors: 2D Vector forces and 3D Vector torques
16
18
  * Motor: torque curves, rotating mass with friction
17
19
  * Gearbox: gear ratios, final drive, rotating mass with friction
18
20
  * Powertrain: combines motor and gearbox
19
21
  * Car: 4 tires, powertrain, extra mass presents a load to the powertrain
20
22
  * Acceleration: via drive traction from "first principles"
23
+ * Variable clutch and throttle
24
+ * Engine braking
25
+ * Power and Energy
26
+
27
+ ### mruby Support
28
+
29
+ This library is primarily a Ruby library, intended for use with CRuby (MRI),
30
+ JRuby, etc. **mruby** is the redheaded stepchild, with significant
31
+ implications for a codebase that wants to support both sides. Supporting
32
+ mruby here meant:
33
+
34
+ 1. Editing the codebase (minimally) to avoid and remove *mruby-deadends*.
35
+ 2. Creating `rake` tasks to process normal .rb files into *mruby-compatible*
36
+ .rb files
37
+ 3. Assembling all needed library .rb files into a master .rb file
38
+ 4. Compiling the library to bytecode
39
+ 5. Executing a toplevel/main script against the library
40
+ 6. Optionally executing toplevel/main bytecode against the library
41
+
42
+ See the main Rakefile near the top for mruby stuff.
21
43
 
22
44
  ## Rationale
23
45
 
data/Rakefile CHANGED
@@ -16,6 +16,90 @@ end
16
16
 
17
17
  task default: :test
18
18
 
19
+ #
20
+ # mruby
21
+ #
22
+
23
+ MRBLIB_DIR = File.join %w[mruby mrblib]
24
+ MRBLIB_FILE = File.join(MRBLIB_DIR, 'driving_physics.rb')
25
+ MRBLIB_MRB = File.join(MRBLIB_DIR, 'driving_physics.mrb')
26
+ MRUBY_DEMO_DIR = File.join %w[demo mruby]
27
+
28
+ def write_mruby(input_file, output_file = MRBLIB_FILE, append: false)
29
+ file_obj = File.open(output_file, append ? 'a' : 'w')
30
+ line_count = 0
31
+
32
+ File.readlines(input_file).each { |line|
33
+ next if line.match /\A *(?:require|autoload)/
34
+ file_obj.write(line)
35
+ line_count += 1
36
+ }
37
+ file_obj.close
38
+ line_count
39
+ end
40
+
41
+ file MRBLIB_FILE do
42
+ line_count = write_mruby(File.join(%w[lib driving_physics.rb]), MRBLIB_FILE)
43
+ %w[cli environment imperial power timer
44
+ disk tire motor gearbox powertrain car pid_controller].each { |name|
45
+ file = File.join('lib', 'driving_physics', "#{name}.rb")
46
+ line_count += write_mruby(file, MRBLIB_FILE, append: true)
47
+ puts "wrote #{file} to #{MRBLIB_FILE}"
48
+ }
49
+ puts "wrote #{line_count} lines to #{MRBLIB_FILE}"
50
+ end
51
+
52
+ file MRBLIB_MRB => MRBLIB_FILE do
53
+ rb_file = File.join(%w[mruby mrblib driving_physics.rb])
54
+ mrb_file = File.join(%w[mruby mrblib driving_physics.mrb])
55
+ sh 'mrbc', rb_file
56
+ puts format("%s: %d bytes (created %s)",
57
+ mrb_file, File.size(mrb_file), File.birthtime(mrb_file))
58
+ end
59
+
60
+ file MRUBY_DEMO_DIR do
61
+ mkdir_p MRUBY_DEMO_DIR
62
+ end
63
+
64
+ desc "Map/Filter/Reduce lib/**/*.rb to mruby/mrblib/driving_physics.rb"
65
+ task mrblib: MRBLIB_FILE
66
+
67
+ desc "Compile mruby/mrblib/driving_physics.rb to .mrb bytecode"
68
+ task mrbc: MRBLIB_MRB
69
+
70
+ %w[disk tire motor gearbox powertrain car pid_controller].each { |name|
71
+ demo_file = File.join(MRUBY_DEMO_DIR, "#{name}.rb")
72
+ demo_mrb = File.join(MRUBY_DEMO_DIR, "#{name}.mrb")
73
+
74
+ file demo_file => MRUBY_DEMO_DIR do
75
+ write_mruby(File.join('demo', "#{name}.rb"), demo_file)
76
+ end
77
+
78
+ file demo_mrb => demo_file do
79
+ sh 'mrbc', demo_file
80
+ end
81
+
82
+ desc "run demo/#{name}.rb via mruby"
83
+ task "demo_#{name}" => [demo_file, MRBLIB_MRB] do
84
+ sh 'mruby', '-r', MRBLIB_MRB, demo_file
85
+ end
86
+
87
+ desc "run demo/#{name}.rb via mruby bytecode"
88
+ task "mrb_#{name}" => [demo_mrb, MRBLIB_MRB] do
89
+ sh 'mruby', '-r', MRBLIB_MRB, '-b', demo_mrb
90
+ end
91
+ }
92
+
93
+ desc "Remove generated .rb and .mrb files"
94
+ task :clean do
95
+ [MRBLIB_DIR, MRUBY_DEMO_DIR].each { |dir|
96
+ Dir[File.join(dir, '*rb')].each { |file|
97
+ rm file unless File.directory?(file)
98
+ }
99
+ }
100
+ end
101
+
102
+
19
103
  #
20
104
  # METRICS
21
105
  #
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1.2
1
+ 0.0.3.1
data/demo/car.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'driving_physics/car'
2
+ require 'driving_physics/cockpit'
2
3
  require 'driving_physics/imperial'
3
4
  require 'driving_physics/cli'
4
5
 
@@ -10,13 +11,16 @@ puts env
10
11
  tire = Tire.new(env)
11
12
  motor = Motor.new(env)
12
13
  gearbox = Gearbox.new(env)
13
- powertrain = Powertrain.new(motor, gearbox)
14
+ powertrain = Powertrain.new(motor: motor, gearbox: gearbox)
14
15
  car = Car.new(tire: tire, powertrain: powertrain) { |c|
15
16
  c.body_mass = 850.0
16
17
  c.frontal_area = 2.5
17
18
  c.cd = 0.5
18
19
  }
19
20
  puts car
21
+
22
+ cockpit = Cockpit.new(car)
23
+ puts cockpit
20
24
  CLI.pause
21
25
 
22
26
  duration = 120
@@ -31,16 +35,20 @@ tire_theta = 0.0
31
35
 
32
36
  crank_alpha = 0.0
33
37
  crank_omega = 0.0
34
- crank_theta = 0.0
35
38
 
36
39
  start = Timer.now
37
40
  paused = 0.0
38
41
  num_ticks = duration * env.hz + 1
39
42
 
40
- clutch = :ok
43
+ rev_match = :ok
41
44
  phase = :ignition
45
+ gearbox.clutch = 0.0 # clutch in to fire up motor
42
46
  flag = false
43
- rpm = 0
47
+
48
+ rpm, crank_torque = 0, 0
49
+ axle_torque, drive_force, net_force = 0, 0, 0
50
+ ar, rr, rf, ir = 0, 0, 0, 0
51
+
44
52
  puts <<EOF
45
53
 
46
54
  #
@@ -51,24 +59,15 @@ EOF
51
59
 
52
60
  num_ticks.times { |i|
53
61
  if phase == :ignition
54
- # ignition phase
55
62
  crank_alpha = motor.alpha(motor.starter_torque, omega: crank_omega)
56
63
  crank_omega += crank_alpha * env.tick
57
- crank_theta += crank_omega * env.tick
58
64
 
59
65
  rpm = DrivingPhysics.rpm(crank_omega)
60
66
 
61
- if i % 100 == 0 or rpm > motor.idle_rpm
62
- puts Timer.display(i)
63
- puts format("%d rad %d rad/s %d rad/s/s",
64
- crank_theta, crank_omega, crank_alpha)
65
- puts format("%d RPM %d Nm starter torque", rpm, motor.starter_torque)
66
- puts
67
- end
68
-
69
- if rpm > motor.idle_rpm
70
- car.gear = 1
71
- car.throttle = 1.0
67
+ if rpm > motor.idle
68
+ flag = true
69
+ cockpit.gear = 1
70
+ cockpit.throttle_pedal = 1.0
72
71
  phase = :running
73
72
 
74
73
  puts <<EOF
@@ -79,23 +78,19 @@ num_ticks.times { |i|
79
78
 
80
79
  EOF
81
80
  end
82
- elsif phase == :running
83
- # track crank_alpha/omega/theta
84
-
85
- # cut throttle after 60 s
86
- car.throttle = 0 if i > 60 * env.hz and car.throttle == 1.0
87
-
81
+ elsif phase == :running or phase == :off_throttle
88
82
  ar = car.air_force(speed)
89
83
  rr = car.tire_rolling_force(tire_omega)
90
84
  rf = car.tire_rotational_force(tire_omega)
91
85
 
92
86
  # note, this fails if we're in neutral
93
- force = car.drive_force(rpm) + ar + rr + rf
87
+ drive_force = car.drive_force(rpm, axle_omega: tire_omega)
88
+ net_force = drive_force + ar + rr + rf
94
89
 
95
- ir = car.tire_inertial_force(force)
96
- force += ir
90
+ ir = car.tire_inertial_force(net_force)
91
+ net_force += ir
97
92
 
98
- acc = DrivingPhysics.acc(force, car.total_mass)
93
+ acc = DrivingPhysics.acc(net_force, car.total_mass)
99
94
  speed += acc * env.tick
100
95
  dist += speed * env.tick
101
96
 
@@ -105,71 +100,89 @@ EOF
105
100
 
106
101
  crank_alpha = tire_alpha / car.powertrain.gearbox.ratio
107
102
  crank_omega += crank_alpha * env.tick
108
- crank_theta += crank_omega * env.tick
109
-
110
- axle_torque = car.powertrain.axle_torque(rpm)
111
- crank_torque = car.powertrain.motor.torque(rpm)
112
-
113
- if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
114
- puts Timer.display(i)
115
- puts format(" Tire: %.1f r/s/s %.2f r/s %.3f r",
116
- tire_alpha, tire_omega, tire_theta)
117
- puts format(" Car: %.1f m/s/s %.2f m/s %.3f m (%.1f MPH)",
118
- acc, speed, dist, Imperial.mph(speed))
119
- puts format(" Motor: %d RPM %.1f Nm", rpm, crank_torque)
120
- puts format(" Axle: %.1f Nm (%d N) Net Force: %.1f N",
121
- axle_torque, car.drive_force(rpm), force)
122
- puts "Resist: " + format(%w[Air Roll Spin Inertial].map { |s|
123
- "#{s}: %.1f N"
124
- }.join(' '), ar, rr, rf, ir)
125
- puts
126
- flag = false
127
- end
128
103
 
129
104
  # tire_omega determines new rpm
130
- new_rpm = car.powertrain.crank_rpm(tire_omega)
131
- new_clutch, proportion = car.powertrain.gearbox.match_rpms(rpm, new_rpm)
105
+ new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
106
+ new_rev_match, clutch, proportion = Cockpit.rev_match(rpm, new_rpm)
132
107
 
133
- if new_clutch != clutch
108
+ if new_rev_match != rev_match
134
109
  flag = true
135
- puts format("Clutch: [%s] %d RPM is %.1f%% from %d RPM",
136
- new_clutch, new_rpm, proportion * 100, rpm)
137
- clutch = new_clutch
138
- paused += CLI.pause
110
+ puts format("Rev Match: [%s] %d RPM is %.1f%% from %d RPM",
111
+ new_rev_match, new_rpm, proportion * 100, rpm)
112
+ # puts format("Recommend clutch to %.1f%%", clutch * 100)
113
+ rev_match = new_rev_match
139
114
  end
140
115
 
141
- case new_clutch
142
- when :ok
143
- rpm = new_rpm
144
- when :mismatch
116
+ clutch_diff = clutch - gearbox.clutch
117
+ if clutch_diff.abs > 0.1
145
118
  flag = true
146
- puts '#'
147
- puts '# LURCH!'
148
- puts '#'
149
- puts
150
- rpm = new_rpm
119
+ puts format("Clutch: %.1f%% Recommended Clutch: %.1f%%",
120
+ gearbox.clutch * 100, clutch * 100)
151
121
  end
152
- next_gear = car.powertrain.gearbox.next_gear(rpm)
153
- if next_gear != gearbox.gear
122
+ # the clutch pedal will reflect this change
123
+ gearbox.clutch += clutch_diff * 0.5
124
+
125
+ # update the motor RPM based on new clutch
126
+ new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
127
+ rpm = new_rpm if new_rpm > motor.idle
128
+
129
+ new_gear = cockpit.choose_gear(rpm)
130
+ if new_gear != cockpit.gear
131
+ flag = true
132
+ puts "Gear Change: #{new_gear}"
133
+ cockpit.gear = new_gear
134
+ end
135
+
136
+ # cut throttle after 60 s
137
+ if i > 60 * env.hz and car.throttle == 1.0
154
138
  flag = true
155
- puts "Gear Change: #{next_gear}"
156
- car.gear = next_gear
157
- paused += CLI.pause
139
+ phase = :off_throttle
140
+ cockpit.throttle_pedal = 0
158
141
  end
159
142
 
160
143
  # maintain idle when revs drop
161
- if car.throttle == 0 and rpm < motor.idle_rpm
144
+ if cockpit.throttle_pedal == 0 and rpm < motor.idle
162
145
  phase = :idling
163
146
  car.gear = 0
164
- paused += CLI.pause
165
147
  end
166
148
 
149
+ print "===\n\n" if flag
167
150
 
168
151
  elsif phase == :idling
169
- # fake
170
- rpm = motor.idle_rpm
152
+ # fake; exit
153
+ rpm = motor.idle
171
154
  break
172
155
  end
156
+
157
+ if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
158
+ puts Timer.display(ms: i)
159
+ puts format(" Phase: %s", phase)
160
+ puts format(" Tire: %.3f r/s/s %.2f r/s %.1f r",
161
+ tire_alpha, tire_omega, tire_theta)
162
+ puts format(" Car: %.3f m/s/s %.2f m/s %.1f m (%.1f MPH)",
163
+ acc, speed, dist, Imperial.mph(speed))
164
+ if phase == :ignition
165
+ puts format(" Motor: %d RPM Starter: %d Nm Friction: %.1f Nm",
166
+ rpm, motor.starter_torque, motor.friction(crank_omega))
167
+ else
168
+ crank_torque = car.powertrain.motor.torque(rpm)
169
+ puts format(" Motor: %d RPM %.1f Nm Friction: %.1f Nm",
170
+ rpm, crank_torque, motor.friction(crank_omega))
171
+ end
172
+ puts format("Gearbox: %s", gearbox.inputs)
173
+ if phase != :ignition
174
+ axle_torque = car.powertrain.axle_torque(rpm, axle_omega: tire_omega)
175
+ puts format(" Axle: %.1f Nm (%d N) Net Force: %.1f N",
176
+ axle_torque, drive_force, net_force)
177
+ end
178
+ puts " Resist: " + format(%w[Air Roll Spin Inertial].map { |s|
179
+ "#{s}: %.1f N"
180
+ }.join(' '), ar, rr, rf, ir)
181
+ puts cockpit
182
+ puts
183
+ paused += CLI.pause if flag
184
+ flag = false
185
+ end
173
186
  }
174
187
 
175
188
  puts Timer.summary(start, num_ticks, paused)
data/demo/gearbox.rb CHANGED
@@ -21,10 +21,11 @@ alpha = 0.0
21
21
  omega = 0.0
22
22
  theta = 0.0
23
23
 
24
+ num_ticks = duration * env.hz + 1
24
25
  start = Timer.now
25
- paused = 0
26
+ paused = 0.0
26
27
 
27
- (duration * env.hz + 1).times { |i|
28
+ num_ticks.times { |i|
28
29
  # just for info, not used in the simulation
29
30
  friction = gearbox.spinner.rotating_friction(omega)
30
31
 
@@ -41,7 +42,7 @@ paused = 0
41
42
  (i < 100 and i % 10 == 0) or
42
43
  (i < 1000 and i % 100 == 0) or
43
44
  i % 1000 == 0
44
- puts Timer.display(i)
45
+ puts Timer.display(ms: i)
45
46
  puts format("RPM %d Torque: %.3f Nm (%d Nm) Friction: %.3f Nm",
46
47
  DrivingPhysics.rpm(omega), net_torque, torque, friction)
47
48
  puts format("%.1f rad %.1f rad/s %.1f rad/s/s", theta, omega, alpha)
data/demo/motor.rb CHANGED
@@ -48,7 +48,7 @@ motor.throttle = 1.0
48
48
  puts
49
49
  puts "Now, the simulation begins..."
50
50
  puts "---"
51
- puts "* Spin the motor up to #{motor.idle_rpm} RPM with the starter motor."
51
+ puts "* Spin the motor up to #{motor.idle} RPM with the starter motor."
52
52
  puts "* Rev it up with the throttle."
53
53
  puts "* Let it die."
54
54
  CLI.pause
@@ -82,15 +82,14 @@ num_ticks.times { |i|
82
82
  omega += alpha * env.tick
83
83
  theta += omega * env.tick
84
84
 
85
- net_torque = motor.implied_torque(alpha)
85
+ # prevent silly oscillations due to ticks or tiny floating point errors
86
+ omega = 0 if omega < 0.0001
86
87
 
87
- # prevent silly oscillations due to tiny floating point errors
88
- omega = 0 if omega < 0.00001
88
+ net_torque = motor.implied_torque(alpha)
89
89
  rpm = DrivingPhysics.rpm(omega)
90
-
91
90
  power = DrivingPhysics.power(net_torque, omega)
92
91
 
93
- if rpm > motor.idle_rpm and status == :ignition
92
+ if rpm > motor.idle and status == :ignition
94
93
  status = :running
95
94
  flag = true
96
95
  end
@@ -107,10 +106,13 @@ num_ticks.times { |i|
107
106
  flag = true
108
107
  end
109
108
 
109
+ # we don't need no PID control
110
110
  if status == :idling
111
- if rpm < 999
111
+ if rpm.round <= 999
112
112
  motor.throttle += (1.0 - motor.throttle) * 0.005
113
- elsif rpm > 1001
113
+ elsif rpm.round == 1000
114
+ motor.throttle += (rand - 0.5) * 0.0005
115
+ else
114
116
  motor.throttle -= motor.throttle * 0.005
115
117
  end
116
118
  end
@@ -122,7 +124,7 @@ num_ticks.times { |i|
122
124
  (i < 10_000 and i % 500 == 0) or
123
125
  (i % 5000 == 0) or
124
126
  (status == :idling and i % 100 == 0)
125
- puts Timer.display(i)
127
+ puts Timer.display(ms: i)
126
128
  puts format("Throttle: %.1f%%", motor.throttle * 100)
127
129
  puts format("%d RPM %.1f Nm (%d Nm) %.1f kW Friction: %.1f Nm",
128
130
  DrivingPhysics.rpm(omega),
@@ -130,7 +132,7 @@ num_ticks.times { |i|
130
132
  torque,
131
133
  power / 1000,
132
134
  motor.spinner.rotating_friction(omega))
133
- puts format("%d rad %.1f rad/s %.1f rad/s/s", theta, omega, alpha)
135
+ puts format("%.3f rad/s/s %.2f rad/s %.1f rad", alpha, omega, theta)
134
136
  puts
135
137
 
136
138
  paused += CLI.pause if flag
data/demo/mruby/car.rb ADDED
@@ -0,0 +1,184 @@
1
+
2
+ include DrivingPhysics
3
+
4
+ env = Environment.new
5
+ puts env
6
+
7
+ tire = Tire.new(env)
8
+ motor = Motor.new(env)
9
+ gearbox = Gearbox.new(env)
10
+ powertrain = Powertrain.new(motor: motor, gearbox: gearbox)
11
+ car = Car.new(tire: tire, powertrain: powertrain) { |c|
12
+ c.body_mass = 850.0
13
+ c.frontal_area = 2.5
14
+ c.cd = 0.5
15
+ }
16
+ puts car
17
+
18
+ cockpit = Cockpit.new(car)
19
+ puts cockpit
20
+ CLI.pause
21
+
22
+ duration = 120
23
+
24
+ acc = 0.0
25
+ speed = 0.0
26
+ dist = 0.0
27
+
28
+ tire_alpha = 0.0
29
+ tire_omega = 0.0
30
+ tire_theta = 0.0
31
+
32
+ crank_alpha = 0.0
33
+ crank_omega = 0.0
34
+
35
+ start = Timer.now
36
+ paused = 0.0
37
+ num_ticks = duration * env.hz + 1
38
+
39
+ rev_match = :ok
40
+ phase = :ignition
41
+ gearbox.clutch = 0.0 # clutch in to fire up motor
42
+ flag = false
43
+
44
+ rpm, crank_torque = 0, 0
45
+ axle_torque, drive_force, net_force = 0, 0, 0
46
+ ar, rr, rf, ir = 0, 0, 0, 0
47
+
48
+ puts <<EOF
49
+
50
+ #
51
+ # IGNITION
52
+ #
53
+
54
+ EOF
55
+
56
+ num_ticks.times { |i|
57
+ if phase == :ignition
58
+ crank_alpha = motor.alpha(motor.starter_torque, omega: crank_omega)
59
+ crank_omega += crank_alpha * env.tick
60
+
61
+ rpm = DrivingPhysics.rpm(crank_omega)
62
+
63
+ if rpm > motor.idle
64
+ flag = true
65
+ cockpit.gear = 1
66
+ cockpit.throttle_pedal = 1.0
67
+ phase = :running
68
+
69
+ puts <<EOF
70
+
71
+ #
72
+ # RUNNING
73
+ #
74
+
75
+ EOF
76
+ end
77
+ elsif phase == :running or phase == :off_throttle
78
+ ar = car.air_force(speed)
79
+ rr = car.tire_rolling_force(tire_omega)
80
+ rf = car.tire_rotational_force(tire_omega)
81
+
82
+ # note, this fails if we're in neutral
83
+ drive_force = car.drive_force(rpm, axle_omega: tire_omega)
84
+ net_force = drive_force + ar + rr + rf
85
+
86
+ ir = car.tire_inertial_force(net_force)
87
+ net_force += ir
88
+
89
+ acc = DrivingPhysics.acc(net_force, car.total_mass)
90
+ speed += acc * env.tick
91
+ dist += speed * env.tick
92
+
93
+ tire_alpha = acc / car.tire.radius
94
+ tire_omega += tire_alpha * env.tick
95
+ tire_theta += tire_omega * env.tick
96
+
97
+ crank_alpha = tire_alpha / car.powertrain.gearbox.ratio
98
+ crank_omega += crank_alpha * env.tick
99
+
100
+ # tire_omega determines new rpm
101
+ new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
102
+ new_rev_match, clutch, proportion = Cockpit.rev_match(rpm, new_rpm)
103
+
104
+ if new_rev_match != rev_match
105
+ flag = true
106
+ puts format("Rev Match: [%s] %d RPM is %.1f%% from %d RPM",
107
+ new_rev_match, new_rpm, proportion * 100, rpm)
108
+ # puts format("Recommend clutch to %.1f%%", clutch * 100)
109
+ rev_match = new_rev_match
110
+ end
111
+
112
+ clutch_diff = clutch - gearbox.clutch
113
+ if clutch_diff.abs > 0.1
114
+ flag = true
115
+ puts format("Clutch: %.1f%% Recommended Clutch: %.1f%%",
116
+ gearbox.clutch * 100, clutch * 100)
117
+ end
118
+ # the clutch pedal will reflect this change
119
+ gearbox.clutch += clutch_diff * 0.5
120
+
121
+ # update the motor RPM based on new clutch
122
+ new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
123
+ rpm = new_rpm if new_rpm > motor.idle
124
+
125
+ new_gear = cockpit.choose_gear(rpm)
126
+ if new_gear != cockpit.gear
127
+ flag = true
128
+ puts "Gear Change: #{new_gear}"
129
+ cockpit.gear = new_gear
130
+ end
131
+
132
+ # cut throttle after 60 s
133
+ if i > 60 * env.hz and car.throttle == 1.0
134
+ flag = true
135
+ phase = :off_throttle
136
+ cockpit.throttle_pedal = 0
137
+ end
138
+
139
+ # maintain idle when revs drop
140
+ if cockpit.throttle_pedal == 0 and rpm < motor.idle
141
+ phase = :idling
142
+ car.gear = 0
143
+ end
144
+
145
+ print "===\n\n" if flag
146
+
147
+ elsif phase == :idling
148
+ # fake; exit
149
+ rpm = motor.idle
150
+ break
151
+ end
152
+
153
+ if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
154
+ puts Timer.display(ms: i)
155
+ puts format(" Phase: %s", phase)
156
+ puts format(" Tire: %.3f r/s/s %.2f r/s %.1f r",
157
+ tire_alpha, tire_omega, tire_theta)
158
+ puts format(" Car: %.3f m/s/s %.2f m/s %.1f m (%.1f MPH)",
159
+ acc, speed, dist, Imperial.mph(speed))
160
+ if phase == :ignition
161
+ puts format(" Motor: %d RPM Starter: %d Nm Friction: %.1f Nm",
162
+ rpm, motor.starter_torque, motor.friction(crank_omega))
163
+ else
164
+ crank_torque = car.powertrain.motor.torque(rpm)
165
+ puts format(" Motor: %d RPM %.1f Nm Friction: %.1f Nm",
166
+ rpm, crank_torque, motor.friction(crank_omega))
167
+ end
168
+ puts format("Gearbox: %s", gearbox.inputs)
169
+ if phase != :ignition
170
+ axle_torque = car.powertrain.axle_torque(rpm, axle_omega: tire_omega)
171
+ puts format(" Axle: %.1f Nm (%d N) Net Force: %.1f N",
172
+ axle_torque, drive_force, net_force)
173
+ end
174
+ puts " Resist: " + format(%w[Air Roll Spin Inertial].map { |s|
175
+ "#{s}: %.1f N"
176
+ }.join(' '), ar, rr, rf, ir)
177
+ puts cockpit
178
+ puts
179
+ paused += CLI.pause if flag
180
+ flag = false
181
+ end
182
+ }
183
+
184
+ puts Timer.summary(start, num_ticks, paused)