larb 0.1.0

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.
data/lib/larb/mat3.rb ADDED
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Larb
4
+ class Mat3
5
+ attr_reader :data
6
+
7
+ def initialize(data = nil)
8
+ @data = data || [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
9
+ end
10
+
11
+ def self.identity
12
+ new
13
+ end
14
+
15
+ def self.zero
16
+ new(Array.new(9, 0.0))
17
+ end
18
+
19
+ def self.from_mat4(m)
20
+ new([
21
+ m[0], m[1], m[2],
22
+ m[4], m[5], m[6],
23
+ m[8], m[9], m[10]
24
+ ])
25
+ end
26
+
27
+ def self.from_mat2d(m)
28
+ new([
29
+ m[0], m[1], 0,
30
+ m[2], m[3], 0,
31
+ m[4], m[5], 1
32
+ ])
33
+ end
34
+
35
+ def self.from_quaternion(q)
36
+ x, y, z, w = q.x, q.y, q.z, q.w
37
+ x2 = x + x
38
+ y2 = y + y
39
+ z2 = z + z
40
+ xx = x * x2
41
+ yx = y * x2
42
+ yy = y * y2
43
+ zx = z * x2
44
+ zy = z * y2
45
+ zz = z * z2
46
+ wx = w * x2
47
+ wy = w * y2
48
+ wz = w * z2
49
+
50
+ new([
51
+ 1 - yy - zz, yx + wz, zx - wy,
52
+ yx - wz, 1 - xx - zz, zy + wx,
53
+ zx + wy, zy - wx, 1 - xx - yy
54
+ ])
55
+ end
56
+
57
+ def self.normal_from_mat4(m)
58
+ from_mat4(m).inverse.transpose
59
+ end
60
+
61
+ def self.projection(width, height)
62
+ new([
63
+ 2.0 / width, 0, 0,
64
+ 0, -2.0 / height, 0,
65
+ -1, 1, 1
66
+ ])
67
+ end
68
+
69
+ def self.translation(x, y)
70
+ new([
71
+ 1, 0, 0,
72
+ 0, 1, 0,
73
+ x.to_f, y.to_f, 1
74
+ ])
75
+ end
76
+
77
+ def self.rotation(radians)
78
+ c = Math.cos(radians)
79
+ s = Math.sin(radians)
80
+ new([
81
+ c, s, 0,
82
+ -s, c, 0,
83
+ 0, 0, 1
84
+ ])
85
+ end
86
+
87
+ def self.scaling(x, y)
88
+ new([
89
+ x.to_f, 0, 0,
90
+ 0, y.to_f, 0,
91
+ 0, 0, 1
92
+ ])
93
+ end
94
+
95
+ def [](i)
96
+ @data[i]
97
+ end
98
+
99
+ def []=(i, v)
100
+ @data[i] = v.to_f
101
+ end
102
+
103
+ def *(other)
104
+ case other
105
+ when Mat3
106
+ a = @data
107
+ b = other.data
108
+ Mat3.new([
109
+ a[0] * b[0] + a[3] * b[1] + a[6] * b[2],
110
+ a[1] * b[0] + a[4] * b[1] + a[7] * b[2],
111
+ a[2] * b[0] + a[5] * b[1] + a[8] * b[2],
112
+ a[0] * b[3] + a[3] * b[4] + a[6] * b[5],
113
+ a[1] * b[3] + a[4] * b[4] + a[7] * b[5],
114
+ a[2] * b[3] + a[5] * b[4] + a[8] * b[5],
115
+ a[0] * b[6] + a[3] * b[7] + a[6] * b[8],
116
+ a[1] * b[6] + a[4] * b[7] + a[7] * b[8],
117
+ a[2] * b[6] + a[5] * b[7] + a[8] * b[8]
118
+ ])
119
+ when Vec3
120
+ Vec3.new(
121
+ @data[0] * other.x + @data[3] * other.y + @data[6] * other.z,
122
+ @data[1] * other.x + @data[4] * other.y + @data[7] * other.z,
123
+ @data[2] * other.x + @data[5] * other.y + @data[8] * other.z
124
+ )
125
+ when Numeric
126
+ Mat3.new(@data.map { |v| v * other })
127
+ end
128
+ end
129
+
130
+ def +(other)
131
+ Mat3.new(@data.zip(other.data).map { |a, b| a + b })
132
+ end
133
+
134
+ def -(other)
135
+ Mat3.new(@data.zip(other.data).map { |a, b| a - b })
136
+ end
137
+
138
+ def determinant
139
+ a = @data
140
+ a[0] * (a[4] * a[8] - a[5] * a[7]) -
141
+ a[3] * (a[1] * a[8] - a[2] * a[7]) +
142
+ a[6] * (a[1] * a[5] - a[2] * a[4])
143
+ end
144
+
145
+ def inverse
146
+ a = @data
147
+ det = determinant
148
+ raise "Matrix is not invertible" if det.abs < 1e-10
149
+
150
+ inv_det = 1.0 / det
151
+ Mat3.new([
152
+ (a[4] * a[8] - a[5] * a[7]) * inv_det,
153
+ (a[2] * a[7] - a[1] * a[8]) * inv_det,
154
+ (a[1] * a[5] - a[2] * a[4]) * inv_det,
155
+ (a[5] * a[6] - a[3] * a[8]) * inv_det,
156
+ (a[0] * a[8] - a[2] * a[6]) * inv_det,
157
+ (a[2] * a[3] - a[0] * a[5]) * inv_det,
158
+ (a[3] * a[7] - a[4] * a[6]) * inv_det,
159
+ (a[1] * a[6] - a[0] * a[7]) * inv_det,
160
+ (a[0] * a[4] - a[1] * a[3]) * inv_det
161
+ ])
162
+ end
163
+
164
+ def transpose
165
+ Mat3.new([
166
+ @data[0], @data[3], @data[6],
167
+ @data[1], @data[4], @data[7],
168
+ @data[2], @data[5], @data[8]
169
+ ])
170
+ end
171
+
172
+ def adjoint
173
+ a = @data
174
+ Mat3.new([
175
+ a[4] * a[8] - a[5] * a[7],
176
+ a[2] * a[7] - a[1] * a[8],
177
+ a[1] * a[5] - a[2] * a[4],
178
+ a[5] * a[6] - a[3] * a[8],
179
+ a[0] * a[8] - a[2] * a[6],
180
+ a[2] * a[3] - a[0] * a[5],
181
+ a[3] * a[7] - a[4] * a[6],
182
+ a[1] * a[6] - a[0] * a[7],
183
+ a[0] * a[4] - a[1] * a[3]
184
+ ])
185
+ end
186
+
187
+ def frobenius_norm
188
+ Math.sqrt(@data.sum { |v| v * v })
189
+ end
190
+
191
+ def translate(x, y)
192
+ a = @data
193
+ Mat3.new([
194
+ a[0], a[1], a[2],
195
+ a[3], a[4], a[5],
196
+ x * a[0] + y * a[3] + a[6],
197
+ x * a[1] + y * a[4] + a[7],
198
+ x * a[2] + y * a[5] + a[8]
199
+ ])
200
+ end
201
+
202
+ def rotate(radians)
203
+ self * Mat3.rotation(radians)
204
+ end
205
+
206
+ def scale(x, y)
207
+ a = @data
208
+ Mat3.new([
209
+ a[0] * x, a[1] * x, a[2] * x,
210
+ a[3] * y, a[4] * y, a[5] * y,
211
+ a[6], a[7], a[8]
212
+ ])
213
+ end
214
+
215
+ def to_a
216
+ @data.dup
217
+ end
218
+
219
+ def ==(other)
220
+ return false unless other.is_a?(Mat3)
221
+
222
+ @data == other.data
223
+ end
224
+
225
+ def near?(other, epsilon = 1e-6)
226
+ @data.zip(other.data).all? { |a, b| (a - b).abs < epsilon }
227
+ end
228
+
229
+ def inspect
230
+ "Mat3[\n" \
231
+ " #{@data[0..2].map { |v| format('%8.4f', v) }.join(', ')}\n" \
232
+ " #{@data[3..5].map { |v| format('%8.4f', v) }.join(', ')}\n" \
233
+ " #{@data[6..8].map { |v| format('%8.4f', v) }.join(', ')}\n]"
234
+ end
235
+
236
+ alias to_s inspect
237
+ end
238
+ end
data/lib/larb/mat4.rb ADDED
@@ -0,0 +1,329 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Larb
4
+ class Mat4
5
+ attr_reader :data
6
+
7
+ def initialize(data = nil)
8
+ @data = data || Array.new(16, 0.0)
9
+ unless data
10
+ @data[0] = @data[5] = @data[10] = @data[15] = 1.0
11
+ end
12
+ end
13
+
14
+ def self.identity
15
+ new
16
+ end
17
+
18
+ def self.zero
19
+ new(Array.new(16, 0.0))
20
+ end
21
+
22
+ def self.translation(x, y, z)
23
+ m = identity
24
+ m[12] = x.to_f
25
+ m[13] = y.to_f
26
+ m[14] = z.to_f
27
+ m
28
+ end
29
+
30
+ def self.scaling(x, y, z)
31
+ m = zero
32
+ m[0] = x.to_f
33
+ m[5] = y.to_f
34
+ m[10] = z.to_f
35
+ m[15] = 1.0
36
+ m
37
+ end
38
+
39
+ def self.rotation_x(radians)
40
+ c = Math.cos(radians)
41
+ s = Math.sin(radians)
42
+ m = identity
43
+ m[5] = c
44
+ m[6] = s
45
+ m[9] = -s
46
+ m[10] = c
47
+ m
48
+ end
49
+
50
+ def self.rotation_y(radians)
51
+ c = Math.cos(radians)
52
+ s = Math.sin(radians)
53
+ m = identity
54
+ m[0] = c
55
+ m[2] = -s
56
+ m[8] = s
57
+ m[10] = c
58
+ m
59
+ end
60
+
61
+ def self.rotation_z(radians)
62
+ c = Math.cos(radians)
63
+ s = Math.sin(radians)
64
+ m = identity
65
+ m[0] = c
66
+ m[1] = s
67
+ m[4] = -s
68
+ m[5] = c
69
+ m
70
+ end
71
+
72
+ def self.rotation(axis, radians)
73
+ axis = axis.normalize
74
+ c = Math.cos(radians)
75
+ s = Math.sin(radians)
76
+ t = 1 - c
77
+ x = axis.x
78
+ y = axis.y
79
+ z = axis.z
80
+
81
+ new([
82
+ t * x * x + c, t * x * y + s * z, t * x * z - s * y, 0,
83
+ t * x * y - s * z, t * y * y + c, t * y * z + s * x, 0,
84
+ t * x * z + s * y, t * y * z - s * x, t * z * z + c, 0,
85
+ 0, 0, 0, 1
86
+ ])
87
+ end
88
+
89
+ def self.look_at(eye, target, up)
90
+ f = (target - eye).normalize
91
+ r = f.cross(up).normalize
92
+ u = r.cross(f)
93
+
94
+ new([
95
+ r.x, u.x, -f.x, 0,
96
+ r.y, u.y, -f.y, 0,
97
+ r.z, u.z, -f.z, 0,
98
+ -r.dot(eye), -u.dot(eye), f.dot(eye), 1
99
+ ])
100
+ end
101
+
102
+ def self.perspective(fov_y, aspect, near, far)
103
+ f = 1.0 / Math.tan(fov_y / 2.0)
104
+ nf = 1.0 / (near - far)
105
+
106
+ new([
107
+ f / aspect, 0, 0, 0,
108
+ 0, f, 0, 0,
109
+ 0, 0, (far + near) * nf, -1,
110
+ 0, 0, 2 * far * near * nf, 0
111
+ ])
112
+ end
113
+
114
+ def self.orthographic(left, right, bottom, top, near, far)
115
+ rl = 1.0 / (right - left)
116
+ tb = 1.0 / (top - bottom)
117
+ fn = 1.0 / (far - near)
118
+
119
+ new([
120
+ 2 * rl, 0, 0, 0,
121
+ 0, 2 * tb, 0, 0,
122
+ 0, 0, -2 * fn, 0,
123
+ -(right + left) * rl, -(top + bottom) * tb, -(far + near) * fn, 1
124
+ ])
125
+ end
126
+
127
+ def self.frustum(left, right, bottom, top, near, far)
128
+ rl = 1.0 / (right - left)
129
+ tb = 1.0 / (top - bottom)
130
+ nf = 1.0 / (near - far)
131
+
132
+ new([
133
+ 2 * near * rl, 0, 0, 0,
134
+ 0, 2 * near * tb, 0, 0,
135
+ (right + left) * rl, (top + bottom) * tb, (far + near) * nf, -1,
136
+ 0, 0, 2 * far * near * nf, 0
137
+ ])
138
+ end
139
+
140
+ def self.from_quaternion(q)
141
+ x, y, z, w = q.x, q.y, q.z, q.w
142
+
143
+ x2 = x + x
144
+ y2 = y + y
145
+ z2 = z + z
146
+
147
+ xx = x * x2
148
+ xy = x * y2
149
+ xz = x * z2
150
+ yy = y * y2
151
+ yz = y * z2
152
+ zz = z * z2
153
+ wx = w * x2
154
+ wy = w * y2
155
+ wz = w * z2
156
+
157
+ new([
158
+ 1 - (yy + zz), xy + wz, xz - wy, 0,
159
+ xy - wz, 1 - (xx + zz), yz + wx, 0,
160
+ xz + wy, yz - wx, 1 - (xx + yy), 0,
161
+ 0, 0, 0, 1
162
+ ])
163
+ end
164
+
165
+ def self.trs(translation, rotation, scale)
166
+ from_quaternion(rotation) * scaling(scale.x, scale.y, scale.z) *
167
+ self.translation(translation.x, translation.y, translation.z)
168
+ end
169
+
170
+ def [](i)
171
+ @data[i]
172
+ end
173
+
174
+ def []=(i, v)
175
+ @data[i] = v.to_f
176
+ end
177
+
178
+ def *(other)
179
+ case other
180
+ when Mat4
181
+ result = Array.new(16, 0.0)
182
+ 4.times do |col|
183
+ 4.times do |row|
184
+ 4.times do |k|
185
+ result[col * 4 + row] += @data[k * 4 + row] * other[col * 4 + k]
186
+ end
187
+ end
188
+ end
189
+ Mat4.new(result)
190
+
191
+ when Vec4
192
+ Vec4.new(
193
+ @data[0] * other.x + @data[4] * other.y + @data[8] * other.z + @data[12] * other.w,
194
+ @data[1] * other.x + @data[5] * other.y + @data[9] * other.z + @data[13] * other.w,
195
+ @data[2] * other.x + @data[6] * other.y + @data[10] * other.z + @data[14] * other.w,
196
+ @data[3] * other.x + @data[7] * other.y + @data[11] * other.z + @data[15] * other.w
197
+ )
198
+
199
+ when Vec3
200
+ self * other.to_vec4
201
+ end
202
+ end
203
+
204
+ def transpose
205
+ Mat4.new([
206
+ @data[0], @data[4], @data[8], @data[12],
207
+ @data[1], @data[5], @data[9], @data[13],
208
+ @data[2], @data[6], @data[10], @data[14],
209
+ @data[3], @data[7], @data[11], @data[15]
210
+ ])
211
+ end
212
+
213
+ def inverse
214
+ m = @data
215
+ inv = Array.new(16)
216
+
217
+ inv[0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10]
218
+ inv[4] = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10]
219
+ inv[8] = m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9]
220
+ inv[12] = -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9]
221
+
222
+ inv[1] = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10]
223
+ inv[5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10]
224
+ inv[9] = -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9]
225
+ inv[13] = m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9]
226
+
227
+ inv[2] = m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7] - m[13] * m[3] * m[6]
228
+ inv[6] = -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7] + m[12] * m[3] * m[6]
229
+ inv[10] = m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7] - m[12] * m[3] * m[5]
230
+ inv[14] = -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6] + m[12] * m[2] * m[5]
231
+
232
+ inv[3] = -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9] * m[2] * m[7] + m[9] * m[3] * m[6]
233
+ inv[7] = m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8] * m[2] * m[7] - m[8] * m[3] * m[6]
234
+ inv[11] = -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - m[8] * m[1] * m[7] + m[8] * m[3] * m[5]
235
+ inv[15] = m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - m[8] * m[2] * m[5]
236
+
237
+ det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]
238
+ raise "Matrix is not invertible" if det.abs < 1e-10
239
+
240
+ det = 1.0 / det
241
+ Mat4.new(inv.map { |v| v * det })
242
+ end
243
+
244
+ def to_a
245
+ @data.dup
246
+ end
247
+
248
+ def determinant
249
+ m = @data
250
+ m[0] * (m[5] * (m[10] * m[15] - m[11] * m[14]) -
251
+ m[9] * (m[6] * m[15] - m[7] * m[14]) +
252
+ m[13] * (m[6] * m[11] - m[7] * m[10])) -
253
+ m[4] * (m[1] * (m[10] * m[15] - m[11] * m[14]) -
254
+ m[9] * (m[2] * m[15] - m[3] * m[14]) +
255
+ m[13] * (m[2] * m[11] - m[3] * m[10])) +
256
+ m[8] * (m[1] * (m[6] * m[15] - m[7] * m[14]) -
257
+ m[5] * (m[2] * m[15] - m[3] * m[14]) +
258
+ m[13] * (m[2] * m[7] - m[3] * m[6])) -
259
+ m[12] * (m[1] * (m[6] * m[11] - m[7] * m[10]) -
260
+ m[5] * (m[2] * m[11] - m[3] * m[10]) +
261
+ m[9] * (m[2] * m[7] - m[3] * m[6]))
262
+ end
263
+
264
+ def +(other)
265
+ Mat4.new(@data.zip(other.data).map { |a, b| a + b })
266
+ end
267
+
268
+ def -(other)
269
+ Mat4.new(@data.zip(other.data).map { |a, b| a - b })
270
+ end
271
+
272
+ def ==(other)
273
+ return false unless other.is_a?(Mat4)
274
+
275
+ @data == other.data
276
+ end
277
+
278
+ def near?(other, epsilon = 1e-6)
279
+ @data.zip(other.data).all? { |a, b| (a - b).abs < epsilon }
280
+ end
281
+
282
+ def extract_translation
283
+ Vec3.new(@data[12], @data[13], @data[14])
284
+ end
285
+
286
+ def extract_scale
287
+ sx = Vec3.new(@data[0], @data[1], @data[2]).length
288
+ sy = Vec3.new(@data[4], @data[5], @data[6]).length
289
+ sz = Vec3.new(@data[8], @data[9], @data[10]).length
290
+ Vec3.new(sx, sy, sz)
291
+ end
292
+
293
+ def extract_rotation
294
+ scale = extract_scale
295
+ m00 = @data[0] / scale.x
296
+ m01 = @data[1] / scale.x
297
+ m02 = @data[2] / scale.x
298
+ m10 = @data[4] / scale.y
299
+ m11 = @data[5] / scale.y
300
+ m12 = @data[6] / scale.y
301
+ m20 = @data[8] / scale.z
302
+ m21 = @data[9] / scale.z
303
+ m22 = @data[10] / scale.z
304
+
305
+ trace = m00 + m11 + m22
306
+ if trace > 0
307
+ s = 0.5 / Math.sqrt(trace + 1.0)
308
+ Quat.new((m12 - m21) * s, (m20 - m02) * s, (m01 - m10) * s, 0.25 / s)
309
+ elsif m00 > m11 && m00 > m22
310
+ s = 2.0 * Math.sqrt(1.0 + m00 - m11 - m22)
311
+ Quat.new(0.25 * s, (m10 + m01) / s, (m20 + m02) / s, (m12 - m21) / s)
312
+ elsif m11 > m22
313
+ s = 2.0 * Math.sqrt(1.0 + m11 - m00 - m22)
314
+ Quat.new((m10 + m01) / s, 0.25 * s, (m21 + m12) / s, (m20 - m02) / s)
315
+ else
316
+ s = 2.0 * Math.sqrt(1.0 + m22 - m00 - m11)
317
+ Quat.new((m20 + m02) / s, (m21 + m12) / s, 0.25 * s, (m01 - m10) / s)
318
+ end
319
+ end
320
+
321
+ def inspect
322
+ "Mat4[\n" \
323
+ " #{@data[0..3].map { |v| format('%8.4f', v) }.join(', ')}\n" \
324
+ " #{@data[4..7].map { |v| format('%8.4f', v) }.join(', ')}\n" \
325
+ " #{@data[8..11].map { |v| format('%8.4f', v) }.join(', ')}\n" \
326
+ " #{@data[12..15].map { |v| format('%8.4f', v) }.join(', ')}\n]"
327
+ end
328
+ end
329
+ end