three-rb 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.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +19 -0
  3. data/LICENSE +21 -0
  4. data/README.md +153 -0
  5. data/docs/browser-runtime.md +153 -0
  6. data/docs/implementation-plan.md +874 -0
  7. data/docs/loaded-assets-design.md +400 -0
  8. data/docs/next-work.md +107 -0
  9. data/docs/publishing.md +64 -0
  10. data/docs/release-readiness.md +83 -0
  11. data/examples/browser/README.md +54 -0
  12. data/examples/browser/assets/animated_triangle.gltf +123 -0
  13. data/examples/browser/assets/checker.svg +11 -0
  14. data/examples/browser/assets/compressed_triangle.gltf +74 -0
  15. data/examples/browser/assets/studio.hdr +5 -0
  16. data/examples/browser/assets/triangle.gltf +67 -0
  17. data/examples/browser/composition/README.md +35 -0
  18. data/examples/browser/composition/boot.mjs +6 -0
  19. data/examples/browser/composition/index.html +136 -0
  20. data/examples/browser/composition/main.rb +216 -0
  21. data/examples/browser/composition/smoke_test.mjs +266 -0
  22. data/examples/browser/cube/README.md +41 -0
  23. data/examples/browser/cube/boot.mjs +6 -0
  24. data/examples/browser/cube/index.html +142 -0
  25. data/examples/browser/cube/main.rb +62 -0
  26. data/examples/browser/cube/smoke_test.mjs +99 -0
  27. data/examples/browser/cubemap/README.md +23 -0
  28. data/examples/browser/cubemap/boot.mjs +6 -0
  29. data/examples/browser/cubemap/index.html +142 -0
  30. data/examples/browser/cubemap/main.rb +84 -0
  31. data/examples/browser/cubemap/smoke_test.mjs +91 -0
  32. data/examples/browser/gltf/README.md +23 -0
  33. data/examples/browser/gltf/boot.mjs +6 -0
  34. data/examples/browser/gltf/index.html +142 -0
  35. data/examples/browser/gltf/main.rb +105 -0
  36. data/examples/browser/gltf/smoke_test.mjs +162 -0
  37. data/examples/browser/picking/README.md +33 -0
  38. data/examples/browser/picking/boot.mjs +6 -0
  39. data/examples/browser/picking/index.html +142 -0
  40. data/examples/browser/picking/main.rb +113 -0
  41. data/examples/browser/picking/smoke_test.mjs +78 -0
  42. data/examples/browser/postprocessing/README.md +26 -0
  43. data/examples/browser/postprocessing/boot.mjs +6 -0
  44. data/examples/browser/postprocessing/index.html +142 -0
  45. data/examples/browser/postprocessing/main.rb +117 -0
  46. data/examples/browser/postprocessing/smoke_test.mjs +121 -0
  47. data/examples/browser/primitives/README.md +33 -0
  48. data/examples/browser/primitives/boot.mjs +6 -0
  49. data/examples/browser/primitives/index.html +142 -0
  50. data/examples/browser/primitives/main.rb +116 -0
  51. data/examples/browser/primitives/smoke_test.mjs +113 -0
  52. data/examples/browser/serialization/README.md +33 -0
  53. data/examples/browser/serialization/boot.mjs +6 -0
  54. data/examples/browser/serialization/index.html +142 -0
  55. data/examples/browser/serialization/main.rb +97 -0
  56. data/examples/browser/serialization/smoke_test.mjs +67 -0
  57. data/examples/browser/shared/boot.mjs +79 -0
  58. data/examples/browser/shared/smoke_test_helpers.mjs +151 -0
  59. data/examples/browser/textures/README.md +35 -0
  60. data/examples/browser/textures/boot.mjs +6 -0
  61. data/examples/browser/textures/index.html +142 -0
  62. data/examples/browser/textures/main.rb +142 -0
  63. data/examples/browser/textures/smoke_test.mjs +189 -0
  64. data/lib/three/animation/animation_action.rb +57 -0
  65. data/lib/three/animation/animation_clip.rb +22 -0
  66. data/lib/three/animation/animation_mixer.rb +43 -0
  67. data/lib/three/backends/base.rb +87 -0
  68. data/lib/three/backends/threejs/materialization.rb +143 -0
  69. data/lib/three/backends/threejs/parameters.rb +97 -0
  70. data/lib/three/backends/threejs/resource_management.rb +69 -0
  71. data/lib/three/backends/threejs/ruby_wasm_adapter.rb +873 -0
  72. data/lib/three/backends/threejs/synchronization.rb +224 -0
  73. data/lib/three/backends/threejs.rb +365 -0
  74. data/lib/three/cameras/camera.rb +39 -0
  75. data/lib/three/cameras/orthographic_camera.rb +107 -0
  76. data/lib/three/cameras/perspective_camera.rb +137 -0
  77. data/lib/three/constants.rb +40 -0
  78. data/lib/three/controls/orbit_controls.rb +118 -0
  79. data/lib/three/core/buffer_attribute.rb +151 -0
  80. data/lib/three/core/buffer_geometry.rb +181 -0
  81. data/lib/three/core/clock.rb +58 -0
  82. data/lib/three/core/event_dispatcher.rb +57 -0
  83. data/lib/three/core/layers.rb +75 -0
  84. data/lib/three/core/object3d.rb +331 -0
  85. data/lib/three/core/raycaster.rb +73 -0
  86. data/lib/three/dirty.rb +58 -0
  87. data/lib/three/exporters/three_json_exporter.rb +187 -0
  88. data/lib/three/geometries/box_geometry.rb +97 -0
  89. data/lib/three/geometries/plane_geometry.rb +70 -0
  90. data/lib/three/geometries/sphere_geometry.rb +107 -0
  91. data/lib/three/lights/ambient_light.rb +12 -0
  92. data/lib/three/lights/directional_light.rb +38 -0
  93. data/lib/three/lights/hemisphere_light.rb +34 -0
  94. data/lib/three/lights/light.rb +85 -0
  95. data/lib/three/lights/point_light.rb +33 -0
  96. data/lib/three/loaders/cube_texture_loader.rb +13 -0
  97. data/lib/three/loaders/gltf_loader.rb +48 -0
  98. data/lib/three/loaders/rgbe_loader.rb +15 -0
  99. data/lib/three/loaders/texture_loader.rb +13 -0
  100. data/lib/three/loaders/three_json_loader.rb +409 -0
  101. data/lib/three/materials/line_basic_material.rb +65 -0
  102. data/lib/three/materials/material.rb +158 -0
  103. data/lib/three/materials/mesh_basic_material.rb +64 -0
  104. data/lib/three/materials/mesh_lambert_material.rb +71 -0
  105. data/lib/three/materials/mesh_matcap_material.rb +86 -0
  106. data/lib/three/materials/mesh_normal_material.rb +42 -0
  107. data/lib/three/materials/mesh_phong_material.rb +119 -0
  108. data/lib/three/materials/mesh_physical_material.rb +155 -0
  109. data/lib/three/materials/mesh_standard_material.rb +149 -0
  110. data/lib/three/materials/mesh_toon_material.rb +98 -0
  111. data/lib/three/materials/points_material.rb +74 -0
  112. data/lib/three/materials/shadow_material.rb +45 -0
  113. data/lib/three/materials/sprite_material.rb +75 -0
  114. data/lib/three/math/color.rb +133 -0
  115. data/lib/three/math/euler.rb +197 -0
  116. data/lib/three/math/math_utils.rb +36 -0
  117. data/lib/three/math/matrix3.rb +255 -0
  118. data/lib/three/math/matrix4.rb +448 -0
  119. data/lib/three/math/quaternion.rb +277 -0
  120. data/lib/three/math/vector2.rb +95 -0
  121. data/lib/three/math/vector3.rb +396 -0
  122. data/lib/three/objects/external_object3d.rb +28 -0
  123. data/lib/three/objects/group.rb +12 -0
  124. data/lib/three/objects/instanced_mesh.rb +110 -0
  125. data/lib/three/objects/line.rb +41 -0
  126. data/lib/three/objects/mesh.rb +45 -0
  127. data/lib/three/objects/points.rb +41 -0
  128. data/lib/three/objects/sprite.rb +57 -0
  129. data/lib/three/postprocessing/dot_screen_pass.rb +83 -0
  130. data/lib/three/postprocessing/effect_composer.rb +56 -0
  131. data/lib/three/postprocessing/output_pass.rb +40 -0
  132. data/lib/three/postprocessing/render_pass.rb +42 -0
  133. data/lib/three/postprocessing/unreal_bloom_pass.rb +56 -0
  134. data/lib/three/renderers/renderer.rb +11 -0
  135. data/lib/three/renderers/threejs_renderer.rb +85 -0
  136. data/lib/three/scenes/scene.rb +29 -0
  137. data/lib/three/textures/cube_texture.rb +72 -0
  138. data/lib/three/textures/rgbe_texture.rb +45 -0
  139. data/lib/three/textures/texture.rb +200 -0
  140. data/lib/three/version.rb +5 -0
  141. data/lib/three-rb.rb +3 -0
  142. data/lib/three.rb +77 -0
  143. data/package.json +30 -0
  144. data/pnpm-lock.yaml +86 -0
  145. metadata +216 -0
