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,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+
5
+ module Sunrb
6
+ module FFI
7
+ module GLFW
8
+ extend ::FFI::Library
9
+
10
+ ffi_lib Platform.glfw_lib
11
+
12
+ CONTEXT_VERSION_MAJOR = 0x00022002
13
+ CONTEXT_VERSION_MINOR = 0x00022003
14
+ OPENGL_PROFILE = 0x00022008
15
+ OPENGL_CORE_PROFILE = 0x00032001
16
+ OPENGL_FORWARD_COMPAT = 0x00022006
17
+ SAMPLES = 0x0002100D
18
+ COCOA_RETINA_FRAMEBUFFER = 0x00023001
19
+
20
+ PRESS = 1
21
+ RELEASE = 0
22
+ REPEAT = 2
23
+
24
+ KEY_ESCAPE = 256
25
+ KEY_SPACE = 32
26
+ KEY_W = 87
27
+ KEY_A = 65
28
+ KEY_S = 83
29
+ KEY_D = 68
30
+ KEY_UP = 265
31
+ KEY_DOWN = 264
32
+ KEY_LEFT = 263
33
+ KEY_RIGHT = 262
34
+
35
+ MOUSE_BUTTON_LEFT = 0
36
+ MOUSE_BUTTON_RIGHT = 1
37
+ MOUSE_BUTTON_MIDDLE = 2
38
+
39
+ callback :error_callback, [:int, :string], :void
40
+
41
+ attach_function :glfwInit, [], :int
42
+ attach_function :glfwTerminate, [], :void
43
+ attach_function :glfwSetErrorCallback, [:error_callback], :pointer
44
+ attach_function :glfwWindowHint, [:int, :int], :void
45
+ attach_function :glfwCreateWindow, [:int, :int, :string, :pointer, :pointer], :pointer
46
+ attach_function :glfwDestroyWindow, [:pointer], :void
47
+ attach_function :glfwWindowShouldClose, [:pointer], :int
48
+ attach_function :glfwSetWindowShouldClose, [:pointer, :int], :void
49
+ attach_function :glfwSetWindowTitle, [:pointer, :string], :void
50
+ attach_function :glfwMakeContextCurrent, [:pointer], :void
51
+ attach_function :glfwSwapInterval, [:int], :void
52
+ attach_function :glfwSwapBuffers, [:pointer], :void
53
+ attach_function :glfwPollEvents, [], :void
54
+ attach_function :glfwGetKey, [:pointer, :int], :int
55
+ attach_function :glfwGetMouseButton, [:pointer, :int], :int
56
+ attach_function :glfwGetCursorPos, [:pointer, :pointer, :pointer], :void
57
+ attach_function :glfwGetFramebufferSize, [:pointer, :pointer, :pointer], :void
58
+ attach_function :glfwGetTime, [], :double
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+
5
+ module Sunrb
6
+ module FFI
7
+ module OpenGL
8
+ extend ::FFI::Library
9
+
10
+ ffi_lib Platform.opengl_lib
11
+
12
+ GL_TRUE = 1
13
+ GL_FALSE = 0
14
+
15
+ GL_COLOR_BUFFER_BIT = 0x00004000
16
+ GL_DEPTH_BUFFER_BIT = 0x00000100
17
+ GL_STENCIL_BUFFER_BIT = 0x00000400
18
+
19
+ GL_DEPTH_TEST = 0x0B71
20
+ GL_BLEND = 0x0BE2
21
+ GL_CULL_FACE = 0x0B44
22
+
23
+ GL_LESS = 0x0201
24
+ GL_LEQUAL = 0x0203
25
+ GL_GREATER = 0x0204
26
+
27
+ GL_SRC_ALPHA = 0x0302
28
+ GL_ONE_MINUS_SRC_ALPHA = 0x0303
29
+
30
+ GL_FRONT = 0x0404
31
+ GL_BACK = 0x0405
32
+ GL_FRONT_AND_BACK = 0x0408
33
+ GL_LINE = 0x1B01
34
+ GL_FILL = 0x1B02
35
+
36
+ GL_CW = 0x0900
37
+ GL_CCW = 0x0901
38
+
39
+ GL_VERTEX_SHADER = 0x8B31
40
+ GL_FRAGMENT_SHADER = 0x8B30
41
+ GL_COMPILE_STATUS = 0x8B81
42
+ GL_LINK_STATUS = 0x8B82
43
+ GL_INFO_LOG_LENGTH = 0x8B84
44
+
45
+ GL_ARRAY_BUFFER = 0x8892
46
+ GL_ELEMENT_ARRAY_BUFFER = 0x8893
47
+ GL_STATIC_DRAW = 0x88E4
48
+ GL_DYNAMIC_DRAW = 0x88E8
49
+
50
+ GL_FLOAT = 0x1406
51
+ GL_UNSIGNED_INT = 0x1405
52
+ GL_UNSIGNED_BYTE = 0x1401
53
+
54
+ GL_TRIANGLES = 0x0004
55
+ GL_LINES = 0x0001
56
+ GL_POINTS = 0x0000
57
+
58
+ GL_TEXTURE_2D = 0x0DE1
59
+ GL_TEXTURE0 = 0x84C0
60
+ GL_TEXTURE_MIN_FILTER = 0x2801
61
+ GL_TEXTURE_MAG_FILTER = 0x2800
62
+ GL_TEXTURE_WRAP_S = 0x2802
63
+ GL_TEXTURE_WRAP_T = 0x2803
64
+ GL_LINEAR = 0x2601
65
+ GL_NEAREST = 0x2600
66
+ GL_REPEAT = 0x2901
67
+ GL_CLAMP_TO_EDGE = 0x812F
68
+ GL_RGBA = 0x1908
69
+ GL_RGB = 0x1907
70
+
71
+ GL_VENDOR = 0x1F00
72
+ GL_RENDERER = 0x1F01
73
+ GL_VERSION = 0x1F02
74
+
75
+ attach_function :glClearColor, [:float, :float, :float, :float], :void
76
+ attach_function :glClear, [:uint], :void
77
+ attach_function :glViewport, [:int, :int, :int, :int], :void
78
+ attach_function :glEnable, [:uint], :void
79
+ attach_function :glDisable, [:uint], :void
80
+ attach_function :glDepthFunc, [:uint], :void
81
+ attach_function :glBlendFunc, [:uint, :uint], :void
82
+ attach_function :glCullFace, [:uint], :void
83
+ attach_function :glFrontFace, [:uint], :void
84
+ attach_function :glGetError, [], :uint
85
+ attach_function :glGetString, [:uint], :pointer
86
+ attach_function :glPolygonMode, [:uint, :uint], :void
87
+ attach_function :glLineWidth, [:float], :void
88
+
89
+ attach_function :glCreateShader, [:uint], :uint
90
+ attach_function :glShaderSource, [:uint, :int, :pointer, :pointer], :void
91
+ attach_function :glCompileShader, [:uint], :void
92
+ attach_function :glGetShaderiv, [:uint, :uint, :pointer], :void
93
+ attach_function :glGetShaderInfoLog, [:uint, :int, :pointer, :pointer], :void
94
+ attach_function :glDeleteShader, [:uint], :void
95
+
96
+ attach_function :glCreateProgram, [], :uint
97
+ attach_function :glAttachShader, [:uint, :uint], :void
98
+ attach_function :glLinkProgram, [:uint], :void
99
+ attach_function :glGetProgramiv, [:uint, :uint, :pointer], :void
100
+ attach_function :glGetProgramInfoLog, [:uint, :int, :pointer, :pointer], :void
101
+ attach_function :glUseProgram, [:uint], :void
102
+ attach_function :glDeleteProgram, [:uint], :void
103
+
104
+ attach_function :glGetUniformLocation, [:uint, :string], :int
105
+ attach_function :glUniform1f, [:int, :float], :void
106
+ attach_function :glUniform2f, [:int, :float, :float], :void
107
+ attach_function :glUniform3f, [:int, :float, :float, :float], :void
108
+ attach_function :glUniform4f, [:int, :float, :float, :float, :float], :void
109
+ attach_function :glUniform1i, [:int, :int], :void
110
+ attach_function :glUniformMatrix4fv, [:int, :int, :uchar, :pointer], :void
111
+ attach_function :glUniformMatrix3fv, [:int, :int, :uchar, :pointer], :void
112
+
113
+ attach_function :glGenVertexArrays, [:int, :pointer], :void
114
+ attach_function :glBindVertexArray, [:uint], :void
115
+ attach_function :glDeleteVertexArrays, [:int, :pointer], :void
116
+
117
+ attach_function :glGenBuffers, [:int, :pointer], :void
118
+ attach_function :glBindBuffer, [:uint, :uint], :void
119
+ attach_function :glBufferData, [:uint, :long, :pointer, :uint], :void
120
+ attach_function :glDeleteBuffers, [:int, :pointer], :void
121
+
122
+ attach_function :glVertexAttribPointer, [:uint, :int, :uint, :uchar, :int, :pointer], :void
123
+ attach_function :glEnableVertexAttribArray, [:uint], :void
124
+ attach_function :glDisableVertexAttribArray, [:uint], :void
125
+
126
+ attach_function :glDrawArrays, [:uint, :int, :int], :void
127
+ attach_function :glDrawElements, [:uint, :int, :uint, :pointer], :void
128
+
129
+ attach_function :glGenTextures, [:int, :pointer], :void
130
+ attach_function :glBindTexture, [:uint, :uint], :void
131
+ attach_function :glTexParameteri, [:uint, :uint, :int], :void
132
+ attach_function :glActiveTexture, [:uint], :void
133
+ attach_function :glDeleteTextures, [:int, :pointer], :void
134
+ attach_function :glTexImage2D, [:uint, :int, :int, :int, :int, :int, :uint, :uint, :pointer], :void
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
5
+ module Sunrb
6
+ module FFI
7
+ module Platform
8
+ class << self
9
+ def os
10
+ @os ||= case RbConfig::CONFIG["host_os"]
11
+ when /darwin/i then :macos
12
+ when /linux/i then :linux
13
+ when /mswin|mingw|cygwin/i then :windows
14
+ else :unknown
15
+ end
16
+ end
17
+
18
+ def glfw_lib
19
+ case os
20
+ when :macos
21
+ find_macos_library("libglfw.3.dylib", "libglfw.dylib")
22
+ when :linux
23
+ "libglfw.so.3"
24
+ when :windows
25
+ "glfw3.dll"
26
+ else
27
+ "libglfw.so.3"
28
+ end
29
+ end
30
+
31
+ def opengl_lib
32
+ case os
33
+ when :macos
34
+ "/System/Library/Frameworks/OpenGL.framework/OpenGL"
35
+ when :linux
36
+ "libGL.so.1"
37
+ when :windows
38
+ "opengl32.dll"
39
+ else
40
+ "libGL.so.1"
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def find_macos_library(*names)
47
+ brew_paths = [
48
+ "/opt/homebrew/lib",
49
+ "/usr/local/lib",
50
+ "/opt/local/lib"
51
+ ]
52
+
53
+ names.each do |name|
54
+ brew_paths.each do |path|
55
+ full_path = File.join(path, name)
56
+ return full_path if File.exist?(full_path)
57
+ end
58
+ end
59
+
60
+ names.first
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sunrb
4
+ class BoxGeometry < BufferGeometry
5
+ attr_reader :parameters
6
+
7
+ def initialize(width: 1, height: 1, depth: 1, width_segments: 1, height_segments: 1, depth_segments: 1)
8
+ super()
9
+
10
+ @parameters = {
11
+ width: width,
12
+ height: height,
13
+ depth: depth,
14
+ width_segments: width_segments,
15
+ height_segments: height_segments,
16
+ depth_segments: depth_segments
17
+ }
18
+
19
+ build_geometry
20
+ end
21
+
22
+ private
23
+
24
+ def build_geometry
25
+ width = @parameters[:width]
26
+ height = @parameters[:height]
27
+ depth = @parameters[:depth]
28
+ width_segments = @parameters[:width_segments].to_i
29
+ height_segments = @parameters[:height_segments].to_i
30
+ depth_segments = @parameters[:depth_segments].to_i
31
+
32
+ indices = []
33
+ vertices = []
34
+ normals = []
35
+ uvs = []
36
+
37
+ number_of_vertices = 0
38
+
39
+ build_plane = lambda do |u, v, w, udir, vdir, width, height, depth, grid_x, grid_y|
40
+ segment_width = width / grid_x
41
+ segment_height = height / grid_y
42
+
43
+ width_half = width / 2.0
44
+ height_half = height / 2.0
45
+ depth_half = depth / 2.0
46
+
47
+ grid_x1 = grid_x + 1
48
+ grid_y1 = grid_y + 1
49
+
50
+ vertex = Vector3.new
51
+ (0...grid_y1).each do |iy|
52
+ y = iy * segment_height - height_half
53
+ (0...grid_x1).each do |ix|
54
+ x = ix * segment_width - width_half
55
+
56
+ vertex.send("#{u}=", x * udir)
57
+ vertex.send("#{v}=", y * vdir)
58
+ vertex.send("#{w}=", depth_half)
59
+
60
+ vertices.push(vertex.x, vertex.y, vertex.z)
61
+
62
+ vertex.send("#{u}=", 0)
63
+ vertex.send("#{v}=", 0)
64
+ vertex.send("#{w}=", depth > 0 ? 1 : -1)
65
+
66
+ normals.push(vertex.x, vertex.y, vertex.z)
67
+
68
+ uvs.push(ix.to_f / grid_x)
69
+ uvs.push(1 - (iy.to_f / grid_y))
70
+ end
71
+ end
72
+
73
+ (0...grid_y).each do |iy|
74
+ (0...grid_x).each do |ix|
75
+ a = number_of_vertices + ix + grid_x1 * iy
76
+ b = number_of_vertices + ix + grid_x1 * (iy + 1)
77
+ c = number_of_vertices + (ix + 1) + grid_x1 * (iy + 1)
78
+ d = number_of_vertices + (ix + 1) + grid_x1 * iy
79
+
80
+ indices.push(a, b, d)
81
+ indices.push(b, c, d)
82
+ end
83
+ end
84
+
85
+ number_of_vertices += grid_x1 * grid_y1
86
+ end
87
+
88
+ build_plane.call(:z, :y, :x, -1, -1, depth, height, width, depth_segments, height_segments)
89
+ build_plane.call(:z, :y, :x, 1, -1, depth, height, -width, depth_segments, height_segments)
90
+ build_plane.call(:x, :z, :y, 1, 1, width, depth, height, width_segments, depth_segments)
91
+ build_plane.call(:x, :z, :y, 1, -1, width, depth, -height, width_segments, depth_segments)
92
+ build_plane.call(:x, :y, :z, 1, -1, width, height, depth, width_segments, height_segments)
93
+ build_plane.call(:x, :y, :z, -1, -1, width, height, -depth, width_segments, height_segments)
94
+
95
+ set_index(indices)
96
+ set_attribute(:position, Float32BufferAttribute.new(vertices, 3))
97
+ set_attribute(:normal, Float32BufferAttribute.new(normals, 3))
98
+ set_attribute(:uv, Float32BufferAttribute.new(uvs, 2))
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,345 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sunrb
4
+ class BufferGeometry
5
+ attr_reader :uuid, :attributes, :index, :groups
6
+ attr_accessor :name, :bounding_box, :bounding_sphere
7
+ attr_accessor :draw_range
8
+
9
+ def initialize
10
+ @uuid = MathUtils.generate_uuid
11
+ @name = ""
12
+ @attributes = {}
13
+ @index = nil
14
+ @groups = []
15
+ @bounding_box = nil
16
+ @bounding_sphere = nil
17
+ @draw_range = { start: 0, count: Float::INFINITY }
18
+ end
19
+
20
+ def set_index(index)
21
+ @index = if index.is_a?(Array)
22
+ index.max > 65535 ? Uint32BufferAttribute.new(index, 1) : Uint16BufferAttribute.new(index, 1)
23
+ else
24
+ index
25
+ end
26
+ self
27
+ end
28
+
29
+ def get_index
30
+ @index
31
+ end
32
+
33
+ def set_attribute(name, attribute)
34
+ @attributes[name.to_sym] = attribute
35
+ self
36
+ end
37
+
38
+ def get_attribute(name)
39
+ @attributes[name.to_sym]
40
+ end
41
+
42
+ def delete_attribute(name)
43
+ @attributes.delete(name.to_sym)
44
+ self
45
+ end
46
+
47
+ def has_attribute?(name)
48
+ @attributes.key?(name.to_sym)
49
+ end
50
+
51
+ def add_group(start, count, material_index = 0)
52
+ @groups << { start: start, count: count, material_index: material_index }
53
+ end
54
+
55
+ def clear_groups
56
+ @groups.clear
57
+ end
58
+
59
+ def set_draw_range(start, count)
60
+ @draw_range = { start: start, count: count }
61
+ end
62
+
63
+ def apply_matrix4(matrix)
64
+ position = get_attribute(:position)
65
+ position&.apply_matrix4(matrix)
66
+
67
+ normal = get_attribute(:normal)
68
+ if normal
69
+ normal_matrix = Matrix3.new.set_from_matrix4(matrix).invert!.transpose!
70
+ normal.apply_normal_matrix(normal_matrix)
71
+ end
72
+
73
+ @bounding_box&.apply_matrix4(matrix)
74
+ @bounding_sphere&.apply_matrix4(matrix)
75
+
76
+ self
77
+ end
78
+
79
+ def apply_quaternion(q)
80
+ m = Matrix4.new.make_rotation_from_quaternion(q)
81
+ apply_matrix4(m)
82
+ end
83
+
84
+ def rotate_x(angle)
85
+ m = Matrix4.new.make_rotation_x(angle)
86
+ apply_matrix4(m)
87
+ end
88
+
89
+ def rotate_y(angle)
90
+ m = Matrix4.new.make_rotation_y(angle)
91
+ apply_matrix4(m)
92
+ end
93
+
94
+ def rotate_z(angle)
95
+ m = Matrix4.new.make_rotation_z(angle)
96
+ apply_matrix4(m)
97
+ end
98
+
99
+ def translate(x, y, z)
100
+ m = Matrix4.new.make_translation(x, y, z)
101
+ apply_matrix4(m)
102
+ end
103
+
104
+ def scale(x, y, z)
105
+ m = Matrix4.new.make_scale(x, y, z)
106
+ apply_matrix4(m)
107
+ end
108
+
109
+ def center
110
+ compute_bounding_box
111
+ center = @bounding_box.get_center
112
+ translate(-center.x, -center.y, -center.z)
113
+ self
114
+ end
115
+
116
+ def compute_bounding_box
117
+ position = get_attribute(:position)
118
+ return unless position
119
+
120
+ @bounding_box ||= BoundingBox.new
121
+
122
+ @bounding_box.set_from_buffer_attribute(position)
123
+ end
124
+
125
+ def compute_bounding_sphere
126
+ position = get_attribute(:position)
127
+ return unless position
128
+
129
+ compute_bounding_box unless @bounding_box
130
+
131
+ center = @bounding_box.get_center
132
+ max_radius_sq = 0.0
133
+
134
+ position.count.times do |i|
135
+ x = position.get_x(i) - center.x
136
+ y = position.get_y(i) - center.y
137
+ z = position.get_z(i) - center.z
138
+ radius_sq = x * x + y * y + z * z
139
+ max_radius_sq = radius_sq if radius_sq > max_radius_sq
140
+ end
141
+
142
+ @bounding_sphere = BoundingSphere.new(center, Math.sqrt(max_radius_sq))
143
+ end
144
+
145
+ def compute_vertex_normals
146
+ position = get_attribute(:position)
147
+ return unless position
148
+
149
+ normal = get_attribute(:normal)
150
+ if normal.nil?
151
+ normal = Float32BufferAttribute.new(Array.new(position.count * 3, 0.0), 3)
152
+ set_attribute(:normal, normal)
153
+ else
154
+ normal.count.times do |i|
155
+ normal.set_xyz(i, 0, 0, 0)
156
+ end
157
+ end
158
+
159
+ p_a = Vector3.new
160
+ p_b = Vector3.new
161
+ p_c = Vector3.new
162
+ cb = Vector3.new
163
+ ab = Vector3.new
164
+
165
+ if @index
166
+ @index.count.step(@index.count - 1, 3) do |i|
167
+ break if i + 2 >= @index.count
168
+
169
+ v_a = @index.get_x(i)
170
+ v_b = @index.get_x(i + 1)
171
+ v_c = @index.get_x(i + 2)
172
+
173
+ p_a.set(position.get_x(v_a), position.get_y(v_a), position.get_z(v_a))
174
+ p_b.set(position.get_x(v_b), position.get_y(v_b), position.get_z(v_b))
175
+ p_c.set(position.get_x(v_c), position.get_y(v_c), position.get_z(v_c))
176
+
177
+ cb.copy(p_c).sub!(p_b)
178
+ ab.copy(p_a).sub!(p_b)
179
+ cb.cross!(ab)
180
+
181
+ normal.set_xyz(v_a, normal.get_x(v_a) + cb.x, normal.get_y(v_a) + cb.y, normal.get_z(v_a) + cb.z)
182
+ normal.set_xyz(v_b, normal.get_x(v_b) + cb.x, normal.get_y(v_b) + cb.y, normal.get_z(v_b) + cb.z)
183
+ normal.set_xyz(v_c, normal.get_x(v_c) + cb.x, normal.get_y(v_c) + cb.y, normal.get_z(v_c) + cb.z)
184
+ end
185
+ else
186
+ (0...position.count).step(3) do |i|
187
+ break if i + 2 >= position.count
188
+
189
+ p_a.set(position.get_x(i), position.get_y(i), position.get_z(i))
190
+ p_b.set(position.get_x(i + 1), position.get_y(i + 1), position.get_z(i + 1))
191
+ p_c.set(position.get_x(i + 2), position.get_y(i + 2), position.get_z(i + 2))
192
+
193
+ cb.copy(p_c).sub!(p_b)
194
+ ab.copy(p_a).sub!(p_b)
195
+ cb.cross!(ab)
196
+
197
+ normal.set_xyz(i, cb.x, cb.y, cb.z)
198
+ normal.set_xyz(i + 1, cb.x, cb.y, cb.z)
199
+ normal.set_xyz(i + 2, cb.x, cb.y, cb.z)
200
+ end
201
+ end
202
+
203
+ normalize_normals
204
+ end
205
+
206
+ def normalize_normals
207
+ normal = get_attribute(:normal)
208
+ return unless normal
209
+
210
+ v = Vector3.new
211
+ normal.count.times do |i|
212
+ v.set(normal.get_x(i), normal.get_y(i), normal.get_z(i))
213
+ v.normalize!
214
+ normal.set_xyz(i, v.x, v.y, v.z)
215
+ end
216
+ end
217
+
218
+ def clone
219
+ new_geometry = self.class.new
220
+ new_geometry.name = @name
221
+
222
+ @attributes.each do |key, attr|
223
+ new_geometry.set_attribute(key, attr.clone)
224
+ end
225
+
226
+ new_geometry.set_index(@index.clone) if @index
227
+
228
+ @groups.each do |group|
229
+ new_geometry.add_group(group[:start], group[:count], group[:material_index])
230
+ end
231
+
232
+ new_geometry
233
+ end
234
+
235
+ def dispose
236
+ @attributes.clear
237
+ @index = nil
238
+ @groups.clear
239
+ end
240
+
241
+ def to_h
242
+ {
243
+ uuid: @uuid,
244
+ type: self.class.name,
245
+ name: @name,
246
+ attributes: @attributes.transform_values(&:to_a)
247
+ }
248
+ end
249
+ end
250
+
251
+ class BoundingBox
252
+ attr_accessor :min, :max
253
+
254
+ def initialize(min = nil, max = nil)
255
+ @min = min || Vector3.new(Float::INFINITY, Float::INFINITY, Float::INFINITY)
256
+ @max = max || Vector3.new(-Float::INFINITY, -Float::INFINITY, -Float::INFINITY)
257
+ end
258
+
259
+ def set_from_buffer_attribute(attribute)
260
+ @min.set(Float::INFINITY, Float::INFINITY, Float::INFINITY)
261
+ @max.set(-Float::INFINITY, -Float::INFINITY, -Float::INFINITY)
262
+
263
+ attribute.count.times do |i|
264
+ x = attribute.get_x(i)
265
+ y = attribute.get_y(i)
266
+ z = attribute.get_z(i)
267
+
268
+ @min.x = x if x < @min.x
269
+ @min.y = y if y < @min.y
270
+ @min.z = z if z < @min.z
271
+
272
+ @max.x = x if x > @max.x
273
+ @max.y = y if y > @max.y
274
+ @max.z = z if z > @max.z
275
+ end
276
+ self
277
+ end
278
+
279
+ def get_center(target = Vector3.new)
280
+ target.set(
281
+ (@min.x + @max.x) / 2,
282
+ (@min.y + @max.y) / 2,
283
+ (@min.z + @max.z) / 2
284
+ )
285
+ end
286
+
287
+ def get_size(target = Vector3.new)
288
+ target.set(
289
+ @max.x - @min.x,
290
+ @max.y - @min.y,
291
+ @max.z - @min.z
292
+ )
293
+ end
294
+
295
+ def apply_matrix4(matrix)
296
+ points = [
297
+ Vector3.new(@min.x, @min.y, @min.z),
298
+ Vector3.new(@min.x, @min.y, @max.z),
299
+ Vector3.new(@min.x, @max.y, @min.z),
300
+ Vector3.new(@min.x, @max.y, @max.z),
301
+ Vector3.new(@max.x, @min.y, @min.z),
302
+ Vector3.new(@max.x, @min.y, @max.z),
303
+ Vector3.new(@max.x, @max.y, @min.z),
304
+ Vector3.new(@max.x, @max.y, @max.z)
305
+ ]
306
+
307
+ @min.set(Float::INFINITY, Float::INFINITY, Float::INFINITY)
308
+ @max.set(-Float::INFINITY, -Float::INFINITY, -Float::INFINITY)
309
+
310
+ points.each do |p|
311
+ p.apply_matrix4(matrix)
312
+ @min.x = p.x if p.x < @min.x
313
+ @min.y = p.y if p.y < @min.y
314
+ @min.z = p.z if p.z < @min.z
315
+ @max.x = p.x if p.x > @max.x
316
+ @max.y = p.y if p.y > @max.y
317
+ @max.z = p.z if p.z > @max.z
318
+ end
319
+ self
320
+ end
321
+
322
+ def clone
323
+ BoundingBox.new(@min.clone, @max.clone)
324
+ end
325
+ end
326
+
327
+ class BoundingSphere
328
+ attr_accessor :center, :radius
329
+
330
+ def initialize(center = nil, radius = -1)
331
+ @center = center || Vector3.new
332
+ @radius = radius
333
+ end
334
+
335
+ def apply_matrix4(matrix)
336
+ @center.apply_matrix4(matrix)
337
+ @radius *= matrix.get_max_scale_on_axis
338
+ self
339
+ end
340
+
341
+ def clone
342
+ BoundingSphere.new(@center.clone, @radius)
343
+ end
344
+ end
345
+ end