driving_physics 0.0.0.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 +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: []
|