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,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Three
4
+ module Backends
5
+ class Base
6
+ def materialize(_object)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def sync(_object)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def dispose(_object, **_options)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def set_renderer_shadow_map(_renderer_handle, **_options)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def traverse_handles(_object, &_block)
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def dispose_subtree(_object, **_options)
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def create_raycaster
31
+ raise NotImplementedError
32
+ end
33
+
34
+ def set_raycaster_from_camera(_raycaster_handle, _coords, _camera)
35
+ raise NotImplementedError
36
+ end
37
+
38
+ def intersect_objects(_raycaster_handle, _objects, recursive: false)
39
+ raise NotImplementedError
40
+ end
41
+
42
+ def create_animation_mixer(_root_handle)
43
+ raise NotImplementedError
44
+ end
45
+
46
+ def animation_mixer_clip_action(_mixer_handle, _clip_handle, _root_handle = nil)
47
+ raise NotImplementedError
48
+ end
49
+
50
+ def update_animation_mixer(_mixer_handle, _delta)
51
+ raise NotImplementedError
52
+ end
53
+
54
+ def stop_all_animation_actions(_mixer_handle)
55
+ raise NotImplementedError
56
+ end
57
+
58
+ def uncache_animation_root(_mixer_handle, _root_handle)
59
+ raise NotImplementedError
60
+ end
61
+
62
+ def set_animation_action_property(_action_handle, _name, _value)
63
+ raise NotImplementedError
64
+ end
65
+
66
+ def play_animation_action(_action_handle)
67
+ raise NotImplementedError
68
+ end
69
+
70
+ def stop_animation_action(_action_handle)
71
+ raise NotImplementedError
72
+ end
73
+
74
+ def reset_animation_action(_action_handle)
75
+ raise NotImplementedError
76
+ end
77
+
78
+ def fade_in_animation_action(_action_handle, _duration)
79
+ raise NotImplementedError
80
+ end
81
+
82
+ def fade_out_animation_action(_action_handle, _duration)
83
+ raise NotImplementedError
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Three
4
+ module Backends
5
+ class ThreeJS
6
+ module Materialization
7
+ private
8
+
9
+ def build_handle(object)
10
+ case object
11
+ when OrthographicCamera
12
+ @adapter.new_orthographic_camera(object.left, object.right, object.top, object.bottom, object.near, object.far)
13
+ when PerspectiveCamera
14
+ @adapter.new_perspective_camera(object.fov, object.aspect, object.near, object.far)
15
+ when Scene
16
+ @adapter.new_scene
17
+ when InstancedMesh
18
+ @adapter.new_instanced_mesh(materialize(object.geometry), materialize(object.material), object.count)
19
+ when Line
20
+ @adapter.new_line(materialize(object.geometry), materialize(object.material))
21
+ when Mesh
22
+ @adapter.new_mesh(materialize(object.geometry), materialize(object.material))
23
+ when Points
24
+ @adapter.new_points(materialize(object.geometry), materialize(object.material))
25
+ when Sprite
26
+ @adapter.new_sprite(materialize(object.material))
27
+ when CubeTexture
28
+ @adapter.load_cube_texture(object.sources, texture_parameters(object))
29
+ when RGBETexture
30
+ @adapter.load_rgbe_texture(object.source, texture_parameters(object))
31
+ when Texture
32
+ @adapter.load_texture(object.source, texture_parameters(object))
33
+ when AmbientLight
34
+ @adapter.new_ambient_light(object.color.hex, object.intensity)
35
+ when DirectionalLight
36
+ @adapter.new_directional_light(object.color.hex, object.intensity)
37
+ when PointLight
38
+ @adapter.new_point_light(object.color.hex, object.intensity, object.distance, object.decay)
39
+ when HemisphereLight
40
+ @adapter.new_hemisphere_light(object.color.hex, object.ground_color.hex, object.intensity)
41
+ when BoxGeometry
42
+ parameters = object.parameters
43
+ @adapter.new_box_geometry(
44
+ parameters[:width],
45
+ parameters[:height],
46
+ parameters[:depth],
47
+ parameters[:width_segments],
48
+ parameters[:height_segments],
49
+ parameters[:depth_segments]
50
+ )
51
+ when PlaneGeometry
52
+ parameters = object.parameters
53
+ @adapter.new_plane_geometry(
54
+ parameters[:width],
55
+ parameters[:height],
56
+ parameters[:width_segments],
57
+ parameters[:height_segments]
58
+ )
59
+ when SphereGeometry
60
+ parameters = object.parameters
61
+ @adapter.new_sphere_geometry(
62
+ parameters[:radius],
63
+ parameters[:width_segments],
64
+ parameters[:height_segments],
65
+ parameters[:phi_start],
66
+ parameters[:phi_length],
67
+ parameters[:theta_start],
68
+ parameters[:theta_length]
69
+ )
70
+ when BufferGeometry
71
+ build_buffer_geometry(object)
72
+ when LineBasicMaterial
73
+ @adapter.new_line_basic_material(material_parameters(object))
74
+ when MeshBasicMaterial
75
+ @adapter.new_mesh_basic_material(material_parameters(object))
76
+ when MeshLambertMaterial
77
+ @adapter.new_mesh_lambert_material(material_parameters(object))
78
+ when MeshMatcapMaterial
79
+ @adapter.new_mesh_matcap_material(material_parameters(object))
80
+ when MeshNormalMaterial
81
+ @adapter.new_mesh_normal_material(material_parameters(object))
82
+ when MeshPhongMaterial
83
+ @adapter.new_mesh_phong_material(material_parameters(object))
84
+ when MeshPhysicalMaterial
85
+ @adapter.new_mesh_physical_material(material_parameters(object))
86
+ when MeshStandardMaterial
87
+ @adapter.new_mesh_standard_material(material_parameters(object))
88
+ when MeshToonMaterial
89
+ @adapter.new_mesh_toon_material(material_parameters(object))
90
+ when PointsMaterial
91
+ @adapter.new_points_material(material_parameters(object))
92
+ when ShadowMaterial
93
+ @adapter.new_shadow_material(material_parameters(object))
94
+ when SpriteMaterial
95
+ @adapter.new_sprite_material(material_parameters(object))
96
+ when Group
97
+ @adapter.new_group
98
+ when ExternalObject3D
99
+ object.handle
100
+ when Object3D
101
+ @adapter.new_object3d
102
+ else
103
+ raise TypeError, "unsupported ThreeJS backend object: #{object.class}"
104
+ end
105
+ end
106
+
107
+ def build_buffer_geometry(geometry)
108
+ handle = @adapter.new_buffer_geometry
109
+
110
+ @adapter.set_geometry_index(handle, build_buffer_attribute(geometry.index)) if geometry.index
111
+ geometry.attributes.each do |name, attribute|
112
+ @adapter.set_geometry_attribute(handle, name, build_buffer_attribute(attribute))
113
+ end
114
+ geometry.groups.each do |group|
115
+ @adapter.add_geometry_group(handle, group[:start], group[:count], group[:material_index])
116
+ end
117
+ @adapter.set_geometry_draw_range(handle, geometry.draw_range[:start], geometry.draw_range[:count])
118
+ handle
119
+ end
120
+
121
+ def build_buffer_attribute(attribute)
122
+ @adapter.new_buffer_attribute(attribute.component_type, attribute.array, attribute.item_size, attribute.normalized)
123
+ end
124
+
125
+ def mark_clean_after_materialize(object)
126
+ case object
127
+ when Material
128
+ object.mark_clean!
129
+ when Texture
130
+ object.mark_clean!
131
+ when BufferGeometry
132
+ @geometry_attribute_names[object.uuid] = object.attributes.keys
133
+ object.mark_clean!
134
+ object.index&.mark_clean!
135
+ object.attributes.each_value(&:mark_clean!)
136
+ end
137
+ end
138
+ end
139
+
140
+ include Materialization
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Three
4
+ module Backends
5
+ class ThreeJS
6
+ module Parameters
7
+ private
8
+
9
+ def material_parameters(material)
10
+ parameters = {
11
+ opacity: material.opacity,
12
+ transparent: material.transparent,
13
+ visible: material.visible,
14
+ side: material.side,
15
+ vertexColors: material.vertex_colors
16
+ }
17
+ parameters[:color] = material.color.hex if material.respond_to?(:color)
18
+ parameters[:emissive] = material.emissive.hex if material.respond_to?(:emissive)
19
+ parameters[:specular] = material.specular.hex if material.respond_to?(:specular)
20
+ parameters[:shininess] = material.shininess if material.respond_to?(:shininess)
21
+ parameters[:roughness] = material.roughness if material.respond_to?(:roughness)
22
+ parameters[:metalness] = material.metalness if material.respond_to?(:metalness)
23
+ parameters[:anisotropy] = material.anisotropy if material.respond_to?(:anisotropy)
24
+ parameters[:anisotropyRotation] = material.anisotropy_rotation if material.respond_to?(:anisotropy_rotation)
25
+ parameters[:clearcoat] = material.clearcoat if material.respond_to?(:clearcoat)
26
+ parameters[:clearcoatRoughness] = material.clearcoat_roughness if material.respond_to?(:clearcoat_roughness)
27
+ parameters[:transmission] = material.transmission if material.respond_to?(:transmission)
28
+ parameters[:thickness] = material.thickness if material.respond_to?(:thickness)
29
+ parameters[:ior] = material.ior if material.respond_to?(:ior)
30
+ parameters[:reflectivity] = material.reflectivity if material.respond_to?(:reflectivity)
31
+ parameters[:iridescence] = material.iridescence if material.respond_to?(:iridescence)
32
+ parameters[:iridescenceIOR] = material.iridescence_ior if material.respond_to?(:iridescence_ior)
33
+ if material.respond_to?(:iridescence_thickness_range) && material.iridescence_thickness_range
34
+ parameters[:iridescenceThicknessRange] = material.iridescence_thickness_range.dup
35
+ end
36
+ parameters[:sheen] = material.sheen if material.respond_to?(:sheen)
37
+ parameters[:sheenColor] = material.sheen_color.hex if material.respond_to?(:sheen_color)
38
+ parameters[:sheenRoughness] = material.sheen_roughness if material.respond_to?(:sheen_roughness)
39
+ parameters[:dispersion] = material.dispersion if material.respond_to?(:dispersion)
40
+ parameters[:specularIntensity] = material.specular_intensity if material.respond_to?(:specular_intensity)
41
+ parameters[:specularColor] = material.specular_color.hex if material.respond_to?(:specular_color)
42
+ parameters[:attenuationDistance] = material.attenuation_distance if material.respond_to?(:attenuation_distance) && material.attenuation_distance
43
+ parameters[:attenuationColor] = material.attenuation_color.hex if material.respond_to?(:attenuation_color)
44
+ parameters[:linewidth] = material.linewidth if material.respond_to?(:linewidth)
45
+ parameters[:linecap] = material.linecap if material.respond_to?(:linecap)
46
+ parameters[:linejoin] = material.linejoin if material.respond_to?(:linejoin)
47
+ parameters[:rotation] = material.rotation if material.respond_to?(:rotation)
48
+ parameters[:size] = material.size if material.respond_to?(:size)
49
+ parameters[:sizeAttenuation] = material.size_attenuation if material.respond_to?(:size_attenuation)
50
+ parameters[:fog] = material.fog if material.respond_to?(:fog)
51
+ material_texture_parameters(material).each do |ruby_name, threejs_name|
52
+ texture = material.public_send(ruby_name)
53
+ parameters[threejs_name] = texture ? sync(texture) : nil
54
+ end
55
+ parameters[:wireframe] = material.wireframe if material.respond_to?(:wireframe)
56
+ parameters[:flatShading] = material.flat_shading if material.respond_to?(:flat_shading)
57
+ parameters
58
+ end
59
+
60
+ def material_texture_parameters(material)
61
+ MATERIAL_TEXTURE_PARAMETERS.select { |ruby_name, _threejs_name| material.respond_to?(ruby_name) }
62
+ end
63
+
64
+ def light_shadow_parameters(light)
65
+ parameters = {
66
+ map_size: light.shadow_map_size,
67
+ bias: light.shadow_bias,
68
+ normal_bias: light.shadow_normal_bias,
69
+ radius: light.shadow_radius
70
+ }
71
+ parameters[:camera] = light.shadow_camera if light.respond_to?(:shadow_camera)
72
+ parameters
73
+ end
74
+
75
+ def texture_parameters(texture)
76
+ {
77
+ mapping: texture.mapping,
78
+ color_space: texture.color_space,
79
+ flip_y: texture.flip_y,
80
+ wrap_s: texture.wrap_s,
81
+ wrap_t: texture.wrap_t,
82
+ mag_filter: texture.mag_filter,
83
+ min_filter: texture.min_filter,
84
+ offset: texture.offset.to_a,
85
+ repeat: texture.repeat.to_a,
86
+ center: texture.center.to_a,
87
+ rotation: texture.rotation,
88
+ matrix_auto_update: texture.matrix_auto_update,
89
+ matrix: texture.matrix.to_a
90
+ }
91
+ end
92
+ end
93
+
94
+ include Parameters
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Three
4
+ module Backends
5
+ class ThreeJS
6
+ module ResourceManagement
7
+ private
8
+
9
+ def dispose_material_textures(material)
10
+ material_textures(material).each { |texture| dispose(texture) }
11
+ end
12
+
13
+ def release_cached_subtree_handles(object, dispose_geometries:, dispose_materials:, dispose_textures:)
14
+ if object.respond_to?(:traverse)
15
+ object.traverse do |node|
16
+ release_cached_object_handles(
17
+ node,
18
+ dispose_geometries: dispose_geometries,
19
+ dispose_materials: dispose_materials,
20
+ dispose_textures: dispose_textures
21
+ )
22
+ end
23
+ else
24
+ key = cache_key(object)
25
+ @handles.delete(key) if key
26
+ end
27
+ end
28
+
29
+ def release_cached_object_handles(object, dispose_geometries:, dispose_materials:, dispose_textures:)
30
+ key = cache_key(object)
31
+ handle = key ? @handles.delete(key) : nil
32
+ handle_key = @adapter.object_handle_key(handle) if handle
33
+ @objects_by_handle_key.delete(handle_key) if handle_key
34
+
35
+ if object.respond_to?(:geometry) && object.respond_to?(:material)
36
+ release_cached_resource(object.geometry) if dispose_geometries
37
+ end
38
+
39
+ if object.respond_to?(:material)
40
+ release_cached_material(object.material, dispose_textures: dispose_textures) if dispose_materials
41
+ end
42
+
43
+ return unless object.is_a?(Scene) && dispose_textures
44
+
45
+ release_cached_resource(object.background)
46
+ release_cached_resource(object.environment)
47
+ end
48
+
49
+ def release_cached_material(material, dispose_textures:)
50
+ release_cached_resource(material)
51
+ material_textures(material).each { |texture| release_cached_resource(texture) } if dispose_textures
52
+ end
53
+
54
+ def release_cached_resource(resource)
55
+ key = cache_key(resource)
56
+ @handles.delete(key) if key
57
+ end
58
+
59
+ def material_textures(material)
60
+ return material.textures if material.respond_to?(:textures)
61
+
62
+ []
63
+ end
64
+ end
65
+
66
+ include ResourceManagement
67
+ end
68
+ end
69
+ end