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,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "camera"
4
+
5
+ module Three
6
+ class OrthographicCamera < Camera
7
+ attr_reader :left, :right, :top, :bottom, :near, :far, :zoom
8
+ attr_reader :view
9
+
10
+ def initialize(left = -1, right = 1, top = 1, bottom = -1, near: 0.1, far: 2000)
11
+ super()
12
+ @type = "OrthographicCamera"
13
+ @left = left
14
+ @right = right
15
+ @top = top
16
+ @bottom = bottom
17
+ @near = near
18
+ @far = far
19
+ @zoom = 1
20
+ @view = nil
21
+ update_projection_matrix
22
+ end
23
+
24
+ def left=(value)
25
+ @left = value
26
+ mark_dirty!(:camera)
27
+ end
28
+
29
+ def right=(value)
30
+ @right = value
31
+ mark_dirty!(:camera)
32
+ end
33
+
34
+ def top=(value)
35
+ @top = value
36
+ mark_dirty!(:camera)
37
+ end
38
+
39
+ def bottom=(value)
40
+ @bottom = value
41
+ mark_dirty!(:camera)
42
+ end
43
+
44
+ def near=(value)
45
+ @near = value
46
+ mark_dirty!(:camera)
47
+ end
48
+
49
+ def far=(value)
50
+ @far = value
51
+ mark_dirty!(:camera)
52
+ end
53
+
54
+ def zoom=(value)
55
+ @zoom = value
56
+ mark_dirty!(:camera)
57
+ end
58
+
59
+ def set_view_offset(full_width, full_height, x, y, width, height)
60
+ @view = {
61
+ enabled: true,
62
+ full_width: full_width,
63
+ full_height: full_height,
64
+ offset_x: x,
65
+ offset_y: y,
66
+ width: width,
67
+ height: height
68
+ }
69
+ mark_dirty!(:camera)
70
+ update_projection_matrix
71
+ self
72
+ end
73
+
74
+ def clear_view_offset
75
+ @view[:enabled] = false if @view
76
+ mark_dirty!(:camera)
77
+ update_projection_matrix
78
+ self
79
+ end
80
+
81
+ def update_projection_matrix
82
+ dx = (@right - @left).to_f / (2 * @zoom)
83
+ dy = (@top - @bottom).to_f / (2 * @zoom)
84
+ cx = (@right + @left).to_f / 2
85
+ cy = (@top + @bottom).to_f / 2
86
+
87
+ left = cx - dx
88
+ right = cx + dx
89
+ top = cy + dy
90
+ bottom = cy - dy
91
+
92
+ if @view && @view[:enabled]
93
+ scale_w = (@right - @left).to_f / @view[:full_width] / @zoom
94
+ scale_h = (@top - @bottom).to_f / @view[:full_height] / @zoom
95
+ left += scale_w * @view[:offset_x]
96
+ right = left + scale_w * @view[:width]
97
+ top -= scale_h * @view[:offset_y]
98
+ bottom = top - scale_h * @view[:height]
99
+ end
100
+
101
+ @projection_matrix.make_orthographic(left, right, top, bottom, @near, @far)
102
+ @projection_matrix_inverse.copy(@projection_matrix).invert
103
+ mark_dirty!(:camera)
104
+ self
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../math/math_utils"
4
+ require_relative "camera"
5
+
6
+ module Three
7
+ class PerspectiveCamera < Camera
8
+ attr_reader :fov, :zoom, :near, :far, :focus, :aspect, :film_gauge, :film_offset
9
+ attr_reader :view
10
+
11
+ def initialize(fov = 50, aspect: 1, near: 0.1, far: 2000)
12
+ super()
13
+ @type = "PerspectiveCamera"
14
+ @fov = fov
15
+ @zoom = 1
16
+ @near = near
17
+ @far = far
18
+ @focus = 10
19
+ @aspect = aspect
20
+ @view = nil
21
+ @film_gauge = 35
22
+ @film_offset = 0
23
+ update_projection_matrix
24
+ end
25
+
26
+ def fov=(value)
27
+ @fov = value
28
+ mark_dirty!(:camera)
29
+ end
30
+
31
+ def zoom=(value)
32
+ @zoom = value
33
+ mark_dirty!(:camera)
34
+ end
35
+
36
+ def near=(value)
37
+ @near = value
38
+ mark_dirty!(:camera)
39
+ end
40
+
41
+ def far=(value)
42
+ @far = value
43
+ mark_dirty!(:camera)
44
+ end
45
+
46
+ def focus=(value)
47
+ @focus = value
48
+ mark_dirty!(:camera)
49
+ end
50
+
51
+ def aspect=(value)
52
+ @aspect = value
53
+ mark_dirty!(:camera)
54
+ end
55
+
56
+ def film_gauge=(value)
57
+ @film_gauge = value
58
+ mark_dirty!(:camera)
59
+ end
60
+
61
+ def film_offset=(value)
62
+ @film_offset = value
63
+ mark_dirty!(:camera)
64
+ end
65
+
66
+ def set_focal_length(focal_length)
67
+ vertical_extent_slope = 0.5 * film_height / focal_length
68
+ @fov = MathUtils.rad_to_deg(2 * Math.atan(vertical_extent_slope))
69
+ mark_dirty!(:camera)
70
+ update_projection_matrix
71
+ self
72
+ end
73
+
74
+ def focal_length
75
+ vertical_extent_slope = Math.tan(MathUtils.deg_to_rad(0.5 * @fov))
76
+ 0.5 * film_height / vertical_extent_slope
77
+ end
78
+
79
+ def effective_fov
80
+ MathUtils.rad_to_deg(2 * Math.atan(Math.tan(MathUtils.deg_to_rad(0.5 * @fov)) / @zoom))
81
+ end
82
+
83
+ def film_width
84
+ @film_gauge * [@aspect, 1].min
85
+ end
86
+
87
+ def film_height
88
+ @film_gauge / [@aspect, 1].max
89
+ end
90
+
91
+ def set_view_offset(full_width, full_height, x, y, width, height)
92
+ @aspect = full_width.to_f / full_height
93
+ @view = {
94
+ enabled: true,
95
+ full_width: full_width,
96
+ full_height: full_height,
97
+ offset_x: x,
98
+ offset_y: y,
99
+ width: width,
100
+ height: height
101
+ }
102
+ mark_dirty!(:camera)
103
+ update_projection_matrix
104
+ self
105
+ end
106
+
107
+ def clear_view_offset
108
+ @view[:enabled] = false if @view
109
+ mark_dirty!(:camera)
110
+ update_projection_matrix
111
+ self
112
+ end
113
+
114
+ def update_projection_matrix
115
+ top = @near * Math.tan(MathUtils.deg_to_rad(0.5 * @fov)) / @zoom
116
+ height = 2 * top
117
+ width = @aspect * height
118
+ left = -0.5 * width
119
+
120
+ if @view && @view[:enabled]
121
+ full_width = @view[:full_width]
122
+ full_height = @view[:full_height]
123
+ left += @view[:offset_x] * width / full_width
124
+ top -= @view[:offset_y] * height / full_height
125
+ width *= @view[:width].to_f / full_width
126
+ height *= @view[:height].to_f / full_height
127
+ end
128
+
129
+ left += @near * @film_offset / film_width unless @film_offset.zero?
130
+
131
+ @projection_matrix.make_perspective(left, left + width, top, top - height, @near, @far)
132
+ @projection_matrix_inverse.copy(@projection_matrix).invert
133
+ mark_dirty!(:camera)
134
+ self
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Three
4
+ FrontSide = 0
5
+ BackSide = 1
6
+ DoubleSide = 2
7
+
8
+ NoBlending = 0
9
+ NormalBlending = 1
10
+
11
+ RepeatWrapping = 1000
12
+ ClampToEdgeWrapping = 1001
13
+ MirroredRepeatWrapping = 1002
14
+
15
+ NearestFilter = 1003
16
+ NearestMipmapNearestFilter = 1004
17
+ NearestMipMapNearestFilter = 1004
18
+ NearestMipmapLinearFilter = 1005
19
+ NearestMipMapLinearFilter = 1005
20
+ LinearFilter = 1006
21
+ LinearMipmapNearestFilter = 1007
22
+ LinearMipMapNearestFilter = 1007
23
+ LinearMipmapLinearFilter = 1008
24
+ LinearMipMapLinearFilter = 1008
25
+
26
+ UVMapping = 300
27
+ CubeReflectionMapping = 301
28
+ CubeRefractionMapping = 302
29
+ EquirectangularReflectionMapping = 303
30
+ EquirectangularRefractionMapping = 304
31
+
32
+ NoColorSpace = ""
33
+ SRGBColorSpace = "srgb"
34
+ LinearSRGBColorSpace = "srgb-linear"
35
+
36
+ BasicShadowMap = 0
37
+ PCFShadowMap = 1
38
+ PCFSoftShadowMap = 2
39
+ VSMShadowMap = 3
40
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../backends/threejs"
4
+ require_relative "../math/vector3"
5
+
6
+ module Three
7
+ module Controls
8
+ class OrbitControls
9
+ PROPERTY_NAMES = {
10
+ enabled: "enabled",
11
+ enable_damping: "enableDamping",
12
+ damping_factor: "dampingFactor",
13
+ enable_zoom: "enableZoom",
14
+ zoom_speed: "zoomSpeed",
15
+ enable_rotate: "enableRotate",
16
+ rotate_speed: "rotateSpeed",
17
+ enable_pan: "enablePan",
18
+ pan_speed: "panSpeed",
19
+ auto_rotate: "autoRotate",
20
+ auto_rotate_speed: "autoRotateSpeed",
21
+ min_distance: "minDistance",
22
+ max_distance: "maxDistance",
23
+ min_zoom: "minZoom",
24
+ max_zoom: "maxZoom"
25
+ }.freeze
26
+
27
+ attr_reader :object, :renderer, :backend, :handle, :dom_element, :target
28
+ attr_reader(*PROPERTY_NAMES.keys)
29
+
30
+ def initialize(object, renderer: nil, dom_element: nil, backend: nil, **parameters)
31
+ @object = object
32
+ @renderer = renderer
33
+ @backend = backend || renderer&.backend || Backends::ThreeJS.new
34
+ @dom_element = dom_element || default_dom_element(renderer)
35
+ @target = Vector3.new
36
+ bind_target_changes
37
+ set_defaults
38
+
39
+ object_handle = @backend.sync(object)
40
+ @handle = @backend.create_orbit_controls(object_handle, @dom_element)
41
+ set_values(parameters)
42
+ end
43
+
44
+ def update
45
+ @backend.update_controls(@handle)
46
+ @backend.sync_object_transform_from_handle(@object)
47
+ self
48
+ end
49
+
50
+ def dispose
51
+ @backend.dispose_controls(@handle)
52
+ self
53
+ end
54
+
55
+ def target=(value)
56
+ @target = coerce_vector3(value)
57
+ bind_target_changes
58
+ sync_target if @handle
59
+ end
60
+
61
+ PROPERTY_NAMES.each do |ruby_name, js_name|
62
+ define_method("#{ruby_name}=") do |value|
63
+ instance_variable_set("@#{ruby_name}", value)
64
+ @backend.set_control_property(@handle, js_name, value) if @handle
65
+ value
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def set_defaults
72
+ @enabled = true
73
+ @enable_damping = false
74
+ @damping_factor = 0.05
75
+ @enable_zoom = true
76
+ @zoom_speed = 1.0
77
+ @enable_rotate = true
78
+ @rotate_speed = 1.0
79
+ @enable_pan = true
80
+ @pan_speed = 1.0
81
+ @auto_rotate = false
82
+ @auto_rotate_speed = 2.0
83
+ @min_distance = 0
84
+ @max_distance = Float::INFINITY
85
+ @min_zoom = 0
86
+ @max_zoom = Float::INFINITY
87
+ end
88
+
89
+ def set_values(parameters)
90
+ parameters.each do |key, value|
91
+ setter = "#{key}="
92
+ public_send(setter, value) if respond_to?(setter)
93
+ end
94
+ end
95
+
96
+ def default_dom_element(renderer)
97
+ return nil unless renderer
98
+
99
+ renderer.dom_element
100
+ end
101
+
102
+ def coerce_vector3(value)
103
+ return value if value.is_a?(Vector3)
104
+ return Vector3.new(*value) if value.respond_to?(:to_ary)
105
+
106
+ raise TypeError, "target must be a Three::Vector3 or an array-like [x, y, z]"
107
+ end
108
+
109
+ def bind_target_changes
110
+ @target.on_change { sync_target if @handle }
111
+ end
112
+
113
+ def sync_target
114
+ @backend.set_orbit_controls_target(@handle, @target.to_a)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../dirty"
4
+ require_relative "event_dispatcher"
5
+
6
+ module Three
7
+ class BufferAttribute < EventDispatcher
8
+ include Dirty
9
+
10
+ @next_id = 0
11
+
12
+ class << self
13
+ attr_accessor :next_id
14
+ end
15
+
16
+ attr_reader :id
17
+ attr_accessor :name, :array, :item_size, :normalized, :usage, :update_ranges, :version
18
+ attr_accessor :component_type
19
+
20
+ def initialize(array, item_size, normalized = false, component_type: :generic)
21
+ super()
22
+ @id = self.class.allocate_id
23
+ @name = ""
24
+ @array = array.dup
25
+ @item_size = item_size
26
+ @normalized = normalized
27
+ @usage = :static_draw
28
+ @update_ranges = []
29
+ @version = 0
30
+ @component_type = component_type
31
+ mark_dirty!
32
+ end
33
+
34
+ def self.allocate_id
35
+ id = BufferAttribute.next_id
36
+ BufferAttribute.next_id += 1
37
+ id
38
+ end
39
+
40
+ def count
41
+ @array.length / @item_size
42
+ end
43
+
44
+ def needs_update=(value)
45
+ return unless value
46
+
47
+ needs_update!
48
+ end
49
+
50
+ def needs_update!
51
+ @version += 1
52
+ mark_dirty!(:array)
53
+ dispatch_event(:change)
54
+ self
55
+ end
56
+
57
+ def set_usage(value)
58
+ @usage = value
59
+ mark_dirty!(:usage)
60
+ self
61
+ end
62
+
63
+ def add_update_range(start, count)
64
+ @update_ranges << { start: start, count: count }
65
+ mark_dirty!(:update_ranges)
66
+ self
67
+ end
68
+
69
+ def clear_update_ranges
70
+ @update_ranges.clear
71
+ mark_dirty!(:update_ranges)
72
+ self
73
+ end
74
+
75
+ def copy(source)
76
+ @name = source.name
77
+ @array = source.array.dup
78
+ @item_size = source.item_size
79
+ @normalized = source.normalized
80
+ @usage = source.usage
81
+ @component_type = source.component_type
82
+ needs_update!
83
+ self
84
+ end
85
+
86
+ def clone
87
+ self.class.new(@array, @item_size, @normalized)
88
+ end
89
+
90
+ def get_component(index, component)
91
+ @array[index * @item_size + component]
92
+ end
93
+
94
+ def set_component(index, component, value)
95
+ @array[index * @item_size + component] = value
96
+ needs_update!
97
+ self
98
+ end
99
+
100
+ def get_x(index)
101
+ get_component(index, 0)
102
+ end
103
+
104
+ def get_y(index)
105
+ get_component(index, 1)
106
+ end
107
+
108
+ def get_z(index)
109
+ get_component(index, 2)
110
+ end
111
+
112
+ def set_x(index, value)
113
+ set_component(index, 0, value)
114
+ end
115
+
116
+ def set_y(index, value)
117
+ set_component(index, 1, value)
118
+ end
119
+
120
+ def set_z(index, value)
121
+ set_component(index, 2, value)
122
+ end
123
+
124
+ def to_h
125
+ {
126
+ item_size: @item_size,
127
+ component_type: @component_type,
128
+ normalized: @normalized,
129
+ array: @array.dup
130
+ }
131
+ end
132
+ end
133
+
134
+ class Float32BufferAttribute < BufferAttribute
135
+ def initialize(array, item_size, normalized = false)
136
+ super(array, item_size, normalized, component_type: :float32)
137
+ end
138
+ end
139
+
140
+ class Uint16BufferAttribute < BufferAttribute
141
+ def initialize(array, item_size, normalized = false)
142
+ super(array, item_size, normalized, component_type: :uint16)
143
+ end
144
+ end
145
+
146
+ class Uint32BufferAttribute < BufferAttribute
147
+ def initialize(array, item_size, normalized = false)
148
+ super(array, item_size, normalized, component_type: :uint32)
149
+ end
150
+ end
151
+ end