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,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Three
4
+ module Backends
5
+ class ThreeJS
6
+ module Synchronization
7
+ private
8
+
9
+ def sync_object3d(object, handle)
10
+ sync_scene(object, handle) if object.is_a?(Scene) && (object.dirty_field?(:scene) || scene_resource_dirty?(object))
11
+
12
+ if object.dirty_field?(:properties)
13
+ @adapter.set_object_name(handle, object.name)
14
+ @adapter.set_object_visible(handle, object.visible)
15
+ @adapter.set_object_shadow(handle, object.cast_shadow, object.receive_shadow)
16
+ @adapter.set_object_layers(handle, object.layers.mask)
17
+ end
18
+
19
+ if object.dirty_field?(:transform)
20
+ @adapter.set_object_transform(handle, object.position.to_a, object.quaternion.to_a, object.scale.to_a)
21
+ end
22
+
23
+ if object.dirty_field?(:camera)
24
+ sync_camera(object, handle)
25
+ end
26
+
27
+ sync_light(object, handle) if object.is_a?(Light) && object.dirty_field?(:light)
28
+
29
+ if object.is_a?(InstancedMesh)
30
+ sync_instanced_mesh(object, handle)
31
+ elsif object.is_a?(Sprite)
32
+ sync_sprite(object, handle)
33
+ elsif geometry_material_object?(object)
34
+ sync_geometry_material_object(object, handle)
35
+ end
36
+
37
+ if object.dirty_field?(:children)
38
+ @adapter.clear_children(handle)
39
+ object.children.each do |child|
40
+ child_handle = sync(child)
41
+ @adapter.add_child(handle, child_handle)
42
+ end
43
+ elsif object.dirty_field?(:descendants)
44
+ object.children.each { |child| sync(child) if child.dirty? }
45
+ else
46
+ # Clean subtrees do not need a Ruby-to-JavaScript sync pass.
47
+ end
48
+
49
+ object.mark_clean! if object.respond_to?(:mark_clean!)
50
+ handle
51
+ end
52
+
53
+ def sync_instanced_mesh(object, handle)
54
+ geometry_handle = sync(object.geometry)
55
+ material_handle = sync(object.material) if object.material.respond_to?(:uuid)
56
+
57
+ if object.dirty_field?(:mesh)
58
+ @adapter.set_object_geometry(handle, geometry_handle)
59
+ @adapter.set_object_material(handle, material_handle) if material_handle
60
+ @adapter.set_instanced_mesh_count(handle, object.count)
61
+ end
62
+
63
+ if object.dirty_field?(:instances)
64
+ object.instance_matrices.each_with_index do |matrix, index|
65
+ @adapter.set_instanced_mesh_matrix_at(handle, index, matrix.to_a)
66
+ end
67
+ @adapter.set_instanced_mesh_instance_matrix_needs_update(handle, true)
68
+ end
69
+
70
+ sync_instanced_mesh_colors(object, handle) if object.dirty_field?(:instance_colors)
71
+ end
72
+
73
+ def geometry_material_object?(object)
74
+ object.is_a?(Mesh) || object.is_a?(Line) || object.is_a?(Points)
75
+ end
76
+
77
+ def sync_sprite(object, handle)
78
+ material_handle = sync(object.material) if object.material.respond_to?(:uuid)
79
+
80
+ if object.dirty_field?(:sprite)
81
+ @adapter.set_object_material(handle, material_handle) if material_handle
82
+ @adapter.set_sprite_center(handle, object.center.to_a)
83
+ end
84
+ end
85
+
86
+ def sync_geometry_material_object(object, handle)
87
+ geometry_handle = sync(object.geometry)
88
+ material_handle = sync(object.material) if object.material.respond_to?(:uuid)
89
+ dirty_field = geometry_material_dirty_field(object)
90
+
91
+ if object.dirty_field?(dirty_field)
92
+ @adapter.set_object_geometry(handle, geometry_handle)
93
+ @adapter.set_object_material(handle, material_handle) if material_handle
94
+ end
95
+ end
96
+
97
+ def geometry_material_dirty_field(object)
98
+ case object
99
+ when Line
100
+ :line
101
+ when Points
102
+ :points
103
+ else
104
+ :mesh
105
+ end
106
+ end
107
+
108
+ def sync_instanced_mesh_colors(object, handle)
109
+ return unless object.instance_colors
110
+
111
+ object.instance_colors.each_with_index do |color, index|
112
+ @adapter.set_instanced_mesh_color_at(handle, index, color.to_a)
113
+ end
114
+ @adapter.set_instanced_mesh_instance_color_needs_update(handle, true)
115
+ end
116
+
117
+ def sync_scene(scene, handle)
118
+ @adapter.set_scene_background(handle, scene.background ? sync(scene.background) : nil)
119
+ @adapter.set_scene_environment(handle, scene.environment ? sync(scene.environment) : nil)
120
+ end
121
+
122
+ def scene_resource_dirty?(scene)
123
+ [scene.background, scene.environment].compact.any? do |resource|
124
+ resource.respond_to?(:dirty?) && resource.dirty?
125
+ end
126
+ end
127
+
128
+ def sync_camera(object, handle)
129
+ case object
130
+ when OrthographicCamera
131
+ @adapter.update_orthographic_camera(handle, object.left, object.right, object.top, object.bottom, object.near, object.far, object.zoom)
132
+ when PerspectiveCamera
133
+ @adapter.update_perspective_camera(handle, object.fov, object.aspect, object.near, object.far, object.zoom)
134
+ end
135
+ end
136
+
137
+ def sync_light(object, handle)
138
+ case object
139
+ when PointLight
140
+ @adapter.update_point_light(handle, object.color.hex, object.intensity, object.distance, object.decay)
141
+ when HemisphereLight
142
+ @adapter.update_hemisphere_light(handle, object.color.hex, object.ground_color.hex, object.intensity)
143
+ else
144
+ @adapter.update_light(handle, object.color.hex, object.intensity)
145
+ end
146
+ @adapter.update_light_shadow(handle, light_shadow_parameters(object)) if shadow_dirty?(object)
147
+ end
148
+
149
+ def shadow_dirty?(object)
150
+ object.dirty_fields.key?(:all) || object.dirty_field?(:shadow)
151
+ end
152
+
153
+ def sync_material(material, handle)
154
+ sync_material_textures(material)
155
+ return handle unless material.dirty?
156
+
157
+ @adapter.update_material(handle, material_parameters(material))
158
+ material.mark_clean!
159
+ handle
160
+ end
161
+
162
+ def sync_material_textures(material)
163
+ material_textures(material).each { |texture| sync(texture) }
164
+ end
165
+
166
+ def sync_texture(texture, handle)
167
+ return handle unless texture.dirty?
168
+
169
+ @adapter.update_texture(handle, texture_parameters(texture))
170
+ texture.mark_clean!
171
+ handle
172
+ end
173
+
174
+ def sync_geometry(geometry, handle)
175
+ return handle if built_in_geometry?(geometry)
176
+ return handle unless geometry_dirty?(geometry)
177
+
178
+ if geometry.dirty_field?(:all) || geometry.dirty_field?(:index) || geometry.index&.dirty?
179
+ @adapter.set_geometry_index(handle, geometry.index ? build_buffer_attribute(geometry.index) : nil)
180
+ geometry.index&.mark_clean!
181
+ end
182
+
183
+ if geometry.dirty_field?(:all) || geometry.dirty_field?(:attributes) || geometry.attributes.values.any?(&:dirty?)
184
+ previous_names = @geometry_attribute_names[geometry.uuid] || []
185
+ current_names = geometry.attributes.keys
186
+ (previous_names - current_names).each { |name| @adapter.delete_geometry_attribute(handle, name) }
187
+
188
+ geometry.attributes.each do |name, attribute|
189
+ @adapter.set_geometry_attribute(handle, name, build_buffer_attribute(attribute))
190
+ attribute.mark_clean!
191
+ end
192
+ @geometry_attribute_names[geometry.uuid] = current_names
193
+ end
194
+
195
+ if geometry.dirty_field?(:all) || geometry.dirty_field?(:groups)
196
+ @adapter.clear_geometry_groups(handle)
197
+ geometry.groups.each do |group|
198
+ @adapter.add_geometry_group(handle, group[:start], group[:count], group[:material_index])
199
+ end
200
+ end
201
+
202
+ if geometry.dirty_field?(:all) || geometry.dirty_field?(:draw_range)
203
+ @adapter.set_geometry_draw_range(handle, geometry.draw_range[:start], geometry.draw_range[:count])
204
+ end
205
+
206
+ geometry.mark_clean!
207
+ handle
208
+ end
209
+
210
+ def geometry_dirty?(geometry)
211
+ geometry.dirty? ||
212
+ geometry.index&.dirty? ||
213
+ geometry.attributes.values.any?(&:dirty?)
214
+ end
215
+
216
+ def built_in_geometry?(geometry)
217
+ geometry.is_a?(BoxGeometry) || geometry.is_a?(PlaneGeometry) || geometry.is_a?(SphereGeometry)
218
+ end
219
+ end
220
+
221
+ include Synchronization
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,365 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../cameras/orthographic_camera"
4
+ require_relative "../cameras/perspective_camera"
5
+ require_relative "../core/buffer_geometry"
6
+ require_relative "../geometries/box_geometry"
7
+ require_relative "../geometries/plane_geometry"
8
+ require_relative "../geometries/sphere_geometry"
9
+ require_relative "../lights/ambient_light"
10
+ require_relative "../lights/directional_light"
11
+ require_relative "../lights/hemisphere_light"
12
+ require_relative "../lights/point_light"
13
+ require_relative "../materials/line_basic_material"
14
+ require_relative "../materials/mesh_basic_material"
15
+ require_relative "../materials/mesh_lambert_material"
16
+ require_relative "../materials/mesh_matcap_material"
17
+ require_relative "../materials/mesh_normal_material"
18
+ require_relative "../materials/mesh_phong_material"
19
+ require_relative "../materials/mesh_standard_material"
20
+ require_relative "../materials/mesh_physical_material"
21
+ require_relative "../materials/mesh_toon_material"
22
+ require_relative "../materials/points_material"
23
+ require_relative "../materials/shadow_material"
24
+ require_relative "../materials/sprite_material"
25
+ require_relative "../objects/external_object3d"
26
+ require_relative "../objects/instanced_mesh"
27
+ require_relative "../objects/line"
28
+ require_relative "../objects/mesh"
29
+ require_relative "../objects/points"
30
+ require_relative "../objects/sprite"
31
+ require_relative "../scenes/scene"
32
+ require_relative "../textures/cube_texture"
33
+ require_relative "../textures/rgbe_texture"
34
+ require_relative "../textures/texture"
35
+ require_relative "base"
36
+
37
+ module Three
38
+ module Backends
39
+ class ThreeJS < Base
40
+ MATERIAL_TEXTURE_PARAMETERS = {
41
+ map: :map,
42
+ alpha_map: :alphaMap,
43
+ ao_map: :aoMap,
44
+ bump_map: :bumpMap,
45
+ displacement_map: :displacementMap,
46
+ emissive_map: :emissiveMap,
47
+ env_map: :envMap,
48
+ gradient_map: :gradientMap,
49
+ light_map: :lightMap,
50
+ matcap: :matcap,
51
+ metalness_map: :metalnessMap,
52
+ normal_map: :normalMap,
53
+ roughness_map: :roughnessMap,
54
+ specular_map: :specularMap,
55
+ anisotropy_map: :anisotropyMap,
56
+ clearcoat_map: :clearcoatMap,
57
+ clearcoat_normal_map: :clearcoatNormalMap,
58
+ clearcoat_roughness_map: :clearcoatRoughnessMap,
59
+ transmission_map: :transmissionMap,
60
+ thickness_map: :thicknessMap,
61
+ iridescence_map: :iridescenceMap,
62
+ iridescence_thickness_map: :iridescenceThicknessMap,
63
+ sheen_color_map: :sheenColorMap,
64
+ sheen_roughness_map: :sheenRoughnessMap,
65
+ specular_color_map: :specularColorMap,
66
+ specular_intensity_map: :specularIntensityMap
67
+ }.freeze
68
+
69
+ MATERIAL_COLOR_PARAMETERS = %i[color emissive specular attenuationColor sheenColor specularColor].freeze
70
+
71
+ attr_reader :adapter, :handles
72
+
73
+ def initialize(adapter: nil)
74
+ @adapter = adapter || RubyWasmAdapter.new
75
+ @handles = {}
76
+ @objects_by_handle_key = {}
77
+ @geometry_attribute_names = {}
78
+ end
79
+
80
+ def create_renderer(canvas: nil, **options)
81
+ @adapter.new_webgl_renderer({ canvas: canvas }.merge(options))
82
+ end
83
+
84
+ def renderer_dom_element(renderer_handle)
85
+ @adapter.renderer_dom_element(renderer_handle)
86
+ end
87
+
88
+ def set_renderer_size(renderer_handle, width, height)
89
+ @adapter.set_renderer_size(renderer_handle, width, height)
90
+ end
91
+
92
+ def set_clear_color(renderer_handle, color, alpha = 1)
93
+ @adapter.set_clear_color(renderer_handle, color, alpha)
94
+ end
95
+
96
+ def set_renderer_shadow_map(renderer_handle, enabled: nil, type: nil, auto_update: nil)
97
+ @adapter.set_renderer_shadow_map(renderer_handle, enabled: enabled, type: type, auto_update: auto_update)
98
+ end
99
+
100
+ def set_animation_loop(renderer_handle, callback)
101
+ @adapter.set_animation_loop(renderer_handle, callback)
102
+ end
103
+
104
+ def render(renderer_handle, scene, camera)
105
+ scene_handle = sync(scene)
106
+ camera_handle = sync(camera)
107
+ @adapter.render(renderer_handle, scene_handle, camera_handle)
108
+ end
109
+
110
+ def create_effect_composer(renderer_handle)
111
+ @adapter.new_effect_composer(renderer_handle)
112
+ end
113
+
114
+ def add_effect_composer_pass(composer_handle, pass_handle)
115
+ @adapter.effect_composer_add_pass(composer_handle, pass_handle)
116
+ end
117
+
118
+ def set_effect_composer_size(composer_handle, width, height)
119
+ @adapter.effect_composer_set_size(composer_handle, width, height)
120
+ end
121
+
122
+ def render_effect_composer(composer_handle, scene = nil, camera = nil)
123
+ sync(scene) if scene
124
+ sync(camera) if camera
125
+ @adapter.effect_composer_render(composer_handle)
126
+ end
127
+
128
+ def dispose_effect_composer(composer_handle)
129
+ @adapter.dispose_effect_composer(composer_handle)
130
+ end
131
+
132
+ def create_render_pass(scene, camera)
133
+ @adapter.new_render_pass(sync(scene), sync(camera))
134
+ end
135
+
136
+ def create_unreal_bloom_pass(resolution, strength, radius, threshold)
137
+ @adapter.new_unreal_bloom_pass(resolution, strength, radius, threshold)
138
+ end
139
+
140
+ def create_dot_screen_pass(center, angle, scale)
141
+ @adapter.new_dot_screen_pass(center, angle, scale)
142
+ end
143
+
144
+ def create_output_pass
145
+ @adapter.new_output_pass
146
+ end
147
+
148
+ def set_postprocessing_pass_property(pass_handle, name, value)
149
+ @adapter.set_postprocessing_pass_property(pass_handle, name, value)
150
+ end
151
+
152
+ def set_postprocessing_pass_uniform(pass_handle, name, value)
153
+ @adapter.set_postprocessing_pass_uniform(pass_handle, name, value)
154
+ end
155
+
156
+ def create_orbit_controls(camera, dom_element = nil)
157
+ @adapter.new_orbit_controls(camera, dom_element)
158
+ end
159
+
160
+ def set_control_property(control_handle, name, value)
161
+ @adapter.set_control_property(control_handle, name, value)
162
+ end
163
+
164
+ def set_orbit_controls_target(control_handle, target)
165
+ @adapter.set_orbit_controls_target(control_handle, target)
166
+ end
167
+
168
+ def update_controls(control_handle)
169
+ @adapter.update_controls(control_handle)
170
+ end
171
+
172
+ def dispose_controls(control_handle)
173
+ @adapter.dispose_controls(control_handle)
174
+ end
175
+
176
+ def sync_object_transform_from_handle(object)
177
+ handle = materialize(object)
178
+ position, quaternion, scale = @adapter.object_transform(handle)
179
+ object.position.set(*position)
180
+ object.quaternion.set(*quaternion)
181
+ object.scale.set(*scale)
182
+ object.mark_clean!(:transform) if object.respond_to?(:mark_clean!)
183
+ object
184
+ end
185
+
186
+ def create_raycaster
187
+ @adapter.new_raycaster
188
+ end
189
+
190
+ def set_raycaster_from_camera(raycaster_handle, coords, camera)
191
+ @adapter.set_raycaster_from_camera(raycaster_handle, coords, sync(camera))
192
+ end
193
+
194
+ def intersect_objects(raycaster_handle, objects, recursive: false)
195
+ handles = Array(objects).map { |object| sync(object) }
196
+ @adapter.intersect_objects(raycaster_handle, handles, recursive: recursive).map do |intersection|
197
+ normalize_intersection(intersection)
198
+ end
199
+ end
200
+
201
+ def create_animation_mixer(root_handle)
202
+ @adapter.new_animation_mixer(root_handle)
203
+ end
204
+
205
+ def animation_mixer_clip_action(mixer_handle, clip_handle, root_handle = nil)
206
+ @adapter.animation_mixer_clip_action(mixer_handle, clip_handle, root_handle)
207
+ end
208
+
209
+ def update_animation_mixer(mixer_handle, delta)
210
+ @adapter.update_animation_mixer(mixer_handle, delta)
211
+ end
212
+
213
+ def stop_all_animation_actions(mixer_handle)
214
+ @adapter.stop_all_animation_actions(mixer_handle)
215
+ end
216
+
217
+ def uncache_animation_root(mixer_handle, root_handle)
218
+ @adapter.uncache_animation_root(mixer_handle, root_handle)
219
+ end
220
+
221
+ def set_animation_action_property(action_handle, name, value)
222
+ @adapter.set_animation_action_property(action_handle, name, value)
223
+ end
224
+
225
+ def play_animation_action(action_handle)
226
+ @adapter.play_animation_action(action_handle)
227
+ end
228
+
229
+ def stop_animation_action(action_handle)
230
+ @adapter.stop_animation_action(action_handle)
231
+ end
232
+
233
+ def reset_animation_action(action_handle)
234
+ @adapter.reset_animation_action(action_handle)
235
+ end
236
+
237
+ def fade_in_animation_action(action_handle, duration)
238
+ @adapter.fade_in_animation_action(action_handle, duration)
239
+ end
240
+
241
+ def fade_out_animation_action(action_handle, duration)
242
+ @adapter.fade_out_animation_action(action_handle, duration)
243
+ end
244
+
245
+ def materialize(object)
246
+ key = cache_key(object)
247
+ return @handles[key] if key && @handles.key?(key)
248
+
249
+ handle = build_handle(object)
250
+ @handles[key] = handle if key
251
+ register_object_handle(object, handle)
252
+ mark_clean_after_materialize(object)
253
+ handle
254
+ end
255
+
256
+ def sync(object)
257
+ handle = materialize(object)
258
+
259
+ case object
260
+ when Object3D
261
+ sync_object3d(object, handle)
262
+ when BufferGeometry
263
+ sync_geometry(object, handle)
264
+ when Texture
265
+ sync_texture(object, handle)
266
+ when Material
267
+ sync_material(object, handle)
268
+ end
269
+
270
+ handle
271
+ end
272
+
273
+ def dispose(object, dispose_textures: false)
274
+ dispose_material_textures(object) if dispose_textures && object.is_a?(Material)
275
+
276
+ key = cache_key(object)
277
+ handle = key ? @handles.delete(key) : nil
278
+ handle_key = @adapter.object_handle_key(handle) if handle
279
+ @objects_by_handle_key.delete(handle_key) if handle_key
280
+ @adapter.dispose(handle) if handle
281
+ handle
282
+ end
283
+
284
+ def traverse_handles(object, &block)
285
+ return enum_for(:traverse_handles, object) unless block
286
+
287
+ handle = object3d_handle(object)
288
+ @adapter.traverse_object3d(handle, block)
289
+ handle
290
+ end
291
+
292
+ def dispose_subtree(
293
+ object,
294
+ remove: true,
295
+ dispose_geometries: true,
296
+ dispose_materials: true,
297
+ dispose_textures: false,
298
+ dispose_skeletons: true
299
+ )
300
+ handle = object3d_handle(object)
301
+ @adapter.dispose_object3d_subtree(
302
+ handle,
303
+ remove: remove,
304
+ dispose_geometries: dispose_geometries,
305
+ dispose_materials: dispose_materials,
306
+ dispose_textures: dispose_textures,
307
+ dispose_skeletons: dispose_skeletons
308
+ )
309
+ object.remove_from_parent if remove && object.respond_to?(:remove_from_parent)
310
+ release_cached_subtree_handles(
311
+ object,
312
+ dispose_geometries: dispose_geometries,
313
+ dispose_materials: dispose_materials,
314
+ dispose_textures: dispose_textures
315
+ )
316
+ handle
317
+ end
318
+
319
+ private
320
+
321
+ def object3d_handle(object)
322
+ raise TypeError, "object must be a Three::Object3D" unless object.is_a?(Object3D)
323
+
324
+ sync(object)
325
+ end
326
+
327
+ def cache_key(object)
328
+ object.respond_to?(:uuid) ? object.uuid : nil
329
+ end
330
+
331
+ def register_object_handle(object, handle)
332
+ return unless object.is_a?(Object3D)
333
+
334
+ handle_key = @adapter.object_handle_key(handle)
335
+ @objects_by_handle_key[handle_key] = object if handle_key
336
+ end
337
+
338
+ def object_for_handle(handle)
339
+ handle_key = @adapter.object_handle_key(handle)
340
+ handle_key ? @objects_by_handle_key[handle_key] : nil
341
+ end
342
+
343
+ def normalize_intersection(intersection)
344
+ object_handle = @adapter.intersection_object(intersection)
345
+ {
346
+ distance: @adapter.intersection_distance(intersection),
347
+ point: @adapter.intersection_point(intersection),
348
+ object: object_for_handle(object_handle),
349
+ object_handle: object_handle,
350
+ uv: @adapter.intersection_uv(intersection),
351
+ face_index: @adapter.intersection_face_index(intersection),
352
+ index: @adapter.intersection_index(intersection),
353
+ instance_id: @adapter.intersection_instance_id(intersection),
354
+ raw: intersection
355
+ }
356
+ end
357
+ end
358
+ end
359
+ end
360
+
361
+ require_relative "threejs/parameters"
362
+ require_relative "threejs/materialization"
363
+ require_relative "threejs/synchronization"
364
+ require_relative "threejs/resource_management"
365
+ require_relative "threejs/ruby_wasm_adapter"
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../core/object3d"
4
+
5
+ module Three
6
+ class Camera < Object3D
7
+ attr_reader :matrix_world_inverse, :projection_matrix, :projection_matrix_inverse
8
+
9
+ def initialize
10
+ super
11
+ @type = "Camera"
12
+ @matrix_world_inverse = Matrix4.new
13
+ @projection_matrix = Matrix4.new
14
+ @projection_matrix_inverse = Matrix4.new
15
+ end
16
+
17
+ def update_matrix_world(force = false)
18
+ super
19
+ update_matrix_world_inverse
20
+ self
21
+ end
22
+
23
+ def update_world_matrix(update_parents = false, update_children = false)
24
+ super
25
+ update_matrix_world_inverse
26
+ self
27
+ end
28
+
29
+ def get_world_direction(target = Vector3.new)
30
+ super(target).negate
31
+ end
32
+
33
+ private
34
+
35
+ def update_matrix_world_inverse
36
+ @matrix_world_inverse.copy(@matrix_world).invert
37
+ end
38
+ end
39
+ end