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
data/test/car.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'driving_physics/car'
|
3
|
+
|
4
|
+
C = DrivingPhysics::Car
|
5
|
+
|
6
|
+
describe C do
|
7
|
+
before do
|
8
|
+
@env = DrivingPhysics::Environment.new
|
9
|
+
@c = C.new(@env)
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_moving
|
13
|
+
@c.controls.brake_pedal = 0.0
|
14
|
+
@c.controls.drive_pedal = 1.0
|
15
|
+
@c.add_fuel 10
|
16
|
+
50.times { @c.tick! }
|
17
|
+
expect(@c.condition.speed).must_be :>, 0.0
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
it "initializes" do
|
22
|
+
expect(@c).must_be_instance_of C
|
23
|
+
end
|
24
|
+
|
25
|
+
it "has a string representation" do
|
26
|
+
str = @c.to_s
|
27
|
+
expect(str).must_be_instance_of String
|
28
|
+
expect(str.length).must_be(:>, 5)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "adds fuel and reports overflow" do
|
32
|
+
expect(@c.condition.fuel).must_equal 0.0
|
33
|
+
@c.add_fuel 10.0
|
34
|
+
expect(@c.condition.fuel).must_equal 10.0
|
35
|
+
overflow = @c.add_fuel @c.fuel_capacity
|
36
|
+
expect(@c.condition.fuel).must_equal @c.fuel_capacity
|
37
|
+
expect(overflow).must_equal 10.0
|
38
|
+
end
|
39
|
+
|
40
|
+
it "varies drive_force based on drive_pedal and available fuel" do
|
41
|
+
expect(@c.drive_force).must_equal 0.0 # no pedal
|
42
|
+
@c.controls.drive_pedal = 1.0
|
43
|
+
expect(@c.drive_force).must_equal 0.0 # no fuel
|
44
|
+
@c.add_fuel 10
|
45
|
+
|
46
|
+
expect(@c.drive_force).must_equal @c.max_drive_force # vroom!
|
47
|
+
end
|
48
|
+
|
49
|
+
it "has a drive vector in direction of @dir" do
|
50
|
+
@c.add_fuel 10
|
51
|
+
@c.controls.drive_pedal = 1.0
|
52
|
+
dv = @c.drive_force_vector
|
53
|
+
expect(dv).must_be_instance_of Vector
|
54
|
+
dvn = dv.normalize
|
55
|
+
[0,1].each { |dim|
|
56
|
+
expect(dvn[dim]).must_be_within_epsilon @c.condition.dir[dim]
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
it "varies brake_force based on brake_pedal" do
|
61
|
+
expect(@c.brake_force).must_equal 0.0 # no pedal
|
62
|
+
@c.controls.brake_pedal = 1.0
|
63
|
+
expect(@c.brake_force).must_equal @c.max_brake_force
|
64
|
+
end
|
65
|
+
|
66
|
+
it "has a brake vector opposing movement or @dir" do
|
67
|
+
# hmm, no good way to go in reverse
|
68
|
+
# just test against forward movement for now
|
69
|
+
@c.controls.brake_pedal = 1.0
|
70
|
+
bv = @c.brake_force_vector
|
71
|
+
expect(bv).must_be_instance_of Vector
|
72
|
+
bvn = bv.normalize
|
73
|
+
[0,1].each { |dim|
|
74
|
+
expect(bvn[dim]).must_be_within_epsilon @c.condition.dir[dim] * -1
|
75
|
+
}
|
76
|
+
|
77
|
+
get_moving
|
78
|
+
|
79
|
+
@c.controls.drive_pedal = 0.0
|
80
|
+
@c.controls.brake_pedal = 1.0
|
81
|
+
bdir = @c.brake_force_vector.normalize
|
82
|
+
vdir = @c.condition.vel.normalize
|
83
|
+
[0,1].each { |dim|
|
84
|
+
expect(bdir[dim]).must_be_within_epsilon vdir[dim] * -1
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
it "tracks the mass of remaining fuel" do
|
89
|
+
expect(@c.fuel_mass).must_equal 0.0
|
90
|
+
@c.add_fuel 10
|
91
|
+
expect(@c.fuel_mass).must_be_within_epsilon 7.1
|
92
|
+
end
|
93
|
+
|
94
|
+
it "tracks total_mass including fuel and driver" do
|
95
|
+
expect(@c.total_mass).must_equal @c.mass + @c.driver_mass
|
96
|
+
@c.add_fuel 10
|
97
|
+
expect(@c.total_mass).must_equal @c.mass + @c.fuel_mass + @c.driver_mass
|
98
|
+
end
|
99
|
+
|
100
|
+
it "computes the total weight based on G" do
|
101
|
+
expect(@c.weight).must_be_within_epsilon 10535.0
|
102
|
+
end
|
103
|
+
|
104
|
+
it "computes resistance forces based on instance variables" do
|
105
|
+
air = @c.air_resistance
|
106
|
+
expect(air).must_be_kind_of Vector
|
107
|
+
expect(air.magnitude).must_equal 0.0
|
108
|
+
|
109
|
+
rot = @c.rotational_resistance
|
110
|
+
expect(rot).must_be_kind_of Vector
|
111
|
+
expect(rot.magnitude).must_equal 0.0
|
112
|
+
|
113
|
+
roll = @c.rolling_resistance
|
114
|
+
expect(roll).must_be_kind_of Vector
|
115
|
+
expect(roll.magnitude).must_be :>, 0
|
116
|
+
end
|
117
|
+
|
118
|
+
describe C::Condition do
|
119
|
+
before do
|
120
|
+
@cond = @c.condition
|
121
|
+
end
|
122
|
+
|
123
|
+
it "intializes" do
|
124
|
+
expect(@cond).must_be_kind_of C::Condition
|
125
|
+
end
|
126
|
+
|
127
|
+
it "has a string representation" do
|
128
|
+
str = @cond.to_s
|
129
|
+
expect(str).must_be_kind_of String
|
130
|
+
expect(str.length).must_be :>, 5
|
131
|
+
end
|
132
|
+
|
133
|
+
it "has a lateral direction clockwise from @dir" do
|
134
|
+
lat = @cond.lat_dir
|
135
|
+
expect(lat).must_be_kind_of Vector
|
136
|
+
expect(lat.magnitude).must_be_within_epsilon 1.0
|
137
|
+
expect(lat.independent?(@cond.dir)).must_equal true
|
138
|
+
end
|
139
|
+
|
140
|
+
it "has a movement_dir based on velocity, or @dir when stopped" do
|
141
|
+
md = @cond.movement_dir
|
142
|
+
expect(md).must_be_kind_of Vector
|
143
|
+
expect(md.magnitude).must_be_within_epsilon 1.0
|
144
|
+
expect(md).must_equal @cond.dir
|
145
|
+
|
146
|
+
get_moving
|
147
|
+
md = @cond.movement_dir
|
148
|
+
expect(md).must_be_kind_of Vector
|
149
|
+
expect(md.magnitude).must_be_within_epsilon 1.0
|
150
|
+
vd = @cond.vel.normalize
|
151
|
+
[0,1].each { |dim|
|
152
|
+
expect(md[dim]).must_be_within_epsilon vd[dim]
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'driving_physics'
|
3
|
+
|
4
|
+
describe DrivingPhysics do
|
5
|
+
it "displays elapsed ms in a friendly form" do
|
6
|
+
expect(DrivingPhysics.elapsed_display 12572358).must_equal "03:29:32.358"
|
7
|
+
end
|
8
|
+
|
9
|
+
it "calculates kph from m/s" do
|
10
|
+
expect(DrivingPhysics.kph 23.2).must_equal 83.52
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "Scalar Physics" do
|
14
|
+
# these functions also work with vectors
|
15
|
+
it "uses F=ma to calculate acceleration given force and mass" do
|
16
|
+
expect(DrivingPhysics.acc 7000, 1000).must_equal 7.0
|
17
|
+
end
|
18
|
+
|
19
|
+
it "calculates a new velocity given acceleration" do
|
20
|
+
expect(DrivingPhysics.vel 0.0, 7.0).must_be_within_epsilon 0.007
|
21
|
+
expect(DrivingPhysics.vel 1.1, 7.1).must_be_within_epsilon 1.1071
|
22
|
+
end
|
23
|
+
|
24
|
+
it "calculates a new position given velocity" do
|
25
|
+
expect(DrivingPhysics.pos 0.0, 3.0).must_be_within_epsilon 0.003
|
26
|
+
expect(DrivingPhysics.pos 2.2, 3.5).must_be_within_epsilon 2.2035
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'driving_physics/scalar_force'
|
3
|
+
|
4
|
+
include DrivingPhysics
|
5
|
+
|
6
|
+
describe ScalarForce do
|
7
|
+
# i.e. multiply this number times speed^2 to approximate drag force
|
8
|
+
it "calculates a reasonable drag constant" do
|
9
|
+
expect(ScalarForce.air_resistance 1).must_be_within_epsilon DRAG
|
10
|
+
end
|
11
|
+
|
12
|
+
# ROT_COF's value is from observing that rotational resistance
|
13
|
+
# matches air resistance at roughly 30 m/s in street cars
|
14
|
+
it "approximates a reasonable rotational resistance constant" do
|
15
|
+
expect(30 * ScalarForce.air_resistance(1)).must_be_within_epsilon ROT_COF
|
16
|
+
end
|
17
|
+
|
18
|
+
it "approximates a positive drag force" do
|
19
|
+
expect(ScalarForce.air_resistance 30).must_be_within_epsilon 383.13
|
20
|
+
end
|
21
|
+
|
22
|
+
it "approximates a positive rotational resistance force" do
|
23
|
+
expect(ScalarForce.rotational_resistance 30).must_be_within_epsilon 383.13
|
24
|
+
end
|
25
|
+
|
26
|
+
it "approximates a positive rolling resistance force" do
|
27
|
+
nf = 1000 * G
|
28
|
+
expect(ScalarForce.rolling_resistance nf).must_be_within_epsilon 98.0
|
29
|
+
end
|
30
|
+
end
|
data/test/tire.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'driving_physics/tire'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
include DrivingPhysics
|
5
|
+
|
6
|
+
describe Tire do
|
7
|
+
TP = Tire::TemperatureProfile
|
8
|
+
|
9
|
+
describe TP do
|
10
|
+
before do
|
11
|
+
@tp = TP.new
|
12
|
+
end
|
13
|
+
|
14
|
+
it "initializes with two same-sized arrays" do
|
15
|
+
expect(@tp).must_be_kind_of TP
|
16
|
+
expect(TP.new([0,1,2,3], [0,1.0,0.7,0.4])).wont_be_nil
|
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
|
21
|
+
end
|
22
|
+
|
23
|
+
it "determines a grip number from a temp number" do
|
24
|
+
{ -500 => TP::MIN_GRIP,
|
25
|
+
-100 => 0.1,
|
26
|
+
-0.0001 => 0.1,
|
27
|
+
0.0 => 0.5
|
28
|
+
}.each { |temp, gf| expect(@tp.grip_factor(temp)).must_equal gf }
|
29
|
+
end
|
30
|
+
|
31
|
+
it "has a critical_temp above the temp for 100%" do
|
32
|
+
expect(@tp.critical_temp).must_be(:>, 90)
|
33
|
+
expect(@tp.critical_temp).must_equal 105
|
34
|
+
end
|
35
|
+
|
36
|
+
it "has a map that increases to 100% and decreases below 80%" do
|
37
|
+
expect {
|
38
|
+
TP.new([0,1,2,3,4], [0.0,0.1,0.2,0.3,0.4])
|
39
|
+
}.must_raise TP::Error
|
40
|
+
|
41
|
+
expect {
|
42
|
+
TP.new([0,1,2,3,4], [0.0, 1.0, 0.99, 0.98, 0.97])
|
43
|
+
}.must_raise TP::Error
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
before do
|
48
|
+
@t = Tire.new
|
49
|
+
end
|
50
|
+
|
51
|
+
it "initializes with default values without a block" do
|
52
|
+
expect(@t).must_be_kind_of Tire
|
53
|
+
end
|
54
|
+
|
55
|
+
it "accepts a block to initialize with custom values" do
|
56
|
+
t = Tire.new { |x|
|
57
|
+
x.tread_mm = 9999
|
58
|
+
x.g_factor = -0.1234
|
59
|
+
}
|
60
|
+
|
61
|
+
expect(t.tread_mm).must_equal 9999
|
62
|
+
expect(t.g_factor).must_equal(-0.1234)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "knows when the tread is gone" do
|
66
|
+
expect(@t.tread_left?).must_equal true
|
67
|
+
|
68
|
+
@t.condition.tread_mm = 0.00001
|
69
|
+
expect(@t.tread_left?).must_equal true
|
70
|
+
|
71
|
+
@t.condition.tread_mm = 0.0
|
72
|
+
expect(@t.tread_left?).must_equal false
|
73
|
+
end
|
74
|
+
|
75
|
+
it "has 50% grip when down to the cords" do
|
76
|
+
expect(@t.tread_factor).must_equal 1.0
|
77
|
+
|
78
|
+
@t.condition.tread_mm = 0.0
|
79
|
+
expect(@t.tread_factor).must_be_within_epsilon 0.5
|
80
|
+
end
|
81
|
+
|
82
|
+
it "has less than 10% tread factor when the cords start to wear" do
|
83
|
+
expect(@t.tread_factor).must_equal 1.0
|
84
|
+
|
85
|
+
@t.condition.tread_mm = 5.0
|
86
|
+
expect(@t.tread_factor).must_equal 1.0
|
87
|
+
|
88
|
+
@t.condition.tread_mm = 0.0
|
89
|
+
expect(@t.tread_factor).must_be_within_epsilon 0.5
|
90
|
+
|
91
|
+
@t.condition.cords_mm = 0.9
|
92
|
+
expect(@t.tread_factor).must_be_within_epsilon 0.45
|
93
|
+
end
|
94
|
+
|
95
|
+
it "has decreasing heat cycle factor" do
|
96
|
+
expect(@t.condition.heat_cycles).must_equal 0
|
97
|
+
expect(@t.heat_cycle_factor).must_equal 1.0
|
98
|
+
|
99
|
+
@t.condition.heat_cycles = 20
|
100
|
+
expect(@t.heat_cycle_factor).must_be(:<, 1.0)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "has a temp factor according to temperature profile" do
|
104
|
+
expect(@t.condition.temp_c).must_equal 25
|
105
|
+
expect(@t.temp_factor).must_equal 0.75
|
106
|
+
|
107
|
+
@t.condition.temp_c = 90
|
108
|
+
expect(@t.temp_factor).must_equal 1.0
|
109
|
+
end
|
110
|
+
|
111
|
+
it "incorporates temp, heat_cycles, and tread depth into available grip" do
|
112
|
+
@t.condition.temp_c = 60
|
113
|
+
expect(@t.temp_factor).must_be_within_epsilon 0.8
|
114
|
+
|
115
|
+
@t.condition.heat_cycles = 10
|
116
|
+
expect(@t.heat_cycle_factor).must_be_within_epsilon 0.96
|
117
|
+
|
118
|
+
@t.condition.tread_mm = 5.0
|
119
|
+
expect(@t.tread_factor).must_equal 1.0
|
120
|
+
expect(@t.max_g).must_be_within_epsilon 0.768
|
121
|
+
|
122
|
+
@t.condition.tread_mm = 0.0
|
123
|
+
expect(@t.max_g).must_be_within_epsilon 0.384
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'driving_physics/vector_force'
|
3
|
+
|
4
|
+
include DrivingPhysics
|
5
|
+
|
6
|
+
describe VectorForce do
|
7
|
+
before do
|
8
|
+
@drive_force = Vector[7000.0, 0.0]
|
9
|
+
@v = Vector[3.0, 0]
|
10
|
+
@mass = 1000
|
11
|
+
@weight = @mass * G
|
12
|
+
end
|
13
|
+
|
14
|
+
it "generates uniformly random numbers centered on zero" do
|
15
|
+
hsh = {}
|
16
|
+
110_000.times {
|
17
|
+
num = DrivingPhysics.random_centered_zero(5)
|
18
|
+
hsh[num] ||= 0
|
19
|
+
hsh[num] += 1
|
20
|
+
}
|
21
|
+
# note, this will fail occasionally due to chance
|
22
|
+
hsh.values.each { |count|
|
23
|
+
expect(count).must_be(:>=, 9000)
|
24
|
+
expect(count).must_be(:<=, 11000)
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
it "coerces magnitude=0 to magnitude=1" do
|
29
|
+
a = Array.new(999) { DrivingPhysics.random_centered_zero(0) }
|
30
|
+
expect(a.all? { |i| i == 0 }).must_equal false
|
31
|
+
end
|
32
|
+
|
33
|
+
it "generates a random unit vector" do
|
34
|
+
low_res = DrivingPhysics.random_unit_vector(2, resolution: 1)
|
35
|
+
if low_res[0] == 0.0
|
36
|
+
expect(low_res[1].abs).must_equal 1.0
|
37
|
+
elsif low_res[0].abs == 1.0
|
38
|
+
expect(low_res[1]).must_equal 0.0
|
39
|
+
elsif low_res[0].abs.round(3) == 0.707
|
40
|
+
expect(low_res[1].abs.round(3)) == 0.707
|
41
|
+
else
|
42
|
+
p low_res
|
43
|
+
raise "unexpected"
|
44
|
+
end
|
45
|
+
|
46
|
+
9.times {
|
47
|
+
high_res = DrivingPhysics.random_unit_vector(3, resolution: 9)
|
48
|
+
expect(high_res.magnitude).must_be_within_epsilon 1.0
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
it "calculates air resistance as the square of velocity" do
|
53
|
+
df = VectorForce.air_resistance(@v,
|
54
|
+
frontal_area: 3,
|
55
|
+
drag_cof: 0.1,
|
56
|
+
air_density: 0.5)
|
57
|
+
|
58
|
+
# double the velocity, drag force goes up by 4
|
59
|
+
df2 = VectorForce.air_resistance(@v * 2,
|
60
|
+
frontal_area: 3,
|
61
|
+
drag_cof: 0.1,
|
62
|
+
air_density: 0.5)
|
63
|
+
|
64
|
+
expect(df2).must_equal df * 4
|
65
|
+
end
|
66
|
+
|
67
|
+
it "calculates the rolling resistance as a function of the normal force" do
|
68
|
+
rr = VectorForce.rolling_resistance(@weight, dir: @v)
|
69
|
+
|
70
|
+
# double the normal force, rolling resistance goes up by 2 (linear)
|
71
|
+
rr2 = VectorForce.rolling_resistance(@weight * 2, dir: @v)
|
72
|
+
|
73
|
+
expect(rr2).must_equal rr * 2
|
74
|
+
end
|
75
|
+
|
76
|
+
it "calculates the rotational resistance as a function of velocity" do
|
77
|
+
rr = VectorForce.rotational_resistance(@v)
|
78
|
+
rr2 = VectorForce.rotational_resistance(@v * 2)
|
79
|
+
expect(rr2).must_equal rr * 2
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sums resistance forces" do
|
83
|
+
rf = VectorForce.all_resistance(@v, dir: @v, nf_mag: @weight)
|
84
|
+
# opposite direction
|
85
|
+
expect(rf.normalize).must_equal(-1 * @v.normalize)
|
86
|
+
|
87
|
+
# smaller magnitude
|
88
|
+
expect(rf.magnitude).must_be(:<, @drive_force.magnitude)
|
89
|
+
end
|
90
|
+
end
|
data/test/wheel.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'driving_physics/wheel'
|
3
|
+
|
4
|
+
W = DrivingPhysics::Wheel
|
5
|
+
|
6
|
+
describe W do
|
7
|
+
describe "Wheel.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 = W.traction(scalar_nf, cof)
|
12
|
+
expect(scalar_t).must_equal 10780.0
|
13
|
+
|
14
|
+
vector_nf = Vector[9800, 0]
|
15
|
+
vector_t = W.traction(vector_nf, cof)
|
16
|
+
expect(vector_t).must_equal Vector[10780.0, 0.0]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "Wheel.volume" do
|
21
|
+
it "calculates the volume (m^3) of disk given radius and width" do
|
22
|
+
cubic_m = W.volume(1.0, 1.0)
|
23
|
+
expect(cubic_m).must_equal Math::PI
|
24
|
+
|
25
|
+
cubic_m = W.volume(0.35, 0.2)
|
26
|
+
expect(cubic_m).must_be_within_epsilon 0.076969
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "Wheel.volume_l" do
|
31
|
+
it "calculates the volume (L) of a disk given radius and width" do
|
32
|
+
liters = W.volume_l(1.0, 1.0)
|
33
|
+
expect(liters).must_equal Math::PI * 1000
|
34
|
+
|
35
|
+
liters = W.volume_l(0.35, 0.2)
|
36
|
+
expect(liters).must_be_within_epsilon 76.96902
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "Wheel.density" do
|
41
|
+
it "calculates the density (kg/L) given mass and volume" do
|
42
|
+
expect(W.density(25.0, 25.0)).must_equal 1.0
|
43
|
+
expect(W.density(50.0, 25.0)).must_equal 2.0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "Wheel.mass" do
|
48
|
+
it "calculates the mass (kg) of a disk given radius, width, and density" do
|
49
|
+
expect(W.mass(0.35, 0.2, W::DENSITY)).must_be_within_epsilon 25.015
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "Wheel.rotational_inertia" do
|
54
|
+
it "calculates rotational inertia for a disk given radius and mass" do
|
55
|
+
expect(W.rotational_inertia(0.35, 25.0)).must_be_within_epsilon 1.53125
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "Wheel.alpha" do
|
60
|
+
it "calculates angular acceleration from torque and inertia" do
|
61
|
+
scalar_torque = 1000
|
62
|
+
inertia = W.rotational_inertia(0.35, 25.0)
|
63
|
+
expect(W.alpha scalar_torque, inertia).must_be_within_epsilon 653.061
|
64
|
+
|
65
|
+
vector_torque = Vector[0, 0, 1000]
|
66
|
+
vector_alpha = W.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
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "Wheel.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 = W.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
|
83
|
+
|
84
|
+
describe "Wheel.force_vector" do
|
85
|
+
it "calculates a (3D) force given 3D torque and 2D radius" do
|
86
|
+
# let's invert the Wheel.torque_vector case from above:
|
87
|
+
torque = Vector[0, 0, 5000]
|
88
|
+
radius = Vector[0, 5]
|
89
|
+
force = W.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 = W.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
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "instance methods" do
|
107
|
+
before do
|
108
|
+
@env = DrivingPhysics::Environment.new
|
109
|
+
@w = W.new(@env)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "initializes" do
|
113
|
+
expect(@w).must_be_instance_of W
|
114
|
+
expect(@w.density).must_equal W::DENSITY # sanity check
|
115
|
+
expect(@w.mass).must_be_within_epsilon 25.01
|
116
|
+
|
117
|
+
with_mass = W.new(@env, mass: 99.01)
|
118
|
+
expect(with_mass.mass).must_equal 99.01
|
119
|
+
expect(with_mass.density).wont_equal W::DENSITY
|
120
|
+
end
|
121
|
+
|
122
|
+
it "has a string representation" do
|
123
|
+
str = @w.to_s
|
124
|
+
expect(str).must_be_instance_of String
|
125
|
+
expect(str.length).must_be(:>, 5)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "loses radius as it wears" do
|
129
|
+
expect(@w.radius).must_equal 350.0
|
130
|
+
@w.wear!(50)
|
131
|
+
expect(@w.radius).must_equal 300.0
|
132
|
+
end
|
133
|
+
|
134
|
+
it "calculates mass from current radius" do
|
135
|
+
expect(@w.mass).must_be_within_epsilon 25.01
|
136
|
+
@w.wear!(50)
|
137
|
+
expect(@w.mass).must_be_within_epsilon 18.378
|
138
|
+
end
|
139
|
+
|
140
|
+
it "has volume" do
|
141
|
+
expect(@w.volume).must_be_within_epsilon 0.07697
|
142
|
+
expect(@w.volume_l).must_be_within_epsilon 76.96902
|
143
|
+
end
|
144
|
+
|
145
|
+
it "has inertia" do
|
146
|
+
expect(@w.rotational_inertia).must_be_within_epsilon 1.5321
|
147
|
+
end
|
148
|
+
|
149
|
+
it "has traction force based on normal force" do
|
150
|
+
scalar_nf = 9800
|
151
|
+
expect(@w.traction scalar_nf).must_equal 10780.0
|
152
|
+
expect(@w.traction scalar_nf, static: false).must_equal 6860.0
|
153
|
+
|
154
|
+
vector_nf = Vector[9800, 0]
|
155
|
+
expect(@w.traction vector_nf).must_equal Vector[10780.0, 0.0]
|
156
|
+
expect(@w.traction vector_nf, static: false).
|
157
|
+
must_equal Vector[6860.0, 0.0]
|
158
|
+
end
|
159
|
+
|
160
|
+
it "determines (e.g. thrust) force based on axle torque" do
|
161
|
+
expect(@w.force 1000).must_be_within_epsilon 2857.143
|
162
|
+
@w.wear! 50
|
163
|
+
expect(@w.force 1000).must_be_within_epsilon 3333.333
|
164
|
+
end
|
165
|
+
|
166
|
+
it "determines tractable torque" do
|
167
|
+
scalar_nf = 9800
|
168
|
+
expect(@w.tractable_torque scalar_nf).must_be_within_epsilon 3773.0
|
169
|
+
kin_tq = @w.tractable_torque scalar_nf, static: false
|
170
|
+
expect(kin_tq).must_be_within_epsilon 2401.0
|
171
|
+
|
172
|
+
# not sure about how torque vectors work, but the "math" "works":
|
173
|
+
vector_nf = Vector[9800, 0]
|
174
|
+
expect(@w.tractable_torque(vector_nf)[0]).must_be_within_epsilon 3773.0
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: driving_physics
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rick Hull
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 1980-01-01 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: WIP
|
14
|
+
email:
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- README.md
|
20
|
+
- Rakefile
|
21
|
+
- VERSION
|
22
|
+
- demo/car.rb
|
23
|
+
- demo/scalar_force.rb
|
24
|
+
- demo/tire.rb
|
25
|
+
- demo/vector_force.rb
|
26
|
+
- demo/wheel.rb
|
27
|
+
- driving_physics.gemspec
|
28
|
+
- lib/driving_physics.rb
|
29
|
+
- lib/driving_physics/car.rb
|
30
|
+
- lib/driving_physics/environment.rb
|
31
|
+
- lib/driving_physics/imperial.rb
|
32
|
+
- lib/driving_physics/scalar_force.rb
|
33
|
+
- lib/driving_physics/tire.rb
|
34
|
+
- lib/driving_physics/vector_force.rb
|
35
|
+
- lib/driving_physics/wheel.rb
|
36
|
+
- test/car.rb
|
37
|
+
- test/driving_physics.rb
|
38
|
+
- test/scalar_force.rb
|
39
|
+
- test/tire.rb
|
40
|
+
- test/vector_force.rb
|
41
|
+
- test/wheel.rb
|
42
|
+
homepage: https://github.com/rickhull/driving_physics
|
43
|
+
licenses:
|
44
|
+
- LGPL-3.0
|
45
|
+
metadata: {}
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubygems_version: 3.2.26
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: WIP
|
65
|
+
test_files: []
|