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.
@@ -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