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.
@@ -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