3rb 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 (100) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/3rb.gemspec +29 -0
  4. data/CHANGELOG.md +12 -0
  5. data/LICENSE +21 -0
  6. data/README.md +321 -0
  7. data/Rakefile +13 -0
  8. data/examples/01_hello_cube.rb +29 -0
  9. data/examples/02_basic_geometries.rb +56 -0
  10. data/examples/03_materials.rb +61 -0
  11. data/examples/04_lighting.rb +63 -0
  12. data/examples/05_animation.rb +79 -0
  13. data/examples/06_custom_shader.rb +92 -0
  14. data/examples/07_scene_graph.rb +74 -0
  15. data/examples/08_orbit_controls.rb +50 -0
  16. data/examples/09_3d_chart.rb +71 -0
  17. data/examples/10_procedural_terrain.rb +140 -0
  18. data/examples/11_particle_system.rb +68 -0
  19. data/examples/12_model_loader.rb +73 -0
  20. data/examples/13_game_prototype.rb +145 -0
  21. data/examples/14_utah_teapot.rb +291 -0
  22. data/examples/15_stanford_bunny.rb +200 -0
  23. data/examples/16_cornell_box.rb +373 -0
  24. data/examples/17_weird_fractal4.rb +130 -0
  25. data/examples/18_platonic_solids.rb +268 -0
  26. data/lib/3rb/animation/animation_clip.rb +287 -0
  27. data/lib/3rb/animation/animation_mixer.rb +366 -0
  28. data/lib/3rb/cameras/camera.rb +50 -0
  29. data/lib/3rb/cameras/orthographic_camera.rb +92 -0
  30. data/lib/3rb/cameras/perspective_camera.rb +103 -0
  31. data/lib/3rb/controls/orbit_controls.rb +341 -0
  32. data/lib/3rb/core/buffer_attribute.rb +172 -0
  33. data/lib/3rb/core/group.rb +9 -0
  34. data/lib/3rb/core/object3d.rb +298 -0
  35. data/lib/3rb/core/scene.rb +78 -0
  36. data/lib/3rb/dsl/helpers.rb +57 -0
  37. data/lib/3rb/dsl/scene_builder.rb +288 -0
  38. data/lib/3rb/ffi/glfw.rb +61 -0
  39. data/lib/3rb/ffi/opengl.rb +137 -0
  40. data/lib/3rb/ffi/platform.rb +65 -0
  41. data/lib/3rb/geometries/box_geometry.rb +101 -0
  42. data/lib/3rb/geometries/buffer_geometry.rb +345 -0
  43. data/lib/3rb/geometries/cone_geometry.rb +29 -0
  44. data/lib/3rb/geometries/cylinder_geometry.rb +149 -0
  45. data/lib/3rb/geometries/plane_geometry.rb +75 -0
  46. data/lib/3rb/geometries/sphere_geometry.rb +93 -0
  47. data/lib/3rb/geometries/torus_geometry.rb +77 -0
  48. data/lib/3rb/lights/ambient_light.rb +9 -0
  49. data/lib/3rb/lights/directional_light.rb +57 -0
  50. data/lib/3rb/lights/hemisphere_light.rb +26 -0
  51. data/lib/3rb/lights/light.rb +27 -0
  52. data/lib/3rb/lights/point_light.rb +68 -0
  53. data/lib/3rb/lights/rect_area_light.rb +35 -0
  54. data/lib/3rb/lights/spot_light.rb +88 -0
  55. data/lib/3rb/loaders/gltf_loader.rb +304 -0
  56. data/lib/3rb/loaders/loader.rb +94 -0
  57. data/lib/3rb/loaders/obj_loader.rb +186 -0
  58. data/lib/3rb/loaders/texture_loader.rb +55 -0
  59. data/lib/3rb/materials/basic_material.rb +70 -0
  60. data/lib/3rb/materials/lambert_material.rb +102 -0
  61. data/lib/3rb/materials/material.rb +114 -0
  62. data/lib/3rb/materials/phong_material.rb +106 -0
  63. data/lib/3rb/materials/shader_material.rb +104 -0
  64. data/lib/3rb/materials/standard_material.rb +106 -0
  65. data/lib/3rb/math/color.rb +246 -0
  66. data/lib/3rb/math/euler.rb +156 -0
  67. data/lib/3rb/math/math_utils.rb +132 -0
  68. data/lib/3rb/math/matrix3.rb +269 -0
  69. data/lib/3rb/math/matrix4.rb +501 -0
  70. data/lib/3rb/math/quaternion.rb +337 -0
  71. data/lib/3rb/math/vector2.rb +216 -0
  72. data/lib/3rb/math/vector3.rb +366 -0
  73. data/lib/3rb/math/vector4.rb +233 -0
  74. data/lib/3rb/native/gl.rb +382 -0
  75. data/lib/3rb/native/native.rb +55 -0
  76. data/lib/3rb/native/window.rb +111 -0
  77. data/lib/3rb/native.rb +9 -0
  78. data/lib/3rb/objects/line.rb +116 -0
  79. data/lib/3rb/objects/mesh.rb +40 -0
  80. data/lib/3rb/objects/points.rb +71 -0
  81. data/lib/3rb/renderers/opengl_renderer.rb +567 -0
  82. data/lib/3rb/renderers/renderer.rb +60 -0
  83. data/lib/3rb/renderers/shader_lib.rb +100 -0
  84. data/lib/3rb/textures/cube_texture.rb +26 -0
  85. data/lib/3rb/textures/data_texture.rb +35 -0
  86. data/lib/3rb/textures/render_target.rb +125 -0
  87. data/lib/3rb/textures/texture.rb +190 -0
  88. data/lib/3rb/version.rb +5 -0
  89. data/lib/3rb.rb +86 -0
  90. data/shaders/basic.frag +19 -0
  91. data/shaders/basic.vert +15 -0
  92. data/shaders/common/lights.glsl +53 -0
  93. data/shaders/common/uniforms.glsl +9 -0
  94. data/shaders/lambert.frag +37 -0
  95. data/shaders/lambert.vert +22 -0
  96. data/shaders/phong.frag +51 -0
  97. data/shaders/phong.vert +28 -0
  98. data/shaders/standard.frag +92 -0
  99. data/shaders/standard.vert +28 -0
  100. metadata +155 -0
