glmath 0.0.1.dev
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/lib/glmath.rb +21 -0
- data/lib/glmath/euler_angle.rb +60 -0
- data/lib/glmath/line.rb +19 -0
- data/lib/glmath/matrix.rb +413 -0
- data/lib/glmath/matrix2.rb +81 -0
- data/lib/glmath/matrix3.rb +108 -0
- data/lib/glmath/matrix4.rb +145 -0
- data/lib/glmath/matrix_stack.rb +91 -0
- data/lib/glmath/quaternion.rb +212 -0
- data/lib/glmath/rect.rb +135 -0
- data/lib/glmath/scalar.rb +33 -0
- data/lib/glmath/vector.rb +155 -0
- data/lib/glmath/vector2.rb +45 -0
- data/lib/glmath/vector3.rb +66 -0
- data/lib/glmath/vector4.rb +54 -0
- data/lib/version.rb +4 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 66841450cf95c7713843605f3c656821a45057fd
|
4
|
+
data.tar.gz: 38ce2be773aa7000ab1bd2eef22c454fa2fe856a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1c3b27612f24dfb30f9a1af21b92acf5cce1d015ffed5dd3889159f4c37e4b0ec2c59dcc57e764a74d9bd011a0a3e354e15acf134e2364f94e80b10b569ee03f
|
7
|
+
data.tar.gz: bcaec488839a917334e4e407f730d06b219a071d5e04e62c1939fd2dd5ac1b5eec6d7dd469571e81e30d5528482a5658ec12f6cda91eaf426fcbe1809ae5935f
|
data/lib/glmath.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Math
|
2
|
+
TAU = 2 * PI
|
3
|
+
end
|
4
|
+
|
5
|
+
require_relative 'version'
|
6
|
+
|
7
|
+
%w'
|
8
|
+
matrix
|
9
|
+
matrix2
|
10
|
+
matrix3
|
11
|
+
matrix4
|
12
|
+
vector
|
13
|
+
vector2
|
14
|
+
vector3
|
15
|
+
vector4
|
16
|
+
scalar
|
17
|
+
quaternion
|
18
|
+
euler_angle
|
19
|
+
matrix_stack
|
20
|
+
'.each { |f| require_relative "glmath/#{f}" }
|
21
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module GLMath
|
2
|
+
class EulerAngle
|
3
|
+
def initialize(y, r, p)
|
4
|
+
@e = y, r, p
|
5
|
+
end
|
6
|
+
|
7
|
+
%w'y r p'.each.each_with_index do |m, i|
|
8
|
+
define_method m, ->(){ @e[i] }
|
9
|
+
define_method "#{m}=", ->(v){ @e[i] = v }
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
return false unless self.class === other
|
14
|
+
@e == other.instance_variable_get(:@e)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_matrix(*args)
|
18
|
+
args = [:y, :p, :r] if args.length == 0
|
19
|
+
cy, sy, cp, sp, cr, sr = nil
|
20
|
+
args.map do |i|
|
21
|
+
case i
|
22
|
+
when :y, :yaw, :h, :heading
|
23
|
+
cy ||= cos(y)
|
24
|
+
sy ||= sin(y)
|
25
|
+
Matrix4[cy, 0, -sy, 0, 0, 1, 0, 0, sy, 0, cy, 0, 0, 0, 0, 1]
|
26
|
+
when :p, :pitch
|
27
|
+
cp ||= cos(p)
|
28
|
+
sp ||= sin(p)
|
29
|
+
Matrix4[1, 0, 0, 0, 0, cp, sp, 0, 0, -sp, cp, 0, 0, 0, 0, 1]
|
30
|
+
when :r, :roll, :b, :bank
|
31
|
+
cr ||= cos(r)
|
32
|
+
sr ||= sin(r)
|
33
|
+
Matrix4[cr, sr, 0, 0, -sr, cr, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
|
34
|
+
else
|
35
|
+
1
|
36
|
+
end
|
37
|
+
end.reduce(&:*)
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_quaternion
|
41
|
+
Quaternion.new(a, b, c, d)
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
"Euler Angle (#{@e.join(', ')})"
|
46
|
+
end
|
47
|
+
|
48
|
+
def dup
|
49
|
+
self.class.new(*@e)
|
50
|
+
end
|
51
|
+
|
52
|
+
alias_method :h, :y
|
53
|
+
alias_method :heading, :y
|
54
|
+
alias_method :yaw, :y
|
55
|
+
alias_method :b, :r
|
56
|
+
alias_method :bank, :r
|
57
|
+
alias_method :pitch, :p
|
58
|
+
alias_method :roll, :r
|
59
|
+
end
|
60
|
+
end
|
data/lib/glmath/line.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module GLMath
|
2
|
+
class Line
|
3
|
+
|
4
|
+
end
|
5
|
+
|
6
|
+
class LineSegment
|
7
|
+
def initialize(x1, y1, z1 = 0, x2, y2, z2 = 0)
|
8
|
+
@start_point = Vector3[x1, y1, z1]
|
9
|
+
@end_point = Vector3[x2, y2, z2]
|
10
|
+
@vector = @end_point - @start_point
|
11
|
+
end
|
12
|
+
|
13
|
+
def length
|
14
|
+
@vector.length
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,413 @@
|
|
1
|
+
|
2
|
+
module GLMath
|
3
|
+
module Matrix
|
4
|
+
module ClassMethods
|
5
|
+
def [](*rows)
|
6
|
+
new(*rows)
|
7
|
+
end
|
8
|
+
|
9
|
+
def build
|
10
|
+
new(*dim.times.flat_map { |r| dim.times.map { |c| yield r, c } })
|
11
|
+
end
|
12
|
+
|
13
|
+
def columns(*args)
|
14
|
+
raise ArgumentError, "wrong number of arguments #{args.length} for #{dim}" unless args.length == dim
|
15
|
+
raise ArgumentError, "wrong array size. All arrays must have size #{dim}" unless args.all? { |arg| arg.length == dim }
|
16
|
+
new(*args.transpose.flatten(1))
|
17
|
+
end
|
18
|
+
|
19
|
+
def diagonal(*args)
|
20
|
+
raise ArgumentError, "wrong number of arguments (#{args.length} for #{dim})" if args.length != dim
|
21
|
+
build { |r, c| r == c ? args[r] : 0.0 }
|
22
|
+
end
|
23
|
+
|
24
|
+
def dim
|
25
|
+
dimension
|
26
|
+
end
|
27
|
+
|
28
|
+
def identity
|
29
|
+
scalar(1.0)
|
30
|
+
end
|
31
|
+
|
32
|
+
def length
|
33
|
+
dim * dim
|
34
|
+
end
|
35
|
+
|
36
|
+
def rows(*args)
|
37
|
+
raise ArgumentError, "wrong number of arguments #{args.length} for #{dim}" unless args.length == dim
|
38
|
+
raise ArgumentError, "wrong array size. All arrays must have size #{dim}" unless args.all? { |arg| arg.length == dim }
|
39
|
+
new(*args.flatten(1))
|
40
|
+
end
|
41
|
+
|
42
|
+
def scalar(n)
|
43
|
+
build { |r, c| r == c ? n : 0.0 }
|
44
|
+
end
|
45
|
+
|
46
|
+
def zero
|
47
|
+
new(*[0.0]*(dim*dim))
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :I, :identity
|
51
|
+
alias_method :unit, :identity
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.included(base)
|
55
|
+
base.extend ClassMethods
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(*args)
|
59
|
+
raise ArgumentError, "wrong number of arguments (#{args.length} for #{dim * dim})" unless args.length == dim * dim
|
60
|
+
raise ArgumentError, "It's not numeric" unless args.all? { |e| Numeric === e }
|
61
|
+
@m = args
|
62
|
+
end
|
63
|
+
|
64
|
+
%w'+ -'.each do |s|
|
65
|
+
define_method(s, ->(m) do
|
66
|
+
raise ArgumentError unless self.class === m
|
67
|
+
m = m.instance_variable_get(:@m)
|
68
|
+
self.class.new(*[@m, m].transpose.map { |a, b| a.send(s, b) })
|
69
|
+
end)
|
70
|
+
end
|
71
|
+
|
72
|
+
def *(v)
|
73
|
+
raise ArgumentError, v.class unless v.is_a?(Numeric)
|
74
|
+
self.class.new(*@m.map { |e| e * v })
|
75
|
+
end
|
76
|
+
|
77
|
+
def /(other)
|
78
|
+
case other
|
79
|
+
when Numeric
|
80
|
+
self.class.new(*@m.map { |v| v / other })
|
81
|
+
when self.class
|
82
|
+
# transpose(A) = A'.
|
83
|
+
# A / B = X (=) A = XB (=) B'X' = A'
|
84
|
+
other.transpose.solve(self.transpose).transpose
|
85
|
+
else
|
86
|
+
raise ArgumentError, "Invalid type #{other.class}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def ==(other)
|
91
|
+
other.is_a?(self.class) && @m == other.instance_variable_get(:@m)
|
92
|
+
end
|
93
|
+
|
94
|
+
def [](r, c = nil)
|
95
|
+
@m[c ? r * dim + c : r]
|
96
|
+
end
|
97
|
+
|
98
|
+
def []= (r, c = nil, n)
|
99
|
+
@m[c ? r * dim + c : r] = n
|
100
|
+
end
|
101
|
+
|
102
|
+
def adjoint
|
103
|
+
clone.adjoint!
|
104
|
+
end
|
105
|
+
|
106
|
+
def adjoint!
|
107
|
+
conjugate!
|
108
|
+
transpose!
|
109
|
+
end
|
110
|
+
|
111
|
+
def adjugate
|
112
|
+
clone.adjugate!
|
113
|
+
end
|
114
|
+
|
115
|
+
def coerce(v)
|
116
|
+
case v
|
117
|
+
when Numeric then return Scalar.new(v), self
|
118
|
+
else raise TypeError, "#{self.class} can't be coerced into #{v.class}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def collect(&block)
|
123
|
+
block_given? ? self.class.new(*@m.collect(&block)) : to_enum(:collect)
|
124
|
+
end
|
125
|
+
|
126
|
+
def column(c)
|
127
|
+
raise ArgumentError, "can only be an Integer between 0 and #{dim - 1}:#{c}" unless c.is_a?(Integer) && (0...dim).include?(c)
|
128
|
+
dim.times.map { |r| @m[r * dim + c] }
|
129
|
+
end
|
130
|
+
|
131
|
+
def columns
|
132
|
+
dim.times.map { |c| column(c) }
|
133
|
+
end
|
134
|
+
|
135
|
+
def conjugate
|
136
|
+
clone.conjugate!
|
137
|
+
end
|
138
|
+
|
139
|
+
def conjugate!
|
140
|
+
@m.map!(&:conjugate)
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
def clone
|
145
|
+
self.class.new(*@m)
|
146
|
+
end
|
147
|
+
|
148
|
+
def diagonal
|
149
|
+
dim.times.map { |r| self[r, r] }
|
150
|
+
end
|
151
|
+
|
152
|
+
def diagonal?
|
153
|
+
each(:off_diagonal).all?(&:zero?)
|
154
|
+
end
|
155
|
+
|
156
|
+
def dimension
|
157
|
+
self.class.dimension
|
158
|
+
end
|
159
|
+
|
160
|
+
def dup
|
161
|
+
self.class.new(*@m)
|
162
|
+
end
|
163
|
+
|
164
|
+
def each(which = :all)
|
165
|
+
return to_enum :each, which unless block_given?
|
166
|
+
case which
|
167
|
+
when :all
|
168
|
+
@m.each { |e| yield e }
|
169
|
+
when :diagonal
|
170
|
+
dim.times.each { |r| yield self[r, r] }
|
171
|
+
when :off_diagonal
|
172
|
+
dim.times.each { |r| dim.times.each { |c| yield self[r, c] unless r == c } }
|
173
|
+
when :lower
|
174
|
+
dim.times.each { |r| dim.times.each { |c| yield self[r, c] if r >= c } }
|
175
|
+
when :strict_lower
|
176
|
+
dim.times.each { |r| dim.times.each { |c| yield self[r, c] if r > c } }
|
177
|
+
when :strict_upper
|
178
|
+
dim.times.each { |r| dim.times.each { |c| yield self[r, c] if r < c } }
|
179
|
+
when :upper
|
180
|
+
dim.times.each { |r| dim.times.each { |c| yield self[r, c] if r <= c } }
|
181
|
+
else
|
182
|
+
raise ArgumentError, which
|
183
|
+
end
|
184
|
+
self
|
185
|
+
end
|
186
|
+
|
187
|
+
def each_with_index(which = :all)
|
188
|
+
return to_enum :each_with_index, which unless block_given?
|
189
|
+
case which
|
190
|
+
when :all
|
191
|
+
@m.each_with_index { |e, i| yield e, i / dim, i % dim }
|
192
|
+
when :diagonal
|
193
|
+
dim.times.each { |r| yield self[r, r], r, r }
|
194
|
+
when :off_diagonal
|
195
|
+
dim.times.each { |r| dim.times.each { |c| yield self[r, c], r, c unless r == c } }
|
196
|
+
when :lower
|
197
|
+
dim.times.each { |r| dim.times.each { |c| yield self[r, c], r, c if r >= c } }
|
198
|
+
when :strict_lower
|
199
|
+
dim.times.each { |r| dim.times.each { |c| yield self[r, c], r, c if r > c } }
|
200
|
+
when :strict_upper
|
201
|
+
dim.times.each { |r| dim.times.each { |c| yield self[r, c], r, c if r < c } }
|
202
|
+
when :upper
|
203
|
+
dim.times.each { |r| dim.times.each { |c| yield self[r, c], r, c if r <= c } }
|
204
|
+
else
|
205
|
+
raise ArgumentError, which
|
206
|
+
end
|
207
|
+
self
|
208
|
+
end
|
209
|
+
|
210
|
+
def eql?(other)
|
211
|
+
other.is_a?(self.class) && @m.eql?(other.to_a)
|
212
|
+
end
|
213
|
+
|
214
|
+
def hash
|
215
|
+
@m.hash
|
216
|
+
end
|
217
|
+
|
218
|
+
def hermitian?
|
219
|
+
transpose.conjugate! == self
|
220
|
+
end
|
221
|
+
|
222
|
+
def imaginary
|
223
|
+
map(&:imaginary)
|
224
|
+
end
|
225
|
+
|
226
|
+
def inspect
|
227
|
+
"Matrix#{dim}#{@m.inspect}"
|
228
|
+
end
|
229
|
+
|
230
|
+
def inverse
|
231
|
+
# the inverse is solution to the equation AX = I
|
232
|
+
solve(self.class.identity)
|
233
|
+
end
|
234
|
+
|
235
|
+
def length
|
236
|
+
@m.length
|
237
|
+
end
|
238
|
+
|
239
|
+
def lower_triangular?
|
240
|
+
each(:strict_upper).all?(&:zero?)
|
241
|
+
end
|
242
|
+
|
243
|
+
def lup
|
244
|
+
raise ArgumentError, "Determinant is zero" if singular?
|
245
|
+
|
246
|
+
# get pivot
|
247
|
+
p = self.class.identity.rows
|
248
|
+
rows = self.rows
|
249
|
+
(dim - 1).times do |i|
|
250
|
+
max, row = rows[i][i], i
|
251
|
+
(i+1...dim).each { |j| max, row = rows[j][i], j if rows[j][i] > max }
|
252
|
+
p[i], p[row] = p[row], p[i]
|
253
|
+
rows[i], rows[row] = rows[row], rows[i]
|
254
|
+
end
|
255
|
+
p = self.class.new(*p.flatten(1))
|
256
|
+
pivoted = self.class.new(*rows.flatten(1))
|
257
|
+
|
258
|
+
# calculate L and U matrices
|
259
|
+
l = self.class.identity
|
260
|
+
u = self.class.zero
|
261
|
+
|
262
|
+
dim.times.each do |i|
|
263
|
+
dim.times.each do |j|
|
264
|
+
if j >= i
|
265
|
+
# upper
|
266
|
+
u[i, j] = pivoted[i, j] - i.times.map { |k| u[k, j] * l[i, k] }.reduce(0.0, &:+)
|
267
|
+
else
|
268
|
+
# lower
|
269
|
+
l[i, j] = (pivoted[i, j] - j.times.map { |k| u[k, j] * l[i, k] }.reduce(0.0, &:+)) / u[j, j]
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
[ l, u, p ]
|
275
|
+
end
|
276
|
+
|
277
|
+
def normal?
|
278
|
+
n = transpose.conjugate!
|
279
|
+
self * n == n * self
|
280
|
+
end
|
281
|
+
|
282
|
+
def permutation?
|
283
|
+
rows.each do |r|
|
284
|
+
return false unless r.select { |v| v.abs < 1e-14 }.count == dim - 1
|
285
|
+
return false unless r.index { |v| (1.0 - v).abs < 1e-14 }
|
286
|
+
end
|
287
|
+
|
288
|
+
columns.each do |c|
|
289
|
+
return false unless c.select { |v| v.abs < 1e-14 }.count == dim - 1
|
290
|
+
return false unless c.index { |v| (1.0 - v).abs < 1e-14 }
|
291
|
+
end
|
292
|
+
|
293
|
+
true
|
294
|
+
end
|
295
|
+
|
296
|
+
def real
|
297
|
+
map(&:real)
|
298
|
+
end
|
299
|
+
|
300
|
+
def real?
|
301
|
+
@m.all?(&:real?)
|
302
|
+
end
|
303
|
+
|
304
|
+
def round(ndigits = 0)
|
305
|
+
map { |e| e.round(ndigits) }
|
306
|
+
end
|
307
|
+
|
308
|
+
def row(r)
|
309
|
+
raise ArgumentError, "can only be between 0 and #{dim}:#{r}" unless (0...dim).include? r
|
310
|
+
@m[r * dim, dim]
|
311
|
+
end
|
312
|
+
|
313
|
+
def rows
|
314
|
+
dim.times.map { |i| row(i) }
|
315
|
+
end
|
316
|
+
|
317
|
+
def scalar?
|
318
|
+
diag = []
|
319
|
+
each_with_index do |v, r, c|
|
320
|
+
return false if r != c && !v.zero?
|
321
|
+
diag << v if r == c
|
322
|
+
end
|
323
|
+
diag.uniq.size == 1
|
324
|
+
end
|
325
|
+
|
326
|
+
def singular?
|
327
|
+
determinant.zero?
|
328
|
+
end
|
329
|
+
|
330
|
+
# Solves *AX = B*, where *A* is `self` and *B* is the argument
|
331
|
+
def solve(matrix)
|
332
|
+
raise ArgumentError, "expected class #{ self.class }, but got #{ matrix.class }" unless self.class === matrix
|
333
|
+
|
334
|
+
l, u, p = lup
|
335
|
+
|
336
|
+
# Ax = b (=) LUx = b (=) Ly = b && Ux = y
|
337
|
+
# If y is defined to be Ux: Ly = b
|
338
|
+
y = self.class.zero
|
339
|
+
matrix = p * matrix
|
340
|
+
# Forward solve Ly = b
|
341
|
+
dim.times do |c|
|
342
|
+
dim.times do |r|
|
343
|
+
y[r, c] = matrix[r, c]
|
344
|
+
(0...r).each { |j| y[r, c] -= l[r, j] * y[j, c] }
|
345
|
+
y[r, c] /= l[r, r]
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# Backward solve Ux = y
|
350
|
+
x = self.class.zero
|
351
|
+
|
352
|
+
dim.times do |c|
|
353
|
+
(dim - 1).downto(0) do |i|
|
354
|
+
x[i, c] = y[i, c]
|
355
|
+
(i+1...dim).each { |j| x[i, c] -= u[i, j] * x[j, c] }
|
356
|
+
x[i, c] /= u[i, i]
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
x
|
361
|
+
end
|
362
|
+
|
363
|
+
def symmetric?
|
364
|
+
each_with_index(:strict_upper).all? { |e, r, c| e == self[c, r] }
|
365
|
+
end
|
366
|
+
|
367
|
+
def to_a
|
368
|
+
@m.dup
|
369
|
+
end
|
370
|
+
|
371
|
+
def to_s(notation = nil)
|
372
|
+
case notation
|
373
|
+
when nil then "#{self.class.name.gsub(/^.*::/,'')}#{@m}"
|
374
|
+
when :matrix then (dim*dim).times.each_slice(dim).map { |s| s.map { |i| self[i] }.join("\t") }.join("\n")
|
375
|
+
else raise ArgumentError, "unknown notation #{notation.inspect}"
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
def trace
|
380
|
+
diagonal.reduce(&:+)
|
381
|
+
end
|
382
|
+
|
383
|
+
def transpose
|
384
|
+
clone.transpose!
|
385
|
+
end
|
386
|
+
|
387
|
+
def transpose!
|
388
|
+
@m = @m.each_slice(dim).to_a.transpose.flatten!(1)
|
389
|
+
self
|
390
|
+
end
|
391
|
+
|
392
|
+
def upper_triangular?
|
393
|
+
each(:strict_lower).all?(&:zero?)
|
394
|
+
end
|
395
|
+
|
396
|
+
def zero?
|
397
|
+
@m.all?(&:zero?)
|
398
|
+
end
|
399
|
+
|
400
|
+
alias_method :component, :[]
|
401
|
+
alias_method :conj, :conjugate
|
402
|
+
alias_method :conj!, :conjugate!
|
403
|
+
alias_method :dim, :dimension
|
404
|
+
alias_method :element, :[]
|
405
|
+
alias_method :imag, :imaginary
|
406
|
+
alias_method :inv, :inverse
|
407
|
+
alias_method :lup_decomposition, :lup
|
408
|
+
alias_method :map, :collect
|
409
|
+
alias_method :t, :transpose
|
410
|
+
alias_method :t!, :transpose!
|
411
|
+
alias_method :tr, :trace
|
412
|
+
end
|
413
|
+
end
|