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,873 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Three
4
+ module Backends
5
+ class ThreeJS
6
+ class RubyWasmAdapter
7
+ TEXTURE_SLOTS = %w[
8
+ map
9
+ normalMap
10
+ roughnessMap
11
+ metalnessMap
12
+ aoMap
13
+ emissiveMap
14
+ alphaMap
15
+ bumpMap
16
+ displacementMap
17
+ envMap
18
+ gradientMap
19
+ lightMap
20
+ matcap
21
+ specularMap
22
+ anisotropyMap
23
+ clearcoatMap
24
+ clearcoatNormalMap
25
+ clearcoatRoughnessMap
26
+ transmissionMap
27
+ thicknessMap
28
+ iridescenceMap
29
+ iridescenceThicknessMap
30
+ sheenColorMap
31
+ sheenRoughnessMap
32
+ specularColorMap
33
+ specularIntensityMap
34
+ ].freeze
35
+
36
+ def initialize(three: nil)
37
+ @three = three || default_three
38
+ end
39
+
40
+ def new_webgl_renderer(options = {})
41
+ parameters = stringify_keys(options)
42
+ parameters["canvas"] = resolve_canvas(options[:canvas] || options["canvas"]) if options[:canvas] || options["canvas"]
43
+ @three[:WebGLRenderer].new(parameters)
44
+ end
45
+
46
+ def renderer_dom_element(renderer)
47
+ renderer[:domElement]
48
+ end
49
+
50
+ def set_renderer_size(renderer, width, height)
51
+ renderer.call(:setSize, width, height)
52
+ end
53
+
54
+ def set_clear_color(renderer, color, alpha = 1)
55
+ renderer.call(:setClearColor, color, alpha)
56
+ end
57
+
58
+ def set_renderer_shadow_map(renderer, enabled: nil, type: nil, auto_update: nil)
59
+ shadow_map = renderer[:shadowMap]
60
+ shadow_map[:enabled] = enabled unless enabled.nil?
61
+ shadow_map[:type] = type unless type.nil?
62
+ shadow_map[:autoUpdate] = auto_update unless auto_update.nil?
63
+ end
64
+
65
+ def set_animation_loop(renderer, callback)
66
+ renderer.call(:setAnimationLoop, callback)
67
+ end
68
+
69
+ def render(renderer, scene, camera)
70
+ render_helper = JS.global[:__threeRbRender]
71
+ if render_helper.typeof == "function"
72
+ JS.global[:__threeRbCurrentRenderer] = renderer
73
+ JS.global[:__threeRbCurrentScene] = scene
74
+ JS.global[:__threeRbCurrentCamera] = camera
75
+ JS.global.call(:__threeRbRender)
76
+ else
77
+ renderer.call(:render, scene, camera)
78
+ end
79
+ end
80
+
81
+ def new_effect_composer(renderer)
82
+ effect_composer_constructor.new(renderer)
83
+ end
84
+
85
+ def effect_composer_add_pass(composer, pass)
86
+ composer.call(:addPass, pass)
87
+ end
88
+
89
+ def effect_composer_set_size(composer, width, height)
90
+ composer.call(:setSize, width, height)
91
+ end
92
+
93
+ def effect_composer_render(composer)
94
+ render_helper = JS.global[:__threeRbRenderComposer]
95
+ if render_helper.typeof == "function"
96
+ JS.global[:__threeRbCurrentComposer] = composer
97
+ JS.global.call(:__threeRbRenderComposer)
98
+ else
99
+ composer.call(:render)
100
+ end
101
+ end
102
+
103
+ def dispose_effect_composer(composer)
104
+ composer.call(:dispose)
105
+ end
106
+
107
+ def new_render_pass(scene, camera)
108
+ render_pass_constructor.new(scene, camera)
109
+ end
110
+
111
+ def new_unreal_bloom_pass(resolution, strength, radius, threshold)
112
+ unreal_bloom_pass_constructor.new(@three[:Vector2].new(*resolution), strength, radius, threshold)
113
+ end
114
+
115
+ def new_dot_screen_pass(center, angle, scale)
116
+ dot_screen_pass_constructor.new(@three[:Vector2].new(*center), angle, scale)
117
+ end
118
+
119
+ def new_output_pass
120
+ output_pass_constructor.new
121
+ end
122
+
123
+ def set_postprocessing_pass_property(pass, name, value)
124
+ pass[name] = value
125
+ end
126
+
127
+ def set_postprocessing_pass_uniform(pass, name, value)
128
+ uniform = pass[:uniforms][name]
129
+ return unless js_present?(uniform)
130
+
131
+ target = uniform[:value]
132
+ if value.respond_to?(:to_a) && js_present?(target) && target.respond_to?(:call)
133
+ target.call(:set, *value.to_a)
134
+ else
135
+ uniform[:value] = value
136
+ end
137
+ end
138
+
139
+ def new_orbit_controls(camera, dom_element)
140
+ constructor = orbit_controls_constructor
141
+ dom_element ? constructor.new(camera, dom_element) : constructor.new(camera)
142
+ end
143
+
144
+ def new_raycaster
145
+ @three[:Raycaster].new
146
+ end
147
+
148
+ def set_raycaster_from_camera(raycaster, coords, camera)
149
+ raycaster.call(:setFromCamera, @three[:Vector2].new(*coords), camera)
150
+ end
151
+
152
+ def intersect_objects(raycaster, objects, recursive: false)
153
+ raycaster.call(:intersectObjects, js_array(objects), recursive).to_a
154
+ end
155
+
156
+ def load_texture(source, parameters = {})
157
+ texture = @three[:TextureLoader].new.call(:load, source)
158
+ update_texture(texture, parameters)
159
+ texture
160
+ end
161
+
162
+ def load_cube_texture(sources, parameters = {})
163
+ texture = @three[:CubeTextureLoader].new.call(:load, js_array(sources))
164
+ update_texture(texture, parameters)
165
+ texture
166
+ end
167
+
168
+ def load_rgbe_texture(source, parameters = {})
169
+ texture = rgbe_loader_constructor.new.call(:loadAsync, source)
170
+ texture = texture.await if texture.respond_to?(:await)
171
+ update_texture(texture, parameters)
172
+ texture
173
+ end
174
+
175
+ def load_gltf(source, draco_decoder_path: nil, draco_decoder_config: nil)
176
+ loader = gltf_loader_constructor.new
177
+ configure_draco_loader(loader, draco_decoder_path, draco_decoder_config) if draco_decoder_path
178
+ loader.call(:loadAsync, source)
179
+ end
180
+
181
+ def gltf_animations(gltf)
182
+ animations = gltf[:animations]
183
+ js_present?(animations) ? animations.to_a : []
184
+ end
185
+
186
+ def animation_clip_name(clip)
187
+ value = clip[:name]
188
+ js_present?(value) ? value.to_s : ""
189
+ end
190
+
191
+ def animation_clip_duration(clip)
192
+ value = clip[:duration]
193
+ js_present?(value) ? value.to_f : 0.0
194
+ end
195
+
196
+ def new_animation_mixer(root)
197
+ @three[:AnimationMixer].new(root)
198
+ end
199
+
200
+ def animation_mixer_clip_action(mixer, clip, root = nil)
201
+ root ? mixer.call(:clipAction, clip, root) : mixer.call(:clipAction, clip)
202
+ end
203
+
204
+ def update_animation_mixer(mixer, delta)
205
+ mixer.call(:update, delta)
206
+ end
207
+
208
+ def stop_all_animation_actions(mixer)
209
+ mixer.call(:stopAllAction)
210
+ end
211
+
212
+ def uncache_animation_root(mixer, root)
213
+ mixer.call(:uncacheRoot, root)
214
+ end
215
+
216
+ def set_animation_action_property(action, name, value)
217
+ action[name] = value
218
+ end
219
+
220
+ def play_animation_action(action)
221
+ action.call(:play)
222
+ end
223
+
224
+ def stop_animation_action(action)
225
+ action.call(:stop)
226
+ end
227
+
228
+ def reset_animation_action(action)
229
+ action.call(:reset)
230
+ end
231
+
232
+ def fade_in_animation_action(action, duration)
233
+ action.call(:fadeIn, duration)
234
+ end
235
+
236
+ def fade_out_animation_action(action, duration)
237
+ action.call(:fadeOut, duration)
238
+ end
239
+
240
+ def update_texture(texture, parameters)
241
+ texture[:mapping] = parameters[:mapping] unless parameters[:mapping].nil?
242
+ texture[:colorSpace] = parameters[:color_space] unless parameters[:color_space].nil?
243
+ texture[:flipY] = parameters[:flip_y] unless parameters[:flip_y].nil?
244
+ texture[:wrapS] = parameters[:wrap_s] unless parameters[:wrap_s].nil?
245
+ texture[:wrapT] = parameters[:wrap_t] unless parameters[:wrap_t].nil?
246
+ texture[:magFilter] = parameters[:mag_filter] unless parameters[:mag_filter].nil?
247
+ texture[:minFilter] = parameters[:min_filter] unless parameters[:min_filter].nil?
248
+ texture[:offset].call(:set, *parameters[:offset]) if parameters[:offset]
249
+ texture[:repeat].call(:set, *parameters[:repeat]) if parameters[:repeat]
250
+ texture[:center].call(:set, *parameters[:center]) if parameters[:center]
251
+ texture[:rotation] = parameters[:rotation] unless parameters[:rotation].nil?
252
+ texture[:matrixAutoUpdate] = parameters[:matrix_auto_update] unless parameters[:matrix_auto_update].nil?
253
+ texture[:matrix].call(:fromArray, js_array(parameters[:matrix])) if parameters[:matrix]
254
+ texture.call(:updateMatrix) if parameters[:matrix_auto_update]
255
+ texture[:needsUpdate] = true
256
+ texture
257
+ end
258
+
259
+ def set_control_property(control, name, value)
260
+ control[name] = value
261
+ end
262
+
263
+ def set_orbit_controls_target(control, target)
264
+ control[:target].call(:set, *target)
265
+ end
266
+
267
+ def update_controls(control)
268
+ control.call(:update)
269
+ end
270
+
271
+ def dispose_controls(control)
272
+ control.call(:dispose)
273
+ end
274
+
275
+ def object_transform(object)
276
+ [
277
+ js_vector_to_a(object[:position], 3),
278
+ js_vector_to_a(object[:quaternion], 4),
279
+ js_vector_to_a(object[:scale], 3)
280
+ ]
281
+ end
282
+
283
+ def new_scene
284
+ @three[:Scene].new
285
+ end
286
+
287
+ def new_group
288
+ @three[:Group].new
289
+ end
290
+
291
+ def new_object3d
292
+ @three[:Object3D].new
293
+ end
294
+
295
+ def new_perspective_camera(fov, aspect, near, far)
296
+ @three[:PerspectiveCamera].new(fov, aspect, near, far)
297
+ end
298
+
299
+ def new_orthographic_camera(left, right, top, bottom, near, far)
300
+ @three[:OrthographicCamera].new(left, right, top, bottom, near, far)
301
+ end
302
+
303
+ def new_ambient_light(color, intensity)
304
+ @three[:AmbientLight].new(color, intensity)
305
+ end
306
+
307
+ def new_directional_light(color, intensity)
308
+ @three[:DirectionalLight].new(color, intensity)
309
+ end
310
+
311
+ def new_point_light(color, intensity, distance, decay)
312
+ @three[:PointLight].new(color, intensity, distance, decay)
313
+ end
314
+
315
+ def new_hemisphere_light(sky_color, ground_color, intensity)
316
+ @three[:HemisphereLight].new(sky_color, ground_color, intensity)
317
+ end
318
+
319
+ def new_mesh(geometry, material)
320
+ @three[:Mesh].new(geometry, material)
321
+ end
322
+
323
+ def new_line(geometry, material)
324
+ @three[:Line].new(geometry, material)
325
+ end
326
+
327
+ def new_points(geometry, material)
328
+ @three[:Points].new(geometry, material)
329
+ end
330
+
331
+ def new_sprite(material)
332
+ @three[:Sprite].new(material)
333
+ end
334
+
335
+ def new_instanced_mesh(geometry, material, count)
336
+ @three[:InstancedMesh].new(geometry, material, count)
337
+ end
338
+
339
+ def set_object_geometry(object, geometry)
340
+ object[:geometry] = geometry
341
+ end
342
+
343
+ def set_object_material(object, material)
344
+ object[:material] = material
345
+ end
346
+
347
+ def set_mesh_geometry(mesh, geometry)
348
+ set_object_geometry(mesh, geometry)
349
+ end
350
+
351
+ def set_mesh_material(mesh, material)
352
+ set_object_material(mesh, material)
353
+ end
354
+
355
+ def set_sprite_center(sprite, center)
356
+ sprite[:center].call(:set, *center)
357
+ end
358
+
359
+ def set_object_layers(object, mask)
360
+ object[:layers][:mask] = mask if js_present?(object[:layers])
361
+ end
362
+
363
+ def set_instanced_mesh_count(mesh, count)
364
+ mesh[:count] = count
365
+ end
366
+
367
+ def set_instanced_mesh_matrix_at(mesh, index, elements)
368
+ matrix = @three[:Matrix4].new
369
+ matrix.call(:fromArray, js_array(elements))
370
+ mesh.call(:setMatrixAt, index, matrix)
371
+ end
372
+
373
+ def set_instanced_mesh_instance_matrix_needs_update(mesh, value)
374
+ mesh[:instanceMatrix][:needsUpdate] = value
375
+ end
376
+
377
+ def set_instanced_mesh_color_at(mesh, index, color)
378
+ mesh.call(:setColorAt, index, @three[:Color].new(*color))
379
+ end
380
+
381
+ def set_instanced_mesh_instance_color_needs_update(mesh, value)
382
+ instance_color = mesh[:instanceColor]
383
+ instance_color[:needsUpdate] = value if js_present?(instance_color)
384
+ end
385
+
386
+ def new_box_geometry(width, height, depth, width_segments, height_segments, depth_segments)
387
+ @three[:BoxGeometry].new(width, height, depth, width_segments, height_segments, depth_segments)
388
+ end
389
+
390
+ def new_plane_geometry(width, height, width_segments, height_segments)
391
+ @three[:PlaneGeometry].new(width, height, width_segments, height_segments)
392
+ end
393
+
394
+ def new_sphere_geometry(radius, width_segments, height_segments, phi_start, phi_length, theta_start, theta_length)
395
+ @three[:SphereGeometry].new(radius, width_segments, height_segments, phi_start, phi_length, theta_start, theta_length)
396
+ end
397
+
398
+ def new_buffer_geometry
399
+ @three[:BufferGeometry].new
400
+ end
401
+
402
+ def new_buffer_attribute(component_type, array, item_size, normalized)
403
+ typed_array = typed_array(component_type, array)
404
+ @three[:BufferAttribute].new(typed_array, item_size, normalized)
405
+ end
406
+
407
+ def set_geometry_index(geometry, attribute)
408
+ geometry.call(:setIndex, attribute)
409
+ end
410
+
411
+ def set_geometry_attribute(geometry, name, attribute)
412
+ geometry.call(:setAttribute, name.to_s, attribute)
413
+ end
414
+
415
+ def delete_geometry_attribute(geometry, name)
416
+ geometry.call(:deleteAttribute, name.to_s)
417
+ end
418
+
419
+ def clear_geometry_groups(geometry)
420
+ geometry.call(:clearGroups)
421
+ end
422
+
423
+ def add_geometry_group(geometry, start, count, material_index)
424
+ geometry.call(:addGroup, start, count, material_index)
425
+ end
426
+
427
+ def set_geometry_draw_range(geometry, start, count)
428
+ geometry.call(:setDrawRange, start, count)
429
+ end
430
+
431
+ def new_mesh_basic_material(parameters)
432
+ @three[:MeshBasicMaterial].new(stringify_keys(parameters))
433
+ end
434
+
435
+ def new_line_basic_material(parameters)
436
+ @three[:LineBasicMaterial].new(stringify_keys(parameters))
437
+ end
438
+
439
+ def new_mesh_lambert_material(parameters)
440
+ @three[:MeshLambertMaterial].new(stringify_keys(parameters))
441
+ end
442
+
443
+ def new_mesh_matcap_material(parameters)
444
+ @three[:MeshMatcapMaterial].new(stringify_keys(parameters))
445
+ end
446
+
447
+ def new_mesh_normal_material(parameters)
448
+ @three[:MeshNormalMaterial].new(stringify_keys(parameters))
449
+ end
450
+
451
+ def new_mesh_phong_material(parameters)
452
+ @three[:MeshPhongMaterial].new(stringify_keys(parameters))
453
+ end
454
+
455
+ def new_mesh_physical_material(parameters)
456
+ @three[:MeshPhysicalMaterial].new(stringify_keys(parameters))
457
+ end
458
+
459
+ def new_mesh_standard_material(parameters)
460
+ @three[:MeshStandardMaterial].new(stringify_keys(parameters))
461
+ end
462
+
463
+ def new_mesh_toon_material(parameters)
464
+ material = @three[:MeshToonMaterial].new(stringify_keys(parameters))
465
+ update_material(material, parameters)
466
+ material
467
+ end
468
+
469
+ def new_points_material(parameters)
470
+ @three[:PointsMaterial].new(stringify_keys(parameters))
471
+ end
472
+
473
+ def new_shadow_material(parameters)
474
+ @three[:ShadowMaterial].new(stringify_keys(parameters))
475
+ end
476
+
477
+ def new_sprite_material(parameters)
478
+ @three[:SpriteMaterial].new(stringify_keys(parameters))
479
+ end
480
+
481
+ def set_object_name(object, name)
482
+ object[:name] = name
483
+ end
484
+
485
+ def set_object_visible(object, visible)
486
+ object[:visible] = visible
487
+ end
488
+
489
+ def set_object_shadow(object, cast_shadow, receive_shadow)
490
+ object[:castShadow] = cast_shadow
491
+ object[:receiveShadow] = receive_shadow
492
+ end
493
+
494
+ def set_object_transform(object, position, quaternion, scale)
495
+ object[:position].call(:set, *position)
496
+ object[:quaternion].call(:set, *quaternion)
497
+ object[:scale].call(:set, *scale)
498
+ end
499
+
500
+ def update_perspective_camera(camera, fov, aspect, near, far, zoom)
501
+ camera[:fov] = fov
502
+ camera[:aspect] = aspect
503
+ camera[:near] = near
504
+ camera[:far] = far
505
+ camera[:zoom] = zoom
506
+ camera.call(:updateProjectionMatrix)
507
+ end
508
+
509
+ def update_orthographic_camera(camera, left, right, top, bottom, near, far, zoom)
510
+ camera[:left] = left
511
+ camera[:right] = right
512
+ camera[:top] = top
513
+ camera[:bottom] = bottom
514
+ camera[:near] = near
515
+ camera[:far] = far
516
+ camera[:zoom] = zoom
517
+ camera.call(:updateProjectionMatrix)
518
+ end
519
+
520
+ def update_light(light, color, intensity)
521
+ light[:color].call(:setHex, color)
522
+ light[:intensity] = intensity
523
+ end
524
+
525
+ def update_point_light(light, color, intensity, distance, decay)
526
+ update_light(light, color, intensity)
527
+ light[:distance] = distance
528
+ light[:decay] = decay
529
+ end
530
+
531
+ def update_hemisphere_light(light, sky_color, ground_color, intensity)
532
+ update_light(light, sky_color, intensity)
533
+ light[:groundColor].call(:setHex, ground_color)
534
+ end
535
+
536
+ def update_light_shadow(light, parameters)
537
+ shadow = light[:shadow]
538
+ return unless js_present?(shadow)
539
+
540
+ if parameters[:map_size]
541
+ shadow[:mapSize].call(:set, *parameters[:map_size])
542
+ end
543
+ shadow[:bias] = parameters[:bias] unless parameters[:bias].nil?
544
+ shadow[:normalBias] = parameters[:normal_bias] unless parameters[:normal_bias].nil?
545
+ shadow[:radius] = parameters[:radius] unless parameters[:radius].nil?
546
+
547
+ camera = shadow[:camera]
548
+ if js_present?(camera) && parameters[:camera]
549
+ parameters[:camera].each do |key, value|
550
+ camera[key] = value
551
+ end
552
+ camera.call(:updateProjectionMatrix)
553
+ end
554
+ end
555
+
556
+ def update_material(material, parameters)
557
+ parameters.each do |key, value|
558
+ if MATERIAL_COLOR_PARAMETERS.include?(key)
559
+ material[key].call(:setHex, value)
560
+ else
561
+ material[key] = value
562
+ end
563
+ end
564
+ material[:needsUpdate] = true
565
+ end
566
+
567
+ def add_child(parent, child)
568
+ parent.call(:add, child)
569
+ end
570
+
571
+ def clear_children(parent)
572
+ parent.call(:clear)
573
+ end
574
+
575
+ def set_scene_background(scene, background)
576
+ scene[:background] = background
577
+ end
578
+
579
+ def set_scene_environment(scene, environment)
580
+ scene[:environment] = environment
581
+ end
582
+
583
+ def dispose(handle)
584
+ handle.call(:dispose) if handle.respond_to?(:call)
585
+ end
586
+
587
+ def object_handle_key(object)
588
+ return nil unless js_present?(object)
589
+
590
+ uuid = object[:uuid]
591
+ js_present?(uuid) ? uuid.to_s : nil
592
+ end
593
+
594
+ def intersection_distance(intersection)
595
+ value = intersection[:distance]
596
+ js_present?(value) ? value.to_f : nil
597
+ end
598
+
599
+ def intersection_point(intersection)
600
+ point = intersection[:point]
601
+ js_present?(point) ? js_vector_to_a(point, 3) : nil
602
+ end
603
+
604
+ def intersection_object(intersection)
605
+ intersection[:object]
606
+ end
607
+
608
+ def intersection_uv(intersection)
609
+ uv = intersection[:uv]
610
+ js_present?(uv) ? js_vector_to_a(uv, 2) : nil
611
+ end
612
+
613
+ def intersection_face_index(intersection)
614
+ value = intersection[:faceIndex]
615
+ js_present?(value) ? value.to_i : nil
616
+ end
617
+
618
+ def intersection_index(intersection)
619
+ value = intersection[:index]
620
+ js_present?(value) ? value.to_i : nil
621
+ end
622
+
623
+ def intersection_instance_id(intersection)
624
+ value = intersection[:instanceId]
625
+ js_present?(value) ? value.to_i : nil
626
+ end
627
+
628
+ def traverse_object3d(object, callback)
629
+ object.call(:traverse, callback)
630
+ object
631
+ end
632
+
633
+ def dispose_object3d_subtree(
634
+ object,
635
+ remove: true,
636
+ dispose_geometries: true,
637
+ dispose_materials: true,
638
+ dispose_textures: false,
639
+ dispose_skeletons: true
640
+ )
641
+ resources = {
642
+ geometries: JS.global[:Set].new,
643
+ materials: JS.global[:Set].new,
644
+ textures: JS.global[:Set].new,
645
+ skeletons: JS.global[:Set].new
646
+ }
647
+
648
+ traverse_object3d(object, proc do |node|
649
+ collect_object3d_resources(
650
+ node,
651
+ resources,
652
+ dispose_geometries: dispose_geometries,
653
+ dispose_materials: dispose_materials,
654
+ dispose_textures: dispose_textures,
655
+ dispose_skeletons: dispose_skeletons
656
+ )
657
+ end)
658
+
659
+ remove_from_js_parent(object) if remove
660
+
661
+ dispose_js_set(resources[:geometries])
662
+ dispose_js_set(resources[:materials])
663
+ dispose_js_set(resources[:textures])
664
+ dispose_js_set(resources[:skeletons])
665
+ object
666
+ end
667
+
668
+ private
669
+
670
+ def default_three
671
+ require "js"
672
+ JS.global[:THREE]
673
+ rescue LoadError
674
+ raise RuntimeError, "Three::Backends::ThreeJS requires ruby.wasm's js gem or an injected adapter"
675
+ end
676
+
677
+ def orbit_controls_constructor
678
+ require "js"
679
+ constructor = JS.global[:THREE_ORBIT_CONTROLS]
680
+ raise RuntimeError, "Three::Controls::OrbitControls requires globalThis.THREE_ORBIT_CONTROLS" if constructor.typeof == "undefined"
681
+
682
+ constructor
683
+ rescue LoadError
684
+ raise RuntimeError, "Three::Controls::OrbitControls requires ruby.wasm's js gem or an injected adapter"
685
+ end
686
+
687
+ def effect_composer_constructor
688
+ require "js"
689
+ constructor = JS.global[:THREE_EFFECT_COMPOSER]
690
+ raise RuntimeError, "Three::Postprocessing::EffectComposer requires globalThis.THREE_EFFECT_COMPOSER" if constructor.typeof == "undefined"
691
+
692
+ constructor
693
+ rescue LoadError
694
+ raise RuntimeError, "Three::Postprocessing::EffectComposer requires ruby.wasm's js gem or an injected adapter"
695
+ end
696
+
697
+ def render_pass_constructor
698
+ require "js"
699
+ constructor = JS.global[:THREE_RENDER_PASS]
700
+ raise RuntimeError, "Three::Postprocessing::RenderPass requires globalThis.THREE_RENDER_PASS" if constructor.typeof == "undefined"
701
+
702
+ constructor
703
+ rescue LoadError
704
+ raise RuntimeError, "Three::Postprocessing::RenderPass requires ruby.wasm's js gem or an injected adapter"
705
+ end
706
+
707
+ def unreal_bloom_pass_constructor
708
+ require "js"
709
+ constructor = JS.global[:THREE_UNREAL_BLOOM_PASS]
710
+ raise RuntimeError, "Three::Postprocessing::UnrealBloomPass requires globalThis.THREE_UNREAL_BLOOM_PASS" if constructor.typeof == "undefined"
711
+
712
+ constructor
713
+ rescue LoadError
714
+ raise RuntimeError, "Three::Postprocessing::UnrealBloomPass requires ruby.wasm's js gem or an injected adapter"
715
+ end
716
+
717
+ def dot_screen_pass_constructor
718
+ require "js"
719
+ constructor = JS.global[:THREE_DOT_SCREEN_PASS]
720
+ raise RuntimeError, "Three::Postprocessing::DotScreenPass requires globalThis.THREE_DOT_SCREEN_PASS" if constructor.typeof == "undefined"
721
+
722
+ constructor
723
+ rescue LoadError
724
+ raise RuntimeError, "Three::Postprocessing::DotScreenPass requires ruby.wasm's js gem or an injected adapter"
725
+ end
726
+
727
+ def output_pass_constructor
728
+ require "js"
729
+ constructor = JS.global[:THREE_OUTPUT_PASS]
730
+ raise RuntimeError, "Three::Postprocessing::OutputPass requires globalThis.THREE_OUTPUT_PASS" if constructor.typeof == "undefined"
731
+
732
+ constructor
733
+ rescue LoadError
734
+ raise RuntimeError, "Three::Postprocessing::OutputPass requires ruby.wasm's js gem or an injected adapter"
735
+ end
736
+
737
+ def gltf_loader_constructor
738
+ require "js"
739
+ constructor = JS.global[:THREE_GLTF_LOADER]
740
+ raise RuntimeError, "Three::Loaders::GLTFLoader requires globalThis.THREE_GLTF_LOADER" if constructor.typeof == "undefined"
741
+
742
+ constructor
743
+ rescue LoadError
744
+ raise RuntimeError, "Three::Loaders::GLTFLoader requires ruby.wasm's js gem or an injected adapter"
745
+ end
746
+
747
+ def draco_loader_constructor
748
+ require "js"
749
+ constructor = JS.global[:THREE_DRACO_LOADER]
750
+ raise RuntimeError, "Three::Loaders::GLTFLoader with DRACO requires globalThis.THREE_DRACO_LOADER" if constructor.typeof == "undefined"
751
+
752
+ constructor
753
+ rescue LoadError
754
+ raise RuntimeError, "Three::Loaders::GLTFLoader with DRACO requires ruby.wasm's js gem or an injected adapter"
755
+ end
756
+
757
+ def configure_draco_loader(loader, decoder_path, decoder_config)
758
+ draco_loader = draco_loader_constructor.new
759
+ draco_loader.call(:setDecoderPath, decoder_path)
760
+ draco_loader.call(:setDecoderConfig, stringify_keys(decoder_config)) if decoder_config
761
+ loader.call(:setDRACOLoader, draco_loader)
762
+ end
763
+
764
+ def rgbe_loader_constructor
765
+ require "js"
766
+ constructor = JS.global[:THREE_RGBE_LOADER]
767
+ raise RuntimeError, "Three::Loaders::RGBELoader requires globalThis.THREE_RGBE_LOADER" if constructor.typeof == "undefined"
768
+
769
+ constructor
770
+ rescue LoadError
771
+ raise RuntimeError, "Three::Loaders::RGBELoader requires ruby.wasm's js gem or an injected adapter"
772
+ end
773
+
774
+ def resolve_canvas(canvas)
775
+ return canvas unless canvas.is_a?(String)
776
+
777
+ JS.global[:document].call(:querySelector, canvas)
778
+ end
779
+
780
+ def typed_array(component_type, array)
781
+ constructor =
782
+ case component_type
783
+ when :float32 then JS.global[:Float32Array]
784
+ when :uint16 then JS.global[:Uint16Array]
785
+ when :uint32 then JS.global[:Uint32Array]
786
+ else JS.global[:Array]
787
+ end
788
+ constructor.new(array)
789
+ end
790
+
791
+ def js_array(values)
792
+ array = JS.global[:Array].new
793
+ values.each { |value| array.call(:push, value) }
794
+ array
795
+ end
796
+
797
+ def stringify_keys(hash)
798
+ hash.each_with_object({}) do |(key, value), result|
799
+ result[key.to_s] = value
800
+ end
801
+ end
802
+
803
+ def js_vector_to_a(vector, length)
804
+ array = vector.call(:toArray)
805
+ length.times.map { |index| array[index].to_f }
806
+ end
807
+
808
+ def collect_object3d_resources(node, resources, dispose_geometries:, dispose_materials:, dispose_textures:, dispose_skeletons:)
809
+ if dispose_geometries
810
+ geometry = node[:geometry]
811
+ resources[:geometries].call(:add, geometry) if js_present?(geometry)
812
+ end
813
+
814
+ if dispose_materials
815
+ collect_materials(node[:material], resources, dispose_textures: dispose_textures)
816
+ end
817
+
818
+ if dispose_textures
819
+ collect_texture(node[:background], resources)
820
+ collect_texture(node[:environment], resources)
821
+ end
822
+
823
+ return unless dispose_skeletons
824
+
825
+ skeleton = node[:skeleton]
826
+ resources[:skeletons].call(:add, skeleton) if js_present?(skeleton)
827
+ end
828
+
829
+ def collect_materials(material, resources, dispose_textures:)
830
+ return unless js_present?(material)
831
+
832
+ if JS.global[:Array].call(:isArray, material) == JS::True
833
+ material[:length].to_i.times do |index|
834
+ collect_material(material[index], resources, dispose_textures: dispose_textures)
835
+ end
836
+ else
837
+ collect_material(material, resources, dispose_textures: dispose_textures)
838
+ end
839
+ end
840
+
841
+ def collect_material(material, resources, dispose_textures:)
842
+ return unless js_present?(material)
843
+
844
+ resources[:materials].call(:add, material)
845
+ return unless dispose_textures
846
+
847
+ TEXTURE_SLOTS.each { |slot| collect_texture(material[slot], resources) }
848
+ end
849
+
850
+ def collect_texture(texture, resources)
851
+ resources[:textures].call(:add, texture) if js_present?(texture)
852
+ end
853
+
854
+ def dispose_js_set(resources)
855
+ resources.call(:forEach, proc { |resource| dispose(resource) })
856
+ end
857
+
858
+ def remove_from_js_parent(object)
859
+ parent = object[:parent]
860
+ parent.call(:remove, object) if js_present?(parent)
861
+ end
862
+
863
+ def js_present?(object)
864
+ return false if object.nil?
865
+ return false if object == JS::Undefined || object == JS::Null
866
+ return false if object.respond_to?(:typeof) && object.typeof == "undefined"
867
+
868
+ true
869
+ end
870
+ end
871
+ end
872
+ end
873
+ end