driving_physics 0.0.0.3 → 0.0.1.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: ec6ff08e7731aa8bcb419efee9ab1a96ef7642f3426c7b8a501dae764692d777
4
- data.tar.gz: ae217c0e452a764392e5a4df9a0b3280369e21b92d6018ef8d8753c0a6f5f594
3
+ metadata.gz: 102b8d70a0aa540ee6e56ca24987497c0f272473790180a9f8691418fd1557f6
4
+ data.tar.gz: d82c3924641569610a538804e9b7cd9df040f8487ff50158e0b227cbf529f3dc
5
5
  SHA512:
6
- metadata.gz: b8844bf3f8c45aae742bc27cbfe8307b7f61f21a600d1eac7f34248aa9a980f9942f6e4ce51ec2b58834978e4ba1a06aa0153ba4323d4e3448b1f038c39495e2
7
- data.tar.gz: b41f09936f696ef4afe82f3be701409abddd6b92add4f39d5d1d42a1a412ce4204b08a411818c339bba6820858a0aae3d5324cdbabff76e2b2256e9228ae1120
6
+ metadata.gz: '08e0488d1a152f1ca7c432fa60c3bbb06f63ef5674c12ec898de7505e8c054a0eac75280b0b2c78985b1cacf6f7fd2925130a0c48b0564c15cbaca5036e68e30'
7
+ data.tar.gz: 1df9c64a0d59f48a4d9a13062166516456d685e18cf9f7bc684c0853bd9f71587460d98f14f4fc9c1f0e3a571ee325e09fe9538ab249ca4403071280b8a0eddb
data/README.md CHANGED
@@ -6,7 +6,18 @@ Physical simulation of how to make a car go around a track quickly, in pure
6
6
  Ruby with minimal dependencies. Physics from first principles, often
7
7
  using `Vector` class from `matrix.rb` in stdlib.
8
8
 
9
- Note, this is very much a **Work In Progress**.
9
+ ## Work In Progress
10
+
11
+ This is very much a **Work In Progress**. Implemented so far:
12
+
13
+ * Spinning: rotational inertia and friction / hysteresis
14
+ * Tires: traction force via friction and the normal force
15
+ * Vectors: 2D Vector forces and 3D Vector torques
16
+ * Motor: torque curves, rotating mass with friction
17
+ * Gearbox: gear ratios, final drive, rotating mass with friction
18
+ * Powertrain: combines motor and gearbox
19
+ * Car: 4 tires, powertrain, extra mass presents a load to the powertrain
20
+ * Acceleration: via drive traction from "first principles"
10
21
 
11
22
  ## Rationale
