driving_physics 0.0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![CI Status](https://github.com/rickhull/driving_physics/actions/workflows/ci.yaml/badge.svg)](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
|