cyberarm_engine 0.13.0 → 0.13.1

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -8
  3. data/.travis.yml +5 -5
  4. data/Gemfile +6 -6
  5. data/LICENSE.txt +21 -21
  6. data/README.md +73 -43
  7. data/Rakefile +10 -10
  8. data/bin/console +14 -14
  9. data/bin/setup +8 -8
  10. data/cyberarm_engine.gemspec +36 -36
  11. data/lib/cyberarm_engine.rb +49 -47
  12. data/lib/cyberarm_engine/animator.rb +53 -53
  13. data/lib/cyberarm_engine/background.rb +175 -175
  14. data/lib/cyberarm_engine/bounding_box.rb +149 -149
  15. data/lib/cyberarm_engine/common.rb +96 -96
  16. data/lib/cyberarm_engine/config_file.rb +46 -0
  17. data/lib/cyberarm_engine/engine.rb +101 -101
  18. data/lib/cyberarm_engine/game_object.rb +256 -256
  19. data/lib/cyberarm_engine/game_state.rb +88 -88
  20. data/lib/cyberarm_engine/gosu_ext/circle.rb +8 -8
  21. data/lib/cyberarm_engine/ray.rb +55 -55
  22. data/lib/cyberarm_engine/shader.rb +398 -262
  23. data/lib/cyberarm_engine/text.rb +146 -146
  24. data/lib/cyberarm_engine/timer.rb +22 -22
  25. data/lib/cyberarm_engine/transform.rb +272 -272
  26. data/lib/cyberarm_engine/ui/border_canvas.rb +100 -100
  27. data/lib/cyberarm_engine/ui/dsl.rb +98 -98
  28. data/lib/cyberarm_engine/ui/element.rb +275 -275
  29. data/lib/cyberarm_engine/ui/elements/button.rb +66 -66
  30. data/lib/cyberarm_engine/ui/elements/check_box.rb +58 -58
  31. data/lib/cyberarm_engine/ui/elements/container.rb +176 -176
  32. data/lib/cyberarm_engine/ui/elements/edit_line.rb +171 -171
  33. data/lib/cyberarm_engine/ui/elements/flow.rb +16 -16
  34. data/lib/cyberarm_engine/ui/elements/image.rb +51 -51
  35. data/lib/cyberarm_engine/ui/elements/label.rb +49 -49
  36. data/lib/cyberarm_engine/ui/elements/progress.rb +49 -49
  37. data/lib/cyberarm_engine/ui/elements/stack.rb +12 -12
  38. data/lib/cyberarm_engine/ui/elements/toggle_button.rb +55 -55
  39. data/lib/cyberarm_engine/ui/event.rb +46 -46
  40. data/lib/cyberarm_engine/ui/gui_state.rb +134 -134
  41. data/lib/cyberarm_engine/ui/style.rb +36 -36
  42. data/lib/cyberarm_engine/ui/theme.rb +120 -120
  43. data/lib/cyberarm_engine/vector.rb +289 -202
  44. data/lib/cyberarm_engine/version.rb +4 -4
  45. metadata +3 -2
