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,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Three
4
+ class Vector2
5
+ include Enumerable
6
+
7
+ attr_reader :x, :y
8
+
9
+ def initialize(x = 0, y = 0)
10
+ @x = x
11
+ @y = y
12
+ @on_change_callback = proc {}
13
+ end
14
+
15
+ def x=(value)
16
+ @x = value
17
+ changed!
18
+ end
19
+
20
+ def y=(value)
21
+ @y = value
22
+ changed!
23
+ end
24
+
25
+ def set(x, y)
26
+ @x = x
27
+ @y = y
28
+ changed!
29
+ self
30
+ end
31
+
32
+ def set_scalar(scalar)
33
+ set(scalar, scalar)
34
+ end
35
+
36
+ def clone
37
+ self.class.new(@x, @y)
38
+ end
39
+
40
+ def copy(vector)
41
+ set(vector.x, vector.y)
42
+ end
43
+
44
+ def from_array(array, offset = 0)
45
+ set(array[offset], array[offset + 1])
46
+ end
47
+
48
+ def to_array(array = [], offset = 0)
49
+ array[offset] = @x
50
+ array[offset + 1] = @y
51
+ array
52
+ end
53
+
54
+ def equals?(vector, epsilon: 0.0)
55
+ (@x - vector.x).abs <= epsilon &&
56
+ (@y - vector.y).abs <= epsilon
57
+ end
58
+
59
+ def ==(other)
60
+ other.is_a?(self.class) && equals?(other)
61
+ end
62
+
63
+ def each
64
+ return enum_for(:each) unless block_given?
65
+
66
+ yield @x
67
+ yield @y
68
+ end
69
+
70
+ def to_a
71
+ [@x, @y]
72
+ end
73
+
74
+ def deconstruct
75
+ to_a
76
+ end
77
+
78
+ def on_change(&callback)
79
+ @on_change_callback = callback || proc {}
80
+ self
81
+ end
82
+
83
+ alias _on_change on_change
84
+
85
+ def inspect
86
+ "#<#{self.class} x=#{@x.inspect} y=#{@y.inspect}>"
87
+ end
88
+
89
+ private
90
+
91
+ def changed!
92
+ @on_change_callback.call
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,396 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Three
4
+ class Vector3
5
+ include Enumerable
6
+
7
+ attr_reader :x, :y, :z
8
+
9
+ def initialize(x = 0, y = 0, z = 0)
10
+ @x = x
11
+ @y = y
12
+ @z = z
13
+ @on_change_callback = proc {}
14
+ end
15
+
16
+ def x=(value)
17
+ @x = value
18
+ changed!
19
+ end
20
+
21
+ def y=(value)
22
+ @y = value
23
+ changed!
24
+ end
25
+
26
+ def z=(value)
27
+ @z = value
28
+ changed!
29
+ end
30
+
31
+ def set(x, y, z = @z)
32
+ @x = x
33
+ @y = y
34
+ @z = z
35
+ changed!
36
+ self
37
+ end
38
+
39
+ def set_scalar(scalar)
40
+ @x = scalar
41
+ @y = scalar
42
+ @z = scalar
43
+ changed!
44
+ self
45
+ end
46
+
47
+ def set_x(x)
48
+ @x = x
49
+ changed!
50
+ self
51
+ end
52
+
53
+ def set_y(y)
54
+ @y = y
55
+ changed!
56
+ self
57
+ end
58
+
59
+ def set_z(z)
60
+ @z = z
61
+ changed!
62
+ self
63
+ end
64
+
65
+ def set_component(index, value)
66
+ case index
67
+ when 0 then @x = value
68
+ when 1 then @y = value
69
+ when 2 then @z = value
70
+ else raise IndexError, "index is out of range: #{index}"
71
+ end
72
+
73
+ changed!
74
+ self
75
+ end
76
+
77
+ def get_component(index)
78
+ case index
79
+ when 0 then @x
80
+ when 1 then @y
81
+ when 2 then @z
82
+ else raise IndexError, "index is out of range: #{index}"
83
+ end
84
+ end
85
+
86
+ alias [] get_component
87
+ alias []= set_component
88
+
89
+ def clone
90
+ self.class.new(@x, @y, @z)
91
+ end
92
+
93
+ def copy(vector)
94
+ @x = vector.x
95
+ @y = vector.y
96
+ @z = vector.z
97
+ changed!
98
+ self
99
+ end
100
+
101
+ def add(vector)
102
+ @x += vector.x
103
+ @y += vector.y
104
+ @z += vector.z
105
+ changed!
106
+ self
107
+ end
108
+
109
+ def add_scalar(scalar)
110
+ @x += scalar
111
+ @y += scalar
112
+ @z += scalar
113
+ changed!
114
+ self
115
+ end
116
+
117
+ def add_vectors(a, b)
118
+ @x = a.x + b.x
119
+ @y = a.y + b.y
120
+ @z = a.z + b.z
121
+ changed!
122
+ self
123
+ end
124
+
125
+ def add_scaled_vector(vector, scale)
126
+ @x += vector.x * scale
127
+ @y += vector.y * scale
128
+ @z += vector.z * scale
129
+ changed!
130
+ self
131
+ end
132
+
133
+ def sub(vector)
134
+ @x -= vector.x
135
+ @y -= vector.y
136
+ @z -= vector.z
137
+ changed!
138
+ self
139
+ end
140
+
141
+ def sub_scalar(scalar)
142
+ @x -= scalar
143
+ @y -= scalar
144
+ @z -= scalar
145
+ changed!
146
+ self
147
+ end
148
+
149
+ def sub_vectors(a, b)
150
+ @x = a.x - b.x
151
+ @y = a.y - b.y
152
+ @z = a.z - b.z
153
+ changed!
154
+ self
155
+ end
156
+
157
+ def multiply(vector)
158
+ @x *= vector.x
159
+ @y *= vector.y
160
+ @z *= vector.z
161
+ changed!
162
+ self
163
+ end
164
+
165
+ def multiply_scalar(scalar)
166
+ @x *= scalar
167
+ @y *= scalar
168
+ @z *= scalar
169
+ changed!
170
+ self
171
+ end
172
+
173
+ def divide(vector)
174
+ @x = @x.to_f / vector.x
175
+ @y = @y.to_f / vector.y
176
+ @z = @z.to_f / vector.z
177
+ changed!
178
+ self
179
+ end
180
+
181
+ def divide_scalar(scalar)
182
+ multiply_scalar(1.0 / scalar)
183
+ end
184
+
185
+ def negate
186
+ multiply_scalar(-1)
187
+ end
188
+
189
+ def dot(vector)
190
+ @x * vector.x + @y * vector.y + @z * vector.z
191
+ end
192
+
193
+ def cross(vector)
194
+ cross_vectors(self, vector)
195
+ end
196
+
197
+ def cross_vectors(a, b)
198
+ ax = a.x
199
+ ay = a.y
200
+ az = a.z
201
+ bx = b.x
202
+ by = b.y
203
+ bz = b.z
204
+
205
+ @x = ay * bz - az * by
206
+ @y = az * bx - ax * bz
207
+ @z = ax * by - ay * bx
208
+ changed!
209
+ self
210
+ end
211
+
212
+ def length_sq
213
+ @x * @x + @y * @y + @z * @z
214
+ end
215
+
216
+ def length
217
+ Math.sqrt(length_sq)
218
+ end
219
+
220
+ def manhattan_length
221
+ @x.abs + @y.abs + @z.abs
222
+ end
223
+
224
+ def normalize
225
+ current_length = length
226
+ divide_scalar(current_length.zero? ? 1 : current_length)
227
+ end
228
+
229
+ def set_length(value)
230
+ old_length = length
231
+ return self if old_length.zero? || old_length == value
232
+
233
+ multiply_scalar(value / old_length)
234
+ end
235
+
236
+ def distance_to(vector)
237
+ Math.sqrt(distance_to_squared(vector))
238
+ end
239
+
240
+ def distance_to_squared(vector)
241
+ dx = @x - vector.x
242
+ dy = @y - vector.y
243
+ dz = @z - vector.z
244
+ dx * dx + dy * dy + dz * dz
245
+ end
246
+
247
+ def apply_matrix4(matrix)
248
+ elements = matrix.elements
249
+ x = @x
250
+ y = @y
251
+ z = @z
252
+ w = 1.0 / (elements[3] * x + elements[7] * y + elements[11] * z + elements[15])
253
+
254
+ @x = (elements[0] * x + elements[4] * y + elements[8] * z + elements[12]) * w
255
+ @y = (elements[1] * x + elements[5] * y + elements[9] * z + elements[13]) * w
256
+ @z = (elements[2] * x + elements[6] * y + elements[10] * z + elements[14]) * w
257
+ changed!
258
+ self
259
+ end
260
+
261
+ def apply_matrix3(matrix)
262
+ elements = matrix.elements
263
+ x = @x
264
+ y = @y
265
+ z = @z
266
+
267
+ @x = elements[0] * x + elements[3] * y + elements[6] * z
268
+ @y = elements[1] * x + elements[4] * y + elements[7] * z
269
+ @z = elements[2] * x + elements[5] * y + elements[8] * z
270
+ changed!
271
+ self
272
+ end
273
+
274
+ def apply_quaternion(quaternion)
275
+ vx = @x
276
+ vy = @y
277
+ vz = @z
278
+ qx = quaternion.x
279
+ qy = quaternion.y
280
+ qz = quaternion.z
281
+ qw = quaternion.w
282
+
283
+ tx = 2 * (qy * vz - qz * vy)
284
+ ty = 2 * (qz * vx - qx * vz)
285
+ tz = 2 * (qx * vy - qy * vx)
286
+
287
+ @x = vx + qw * tx + qy * tz - qz * ty
288
+ @y = vy + qw * ty + qz * tx - qx * tz
289
+ @z = vz + qw * tz + qx * ty - qy * tx
290
+ changed!
291
+ self
292
+ end
293
+
294
+ def set_from_matrix_position(matrix)
295
+ elements = matrix.elements
296
+ @x = elements[12]
297
+ @y = elements[13]
298
+ @z = elements[14]
299
+ changed!
300
+ self
301
+ end
302
+
303
+ def set_from_matrix_scale(matrix)
304
+ sx = set_from_matrix_column(matrix, 0).length
305
+ sy = set_from_matrix_column(matrix, 1).length
306
+ sz = set_from_matrix_column(matrix, 2).length
307
+ set(sx, sy, sz)
308
+ end
309
+
310
+ def set_from_matrix_column(matrix, index)
311
+ from_array(matrix.elements, index * 4)
312
+ end
313
+
314
+ def set_from_matrix3_column(matrix, index)
315
+ from_array(matrix.elements, index * 3)
316
+ end
317
+
318
+ def from_array(array, offset = 0)
319
+ @x = array[offset]
320
+ @y = array[offset + 1]
321
+ @z = array[offset + 2]
322
+ changed!
323
+ self
324
+ end
325
+
326
+ def on_change(&callback)
327
+ @on_change_callback = callback || proc {}
328
+ self
329
+ end
330
+
331
+ alias _on_change on_change
332
+
333
+ def to_array(array = [], offset = 0)
334
+ array[offset] = @x
335
+ array[offset + 1] = @y
336
+ array[offset + 2] = @z
337
+ array
338
+ end
339
+
340
+ def equals?(vector, epsilon: 0.0)
341
+ (@x - vector.x).abs <= epsilon &&
342
+ (@y - vector.y).abs <= epsilon &&
343
+ (@z - vector.z).abs <= epsilon
344
+ end
345
+
346
+ def ==(other)
347
+ other.is_a?(self.class) && equals?(other)
348
+ end
349
+
350
+ def +(other)
351
+ clone.add(other)
352
+ end
353
+
354
+ def -(other)
355
+ clone.sub(other)
356
+ end
357
+
358
+ def *(scalar)
359
+ clone.multiply_scalar(scalar)
360
+ end
361
+
362
+ def /(scalar)
363
+ clone.divide_scalar(scalar)
364
+ end
365
+
366
+ def -@
367
+ clone.negate
368
+ end
369
+
370
+ def each
371
+ return enum_for(:each) unless block_given?
372
+
373
+ yield @x
374
+ yield @y
375
+ yield @z
376
+ end
377
+
378
+ def to_a
379
+ [@x, @y, @z]
380
+ end
381
+
382
+ def deconstruct
383
+ to_a
384
+ end
385
+
386
+ def inspect
387
+ "#<#{self.class} x=#{@x.inspect} y=#{@y.inspect} z=#{@z.inspect}>"
388
+ end
389
+
390
+ private
391
+
392
+ def changed!
393
+ @on_change_callback.call
394
+ end
395
+ end
396
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../core/object3d"
4
+
5
+ module Three
6
+ class ExternalObject3D < Object3D
7
+ attr_reader :handle
8
+
9
+ def initialize(handle, type: "ExternalObject3D")
10
+ super()
11
+ @handle = handle
12
+ @type = type
13
+ mark_clean!
14
+ end
15
+
16
+ def add(*)
17
+ raise NotImplementedError, "ExternalObject3D does not support Ruby child mutation yet"
18
+ end
19
+
20
+ def remove(*)
21
+ raise NotImplementedError, "ExternalObject3D does not support Ruby child mutation yet"
22
+ end
23
+
24
+ def clear
25
+ raise NotImplementedError, "ExternalObject3D does not support Ruby child mutation yet"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../core/object3d"
4
+
5
+ module Three
6
+ class Group < Object3D
7
+ def initialize
8
+ super
9
+ @type = "Group"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../core/buffer_geometry"
4
+ require_relative "../materials/mesh_basic_material"
5
+ require_relative "../math/color"
6
+ require_relative "../math/matrix4"
7
+ require_relative "mesh"
8
+
9
+ module Three
10
+ class InstancedMesh < Mesh
11
+ attr_reader :count, :capacity, :instance_matrices, :instance_colors
12
+
13
+ def initialize(geometry = BufferGeometry.new, material = MeshBasicMaterial.new, count = 1)
14
+ super(geometry, material)
15
+ @type = "InstancedMesh"
16
+ @capacity = coerce_count(count)
17
+ @count = @capacity
18
+ @instance_matrices = Array.new(@capacity) { Matrix4.new }
19
+ @instance_colors = nil
20
+ mark_dirty!(:instances)
21
+ end
22
+
23
+ def count=(value)
24
+ integer = coerce_count(value)
25
+ raise ArgumentError, "count cannot exceed capacity" if integer > @capacity
26
+
27
+ @count = integer
28
+ mark_dirty!(:mesh)
29
+ end
30
+
31
+ def set_matrix_at(index, matrix)
32
+ validate_instance_index(index)
33
+ @instance_matrices[index] = coerce_matrix(matrix)
34
+ mark_dirty!(:instances)
35
+ self
36
+ end
37
+
38
+ def get_matrix_at(index, target = Matrix4.new)
39
+ validate_instance_index(index)
40
+ target.copy(@instance_matrices[index])
41
+ end
42
+
43
+ def set_color_at(index, color)
44
+ validate_instance_index(index)
45
+ ensure_instance_colors
46
+ @instance_colors[index] = coerce_color(color)
47
+ mark_dirty!(:instance_colors)
48
+ self
49
+ end
50
+
51
+ def get_color_at(index, target = Color.new)
52
+ validate_instance_index(index)
53
+ target.copy(@instance_colors ? @instance_colors[index] : Color.new)
54
+ end
55
+
56
+ def instance_matrix_needs_update!
57
+ mark_dirty!(:instances)
58
+ self
59
+ end
60
+
61
+ def instance_color_needs_update!
62
+ mark_dirty!(:instance_colors)
63
+ self
64
+ end
65
+
66
+ def to_h
67
+ super.merge(
68
+ count: @count,
69
+ capacity: @capacity,
70
+ instance_matrices: @instance_matrices.map(&:to_a),
71
+ instance_colors: @instance_colors&.map(&:to_a)
72
+ )
73
+ end
74
+
75
+ private
76
+
77
+ def coerce_count(value)
78
+ integer = Integer(value)
79
+ raise ArgumentError, "count must be non-negative" if integer.negative?
80
+
81
+ integer
82
+ end
83
+
84
+ def validate_instance_index(index)
85
+ raise IndexError, "instance index #{index} is out of range" unless index.is_a?(Integer) && index >= 0 && index < @capacity
86
+ end
87
+
88
+ def ensure_instance_colors
89
+ @instance_colors ||= Array.new(@capacity) { Color.new }
90
+ end
91
+
92
+ def coerce_matrix(matrix)
93
+ return matrix.clone if matrix.is_a?(Matrix4)
94
+
95
+ array = matrix.to_a if matrix.respond_to?(:to_a)
96
+ raise TypeError, "matrix must be a Three::Matrix4 or 16-element array" unless array&.length == 16
97
+
98
+ Matrix4.new.from_array(array)
99
+ end
100
+
101
+ def coerce_color(color)
102
+ return color.clone if color.is_a?(Color)
103
+
104
+ array = color.to_a if color.respond_to?(:to_a)
105
+ return Color.new(*array) if array&.length == 3
106
+
107
+ Color.new(color)
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../core/buffer_geometry"
4
+ require_relative "../materials/line_basic_material"
5
+ require_relative "group"
6
+
7
+ module Three
8
+ class Line < Object3D
9
+ attr_reader :geometry, :material
10
+
11
+ def initialize(geometry = BufferGeometry.new, material = LineBasicMaterial.new)
12
+ super()
13
+ @type = "Line"
14
+ @geometry = nil
15
+ @material = nil
16
+ self.geometry = geometry
17
+ self.material = material
18
+ end
19
+
20
+ def geometry=(value)
21
+ @geometry.remove_dirty_dependent(self) if @geometry.respond_to?(:remove_dirty_dependent)
22
+ @geometry = value
23
+ @geometry.add_dirty_dependent(self) if @geometry.respond_to?(:add_dirty_dependent)
24
+ mark_dirty!(:line)
25
+ end
26
+
27
+ def material=(value)
28
+ @material.remove_dirty_dependent(self) if @material.respond_to?(:remove_dirty_dependent)
29
+ @material = value
30
+ @material.add_dirty_dependent(self) if @material.respond_to?(:add_dirty_dependent)
31
+ mark_dirty!(:line)
32
+ end
33
+
34
+ def to_h
35
+ super.merge(
36
+ geometry: @geometry.uuid,
37
+ material: @material.respond_to?(:uuid) ? @material.uuid : nil
38
+ )
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../core/buffer_geometry"
4
+ require_relative "../materials/mesh_basic_material"
5
+ require_relative "group"
6
+
7
+ module Three
8
+ class Mesh < Object3D
9
+ attr_reader :geometry, :material
10
+ attr_accessor :morph_target_dictionary, :morph_target_influences, :count
11
+
12
+ def initialize(geometry = BufferGeometry.new, material = MeshBasicMaterial.new)
13
+ super()
14
+ @type = "Mesh"
15
+ @geometry = nil
16
+ @material = nil
17
+ @morph_target_dictionary = nil
18
+ @morph_target_influences = nil
19
+ @count = 1
20
+ self.geometry = geometry
21
+ self.material = material
22
+ end
23
+
24
+ def geometry=(value)
25
+ @geometry.remove_dirty_dependent(self) if @geometry.respond_to?(:remove_dirty_dependent)
26
+ @geometry = value
27
+ @geometry.add_dirty_dependent(self) if @geometry.respond_to?(:add_dirty_dependent)
28
+ mark_dirty!(:mesh)
29
+ end
30
+
31
+ def material=(value)
32
+ @material.remove_dirty_dependent(self) if @material.respond_to?(:remove_dirty_dependent)
33
+ @material = value
34
+ @material.add_dirty_dependent(self) if @material.respond_to?(:add_dirty_dependent)
35
+ mark_dirty!(:mesh)
36
+ end
37
+
38
+ def to_h
39
+ super.merge(
40
+ geometry: @geometry.uuid,
41
+ material: @material.respond_to?(:uuid) ? @material.uuid : nil
42
+ )
43
+ end
44
+ end
45
+ end