arcanoae 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/CHANGELOG.md +12 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +128 -0
  5. data/arcanoae.gemspec +31 -0
  6. data/examples/01_hello_world.rb +47 -0
  7. data/examples/02_basic_shapes.rb +58 -0
  8. data/examples/03_materials.rb +69 -0
  9. data/examples/04_textures.rb +48 -0
  10. data/examples/05_lighting.rb +66 -0
  11. data/examples/06_camera_controls.rb +49 -0
  12. data/examples/07_animation.rb +83 -0
  13. data/examples/08_particles.rb +86 -0
  14. data/examples/09_physics.rb +66 -0
  15. data/examples/10_post_processing.rb +60 -0
  16. data/examples/11_gui.rb +85 -0
  17. data/examples/12_load_model.rb +73 -0
  18. data/examples/13_pbr_demo.rb +98 -0
  19. data/examples/14_complete_game.rb +159 -0
  20. data/examples/15_spectral_cornell_box.rb +350 -0
  21. data/examples/16_ward_brdf_teapot.rb +321 -0
  22. data/examples/17_terrain_flight.rb +263 -0
  23. data/examples/18_cellular_automata.rb +417 -0
  24. data/lib/arcanoae/animation/animatable.rb +108 -0
  25. data/lib/arcanoae/animation/animation.rb +110 -0
  26. data/lib/arcanoae/animation/animation_group.rb +159 -0
  27. data/lib/arcanoae/animation/easing_functions.rb +214 -0
  28. data/lib/arcanoae/audio/audio_engine.rb +85 -0
  29. data/lib/arcanoae/audio/sound.rb +135 -0
  30. data/lib/arcanoae/audio/sound_track.rb +53 -0
  31. data/lib/arcanoae/backends/glfw_bindings.rb +99 -0
  32. data/lib/arcanoae/backends/opengl_bindings.rb +240 -0
  33. data/lib/arcanoae/cameras/arc_rotate_camera.rb +134 -0
  34. data/lib/arcanoae/cameras/camera.rb +52 -0
  35. data/lib/arcanoae/cameras/perspective_camera.rb +35 -0
  36. data/lib/arcanoae/core/disposable.rb +25 -0
  37. data/lib/arcanoae/core/engine.rb +163 -0
  38. data/lib/arcanoae/core/logger.rb +51 -0
  39. data/lib/arcanoae/dsl/scene_builder.rb +238 -0
  40. data/lib/arcanoae/gui/container.rb +50 -0
  41. data/lib/arcanoae/gui/control.rb +91 -0
  42. data/lib/arcanoae/gui/controls.rb +195 -0
  43. data/lib/arcanoae/input/input_manager.rb +172 -0
  44. data/lib/arcanoae/lights/directional_light.rb +24 -0
  45. data/lib/arcanoae/lights/hemispheric_light.rb +26 -0
  46. data/lib/arcanoae/lights/light.rb +30 -0
  47. data/lib/arcanoae/lights/point_light.rb +23 -0
  48. data/lib/arcanoae/lights/shadow_generator.rb +151 -0
  49. data/lib/arcanoae/loaders/gltf_loader.rb +223 -0
  50. data/lib/arcanoae/loaders/obj_loader.rb +179 -0
  51. data/lib/arcanoae/materials/material.rb +67 -0
  52. data/lib/arcanoae/materials/pbr_material.rb +75 -0
  53. data/lib/arcanoae/materials/standard_material.rb +38 -0
  54. data/lib/arcanoae/math/bounding_box.rb +98 -0
  55. data/lib/arcanoae/math/bounding_sphere.rb +52 -0
  56. data/lib/arcanoae/math/color3.rb +183 -0
  57. data/lib/arcanoae/math/color4.rb +143 -0
  58. data/lib/arcanoae/math/frustum.rb +101 -0
  59. data/lib/arcanoae/math/matrix4.rb +357 -0
  60. data/lib/arcanoae/math/plane.rb +51 -0
  61. data/lib/arcanoae/math/quaternion.rb +247 -0
  62. data/lib/arcanoae/math/ray.rb +78 -0
  63. data/lib/arcanoae/math/vector2.rb +159 -0
  64. data/lib/arcanoae/math/vector3.rb +173 -0
  65. data/lib/arcanoae/math/vector4.rb +132 -0
  66. data/lib/arcanoae/math.rb +45 -0
  67. data/lib/arcanoae/meshes/mesh.rb +101 -0
  68. data/lib/arcanoae/meshes/mesh_builder.rb +286 -0
  69. data/lib/arcanoae/meshes/vertex_data.rb +166 -0
  70. data/lib/arcanoae/physics/physics_engine.rb +145 -0
  71. data/lib/arcanoae/physics/physics_impostor.rb +84 -0
  72. data/lib/arcanoae/post_process/effects/fxaa_post_process.rb +112 -0
  73. data/lib/arcanoae/post_process/post_process.rb +68 -0
  74. data/lib/arcanoae/post_process/post_process_manager.rb +62 -0
  75. data/lib/arcanoae/rendering/gpu_buffer.rb +70 -0
  76. data/lib/arcanoae/rendering/mesh_renderer.rb +144 -0
  77. data/lib/arcanoae/rendering/render_pipeline.rb +98 -0
  78. data/lib/arcanoae/rendering/shader_program.rb +114 -0
  79. data/lib/arcanoae/rendering/shader_store.rb +226 -0
  80. data/lib/arcanoae/scene/scene.rb +131 -0
  81. data/lib/arcanoae/scene/transform_node.rb +185 -0
  82. data/lib/arcanoae/textures/base_texture.rb +48 -0
  83. data/lib/arcanoae/textures/render_target_texture.rb +73 -0
  84. data/lib/arcanoae/textures/texture.rb +90 -0
  85. data/lib/arcanoae/version.rb +5 -0
  86. data/lib/arcanoae.rb +89 -0
  87. data/shaders/blur.frag +29 -0
  88. data/shaders/default.frag +30 -0
  89. data/shaders/default.vert +23 -0
  90. data/shaders/fxaa.frag +55 -0
  91. data/shaders/particles.frag +25 -0
  92. data/shaders/particles.vert +33 -0
  93. data/shaders/pbr.frag +176 -0
  94. data/shaders/pbr.vert +31 -0
  95. data/shaders/post_process.vert +11 -0
  96. data/shaders/shadow_map.frag +4 -0
  97. data/shaders/shadow_map.vert +10 -0
  98. data/shaders/standard.frag +99 -0
  99. data/shaders/standard.vert +29 -0
  100. metadata +184 -0
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcanoae
4
+ module Audio
5
+ class Sound
6
+ include Arcanoae::Disposable
7
+
8
+ attr_accessor :name, :loop, :volume, :playback_rate
9
+ attr_accessor :spatial_sound
10
+ attr_reader :url, :scene, :attached_mesh
11
+
12
+ def initialize(name, url, scene, options = {})
13
+ @name = name
14
+ @url = url
15
+ @scene = scene
16
+
17
+ @loop = options.fetch(:loop, false)
18
+ @volume = options.fetch(:volume, 1.0)
19
+ @playback_rate = options.fetch(:playback_rate, 1.0)
20
+ @spatial_sound = options.fetch(:spatial_sound, false)
21
+ @autoplay = options.fetch(:autoplay, false)
22
+
23
+ @is_playing = false
24
+ @is_paused = false
25
+ @is_ready = false
26
+ @attached_mesh = nil
27
+ @position = Math::Vector3.zero
28
+
29
+ @on_ready_callbacks = []
30
+ @on_ended_callbacks = []
31
+
32
+ load_sound(url) if url
33
+
34
+ scene.audio_engine&.add_sound(self)
35
+ end
36
+
37
+ def play(time = 0, offset = 0)
38
+ return unless @is_ready
39
+
40
+ @is_playing = true
41
+ @is_paused = false
42
+ end
43
+
44
+ def pause
45
+ return unless @is_playing
46
+
47
+ @is_paused = true
48
+ @is_playing = false
49
+ end
50
+
51
+ def stop
52
+ @is_playing = false
53
+ @is_paused = false
54
+ end
55
+
56
+ def resume
57
+ return unless @is_paused
58
+
59
+ @is_paused = false
60
+ @is_playing = true
61
+ end
62
+
63
+ def playing?
64
+ @is_playing && !@is_paused
65
+ end
66
+
67
+ def paused?
68
+ @is_paused
69
+ end
70
+
71
+ def ready?
72
+ @is_ready
73
+ end
74
+
75
+ def set_position(position)
76
+ @position = position
77
+ end
78
+
79
+ def get_position
80
+ @position
81
+ end
82
+
83
+ def attach_to_mesh(mesh)
84
+ @attached_mesh = mesh
85
+ @spatial_sound = true
86
+ end
87
+
88
+ def detach_from_mesh
89
+ @attached_mesh = nil
90
+ end
91
+
92
+ def update(delta_time)
93
+ if @attached_mesh && @spatial_sound
94
+ @position = @attached_mesh.absolute_position
95
+ end
96
+ end
97
+
98
+ def update_volume
99
+ end
100
+
101
+ def on_ready(&block)
102
+ @on_ready_callbacks << block
103
+ block.call if @is_ready
104
+ end
105
+
106
+ def on_ended(&block)
107
+ @on_ended_callbacks << block
108
+ end
109
+
110
+ def dispose
111
+ return if disposed?
112
+
113
+ stop
114
+ @scene.audio_engine&.remove_sound(self)
115
+ @on_ready_callbacks.clear
116
+ @on_ended_callbacks.clear
117
+
118
+ super
119
+ end
120
+
121
+ private
122
+
123
+ def load_sound(url)
124
+ return unless File.exist?(url)
125
+
126
+ @is_ready = true
127
+ @on_ready_callbacks.each(&:call)
128
+
129
+ play if @autoplay
130
+
131
+ Arcanoae::Logger.debug("Sound loaded: #{url}")
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcanoae
4
+ module Audio
5
+ class SoundTrack
6
+ include Arcanoae::Disposable
7
+
8
+ attr_accessor :name, :volume
9
+ attr_reader :sounds
10
+
11
+ def initialize(name, scene, options = {})
12
+ @name = name
13
+ @scene = scene
14
+ @volume = options.fetch(:volume, 1.0)
15
+ @sounds = []
16
+ end
17
+
18
+ def add_sound(sound)
19
+ @sounds << sound unless @sounds.include?(sound)
20
+ end
21
+
22
+ def remove_sound(sound)
23
+ @sounds.delete(sound)
24
+ end
25
+
26
+ def play
27
+ @sounds.each(&:play)
28
+ end
29
+
30
+ def pause
31
+ @sounds.each(&:pause)
32
+ end
33
+
34
+ def stop
35
+ @sounds.each(&:stop)
36
+ end
37
+
38
+ def set_volume(volume)
39
+ @volume = Arcanoae::Math.clamp(volume, 0.0, 1.0)
40
+ @sounds.each(&:update_volume)
41
+ end
42
+
43
+ def dispose
44
+ return if disposed?
45
+
46
+ @sounds.each(&:dispose)
47
+ @sounds.clear
48
+
49
+ super
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module Arcanoae
6
+ module Backends
7
+ module GLFW
8
+ extend FFI::Library
9
+
10
+ case RbConfig::CONFIG['host_os']
11
+ when /darwin/
12
+ ffi_lib '/opt/homebrew/lib/libglfw.dylib'
13
+ when /linux/
14
+ ffi_lib 'libglfw.so.3'
15
+ when /mingw|mswin/
16
+ ffi_lib 'glfw3.dll'
17
+ else
18
+ ffi_lib 'glfw'
19
+ end
20
+
21
+ TRUE = 1
22
+ FALSE = 0
23
+
24
+ RELEASE = 0
25
+ PRESS = 1
26
+ REPEAT = 2
27
+
28
+ CONTEXT_VERSION_MAJOR = 0x00022002
29
+ CONTEXT_VERSION_MINOR = 0x00022003
30
+ OPENGL_PROFILE = 0x00022008
31
+ OPENGL_FORWARD_COMPAT = 0x00022006
32
+ OPENGL_CORE_PROFILE = 0x00032001
33
+ SAMPLES = 0x0002100D
34
+
35
+ KEY_ESCAPE = 256
36
+ KEY_SPACE = 32
37
+ KEY_0 = 48
38
+ KEY_1 = 49
39
+ KEY_2 = 50
40
+ KEY_3 = 51
41
+ KEY_4 = 52
42
+ KEY_5 = 53
43
+ KEY_6 = 54
44
+ KEY_7 = 55
45
+ KEY_8 = 56
46
+ KEY_9 = 57
47
+ KEY_W = 87
48
+ KEY_A = 65
49
+ KEY_S = 83
50
+ KEY_D = 68
51
+ KEY_UP = 265
52
+ KEY_DOWN = 264
53
+ KEY_LEFT = 263
54
+ KEY_RIGHT = 262
55
+
56
+ attach_function :glfwInit, [], :int
57
+ attach_function :glfwTerminate, [], :void
58
+ attach_function :glfwWindowHint, [:int, :int], :void
59
+ attach_function :glfwCreateWindow, [:int, :int, :string, :pointer, :pointer], :pointer
60
+ attach_function :glfwDestroyWindow, [:pointer], :void
61
+ attach_function :glfwMakeContextCurrent, [:pointer], :void
62
+ attach_function :glfwSwapBuffers, [:pointer], :void
63
+ attach_function :glfwSwapInterval, [:int], :void
64
+ attach_function :glfwPollEvents, [], :void
65
+ attach_function :glfwWindowShouldClose, [:pointer], :int
66
+ attach_function :glfwSetWindowShouldClose, [:pointer, :int], :void
67
+ attach_function :glfwGetTime, [], :double
68
+ attach_function :glfwGetFramebufferSize, [:pointer, :pointer, :pointer], :void
69
+ attach_function :glfwGetKey, [:pointer, :int], :int
70
+ attach_function :glfwGetMouseButton, [:pointer, :int], :int
71
+ attach_function :glfwGetCursorPos, [:pointer, :pointer, :pointer], :void
72
+ attach_function :glfwSetInputMode, [:pointer, :int, :int], :void
73
+
74
+ callback :key_callback, [:pointer, :int, :int, :int, :int], :void
75
+ callback :cursor_pos_callback, [:pointer, :double, :double], :void
76
+ callback :mouse_button_callback, [:pointer, :int, :int, :int], :void
77
+ callback :framebuffer_size_callback, [:pointer, :int, :int], :void
78
+
79
+ attach_function :glfwSetKeyCallback, [:pointer, :key_callback], :pointer
80
+ attach_function :glfwSetCursorPosCallback, [:pointer, :cursor_pos_callback], :pointer
81
+ attach_function :glfwSetMouseButtonCallback, [:pointer, :mouse_button_callback], :pointer
82
+ attach_function :glfwSetFramebufferSizeCallback, [:pointer, :framebuffer_size_callback], :pointer
83
+
84
+ def self.get_framebuffer_size(window)
85
+ width_ptr = FFI::MemoryPointer.new(:int)
86
+ height_ptr = FFI::MemoryPointer.new(:int)
87
+ glfwGetFramebufferSize(window, width_ptr, height_ptr)
88
+ [width_ptr.read_int, height_ptr.read_int]
89
+ end
90
+
91
+ def self.get_cursor_pos(window)
92
+ x_ptr = FFI::MemoryPointer.new(:double)
93
+ y_ptr = FFI::MemoryPointer.new(:double)
94
+ glfwGetCursorPos(window, x_ptr, y_ptr)
95
+ [x_ptr.read_double, y_ptr.read_double]
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module Arcanoae
6
+ module Backends
7
+ module GL
8
+ extend FFI::Library
9
+
10
+ case RbConfig::CONFIG['host_os']
11
+ when /darwin/
12
+ ffi_lib '/System/Library/Frameworks/OpenGL.framework/OpenGL'
13
+ when /linux/
14
+ ffi_lib 'libGL.so.1'
15
+ when /mingw|mswin/
16
+ ffi_lib 'opengl32.dll'
17
+ else
18
+ ffi_lib 'GL'
19
+ end
20
+
21
+ GL_FALSE = 0
22
+ GL_TRUE = 1
23
+
24
+ GL_DEPTH_BUFFER_BIT = 0x00000100
25
+ GL_COLOR_BUFFER_BIT = 0x00004000
26
+
27
+ GL_POINTS = 0x0000
28
+ GL_LINES = 0x0001
29
+ GL_TRIANGLES = 0x0004
30
+
31
+ GL_DEPTH_TEST = 0x0B71
32
+ GL_CULL_FACE = 0x0B44
33
+ GL_BLEND = 0x0BE2
34
+ GL_MULTISAMPLE = 0x809D
35
+
36
+ GL_FRONT = 0x0404
37
+ GL_BACK = 0x0405
38
+
39
+ GL_SRC_ALPHA = 0x0302
40
+ GL_ONE_MINUS_SRC_ALPHA = 0x0303
41
+
42
+ GL_FLOAT = 0x1406
43
+ GL_UNSIGNED_INT = 0x1405
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_FRAGMENT_SHADER = 0x8B30
51
+ GL_VERTEX_SHADER = 0x8B31
52
+ GL_COMPILE_STATUS = 0x8B81
53
+ GL_LINK_STATUS = 0x8B82
54
+ GL_INFO_LOG_LENGTH = 0x8B84
55
+
56
+ attach_function :glEnable, [:uint], :void
57
+ attach_function :glDisable, [:uint], :void
58
+ attach_function :glClear, [:uint], :void
59
+ attach_function :glClearColor, [:float, :float, :float, :float], :void
60
+ attach_function :glViewport, [:int, :int, :int, :int], :void
61
+ attach_function :glCullFace, [:uint], :void
62
+ attach_function :glBlendFunc, [:uint, :uint], :void
63
+
64
+ attach_function :glGenBuffers, [:int, :pointer], :void
65
+ attach_function :glDeleteBuffers, [:int, :pointer], :void
66
+ attach_function :glBindBuffer, [:uint, :uint], :void
67
+ attach_function :glBufferData, [:uint, :long, :pointer, :uint], :void
68
+
69
+ attach_function :glGenVertexArrays, [:int, :pointer], :void
70
+ attach_function :glDeleteVertexArrays, [:int, :pointer], :void
71
+ attach_function :glBindVertexArray, [:uint], :void
72
+ attach_function :glEnableVertexAttribArray, [:uint], :void
73
+ attach_function :glVertexAttribPointer, [:uint, :int, :uint, :uchar, :int, :pointer], :void
74
+
75
+ attach_function :glCreateShader, [:uint], :uint
76
+ attach_function :glDeleteShader, [:uint], :void
77
+ attach_function :glShaderSource, [:uint, :int, :pointer, :pointer], :void
78
+ attach_function :glCompileShader, [:uint], :void
79
+ attach_function :glGetShaderiv, [:uint, :uint, :pointer], :void
80
+ attach_function :glGetShaderInfoLog, [:uint, :int, :pointer, :pointer], :void
81
+
82
+ attach_function :glCreateProgram, [], :uint
83
+ attach_function :glDeleteProgram, [:uint], :void
84
+ attach_function :glAttachShader, [:uint, :uint], :void
85
+ attach_function :glLinkProgram, [:uint], :void
86
+ attach_function :glUseProgram, [:uint], :void
87
+ attach_function :glGetProgramiv, [:uint, :uint, :pointer], :void
88
+ attach_function :glGetProgramInfoLog, [:uint, :int, :pointer, :pointer], :void
89
+
90
+ attach_function :glGetUniformLocation, [:uint, :string], :int
91
+ attach_function :glGetAttribLocation, [:uint, :string], :int
92
+ attach_function :glUniform1i, [:int, :int], :void
93
+ attach_function :glUniform1f, [:int, :float], :void
94
+ attach_function :glUniform2f, [:int, :float, :float], :void
95
+ attach_function :glUniform3f, [:int, :float, :float, :float], :void
96
+ attach_function :glUniform4f, [:int, :float, :float, :float, :float], :void
97
+ attach_function :glUniformMatrix4fv, [:int, :int, :uchar, :pointer], :void
98
+
99
+ attach_function :glDrawArrays, [:uint, :int, :int], :void
100
+ attach_function :glDrawElements, [:uint, :int, :uint, :pointer], :void
101
+ attach_function :glGetError, [], :uint
102
+
103
+ GL_NO_ERROR = 0
104
+ GL_INVALID_ENUM = 0x0500
105
+ GL_INVALID_VALUE = 0x0501
106
+ GL_INVALID_OPERATION = 0x0502
107
+ GL_OUT_OF_MEMORY = 0x0505
108
+
109
+ GL_TEXTURE_2D = 0x0DE1
110
+ GL_TEXTURE0 = 0x84C0
111
+ GL_TEXTURE_MIN_FILTER = 0x2801
112
+ GL_TEXTURE_MAG_FILTER = 0x2800
113
+ GL_TEXTURE_WRAP_S = 0x2802
114
+ GL_TEXTURE_WRAP_T = 0x2803
115
+ GL_NEAREST = 0x2600
116
+ GL_LINEAR = 0x2601
117
+ GL_REPEAT = 0x2901
118
+ GL_CLAMP_TO_EDGE = 0x812F
119
+
120
+ GL_RGBA = 0x1908
121
+ GL_RGBA32F = 0x8814
122
+
123
+ GL_FRAMEBUFFER = 0x8D40
124
+ GL_COLOR_ATTACHMENT0 = 0x8CE0
125
+ GL_FRAMEBUFFER_COMPLETE = 0x8CD5
126
+
127
+ attach_function :glGenTextures, [:int, :pointer], :void
128
+ attach_function :glDeleteTextures, [:int, :pointer], :void
129
+ attach_function :glBindTexture, [:uint, :uint], :void
130
+ attach_function :glTexImage2D, [:uint, :int, :int, :int, :int, :int, :uint, :uint, :pointer], :void
131
+ attach_function :glTexParameteri, [:uint, :uint, :int], :void
132
+ attach_function :glActiveTexture, [:uint], :void
133
+
134
+ attach_function :glGenFramebuffers, [:int, :pointer], :void
135
+ attach_function :glDeleteFramebuffers, [:int, :pointer], :void
136
+ attach_function :glBindFramebuffer, [:uint, :uint], :void
137
+ attach_function :glFramebufferTexture2D, [:uint, :uint, :uint, :uint, :int], :void
138
+ attach_function :glCheckFramebufferStatus, [:uint], :uint
139
+
140
+ def self.check_error(context = "")
141
+ error = glGetError
142
+ return if error == GL_NO_ERROR
143
+
144
+ error_name = case error
145
+ when GL_INVALID_ENUM then "GL_INVALID_ENUM"
146
+ when GL_INVALID_VALUE then "GL_INVALID_VALUE"
147
+ when GL_INVALID_OPERATION then "GL_INVALID_OPERATION"
148
+ when GL_OUT_OF_MEMORY then "GL_OUT_OF_MEMORY"
149
+ else "Unknown error #{error}"
150
+ end
151
+ puts "OpenGL Error [#{context}]: #{error_name}"
152
+ end
153
+
154
+ def self.gen_buffer
155
+ buf = FFI::MemoryPointer.new(:uint)
156
+ glGenBuffers(1, buf)
157
+ buf.read_uint
158
+ end
159
+
160
+ def self.gen_vertex_array
161
+ buf = FFI::MemoryPointer.new(:uint)
162
+ glGenVertexArrays(1, buf)
163
+ buf.read_uint
164
+ end
165
+
166
+ def self.delete_buffer(id)
167
+ buf = FFI::MemoryPointer.new(:uint)
168
+ buf.write_uint(id)
169
+ glDeleteBuffers(1, buf)
170
+ end
171
+
172
+ def self.delete_vertex_array(id)
173
+ buf = FFI::MemoryPointer.new(:uint)
174
+ buf.write_uint(id)
175
+ glDeleteVertexArrays(1, buf)
176
+ end
177
+
178
+ def self.get_shader_iv(shader, pname)
179
+ buf = FFI::MemoryPointer.new(:int)
180
+ glGetShaderiv(shader, pname, buf)
181
+ buf.read_int
182
+ end
183
+
184
+ def self.get_program_iv(program, pname)
185
+ buf = FFI::MemoryPointer.new(:int)
186
+ glGetProgramiv(program, pname, buf)
187
+ buf.read_int
188
+ end
189
+
190
+ def self.get_shader_info_log(shader)
191
+ len = get_shader_iv(shader, GL_INFO_LOG_LENGTH)
192
+ return "" if len <= 0
193
+
194
+ buf = FFI::MemoryPointer.new(:char, len)
195
+ glGetShaderInfoLog(shader, len, nil, buf)
196
+ buf.read_string
197
+ end
198
+
199
+ def self.get_program_info_log(program)
200
+ len = get_program_iv(program, GL_INFO_LOG_LENGTH)
201
+ return "" if len <= 0
202
+
203
+ buf = FFI::MemoryPointer.new(:char, len)
204
+ glGetProgramInfoLog(program, len, nil, buf)
205
+ buf.read_string
206
+ end
207
+
208
+ def self.shader_source(shader, source)
209
+ src_ptr = FFI::MemoryPointer.from_string(source)
210
+ src_array = FFI::MemoryPointer.new(:pointer)
211
+ src_array.write_pointer(src_ptr)
212
+ glShaderSource(shader, 1, src_array, nil)
213
+ end
214
+
215
+ def self.buffer_data(target, data, usage)
216
+ if data.is_a?(Array)
217
+ if data.first.is_a?(Integer)
218
+ packed = data.pack('L*')
219
+ else
220
+ packed = data.pack('f*')
221
+ end
222
+ else
223
+ packed = data
224
+ end
225
+
226
+ ptr = FFI::MemoryPointer.new(:char, packed.bytesize)
227
+ ptr.put_bytes(0, packed)
228
+ glBufferData(target, packed.bytesize, ptr, usage)
229
+ packed.bytesize
230
+ end
231
+
232
+ def self.uniform_matrix4fv(location, matrix)
233
+ data = matrix.is_a?(Array) ? matrix : matrix.to_a
234
+ ptr = FFI::MemoryPointer.new(:float, 16)
235
+ ptr.put_array_of_float(0, data)
236
+ glUniformMatrix4fv(location, 1, GL_FALSE, ptr)
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcanoae
4
+ module Cameras
5
+ class ArcRotateCamera < Camera
6
+ attr_accessor :alpha, :beta, :radius, :target
7
+ attr_accessor :lower_alpha_limit, :upper_alpha_limit
8
+ attr_accessor :lower_beta_limit, :upper_beta_limit
9
+ attr_accessor :lower_radius_limit, :upper_radius_limit
10
+ attr_accessor :panning_inertia, :wheel_precision
11
+
12
+ def initialize(name, alpha, beta, radius, target, scene = nil)
13
+ super(name, compute_position(alpha, beta, radius, target), scene)
14
+ @alpha = alpha
15
+ @beta = beta
16
+ @radius = radius
17
+ @target = target.clone
18
+
19
+ @lower_alpha_limit = nil
20
+ @upper_alpha_limit = nil
21
+ @lower_beta_limit = 0.01
22
+ @upper_beta_limit = ::Math::PI - 0.01
23
+ @lower_radius_limit = 0.01
24
+ @upper_radius_limit = nil
25
+ @panning_inertia = 0.9
26
+ @wheel_precision = 3.0
27
+ @fov = ::Math::PI / 4
28
+ @aspect_ratio = 16.0 / 9.0
29
+
30
+ rebuild_position
31
+ end
32
+
33
+ attr_accessor :fov, :aspect_ratio
34
+
35
+ def set_position(position)
36
+ @position = position.clone
37
+ rebuild_angles_and_radius
38
+ self
39
+ end
40
+
41
+ def set_target(target)
42
+ @target = target.clone
43
+ rebuild_position
44
+ self
45
+ end
46
+
47
+ def zoom_on(meshes)
48
+ return self if meshes.empty?
49
+
50
+ min = Math::Vector3.new(Float::INFINITY, Float::INFINITY, Float::INFINITY)
51
+ max = Math::Vector3.new(-Float::INFINITY, -Float::INFINITY, -Float::INFINITY)
52
+
53
+ meshes.each do |mesh|
54
+ info = mesh.bounding_info
55
+ next unless info
56
+
57
+ box = info[:bounding_box]
58
+ min = Math::Vector3.new([min.x, box.min.x].min, [min.y, box.min.y].min, [min.z, box.min.z].min)
59
+ max = Math::Vector3.new([max.x, box.max.x].max, [max.y, box.max.y].max, [max.z, box.max.z].max)
60
+ end
61
+
62
+ center = Math::Vector3.new(
63
+ (min.x + max.x) / 2,
64
+ (min.y + max.y) / 2,
65
+ (min.z + max.z) / 2
66
+ )
67
+
68
+ @target = center
69
+ distance = max.distance_to(min)
70
+ @radius = distance / (2 * ::Math.tan(@fov / 2))
71
+ rebuild_position
72
+ self
73
+ end
74
+
75
+ def view_matrix
76
+ Math::Matrix4.look_at(@position, @target, Math::Vector3.up)
77
+ end
78
+
79
+ def projection_matrix
80
+ aspect = compute_aspect_ratio
81
+ Math::Matrix4.perspective(@fov, aspect, @min_z, @max_z)
82
+ end
83
+
84
+ def compute_aspect_ratio
85
+ if @scene&.engine
86
+ @scene.engine.render_width.to_f / @scene.engine.render_height
87
+ else
88
+ @aspect_ratio
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def rebuild_angles_and_radius
95
+ diff = @position - @target
96
+ @radius = diff.length
97
+ return if @radius.zero?
98
+
99
+ @alpha = ::Math.atan2(diff.x, diff.z)
100
+ @beta = ::Math.acos(diff.y / @radius)
101
+ end
102
+
103
+ def rebuild_position
104
+ @beta = clamp_beta(@beta)
105
+ @alpha = clamp_alpha(@alpha)
106
+ @radius = clamp_radius(@radius)
107
+ @position = compute_position(@alpha, @beta, @radius, @target)
108
+ end
109
+
110
+ def compute_position(alpha, beta, radius, target)
111
+ x = target.x + radius * ::Math.sin(beta) * ::Math.sin(alpha)
112
+ y = target.y + radius * ::Math.cos(beta)
113
+ z = target.z + radius * ::Math.sin(beta) * ::Math.cos(alpha)
114
+ Math::Vector3.new(x, y, z)
115
+ end
116
+
117
+ def clamp_alpha(value)
118
+ return value unless @lower_alpha_limit && @upper_alpha_limit
119
+
120
+ [[value, @lower_alpha_limit].max, @upper_alpha_limit].min
121
+ end
122
+
123
+ def clamp_beta(value)
124
+ [[value, @lower_beta_limit].max, @upper_beta_limit].min
125
+ end
126
+
127
+ def clamp_radius(value)
128
+ min = @lower_radius_limit || 0.01
129
+ max = @upper_radius_limit || Float::INFINITY
130
+ [[value, min].max, max].min
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arcanoae
4
+ module Cameras
5
+ class Camera < Scene::TransformNode
6
+ attr_accessor :min_z, :max_z, :viewport, :inertia
7
+
8
+ def initialize(name, position, scene = nil)
9
+ super(name, scene)
10
+ @position = position.clone
11
+ @min_z = 0.1
12
+ @max_z = 10000.0
13
+ @viewport = { x: 0, y: 0, width: 1, height: 1 }
14
+ @inertia = 0.9
15
+ @attached = false
16
+
17
+ scene&.add_camera(self)
18
+ end
19
+
20
+ def attach_control(canvas = nil, no_prevent_default = false)
21
+ @attached = true
22
+ end
23
+
24
+ def detach_control
25
+ @attached = false
26
+ end
27
+
28
+ def attached?
29
+ @attached
30
+ end
31
+
32
+ def view_matrix
33
+ raise NotImplementedError, "Subclasses must implement view_matrix"
34
+ end
35
+
36
+ def projection_matrix
37
+ raise NotImplementedError, "Subclasses must implement projection_matrix"
38
+ end
39
+
40
+ def get_transform_matrix
41
+ view_matrix * projection_matrix
42
+ end
43
+
44
+ def dispose
45
+ return if disposed?
46
+
47
+ @scene&.remove_camera(self)
48
+ super
49
+ end
50
+ end
51
+ end
52
+ end