@@ -1,89 +1,89 @@
1
- module CyberarmEngine
2
- class GameState
3
- include Common
4
-
5
- attr_accessor :options, :global_pause
6
- attr_reader :game_objects, :containers
7
-
8
- def initialize(options={})
9
- @options = options
10
- @game_objects = []
11
- @global_pause = false
12
- $window.text_input = nil unless options[:preserve_text_input]
13
-
14
- @down_keys = {}
15
- end
16
-
17
- def setup
18
- end
19
-
20
- def draw
21
- @game_objects.each(&:draw)
22
- end
23
-
24
- def update
25
- @game_objects.each(&:update)
26
- end
27
-
28
- def draw_bounding_box(box)
29
- x,y, max_x, max_y = box.x, box.y, box.max_x, box.max_y
30
-
31
- color = Gosu::Color.rgba(255, 127, 64, 240)
32
-
33
- # pipe = 4
34
- # Gosu.draw_rect(x-width, y-height, x+(width*2), y+(height*2), color, Float::INFINITY)
35
- # puts "BB render: #{x}:#{y} w:#{x.abs+width} h:#{y.abs+height}"
36
- # Gosu.draw_rect(x, y, x.abs+width, y.abs+height, color, Float::INFINITY)
37
-
38
- # TOP LEFT to BOTTOM LEFT
39
- $window.draw_line(
40
- x, y, color,
41
- x, max_y, color,
42
- Float::INFINITY
43
- )
44
- # BOTTOM LEFT to BOTTOM RIGHT
45
- $window.draw_line(
46
- x, max_y, color,
47
- max_x, max_y, color,
48
- Float::INFINITY
49
- )
50
- # BOTTOM RIGHT to TOP RIGHT
51
- $window.draw_line(
52
- max_x, max_y, color,
53
- max_x, y, color,
54
- Float::INFINITY
55
- )
56
- # TOP RIGHT to TOP LEFT
57
- $window.draw_line(
58
- max_x, y, color,
59
- x, y, color,
60
- Float::INFINITY
61
- )
62
- end
63
-
64
- def destroy
65
- @options.clear
66
- @game_objects.clear
67
- end
68
-
69
- def button_down(id)
70
- @down_keys[id] = true
71
-
72
- @game_objects.each do |o|
73
- o.button_down(id)
74
- end
75
- end
76
-
77
- def button_up(id)
78
- @down_keys.delete(id)
79
-
80
- @game_objects.each do |o|
81
- o.button_up(id)
82
- end
83
- end
84
-
85
- def add_game_object(object)
86
- @game_objects << object
87
- end
88
- end
1
+ module CyberarmEngine
2
+ class GameState
3
+ include Common
4
+
5
+ attr_accessor :options, :global_pause
6
+ attr_reader :game_objects
7
+
8
+ def initialize(options={})
9
+ @options = options
10
+ @game_objects = []
11
+ @global_pause = false
12
+ $window.text_input = nil unless options[:preserve_text_input]
13
+
14
+ @down_keys = {}
15
+ end
16
+
17
+ def setup
18
+ end
19
+
20
+ def draw
21
+ @game_objects.each(&:draw)
22
+ end
23
+
24
+ def update
25
+ @game_objects.each(&:update)
26
+ end
27
+
28
+ def draw_bounding_box(box)
29
+ x,y, max_x, max_y = box.x, box.y, box.max_x, box.max_y
30
+
31
+ color = Gosu::Color.rgba(255, 127, 64, 240)
32
+
33
+ # pipe = 4
34
+ # Gosu.draw_rect(x-width, y-height, x+(width*2), y+(height*2), color, Float::INFINITY)
35
+ # puts "BB render: #{x}:#{y} w:#{x.abs+width} h:#{y.abs+height}"
36
+ # Gosu.draw_rect(x, y, x.abs+width, y.abs+height, color, Float::INFINITY)
37
+
38
+ # TOP LEFT to BOTTOM LEFT
39
+ $window.draw_line(
40
+ x, y, color,
41
+ x, max_y, color,
42
+ Float::INFINITY
43
+ )
44
+ # BOTTOM LEFT to BOTTOM RIGHT
45
+ $window.draw_line(
46
+ x, max_y, color,
47
+ max_x, max_y, color,
48
+ Float::INFINITY
49
+ )
50
+ # BOTTOM RIGHT to TOP RIGHT
51
+ $window.draw_line(
52
+ max_x, max_y, color,
53
+ max_x, y, color,
54
+ Float::INFINITY
55
+ )
56
+ # TOP RIGHT to TOP LEFT
57
+ $window.draw_line(
58
+ max_x, y, color,
59
+ x, y, color,
60
+ Float::INFINITY
61
+ )
62
+ end
63
+
64
+ def destroy
65
+ @options.clear
66
+ @game_objects.clear
67
+ end
68
+
69
+ def button_down(id)
70
+ @down_keys[id] = true
71
+
72
+ @game_objects.each do |o|
73
+ o.button_down(id)
74
+ end
75
+ end
76
+
77
+ def button_up(id)
78
+ @down_keys.delete(id)
79
+
80
+ @game_objects.each do |o|
81
+ o.button_up(id)
82
+ end
83
+ end
84
+
85
+ def add_game_object(object)
86
+ @game_objects << object
87
+ end
88
+ end
89
89
  end
@@ -1,9 +1,9 @@
1
- module Gosu
2
- # Sourced from https://gist.github.com/ippa/662583
3
- def self.draw_circle(cx,cy,r, z = 9999,color = Gosu::Color::GREEN, step = 10)
4
- 0.step(360, step) do |a1|
5
- a2 = a1 + step
6
- draw_line(cx + Gosu.offset_x(a1, r), cy + Gosu.offset_y(a1, r), color, cx + Gosu.offset_x(a2, r), cy + Gosu.offset_y(a2, r), color, z)
7
- end
8
- end
1
+ module Gosu
2
+ # Sourced from https://gist.github.com/ippa/662583
3
+ def self.draw_circle(cx,cy,r, z = 9999,color = Gosu::Color::GREEN, step = 10)
4
+ 0.step(360, step) do |a1|
5
+ a2 = a1 + step
6
+ draw_line(cx + Gosu.offset_x(a1, r), cy + Gosu.offset_y(a1, r), color, cx + Gosu.offset_x(a2, r), cy + Gosu.offset_y(a2, r), color, z)
7
+ end
8
+ end
9
9
  end
