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,146 +1,146 @@
1
- module CyberarmEngine
2
- class Text
3
- CACHE = {}
4
-
5
- attr_accessor :x, :y, :z, :size, :options
6
- attr_reader :text, :textobject, :factor_x, :factor_y, :color, :shadow, :shadow_size, :shadow_alpha, :shadow_color
7
-
8
- def initialize(text, options={})
9
- @text = text.to_s || ""
10
- @options = options
11
- @size = options[:size] || 18
12
- @font = options[:font] || "sans-serif"#Gosu.default_font_name
13
- @x = options[:x] || 0
14
- @y = options[:y] || 0
15
- @z = options[:z] || 1025
16
- @factor_x = options[:factor_x] || 1
17
- @factor_y = options[:factor_y] || 1
18
- @color = options[:color] || Gosu::Color::WHITE
19
- @alignment= options[:alignment] || nil
20
- @shadow = true if options[:shadow] == true
21
- @shadow = false if options[:shadow] == false
22
- @shadow = true if options[:shadow] == nil
23
- @shadow_size = options[:shadow_size] ? options[:shadow_size] : 1
24
- @shadow_alpha= options[:shadow_alpha] ? options[:shadow_alpha] : 30
25
- @shadow_alpha= options[:shadow_alpha] ? options[:shadow_alpha] : 30
26
- @shadow_color= options[:shadow_color]
27
- @textobject = check_cache(@size, @font)
28
-
29
- if @alignment
30
- case @alignment
31
- when :left
32
- @x = 0+BUTTON_PADDING
33
- when :center
34
- @x = ($window.width/2)-(@textobject.text_width(@text)/2)
35
- when :right
36
- @x = $window.width-BUTTON_PADDING-@textobject.text_width(@text)
37
- end
38
- end
39
-
40
- return self
41
- end
42
-
43
- def check_cache(size, font_name)
44
- available = false
45
- font = nil
46
-
47
- if CACHE[size]
48
- if CACHE[size][font_name]
49
- font = CACHE[size][font_name]
50
- available = true
51
- else
52
- available = false
53
- end
54
- else
55
- available = false
56
- end
57
-
58
- unless available
59
- font = Gosu::Font.new(@size, name: @font)
60
- CACHE[@size] = {} unless CACHE[@size].is_a?(Hash)
61
- CACHE[@size][@font] = font
62
- end
63
-
64
- return font
65
- end
66
-
67
- def text=(string)
68
- @rendered_shadow = nil
69
- @text = string
70
- end
71
-
72
- def factor_x=(n)
73
- @rendered_shadow = nil
74
- @factor_x = n
75
- end
76
- def factor_y=(n)
77
- @rendered_shadow = nil
78
- @factor_y = n
79
- end
80
- def color=(color)
81
- @rendered_shadow = nil
82
- @color = color
83
- end
84
- def shadow=(boolean)
85
- @rendered_shadow = nil
86
- @shadow = boolean
87
- end
88
- def shadow_size=(n)
89
- @rendered_shadow = nil
90
- @shadow_size = n
91
- end
92
- def shadow_alpha=(n)
93
- @rendered_shadow = nil
94
- @shadow_alpha = n
95
- end
96
- def shadow_color=(n)
97
- @rendered_shadow = nil
98
- @shadow_color = n
99
- end
100
-
101
- def width
102
- textobject.text_width(@text)
103
- end
104
-
105
- def height
106
- @text.lines.count > 0 ? (@text.lines.count) * textobject.height : @textobject.height
107
- end
108
-
109
- def draw
110
- if @shadow && !ARGV.join.include?("--no-shadow")
111
- shadow_alpha = @color.alpha <= 30 ? @color.alpha : @shadow_alpha
112
- shadow_color = @shadow_color ? @shadow_color : Gosu::Color.rgba(@color.red, @color.green, @color.blue, shadow_alpha)
113
-
114
- _x = @shadow_size
115
- _y = @shadow_size
116
-
117
- @rendered_shadow ||= Gosu.render((self.width+(shadow_size*2)).ceil, (self.height+(@shadow_size*2)).ceil) do
118
- @textobject.draw_markup(@text, _x-@shadow_size, _y, @z)
119
- @textobject.draw_markup(@text, _x-@shadow_size, _y-@shadow_size, @z)
120
-
121
- @textobject.draw_markup(@text, _x, _y-@shadow_size, @z, @factor_x)
122
- @textobject.draw_markup(@text, _x+@shadow_size, _y-@shadow_size, @z)
123
-
124
- @textobject.draw_markup(@text, _x, _y+@shadow_size, @z)
125
- @textobject.draw_markup(@text, _x-@shadow_size, _y+@shadow_size, @z)
126
-
127
- @textobject.draw_markup(@text, _x+@shadow_size, _y, @z)
128
- @textobject.draw_markup(@text, _x+@shadow_size, _y+@shadow_size, @z)
129
- end
130
- @rendered_shadow.draw(@x-@shadow_size, @y-@shadow_size, @z, @factor_x, @factor_y, shadow_color)
131
- end
132
-
133
- @textobject.draw_markup(@text, @x, @y, @z, @factor_x, @factor_y, @color)
134
- end
135
-
136
- def alpha=(n)
137
- @color = Gosu::Color.rgba(@color.red, @color.green, @color.blue, n)
138
- end
139
-
140
- def alpha
141
- @color.alpha
142
- end
143
-
144
- def update; end
145
- end
146
- end
1
+ module CyberarmEngine
2
+ class Text
3
+ CACHE = {}
4
+
5
+ attr_accessor :x, :y, :z, :size, :options
6
+ attr_reader :text, :textobject, :factor_x, :factor_y, :color, :shadow, :shadow_size, :shadow_alpha, :shadow_color
7
+
8
+ def initialize(text, options={})
9
+ @text = text.to_s || ""
10
+ @options = options
11
+ @size = options[:size] || 18
12
+ @font = options[:font] || "sans-serif"#Gosu.default_font_name
13
+ @x = options[:x] || 0
14
+ @y = options[:y] || 0
15
+ @z = options[:z] || 1025
16
+ @factor_x = options[:factor_x] || 1
17
+ @factor_y = options[:factor_y] || 1
18
+ @color = options[:color] || Gosu::Color::WHITE
19
+ @alignment= options[:alignment] || nil
20
+ @shadow = true if options[:shadow] == true
21
+ @shadow = false if options[:shadow] == false
22
+ @shadow = true if options[:shadow] == nil
23
+ @shadow_size = options[:shadow_size] ? options[:shadow_size] : 1
24
+ @shadow_alpha= options[:shadow_alpha] ? options[:shadow_alpha] : 30
25
+ @shadow_alpha= options[:shadow_alpha] ? options[:shadow_alpha] : 30
26
+ @shadow_color= options[:shadow_color]
27
+ @textobject = check_cache(@size, @font)
28
+
29
+ if @alignment
30
+ case @alignment
31
+ when :left
32
+ @x = 0+BUTTON_PADDING
33
+ when :center
34
+ @x = ($window.width/2)-(@textobject.text_width(@text)/2)
35
+ when :right
36
+ @x = $window.width-BUTTON_PADDING-@textobject.text_width(@text)
37
+ end
38
+ end
39
+
40
+ return self
41
+ end
42
+
43
+ def check_cache(size, font_name)
44
+ available = false
45
+ font = nil
46
+
47
+ if CACHE[size]
48
+ if CACHE[size][font_name]
49
+ font = CACHE[size][font_name]
50
+ available = true
51
+ else
52
+ available = false
53
+ end
54
+ else
55
+ available = false
56
+ end
57
+
58
+ unless available
59
+ font = Gosu::Font.new(@size, name: @font)
60
+ CACHE[@size] = {} unless CACHE[@size].is_a?(Hash)
61
+ CACHE[@size][@font] = font
62
+ end
63
+
64
+ return font
65
+ end
66
+
67
+ def text=(string)
68
+ @rendered_shadow = nil
69
+ @text = string
70
+ end
71
+
72
+ def factor_x=(n)
73
+ @rendered_shadow = nil
74
+ @factor_x = n
75
+ end
76
+ def factor_y=(n)
77
+ @rendered_shadow = nil
78
+ @factor_y = n
79
+ end
80
+ def color=(color)
81
+ @rendered_shadow = nil
82
+ @color = color
83
+ end
84
+ def shadow=(boolean)
85
+ @rendered_shadow = nil
86
+ @shadow = boolean
87
+ end
88
+ def shadow_size=(n)
89
+ @rendered_shadow = nil
90
+ @shadow_size = n
91
+ end
92
+ def shadow_alpha=(n)
93
+ @rendered_shadow = nil
94
+ @shadow_alpha = n
95
+ end
96
+ def shadow_color=(n)
97
+ @rendered_shadow = nil
98
+ @shadow_color = n
99
+ end
100
+
101
+ def width
102
+ textobject.text_width(@text)
103
+ end
104
+
105
+ def height
106
+ @text.lines.count > 0 ? (@text.lines.count) * textobject.height : @textobject.height
107
+ end
108
+
109
+ def draw
110
+ if @shadow && !ARGV.join.include?("--no-shadow")
111
+ shadow_alpha = @color.alpha <= 30 ? @color.alpha : @shadow_alpha
112
+ shadow_color = @shadow_color ? @shadow_color : Gosu::Color.rgba(@color.red, @color.green, @color.blue, shadow_alpha)
113
+
114
+ _x = @shadow_size
115
+ _y = @shadow_size
116
+
117
+ @rendered_shadow ||= Gosu.render((self.width+(shadow_size*2)).ceil, (self.height+(@shadow_size*2)).ceil) do
118
+ @textobject.draw_markup(@text, _x-@shadow_size, _y, @z)
119
+ @textobject.draw_markup(@text, _x-@shadow_size, _y-@shadow_size, @z)
120
+
121
+ @textobject.draw_markup(@text, _x, _y-@shadow_size, @z, @factor_x)
122
+ @textobject.draw_markup(@text, _x+@shadow_size, _y-@shadow_size, @z)
123
+
124
+ @textobject.draw_markup(@text, _x, _y+@shadow_size, @z)
125
+ @textobject.draw_markup(@text, _x-@shadow_size, _y+@shadow_size, @z)
126
+
127
+ @textobject.draw_markup(@text, _x+@shadow_size, _y, @z)
128
+ @textobject.draw_markup(@text, _x+@shadow_size, _y+@shadow_size, @z)
129
+ end
130
+ @rendered_shadow.draw(@x-@shadow_size, @y-@shadow_size, @z, @factor_x, @factor_y, shadow_color)
131
+ end
132
+
133
+ @textobject.draw_markup(@text, @x, @y, @z, @factor_x, @factor_y, @color)
134
+ end
135
+
136
+ def alpha=(n)
137
+ @color = Gosu::Color.rgba(@color.red, @color.green, @color.blue, n)
138
+ end
139
+
140
+ def alpha
141
+ @color.alpha
142
+ end
143
+
144
+ def update; end
145
+ end
146
+ end
@@ -1,23 +1,23 @@
1
- module CyberarmEngine
2
- class Timer
3
- def initialize(interval, looping = true, &block)
4
- @interval = interval
5
- @looping = looping
6
- @block = block
7
-
8
- @last_interval = Gosu.milliseconds
9
- @triggered = false
10
- end
11
-
12
- def update
13
- return if !@looping && @triggered
14
-
15
- if Gosu.milliseconds >= @last_interval + @interval
16
- @last_interval = Gosu.milliseconds
17
- @triggered = true
18
-
19
- @block.call if @block
20
- end
21
- end
22
- end
1
+ module CyberarmEngine
2
+ class Timer
3
+ def initialize(interval, looping = true, &block)
4
+ @interval = interval
5
+ @looping = looping
6
+ @block = block
7
+
8
+ @last_interval = Gosu.milliseconds
9
+ @triggered = false
10
+ end
11
+
12
+ def update
13
+ return if !@looping && @triggered
14
+
15
+ if Gosu.milliseconds >= @last_interval + @interval
16
+ @last_interval = Gosu.milliseconds
17
+ @triggered = true
18
+
19
+ @block.call if @block
20
+ end
21
+ end
22
+ end
23
23
  end
