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.
- checksums.yaml +4 -4
- data/.gitignore +8 -8
- data/.travis.yml +5 -5
- data/Gemfile +6 -6
- data/LICENSE.txt +21 -21
- data/README.md +73 -43
- data/Rakefile +10 -10
- data/bin/console +14 -14
- data/bin/setup +8 -8
- data/cyberarm_engine.gemspec +36 -36
- data/lib/cyberarm_engine.rb +49 -47
- data/lib/cyberarm_engine/animator.rb +53 -53
- data/lib/cyberarm_engine/background.rb +175 -175
- data/lib/cyberarm_engine/bounding_box.rb +149 -149
- data/lib/cyberarm_engine/common.rb +96 -96
- data/lib/cyberarm_engine/config_file.rb +46 -0
- data/lib/cyberarm_engine/engine.rb +101 -101
- data/lib/cyberarm_engine/game_object.rb +256 -256
- data/lib/cyberarm_engine/game_state.rb +88 -88
- data/lib/cyberarm_engine/gosu_ext/circle.rb +8 -8
- data/lib/cyberarm_engine/ray.rb +55 -55
- data/lib/cyberarm_engine/shader.rb +398 -262
- data/lib/cyberarm_engine/text.rb +146 -146
- data/lib/cyberarm_engine/timer.rb +22 -22
- data/lib/cyberarm_engine/transform.rb +272 -272
- data/lib/cyberarm_engine/ui/border_canvas.rb +100 -100
- data/lib/cyberarm_engine/ui/dsl.rb +98 -98
- data/lib/cyberarm_engine/ui/element.rb +275 -275
- data/lib/cyberarm_engine/ui/elements/button.rb +66 -66
- data/lib/cyberarm_engine/ui/elements/check_box.rb +58 -58
- data/lib/cyberarm_engine/ui/elements/container.rb +176 -176
- data/lib/cyberarm_engine/ui/elements/edit_line.rb +171 -171
- data/lib/cyberarm_engine/ui/elements/flow.rb +16 -16
- data/lib/cyberarm_engine/ui/elements/image.rb +51 -51
- data/lib/cyberarm_engine/ui/elements/label.rb +49 -49
- data/lib/cyberarm_engine/ui/elements/progress.rb +49 -49
- data/lib/cyberarm_engine/ui/elements/stack.rb +12 -12
- data/lib/cyberarm_engine/ui/elements/toggle_button.rb +55 -55
- data/lib/cyberarm_engine/ui/event.rb +46 -46
- data/lib/cyberarm_engine/ui/gui_state.rb +134 -134
- data/lib/cyberarm_engine/ui/style.rb +36 -36
- data/lib/cyberarm_engine/ui/theme.rb +120 -120
- data/lib/cyberarm_engine/vector.rb +289 -202
- data/lib/cyberarm_engine/version.rb +4 -4
- 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
|
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
|
data/lib/cyberarm_engine/ray.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def self.
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
else
|
89
|
-
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
@
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
#
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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
|