driving_physics 0.0.0.2 → 0.0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -1
- data/Rakefile +11 -4
- data/VERSION +1 -1
- data/demo/car.rb +165 -18
- data/demo/disk.rb +83 -0
- data/demo/gearbox.rb +52 -0
- data/demo/motor.rb +141 -0
- data/demo/powertrain.rb +47 -0
- data/demo/scalar_force.rb +41 -15
- data/demo/tire.rb +80 -162
- data/demo/vector_force.rb +46 -16
- data/lib/driving_physics/car.rb +77 -248
- data/lib/driving_physics/cli.rb +51 -0
- data/lib/driving_physics/disk.rb +185 -0
- data/lib/driving_physics/gearbox.rb +109 -0
- data/lib/driving_physics/imperial.rb +6 -0
- data/lib/driving_physics/motor.rb +103 -0
- data/lib/driving_physics/power.rb +20 -0
- data/lib/driving_physics/powertrain.rb +50 -0
- data/lib/driving_physics/scalar_force.rb +6 -3
- data/lib/driving_physics/tire.rb +90 -258
- data/lib/driving_physics/vector_force.rb +15 -2
- data/lib/driving_physics.rb +2 -0
- data/test/disk.rb +129 -0
- data/test/scalar_force.rb +7 -5
- data/test/tire.rb +144 -88
- data/test/vector_force.rb +7 -1
- metadata +12 -5
- data/demo/wheel.rb +0 -84
- data/lib/driving_physics/wheel.rb +0 -191
- data/test/car.rb +0 -156
- data/test/wheel.rb +0 -177
data/test/tire.rb
CHANGED
@@ -1,125 +1,181 @@
|
|
1
|
-
require 'driving_physics/tire'
|
2
1
|
require 'minitest/autorun'
|
2
|
+
require 'driving_physics/tire'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
describe Tire do
|
7
|
-
TP = Tire::TemperatureProfile
|
4
|
+
T = DrivingPhysics::Tire
|
8
5
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
describe T do
|
7
|
+
describe "Tire.traction" do
|
8
|
+
it "calculates traction force from normal force and coeff of friction" do
|
9
|
+
scalar_nf = 9800 # N
|
10
|
+
cof = 1.1
|
11
|
+
scalar_t = T.traction(scalar_nf, cof)
|
12
|
+
expect(scalar_t).must_equal 10780.0
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
expect(
|
17
|
-
expect { TP.new('', '') }.must_raise ArgumentError
|
18
|
-
expect { TP.new([]) }.must_raise ArgumentError
|
19
|
-
expect { TP.new([0], []) }.must_raise ArgumentError
|
20
|
-
expect { TP.new([], [0.0]) }.must_raise ArgumentError
|
14
|
+
vector_nf = Vector[9800, 0]
|
15
|
+
vector_t = T.traction(vector_nf, cof)
|
16
|
+
expect(vector_t).must_equal Vector[10780.0, 0.0]
|
21
17
|
end
|
18
|
+
end
|
22
19
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
0.0 => 0.5
|
28
|
-
}.each { |temp, gf| expect(@tp.grip_factor(temp)).must_equal gf }
|
29
|
-
end
|
20
|
+
describe "Tire.volume" do
|
21
|
+
it "calculates the volume (m^3) of disk given radius and width" do
|
22
|
+
cubic_m = T.volume(1.0, 1.0)
|
23
|
+
expect(cubic_m).must_equal Math::PI
|
30
24
|
|
31
|
-
|
32
|
-
expect(
|
33
|
-
expect(@tp.critical_temp).must_equal 105
|
25
|
+
cubic_m = T.volume(0.35, 0.2)
|
26
|
+
expect(cubic_m).must_be_within_epsilon 0.076969
|
34
27
|
end
|
28
|
+
end
|
35
29
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
30
|
+
describe "Tire.volume_l" do
|
31
|
+
it "calculates the volume (L) of a disk given radius and width" do
|
32
|
+
liters = T.volume_l(1.0, 1.0)
|
33
|
+
expect(liters).must_equal Math::PI * 1000
|
40
34
|
|
41
|
-
|
42
|
-
|
43
|
-
}.must_raise TP::Error
|
35
|
+
liters = T.volume_l(0.35, 0.2)
|
36
|
+
expect(liters).must_be_within_epsilon 76.96902
|
44
37
|
end
|
45
38
|
end
|
46
39
|
|
47
|
-
|
48
|
-
|
40
|
+
describe "Tire.density" do
|
41
|
+
it "calculates the density (kg/L) given mass and volume" do
|
42
|
+
expect(T.density(25.0, 25.0)).must_equal 1.0
|
43
|
+
expect(T.density(50.0, 25.0)).must_equal 2.0
|
44
|
+
end
|
49
45
|
end
|
50
46
|
|
51
|
-
|
52
|
-
|
47
|
+
describe "Tire.mass" do
|
48
|
+
it "calculates the mass (kg) of a disk given radius, width, and density" do
|
49
|
+
expect(T.mass(0.35, 0.2, T::DENSITY)).must_be_within_epsilon 25.015
|
50
|
+
end
|
53
51
|
end
|
54
52
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
}
|
60
|
-
|
61
|
-
expect(t.tread_mm).must_equal 9999
|
62
|
-
expect(t.g_factor).must_equal(-0.1234)
|
53
|
+
describe "Tire.rotational_inertia" do
|
54
|
+
it "calculates rotational inertia for a disk given radius and mass" do
|
55
|
+
expect(T.rotational_inertia(0.35, 25.0)).must_be_within_epsilon 1.53125
|
56
|
+
end
|
63
57
|
end
|
64
58
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
59
|
+
describe "Tire.alpha" do
|
60
|
+
it "calculates angular acceleration from torque and inertia" do
|
61
|
+
scalar_torque = 1000
|
62
|
+
inertia = T.rotational_inertia(0.35, 25.0)
|
63
|
+
expect(T.alpha scalar_torque, inertia).must_be_within_epsilon 653.061
|
64
|
+
|
65
|
+
vector_torque = Vector[0, 0, 1000]
|
66
|
+
vector_alpha = T.alpha vector_torque, inertia
|
67
|
+
expect(vector_alpha).must_be_instance_of Vector
|
68
|
+
expect(vector_alpha.size).must_equal 3
|
69
|
+
expect(vector_alpha[2]).must_be_within_epsilon 653.06
|
70
|
+
end
|
73
71
|
end
|
74
72
|
|
75
|
-
|
76
|
-
|
73
|
+
describe "Tire.torque_vector" do
|
74
|
+
it "calculates a torque in the 3rd dimension given 2D force and radius" do
|
75
|
+
force = Vector[1000, 0]
|
76
|
+
radius = Vector[0, 5]
|
77
|
+
torque = T.torque_vector(force, radius)
|
78
|
+
expect(torque).must_be_instance_of Vector
|
79
|
+
expect(torque.size).must_equal 3
|
80
|
+
expect(torque[2]).must_be_within_epsilon 5000.0
|
81
|
+
end
|
82
|
+
end
|
77
83
|
|
78
|
-
|
79
|
-
|
84
|
+
describe "Tire.force_vector" do
|
85
|
+
it "calculates a (3D) force given 3D torque and 2D radius" do
|
86
|
+
# let's invert the Tire.torque_vector case from above:
|
87
|
+
torque = Vector[0, 0, 5000]
|
88
|
+
radius = Vector[0, 5]
|
89
|
+
force = T.force_vector(torque, radius)
|
90
|
+
expect(force).must_be_instance_of Vector
|
91
|
+
expect(force.size).must_equal 3
|
92
|
+
expect(force[0]).must_be_within_epsilon 1000.0
|
93
|
+
|
94
|
+
# now let's rotate the radius into the x-dimension
|
95
|
+
# right hand rule, positive torque means thumb into screen, clockwise
|
96
|
+
# negative-x radius means positive-y force
|
97
|
+
torque = Vector[0, 0, 500]
|
98
|
+
radius = Vector[-5, 0]
|
99
|
+
force = T.force_vector(torque, radius)
|
100
|
+
expect(force).must_be_instance_of Vector
|
101
|
+
expect(force.size).must_equal 3
|
102
|
+
expect(force[1]).must_be_within_epsilon 100.0
|
103
|
+
end
|
80
104
|
end
|
81
105
|
|
82
|
-
|
83
|
-
|
106
|
+
describe "instance methods" do
|
107
|
+
before do
|
108
|
+
@env = DrivingPhysics::Environment.new
|
109
|
+
@tire = T.new(@env)
|
110
|
+
end
|
84
111
|
|
85
|
-
|
86
|
-
|
112
|
+
it "initializes" do
|
113
|
+
expect(@tire).must_be_instance_of T
|
114
|
+
expect(@tire.density).must_equal T::DENSITY # sanity check
|
115
|
+
expect(@tire.mass).must_be_within_epsilon 25.01
|
87
116
|
|
88
|
-
|
89
|
-
|
117
|
+
with_mass = T.new(@env) { |w|
|
118
|
+
w.mass = 99.01
|
119
|
+
}
|
120
|
+
expect(with_mass.mass).must_equal 99.01
|
121
|
+
expect(with_mass.density).wont_equal T::DENSITY
|
122
|
+
end
|
90
123
|
|
91
|
-
|
92
|
-
|
93
|
-
|
124
|
+
it "has a string representation" do
|
125
|
+
str = @tire.to_s
|
126
|
+
expect(str).must_be_instance_of String
|
127
|
+
expect(str.length).must_be(:>, 5)
|
128
|
+
end
|
94
129
|
|
95
|
-
|
96
|
-
|
97
|
-
|
130
|
+
it "loses radius as it wears" do
|
131
|
+
old_r = @tire.radius
|
132
|
+
wear_amt = 50/1000r
|
133
|
+
@tire.wear! wear_amt
|
134
|
+
expect(@tire.radius).must_equal old_r - wear_amt
|
135
|
+
end
|
98
136
|
|
99
|
-
|
100
|
-
|
101
|
-
|
137
|
+
it "calculates mass from current radius" do
|
138
|
+
expect(@tire.mass).must_be_within_epsilon 25.01
|
139
|
+
@tire.wear!(50/1000r)
|
140
|
+
expect(@tire.mass).must_be_within_epsilon 18.378
|
141
|
+
end
|
102
142
|
|
103
|
-
|
104
|
-
|
105
|
-
|
143
|
+
it "has volume" do
|
144
|
+
expect(@tire.volume).must_be_within_epsilon 0.07697
|
145
|
+
expect(@tire.volume_l).must_be_within_epsilon 76.96902
|
146
|
+
end
|
106
147
|
|
107
|
-
|
108
|
-
|
109
|
-
|
148
|
+
it "has inertia" do
|
149
|
+
expect(@tire.rotational_inertia).must_be_within_epsilon 1.5321
|
150
|
+
end
|
110
151
|
|
111
|
-
|
112
|
-
|
113
|
-
|
152
|
+
it "has traction force based on normal force" do
|
153
|
+
scalar_nf = 9800
|
154
|
+
expect(@tire.traction scalar_nf).must_equal 10780.0
|
155
|
+
expect(@tire.traction scalar_nf, static: false).must_equal 6860.0
|
114
156
|
|
115
|
-
|
116
|
-
|
157
|
+
vector_nf = Vector[9800, 0]
|
158
|
+
expect(@tire.traction vector_nf).must_equal Vector[10780.0, 0.0]
|
159
|
+
expect(@tire.traction vector_nf, static: false).
|
160
|
+
must_equal Vector[6860.0, 0.0]
|
161
|
+
end
|
162
|
+
|
163
|
+
it "determines (e.g. thrust) force based on axle torque" do
|
164
|
+
expect(@tire.force 1000).must_be_within_epsilon 2857.143
|
165
|
+
@tire.wear! 50/1000r
|
166
|
+
expect(@tire.force 1000).must_be_within_epsilon 3333.333
|
167
|
+
end
|
117
168
|
|
118
|
-
|
119
|
-
|
120
|
-
|
169
|
+
it "determines tractable torque" do
|
170
|
+
scalar_nf = 9800
|
171
|
+
expect(@tire.tractable_torque scalar_nf).must_be_within_epsilon 3773.0
|
172
|
+
kin_tq = @tire.tractable_torque scalar_nf, static: false
|
173
|
+
expect(kin_tq).must_be_within_epsilon 2401.0
|
121
174
|
|
122
|
-
|
123
|
-
|
175
|
+
# not sure about how torque vectors work, but the "math" "works":
|
176
|
+
vector_nf = Vector[9800, 0]
|
177
|
+
expect(@tire.tractable_torque(vector_nf)[0]).
|
178
|
+
must_be_within_epsilon 3773.0
|
179
|
+
end
|
124
180
|
end
|
125
181
|
end
|
data/test/vector_force.rb
CHANGED
@@ -74,9 +74,15 @@ describe VectorForce do
|
|
74
74
|
end
|
75
75
|
|
76
76
|
it "calculates the rotational resistance as a function of velocity" do
|
77
|
+
rr = VectorForce.rotational_resistance(@v, rot_const: 0)
|
78
|
+
rr2 = VectorForce.rotational_resistance(@v * 2, rot_const: 0)
|
79
|
+
expect(rr2).must_equal rr * 2
|
80
|
+
|
81
|
+
# now with rot_const != 0, the relationship is skewed
|
77
82
|
rr = VectorForce.rotational_resistance(@v)
|
78
83
|
rr2 = VectorForce.rotational_resistance(@v * 2)
|
79
|
-
expect(rr2).
|
84
|
+
expect(rr2).wont_equal rr * 2 # because of rot_const
|
85
|
+
expect(rr2.magnitude).must_be(:<, (rr * 2).magnitude)
|
80
86
|
end
|
81
87
|
|
82
88
|
it "sums resistance forces" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: driving_physics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rick Hull
|
@@ -20,25 +20,32 @@ files:
|
|
20
20
|
- Rakefile
|
21
21
|
- VERSION
|
22
22
|
- demo/car.rb
|
23
|
+
- demo/disk.rb
|
24
|
+
- demo/gearbox.rb
|
25
|
+
- demo/motor.rb
|
26
|
+
- demo/powertrain.rb
|
23
27
|
- demo/scalar_force.rb
|
24
28
|
- demo/tire.rb
|
25
29
|
- demo/vector_force.rb
|
26
|
-
- demo/wheel.rb
|
27
30
|
- driving_physics.gemspec
|
28
31
|
- lib/driving_physics.rb
|
29
32
|
- lib/driving_physics/car.rb
|
33
|
+
- lib/driving_physics/cli.rb
|
34
|
+
- lib/driving_physics/disk.rb
|
30
35
|
- lib/driving_physics/environment.rb
|
36
|
+
- lib/driving_physics/gearbox.rb
|
31
37
|
- lib/driving_physics/imperial.rb
|
38
|
+
- lib/driving_physics/motor.rb
|
39
|
+
- lib/driving_physics/power.rb
|
40
|
+
- lib/driving_physics/powertrain.rb
|
32
41
|
- lib/driving_physics/scalar_force.rb
|
33
42
|
- lib/driving_physics/tire.rb
|
34
43
|
- lib/driving_physics/vector_force.rb
|
35
|
-
-
|
36
|
-
- test/car.rb
|
44
|
+
- test/disk.rb
|
37
45
|
- test/driving_physics.rb
|
38
46
|
- test/scalar_force.rb
|
39
47
|
- test/tire.rb
|
40
48
|
- test/vector_force.rb
|
41
|
-
- test/wheel.rb
|
42
49
|
homepage: https://github.com/rickhull/driving_physics
|
43
50
|
licenses:
|
44
51
|
- LGPL-3.0
|
data/demo/wheel.rb
DELETED
@@ -1,84 +0,0 @@
|
|
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
|
-
}
|
@@ -1,191 +0,0 @@
|
|
1
|
-
require 'driving_physics/environment'
|
2
|
-
require 'driving_physics/vector_force'
|
3
|
-
|
4
|
-
module DrivingPhysics
|
5
|
-
# Rotational complements to acc/vel/pos
|
6
|
-
# alpha - angular acceleration
|
7
|
-
# omega - angular velocity (radians / s)
|
8
|
-
# theta - radians
|
9
|
-
|
10
|
-
class Wheel
|
11
|
-
# Note, this is not the density of solid rubber. This density
|
12
|
-
# yields a sensible mass for a wheel / tire combo at common radius
|
13
|
-
# and width, assuming a uniform density
|
14
|
-
# e.g. 25kg at 350mm R x 200mm W
|
15
|
-
#
|
16
|
-
DENSITY = 0.325 # kg / L
|
17
|
-
|
18
|
-
# * the traction force opposes the axle torque / drive force
|
19
|
-
# thus, driving the car forward
|
20
|
-
# * if the drive force exceeds the traction force, slippage occurs
|
21
|
-
# * slippage reduces the available traction force further
|
22
|
-
# * if the drive force is not reduced, the slippage increases
|
23
|
-
# until resistance forces equal the drive force
|
24
|
-
def self.traction(normal_force, cof)
|
25
|
-
normal_force * cof
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.force(axle_torque, radius_m)
|
29
|
-
axle_torque / radius_m.to_f
|
30
|
-
end
|
31
|
-
|
32
|
-
# in m^3
|
33
|
-
def self.volume(radius_m, width_m)
|
34
|
-
Math::PI * radius_m ** 2 * width_m.to_f
|
35
|
-
end
|
36
|
-
|
37
|
-
# in L
|
38
|
-
def self.volume_l(radius_m, width_m)
|
39
|
-
volume(radius_m, width_m) * 1000
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.density(mass, volume_l)
|
43
|
-
mass.to_f / volume_l
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.mass(radius_m, width_m, density)
|
47
|
-
density * volume_l(radius_m, width_m)
|
48
|
-
end
|
49
|
-
|
50
|
-
# I = 1/2 (m)(r^2) for a disk
|
51
|
-
def self.rotational_inertia(radius_m, mass)
|
52
|
-
mass * radius_m**2 / 2.0
|
53
|
-
end
|
54
|
-
class << self
|
55
|
-
alias_method(:moment_of_inertia, :rotational_inertia)
|
56
|
-
end
|
57
|
-
|
58
|
-
# angular acceleration
|
59
|
-
def self.alpha(torque, inertia)
|
60
|
-
torque / inertia
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.tangential(rotational, radius_m)
|
64
|
-
rotational * radius_m
|
65
|
-
end
|
66
|
-
class << self
|
67
|
-
alias_method(:tangential_a, :tangential)
|
68
|
-
alias_method(:tangential_v, :tangential)
|
69
|
-
alias_method(:tangential_p, :tangential)
|
70
|
-
end
|
71
|
-
|
72
|
-
# vectors only
|
73
|
-
def self.torque_vector(force, radius)
|
74
|
-
if !force.is_a?(Vector) or force.size != 2
|
75
|
-
raise(ArgumentError, "force must be a 2D vector")
|
76
|
-
end
|
77
|
-
if !radius.is_a?(Vector) or radius.size != 2
|
78
|
-
raise(ArgumentError, "radius must be a 2D vector")
|
79
|
-
end
|
80
|
-
force = Vector[force[0], force[1], 0]
|
81
|
-
radius = Vector[radius[0], radius[1], 0]
|
82
|
-
force.cross(radius)
|
83
|
-
end
|
84
|
-
|
85
|
-
# vectors only
|
86
|
-
def self.force_vector(torque, radius)
|
87
|
-
if !torque.is_a?(Vector) or torque.size != 3
|
88
|
-
raise(ArgumentError, "torque must be a 3D vector")
|
89
|
-
end
|
90
|
-
if !radius.is_a?(Vector) or radius.size != 2
|
91
|
-
raise(ArgumentError, "radius must be a 2D vector")
|
92
|
-
end
|
93
|
-
radius = Vector[radius[0], radius[1], 0]
|
94
|
-
radius.cross(torque) / radius.dot(radius)
|
95
|
-
end
|
96
|
-
|
97
|
-
attr_reader :env, :radius, :radius_m, :width, :width_m, :density, :temp,
|
98
|
-
:mu_s, :mu_k, :omega_friction
|
99
|
-
|
100
|
-
def initialize(env,
|
101
|
-
radius: 350, width: 200, density: DENSITY,
|
102
|
-
temp: nil, mass: nil,
|
103
|
-
mu_s: 1.1, mu_k: 0.7,
|
104
|
-
omega_friction: 0.002)
|
105
|
-
@env = env
|
106
|
-
@radius = radius.to_f # mm
|
107
|
-
@radius_m = @radius / 1000
|
108
|
-
@width = width.to_f # mm
|
109
|
-
@width_m = @width / 1000
|
110
|
-
@mu_s = mu_s.to_f # static friction
|
111
|
-
@mu_k = mu_k.to_f # kinetic friction
|
112
|
-
@omega_friction = omega_friction # scales with speed
|
113
|
-
@density = mass.nil? ? density : self.class.density(mass, volume_l)
|
114
|
-
@temp = temp.to_f || @env.air_temp
|
115
|
-
end
|
116
|
-
|
117
|
-
def to_s
|
118
|
-
[[format("%d mm (R) x %d mm (W)", @radius, @width),
|
119
|
-
format("Mass: %.1f kg %.3f kg/L", self.mass, @density),
|
120
|
-
format("cF: %.1f / %.1f", @mu_s, @mu_k),
|
121
|
-
].join(" | "),
|
122
|
-
[format("Temp: %.1f C", @temp),
|
123
|
-
].join(" | "),
|
124
|
-
].join("\n")
|
125
|
-
end
|
126
|
-
|
127
|
-
def wear!(amount_mm)
|
128
|
-
@radius -= amount_mm
|
129
|
-
@radius_m = @radius / 1000
|
130
|
-
end
|
131
|
-
|
132
|
-
def heat!(amount_deg_c)
|
133
|
-
@temp += amount_deg_c
|
134
|
-
end
|
135
|
-
|
136
|
-
def mass
|
137
|
-
self.class.mass(@radius_m, @width_m, @density)
|
138
|
-
end
|
139
|
-
|
140
|
-
# in m^3
|
141
|
-
def volume
|
142
|
-
self.class.volume(@radius_m, @width_m)
|
143
|
-
end
|
144
|
-
|
145
|
-
# in L
|
146
|
-
def volume_l
|
147
|
-
self.class.volume_l(@radius_m, @width_m)
|
148
|
-
end
|
149
|
-
|
150
|
-
def rotational_inertia
|
151
|
-
self.class.rotational_inertia(@radius_m, self.mass)
|
152
|
-
end
|
153
|
-
alias_method(:moment_of_inertia, :rotational_inertia)
|
154
|
-
|
155
|
-
def traction(nf, static: true)
|
156
|
-
self.class.traction(nf, static ? @mu_s : @mu_k)
|
157
|
-
end
|
158
|
-
|
159
|
-
def force(axle_torque)
|
160
|
-
self.class.force(axle_torque, @radius_m)
|
161
|
-
end
|
162
|
-
|
163
|
-
# how much torque to accelerate rotational inertia at alpha
|
164
|
-
def inertial_torque(alpha)
|
165
|
-
alpha * self.rotational_inertia
|
166
|
-
end
|
167
|
-
|
168
|
-
# this doesn't take inertial losses or internal frictional losses
|
169
|
-
# into account. input torque required to saturate traction will be
|
170
|
-
# higher than what this method returns
|
171
|
-
def tractable_torque(nf, static: true)
|
172
|
-
traction(nf, static: static) * @radius_m
|
173
|
-
end
|
174
|
-
|
175
|
-
# inertial loss in terms of axle torque when used as a drive wheel
|
176
|
-
def inertial_loss(axle_torque, total_driven_mass)
|
177
|
-
drive_force = self.force(axle_torque)
|
178
|
-
force_loss = 0
|
179
|
-
# The force loss depends on the acceleration, but the acceleration
|
180
|
-
# depends on the force loss. Converge the value via 5 round trips.
|
181
|
-
# This is a rough way to compute an integral and should be accurate
|
182
|
-
# to 8+ digits.
|
183
|
-
5.times {
|
184
|
-
acc = DrivingPhysics.acc(drive_force - force_loss, total_driven_mass)
|
185
|
-
alpha = acc / @radius_m
|
186
|
-
force_loss = self.inertial_torque(alpha) / @radius_m
|
187
|
-
}
|
188
|
-
force_loss * @radius_m
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|