cyberarm_engine 0.13.0 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
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