@@ -1,56 +1,56 @@
1
- module CyberarmEngine
2
- class Ray
3
- def initialize(origin, direction, range = Float::INFINITY)
4
- raise "Origin must be a Vector!" unless origin.is_a?(Vector)
5
- raise "Direction must be a Vector!" unless direction.is_a?(Vector)
6
-
7
- @origin = origin
8
- @direction = direction
9
- @range = range
10
-
11
- @inverse_direction = @direction.inverse
12
- end
13
-
14
- def intersect?(intersectable)
15
- if intersectable.is_a?(BoundingBox)
16
- intersect_bounding_box?(intersectable)
17
- else
18
- raise NotImplementedError, "Ray intersection test for #{intersectable.class} not implemented."
19
- end
20
- end
21
-
22
- # Based on: https://tavianator.com/fast-branchless-raybounding-box-intersections/
23
- def intersect_bounding_box?(box)
24
- tmin = -@range
25
- tmax = @range
26
-
27
- tx1 = (box.min.x - @origin.x) * @inverse_direction.x
28
- tx2 = (box.max.x - @origin.x) * @inverse_direction.x
29
-
30
- tmin = max(tmin, min(tx1, tx2))
31
- tmax = min(tmax, max(tx1, tx2))
32
-
33
- ty1 = (box.min.y - @origin.y) * @inverse_direction.y
34
- ty2 = (box.max.y - @origin.y) * @inverse_direction.y
35
-
36
- tmin = max(tmin, min(ty1, ty2))
37
- tmax = min(tmax, max(ty1, ty2))
38
-
39
- tz1 = (box.min.z - @origin.z) * @inverse_direction.z
40
- tz2 = (box.max.z - @origin.z) * @inverse_direction.z
41
-
42
- tmin = max(tmin, min(tz1, tz2))
43
- tmax = min(tmax, max(tz1, tz2))
44
-
45
- return tmax >= max(tmin, 0.0);
46
- end
47
-
48
- def min(x, y)
49
- ((x) < (y) ? (x) : (y))
50
- end
51
-
52
- def max(x, y)
53
- ((x) > (y) ? (x) : (y))
54
- end
55
- end
1
+ module CyberarmEngine
2
+ class Ray
3
+ def initialize(origin, direction, range = Float::INFINITY)
4
+ raise "Origin must be a Vector!" unless origin.is_a?(Vector)
5
+ raise "Direction must be a Vector!" unless direction.is_a?(Vector)
6
+
7
+ @origin = origin
8
+ @direction = direction
9
+ @range = range
10
+
11
+ @inverse_direction = @direction.inverse
12
+ end
13
+
14
+ def intersect?(intersectable)
15
+ if intersectable.is_a?(BoundingBox)
16
+ intersect_bounding_box?(intersectable)
17
+ else
18
+ raise NotImplementedError, "Ray intersection test for #{intersectable.class} not implemented."
19
+ end
20
+ end
21
+
22
+ # Based on: https://tavianator.com/fast-branchless-raybounding-box-intersections/
23
+ def intersect_bounding_box?(box)
24
+ tmin = -@range
25
+ tmax = @range
26
+
27
+ tx1 = (box.min.x - @origin.x) * @inverse_direction.x
28
+ tx2 = (box.max.x - @origin.x) * @inverse_direction.x
29
+
30
+ tmin = max(tmin, min(tx1, tx2))
31
+ tmax = min(tmax, max(tx1, tx2))
32
+
33
+ ty1 = (box.min.y - @origin.y) * @inverse_direction.y
34
+ ty2 = (box.max.y - @origin.y) * @inverse_direction.y
35
+
36
+ tmin = max(tmin, min(ty1, ty2))
37
+ tmax = min(tmax, max(ty1, ty2))
38
+
39
+ tz1 = (box.min.z - @origin.z) * @inverse_direction.z
40
+ tz2 = (box.max.z - @origin.z) * @inverse_direction.z
41
+
42
+ tmin = max(tmin, min(tz1, tz2))
43
+ tmax = min(tmax, max(tz1, tz2))
44
+
45
+ return tmax >= max(tmin, 0.0);
46
+ end
47
+
48
+ def min(x, y)
49
+ ((x) < (y) ? (x) : (y))
50
+ end
51
+
52
+ def max(x, y)
53
+ ((x) > (y) ? (x) : (y))
54
+ end
55
+ end
56
56
  end