@@ -1,273 +1,273 @@
1
- module CyberarmEngine
2
- # Basic 4x4 matrix operations
3
- class Transform
4
- attr_reader :elements
5
- def initialize(matrix)
6
- @elements = matrix
7
-
8
- raise "Transform is wrong size! Got #{@elements.size}, expected 16" if 16 != @elements.size
9
- raise "Invalid value for matrix, must all be numeric!" if @elements.any? { |e| e.nil? || !e.is_a?(Numeric)}
10
- end
11
-
12
- def self.identity
13
- Transform.new(
14
- [
15
- 1, 0, 0, 0,
16
- 0, 1, 0, 0,
17
- 0, 0, 1, 0,
18
- 0, 0, 0, 1
19
- ]
20
- )
21
- end
22
-
23
- ### 2D Operations meant for interacting with Gosu ###
24
-
25
- # 2d rotate operation, replicates Gosu's Gosu.rotate function
26
- def self.rotate(angle, rotate_around = nil)
27
- double c = Math.cos(angle).degrees_to_radians
28
- double s = Math.sin(angle).degrees_to_radians
29
- matrix = [
30
- +c, +s, 0, 0,
31
- -s, +c, 0, 0,
32
- 0, 0, 1, 0,
33
- 0, 0, 0, 1,
34
- ]
35
-
36
- rotate_matrix = Transform.new(matrix, rows: 4, columns: 4)
37
-
38
- if rotate_around && (rotate_around.x != 0.0 || rotate_around.y != 0.0)
39
- negative_rotate_around = Vector.new(-rotate_around.x, -rotate_around.y, -rotate_around.z)
40
-
41
- rotate_matrix = concat(
42
- concat(translate(negative_rotate_around), rotate_matrix),
43
- translate(rotate_around)
44
- )
45
- end
46
-
47
- return rotate_matrix
48
- end
49
-
50
- # 2d translate operation, replicates Gosu's Gosu.translate function
51
- def self.translate(vector)
52
- x, y, z = vector.to_a[0..2]
53
- matrix = [
54
- 1, 0, 0, 0,
55
- 0, 1, 0, 0,
56
- 0, 0, 1, 0,
57
- x, y, z, 1,
58
- ]
59
-
60
- Transform.new(matrix)
61
- end
62
-
63
- # 2d scale operation, replicates Gosu's Gosu.rotate function
64
- def self.scale(vector, center_around = nil)
65
- scale_x, scale_y, scale_z = vector.to_a[0..2]
66
- matrix = [
67
- scale_x, 0, 0, 0,
68
- 0, scale_y, 0, 0,
69
- 0, 0, scale_z, 0,
70
- 0, 0, 0, 1,
71
- ]
72
-
73
- scale_matrix = Transform.new(matrix)
74
-
75
- if center_around && (center_around.x != 0.0 || center_around.y != 0.0)
76
- negative_center_around = Vector.new(-center_around.x, -center_around.y, -center_around.z)
77
-
78
- scale_matrix = concat(
79
- concat(translate(negative_center_around), scale_matrix),
80
- translate(center_around)
81
- )
82
- end
83
-
84
- return scale_matrix
85
- end
86
-
87
- def self.concat(left, right)
88
- matrix = Array.new(left.elements.size)
89
- rows = 4
90
-
91
- matrix.size.times do |i|
92
- matrix[i] = 0
93
-
94
- rows.times do |j|
95
- matrix[i] += left.elements[i / rows * rows + j] * right.elements[i % rows + j * rows]
96
- end
97
- end
98
-
99
- Transform.new(matrix)
100
- end
101
-
102
- #### 3D Operations meant for OpenGL ###
103
-
104
- def self.translate_3d(vector)
105
- x, y, z = vector.to_a[0..2]
106
- matrix = [
107
- 1, 0, 0, x,
108
- 0, 1, 0, y,
109
- 0, 0, 1, z,
110
- 0, 0, 0, 1,
111
- ]
112
-
113
- Transform.new(matrix)
114
- end
115
-
116
- def self.rotate_3d(vector, order = "zyx")
117
- x, y, z = vector.to_a[0..2].map { |axis| axis * Math::PI / 180.0 }
118
-
119
- rotation_x = Transform.new(
120
- [
121
- 1, 0, 0, 0,
122
- 0, Math.cos(x), -Math.sin(x), 0,
123
- 0, Math.sin(x), Math.cos(x), 0,
124
- 0, 0, 0, 1
125
- ]
126
- )
127
-
128
- rotation_y = Transform.new(
129
- [
130
- Math.cos(y), 0, Math.sin(y), 0,
131
- 0, 1, 0, 0,
132
- -Math.sin(y), 0, Math.cos(y), 0,
133
- 0, 0, 0, 1
134
- ]
135
- )
136
-
137
- rotation_z = Transform.new(
138
- [
139
- Math.cos(z), -Math.sin(z), 0, 0,
140
- Math.sin(z), Math.cos(z), 0, 0,
141
- 0, 0, 1, 0,
142
- 0, 0, 0, 1
143
- ]
144
- )
145
-
146
- rotation_z * rotation_y * rotation_x
147
- end
148
-
149
- # Implements glRotatef
150
- # https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glRotate.xml
151
- def self.rotate_gl(angle, axis)
152
- radians = angle * Math::PI / 180.0
153
- s = Math.sin(radians)
154
- c = Math.cos(radians)
155
-
156
- axis = axis.normalized
157
- x, y, z = axis.to_a[0..2]
158
-
159
- n = (1.0 - c)
160
-
161
- Transform.new(
162
- [
163
- x * x * n + c, x * y * n - z * s, x * z * n + y * s, 0,
164
- y * x * n + z * s, y * y * n + c, y * z * n - x * s, 0,
165
- x * z * n - y * s, y * z * n + x * s, z * z * n + c, 0,
166
- 0, 0, 0, 1.0
167
- ]
168
- )
169
- end
170
-
171
- def self.scale_3d(vector)
172
- x, y, z = vector.to_a[0..2]
173
-
174
- Transform.new(
175
- [
176
- x, 0, 0, 0,
177
- 0, y, 0, 0,
178
- 0, 0, z, 0,
179
- 0, 0, 0, 1
180
- ]
181
- )
182
- end
183
-
184
- def self.perspective(fov_y, aspect_ratio, near, far)
185
- f = 1.0 / Math.tan(fov_y.degrees_to_radians / 2.0) # cotangent
186
- zn = (far + near.to_f) / (near - far.to_f)
187
- zf = (2.0 * far * near.to_f) / (near - far.to_f)
188
-
189
- Transform.new(
190
- [
191
- f / aspect_ratio, 0.0, 0.0, 0.0,
192
- 0.0, f, 0.0, 0.0,
193
- 0.0, 0.0, zn, zf,
194
- 0.0, 0.0, -1.0, 0.0
195
- ]
196
- )
197
- end
198
-
199
- def self.view(eye, orientation)
200
- # https://www.3dgep.com/understanding-the-view-matrix/#The_View_Matrix
201
- cosPitch = Math.cos(orientation.z * Math::PI / 180.0)
202
- sinPitch = Math.sin(orientation.z * Math::PI / 180.0)
203
- cosYaw = Math.cos(orientation.y * Math::PI / 180.0)
204
- sinYaw = Math.sin(orientation.y * Math::PI / 180.0)
205
-
206
- x_axis = Vector.new(cosYaw, 0, -sinYaw)
207
- y_axis = Vector.new(sinYaw * sinPitch, cosPitch, cosYaw * sinPitch)
208
- z_axis = Vector.new(sinYaw * cosPitch, -sinPitch, cosPitch * cosYaw)
209
-
210
- Transform.new(
211
- [
212
- x_axis.x, y_axis.y, z_axis.z, 0,
213
- x_axis.x, y_axis.y, z_axis.z, 0,
214
- x_axis.x, y_axis.y, z_axis.z, 0,
215
- -x_axis.dot(eye), -y_axis.dot(eye), -z_axis.dot(eye), 1
216
- ]
217
- )
218
- end
219
-
220
- def *(other)
221
-
222
- case other
223
- when CyberarmEngine::Vector
224
- matrix = @elements.clone
225
- list = other.to_a
226
-
227
- @elements.each_with_index do |e, i|
228
- matrix[i] = e + list[i % 4]
229
- end
230
-
231
- Transform.new(matrix)
232
-
233
- when CyberarmEngine::Transform
234
- return multiply_matrices(other)
235
- else
236
- p other.class
237
- raise TypeError, "Expected CyberarmEngine::Vector or CyberarmEngine::Transform got #{other.class}"
238
- end
239
- end
240
-
241
- def get(x, y)
242
- width = 4
243
-
244
- # puts "Transform|#{self.object_id} -> #{@elements[width * y + x].inspect} (index: #{width * y + x})"
245
- @elements[width * y + x]
246
- end
247
-
248
- def multiply_matrices(other)
249
- matrix = Array.new(16, 0)
250
-
251
- 4.times do |x|
252
- 4.times do |y|
253
- 4.times do |k|
254
- matrix[4 * y + x] += get(x, k) * other.get(k, y)
255
- end
256
- end
257
- end
258
-
259
- return Transform.new(matrix)
260
- end
261
-
262
- # arranges Matrix in column major form
263
- def to_gl
264
- e = @elements
265
- [
266
- e[0], e[4], e[8], e[12],
267
- e[1], e[5], e[9], e[13],
268
- e[2], e[6], e[10], e[14],
269
- e[3], e[7], e[11], e[15]
270
- ]
271
- end
272
- end
1
+ module CyberarmEngine
2
+ # Basic 4x4 matrix operations
3
+ class Transform
4
+ attr_reader :elements
5
+ def initialize(matrix)
6
+ @elements = matrix
7
+
8
+ raise "Transform is wrong size! Got #{@elements.size}, expected 16" if 16 != @elements.size
9
+ raise "Invalid value for matrix, must all be numeric!" if @elements.any? { |e| e.nil? || !e.is_a?(Numeric)}
10
+ end
11
+
12
+ def self.identity
13
+ Transform.new(
14
+ [
15
+ 1, 0, 0, 0,
16
+ 0, 1, 0, 0,
17
+ 0, 0, 1, 0,
18
+ 0, 0, 0, 1
19
+ ]
20
+ )
21
+ end
22
+
23
+ ### 2D Operations meant for interacting with Gosu ###
24
+
25
+ # 2d rotate operation, replicates Gosu's Gosu.rotate function
26
+ def self.rotate(angle, rotate_around = nil)
27
+ double c = Math.cos(angle).degrees_to_radians
28
+ double s = Math.sin(angle).degrees_to_radians
29
+ matrix = [
30
+ +c, +s, 0, 0,
31
+ -s, +c, 0, 0,
32
+ 0, 0, 1, 0,
33
+ 0, 0, 0, 1,
34
+ ]
35
+
36
+ rotate_matrix = Transform.new(matrix, rows: 4, columns: 4)
37
+
38
+ if rotate_around && (rotate_around.x != 0.0 || rotate_around.y != 0.0)
39
+ negative_rotate_around = Vector.new(-rotate_around.x, -rotate_around.y, -rotate_around.z)
40
+
41
+ rotate_matrix = concat(
42
+ concat(translate(negative_rotate_around), rotate_matrix),
43
+ translate(rotate_around)
44
+ )
45
+ end
46
+
47
+ return rotate_matrix
48
+ end
49
+
50
+ # 2d translate operation, replicates Gosu's Gosu.translate function
51
+ def self.translate(vector)
52
+ x, y, z = vector.to_a[0..2]
53
+ matrix = [
54
+ 1, 0, 0, 0,
55
+ 0, 1, 0, 0,
56
+ 0, 0, 1, 0,
57
+ x, y, z, 1,
58
+ ]
59
+
60
+ Transform.new(matrix)
61
+ end
62
+
63
+ # 2d scale operation, replicates Gosu's Gosu.rotate function
64
+ def self.scale(vector, center_around = nil)
65
+ scale_x, scale_y, scale_z = vector.to_a[0..2]
66
+ matrix = [
67
+ scale_x, 0, 0, 0,
68
+ 0, scale_y, 0, 0,
69
+ 0, 0, scale_z, 0,
70
+ 0, 0, 0, 1,
71
+ ]
72
+
73
+ scale_matrix = Transform.new(matrix)
74
+
75
+ if center_around && (center_around.x != 0.0 || center_around.y != 0.0)
76
+ negative_center_around = Vector.new(-center_around.x, -center_around.y, -center_around.z)
77
+
78
+ scale_matrix = concat(
79
+ concat(translate(negative_center_around), scale_matrix),
80
+ translate(center_around)
81
+ )
82
+ end
83
+
84
+ return scale_matrix
85
+ end
86
+
87
+ def self.concat(left, right)
88
+ matrix = Array.new(left.elements.size)
89
+ rows = 4
90
+
91
+ matrix.size.times do |i|
92
+ matrix[i] = 0
93
+
94
+ rows.times do |j|
95
+ matrix[i] += left.elements[i / rows * rows + j] * right.elements[i % rows + j * rows]
96
+ end
97
+ end
98
+
99
+ Transform.new(matrix)
100
+ end
101
+
102
+ #### 3D Operations meant for OpenGL ###
103
+
104
+ def self.translate_3d(vector)
105
+ x, y, z = vector.to_a[0..2]
106
+ matrix = [
107
+ 1, 0, 0, x,
108
+ 0, 1, 0, y,
109
+ 0, 0, 1, z,
110
+ 0, 0, 0, 1,
111
+ ]
112
+
113
+ Transform.new(matrix)
114
+ end
115
+
116
+ def self.rotate_3d(vector, order = "zyx")
117
+ x, y, z = vector.to_a[0..2].map { |axis| axis * Math::PI / 180.0 }
118
+
119
+ rotation_x = Transform.new(
120
+ [
121
+ 1, 0, 0, 0,
122
+ 0, Math.cos(x), -Math.sin(x), 0,
123
+ 0, Math.sin(x), Math.cos(x), 0,
124
+ 0, 0, 0, 1
125
+ ]
126
+ )
127
+
128
+ rotation_y = Transform.new(
129
+ [
130
+ Math.cos(y), 0, Math.sin(y), 0,
131
+ 0, 1, 0, 0,
132
+ -Math.sin(y), 0, Math.cos(y), 0,
133
+ 0, 0, 0, 1
134
+ ]
135
+ )
136
+
137
+ rotation_z = Transform.new(
138
+ [
139
+ Math.cos(z), -Math.sin(z), 0, 0,
140
+ Math.sin(z), Math.cos(z), 0, 0,
141
+ 0, 0, 1, 0,
142
+ 0, 0, 0, 1
143
+ ]
144
+ )
145
+
146
+ rotation_z * rotation_y * rotation_x
147
+ end
148
+
149
+ # Implements glRotatef
150
+ # https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glRotate.xml
151
+ def self.rotate_gl(angle, axis)
152
+ radians = angle * Math::PI / 180.0
153
+ s = Math.sin(radians)
154
+ c = Math.cos(radians)
155
+
156
+ axis = axis.normalized
157
+ x, y, z = axis.to_a[0..2]
158
+
159
+ n = (1.0 - c)
160
+
161
+ Transform.new(
162
+ [
163
+ x * x * n + c, x * y * n - z * s, x * z * n + y * s, 0,
164
+ y * x * n + z * s, y * y * n + c, y * z * n - x * s, 0,
165
+ x * z * n - y * s, y * z * n + x * s, z * z * n + c, 0,
166
+ 0, 0, 0, 1.0
167
+ ]
168
+ )
169
+ end
170
+
171
+ def self.scale_3d(vector)
172
+ x, y, z = vector.to_a[0..2]
173
+
174
+ Transform.new(
175
+ [
176
+ x, 0, 0, 0,
177
+ 0, y, 0, 0,
178
+ 0, 0, z, 0,
179
+ 0, 0, 0, 1
180
+ ]
181
+ )
182
+ end
183
+
184
+ def self.perspective(fov_y, aspect_ratio, near, far)
185
+ f = 1.0 / Math.tan(fov_y.degrees_to_radians / 2.0) # cotangent
186
+ zn = (far + near.to_f) / (near - far.to_f)
187
+ zf = (2.0 * far * near.to_f) / (near - far.to_f)
188
+
189
+ Transform.new(
190
+ [
191
+ f / aspect_ratio, 0.0, 0.0, 0.0,
192
+ 0.0, f, 0.0, 0.0,
193
+ 0.0, 0.0, zn, zf,
194
+ 0.0, 0.0, -1.0, 0.0
195
+ ]
196
+ )
197
+ end
198
+
199
+ def self.view(eye, orientation)
200
+ # https://www.3dgep.com/understanding-the-view-matrix/#The_View_Matrix
201
+ cosPitch = Math.cos(orientation.z * Math::PI / 180.0)
202
+ sinPitch = Math.sin(orientation.z * Math::PI / 180.0)
203
+ cosYaw = Math.cos(orientation.y * Math::PI / 180.0)
204
+ sinYaw = Math.sin(orientation.y * Math::PI / 180.0)
205
+
206
+ x_axis = Vector.new(cosYaw, 0, -sinYaw)
207
+ y_axis = Vector.new(sinYaw * sinPitch, cosPitch, cosYaw * sinPitch)
208
+ z_axis = Vector.new(sinYaw * cosPitch, -sinPitch, cosPitch * cosYaw)
209
+
210
+ Transform.new(
211
+ [
212
+ x_axis.x, y_axis.y, z_axis.z, 0,
213
+ x_axis.x, y_axis.y, z_axis.z, 0,
214
+ x_axis.x, y_axis.y, z_axis.z, 0,
215
+ -x_axis.dot(eye), -y_axis.dot(eye), -z_axis.dot(eye), 1
216
+ ]
217
+ )
218
+ end
219
+
220
+ def *(other)
221
+
222
+ case other
223
+ when CyberarmEngine::Vector
224
+ matrix = @elements.clone
225
+ list = other.to_a
226
+
227
+ @elements.each_with_index do |e, i|
228
+ matrix[i] = e + list[i % 4]
229
+ end
230
+
231
+ Transform.new(matrix)
232
+
233
+ when CyberarmEngine::Transform
234
+ return multiply_matrices(other)
235
+ else
236
+ p other.class
237
+ raise TypeError, "Expected CyberarmEngine::Vector or CyberarmEngine::Transform got #{other.class}"
238
+ end
239
+ end
240
+
241
+ def get(x, y)
242
+ width = 4
243
+
244
+ # puts "Transform|#{self.object_id} -> #{@elements[width * y + x].inspect} (index: #{width * y + x})"
245
+ @elements[width * y + x]
246
+ end
247
+
248
+ def multiply_matrices(other)
249
+ matrix = Array.new(16, 0)
250
+
251
+ 4.times do |x|
252
+ 4.times do |y|
253
+ 4.times do |k|
254
+ matrix[4 * y + x] += get(x, k) * other.get(k, y)
255
+ end
256
+ end
257
+ end
258
+
259
+ return Transform.new(matrix)
260
+ end
261
+
262
+ # arranges Matrix in column major form
263
+ def to_gl
264
+ e = @elements
265
+ [
266
+ e[0], e[4], e[8], e[12],
267
+ e[1], e[5], e[9], e[13],
268
+ e[2], e[6], e[10], e[14],
269
+ e[3], e[7], e[11], e[15]
270
+ ]
271
+ end
272
+ end
273
273
  end