driving_physics 0.0.0.2 → 0.0.1.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 +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
|