@@ -0,0 +1,448 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "vector3"
4
+
5
+ module Three
6
+ class Matrix4
7
+ attr_reader :elements
8
+
9
+ def initialize(*values)
10
+ @elements = [
11
+ 1, 0, 0, 0,
12
+ 0, 1, 0, 0,
13
+ 0, 0, 1, 0,
14
+ 0, 0, 0, 1
15
+ ]
16
+
17
+ set(*values) unless values.empty?
18
+ end
19
+
20
+ def set(n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44)
21
+ @elements[0] = n11
22
+ @elements[4] = n12
23
+ @elements[8] = n13
24
+ @elements[12] = n14
25
+ @elements[1] = n21
26
+ @elements[5] = n22
27
+ @elements[9] = n23
28
+ @elements[13] = n24
29
+ @elements[2] = n31
30
+ @elements[6] = n32
31
+ @elements[10] = n33
32
+ @elements[14] = n34
33
+ @elements[3] = n41
34
+ @elements[7] = n42
35
+ @elements[11] = n43
36
+ @elements[15] = n44
37
+ self
38
+ end
39
+
40
+ def identity
41
+ set(
42
+ 1, 0, 0, 0,
43
+ 0, 1, 0, 0,
44
+ 0, 0, 1, 0,
45
+ 0, 0, 0, 1
46
+ )
47
+ end
48
+
49
+ def clone
50
+ self.class.new.from_array(@elements)
51
+ end
52
+
53
+ def copy(matrix)
54
+ @elements = matrix.elements.dup
55
+ self
56
+ end
57
+
58
+ def copy_position(matrix)
59
+ @elements[12] = matrix.elements[12]
60
+ @elements[13] = matrix.elements[13]
61
+ @elements[14] = matrix.elements[14]
62
+ self
63
+ end
64
+
65
+ def make_translation(x, y = nil, z = nil)
66
+ if y.nil? && z.nil?
67
+ y = x.y
68
+ z = x.z
69
+ x = x.x
70
+ end
71
+
72
+ set(
73
+ 1, 0, 0, x,
74
+ 0, 1, 0, y,
75
+ 0, 0, 1, z,
76
+ 0, 0, 0, 1
77
+ )
78
+ end
79
+
80
+ def make_scale(x, y, z)
81
+ set(
82
+ x, 0, 0, 0,
83
+ 0, y, 0, 0,
84
+ 0, 0, z, 0,
85
+ 0, 0, 0, 1
86
+ )
87
+ end
88
+
89
+ def make_rotation_from_quaternion(quaternion)
90
+ compose(Vector3.new, quaternion, Vector3.new(1, 1, 1))
91
+ end
92
+
93
+ def set_position(x, y = nil, z = nil)
94
+ if y.nil? && z.nil?
95
+ @elements[12] = x.x
96
+ @elements[13] = x.y
97
+ @elements[14] = x.z
98
+ else
99
+ @elements[12] = x
100
+ @elements[13] = y
101
+ @elements[14] = z
102
+ end
103
+
104
+ self
105
+ end
106
+
107
+ def multiply(matrix)
108
+ multiply_matrices(self, matrix)
109
+ end
110
+
111
+ def premultiply(matrix)
112
+ multiply_matrices(matrix, self)
113
+ end
114
+
115
+ def multiply_matrices(a, b)
116
+ ae = a.elements
117
+ be = b.elements
118
+
119
+ a11 = ae[0]
120
+ a12 = ae[4]
121
+ a13 = ae[8]
122
+ a14 = ae[12]
123
+ a21 = ae[1]
124
+ a22 = ae[5]
125
+ a23 = ae[9]
126
+ a24 = ae[13]
127
+ a31 = ae[2]
128
+ a32 = ae[6]
129
+ a33 = ae[10]
130
+ a34 = ae[14]
131
+ a41 = ae[3]
132
+ a42 = ae[7]
133
+ a43 = ae[11]
134
+ a44 = ae[15]
135
+
136
+ b11 = be[0]
137
+ b12 = be[4]
138
+ b13 = be[8]
139
+ b14 = be[12]
140
+ b21 = be[1]
141
+ b22 = be[5]
142
+ b23 = be[9]
143
+ b24 = be[13]
144
+ b31 = be[2]
145
+ b32 = be[6]
146
+ b33 = be[10]
147
+ b34 = be[14]
148
+ b41 = be[3]
149
+ b42 = be[7]
150
+ b43 = be[11]
151
+ b44 = be[15]
152
+
153
+ @elements[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41
154
+ @elements[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42
155
+ @elements[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43
156
+ @elements[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44
157
+
158
+ @elements[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41
159
+ @elements[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42
160
+ @elements[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43
161
+ @elements[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44
162
+
163
+ @elements[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41
164
+ @elements[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42
165
+ @elements[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43
166
+ @elements[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44
167
+
168
+ @elements[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41
169
+ @elements[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42
170
+ @elements[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43
171
+ @elements[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44
172
+ self
173
+ end
174
+
175
+ def multiply_scalar(scalar)
176
+ @elements.map! { |value| value * scalar }
177
+ self
178
+ end
179
+
180
+ def determinant
181
+ n11 = @elements[0]
182
+ n12 = @elements[4]
183
+ n13 = @elements[8]
184
+ n14 = @elements[12]
185
+ n21 = @elements[1]
186
+ n22 = @elements[5]
187
+ n23 = @elements[9]
188
+ n24 = @elements[13]
189
+ n31 = @elements[2]
190
+ n32 = @elements[6]
191
+ n33 = @elements[10]
192
+ n34 = @elements[14]
193
+ n41 = @elements[3]
194
+ n42 = @elements[7]
195
+ n43 = @elements[11]
196
+ n44 = @elements[15]
197
+
198
+ t11 = n23 * n34 - n24 * n33
199
+ t12 = n22 * n34 - n24 * n32
200
+ t13 = n22 * n33 - n23 * n32
201
+ t21 = n21 * n34 - n24 * n31
202
+ t22 = n21 * n33 - n23 * n31
203
+ t23 = n21 * n32 - n22 * n31
204
+
205
+ n11 * (n42 * t11 - n43 * t12 + n44 * t13) -
206
+ n12 * (n41 * t11 - n43 * t21 + n44 * t22) +
207
+ n13 * (n41 * t12 - n42 * t21 + n44 * t23) -
208
+ n14 * (n41 * t13 - n42 * t22 + n43 * t23)
209
+ end
210
+
211
+ def transpose
212
+ @elements[1], @elements[4] = @elements[4], @elements[1]
213
+ @elements[2], @elements[8] = @elements[8], @elements[2]
214
+ @elements[6], @elements[9] = @elements[9], @elements[6]
215
+ @elements[3], @elements[12] = @elements[12], @elements[3]
216
+ @elements[7], @elements[13] = @elements[13], @elements[7]
217
+ @elements[11], @elements[14] = @elements[14], @elements[11]
218
+ self
219
+ end
220
+
221
+ def invert
222
+ n11 = @elements[0]
223
+ n21 = @elements[1]
224
+ n31 = @elements[2]
225
+ n41 = @elements[3]
226
+ n12 = @elements[4]
227
+ n22 = @elements[5]
228
+ n32 = @elements[6]
229
+ n42 = @elements[7]
230
+ n13 = @elements[8]
231
+ n23 = @elements[9]
232
+ n33 = @elements[10]
233
+ n43 = @elements[11]
234
+ n14 = @elements[12]
235
+ n24 = @elements[13]
236
+ n34 = @elements[14]
237
+ n44 = @elements[15]
238
+
239
+ t1 = n11 * n22 - n21 * n12
240
+ t2 = n11 * n32 - n31 * n12
241
+ t3 = n11 * n42 - n41 * n12
242
+ t4 = n21 * n32 - n31 * n22
243
+ t5 = n21 * n42 - n41 * n22
244
+ t6 = n31 * n42 - n41 * n32
245
+ t7 = n13 * n24 - n23 * n14
246
+ t8 = n13 * n34 - n33 * n14
247
+ t9 = n13 * n44 - n43 * n14
248
+ t10 = n23 * n34 - n33 * n24
249
+ t11 = n23 * n44 - n43 * n24
250
+ t12 = n33 * n44 - n43 * n34
251
+
252
+ det = t1 * t12 - t2 * t11 + t3 * t10 + t4 * t9 - t5 * t8 + t6 * t7
253
+ return set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) if det.zero?
254
+
255
+ det_inv = 1.0 / det
256
+
257
+ @elements[0] = (n22 * t12 - n32 * t11 + n42 * t10) * det_inv
258
+ @elements[1] = (n31 * t11 - n21 * t12 - n41 * t10) * det_inv
259
+ @elements[2] = (n24 * t6 - n34 * t5 + n44 * t4) * det_inv
260
+ @elements[3] = (n33 * t5 - n23 * t6 - n43 * t4) * det_inv
261
+ @elements[4] = (n32 * t9 - n12 * t12 - n42 * t8) * det_inv
262
+ @elements[5] = (n11 * t12 - n31 * t9 + n41 * t8) * det_inv
263
+ @elements[6] = (n34 * t3 - n14 * t6 - n44 * t2) * det_inv
264
+ @elements[7] = (n13 * t6 - n33 * t3 + n43 * t2) * det_inv
265
+ @elements[8] = (n12 * t11 - n22 * t9 + n42 * t7) * det_inv
266
+ @elements[9] = (n21 * t9 - n11 * t11 - n41 * t7) * det_inv
267
+ @elements[10] = (n14 * t5 - n24 * t3 + n44 * t1) * det_inv
268
+ @elements[11] = (n23 * t3 - n13 * t5 - n43 * t1) * det_inv
269
+ @elements[12] = (n22 * t8 - n12 * t10 - n32 * t7) * det_inv
270
+ @elements[13] = (n11 * t10 - n21 * t8 + n31 * t7) * det_inv
271
+ @elements[14] = (n24 * t2 - n14 * t4 - n34 * t1) * det_inv
272
+ @elements[15] = (n13 * t4 - n23 * t2 + n33 * t1) * det_inv
273
+ self
274
+ end
275
+
276
+ def scale(vector)
277
+ @elements[0] *= vector.x
278
+ @elements[4] *= vector.y
279
+ @elements[8] *= vector.z
280
+ @elements[1] *= vector.x
281
+ @elements[5] *= vector.y
282
+ @elements[9] *= vector.z
283
+ @elements[2] *= vector.x
284
+ @elements[6] *= vector.y
285
+ @elements[10] *= vector.z
286
+ @elements[3] *= vector.x
287
+ @elements[7] *= vector.y
288
+ @elements[11] *= vector.z
289
+ self
290
+ end
291
+
292
+ def make_perspective(left, right, top, bottom, near, far)
293
+ x = 2.0 * near / (right - left)
294
+ y = 2.0 * near / (top - bottom)
295
+ a = (right + left).to_f / (right - left)
296
+ b = (top + bottom).to_f / (top - bottom)
297
+ c = -(far + near).to_f / (far - near)
298
+ d = (-2.0 * far * near) / (far - near)
299
+
300
+ @elements[0] = x
301
+ @elements[4] = 0
302
+ @elements[8] = a
303
+ @elements[12] = 0
304
+ @elements[1] = 0
305
+ @elements[5] = y
306
+ @elements[9] = b
307
+ @elements[13] = 0
308
+ @elements[2] = 0
309
+ @elements[6] = 0
310
+ @elements[10] = c
311
+ @elements[14] = d
312
+ @elements[3] = 0
313
+ @elements[7] = 0
314
+ @elements[11] = -1
315
+ @elements[15] = 0
316
+ self
317
+ end
318
+
319
+ def make_orthographic(left, right, top, bottom, near, far)
320
+ w = 1.0 / (right - left)
321
+ h = 1.0 / (top - bottom)
322
+ p = 1.0 / (far - near)
323
+ x = (right + left) * w
324
+ y = (top + bottom) * h
325
+ z = (far + near) * p
326
+
327
+ @elements[0] = 2 * w
328
+ @elements[4] = 0
329
+ @elements[8] = 0
330
+ @elements[12] = -x
331
+ @elements[1] = 0
332
+ @elements[5] = 2 * h
333
+ @elements[9] = 0
334
+ @elements[13] = -y
335
+ @elements[2] = 0
336
+ @elements[6] = 0
337
+ @elements[10] = -2 * p
338
+ @elements[14] = -z
339
+ @elements[3] = 0
340
+ @elements[7] = 0
341
+ @elements[11] = 0
342
+ @elements[15] = 1
343
+ self
344
+ end
345
+
346
+ def compose(position, quaternion, scale)
347
+ x = quaternion.x
348
+ y = quaternion.y
349
+ z = quaternion.z
350
+ w = quaternion.w
351
+ x2 = x + x
352
+ y2 = y + y
353
+ z2 = z + z
354
+ xx = x * x2
355
+ xy = x * y2
356
+ xz = x * z2
357
+ yy = y * y2
358
+ yz = y * z2
359
+ zz = z * z2
360
+ wx = w * x2
361
+ wy = w * y2
362
+ wz = w * z2
363
+ sx = scale.x
364
+ sy = scale.y
365
+ sz = scale.z
366
+
367
+ @elements[0] = (1 - (yy + zz)) * sx
368
+ @elements[1] = (xy + wz) * sx
369
+ @elements[2] = (xz - wy) * sx
370
+ @elements[3] = 0
371
+
372
+ @elements[4] = (xy - wz) * sy
373
+ @elements[5] = (1 - (xx + zz)) * sy
374
+ @elements[6] = (yz + wx) * sy
375
+ @elements[7] = 0
376
+
377
+ @elements[8] = (xz + wy) * sz
378
+ @elements[9] = (yz - wx) * sz
379
+ @elements[10] = (1 - (xx + yy)) * sz
380
+ @elements[11] = 0
381
+
382
+ @elements[12] = position.x
383
+ @elements[13] = position.y
384
+ @elements[14] = position.z
385
+ @elements[15] = 1
386
+ self
387
+ end
388
+
389
+ def decompose(position, quaternion, scale)
390
+ position.set(@elements[12], @elements[13], @elements[14])
391
+
392
+ det = determinant
393
+ if det.zero?
394
+ scale.set(1, 1, 1)
395
+ quaternion.identity
396
+ return self
397
+ end
398
+
399
+ sx = Vector3.new(@elements[0], @elements[1], @elements[2]).length
400
+ sy = Vector3.new(@elements[4], @elements[5], @elements[6]).length
401
+ sz = Vector3.new(@elements[8], @elements[9], @elements[10]).length
402
+ sx = -sx if det.negative?
403
+
404
+ matrix = clone
405
+ matrix.elements[0] *= 1.0 / sx
406
+ matrix.elements[1] *= 1.0 / sx
407
+ matrix.elements[2] *= 1.0 / sx
408
+ matrix.elements[4] *= 1.0 / sy
409
+ matrix.elements[5] *= 1.0 / sy
410
+ matrix.elements[6] *= 1.0 / sy
411
+ matrix.elements[8] *= 1.0 / sz
412
+ matrix.elements[9] *= 1.0 / sz
413
+ matrix.elements[10] *= 1.0 / sz
414
+
415
+ quaternion.set_from_rotation_matrix(matrix)
416
+ scale.set(sx, sy, sz)
417
+ self
418
+ end
419
+
420
+ def from_array(array, offset = 0)
421
+ 16.times { |index| @elements[index] = array[index + offset] }
422
+ self
423
+ end
424
+
425
+ def to_a
426
+ @elements.dup
427
+ end
428
+
429
+ def to_array(array = [], offset = 0)
430
+ 16.times { |index| array[index + offset] = @elements[index] }
431
+ array
432
+ end
433
+
434
+ def equals?(matrix, epsilon: 0.0)
435
+ @elements.each_with_index.all? do |value, index|
436
+ (value - matrix.elements[index]).abs <= epsilon
437
+ end
438
+ end
439
+
440
+ def ==(other)
441
+ other.is_a?(self.class) && equals?(other)
442
+ end
443
+
444
+ def inspect
445
+ "#<#{self.class} elements=#{@elements.inspect}>"
446
+ end
447
+ end
448
+ end
@@ -0,0 +1,277 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "math_utils"
4
+
5
+ module Three
6
+ class Quaternion
7
+ include Enumerable
8
+
9
+ attr_reader :x, :y, :z, :w
10
+
11
+ def initialize(x = 0, y = 0, z = 0, w = 1)
12
+ @x = x
13
+ @y = y
14
+ @z = z
15
+ @w = w
16
+ @on_change_callback = proc {}
17
+ end
18
+
19
+ def x=(value)
20
+ @x = value
21
+ changed!
22
+ end
23
+
24
+ def y=(value)
25
+ @y = value
26
+ changed!
27
+ end
28
+
29
+ def z=(value)
30
+ @z = value
31
+ changed!
32
+ end
33
+
34
+ def w=(value)
35
+ @w = value
36
+ changed!
37
+ end
38
+
39
+ def set(x, y, z, w, update: true)
40
+ @x = x
41
+ @y = y
42
+ @z = z
43
+ @w = w
44
+ changed! if update
45
+ self
46
+ end
47
+
48
+ def identity(update: true)
49
+ set(0, 0, 0, 1, update: update)
50
+ end
51
+
52
+ def clone
53
+ self.class.new(@x, @y, @z, @w)
54
+ end
55
+
56
+ def copy(quaternion, update: true)
57
+ set(quaternion.x, quaternion.y, quaternion.z, quaternion.w, update: update)
58
+ end
59
+
60
+ def set_from_euler(euler, update: true)
61
+ c1 = Math.cos(euler.x / 2.0)
62
+ c2 = Math.cos(euler.y / 2.0)
63
+ c3 = Math.cos(euler.z / 2.0)
64
+ s1 = Math.sin(euler.x / 2.0)
65
+ s2 = Math.sin(euler.y / 2.0)
66
+ s3 = Math.sin(euler.z / 2.0)
67
+
68
+ case euler.order
69
+ when "XYZ"
70
+ @x = s1 * c2 * c3 + c1 * s2 * s3
71
+ @y = c1 * s2 * c3 - s1 * c2 * s3
72
+ @z = c1 * c2 * s3 + s1 * s2 * c3
73
+ @w = c1 * c2 * c3 - s1 * s2 * s3
74
+ when "YXZ"
75
+ @x = s1 * c2 * c3 + c1 * s2 * s3
76
+ @y = c1 * s2 * c3 - s1 * c2 * s3
77
+ @z = c1 * c2 * s3 - s1 * s2 * c3
78
+ @w = c1 * c2 * c3 + s1 * s2 * s3
79
+ when "ZXY"
80
+ @x = s1 * c2 * c3 - c1 * s2 * s3
81
+ @y = c1 * s2 * c3 + s1 * c2 * s3
82
+ @z = c1 * c2 * s3 + s1 * s2 * c3
83
+ @w = c1 * c2 * c3 - s1 * s2 * s3
84
+ when "ZYX"
85
+ @x = s1 * c2 * c3 - c1 * s2 * s3
86
+ @y = c1 * s2 * c3 + s1 * c2 * s3
87
+ @z = c1 * c2 * s3 - s1 * s2 * c3
88
+ @w = c1 * c2 * c3 + s1 * s2 * s3
89
+ when "YZX"
90
+ @x = s1 * c2 * c3 + c1 * s2 * s3
91
+ @y = c1 * s2 * c3 + s1 * c2 * s3
92
+ @z = c1 * c2 * s3 - s1 * s2 * c3
93
+ @w = c1 * c2 * c3 - s1 * s2 * s3
94
+ when "XZY"
95
+ @x = s1 * c2 * c3 - c1 * s2 * s3
96
+ @y = c1 * s2 * c3 - s1 * c2 * s3
97
+ @z = c1 * c2 * s3 + s1 * s2 * c3
98
+ @w = c1 * c2 * c3 + s1 * s2 * s3
99
+ else
100
+ raise ArgumentError, "unknown Euler order: #{euler.order}"
101
+ end
102
+
103
+ changed! if update
104
+ self
105
+ end
106
+
107
+ def set_from_axis_angle(axis, angle, update: true)
108
+ half_angle = angle / 2.0
109
+ s = Math.sin(half_angle)
110
+
111
+ @x = axis.x * s
112
+ @y = axis.y * s
113
+ @z = axis.z * s
114
+ @w = Math.cos(half_angle)
115
+ changed! if update
116
+ self
117
+ end
118
+
119
+ def set_from_rotation_matrix(matrix, update: true)
120
+ elements = matrix.elements
121
+ m11 = elements[0]
122
+ m12 = elements[4]
123
+ m13 = elements[8]
124
+ m21 = elements[1]
125
+ m22 = elements[5]
126
+ m23 = elements[9]
127
+ m31 = elements[2]
128
+ m32 = elements[6]
129
+ m33 = elements[10]
130
+ trace = m11 + m22 + m33
131
+
132
+ if trace.positive?
133
+ s = 0.5 / Math.sqrt(trace + 1.0)
134
+ @w = 0.25 / s
135
+ @x = (m32 - m23) * s
136
+ @y = (m13 - m31) * s
137
+ @z = (m21 - m12) * s
138
+ elsif m11 > m22 && m11 > m33
139
+ s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33)
140
+ @w = (m32 - m23) / s
141
+ @x = 0.25 * s
142
+ @y = (m12 + m21) / s
143
+ @z = (m13 + m31) / s
144
+ elsif m22 > m33
145
+ s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33)
146
+ @w = (m13 - m31) / s
147
+ @x = (m12 + m21) / s
148
+ @y = 0.25 * s
149
+ @z = (m23 + m32) / s
150
+ else
151
+ s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22)
152
+ @w = (m21 - m12) / s
153
+ @x = (m13 + m31) / s
154
+ @y = (m23 + m32) / s
155
+ @z = 0.25 * s
156
+ end
157
+
158
+ changed! if update
159
+ self
160
+ end
161
+
162
+ def multiply(quaternion)
163
+ multiply_quaternions(self, quaternion)
164
+ end
165
+
166
+ def premultiply(quaternion)
167
+ multiply_quaternions(quaternion, self)
168
+ end
169
+
170
+ def multiply_quaternions(a, b)
171
+ qax = a.x
172
+ qay = a.y
173
+ qaz = a.z
174
+ qaw = a.w
175
+ qbx = b.x
176
+ qby = b.y
177
+ qbz = b.z
178
+ qbw = b.w
179
+
180
+ @x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby
181
+ @y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz
182
+ @z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx
183
+ @w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz
184
+ changed!
185
+ self
186
+ end
187
+
188
+ def conjugate
189
+ @x *= -1
190
+ @y *= -1
191
+ @z *= -1
192
+ changed!
193
+ self
194
+ end
195
+
196
+ def invert
197
+ conjugate
198
+ end
199
+
200
+ def dot(quaternion)
201
+ @x * quaternion.x + @y * quaternion.y + @z * quaternion.z + @w * quaternion.w
202
+ end
203
+
204
+ def length_sq
205
+ dot(self)
206
+ end
207
+
208
+ def length
209
+ Math.sqrt(length_sq)
210
+ end
211
+
212
+ def normalize
213
+ current_length = length
214
+
215
+ if current_length.zero?
216
+ @x = 0
217
+ @y = 0
218
+ @z = 0
219
+ @w = 1
220
+ else
221
+ scale = 1.0 / current_length
222
+ @x *= scale
223
+ @y *= scale
224
+ @z *= scale
225
+ @w *= scale
226
+ end
227
+
228
+ changed!
229
+ self
230
+ end
231
+
232
+ def equals?(quaternion, epsilon: 0.0)
233
+ (@x - quaternion.x).abs <= epsilon &&
234
+ (@y - quaternion.y).abs <= epsilon &&
235
+ (@z - quaternion.z).abs <= epsilon &&
236
+ (@w - quaternion.w).abs <= epsilon
237
+ end
238
+
239
+ def ==(other)
240
+ other.is_a?(self.class) && equals?(other)
241
+ end
242
+
243
+ def on_change(&callback)
244
+ @on_change_callback = callback || proc {}
245
+ self
246
+ end
247
+
248
+ alias _on_change on_change
249
+
250
+ def each
251
+ return enum_for(:each) unless block_given?
252
+
253
+ yield @x
254
+ yield @y
255
+ yield @z
256
+ yield @w
257
+ end
258
+
259
+ def to_a
260
+ [@x, @y, @z, @w]
261
+ end
262
+
263
+ def deconstruct
264
+ to_a
265
+ end
266
+
267
+ def inspect
268
+ "#<#{self.class} x=#{@x.inspect} y=#{@y.inspect} z=#{@z.inspect} w=#{@w.inspect}>"
269
+ end
270
+
271
+ private
272
+
273
+ def changed!
274
+ @on_change_callback.call
275
+ end
276
+ end
277
+ end