driving_physics 0.0.0.2
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 +7 -0
- data/README.md +83 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/demo/car.rb +28 -0
- data/demo/scalar_force.rb +26 -0
- data/demo/tire.rb +169 -0
- data/demo/vector_force.rb +26 -0
- data/demo/wheel.rb +84 -0
- data/driving_physics.gemspec +17 -0
- data/lib/driving_physics/car.rb +294 -0
- data/lib/driving_physics/environment.rb +29 -0
- data/lib/driving_physics/imperial.rb +61 -0
- data/lib/driving_physics/scalar_force.rb +68 -0
- data/lib/driving_physics/tire.rb +288 -0
- data/lib/driving_physics/vector_force.rb +136 -0
- data/lib/driving_physics/wheel.rb +191 -0
- data/lib/driving_physics.rb +71 -0
- data/test/car.rb +156 -0
- data/test/driving_physics.rb +29 -0
- data/test/scalar_force.rb +30 -0
- data/test/tire.rb +125 -0
- data/test/vector_force.rb +90 -0
- data/test/wheel.rb +177 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4b32478386d13b88eb67f9d2bb826c23dd06dd5100d807aa918cea6af962c6e3
|
4
|
+
data.tar.gz: 3ebcad787d058dac2d36718d46e5cf400dcc968f06427d2f65af4d1682f99166
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4ce4c55bbb0e9e5f12146856c45df05860443b18d7fd54c173e8b3026e55e1c8ccdfa3e9d48a6974af5f28c79e6df04b487c9728d5a831ec88d657a7ce997db1
|
7
|
+
data.tar.gz: 28ea2e0361cc180a5a405ed66caffcced3d1669ec18b87ce14f3ab0d77d130de9bd92864a2b78bddbcee236fff7bd982d66272456c4ba81eec43b863691e044d
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
[](https://github.com/rickhull/driving_physics/actions/workflows/ci.yaml)
|
2
|
+
|
3
|
+
# Driving Simulation
|
4
|
+
|
5
|
+
Physical simulation of how to make a car go around a track quickly, in pure
|
6
|
+
Ruby with minimal dependencies. Physics from first principles, often
|
7
|
+
using `Vector` class from `matrix.rb` in stdlib.
|
8
|
+
|
9
|
+
Note, this is very much a **Work In Progress**.
|
10
|
+
|
11
|
+
## Rationale
|
12
|
+
|
13
|
+
This is a toy project intended to scratch a personal itch: I want to create
|
14
|
+
an environment to play with performance objectives under reasonable real-world
|
15
|
+
constraints:
|
16
|
+
|
17
|
+
Primarily, one must slow down in order to navigate a curve
|
18
|
+
quickly.
|
19
|
+
|
20
|
+
All of this is done with an eye towards handing the controls over to an AI,
|
21
|
+
likely a neural net, so that it can learn (and possibly teach) techniques
|
22
|
+
that increase performance via decreasing lap times.
|
23
|
+
|
24
|
+
## Rough Notes, Thoughts, and Objectives
|
25
|
+
|
26
|
+
### Constraints:
|
27
|
+
|
28
|
+
1. cars must slow down to make tighter turns
|
29
|
+
2. cars must manage grip levels
|
30
|
+
3. equations to describe available grip
|
31
|
+
4. grip depends on tires and road surface (coefficient of grip)
|
32
|
+
5. grip "releases", whereby car controls lose effectiveness (sliding)
|
33
|
+
6. higher speed means higher turning radius
|
34
|
+
7. more mass (weight) means more grip, but also more force opposing that grip
|
35
|
+
8. one clear downside of more mass is increased tire wear and tire heat
|
36
|
+
9. tires get more grip with more heat up to a critical temp and then grip
|
37
|
+
falls off dramatically
|
38
|
+
10. heat cycles (overheat and then cool down) reduce grip capacity over time
|
39
|
+
11. sliding dramatically increases tire wear and decreases grip
|
40
|
+
12. tire wear minimally affects grip (outside of heat cycling), up until tire
|
41
|
+
is completely worn
|
42
|
+
13. sliding implies a gentle reduction of velocity over time, all things equal
|
43
|
+
14. wheelspin may occur on driven wheels (different from brake- or
|
44
|
+
turn-induced sliding)
|
45
|
+
15. wheelspin incurs similar wear and heat penalties to sliding (relative
|
46
|
+
velocity between tire and surface)
|
47
|
+
|
48
|
+
### Given These Constraints:
|
49
|
+
|
50
|
+
1. Car has enough power to create wheelspin (force applied to driven wheels
|
51
|
+
exceeds grip-reactive force)
|
52
|
+
2. Car can achieve velocity that requires braking to successfully achieve a
|
53
|
+
given turn radius
|
54
|
+
3. Grip level can be modeled as the ability to sustain acceleration around 1G
|
55
|
+
4. Consider slip angles and modulated sliding (e.g. threshold braking, mild
|
56
|
+
wheelspin, limit-of-grip controlled slides)
|
57
|
+
5. Fuel consumption reduces mass over time
|
58
|
+
6. Driving outputs: gas pedal position (0-100), brake pedal position (0-100),
|
59
|
+
steering wheel position (-100 - +100)
|
60
|
+
7. Car slows gently with 0 gas pedal
|
61
|
+
8. Car consumes fuel gently with 0 gas pedal (idle)
|
62
|
+
9. Car consumes fuel linearly with gas pedal position
|
63
|
+
10. Brakes wear with brake usage (linear plus heat factor)
|
64
|
+
11. Brakes can overheat
|
65
|
+
12. Hot brakes wear faster
|
66
|
+
13. Driving inputs: visual track position, fuel gauge, tire temp, brake temp
|
67
|
+
|
68
|
+
### Examples
|
69
|
+
|
70
|
+
Given:
|
71
|
+
* 1.0 g lateral acceleration
|
72
|
+
* 100 ft radius turn
|
73
|
+
|
74
|
+
How fast can the car go around the turn?
|
75
|
+
|
76
|
+
```
|
77
|
+
Vmax = sqrt(r) * sqrt(a) = sqrt(r*a)
|
78
|
+
Vmax = sqrt(100 ft * 32.2 ft / sec^2)
|
79
|
+
Vmax = sqrt(3220) ft/sec = 56.75 ft/sec
|
80
|
+
|
81
|
+
56.75 ft/sec * 3600 sec/hr / 5280 ft/mile
|
82
|
+
56.75 ft/sec * 3600/5280 = 38.7 mph
|
83
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
Rake::TestTask.new :test do |t|
|
4
|
+
t.pattern = "test/*.rb"
|
5
|
+
t.warning = true
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Run demo scripts"
|
9
|
+
task demo: [:test] do
|
10
|
+
Dir['demo/*.rb'].each { |filepath|
|
11
|
+
puts
|
12
|
+
sh "ruby -w -Ilib #{filepath}"
|
13
|
+
puts
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
task default: :test
|
18
|
+
|
19
|
+
#
|
20
|
+
# METRICS
|
21
|
+
#
|
22
|
+
|
23
|
+
begin
|
24
|
+
require 'flog_task'
|
25
|
+
FlogTask.new do |t|
|
26
|
+
t.threshold = 9000
|
27
|
+
t.dirs = ['lib']
|
28
|
+
t.verbose = true
|
29
|
+
end
|
30
|
+
rescue LoadError
|
31
|
+
warn 'flog_task unavailable'
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
require 'flay_task'
|
36
|
+
FlayTask.new do |t|
|
37
|
+
t.dirs = ['lib']
|
38
|
+
t.verbose = true
|
39
|
+
end
|
40
|
+
rescue LoadError
|
41
|
+
warn 'flay_task unavailable'
|
42
|
+
end
|
43
|
+
|
44
|
+
begin
|
45
|
+
require 'roodi_task'
|
46
|
+
# RoodiTask.new config: '.roodi.yml', patterns: ['lib/**/*.rb']
|
47
|
+
RoodiTask.new patterns: ['lib/**/*.rb']
|
48
|
+
rescue LoadError
|
49
|
+
warn "roodi_task unavailable"
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
require 'buildar'
|
54
|
+
|
55
|
+
Buildar.new do |b|
|
56
|
+
b.gemspec_file = 'driving_physics.gemspec'
|
57
|
+
b.version_file = 'VERSION'
|
58
|
+
b.use_git = true
|
59
|
+
end
|
60
|
+
rescue LoadError
|
61
|
+
warn "buildar tasks unavailable"
|
62
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0.2
|
data/demo/car.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'driving_physics/car'
|
2
|
+
|
3
|
+
DP = DrivingPhysics
|
4
|
+
|
5
|
+
env = DP::Environment.new
|
6
|
+
car = DP::Car.new(env)
|
7
|
+
car.add_fuel 10
|
8
|
+
duration = 120 # seconds
|
9
|
+
|
10
|
+
puts env
|
11
|
+
puts
|
12
|
+
puts car
|
13
|
+
|
14
|
+
car.controls.drive_pedal = 1.0
|
15
|
+
|
16
|
+
(duration * env.hz).times { |i|
|
17
|
+
car.tick!
|
18
|
+
if i % env.hz == 0
|
19
|
+
if car.sum_forces.magnitude < 1
|
20
|
+
car.controls.drive_pedal = 0.0
|
21
|
+
car.controls.brake_pedal = 1.0
|
22
|
+
end
|
23
|
+
puts
|
24
|
+
puts "[t = #{i / env.hz}]"
|
25
|
+
puts car
|
26
|
+
gets if i % (env.hz * 10) == 0
|
27
|
+
end
|
28
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'driving_physics/scalar_force'
|
2
|
+
|
3
|
+
DP = DrivingPhysics
|
4
|
+
|
5
|
+
pos = 0 # m
|
6
|
+
spd = 0 # m/s
|
7
|
+
mass = 1000 # kg
|
8
|
+
weight = mass * DP::G # N
|
9
|
+
drive_force = 7000 # N
|
10
|
+
duration = 100 # seconds
|
11
|
+
tick = 1.0 / DP::HZ
|
12
|
+
|
13
|
+
(duration * DP::HZ).times { |i|
|
14
|
+
nf = drive_force - DP::ScalarForce.all_resistance(spd, nf_mag: weight)
|
15
|
+
|
16
|
+
a = DP.a(nf, mass)
|
17
|
+
spd = DP.v(a, spd, dt: tick)
|
18
|
+
pos = DP.p(spd, pos, dt: tick)
|
19
|
+
|
20
|
+
if i % DP::HZ == 0
|
21
|
+
puts [i / DP::HZ,
|
22
|
+
format("%.2f m/s", spd),
|
23
|
+
format("%.2f m", pos),
|
24
|
+
].join("\t")
|
25
|
+
end
|
26
|
+
}
|
data/demo/tire.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'driving_physics/tire'
|
2
|
+
|
3
|
+
include DrivingPhysics
|
4
|
+
|
5
|
+
t = Tire.new
|
6
|
+
t.condition.debug_temp = false
|
7
|
+
|
8
|
+
# drive time
|
9
|
+
mins = 45
|
10
|
+
|
11
|
+
drive_map = {
|
12
|
+
acc: {
|
13
|
+
label: "ACCELERATING",
|
14
|
+
g: 0.5,
|
15
|
+
fuel: 0.0002, # kg consumed / tick
|
16
|
+
small_slide: 1, # m/s of wheelspin
|
17
|
+
big_slide: 10,
|
18
|
+
seconds: 1,
|
19
|
+
next_sector: :brake,
|
20
|
+
},
|
21
|
+
brake: {
|
22
|
+
label: "BRAKING",
|
23
|
+
g: 1.0,
|
24
|
+
fuel: 0.00001,
|
25
|
+
small_slide: 0.5,
|
26
|
+
big_slide: 2,
|
27
|
+
seconds: 1,
|
28
|
+
next_sector: :corner,
|
29
|
+
},
|
30
|
+
corner: {
|
31
|
+
label: "CORNERING",
|
32
|
+
g: 0.8,
|
33
|
+
fuel: 0.0001,
|
34
|
+
small_slide: 0.5,
|
35
|
+
big_slide: 5,
|
36
|
+
seconds: 2,
|
37
|
+
next_sector: :acc,
|
38
|
+
},
|
39
|
+
}
|
40
|
+
|
41
|
+
slide_speed = 0
|
42
|
+
sliding = false
|
43
|
+
mass = 900.0
|
44
|
+
ambient_temp = 25
|
45
|
+
critical_temp = 100
|
46
|
+
|
47
|
+
drive_time = 0 # how many ticks elapsed in the current sector
|
48
|
+
sector_map = drive_map[:acc]
|
49
|
+
puts "ACCELERATING"
|
50
|
+
puts "---"
|
51
|
+
|
52
|
+
cooldown = false
|
53
|
+
pushing = false
|
54
|
+
|
55
|
+
# 100 ticks / sec
|
56
|
+
(mins * 60 * 100).times { |i|
|
57
|
+
drive_time += 1
|
58
|
+
|
59
|
+
dynamic_g = sector_map[:g]
|
60
|
+
|
61
|
+
if t.condition.temp_c <= 80 and cooldown
|
62
|
+
puts "ENDING COOLDOWN"
|
63
|
+
cooldown = false
|
64
|
+
elsif t.condition.temp_c >= 110 and Random.rand(100) >= 90
|
65
|
+
puts "COOLING DOWN"
|
66
|
+
cooldown = true
|
67
|
+
end
|
68
|
+
dynamic_g -= 0.2 if cooldown
|
69
|
+
|
70
|
+
if pushing
|
71
|
+
# stop pushing at very high temp
|
72
|
+
# 1/1000 chance to stop pushing
|
73
|
+
if cooldown
|
74
|
+
puts "ENDING PUSH BECAUSE COOLDOWN"
|
75
|
+
pushing = false
|
76
|
+
else
|
77
|
+
if t.condition.temp_c >= critical_temp and Random.rand(1000) >= 999
|
78
|
+
puts "ENDING PUSH"
|
79
|
+
pushing = false
|
80
|
+
else
|
81
|
+
dynamic_g += 0.2
|
82
|
+
end
|
83
|
+
end
|
84
|
+
else
|
85
|
+
if !cooldown and
|
86
|
+
t.condition.temp_c <= critical_temp and
|
87
|
+
Random.rand(1000) >= 999
|
88
|
+
|
89
|
+
puts "PUSHING!"
|
90
|
+
pushing = true
|
91
|
+
dynamic_g += 0.2
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
if sliding
|
96
|
+
# 5% chance to end the slide
|
97
|
+
if Random.rand(100) >= 95
|
98
|
+
puts " -= CAUGHT THE SLIDE! =-"
|
99
|
+
sliding = false
|
100
|
+
slide_speed = 0
|
101
|
+
end
|
102
|
+
else
|
103
|
+
# 1% chance to start a small slide
|
104
|
+
# 0.1% chance to start a big slide
|
105
|
+
if Random.rand(100) >= 99
|
106
|
+
puts " -= SMALL SLIDE! =-"
|
107
|
+
sliding = true
|
108
|
+
slide_speed = sector_map[:small_slide]
|
109
|
+
elsif Random.rand(1000) >= 999
|
110
|
+
puts " -= BIG SLIDE! =-"
|
111
|
+
sliding = true
|
112
|
+
slide_speed = sector_map[:big_slide]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# fuel consumption
|
117
|
+
# 5L of fuel should last 5 minutes
|
118
|
+
# ~3.5 kg of fuel consumption
|
119
|
+
# 1.2e-4 kg / tick
|
120
|
+
mass -= sector_map[:fuel]
|
121
|
+
|
122
|
+
begin
|
123
|
+
t.condition.tick!(ambient_temp: ambient_temp, g: dynamic_g,
|
124
|
+
slide_speed: slide_speed,
|
125
|
+
mass: mass, tire_mass: 12, critical_temp: critical_temp)
|
126
|
+
|
127
|
+
if i % 10 == 0
|
128
|
+
condition = if pushing
|
129
|
+
"Pushing"
|
130
|
+
elsif cooldown
|
131
|
+
"Cooldown"
|
132
|
+
else
|
133
|
+
"Normal"
|
134
|
+
end
|
135
|
+
|
136
|
+
puts [sector_map[:label].ljust(12, ' '),
|
137
|
+
'%.2f' % dynamic_g,
|
138
|
+
'%.1f' % slide_speed,
|
139
|
+
'%.3f' % t.condition.temp_c,
|
140
|
+
].join(' ')
|
141
|
+
end
|
142
|
+
if i % 600 == 0
|
143
|
+
puts
|
144
|
+
puts "Condition: #{condition}"
|
145
|
+
puts "Mass: #{'%.2f' % mass} kg"
|
146
|
+
if t.condition.tread_mm > 0
|
147
|
+
puts "Tread remaining: #{'%.3f' % t.condition.tread_mm}"
|
148
|
+
else
|
149
|
+
puts "Cords remaining: #{'%.3f' % t.condition.cords_mm}"
|
150
|
+
end
|
151
|
+
puts "Heat cycles: #{t.condition.heat_cycles}"
|
152
|
+
puts DrivingPhysics.elapsed_display(i * 10)
|
153
|
+
puts "[Enter] to continue"
|
154
|
+
gets
|
155
|
+
end
|
156
|
+
rescue Tire::Condition::Error => e
|
157
|
+
puts "FATAL:"
|
158
|
+
puts [e.class, e.message].join(': ')
|
159
|
+
break
|
160
|
+
end
|
161
|
+
|
162
|
+
if drive_time > sector_map[:seconds] * 100
|
163
|
+
sector_map = drive_map[sector_map[:next_sector]]
|
164
|
+
drive_time = 0
|
165
|
+
puts
|
166
|
+
puts sector_map[:label]
|
167
|
+
puts '---'
|
168
|
+
end
|
169
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'driving_physics/vector_force'
|
2
|
+
|
3
|
+
DP = DrivingPhysics
|
4
|
+
|
5
|
+
p = Vector[0, 0] # m
|
6
|
+
v = Vector[0, 0] # m/s
|
7
|
+
mass = 1000 # kg
|
8
|
+
weight = mass * DP::G # N
|
9
|
+
duration = 100 # seconds
|
10
|
+
drive_force = DP.random_unit_vector * 7000 # N
|
11
|
+
tick = 1.0 / DP::HZ
|
12
|
+
|
13
|
+
(duration * DP::HZ).times { |i|
|
14
|
+
nf = drive_force + DP::VectorForce.all_resistance(v, dir: v, nf_mag: weight)
|
15
|
+
|
16
|
+
a = DP.a(nf, mass)
|
17
|
+
v = DP.v(a, v, dt: tick)
|
18
|
+
p = DP.p(v, p, dt: tick)
|
19
|
+
|
20
|
+
if i % DP::HZ == 0
|
21
|
+
puts [i / DP::HZ,
|
22
|
+
format("%.2f m/s", v.magnitude),
|
23
|
+
format("%.2f m", p.magnitude),
|
24
|
+
].join("\t")
|
25
|
+
end
|
26
|
+
}
|
data/demo/wheel.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'driving_physics/wheel'
|
2
|
+
|
3
|
+
include DrivingPhysics
|
4
|
+
|
5
|
+
env = Environment.new
|
6
|
+
wheel = Wheel.new(env, mass: 25.0)
|
7
|
+
|
8
|
+
puts env
|
9
|
+
puts wheel
|
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 * wheel.mass
|
17
|
+
corner_mass = Rational(total_mass) / 4
|
18
|
+
normal_force = corner_mass * env.g
|
19
|
+
axle_torque = 1000 # N*m
|
20
|
+
friction_loss = 0.05 # 5% friction / hysteresis loss
|
21
|
+
|
22
|
+
puts [format("Corner mass: %d kg", corner_mass),
|
23
|
+
format("Normal force: %.1f N", normal_force),
|
24
|
+
format("Axle torque: %d Nm", axle_torque),
|
25
|
+
].join("\n")
|
26
|
+
puts
|
27
|
+
|
28
|
+
traction = wheel.traction(normal_force)
|
29
|
+
drive_force = wheel.force(axle_torque)
|
30
|
+
inertial_loss = wheel.inertial_loss(axle_torque, supported_mass)
|
31
|
+
friction_loss *= axle_torque # 5% of the axle torque
|
32
|
+
|
33
|
+
# drive force = (axle torque - inertia - friction) limited by traction
|
34
|
+
|
35
|
+
net_axle_torque = axle_torque - inertial_loss - friction_loss
|
36
|
+
net_drive_force = wheel.force(net_axle_torque)
|
37
|
+
net_drive_force = traction if net_drive_force > traction # traction limited
|
38
|
+
|
39
|
+
acc = DrivingPhysics.acc(net_drive_force, supported_mass) # translational
|
40
|
+
|
41
|
+
puts [format("Traction: %.1f N", traction),
|
42
|
+
format("Drive force: %.1f N", drive_force),
|
43
|
+
format("Inertial loss: %.1f Nm", inertial_loss),
|
44
|
+
format("Friction loss: %.1f Nm", friction_loss),
|
45
|
+
format("Net Axle Torque: %.1f Nm", net_axle_torque),
|
46
|
+
format("Net Drive Force: %.1f N", net_drive_force),
|
47
|
+
format("Acceleration: %.1f m/s/s", acc),
|
48
|
+
format("Alpha: %.2f r/s/s", acc / wheel.radius_m),
|
49
|
+
].join("\n")
|
50
|
+
puts
|
51
|
+
|
52
|
+
duration = 100 # sec
|
53
|
+
|
54
|
+
dist = 0.0 # meters
|
55
|
+
speed = 0.0 # meters/s
|
56
|
+
|
57
|
+
theta = 0.0 # radians
|
58
|
+
omega = 0.0 # radians/s
|
59
|
+
|
60
|
+
(duration * env.hz).times { |i|
|
61
|
+
# accumulate frictional losses with speed (omega)
|
62
|
+
omega_loss_cof = [wheel.omega_friction * omega, 1.0].min
|
63
|
+
slowed_acc = acc - acc * omega_loss_cof
|
64
|
+
|
65
|
+
# translational kinematics
|
66
|
+
speed += slowed_acc * env.tick
|
67
|
+
dist += speed * env.tick
|
68
|
+
|
69
|
+
# rotational kinematics
|
70
|
+
alpha = slowed_acc / wheel.radius_m
|
71
|
+
omega += alpha * env.tick
|
72
|
+
theta += omega * env.tick
|
73
|
+
|
74
|
+
if i < 10 or
|
75
|
+
(i < 10_000 and i%1000 == 0) or
|
76
|
+
(i % 10_000 == 0)
|
77
|
+
puts DrivingPhysics.elapsed_display(i)
|
78
|
+
puts format("Wheel: %.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", dist, speed, slowed_acc)
|
80
|
+
puts format("Omega Frictional Loss: %.1f%%", omega_loss_cof * 100)
|
81
|
+
puts "Press [enter]"
|
82
|
+
gets
|
83
|
+
end
|
84
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'driving_physics'
|
3
|
+
s.summary = "WIP"
|
4
|
+
s.description = "WIP"
|
5
|
+
s.authors = ["Rick Hull"]
|
6
|
+
s.homepage = "https://github.com/rickhull/driving_physics"
|
7
|
+
s.license = "LGPL-3.0"
|
8
|
+
|
9
|
+
s.required_ruby_version = "> 2"
|
10
|
+
|
11
|
+
s.version = File.read(File.join(__dir__, 'VERSION')).chomp
|
12
|
+
|
13
|
+
s.files = %w[driving_physics.gemspec VERSION README.md Rakefile]
|
14
|
+
s.files += Dir['lib/**/*.rb']
|
15
|
+
s.files += Dir['test/**/*.rb']
|
16
|
+
s.files += Dir['demo/**/*.rb']
|
17
|
+
end
|