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,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