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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +128 -0
- data/arcanoae.gemspec +31 -0
- data/examples/01_hello_world.rb +47 -0
- data/examples/02_basic_shapes.rb +58 -0
- data/examples/03_materials.rb +69 -0
- data/examples/04_textures.rb +48 -0
- data/examples/05_lighting.rb +66 -0
- data/examples/06_camera_controls.rb +49 -0
- data/examples/07_animation.rb +83 -0
- data/examples/08_particles.rb +86 -0
- data/examples/09_physics.rb +66 -0
- data/examples/10_post_processing.rb +60 -0
- data/examples/11_gui.rb +85 -0
- data/examples/12_load_model.rb +73 -0
- data/examples/13_pbr_demo.rb +98 -0
- data/examples/14_complete_game.rb +159 -0
- data/examples/15_spectral_cornell_box.rb +350 -0
- data/examples/16_ward_brdf_teapot.rb +321 -0
- data/examples/17_terrain_flight.rb +263 -0
- data/examples/18_cellular_automata.rb +417 -0
- data/lib/arcanoae/animation/animatable.rb +108 -0
- data/lib/arcanoae/animation/animation.rb +110 -0
- data/lib/arcanoae/animation/animation_group.rb +159 -0
- data/lib/arcanoae/animation/easing_functions.rb +214 -0
- data/lib/arcanoae/audio/audio_engine.rb +85 -0
- data/lib/arcanoae/audio/sound.rb +135 -0
- data/lib/arcanoae/audio/sound_track.rb +53 -0
- data/lib/arcanoae/backends/glfw_bindings.rb +99 -0
- data/lib/arcanoae/backends/opengl_bindings.rb +240 -0
- data/lib/arcanoae/cameras/arc_rotate_camera.rb +134 -0
- data/lib/arcanoae/cameras/camera.rb +52 -0
- data/lib/arcanoae/cameras/perspective_camera.rb +35 -0
- data/lib/arcanoae/core/disposable.rb +25 -0
- data/lib/arcanoae/core/engine.rb +163 -0
- data/lib/arcanoae/core/logger.rb +51 -0
- data/lib/arcanoae/dsl/scene_builder.rb +238 -0
- data/lib/arcanoae/gui/container.rb +50 -0
- data/lib/arcanoae/gui/control.rb +91 -0
- data/lib/arcanoae/gui/controls.rb +195 -0
- data/lib/arcanoae/input/input_manager.rb +172 -0
- data/lib/arcanoae/lights/directional_light.rb +24 -0
- data/lib/arcanoae/lights/hemispheric_light.rb +26 -0
- data/lib/arcanoae/lights/light.rb +30 -0
- data/lib/arcanoae/lights/point_light.rb +23 -0
- data/lib/arcanoae/lights/shadow_generator.rb +151 -0
- data/lib/arcanoae/loaders/gltf_loader.rb +223 -0
- data/lib/arcanoae/loaders/obj_loader.rb +179 -0
- data/lib/arcanoae/materials/material.rb +67 -0
- data/lib/arcanoae/materials/pbr_material.rb +75 -0
- data/lib/arcanoae/materials/standard_material.rb +38 -0
- data/lib/arcanoae/math/bounding_box.rb +98 -0
- data/lib/arcanoae/math/bounding_sphere.rb +52 -0
- data/lib/arcanoae/math/color3.rb +183 -0
- data/lib/arcanoae/math/color4.rb +143 -0
- data/lib/arcanoae/math/frustum.rb +101 -0
- data/lib/arcanoae/math/matrix4.rb +357 -0
- data/lib/arcanoae/math/plane.rb +51 -0
- data/lib/arcanoae/math/quaternion.rb +247 -0
- data/lib/arcanoae/math/ray.rb +78 -0
- data/lib/arcanoae/math/vector2.rb +159 -0
- data/lib/arcanoae/math/vector3.rb +173 -0
- data/lib/arcanoae/math/vector4.rb +132 -0
- data/lib/arcanoae/math.rb +45 -0
- data/lib/arcanoae/meshes/mesh.rb +101 -0
- data/lib/arcanoae/meshes/mesh_builder.rb +286 -0
- data/lib/arcanoae/meshes/vertex_data.rb +166 -0
- data/lib/arcanoae/physics/physics_engine.rb +145 -0
- data/lib/arcanoae/physics/physics_impostor.rb +84 -0
- data/lib/arcanoae/post_process/effects/fxaa_post_process.rb +112 -0
- data/lib/arcanoae/post_process/post_process.rb +68 -0
- data/lib/arcanoae/post_process/post_process_manager.rb +62 -0
- data/lib/arcanoae/rendering/gpu_buffer.rb +70 -0
- data/lib/arcanoae/rendering/mesh_renderer.rb +144 -0
- data/lib/arcanoae/rendering/render_pipeline.rb +98 -0
- data/lib/arcanoae/rendering/shader_program.rb +114 -0
- data/lib/arcanoae/rendering/shader_store.rb +226 -0
- data/lib/arcanoae/scene/scene.rb +131 -0
- data/lib/arcanoae/scene/transform_node.rb +185 -0
- data/lib/arcanoae/textures/base_texture.rb +48 -0
- data/lib/arcanoae/textures/render_target_texture.rb +73 -0
- data/lib/arcanoae/textures/texture.rb +90 -0
- data/lib/arcanoae/version.rb +5 -0
- data/lib/arcanoae.rb +89 -0
- data/shaders/blur.frag +29 -0
- data/shaders/default.frag +30 -0
- data/shaders/default.vert +23 -0
- data/shaders/fxaa.frag +55 -0
- data/shaders/particles.frag +25 -0
- data/shaders/particles.vert +33 -0
- data/shaders/pbr.frag +176 -0
- data/shaders/pbr.vert +31 -0
- data/shaders/post_process.vert +11 -0
- data/shaders/shadow_map.frag +4 -0
- data/shaders/shadow_map.vert +10 -0
- data/shaders/standard.frag +99 -0
- data/shaders/standard.vert +29 -0
- metadata +184 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arcanoae
|
|
4
|
+
module GUI
|
|
5
|
+
class TextBlock < Control
|
|
6
|
+
attr_accessor :text, :text_wrapping, :resize_to_fit, :text_horizontal_alignment
|
|
7
|
+
|
|
8
|
+
def initialize(name = nil)
|
|
9
|
+
super
|
|
10
|
+
@text = ""
|
|
11
|
+
@text_wrapping = false
|
|
12
|
+
@resize_to_fit = false
|
|
13
|
+
@text_horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Button < Container
|
|
18
|
+
attr_accessor :text_block, :image, :background, :corner_radius
|
|
19
|
+
attr_accessor :thickness, :hover_background
|
|
20
|
+
|
|
21
|
+
def initialize(name = nil)
|
|
22
|
+
super
|
|
23
|
+
@text_block = TextBlock.new
|
|
24
|
+
@image = nil
|
|
25
|
+
@background = "gray"
|
|
26
|
+
@hover_background = "lightgray"
|
|
27
|
+
@corner_radius = 4
|
|
28
|
+
@thickness = 1
|
|
29
|
+
add_control(@text_block)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.create_simple_button(name, text)
|
|
33
|
+
button = new(name)
|
|
34
|
+
button.text_block.text = text
|
|
35
|
+
button
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.create_image_button(name, text, image_url)
|
|
39
|
+
button = new(name)
|
|
40
|
+
button.text_block.text = text
|
|
41
|
+
button.image = Image.new
|
|
42
|
+
button.image.source = image_url
|
|
43
|
+
button.add_control(button.image)
|
|
44
|
+
button
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class Image < Control
|
|
49
|
+
attr_accessor :source, :stretch, :auto_scale
|
|
50
|
+
|
|
51
|
+
STRETCH_NONE = 0
|
|
52
|
+
STRETCH_FILL = 1
|
|
53
|
+
STRETCH_UNIFORM = 2
|
|
54
|
+
STRETCH_EXTEND = 3
|
|
55
|
+
|
|
56
|
+
def initialize(name = nil)
|
|
57
|
+
super
|
|
58
|
+
@source = nil
|
|
59
|
+
@stretch = STRETCH_UNIFORM
|
|
60
|
+
@auto_scale = false
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class Slider < Control
|
|
65
|
+
attr_accessor :minimum, :maximum, :value, :step
|
|
66
|
+
attr_accessor :bar_color, :thumb_color, :is_vertical
|
|
67
|
+
|
|
68
|
+
def initialize(name = nil)
|
|
69
|
+
super
|
|
70
|
+
@minimum = 0
|
|
71
|
+
@maximum = 100
|
|
72
|
+
@value = 50
|
|
73
|
+
@step = 1
|
|
74
|
+
@bar_color = "gray"
|
|
75
|
+
@thumb_color = "white"
|
|
76
|
+
@is_vertical = false
|
|
77
|
+
@on_value_changed_callbacks = []
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def on_value_changed(&block)
|
|
81
|
+
@on_value_changed_callbacks << block if block
|
|
82
|
+
self
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def value=(new_value)
|
|
86
|
+
clamped = [[new_value, @minimum].max, @maximum].min
|
|
87
|
+
return if @value == clamped
|
|
88
|
+
|
|
89
|
+
@value = clamped
|
|
90
|
+
@on_value_changed_callbacks.each { |cb| cb.call(@value) }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class Checkbox < Control
|
|
95
|
+
attr_accessor :is_checked, :check_size_ratio, :background, :check_color
|
|
96
|
+
|
|
97
|
+
def initialize(name = nil)
|
|
98
|
+
super
|
|
99
|
+
@is_checked = false
|
|
100
|
+
@check_size_ratio = 0.8
|
|
101
|
+
@background = "gray"
|
|
102
|
+
@check_color = "white"
|
|
103
|
+
@on_checked_changed_callbacks = []
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def on_checked_changed(&block)
|
|
107
|
+
@on_checked_changed_callbacks << block if block
|
|
108
|
+
self
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def toggle
|
|
112
|
+
self.is_checked = !@is_checked
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def is_checked=(value)
|
|
116
|
+
return if @is_checked == value
|
|
117
|
+
|
|
118
|
+
@is_checked = value
|
|
119
|
+
@on_checked_changed_callbacks.each { |cb| cb.call(@is_checked) }
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class StackPanel < Container
|
|
124
|
+
attr_accessor :is_vertical, :spacing
|
|
125
|
+
|
|
126
|
+
def initialize(name = nil)
|
|
127
|
+
super
|
|
128
|
+
@is_vertical = true
|
|
129
|
+
@spacing = 5
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class Grid < Container
|
|
134
|
+
def initialize(name = nil)
|
|
135
|
+
super
|
|
136
|
+
@column_definitions = []
|
|
137
|
+
@row_definitions = []
|
|
138
|
+
@cell_info = {}
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def add_column_definition(width, is_pixel = false)
|
|
142
|
+
@column_definitions << { width: width, is_pixel: is_pixel }
|
|
143
|
+
self
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def add_row_definition(height, is_pixel = false)
|
|
147
|
+
@row_definitions << { height: height, is_pixel: is_pixel }
|
|
148
|
+
self
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def add_control(control, row = 0, column = 0)
|
|
152
|
+
super(control)
|
|
153
|
+
@cell_info[control] = { row: row, column: column }
|
|
154
|
+
self
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def get_cell_info(control)
|
|
158
|
+
@cell_info[control]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
attr_reader :column_definitions, :row_definitions
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
class AdvancedDynamicTexture < Container
|
|
165
|
+
attr_accessor :ideal_width, :ideal_height, :render_at_ideal_size
|
|
166
|
+
|
|
167
|
+
def initialize(name = nil)
|
|
168
|
+
super
|
|
169
|
+
@ideal_width = 1024
|
|
170
|
+
@ideal_height = 1024
|
|
171
|
+
@render_at_ideal_size = false
|
|
172
|
+
@is_foreground = true
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def self.create_full_screen_ui(name, foreground = true, scene = nil)
|
|
176
|
+
texture = new(name)
|
|
177
|
+
texture.instance_variable_set(:@is_foreground, foreground)
|
|
178
|
+
texture.instance_variable_set(:@scene, scene)
|
|
179
|
+
texture
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def self.create_for_mesh(mesh, width = 1024, height = 1024)
|
|
183
|
+
texture = new("MeshUI")
|
|
184
|
+
texture.ideal_width = width
|
|
185
|
+
texture.ideal_height = height
|
|
186
|
+
texture.instance_variable_set(:@mesh, mesh)
|
|
187
|
+
texture
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def foreground?
|
|
191
|
+
@is_foreground
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arcanoae
|
|
4
|
+
module Input
|
|
5
|
+
class InputManager
|
|
6
|
+
attr_reader :pointer_x, :pointer_y
|
|
7
|
+
attr_reader :pointer_delta_x, :pointer_delta_y
|
|
8
|
+
|
|
9
|
+
def initialize(scene = nil)
|
|
10
|
+
@scene = scene
|
|
11
|
+
@keys_down = {}
|
|
12
|
+
@keys_pressed = {}
|
|
13
|
+
@keys_released = {}
|
|
14
|
+
@mouse_buttons = {}
|
|
15
|
+
@pointer_x = 0
|
|
16
|
+
@pointer_y = 0
|
|
17
|
+
@pointer_delta_x = 0
|
|
18
|
+
@pointer_delta_y = 0
|
|
19
|
+
@mouse_wheel = 0
|
|
20
|
+
@gamepads = []
|
|
21
|
+
@callbacks = {
|
|
22
|
+
key_down: [],
|
|
23
|
+
key_up: [],
|
|
24
|
+
pointer_down: [],
|
|
25
|
+
pointer_up: [],
|
|
26
|
+
pointer_move: []
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def key_down?(key_code)
|
|
31
|
+
@keys_down[key_code] || false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def key_pressed?(key_code)
|
|
35
|
+
@keys_pressed[key_code] || false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def key_released?(key_code)
|
|
39
|
+
@keys_released[key_code] || false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def mouse_button_down?(button)
|
|
43
|
+
@mouse_buttons[button] || false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
attr_reader :mouse_wheel
|
|
47
|
+
|
|
48
|
+
def gamepads
|
|
49
|
+
@gamepads.dup
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def gamepad(index)
|
|
53
|
+
@gamepads[index]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def on_key_down(&block)
|
|
57
|
+
@callbacks[:key_down] << block if block
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def on_key_up(&block)
|
|
62
|
+
@callbacks[:key_up] << block if block
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def on_pointer_down(&block)
|
|
67
|
+
@callbacks[:pointer_down] << block if block
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def on_pointer_up(&block)
|
|
72
|
+
@callbacks[:pointer_up] << block if block
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def on_pointer_move(&block)
|
|
77
|
+
@callbacks[:pointer_move] << block if block
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def process_key_down(key_code)
|
|
82
|
+
was_down = @keys_down[key_code]
|
|
83
|
+
@keys_down[key_code] = true
|
|
84
|
+
@keys_pressed[key_code] = !was_down
|
|
85
|
+
@callbacks[:key_down].each { |cb| cb.call(key_code) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def process_key_up(key_code)
|
|
89
|
+
@keys_down[key_code] = false
|
|
90
|
+
@keys_released[key_code] = true
|
|
91
|
+
@callbacks[:key_up].each { |cb| cb.call(key_code) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def process_pointer_move(x, y)
|
|
95
|
+
@pointer_delta_x = x - @pointer_x
|
|
96
|
+
@pointer_delta_y = y - @pointer_y
|
|
97
|
+
@pointer_x = x
|
|
98
|
+
@pointer_y = y
|
|
99
|
+
@callbacks[:pointer_move].each { |cb| cb.call(x, y) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def process_pointer_down(button, x, y)
|
|
103
|
+
@mouse_buttons[button] = true
|
|
104
|
+
@callbacks[:pointer_down].each { |cb| cb.call(button, x, y) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def process_pointer_up(button, x, y)
|
|
108
|
+
@mouse_buttons[button] = false
|
|
109
|
+
@callbacks[:pointer_up].each { |cb| cb.call(button, x, y) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def process_mouse_wheel(delta)
|
|
113
|
+
@mouse_wheel = delta
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def update
|
|
117
|
+
@keys_pressed.clear
|
|
118
|
+
@keys_released.clear
|
|
119
|
+
@mouse_wheel = 0
|
|
120
|
+
@pointer_delta_x = 0
|
|
121
|
+
@pointer_delta_y = 0
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def dispose
|
|
125
|
+
@callbacks.each_value(&:clear)
|
|
126
|
+
@keys_down.clear
|
|
127
|
+
@mouse_buttons.clear
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class Gamepad
|
|
132
|
+
attr_reader :id, :index
|
|
133
|
+
attr_accessor :connected
|
|
134
|
+
|
|
135
|
+
def initialize(id, index)
|
|
136
|
+
@id = id
|
|
137
|
+
@index = index
|
|
138
|
+
@connected = true
|
|
139
|
+
@buttons = {}
|
|
140
|
+
@axes = {}
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def connected?
|
|
144
|
+
@connected
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def button_down?(button)
|
|
148
|
+
@buttons[button] || false
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def axis(axis_index)
|
|
152
|
+
@axes[axis_index] || 0.0
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def update_button(button, pressed)
|
|
156
|
+
@buttons[button] = pressed
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def update_axis(axis_index, value)
|
|
160
|
+
@axes[axis_index] = value
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def left_stick
|
|
164
|
+
Math::Vector2.new(axis(0), axis(1))
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def right_stick
|
|
168
|
+
Math::Vector2.new(axis(2), axis(3))
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arcanoae
|
|
4
|
+
module Lights
|
|
5
|
+
class DirectionalLight < Light
|
|
6
|
+
attr_accessor :direction
|
|
7
|
+
|
|
8
|
+
def initialize(name, direction, scene = nil)
|
|
9
|
+
super(name, scene)
|
|
10
|
+
@direction = direction.normalize
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def transfer_to_effect(effect, light_index)
|
|
14
|
+
{
|
|
15
|
+
type: :directional,
|
|
16
|
+
direction: @direction,
|
|
17
|
+
diffuse: @diffuse,
|
|
18
|
+
specular: @specular,
|
|
19
|
+
intensity: @intensity
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arcanoae
|
|
4
|
+
module Lights
|
|
5
|
+
class HemisphericLight < Light
|
|
6
|
+
attr_accessor :direction, :ground_color
|
|
7
|
+
|
|
8
|
+
def initialize(name, direction, scene = nil)
|
|
9
|
+
super(name, scene)
|
|
10
|
+
@direction = direction.normalize
|
|
11
|
+
@ground_color = Math::Color3.black
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def transfer_to_effect(effect, light_index)
|
|
15
|
+
{
|
|
16
|
+
type: :hemispheric,
|
|
17
|
+
direction: @direction,
|
|
18
|
+
diffuse: @diffuse,
|
|
19
|
+
ground_color: @ground_color,
|
|
20
|
+
specular: @specular,
|
|
21
|
+
intensity: @intensity
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arcanoae
|
|
4
|
+
module Lights
|
|
5
|
+
class Light < Scene::TransformNode
|
|
6
|
+
attr_accessor :diffuse, :specular, :intensity, :range
|
|
7
|
+
|
|
8
|
+
def initialize(name, scene = nil)
|
|
9
|
+
super(name, scene)
|
|
10
|
+
@diffuse = Math::Color3.white
|
|
11
|
+
@specular = Math::Color3.white
|
|
12
|
+
@intensity = 1.0
|
|
13
|
+
@range = Float::INFINITY
|
|
14
|
+
|
|
15
|
+
scene&.add_light(self)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def transfer_to_effect(effect, light_index)
|
|
19
|
+
# Override in subclasses
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def dispose
|
|
23
|
+
return if disposed?
|
|
24
|
+
|
|
25
|
+
@scene&.remove_light(self)
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arcanoae
|
|
4
|
+
module Lights
|
|
5
|
+
class PointLight < Light
|
|
6
|
+
def initialize(name, position, scene = nil)
|
|
7
|
+
super(name, scene)
|
|
8
|
+
@position = position.clone
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def transfer_to_effect(effect, light_index)
|
|
12
|
+
{
|
|
13
|
+
type: :point,
|
|
14
|
+
position: @position,
|
|
15
|
+
diffuse: @diffuse,
|
|
16
|
+
specular: @specular,
|
|
17
|
+
intensity: @intensity,
|
|
18
|
+
range: @range
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arcanoae
|
|
4
|
+
module Lights
|
|
5
|
+
class ShadowGenerator
|
|
6
|
+
include Arcanoae::Disposable
|
|
7
|
+
|
|
8
|
+
attr_reader :light, :shadow_map
|
|
9
|
+
attr_accessor :map_size, :bias, :darkness
|
|
10
|
+
attr_accessor :use_percentage_closer_filtering
|
|
11
|
+
attr_accessor :filter_quality
|
|
12
|
+
attr_reader :shadow_casters
|
|
13
|
+
|
|
14
|
+
DEFAULT_MAP_SIZE = 1024
|
|
15
|
+
DEFAULT_BIAS = 0.00005
|
|
16
|
+
DEFAULT_DARKNESS = 0.0
|
|
17
|
+
|
|
18
|
+
def initialize(map_size, light)
|
|
19
|
+
@map_size = map_size || DEFAULT_MAP_SIZE
|
|
20
|
+
@light = light
|
|
21
|
+
@bias = DEFAULT_BIAS
|
|
22
|
+
@darkness = DEFAULT_DARKNESS
|
|
23
|
+
@use_percentage_closer_filtering = false
|
|
24
|
+
@filter_quality = :medium
|
|
25
|
+
@shadow_casters = []
|
|
26
|
+
@shadow_map = nil
|
|
27
|
+
|
|
28
|
+
create_shadow_map
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def add_shadow_caster(mesh, include_descendants: false)
|
|
32
|
+
return if @shadow_casters.include?(mesh)
|
|
33
|
+
|
|
34
|
+
@shadow_casters << mesh
|
|
35
|
+
|
|
36
|
+
if include_descendants && mesh.respond_to?(:get_descendants)
|
|
37
|
+
mesh.get_descendants.each do |descendant|
|
|
38
|
+
add_shadow_caster(descendant, include_descendants: false)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def remove_shadow_caster(mesh, include_descendants: false)
|
|
44
|
+
@shadow_casters.delete(mesh)
|
|
45
|
+
|
|
46
|
+
if include_descendants && mesh.respond_to?(:get_descendants)
|
|
47
|
+
mesh.get_descendants.each do |descendant|
|
|
48
|
+
remove_shadow_caster(descendant, include_descendants: false)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def get_shadow_map
|
|
54
|
+
@shadow_map
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def is_ready?
|
|
58
|
+
@shadow_map&.ready?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def prepare_shadow_map
|
|
62
|
+
return unless @shadow_map
|
|
63
|
+
|
|
64
|
+
calculate_light_matrices
|
|
65
|
+
render_shadow_casters
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def get_transformation_matrix
|
|
69
|
+
@transform_matrix ||= Math::Matrix4.identity
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def recreate_shadow_map
|
|
73
|
+
dispose_shadow_map
|
|
74
|
+
create_shadow_map
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def dispose
|
|
78
|
+
return if disposed?
|
|
79
|
+
|
|
80
|
+
dispose_shadow_map
|
|
81
|
+
@shadow_casters.clear
|
|
82
|
+
|
|
83
|
+
super
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def create_shadow_map
|
|
89
|
+
return unless @light&.scene
|
|
90
|
+
|
|
91
|
+
@shadow_map = Textures::RenderTargetTexture.new(
|
|
92
|
+
"#{@light.name}_shadowMap",
|
|
93
|
+
@map_size,
|
|
94
|
+
@light.scene,
|
|
95
|
+
generate_mip_maps: false
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@transform_matrix = Math::Matrix4.identity
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def dispose_shadow_map
|
|
102
|
+
@shadow_map&.dispose
|
|
103
|
+
@shadow_map = nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def calculate_light_matrices
|
|
107
|
+
return unless @light
|
|
108
|
+
|
|
109
|
+
if @light.is_a?(DirectionalLight)
|
|
110
|
+
calculate_directional_light_matrices
|
|
111
|
+
elsif @light.is_a?(PointLight)
|
|
112
|
+
calculate_point_light_matrices
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def calculate_directional_light_matrices
|
|
117
|
+
direction = @light.direction.normalize
|
|
118
|
+
up = if direction.y.abs > 0.99
|
|
119
|
+
Math::Vector3.forward
|
|
120
|
+
else
|
|
121
|
+
Math::Vector3.up
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
view_matrix = Math::Matrix4.look_at(
|
|
125
|
+
Math::Vector3.zero - direction * 50,
|
|
126
|
+
Math::Vector3.zero,
|
|
127
|
+
up
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
projection_matrix = Math::Matrix4.orthographic(-50, 50, -50, 50, 0.1, 200)
|
|
131
|
+
|
|
132
|
+
@transform_matrix = projection_matrix * view_matrix
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def calculate_point_light_matrices
|
|
136
|
+
@transform_matrix = Math::Matrix4.identity
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def render_shadow_casters
|
|
140
|
+
@shadow_casters.each do |mesh|
|
|
141
|
+
next unless mesh.is_visible && mesh.cast_shadows
|
|
142
|
+
|
|
143
|
+
render_mesh_to_shadow_map(mesh)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def render_mesh_to_shadow_map(mesh)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|