larb 0.1.0 → 1.0.1
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/CHANGELOG.md +13 -1
- data/README.md +63 -1
- data/ext/larb/color.c +449 -0
- data/ext/larb/color.h +35 -0
- data/ext/larb/extconf.rb +11 -0
- data/ext/larb/larb.c +27 -0
- data/ext/larb/larb.h +31 -0
- data/ext/larb/mat2.c +303 -0
- data/ext/larb/mat2.h +30 -0
- data/ext/larb/mat2d.c +397 -0
- data/ext/larb/mat2d.h +35 -0
- data/ext/larb/mat3.c +455 -0
- data/ext/larb/mat3.h +33 -0
- data/ext/larb/mat4.c +651 -0
- data/ext/larb/mat4.h +31 -0
- data/ext/larb/matrix_utils.h +97 -0
- data/ext/larb/quat.c +567 -0
- data/ext/larb/quat.h +39 -0
- data/ext/larb/quat2.c +511 -0
- data/ext/larb/quat2.h +39 -0
- data/ext/larb/vec2.c +365 -0
- data/ext/larb/vec2.h +43 -0
- data/ext/larb/vec3.c +538 -0
- data/ext/larb/vec3.h +52 -0
- data/ext/larb/vec4.c +361 -0
- data/ext/larb/vec4.h +38 -0
- data/lib/larb/version.rb +5 -0
- data/lib/larb.rb +2 -14
- data/test/larb/color_test.rb +299 -0
- data/test/larb/mat2_test.rb +144 -0
- data/test/larb/mat2d_test.rb +197 -0
- data/test/larb/mat3_test.rb +147 -0
- data/test/larb/mat4_test.rb +347 -0
- data/test/larb/matrix_test.rb +135 -0
- data/test/larb/native_value_test.rb +157 -0
- data/test/larb/quat2_test.rb +254 -0
- data/test/larb/quat_test.rb +284 -0
- data/test/larb/vec2_test.rb +274 -0
- data/test/larb/vec3_test.rb +373 -0
- data/test/larb/vec4_test.rb +196 -0
- data/test/test_helper.rb +4 -0
- metadata +60 -18
- data/Rakefile +0 -11
- data/lib/larb/color.rb +0 -148
- data/lib/larb/mat2.rb +0 -119
- data/lib/larb/mat2d.rb +0 -180
- data/lib/larb/mat3.rb +0 -238
- data/lib/larb/mat4.rb +0 -329
- data/lib/larb/quat.rb +0 -238
- data/lib/larb/quat2.rb +0 -193
- data/lib/larb/vec2.rb +0 -150
- data/lib/larb/vec3.rb +0 -218
- data/lib/larb/vec4.rb +0 -125
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../test_helper"
|
|
4
|
+
|
|
5
|
+
class Mat3Test < Test::Unit::TestCase
|
|
6
|
+
def test_new_identity_by_default
|
|
7
|
+
m = Larb::Mat3.new
|
|
8
|
+
assert_equal 1.0, m[0]
|
|
9
|
+
assert_equal 1.0, m[4]
|
|
10
|
+
assert_equal 1.0, m[8]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_new_with_data
|
|
14
|
+
data = Array.new(9) { |i| i.to_f }
|
|
15
|
+
m = Larb::Mat3.new(data)
|
|
16
|
+
assert_equal data, m.data
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_identity
|
|
20
|
+
m = Larb::Mat3.identity
|
|
21
|
+
assert_equal 1.0, m[0]
|
|
22
|
+
assert_equal 1.0, m[4]
|
|
23
|
+
assert_equal 1.0, m[8]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_zero
|
|
27
|
+
m = Larb::Mat3.zero
|
|
28
|
+
9.times { |i| assert_equal 0.0, m[i] }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_from_mat4
|
|
32
|
+
m4 = Larb::Mat4.identity
|
|
33
|
+
m3 = Larb::Mat3.from_mat4(m4)
|
|
34
|
+
assert_equal 1.0, m3[0]
|
|
35
|
+
assert_equal 1.0, m3[4]
|
|
36
|
+
assert_equal 1.0, m3[8]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_from_quaternion
|
|
40
|
+
q = Larb::Quat.from_axis_angle(Larb::Vec3.new(0, 0, 1), Math::PI / 2)
|
|
41
|
+
m = Larb::Mat3.from_quaternion(q)
|
|
42
|
+
assert_in_delta 0.0, m[0], 1e-10
|
|
43
|
+
assert_in_delta 1.0, m[1], 1e-10
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_translation
|
|
47
|
+
m = Larb::Mat3.translation(5, 10)
|
|
48
|
+
assert_equal 5.0, m[6]
|
|
49
|
+
assert_equal 10.0, m[7]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_rotation
|
|
53
|
+
m = Larb::Mat3.rotation(Math::PI / 2)
|
|
54
|
+
assert_in_delta 0.0, m[0], 1e-10
|
|
55
|
+
assert_in_delta 1.0, m[1], 1e-10
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_scaling
|
|
59
|
+
m = Larb::Mat3.scaling(2, 3)
|
|
60
|
+
assert_equal 2.0, m[0]
|
|
61
|
+
assert_equal 3.0, m[4]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_multiply_mat3
|
|
65
|
+
m1 = Larb::Mat3.identity
|
|
66
|
+
m2 = Larb::Mat3.translation(1, 2)
|
|
67
|
+
result = m1 * m2
|
|
68
|
+
assert_equal 1.0, result[6]
|
|
69
|
+
assert_equal 2.0, result[7]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_multiply_vec3
|
|
73
|
+
m = Larb::Mat3.scaling(2, 3)
|
|
74
|
+
v = Larb::Vec3.new(1, 1, 1)
|
|
75
|
+
result = m * v
|
|
76
|
+
assert_equal 2.0, result.x
|
|
77
|
+
assert_equal 3.0, result.y
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_determinant
|
|
81
|
+
m = Larb::Mat3.identity
|
|
82
|
+
assert_equal 1.0, m.determinant
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_determinant_scaling
|
|
86
|
+
m = Larb::Mat3.scaling(2, 3)
|
|
87
|
+
assert_equal 6.0, m.determinant
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_inverse
|
|
91
|
+
m = Larb::Mat3.scaling(2, 4)
|
|
92
|
+
inv = m.inverse
|
|
93
|
+
result = m * inv
|
|
94
|
+
assert result.near?(Larb::Mat3.identity)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_inverse_singular
|
|
98
|
+
m = Larb::Mat3.zero
|
|
99
|
+
assert_raise_message("Matrix is not invertible") { m.inverse }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_transpose
|
|
103
|
+
data = Array.new(9) { |i| i.to_f }
|
|
104
|
+
m = Larb::Mat3.new(data)
|
|
105
|
+
result = m.transpose
|
|
106
|
+
assert_equal 3.0, result[1]
|
|
107
|
+
assert_equal 1.0, result[3]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_translate
|
|
111
|
+
m = Larb::Mat3.identity
|
|
112
|
+
result = m.translate(5, 10)
|
|
113
|
+
assert_equal 5.0, result[6]
|
|
114
|
+
assert_equal 10.0, result[7]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_rotate
|
|
118
|
+
m = Larb::Mat3.identity
|
|
119
|
+
result = m.rotate(Math::PI / 2)
|
|
120
|
+
assert_in_delta 0.0, result[0], 1e-10
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_scale
|
|
124
|
+
m = Larb::Mat3.identity
|
|
125
|
+
result = m.scale(2, 3)
|
|
126
|
+
assert_equal 2.0, result[0]
|
|
127
|
+
assert_equal 3.0, result[4]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def test_equality
|
|
131
|
+
m1 = Larb::Mat3.identity
|
|
132
|
+
m2 = Larb::Mat3.identity
|
|
133
|
+
assert_equal m1, m2
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_near
|
|
137
|
+
m1 = Larb::Mat3.identity
|
|
138
|
+
m2 = Larb::Mat3.identity
|
|
139
|
+
m2[0] = 1.0000001
|
|
140
|
+
assert m1.near?(m2)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_inspect
|
|
144
|
+
m = Larb::Mat3.identity
|
|
145
|
+
assert_match(/Mat3/, m.inspect)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../test_helper"
|
|
4
|
+
|
|
5
|
+
class Mat4Test < Test::Unit::TestCase
|
|
6
|
+
def test_new_identity_by_default
|
|
7
|
+
m = Larb::Mat4.new
|
|
8
|
+
assert_equal 1.0, m[0]
|
|
9
|
+
assert_equal 1.0, m[5]
|
|
10
|
+
assert_equal 1.0, m[10]
|
|
11
|
+
assert_equal 1.0, m[15]
|
|
12
|
+
assert_equal 0.0, m[1]
|
|
13
|
+
assert_equal 0.0, m[4]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_new_with_data
|
|
17
|
+
data = Array.new(16) { |i| i.to_f }
|
|
18
|
+
m = Larb::Mat4.new(data)
|
|
19
|
+
assert_equal data, m.data
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_identity
|
|
23
|
+
m = Larb::Mat4.identity
|
|
24
|
+
assert_equal 1.0, m[0]
|
|
25
|
+
assert_equal 1.0, m[5]
|
|
26
|
+
assert_equal 1.0, m[10]
|
|
27
|
+
assert_equal 1.0, m[15]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_zero
|
|
31
|
+
m = Larb::Mat4.zero
|
|
32
|
+
16.times { |i| assert_equal 0.0, m[i] }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_translation
|
|
36
|
+
m = Larb::Mat4.translation(1, 2, 3)
|
|
37
|
+
assert_equal 1.0, m[12]
|
|
38
|
+
assert_equal 2.0, m[13]
|
|
39
|
+
assert_equal 3.0, m[14]
|
|
40
|
+
assert_equal 1.0, m[0]
|
|
41
|
+
assert_equal 1.0, m[5]
|
|
42
|
+
assert_equal 1.0, m[10]
|
|
43
|
+
assert_equal 1.0, m[15]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_scaling
|
|
47
|
+
m = Larb::Mat4.scaling(2, 3, 4)
|
|
48
|
+
assert_equal 2.0, m[0]
|
|
49
|
+
assert_equal 3.0, m[5]
|
|
50
|
+
assert_equal 4.0, m[10]
|
|
51
|
+
assert_equal 1.0, m[15]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_rotation_x
|
|
55
|
+
m = Larb::Mat4.rotation_x(Math::PI / 2)
|
|
56
|
+
assert_equal 1.0, m[0]
|
|
57
|
+
assert_in_delta 0.0, m[5], 1e-10
|
|
58
|
+
assert_in_delta 1.0, m[6], 1e-10
|
|
59
|
+
assert_in_delta(-1.0, m[9], 1e-10)
|
|
60
|
+
assert_in_delta 0.0, m[10], 1e-10
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_rotation_y
|
|
64
|
+
m = Larb::Mat4.rotation_y(Math::PI / 2)
|
|
65
|
+
assert_in_delta 0.0, m[0], 1e-10
|
|
66
|
+
assert_in_delta(-1.0, m[2], 1e-10)
|
|
67
|
+
assert_equal 1.0, m[5]
|
|
68
|
+
assert_in_delta 1.0, m[8], 1e-10
|
|
69
|
+
assert_in_delta 0.0, m[10], 1e-10
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_rotation_z
|
|
73
|
+
m = Larb::Mat4.rotation_z(Math::PI / 2)
|
|
74
|
+
assert_in_delta 0.0, m[0], 1e-10
|
|
75
|
+
assert_in_delta 1.0, m[1], 1e-10
|
|
76
|
+
assert_in_delta(-1.0, m[4], 1e-10)
|
|
77
|
+
assert_in_delta 0.0, m[5], 1e-10
|
|
78
|
+
assert_equal 1.0, m[10]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_rotation
|
|
82
|
+
m = Larb::Mat4.rotation(Larb::Vec3.new(0, 1, 0), Math::PI / 2)
|
|
83
|
+
assert_in_delta 0.0, m[0], 1e-10
|
|
84
|
+
assert_in_delta 1.0, m[8], 1e-10
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_look_at
|
|
88
|
+
eye = Larb::Vec3.new(0, 0, 5)
|
|
89
|
+
target = Larb::Vec3.new(0, 0, 0)
|
|
90
|
+
up = Larb::Vec3.new(0, 1, 0)
|
|
91
|
+
m = Larb::Mat4.look_at(eye, target, up)
|
|
92
|
+
assert_instance_of Larb::Mat4, m
|
|
93
|
+
assert_in_delta(-5.0, m[14], 1e-10)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_perspective
|
|
97
|
+
m = Larb::Mat4.perspective(Math::PI / 4, 16.0 / 9.0, 0.1, 100.0)
|
|
98
|
+
assert_instance_of Larb::Mat4, m
|
|
99
|
+
assert_equal(-1.0, m[11])
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_orthographic
|
|
103
|
+
m = Larb::Mat4.orthographic(-1, 1, -1, 1, 0.1, 100)
|
|
104
|
+
assert_instance_of Larb::Mat4, m
|
|
105
|
+
assert_equal 1.0, m[0]
|
|
106
|
+
assert_equal 1.0, m[5]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_index_access
|
|
110
|
+
m = Larb::Mat4.identity
|
|
111
|
+
assert_equal 1.0, m[0]
|
|
112
|
+
assert_equal 0.0, m[1]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_index_assignment
|
|
116
|
+
m = Larb::Mat4.identity
|
|
117
|
+
m[0] = 5.0
|
|
118
|
+
assert_equal 5.0, m[0]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_multiply_mat4
|
|
122
|
+
m1 = Larb::Mat4.translation(1, 0, 0)
|
|
123
|
+
m2 = Larb::Mat4.translation(0, 1, 0)
|
|
124
|
+
result = m1 * m2
|
|
125
|
+
assert_equal 1.0, result[12]
|
|
126
|
+
assert_equal 1.0, result[13]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_multiply_identity
|
|
130
|
+
m = Larb::Mat4.translation(1, 2, 3)
|
|
131
|
+
result = Larb::Mat4.identity * m
|
|
132
|
+
assert_equal m.data, result.data
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_multiply_vec4
|
|
136
|
+
m = Larb::Mat4.translation(1, 2, 3)
|
|
137
|
+
v = Larb::Vec4.new(0, 0, 0, 1)
|
|
138
|
+
result = m * v
|
|
139
|
+
assert_equal 1.0, result.x
|
|
140
|
+
assert_equal 2.0, result.y
|
|
141
|
+
assert_equal 3.0, result.z
|
|
142
|
+
assert_equal 1.0, result.w
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def test_multiply_vec3
|
|
146
|
+
m = Larb::Mat4.translation(1, 2, 3)
|
|
147
|
+
v = Larb::Vec3.new(0, 0, 0)
|
|
148
|
+
result = m * v
|
|
149
|
+
assert_equal 1.0, result.x
|
|
150
|
+
assert_equal 2.0, result.y
|
|
151
|
+
assert_equal 3.0, result.z
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def test_transpose
|
|
155
|
+
data = Array.new(16) { |i| i.to_f }
|
|
156
|
+
m = Larb::Mat4.new(data)
|
|
157
|
+
result = m.transpose
|
|
158
|
+
assert_equal 4.0, result[1]
|
|
159
|
+
assert_equal 1.0, result[4]
|
|
160
|
+
assert_equal 8.0, result[2]
|
|
161
|
+
assert_equal 2.0, result[8]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_inverse
|
|
165
|
+
m = Larb::Mat4.translation(1, 2, 3)
|
|
166
|
+
inv = m.inverse
|
|
167
|
+
result = m * inv
|
|
168
|
+
assert_in_delta 1.0, result[0], 1e-10
|
|
169
|
+
assert_in_delta 1.0, result[5], 1e-10
|
|
170
|
+
assert_in_delta 1.0, result[10], 1e-10
|
|
171
|
+
assert_in_delta 1.0, result[15], 1e-10
|
|
172
|
+
assert_in_delta 0.0, result[12], 1e-10
|
|
173
|
+
assert_in_delta 0.0, result[13], 1e-10
|
|
174
|
+
assert_in_delta 0.0, result[14], 1e-10
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def test_inverse_singular
|
|
178
|
+
m = Larb::Mat4.zero
|
|
179
|
+
assert_raise_message("Matrix is not invertible") { m.inverse }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def test_to_a
|
|
183
|
+
m = Larb::Mat4.identity
|
|
184
|
+
arr = m.to_a
|
|
185
|
+
assert_equal m.data, arr
|
|
186
|
+
arr[0] = 999
|
|
187
|
+
assert_equal 1.0, m.data[0]
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def test_determinant
|
|
191
|
+
m = Larb::Mat4.identity
|
|
192
|
+
assert_equal 1.0, m.determinant
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def test_determinant_zero
|
|
196
|
+
m = Larb::Mat4.zero
|
|
197
|
+
assert_equal 0.0, m.determinant
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def test_determinant_scaling
|
|
201
|
+
m = Larb::Mat4.scaling(2, 3, 4)
|
|
202
|
+
assert_equal 24.0, m.determinant
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def test_add
|
|
206
|
+
m1 = Larb::Mat4.identity
|
|
207
|
+
m2 = Larb::Mat4.identity
|
|
208
|
+
result = m1 + m2
|
|
209
|
+
assert_equal 2.0, result[0]
|
|
210
|
+
assert_equal 2.0, result[5]
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_subtract
|
|
214
|
+
m1 = Larb::Mat4.identity
|
|
215
|
+
m2 = Larb::Mat4.identity
|
|
216
|
+
result = m1 - m2
|
|
217
|
+
assert_equal 0.0, result[0]
|
|
218
|
+
assert_equal 0.0, result[5]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def test_equality
|
|
222
|
+
m1 = Larb::Mat4.identity
|
|
223
|
+
m2 = Larb::Mat4.identity
|
|
224
|
+
assert_equal m1, m2
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def test_inequality
|
|
228
|
+
m1 = Larb::Mat4.identity
|
|
229
|
+
m2 = Larb::Mat4.translation(1, 0, 0)
|
|
230
|
+
assert_not_equal m1, m2
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def test_near
|
|
234
|
+
m1 = Larb::Mat4.identity
|
|
235
|
+
m2 = Larb::Mat4.identity
|
|
236
|
+
m2[0] = 1.0000001
|
|
237
|
+
assert m1.near?(m2)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def test_extract_translation
|
|
241
|
+
m = Larb::Mat4.translation(1, 2, 3)
|
|
242
|
+
result = m.extract_translation
|
|
243
|
+
assert_equal Larb::Vec3.new(1, 2, 3), result
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def test_extract_scale
|
|
247
|
+
m = Larb::Mat4.scaling(2, 3, 4)
|
|
248
|
+
result = m.extract_scale
|
|
249
|
+
assert_in_delta 2.0, result.x, 1e-10
|
|
250
|
+
assert_in_delta 3.0, result.y, 1e-10
|
|
251
|
+
assert_in_delta 4.0, result.z, 1e-10
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def test_frustum
|
|
255
|
+
m = Larb::Mat4.frustum(-1, 1, -1, 1, 0.1, 100)
|
|
256
|
+
assert_instance_of Larb::Mat4, m
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def test_from_quaternion
|
|
260
|
+
q = Larb::Quat.from_axis_angle(Larb::Vec3.new(0, 1, 0), Math::PI / 2)
|
|
261
|
+
m = Larb::Mat4.from_quaternion(q)
|
|
262
|
+
assert_instance_of Larb::Mat4, m
|
|
263
|
+
assert_in_delta 0.0, m[0], 1e-10
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def test_inspect
|
|
267
|
+
m = Larb::Mat4.identity
|
|
268
|
+
assert_match(/Mat4/, m.inspect)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def test_trs_applies_scale_rotation_then_translation
|
|
272
|
+
matrix = Larb::Mat4.trs(
|
|
273
|
+
Larb::Vec3.new(1, 2, 3),
|
|
274
|
+
Larb::Quat.from_axis_angle(Larb::Vec3.back, Math::PI / 2),
|
|
275
|
+
Larb::Vec3.new(2, 3, 4)
|
|
276
|
+
)
|
|
277
|
+
assert_finite_close [1, 2, 3], matrix.extract_translation.to_a
|
|
278
|
+
assert_finite_close [-2, 4, 7, 1], (matrix * Larb::Vec4.new(1, 1, 1, 1)).to_a
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def test_decomposition_round_trips_signed_scales
|
|
282
|
+
rotations = [Larb::Quat.identity] + [Larb::Vec3.right, Larb::Vec3.up, Larb::Vec3.forward].map do |axis|
|
|
283
|
+
Larb::Quat.from_axis_angle(axis, Math::PI)
|
|
284
|
+
end
|
|
285
|
+
rotations << Larb::Quat.from_axis_angle(Larb::Vec3.new(1, 2, 3), 1.2)
|
|
286
|
+
[-1, 1].repeated_permutation(3) do |signs|
|
|
287
|
+
rotations.each do |rotation|
|
|
288
|
+
scale = Larb::Vec3.new(signs[0] * 2, signs[1] * 3, signs[2] * 4)
|
|
289
|
+
matrix = Larb::Mat4.trs(Larb::Vec3.new(1, 2, 3), rotation, scale)
|
|
290
|
+
extracted_rotation = matrix.extract_rotation
|
|
291
|
+
extracted_scale = matrix.extract_scale
|
|
292
|
+
assert extracted_rotation.to_a.all?(&:finite?)
|
|
293
|
+
assert_in_delta 1.0, extracted_rotation.length, 1e-12
|
|
294
|
+
assert_equal signs.inject(:*) < 0, extracted_scale.x < 0
|
|
295
|
+
rebuilt = Larb::Mat4.trs(matrix.extract_translation, extracted_rotation, extracted_scale)
|
|
296
|
+
assert_finite_close matrix.to_a, rebuilt.to_a
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def test_decomposition_handles_extreme_scales
|
|
302
|
+
[1e-200, 1e200].each do |magnitude|
|
|
303
|
+
matrix = Larb::Mat4.scaling(-magnitude, magnitude, magnitude)
|
|
304
|
+
scale = matrix.extract_scale
|
|
305
|
+
assert_finite_close [-1, 1, 1], scale.to_a.map { |value| value / magnitude }
|
|
306
|
+
assert_finite_close [0, 0, 0, 1], matrix.extract_rotation.to_a
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def test_decomposition_rejects_unsupported_matrices
|
|
311
|
+
shear = Larb::Mat4.identity
|
|
312
|
+
shear[4] = 0.5
|
|
313
|
+
nonfinite = Larb::Mat4.identity
|
|
314
|
+
nonfinite[0] = Float::NAN
|
|
315
|
+
matrices = [shear, nonfinite, Larb::Mat4.scaling(0, 1, 1),
|
|
316
|
+
Larb::Mat4.perspective(Math::PI / 4, 1, 0.1, 100)]
|
|
317
|
+
matrices.each do |matrix|
|
|
318
|
+
assert_raise(ArgumentError) { matrix.extract_rotation }
|
|
319
|
+
assert_raise(ArgumentError) { matrix.extract_scale }
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def test_look_at_rejects_degenerate_directions
|
|
324
|
+
origin = Larb::Vec3.zero
|
|
325
|
+
assert_raise(ArgumentError) { Larb::Mat4.look_at(origin, origin, Larb::Vec3.up) }
|
|
326
|
+
assert_raise(ArgumentError) { Larb::Mat4.look_at(origin, Larb::Vec3.forward, origin) }
|
|
327
|
+
assert_raise(ArgumentError) { Larb::Mat4.look_at(origin, Larb::Vec3.forward, Larb::Vec3.forward) }
|
|
328
|
+
assert_raise(ArgumentError) do
|
|
329
|
+
Larb::Mat4.look_at(origin, Larb::Vec3.new(Float::NAN, 0, 1), Larb::Vec3.up)
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def test_look_at_normalizes_extreme_directions
|
|
334
|
+
[1e-200, 1e200].each do |magnitude|
|
|
335
|
+
matrix = Larb::Mat4.look_at(Larb::Vec3.zero, Larb::Vec3.new(0, 0, -magnitude),
|
|
336
|
+
Larb::Vec3.new(0, magnitude, 0))
|
|
337
|
+
assert_finite_close Larb::Mat4.identity.to_a, matrix.to_a
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
private
|
|
342
|
+
|
|
343
|
+
def assert_finite_close(expected, actual)
|
|
344
|
+
assert actual.all?(&:finite?), actual.inspect
|
|
345
|
+
expected.zip(actual).each { |a, b| assert_in_delta a, b, 1e-9 }
|
|
346
|
+
end
|
|
347
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../test_helper"
|
|
4
|
+
|
|
5
|
+
class MatrixTest < Test::Unit::TestCase
|
|
6
|
+
MATRIX_CLASSES = [Larb::Mat2, Larb::Mat2d, Larb::Mat3, Larb::Mat4].freeze
|
|
7
|
+
|
|
8
|
+
def test_array_sizes_and_component_types
|
|
9
|
+
MATRIX_CLASSES.each do |klass|
|
|
10
|
+
size = klass.identity.to_a.size
|
|
11
|
+
[[], Array.new(size - 1, 0), Array.new(size + 1, 0)].each do |values|
|
|
12
|
+
assert_raise(ArgumentError) { klass.new(values) }
|
|
13
|
+
end
|
|
14
|
+
[nil, "1", Object.new].each do |value|
|
|
15
|
+
assert_raise(TypeError) { klass.new(value) }
|
|
16
|
+
values = klass.identity.to_a
|
|
17
|
+
values[-1] = value
|
|
18
|
+
assert_raise(TypeError) { klass.new(values) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_numeric_scalar_multiplication_and_invalid_operands
|
|
24
|
+
MATRIX_CLASSES.each do |klass|
|
|
25
|
+
matrix = klass.identity
|
|
26
|
+
assert_equal matrix.to_a.map { |value| value * 2 }, (matrix * 2).to_a
|
|
27
|
+
[nil, "2", Object.new, []].each do |other|
|
|
28
|
+
assert_raise(TypeError) { matrix * other }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_near_rejects_nonfinite_components_and_invalid_epsilon
|
|
34
|
+
MATRIX_CLASSES.each do |klass|
|
|
35
|
+
matrix = klass.identity
|
|
36
|
+
[Float::NAN, Float::INFINITY, -Float::INFINITY].each do |value|
|
|
37
|
+
other = klass.identity
|
|
38
|
+
other[0] = value
|
|
39
|
+
assert_false matrix.near?(other)
|
|
40
|
+
assert_false other.near?(other)
|
|
41
|
+
end
|
|
42
|
+
[0, -1, Float::NAN, Float::INFINITY, -Float::INFINITY].each do |epsilon|
|
|
43
|
+
assert_raise(ArgumentError) { matrix.near?(matrix, epsilon) }
|
|
44
|
+
end
|
|
45
|
+
assert_raise(TypeError) { matrix.near?(matrix, nil) }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_inverse_does_not_depend_on_absolute_scale
|
|
50
|
+
MATRIX_CLASSES.each do |klass|
|
|
51
|
+
[1e-200, 1e-6, 1e200].each do |magnitude|
|
|
52
|
+
matrix = klass.identity * magnitude
|
|
53
|
+
inverse = matrix.inverse
|
|
54
|
+
assert inverse.to_a.all?(&:finite?)
|
|
55
|
+
assert_finite_close klass.identity.to_a, (matrix * inverse).to_a
|
|
56
|
+
assert_finite_close klass.identity.to_a, (inverse * matrix).to_a
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_inverse_uses_pivot_swaps_and_handles_different_axis_scales
|
|
62
|
+
matrices = [
|
|
63
|
+
Larb::Mat2.new([0, 2, 3, 4]),
|
|
64
|
+
Larb::Mat2d.new([0, 2, 3, 4, 5, 6]),
|
|
65
|
+
Larb::Mat3.new([0, 1, 0, 1, 2, 0, 0, 0, 3]),
|
|
66
|
+
Larb::Mat4.new([0, 1, 0, 1, 1, 2, 1, 0, 2, 0, 3, 1, 1, 0, 0, 4]),
|
|
67
|
+
Larb::Mat2.scaling(1e-200, 1e200),
|
|
68
|
+
Larb::Mat2d.scaling(1e-200, 1e200),
|
|
69
|
+
Larb::Mat3.scaling(1e-200, 1e200),
|
|
70
|
+
Larb::Mat4.scaling(1e-200, 1e200, 1),
|
|
71
|
+
Larb::Mat2d.translation(1e200, 0),
|
|
72
|
+
Larb::Mat4.translation(1e200, 0, 0)
|
|
73
|
+
]
|
|
74
|
+
matrices.each do |matrix|
|
|
75
|
+
inverse = matrix.inverse
|
|
76
|
+
assert inverse.to_a.all?(&:finite?)
|
|
77
|
+
assert_finite_close matrix.class.identity.to_a, (matrix * inverse).to_a
|
|
78
|
+
assert_finite_close matrix.class.identity.to_a, (inverse * matrix).to_a
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_inverse_rejects_rank_deficient_and_nonfinite_matrices
|
|
83
|
+
MATRIX_CLASSES.each do |klass|
|
|
84
|
+
assert_raise(RuntimeError) { klass.new(Array.new(klass.identity.to_a.size, 1)).inverse }
|
|
85
|
+
[Float::NAN, Float::INFINITY].each do |value|
|
|
86
|
+
matrix = klass.identity
|
|
87
|
+
matrix[0] = value
|
|
88
|
+
assert_raise(RuntimeError) { matrix.inverse }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
assert_raise(RuntimeError) { Larb::Mat3.new([1, 2, 3, 4, 5, 6, 5, 7, 9]).inverse }
|
|
92
|
+
assert_raise(RuntimeError) do
|
|
93
|
+
Larb::Mat4.new([1, 2, 3, 4, 5, 6, 7, 8, 6, 8, 10, 12, 0, 1, 0, 1]).inverse
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_inverse_preserves_small_components_in_rows_and_columns
|
|
98
|
+
cases = [
|
|
99
|
+
[[1e200, 1e-200, 1e200, -1e-200], [5e-201, 5e-201, 5e199, -5e199]],
|
|
100
|
+
[[1e200, 1e200, 1e-200, -1e-200], [5e-201, 5e199, 5e-201, -5e199]]
|
|
101
|
+
]
|
|
102
|
+
MATRIX_CLASSES.each do |klass|
|
|
103
|
+
size = klass == Larb::Mat2d ? 2 : Math.sqrt(klass.identity.to_a.size).to_i
|
|
104
|
+
indices = [0, 1, size, size + 1]
|
|
105
|
+
cases.each do |values, expected|
|
|
106
|
+
matrix = klass.identity
|
|
107
|
+
indices.zip(values).each { |index, value| matrix[index] = value }
|
|
108
|
+
inverse = matrix.inverse.to_a
|
|
109
|
+
assert inverse.all?(&:finite?)
|
|
110
|
+
indices.zip(expected).each do |index, value|
|
|
111
|
+
assert_in_delta 1.0, inverse[index] / value, 1e-12
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_frobenius_norm_handles_extreme_magnitudes
|
|
118
|
+
[Larb::Mat2, Larb::Mat2d, Larb::Mat3].each do |klass|
|
|
119
|
+
[1e-200, 1e200].each do |magnitude|
|
|
120
|
+
values = klass.zero.to_a
|
|
121
|
+
values[0] = magnitude
|
|
122
|
+
norm = klass.new(values).frobenius_norm
|
|
123
|
+
assert norm.finite?
|
|
124
|
+
assert_in_delta 1.0, norm / magnitude, 1e-12
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def assert_finite_close(expected, actual)
|
|
132
|
+
assert actual.all?(&:finite?), actual.inspect
|
|
133
|
+
expected.zip(actual).each { |a, b| assert_in_delta a, b, 1e-9 }
|
|
134
|
+
end
|
|
135
|
+
end
|