12
23
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0.3
1
+ 0.0.1.1
data/demo/car.rb ADDED
@@ -0,0 +1,153 @@
1
+ require 'driving_physics/car'
2
+ require 'driving_physics/imperial'
3
+ require 'driving_physics/cli'
4
+
5
+ include DrivingPhysics
6
+
7
+ env = Environment.new
8
+ puts env
9
+
10
+ tire = Tire.new(env)
11
+ motor = Motor.new(env)
12
+ gearbox = Gearbox.new(env)
13
+ pt = Powertrain.new(motor, gearbox)
14
+ car = Car.new(tire: tire, powertrain: pt) { |c|
15
+ c.mass = 1050.0
16
+ c.frontal_area = 2.5
17
+ c.cd = 0.5
18
+ }
19
+ puts car
20
+ CLI.pause
21
+
22
+ duration = 120
23
+
24
+ speed = 0.0
25
+ dist = 0.0
26
+
27
+ tire_omega = 0.0
28
+ tire_theta = 0.0
29
+
30
+ crank_omega = 0.0
31
+ crank_theta = 0.0
32
+
33
+ t = Time.now
34
+ num_ticks = duration * env.hz + 1
35
+
36
+ clutch = :ok
37
+ phase = :ignition
38
+ flag = false
39
+ rpm = 0
40
+ puts <<EOF
41
+
42
+ #
43
+ # IGNITION
44
+ #
45
+
46
+ EOF
47
+
48
+ num_ticks.times { |i|
49
+ if phase == :ignition
50
+ # ignition phase
51
+ crank_alpha = motor.alpha(motor.starter_torque, omega: crank_omega)
52
+ crank_omega += crank_alpha * env.tick
53
+ crank_theta += crank_omega * env.tick
54
+
55
+ rpm = DrivingPhysics.rpm(crank_omega)
56
+
57
+ if i % 100 == 0 or rpm > motor.idle_rpm
58
+ puts DrivingPhysics.elapsed_display(i)
59
+ puts format("%d rad %d rad/s %d rad/s/s",
60
+ crank_theta, crank_omega, crank_alpha)
61
+ puts "RPM: #{rpm.round}"
62
+ puts
63
+ end
64
+
65
+ if rpm > motor.idle_rpm
66
+ pt.select_gear(1)
67
+ phase = :running
68
+
69
+ puts <<EOF
70
+
71
+ #
72
+ # RUNNING
73
+ #
74
+
75
+ EOF
76
+ end
77
+ elsif phase == :running
78
+ # track crank_alpha/omega/theta
79
+
80
+ ar = car.air_force(speed)
81
+ rr = car.tire_rolling_force(tire_omega)
82
+ rf = car.tire_rotational_force(tire_omega)
83
+
84
+ force = car.drive_force(rpm) + ar + rr + rf
85
+
86
+
87
+ ir = car.tire_inertial_force(force)
88
+ force += ir
89
+
90
+ acc = DrivingPhysics.acc(force, car.total_mass)
91
+ speed += acc * env.tick
92
+ dist += speed * env.tick
93
+
94
+ tire_alpha = acc / car.tire.radius
95
+ tire_omega += tire_alpha * env.tick
96
+ tire_theta += tire_omega * env.tick
97
+
98
+ crank_alpha = tire_alpha / car.powertrain.gearbox.ratio
99
+ crank_omega += crank_alpha * env.tick
100
+ crank_theta += crank_omega * env.tick
101
+
102
+ axle_torque = car.powertrain.axle_torque(rpm)
103
+ crank_torque = car.powertrain.motor.torque(rpm)
104
+
105
+ if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
106
+ puts DrivingPhysics.elapsed_display(i)
107
+ puts format(" Tire: %.1f r %.2f r/s %.3f r/s^2",
108
+ tire_theta, tire_omega, tire_alpha)
109
+ puts format(" Car: %.1f m %.2f m/s %.3f m/s^2 (%.1f MPH)",
110
+ dist, speed, acc, Imperial.mph(speed))
111
+ puts format(" Motor: %d RPM %.1f Nm", rpm, crank_torque)
112
+ puts format(" Axle: %.1f Nm (%d N) Net Force: %.1f N",
113
+ axle_torque, car.drive_force(rpm), force)
114
+ puts "Resist: " + format(%w[Air Roll Spin Inertial].map { |s|
115
+ "#{s}: %.1f N"
116
+ }.join(' '), ar, rr, rf, ir)
117
+ puts
118
+ flag = false
119
+ end
120
+
121
+ # tire_omega determines new rpm
122
+ new_rpm = car.powertrain.crank_rpm(tire_omega)
123
+ new_clutch, proportion = car.powertrain.gearbox.match_rpms(rpm, new_rpm)
124
+
125
+ if new_clutch != clutch
126
+ flag = true
127
+ puts format("Clutch: [%s] %d RPM is %.1f%% from %d RPM",
128
+ new_clutch, new_rpm, proportion * 100, rpm)
129
+ clutch = new_clutch
130
+ CLI.pause
131
+ end
132
+
133
+ case new_clutch
134
+ when :ok
135
+ rpm = new_rpm
136
+ when :mismatch
137
+ flag = true
138
+ puts "LURCH!"
139
+ puts
140
+ rpm = new_rpm
141
+ end
142
+ next_gear = car.powertrain.gearbox.next_gear(rpm)
143
+ if next_gear != gearbox.gear
144
+ flag = true
145
+ puts "Gear Change: #{next_gear}"
146
+ car.powertrain.select_gear(next_gear)
147
+ CLI.pause
148
+ end
149
+ end
150
+ }
151
+
152
+ elapsed = Time.now - t
153
+ puts format("%.2f s (%d ticks / s)", elapsed, num_ticks / elapsed)
data/demo/disk.rb ADDED
@@ -0,0 +1,76 @@
1
+ require 'driving_physics/disk'
2
+ require 'driving_physics/cli'
3
+
4
+ include DrivingPhysics
5
+
6
+ env = Environment.new
7
+ disk = Disk.new(env)
8
+
9
+ puts env
10
+ puts disk
11
+ puts
12
+
13
+ axle_torque = 50
14
+ alpha = disk.alpha(axle_torque)
15
+ drive_force = disk.force(axle_torque)
16
+
17
+ puts [format("Axle torque: %.1f Nm", axle_torque),
18
+ format(" Alpha: %.1f rad/s/s", alpha),
19
+ format("Drive force: %.1f N", drive_force),
20
+ ].join("\n")
21
+ puts
22
+ CLI.pause
23
+
24
+ duration = 750 # sec
25
+
26
+ dist = 0.0 # meters
27
+ speed = 0.0 # meters/s
28
+
29
+ theta = 0.0 # radians
30
+ omega = 0.0 # radians/s
31
+
32
+ t = Time.now
33
+ elapsed = 0.0
34
+ num_ticks = duration * env.hz
35
+
36
+ num_ticks.times { |i|
37
+ # shut off the powah!
38
+ if i == 19_000
39
+ puts
40
+ puts " ### CUT POWER ###"
41
+ puts
42
+ axle_torque = 0
43
+ elapsed += Time.now - t
44
+ CLI.pause
45
+ t = Time.now
46
+ end
47
+
48
+ rotating_friction = disk.rotating_friction(omega)
49
+ net_torque = axle_torque + rotating_friction
50
+ net_force = disk.force(net_torque)
51
+
52
+ # rotational kinematics
53
+ alpha = disk.alpha(net_torque)
54
+ omega += alpha * env.tick
55
+ omega = 0.0 if omega.abs < 0.0001
56
+ theta += omega * env.tick
57
+
58
+ if i < 10 or
59
+ (i < 20_000 and i%1000 == 0) or
60
+ (i % 10_000 == 0) or
61
+ i == duration * env.hz - 1
62
+
63
+ puts DrivingPhysics.elapsed_display(i)
64
+ puts format(" Torque: %.1f Nm (%d Nm) Friction: %.1f Nm",
65
+ net_torque, axle_torque, rotating_friction)
66
+ puts format("Radians: %.1f r %.2f r/s %.3f r/s^2", theta, omega, alpha)
67
+ puts format(" Revs: %d revs %d revs/s %d rpm",
68
+ DrivingPhysics.revs(theta),
69
+ DrivingPhysics.revs(omega),
70
+ DrivingPhysics.rpm(omega))
71
+ puts
72
+ end
73
+ }
74
+
75
+ elapsed += Time.now - t
76
+ puts format("%.2f s (%d ticks / s)", elapsed, num_ticks / elapsed)
data/demo/gearbox.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'driving_physics/gearbox'
2
+ require 'driving_physics/cli'
3
+
4
+ include DrivingPhysics
5
+
6
+ env = Environment.new
7
+ gearbox = Gearbox.new(env)
8
+
9
+ torque = 30
10
+ duration = 30
11
+
12
+ puts env
13
+ puts gearbox
14
+ puts
15
+ puts "Spin up the gearbox with #{torque} Nm of input torque"
16
+ puts "How fast will it go in #{duration} seconds?"
17
+ CLI.pause
18
+
19
+ # rotational kinematics
20
+ alpha = 0.0
21
+ omega = 0.0
22
+ theta = 0.0
23
+
24
+ (duration * env.hz + 1).times { |i|
25
+ # just for info, not used in the simulation
26
+ friction = gearbox.spinner.rotating_friction(omega)
27
+
28
+ # update rotational kinematics
29
+ # gearbox.alpha incorporates friction and inertia
30
+ alpha = gearbox.alpha(torque, omega: omega)
31
+ omega += alpha * env.tick
32
+ theta += omega * env.tick
33
+
34
+ net_torque = gearbox.implied_torque(alpha)
35
+
36
+ # periodic output
37
+ if i < 10 or
38
+ (i < 100 and i % 10 == 0) or
39
+ (i < 1000 and i % 100 == 0) or
40
+ i % 1000 == 0
41
+ puts DrivingPhysics.elapsed_display(i)
42
+ puts format("RPM %d Torque: %.3f Nm (%d Nm) Friction: %.3f Nm",
43
+ DrivingPhysics.rpm(omega), net_torque, torque, friction)
44
+ puts format("%.1f rad %.1f rad/s %.1f rad/s/s", theta, omega, alpha)
45
+ puts
46
+ end
47
+ }
data/demo/motor.rb ADDED
@@ -0,0 +1,89 @@
1
+ require 'driving_physics/motor'
2
+ require 'driving_physics/cli'
3
+
4
+ # fun idea for a different demo: keep increasing torque until idle is
5
+ # maintained
6
+
7
+ include DrivingPhysics
8
+
9
+ env = Environment.new
10
+ motor = Motor.new(env)
11
+ puts env
12
+ puts motor
13
+
14
+ puts "Rev it up!"
15
+ 800.upto(7000) { |rpm|
16
+ next unless rpm % 200 == 0
17
+ tq = motor.torque(rpm).to_f
18
+ puts format("%s RPM: %s Nm\t%s",
19
+ rpm.to_s.rjust(4, ' '),
20
+ tq.round(1).to_s.rjust(5, ' '),
21
+ '#' * (tq.to_f / 10).round)
22
+ }
23
+
24
+ puts
25
+ puts "* Spin the motor up to #{motor.idle_rpm} RPM with the starter motor."
26
+ puts "* Rev it up with the throttle."
27
+ puts "* Let it die."
28
+ CLI.pause
29
+
30
+ alpha = 0.0
31
+ omega = 0.0
32
+ theta = 0.0
33
+
34
+ duration = 60
35
+
36
+ status = :ignition
37
+ rpm = 0
38
+
39
+ (duration * env.hz + 1).times { |i|
40
+ # this is an input torque; alpha is determined after inertia and friction
41
+ torque = case status
42
+ when :ignition
43
+ motor.starter_torque
44
+ when :running
45
+ motor.torque(rpm)
46
+ else
47
+ 0
48
+ end
49
+
50
+ # Motor#alpha incorporates inertia and friction
51
+ alpha = motor.alpha(torque, omega: omega)
52
+ omega += alpha * env.tick
53
+ theta += omega * env.tick
54
+
55
+ net_torque = motor.implied_torque(alpha)
56
+
57
+ # prevent silly oscillations due to tiny floating point errors
58
+ omega = 0 if omega < 0.00001
59
+ rpm = DrivingPhysics.rpm(omega)
60
+
61
+ if rpm > motor.idle_rpm and status == :ignition
62
+ status = :running
63
+ flag = true
64
+ end
65
+
66
+ if rpm > 7000 and status == :running
67
+ status = :off
68
+ flag = true
69
+ end
70
+
71
+ if flag or
72
+ (i < 10) or
73
+ (i < 100 and i % 10 == 0) or
74
+ (i < 1000 and i % 100 == 0) or
75
+ (i < 10_000 and i % 500 == 0) or
76
+ i % 5000 == 0
77
+ puts DrivingPhysics.elapsed_display(i)
78
+ puts format("%d RPM %.1f Nm (%d Nm) Friction: %.1f Nm",
79
+ DrivingPhysics.rpm(omega),
80
+ net_torque,
81
+ torque,
82
+ motor.spinner.rotating_friction(omega))
83
+ puts format("%d rad %.1f rad/s %.1f rad/s/s", theta, omega, alpha)
84
+ puts
85
+
86
+ CLI.pause if flag
87
+ flag = false
88
+ end
89
+ }
@@ -0,0 +1,40 @@
1
+ require 'driving_physics/powertrain'
2
+ require 'driving_physics/imperial'
3
+ require 'driving_physics/cli'
4
+
5
+ include DrivingPhysics
6
+
7
+ env = Environment.new
8
+ motor = Motor.new(env)
9
+ gearbox = Gearbox.new(env)
10
+ powertrain = Powertrain.new(motor, gearbox)
11
+ puts env
12
+ puts powertrain
13
+ CLI.pause
14
+
15
+ crank_alpha = 0.0
16
+ crank_omega = 0.0
17
+
18
+ axle_alpha = 0.0
19
+ axle_omega = 0.0
20
+
21
+
22
+ # Run through the gears
23
+
24
+ 1.upto(6) { |gear|
25
+ powertrain.select_gear(gear)
26
+
27
+ puts <<EOF
28
+
29
+ # GEAR #{gear} (#{powertrain.gearbox.ratio})
30
+ #
31
+ EOF
32
+
33
+ 800.upto(7000) { |rpm|
34
+ next unless rpm % 500 == 0
35
+
36
+ axle_torque = powertrain.axle_torque(rpm)
37
+ mph = Imperial.mph(Disk.tangential(powertrain.axle_omega(rpm), 0.32))
38
+ puts format("%d RPM: %.1f Nm %d mph", rpm, axle_torque, mph)
39
+ }
40
+ }
data/demo/scalar_force.rb CHANGED
@@ -13,9 +13,9 @@ tick = 1.0 / DP::HZ
13
13
  (duration * DP::HZ).times { |i|
14
14
  nf = drive_force - DP::ScalarForce.all_resistance(spd, nf_mag: weight)
15
15
 
16
- a = DP.a(nf, mass)
17
- spd = DP.v(a, spd, dt: tick)
18
- pos = DP.p(spd, pos, dt: tick)
16
+ a = DP.acc(nf, mass)
17
+ spd = DP.vel(spd, a, dt: tick)
18
+ pos = DP.pos(pos, spd, dt: tick)
19
19
 
20
20
  if i % DP::HZ == 0
21
21
  puts [i / DP::HZ,
data/demo/tire.rb ADDED
@@ -0,0 +1,88 @@
1
+ require 'driving_physics/tire'
2
+
3
+ include DrivingPhysics
4
+
5
+ env = Environment.new
6
+ tire = Tire.new(env)
7
+
8
+ puts env
9
+ puts tire
10
+
11
+ # 1000 kg car
12
+ # 4 tires
13
+ # 250 kg per tire plus tire mass
14
+
15
+ supported_mass = 1000 # kg
16
+ total_mass = supported_mass + 4 * tire.mass
17
+ corner_mass = Rational(total_mass) / 4
18
+ normal_force = corner_mass * env.g
19
+ axle_torque = 1000 # N*m
20
+
21
+ puts [format("Corner mass: %d kg", corner_mass),
22
+ format("Normal force: %.1f N", normal_force),
23
+ format("Axle torque: %d Nm", axle_torque),
24
+ ].join("\n")
25
+ puts
26
+
27
+ traction = tire.traction(normal_force)
28
+ drive_force = tire.force(axle_torque)
29
+ inertial_loss = tire.inertial_loss(axle_torque, driven_mass: supported_mass)
30
+ net_axle_torque = axle_torque - inertial_loss
31
+ net_drive_force = tire.force(net_axle_torque)
32
+ acc = DrivingPhysics.acc(net_drive_force, supported_mass) # translational
33
+ alpha = acc / tire.radius
34
+
35
+ puts [format("Traction: %.1f N", traction),
36
+ format("Drive force: %.1f N", drive_force),
37
+ format("Inertial loss: %.1f Nm", inertial_loss),
38
+ format("Net Axle Torque: %.1f Nm", net_axle_torque),
39
+ format("Net Drive Force: %.1f N", net_drive_force),
40
+ format("Acceleration: %.1f m/s/s", acc),
41
+ format("Alpha: %.2f r/s/s", alpha),
42
+ ].join("\n")
43
+ puts
44
+
45
+ duration = 100 # sec
46
+
47
+ dist = 0.0 # meters
48
+ speed = 0.0 # meters/s
49
+
50
+ theta = 0.0 # radians
51
+ omega = 0.0 # radians/s
52
+
53
+ t = Time.now
54
+ num_ticks = duration * env.hz
55
+
56
+ num_ticks.times { |i|
57
+ torque = tire.net_tractable_torque(axle_torque,
58
+ mass: total_mass,
59
+ omega: omega,
60
+ normal_force: normal_force)
61
+ force = tire.force(torque)
62
+
63
+ # translational kinematics
64
+ acc = DrivingPhysics.acc(force, supported_mass)
65
+ speed += acc * env.tick
66
+ dist += speed * env.tick
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) or
76
+ i == duration * env.hz - 1
77
+
78
+ puts DrivingPhysics.elapsed_display(i)
79
+ puts format(" Tire: %.1f r %.2f r/s %.3f r/s^2", theta, omega, alpha)
80
+ puts format(" Car: %.1f m %.2f m/s %.3f m/s^2", dist, speed, acc)
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
+ elapsed = Time.now - t
88
+ puts format("%.2f s (%d ticks / s)", elapsed, num_ticks / elapsed)
data/demo/vector_force.rb CHANGED
@@ -13,9 +13,9 @@ tick = 1.0 / DP::HZ
13
13
  (duration * DP::HZ).times { |i|
14
14
  nf = drive_force + DP::VectorForce.all_resistance(v, dir: v, nf_mag: weight)
15
15
 
16
- a = DP.a(nf, mass)
17
- v = DP.v(a, v, dt: tick)
18
- p = DP.p(v, p, dt: tick)
16
+ a = DP.acc(nf, mass)
17
+ v = DP.vel(v, a, dt: tick)
18
+ p = DP.pos(p, v, dt: tick)
19
19
 
20
20
  if i % DP::HZ == 0
21
21
  puts [i / DP::HZ,
@@ -0,0 +1,103 @@
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, :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
+ @mass = Rational(1000)
15
+ @frontal_area = DrivingPhysics::FRONTAL_AREA
16
+ @cd = DrivingPhysics::DRAG_COF
17
+
18
+ yield self if block_given?
19
+ end
20
+
21
+ # force opposing speed
22
+ def air_force(speed)
23
+ -0.5 * @frontal_area * @cd * @env.air_density * speed ** 2
24
+ end
25
+
26
+ # force of opposite sign to omega
27
+ def tire_rolling_force(omega)
28
+ @num_tires *
29
+ @tire.rolling_friction(omega, normal_force: self.normal_force) /
30
+ @tire.radius
31
+ end
32
+
33
+ # force of opposite sign to omega
34
+ def tire_rotational_force(omega)
35
+ @num_tires *
36
+ @tire.rotating_friction(omega, normal_force: self.normal_force) /
37
+ @tire.radius
38
+ end
39
+
40
+ # force of opposite sign to force
41
+ def tire_inertial_force(force)
42
+ mag = force.abs
43
+ sign = force / mag
44
+ force_loss = 0
45
+ 5.times {
46
+ # use magnitude, so we are subtracting only positive numbers
47
+ acc = DrivingPhysics.acc(mag - force_loss, self.total_mass)
48
+ alpha = acc / @tire.radius
49
+ # this will be a positive number
50
+ force_loss = @num_tires * @tire.implied_torque(alpha) /
51
+ @tire.radius
52
+ }
53
+ # oppose initial force
54
+ -1 * sign * force_loss
55
+ end
56
+
57
+ def to_s
58
+ [[format("Mass: %.1f kg", self.total_mass),
59
+ format("Fr.A: %.2f m^2", @frontal_area),
60
+ format("cD: %.2f", @cd),
61
+ ].join(' | '),
62
+ format("Powertrain: %s", @powertrain),
63
+ format("Tires: %s", @tire),
64
+ format("Corner mass: %.1f kg | Normal force: %.1f N",
65
+ self.corner_mass, self.normal_force),
66
+ ].join("\n")
67
+ end
68
+
69
+ def drive_force(rpm)
70
+ @tire.force(@powertrain.axle_torque(rpm))
71
+ end
72
+
73
+ def tire_speed(rpm)
74
+ @tire.tangential(@powertrain.axle_omega(rpm))
75
+ end
76
+
77
+ def motor_rpm(tire_speed)
78
+ @powertrain.gearbox.crank_rpm(tire_speed / @tire_radius)
79
+ end
80
+
81
+ def total_mass
82
+ @mass + @tire.mass * @num_tires
83
+ end
84
+
85
+ def corner_mass
86
+ Rational(self.total_mass) / @num_tires
87
+ end
88
+
89
+ # per tire
90
+ def normal_force
91
+ self.corner_mass * @env.g
92
+ end
93
+
94
+ # per tire
95
+ def tire_traction
96
+ @tire.traction(self.normal_force)
97
+ end
98
+
99
+ def total_normal_force
100
+ self.total_mass * env.g
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,17 @@
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
11
+ def self.pause(msg = '')
12
+ puts msg unless msg.empty?
13
+ puts ' [ Press Enter ]'
14
+ $stdin.gets
15
+ end
16
+ end
17
+ end