glmath 0.0.1.dev

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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