@@ -0,0 +1,341 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sunrb
4
+ class OrbitControls
5
+ STATE = {
6
+ none: -1,
7
+ rotate: 0,
8
+ dolly: 1,
9
+ pan: 2,
10
+ touch_rotate: 3,
11
+ touch_pan: 4,
12
+ touch_dolly_pan: 5,
13
+ touch_dolly_rotate: 6
14
+ }.freeze
15
+
16
+ attr_accessor :object, :dom_element
17
+ attr_accessor :enabled, :target
18
+ attr_accessor :min_distance, :max_distance
19
+ attr_accessor :min_zoom, :max_zoom
20
+ attr_accessor :min_polar_angle, :max_polar_angle
21
+ attr_accessor :min_azimuth_angle, :max_azimuth_angle
22
+ attr_accessor :enable_damping, :damping_factor
23
+ attr_accessor :enable_zoom, :zoom_speed
24
+ attr_accessor :enable_rotate, :rotate_speed
25
+ attr_accessor :enable_pan, :pan_speed, :screen_space_panning
26
+ attr_accessor :key_pan_speed
27
+ attr_accessor :auto_rotate, :auto_rotate_speed
28
+
29
+ def initialize(object, dom_element = nil)
30
+ @object = object
31
+ @dom_element = dom_element
32
+
33
+ @enabled = true
34
+ @target = Vector3.new
35
+
36
+ @min_distance = 0
37
+ @max_distance = Float::INFINITY
38
+
39
+ @min_zoom = 0
40
+ @max_zoom = Float::INFINITY
41
+
42
+ @min_polar_angle = 0
43
+ @max_polar_angle = Math::PI
44
+
45
+ @min_azimuth_angle = -Float::INFINITY
46
+ @max_azimuth_angle = Float::INFINITY
47
+
48
+ @enable_damping = false
49
+ @damping_factor = 0.05
50
+
51
+ @enable_zoom = true
52
+ @zoom_speed = 1.0
53
+
54
+ @enable_rotate = true
55
+ @rotate_speed = 1.0
56
+
57
+ @enable_pan = true
58
+ @pan_speed = 1.0
59
+ @screen_space_panning = true
60
+ @key_pan_speed = 7.0
61
+
62
+ @auto_rotate = false
63
+ @auto_rotate_speed = 2.0
64
+
65
+ @state = STATE[:none]
66
+
67
+ @spherical = Spherical.new
68
+ @spherical_delta = Spherical.new
69
+
70
+ @scale = 1
71
+ @pan_offset = Vector3.new
72
+ @zoom_changed = false
73
+
74
+ @rotate_start = Vector2.new
75
+ @rotate_end = Vector2.new
76
+ @rotate_delta = Vector2.new
77
+
78
+ @pan_start = Vector2.new
79
+ @pan_end = Vector2.new
80
+ @pan_delta = Vector2.new
81
+
82
+ @dolly_start = Vector2.new
83
+ @dolly_end = Vector2.new
84
+ @dolly_delta = Vector2.new
85
+
86
+ @offset = Vector3.new
87
+ @quat = Quaternion.new
88
+ @quat_inverse = Quaternion.new
89
+ @last_position = Vector3.new
90
+ @last_quaternion = Quaternion.new
91
+
92
+ update
93
+ end
94
+
95
+ def update(delta_time = nil)
96
+ position = @object.position
97
+
98
+ @offset.copy(position).sub(@target)
99
+
100
+ @quat.set_from_euler(Euler.new(0, 0, 0))
101
+ @quat_inverse.copy(@quat).invert
102
+
103
+ @offset.apply_quaternion(@quat)
104
+
105
+ @spherical.set_from_vector3(@offset)
106
+
107
+ if @auto_rotate && @state == STATE[:none]
108
+ rotate_left(auto_rotation_angle(delta_time))
109
+ end
110
+
111
+ if @enable_damping
112
+ @spherical.theta += @spherical_delta.theta * @damping_factor
113
+ @spherical.phi += @spherical_delta.phi * @damping_factor
114
+ else
115
+ @spherical.theta += @spherical_delta.theta
116
+ @spherical.phi += @spherical_delta.phi
117
+ end
118
+
119
+ @spherical.theta = clamp_azimuth_angle(@spherical.theta)
120
+ @spherical.phi = [@min_polar_angle, [@max_polar_angle, @spherical.phi].min].max
121
+
122
+ @spherical.make_safe
123
+
124
+ @spherical.radius *= @scale
125
+
126
+ @spherical.radius = [@min_distance, [@max_distance, @spherical.radius].min].max
127
+
128
+ if @enable_damping
129
+ @target.add(@pan_offset.multiply_scalar(@damping_factor))
130
+ else
131
+ @target.add(@pan_offset)
132
+ end
133
+
134
+ @offset.set_from_spherical(@spherical)
135
+ @offset.apply_quaternion(@quat_inverse)
136
+
137
+ position.copy(@target).add(@offset)
138
+
139
+ @object.look_at(@target)
140
+
141
+ if @enable_damping
142
+ @spherical_delta.theta *= (1 - @damping_factor)
143
+ @spherical_delta.phi *= (1 - @damping_factor)
144
+ @pan_offset.multiply_scalar(1 - @damping_factor)
145
+ else
146
+ @spherical_delta.set(0, 0, 0)
147
+ @pan_offset.set(0, 0, 0)
148
+ end
149
+
150
+ @scale = 1
151
+
152
+ if @zoom_changed ||
153
+ @last_position.distance_to(@object.position) > 0.000001 ||
154
+ 8 * (1 - @last_quaternion.dot(@object.quaternion)) > 0.000001
155
+
156
+ @last_position.copy(@object.position)
157
+ @last_quaternion.copy(@object.quaternion)
158
+ @zoom_changed = false
159
+
160
+ true
161
+ else
162
+ false
163
+ end
164
+ end
165
+
166
+ def rotate_left(angle)
167
+ @spherical_delta.theta -= angle
168
+ end
169
+
170
+ def rotate_up(angle)
171
+ @spherical_delta.phi -= angle
172
+ end
173
+
174
+ def pan_left(distance, object_matrix)
175
+ v = Vector3.new
176
+ v.set_from_matrix_column(object_matrix, 0)
177
+ v.multiply_scalar(-distance)
178
+ @pan_offset.add(v)
179
+ end
180
+
181
+ def pan_up(distance, object_matrix)
182
+ v = Vector3.new
183
+
184
+ if @screen_space_panning
185
+ v.set_from_matrix_column(object_matrix, 1)
186
+ else
187
+ v.set_from_matrix_column(object_matrix, 0)
188
+ v.cross_vectors(@object.up, v)
189
+ end
190
+
191
+ v.multiply_scalar(distance)
192
+ @pan_offset.add(v)
193
+ end
194
+
195
+ def pan(delta_x, delta_y)
196
+ element = @dom_element
197
+
198
+ if @object.is_a?(PerspectiveCamera)
199
+ position = @object.position
200
+ offset = position.clone.sub(@target)
201
+ target_distance = offset.length
202
+
203
+ target_distance *= Math.tan((@object.fov / 2) * Math::PI / 180.0)
204
+
205
+ height = element ? element[:height] : 1
206
+ pan_left(2 * delta_x * target_distance / height, @object.matrix_world)
207
+ pan_up(2 * delta_y * target_distance / height, @object.matrix_world)
208
+ elsif @object.is_a?(OrthographicCamera)
209
+ width = element ? element[:width] : 1
210
+ height = element ? element[:height] : 1
211
+ pan_left(delta_x * (@object.right - @object.left) / @object.zoom / width, @object.matrix_world)
212
+ pan_up(delta_y * (@object.top - @object.bottom) / @object.zoom / height, @object.matrix_world)
213
+ end
214
+ end
215
+
216
+ def dolly_out(dolly_scale)
217
+ if @object.is_a?(PerspectiveCamera)
218
+ @scale /= dolly_scale
219
+ elsif @object.is_a?(OrthographicCamera)
220
+ @object.zoom = [@min_zoom, [@max_zoom, @object.zoom / dolly_scale].min].max
221
+ @object.update_projection_matrix
222
+ @zoom_changed = true
223
+ end
224
+ end
225
+
226
+ def dolly_in(dolly_scale)
227
+ if @object.is_a?(PerspectiveCamera)
228
+ @scale *= dolly_scale
229
+ elsif @object.is_a?(OrthographicCamera)
230
+ @object.zoom = [@min_zoom, [@max_zoom, @object.zoom * dolly_scale].min].max
231
+ @object.update_projection_matrix
232
+ @zoom_changed = true
233
+ end
234
+ end
235
+
236
+ def reset
237
+ @target.copy(Vector3.new)
238
+ @object.position.copy(Vector3.new(0, 0, 1))
239
+ @object.zoom = 1
240
+ @object.update_projection_matrix if @object.respond_to?(:update_projection_matrix)
241
+ update
242
+ @state = STATE[:none]
243
+ end
244
+
245
+ def save_state
246
+ @target0 = @target.clone
247
+ @position0 = @object.position.clone
248
+ @zoom0 = @object.zoom
249
+ end
250
+
251
+ def get_polar_angle
252
+ @spherical.phi
253
+ end
254
+
255
+ def get_azimuthal_angle
256
+ @spherical.theta
257
+ end
258
+
259
+ def get_distance
260
+ @object.position.distance_to(@target)
261
+ end
262
+
263
+ def dispose
264
+ # Clean up event listeners if any
265
+ end
266
+
267
+ private
268
+
269
+ def auto_rotation_angle(delta_time)
270
+ if delta_time
271
+ (2 * Math::PI / 60 / 60) * @auto_rotate_speed * delta_time
272
+ else
273
+ (2 * Math::PI / 60 / 60) * @auto_rotate_speed
274
+ end
275
+ end
276
+
277
+ def clamp_azimuth_angle(angle)
278
+ return angle if @min_azimuth_angle == -Float::INFINITY && @max_azimuth_angle == Float::INFINITY
279
+
280
+ if angle < -Math::PI
281
+ angle += 2 * Math::PI
282
+ elsif angle > Math::PI
283
+ angle -= 2 * Math::PI
284
+ end
285
+
286
+ [@min_azimuth_angle, [@max_azimuth_angle, angle].min].max
287
+ end
288
+ end
289
+
290
+ class Spherical
291
+ attr_accessor :radius, :phi, :theta
292
+
293
+ def initialize(radius = 1.0, phi = 0, theta = 0)
294
+ @radius = radius
295
+ @phi = phi
296
+ @theta = theta
297
+ end
298
+
299
+ def set(radius, phi, theta)
300
+ @radius = radius
301
+ @phi = phi
302
+ @theta = theta
303
+ self
304
+ end
305
+
306
+ def copy(other)
307
+ @radius = other.radius
308
+ @phi = other.phi
309
+ @theta = other.theta
310
+ self
311
+ end
312
+
313
+ def clone
314
+ Spherical.new(@radius, @phi, @theta)
315
+ end
316
+
317
+ def make_safe
318
+ eps = 0.000001
319
+ @phi = [eps, [Math::PI - eps, @phi].min].max
320
+ self
321
+ end
322
+
323
+ def set_from_vector3(v)
324
+ set_from_cartesian_coords(v.x, v.y, v.z)
325
+ end
326
+
327
+ def set_from_cartesian_coords(x, y, z)
328
+ @radius = Math.sqrt(x * x + y * y + z * z)
329
+
330
+ if @radius == 0
331
+ @theta = 0
332
+ @phi = 0
333
+ else
334
+ @theta = Math.atan2(x, z)
335
+ @phi = Math.acos([[-1, y / @radius].max, 1].min)
336
+ end
337
+
338
+ self
339
+ end
340
+ end
341
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sunrb
4
+ class BufferAttribute
5
+ attr_accessor :array, :item_size, :normalized
6
+ attr_accessor :usage, :update_range
7
+ attr_reader :count, :version
8
+
9
+ STATIC_DRAW = 35044
10
+ DYNAMIC_DRAW = 35048
11
+
12
+ def initialize(array, item_size, normalized: false)
13
+ @array = array.is_a?(Array) ? array.map(&:to_f) : array
14
+ @item_size = item_size
15
+ @normalized = normalized
16
+ @count = @array.length / item_size
17
+ @version = 0
18
+ @usage = STATIC_DRAW
19
+ @update_range = { offset: 0, count: -1 }
20
+ end
21
+
22
+ def needs_update=(value)
23
+ @version += 1 if value
24
+ end
25
+
26
+ def set_usage(value)
27
+ @usage = value
28
+ self
29
+ end
30
+
31
+ def get_x(index)
32
+ @array[index * @item_size]
33
+ end
34
+
35
+ def set_x(index, x)
36
+ @array[index * @item_size] = x.to_f
37
+ self
38
+ end
39
+
40
+ def get_y(index)
41
+ @array[index * @item_size + 1]
42
+ end
43
+
44
+ def set_y(index, y)
45
+ @array[index * @item_size + 1] = y.to_f
46
+ self
47
+ end
48
+
49
+ def get_z(index)
50
+ @array[index * @item_size + 2]
51
+ end
52
+
53
+ def set_z(index, z)
54
+ @array[index * @item_size + 2] = z.to_f
55
+ self
56
+ end
57
+
58
+ def get_w(index)
59
+ @array[index * @item_size + 3]
60
+ end
61
+
62
+ def set_w(index, w)
63
+ @array[index * @item_size + 3] = w.to_f
64
+ self
65
+ end
66
+
67
+ def set_xy(index, x, y)
68
+ idx = index * @item_size
69
+ @array[idx] = x.to_f
70
+ @array[idx + 1] = y.to_f
71
+ self
72
+ end
73
+
74
+ def set_xyz(index, x, y, z)
75
+ idx = index * @item_size
76
+ @array[idx] = x.to_f
77
+ @array[idx + 1] = y.to_f
78
+ @array[idx + 2] = z.to_f
79
+ self
80
+ end
81
+
82
+ def set_xyzw(index, x, y, z, w)
83
+ idx = index * @item_size
84
+ @array[idx] = x.to_f
85
+ @array[idx + 1] = y.to_f
86
+ @array[idx + 2] = z.to_f
87
+ @array[idx + 3] = w.to_f
88
+ self
89
+ end
90
+
91
+ def copy_at(index1, attribute, index2)
92
+ idx1 = index1 * @item_size
93
+ idx2 = index2 * attribute.item_size
94
+ @item_size.times do |i|
95
+ @array[idx1 + i] = attribute.array[idx2 + i]
96
+ end
97
+ self
98
+ end
99
+
100
+ def copy_array(array)
101
+ @array = array.map(&:to_f)
102
+ self
103
+ end
104
+
105
+ def apply_matrix3(m)
106
+ return self if @item_size != 2
107
+
108
+ v = Vector2.new
109
+ @count.times do |i|
110
+ v.x = get_x(i)
111
+ v.y = get_y(i)
112
+ v.apply_matrix3(m)
113
+ set_xy(i, v.x, v.y)
114
+ end
115
+ self
116
+ end
117
+
118
+ def apply_matrix4(m)
119
+ return self if @item_size < 3
120
+
121
+ v = Vector3.new
122
+ @count.times do |i|
123
+ v.x = get_x(i)
124
+ v.y = get_y(i)
125
+ v.z = get_z(i)
126
+ v.apply_matrix4(m)
127
+ set_xyz(i, v.x, v.y, v.z)
128
+ end
129
+ self
130
+ end
131
+
132
+ def apply_normal_matrix(m)
133
+ return self if @item_size != 3
134
+
135
+ v = Vector3.new
136
+ @count.times do |i|
137
+ v.x = get_x(i)
138
+ v.y = get_y(i)
139
+ v.z = get_z(i)
140
+ v.apply_matrix3(m).normalize!
141
+ set_xyz(i, v.x, v.y, v.z)
142
+ end
143
+ self
144
+ end
145
+
146
+ def clone
147
+ BufferAttribute.new(@array.dup, @item_size, normalized: @normalized)
148
+ end
149
+
150
+ def to_a
151
+ @array.dup
152
+ end
153
+ end
154
+
155
+ class Float32BufferAttribute < BufferAttribute
156
+ def initialize(array, item_size, normalized: false)
157
+ super(array, item_size, normalized: normalized)
158
+ end
159
+ end
160
+
161
+ class Uint16BufferAttribute < BufferAttribute
162
+ def initialize(array, item_size, normalized: false)
163
+ super(array.map(&:to_i), item_size, normalized: normalized)
164
+ end
165
+ end
166
+
167
+ class Uint32BufferAttribute < BufferAttribute
168
+ def initialize(array, item_size, normalized: false)
169
+ super(array.map(&:to_i), item_size, normalized: normalized)
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sunrb
4
+ class Group < Object3D
5
+ def initialize
6
+ super
7
+ end
8
+ end
9
+ end