@@ -1,262 +1,398 @@
1
- module CyberarmEngine
2
- # Ref: https://github.com/vaiorabbit/ruby-opengl/blob/master/sample/OrangeBook/brick.rb
3
- class Shader
4
- include OpenGL
5
- @@shaders = {}
6
- PREPROCESSOR_CHARACTER = "@"
7
-
8
- def self.add(name, instance)
9
- @@shaders[name] = instance
10
- end
11
-
12
- def self.use(name, &block)
13
- shader = @@shaders.dig(name)
14
- if shader
15
- shader.use(&block)
16
- else
17
- raise ArgumentError, "Shader '#{name}' not found!"
18
- end
19
- end
20
-
21
- def self.available?(name)
22
- @@shaders.dig(name).is_a?(Shader)
23
- end
24
-
25
- def self.get(name)
26
- @@shaders.dig(name)
27
- end
28
-
29
- def self.active_shader
30
- @active_shader
31
- end
32
-
33
- def self.active_shader=(instance)
34
- @active_shader = instance
35
- end
36
-
37
- def self.stop
38
- shader = Shader.active_shader
39
-
40
- if shader
41
- shader.stop
42
- else
43
- raise ArgumentError, "No active shader to stop!"
44
- end
45
- end
46
-
47
- def self.attribute_location(variable)
48
- raise RuntimeError, "No active shader!" unless Shader.active_shader
49
- Shader.active_shader.attribute_location(variable)
50
- end
51
-
52
- def self.set_uniform(variable, value)
53
- raise RuntimeError, "No active shader!" unless Shader.active_shader
54
- Shader.active_shader.set_uniform(variable, value)
55
- end
56
-
57
- attr_reader :name, :program
58
- def initialize(name:, includes_dir: nil, vertex: "shaders/default.vert", fragment:)
59
- raise "Shader name can not be blank" if name.length == 0
60
-
61
- @name = name
62
- @includes_dir = includes_dir
63
- @compiled = false
64
-
65
- @program = nil
66
-
67
- @error_buffer_size = 1024
68
- @variable_missing = {}
69
-
70
- @data = {shaders: {}}
71
-
72
- unless shader_files_exist?(vertex: vertex, fragment: fragment)
73
- raise ArgumentError, "Shader files not found: #{vertex} or #{fragment}"
74
- end
75
-
76
- create_shader(type: :vertex, source: File.read(vertex))
77
- create_shader(type: :fragment, source: File.read(fragment))
78
-
79
- compile_shader(type: :vertex)
80
- compile_shader(type: :fragment)
81
- link_shaders
82
-
83
- # Only add shader if it successfully compiles
84
- if @compiled
85
- puts "compiled!"
86
- puts "Compiled shader: #{@name}"
87
- Shader.add(@name, self)
88
- else
89
- warn "FAILED to compile shader: #{@name}", ""
90
- end
91
- end
92
-
93
- def shader_files_exist?(vertex:, fragment:)
94
- File.exist?(vertex) && File.exist?(fragment)
95
- end
96
-
97
- def create_shader(type:, source:)
98
- _shader = nil
99
-
100
- case type
101
- when :vertex
102
- _shader = glCreateShader(GL_VERTEX_SHADER)
103
- when :fragment
104
- _shader = glCreateShader(GL_FRAGMENT_SHADER)
105
- else
106
- warn "Unsupported shader type: #{type.inspect}"
107
- end
108
-
109
- processed_source = preprocess_source(source: source)
110
-
111
- _source = [processed_source].pack("p")
112
- _size = [processed_source.length].pack("I")
113
- glShaderSource(_shader, 1, _source, _size)
114
-
115
- @data[:shaders][type] =_shader
116
- end
117
-
118
- def preprocess_source(source:)
119
- lines = source.lines
120
-
121
- lines.each_with_index do |line, i|
122
- if line.start_with?(PREPROCESSOR_CHARACTER)
123
- preprocessor = line.strip.split(" ")
124
- lines.delete(line)
125
-
126
- case preprocessor.first
127
- when "@include"
128
- raise ArgumentError, "Shader preprocessor include directory was not given for shader #{@name}" unless @includes_dir
129
-
130
- preprocessor[1..preprocessor.length - 1].join.scan(/"([^"]*)"/).flatten.each do |file|
131
- source = File.read("#{@includes_dir}/#{file}.glsl")
132
-
133
- lines.insert(i, source)
134
- end
135
- else
136
- warn "Unsupported preprocessor #{preprocessor.first} for #{@name}"
137
- end
138
- end
139
- end
140
-
141
- lines.join
142
- end
143
-
144
- def compile_shader(type:)
145
- _compiled = false
146
- _shader = @data[:shaders][type]
147
- raise ArgumentError, "No shader for #{type.inspect}" unless _shader
148
-
149
- glCompileShader(_shader)
150
- buffer = ' '
151
- glGetShaderiv(_shader, GL_COMPILE_STATUS, buffer)
152
- compiled = buffer.unpack('L')[0]
153
-
154
- if compiled == 0
155
- log = ' ' * @error_buffer_size
156
- glGetShaderInfoLog(_shader, @error_buffer_size, nil, log)
157
- puts "Shader Error: Program \"#{@name}\""
158
- puts " #{type.to_s.capitalize} Shader InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n"
159
- puts " Shader Compiled status: #{compiled}"
160
- puts " NOTE: assignment of uniforms in shaders is illegal!"
161
- puts
162
- else
163
- _compiled = true
164
- end
165
-
166
- return _compiled
167
- end
168
-
169
- def link_shaders
170
- @program = glCreateProgram
171
- @data[:shaders].values.each do |_shader|
172
- glAttachShader(@program, _shader)
173
- end
174
- glLinkProgram(@program)
175
-
176
- buffer = ' '
177
- glGetProgramiv(@program, GL_LINK_STATUS, buffer)
178
- linked = buffer.unpack('L')[0]
179
-
180
- if linked == 0
181
- log = ' ' * @error_buffer_size
182
- glGetProgramInfoLog(@program, @error_buffer_size, nil, log)
183
- puts "Shader Error: Program \"#{@name}\""
184
- puts " Program InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n"
185
- end
186
-
187
- @compiled = linked == 0 ? false : true
188
- end
189
-
190
- # Returns the location of a uniform variable
191
- def variable(variable)
192
- loc = glGetUniformLocation(@program, variable)
193
- if (loc == -1)
194
- puts "Shader Error: Program \"#{@name}\" has no such uniform named \"#{variable}\"", " Is it used in the shader? GLSL may have optimized it out.", " Is it miss spelled?" unless @variable_missing[variable]
195
- @variable_missing[variable] = true
196
- end
197
- return loc
198
- end
199
-
200
- def use(&block)
201
- return unless compiled?
202
- raise "Another shader is already in use! #{Shader.active_shader.name.inspect}" if Shader.active_shader
203
- Shader.active_shader=self
204
-
205
- glUseProgram(@program)
206
-
207
- if block
208
- block.call(self)
209
- stop
210
- end
211
- end
212
-
213
- def stop
214
- Shader.active_shader = nil if Shader.active_shader == self
215
- glUseProgram(0)
216
- end
217
-
218
- def compiled?
219
- @compiled
220
- end
221
-
222
- def attribute_location(variable)
223
- glGetUniformLocation(@program, variable)
224
- end
225
-
226
- def uniform_transform(variable, value, location = nil)
227
- attr_loc = location ? location : attribute_location(variable)
228
-
229
- glUniformMatrix4fv(attr_loc, 1, GL_FALSE, value.to_gl.pack("F16"))
230
- end
231
-
232
- def uniform_boolean(variable, value, location = nil)
233
- attr_loc = location ? location : attribute_location(variable)
234
-
235
- glUniform1i(attr_loc, value ? 1 : 0)
236
- end
237
-
238
- def uniform_integer(variable, value, location = nil)
239
- attr_loc = location ? location : attribute_location(variable)
240
-
241
- glUniform1i(attr_loc, value)
242
- end
243
-
244
- def uniform_float(variable, value, location = nil)
245
- attr_loc = location ? location : attribute_location(variable)
246
-
247
- glUniform1f(attr_loc, value)
248
- end
249
-
250
- def uniform_vec3(variable, value, location = nil)
251
- attr_loc = location ? location : attribute_location(variable)
252
-
253
- glUniform3f(attr_loc, *value.to_a[0..2])
254
- end
255
-
256
- def uniform_vec4(variable, value, location = nil)
257
- attr_loc = location ? location : attribute_location(variable)
258
-
259
- glUniform4f(attr_loc, *value.to_a)
260
- end
261
- end
262
- end
1
+ module CyberarmEngine
2
+ # Ref: https://github.com/vaiorabbit/ruby-opengl/blob/master/sample/OrangeBook/brick.rb
3
+ class Shader
4
+ include OpenGL
5
+ @@shaders = {} # Cache for {Shader} instances
6
+ PREPROCESSOR_CHARACTER = "@".freeze # magic character for preprocessor phase of {Shader} compilation
7
+
8
+ # add instance of {Shader} to cache
9
+ #
10
+ # @param name [String]
11
+ # @param instance [Shader]
12
+ def self.add(name, instance)
13
+ @@shaders[name] = instance
14
+ end
15
+
16
+ # removes {Shader} from cache and cleans up
17
+ #
18
+ # @param name [String]
19
+ def self.delete(name)
20
+ shader = @@shaders.dig(name)
21
+
22
+ if shader
23
+ @@shaders.delete(name)
24
+
25
+ if shader.compiled?
26
+ glDeleteProgram(shader.program)
27
+ end
28
+ end
29
+ end
30
+
31
+ ##
32
+ # runs _block_ using {Shader} with _name_
33
+ #
34
+ # @example
35
+ #
36
+ # CyberarmEngine::Shader.use("blur") do |shader|
37
+ # shader.uniform_float("radius", 20.0)
38
+ # # OpenGL Code that uses shader
39
+ # end
40
+ #
41
+ # @param name [String] name of {Shader} to use
42
+ # @return [void]
43
+ def self.use(name, &block)
44
+ shader = @@shaders.dig(name)
45
+ if shader
46
+ shader.use(&block)
47
+ else
48
+ raise ArgumentError, "Shader '#{name}' not found!"
49
+ end
50
+ end
51
+
52
+ # returns whether {Shader} with _name_ is in cache
53
+ #
54
+ # @param name [String]
55
+ # @return [Boolean]
56
+ def self.available?(name)
57
+ @@shaders.dig(name).is_a?(Shader)
58
+ end
59
+
60
+ # returns instance of {Shader}, if it exists
61
+ #
62
+ # @param name [String]
63
+ # @return [Shader?]
64
+ def self.get(name)
65
+ @@shaders.dig(name)
66
+ end
67
+
68
+ # returns currently active {Shader}, if one is active
69
+ #
70
+ # @return [Shader?]
71
+ def self.active_shader
72
+ @active_shader
73
+ end
74
+
75
+ # sets currently active {Shader}
76
+ #
77
+ # @param instance [Shader] instance of {Shader} to set as active
78
+ def self.active_shader=(instance)
79
+ @active_shader = instance
80
+ end
81
+
82
+ # stops using currently active {Shader}
83
+ def self.stop
84
+ shader = Shader.active_shader
85
+
86
+ if shader
87
+ shader.stop
88
+ else
89
+ raise ArgumentError, "No active shader to stop!"
90
+ end
91
+ end
92
+
93
+ # returns location of OpenGL Shader uniform
94
+ #
95
+ # @param variable [String]
96
+ def self.attribute_location(variable)
97
+ raise RuntimeError, "No active shader!" unless Shader.active_shader
98
+ Shader.active_shader.attribute_location(variable)
99
+ end
100
+
101
+ # sets _variable_ to _value_
102
+ #
103
+ # @param variable [String]
104
+ # @param value
105
+ def self.set_uniform(variable, value)
106
+ raise RuntimeError, "No active shader!" unless Shader.active_shader
107
+ Shader.active_shader.set_uniform(variable, value)
108
+ end
109
+
110
+ attr_reader :name, :program
111
+ def initialize(name:, includes_dir: nil, vertex: "shaders/default.vert", fragment:)
112
+ raise "Shader name can not be blank" if name.length == 0
113
+
114
+ @name = name
115
+ @includes_dir = includes_dir
116
+ @compiled = false
117
+
118
+ @program = nil
119
+
120
+ @error_buffer_size = 1024 * 8
121
+ @variable_missing = {}
122
+
123
+ @data = {shaders: {}}
124
+
125
+ unless shader_files_exist?(vertex: vertex, fragment: fragment)
126
+ raise ArgumentError, "Shader files not found: #{vertex} or #{fragment}"
127
+ end
128
+
129
+ create_shader(type: :vertex, source: File.read(vertex))
130
+ create_shader(type: :fragment, source: File.read(fragment))
131
+
132
+ compile_shader(type: :vertex)
133
+ compile_shader(type: :fragment)
134
+ link_shaders
135
+
136
+ @data[:shaders].each { |key, id| glDeleteShader(id) }
137
+
138
+ # Only add shader if it successfully compiles
139
+ if @compiled
140
+ puts "compiled!"
141
+ puts "Compiled shader: #{@name}"
142
+ Shader.add(@name, self)
143
+ else
144
+ glDeleteProgram(@program)
145
+ warn "FAILED to compile shader: #{@name}", ""
146
+ end
147
+ end
148
+
149
+ # whether vertex and fragment files exist on disk
150
+ #
151
+ # @return [Boolean]
152
+ def shader_files_exist?(vertex:, fragment:)
153
+ File.exist?(vertex) && File.exist?(fragment)
154
+ end
155
+
156
+ # creates an OpenGL Shader of _type_ using _source_
157
+ #
158
+ # @param type [Symbol] valid values are: :vertex, :fragment
159
+ # @param source [String] source code for shader
160
+ def create_shader(type:, source:)
161
+ _shader = nil
162
+
163
+ case type
164
+ when :vertex
165
+ _shader = glCreateShader(GL_VERTEX_SHADER)
166
+ when :fragment
167
+ _shader = glCreateShader(GL_FRAGMENT_SHADER)
168
+ else
169
+ raise ArgumentError, "Unsupported shader type: #{type.inspect}"
170
+ end
171
+
172
+ processed_source = preprocess_source(source: source)
173
+
174
+ _source = [processed_source].pack("p")
175
+ _size = [processed_source.length].pack("I")
176
+ glShaderSource(_shader, 1, _source, _size)
177
+
178
+ @data[:shaders][type] =_shader
179
+ end
180
+
181
+ # evaluates shader preprocessors
182
+ #
183
+ # currently supported preprocessors:
184
+ #
185
+ # @include "file/path" "another/file/path" # => Replace line with contents of file; Shader includes_dir must be specified in constructor
186
+ #
187
+ # @example
188
+ # # Example Vertex Shader #
189
+ # # #version 330 core
190
+ # # @include "material_struct"
191
+ # # void main() {
192
+ # # gl_Position = vec4(1, 1, 1, 1);
193
+ # # }
194
+ #
195
+ # Shader.new(name: "model_renderer", includes_dir: "path/to/includes", vertex: "path/to/vertex_shader.glsl")
196
+ #
197
+ # @param source shader source code
198
+ def preprocess_source(source:)
199
+ lines = source.lines
200
+
201
+ lines.each_with_index do |line, i|
202
+ if line.start_with?(PREPROCESSOR_CHARACTER)
203
+ preprocessor = line.strip.split(" ")
204
+ lines.delete(line)
205
+
206
+ case preprocessor.first
207
+ when "@include"
208
+ raise ArgumentError, "Shader preprocessor include directory was not given for shader #{@name}" unless @includes_dir
209
+
210
+ preprocessor[1..preprocessor.length - 1].join.scan(/"([^"]*)"/).flatten.each do |file|
211
+ source = File.read("#{@includes_dir}/#{file}.glsl")
212
+
213
+ lines.insert(i, source)
214
+ end
215
+ else
216
+ warn "Unsupported preprocessor #{preprocessor.first} for #{@name}"
217
+ end
218
+ end
219
+ end
220
+
221
+ lines.join
222
+ end
223
+
224
+ # compile OpenGL Shader of _type_
225
+ #
226
+ # @return [Boolean] whether compilation succeeded
227
+ def compile_shader(type:)
228
+ _compiled = false
229
+ _shader = @data[:shaders][type]
230
+ raise ArgumentError, "No shader for #{type.inspect}" unless _shader
231
+
232
+ glCompileShader(_shader)
233
+ buffer = ' '
234
+ glGetShaderiv(_shader, GL_COMPILE_STATUS, buffer)
235
+ compiled = buffer.unpack('L')[0]
236
+
237
+ if compiled == 0
238
+ log = ' ' * @error_buffer_size
239
+ glGetShaderInfoLog(_shader, @error_buffer_size, nil, log)
240
+ puts "Shader Error: Program \"#{@name}\""
241
+ puts " #{type.to_s.capitalize} Shader InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n"
242
+ puts " Shader Compiled status: #{compiled}"
243
+ puts " NOTE: assignment of uniforms in shaders is illegal!"
244
+ puts
245
+ else
246
+ _compiled = true
247
+ end
248
+
249
+ return _compiled
250
+ end
251
+
252
+ # link compiled OpenGL Shaders in to a OpenGL Program
253
+ #
254
+ # @note linking must succeed or shader cannot be used
255
+ #
256
+ # @return [Boolean] whether linking succeeded
257
+ def link_shaders
258
+ @program = glCreateProgram
259
+ @data[:shaders].values.each do |_shader|
260
+ glAttachShader(@program, _shader)
261
+ end
262
+ glLinkProgram(@program)
263
+
264
+ buffer = ' '
265
+ glGetProgramiv(@program, GL_LINK_STATUS, buffer)
266
+ linked = buffer.unpack('L')[0]
267
+
268
+ if linked == 0
269
+ log = ' ' * @error_buffer_size
270
+ glGetProgramInfoLog(@program, @error_buffer_size, nil, log)
271
+ puts "Shader Error: Program \"#{@name}\""
272
+ puts " Program InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n"
273
+ end
274
+
275
+ @compiled = linked == 0 ? false : true
276
+ end
277
+
278
+ # Returns the location of a uniform _variable_
279
+ #
280
+ # @param variable [String]
281
+ # @return [Integer] location of uniform
282
+ def variable(variable)
283
+ loc = glGetUniformLocation(@program, variable)
284
+ if (loc == -1)
285
+ puts "Shader Error: Program \"#{@name}\" has no such uniform named \"#{variable}\"", " Is it used in the shader? GLSL may have optimized it out.", " Is it miss spelled?" unless @variable_missing[variable]
286
+ @variable_missing[variable] = true
287
+ end
288
+ return loc
289
+ end
290
+
291
+ # @see Shader.use Shader.use
292
+ def use(&block)
293
+ return unless compiled?
294
+ raise "Another shader is already in use! #{Shader.active_shader.name.inspect}" if Shader.active_shader
295
+ Shader.active_shader=self
296
+
297
+ glUseProgram(@program)
298
+
299
+ if block
300
+ block.call(self)
301
+ stop
302
+ end
303
+ end
304
+
305
+ # stop using shader, if shader is active
306
+ def stop
307
+ Shader.active_shader = nil if Shader.active_shader == self
308
+ glUseProgram(0)
309
+ end
310
+
311
+ # @return [Boolean] whether {Shader} successfully compiled
312
+ def compiled?
313
+ @compiled
314
+ end
315
+
316
+ # returns location of a uniform _variable_
317
+ #
318
+ # @note Use {#variable} for friendly error handling
319
+ # @see #variable Shader#variable
320
+ #
321
+ # @param variable [String]
322
+ # @return [Integer]
323
+ def attribute_location(variable)
324
+ glGetUniformLocation(@program, variable)
325
+ end
326
+
327
+ # send {Transform} to {Shader}
328
+ #
329
+ # @param variable [String]
330
+ # @param value [Transform]
331
+ # @param location [Integer]
332
+ # @return [void]
333
+ def uniform_transform(variable, value, location = nil)
334
+ attr_loc = location ? location : attribute_location(variable)
335
+
336
+ glUniformMatrix4fv(attr_loc, 1, GL_FALSE, value.to_gl.pack("F16"))
337
+ end
338
+
339
+ # send Boolean to {Shader}
340
+ #
341
+ # @param variable [String]
342
+ # @param value [Boolean]
343
+ # @param location [Integer]
344
+ # @return [void]
345
+ def uniform_boolean(variable, value, location = nil)
346
+ attr_loc = location ? location : attribute_location(variable)
347
+
348
+ glUniform1i(attr_loc, value ? 1 : 0)
349
+ end
350
+
351
+ # send Integer to {Shader}
352
+ # @param variable [String]
353
+ # @param value [Integer]
354
+ # @param location [Integer]
355
+ # @return [void]
356
+ def uniform_integer(variable, value, location = nil)
357
+ attr_loc = location ? location : attribute_location(variable)
358
+
359
+ glUniform1i(attr_loc, value)
360
+ end
361
+
362
+ # send Float to {Shader}
363
+ #
364
+ # @param variable [String]
365
+ # @param value [Float]
366
+ # @param location [Integer]
367
+ # @return [void]
368
+ def uniform_float(variable, value, location = nil)
369
+ attr_loc = location ? location : attribute_location(variable)
370
+
371
+ glUniform1f(attr_loc, value)
372
+ end
373
+
374
+ # send {Vector} (x, y, z) to {Shader}
375
+ #
376
+ # @param variable [String]
377
+ # @param value [Vector]
378
+ # @param location [Integer]
379
+ # @return [void]
380
+ def uniform_vec3(variable, value, location = nil)
381
+ attr_loc = location ? location : attribute_location(variable)
382
+
383
+ glUniform3f(attr_loc, *value.to_a[0..2])
384
+ end
385
+
386
+ # send {Vector} to {Shader}
387
+ #
388
+ # @param variable [String]
389
+ # @param value [Vector]
390
+ # @param location [Integer]
391
+ # @return [void]
392
+ def uniform_vec4(variable, value, location = nil)
393
+ attr_loc = location ? location : attribute_location(variable)
394
+
395
+ glUniform4f(attr_loc, *value.to_a)
396
+ end
397
+ end
398
+ end