gosling 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ require_relative 'actor.rb'
2
+ require_relative 'collision.rb'
3
+
4
+ module Gosling
5
+ class Polygon < Actor
6
+ def initialize(window)
7
+ super(window)
8
+ @vertices = [
9
+ Vector[0, 0, 0],
10
+ Vector[1, 0, 0],
11
+ Vector[1, 1, 0],
12
+ Vector[0, 1, 0]
13
+ ]
14
+ end
15
+
16
+ def get_vertices
17
+ @vertices.dup
18
+ end
19
+
20
+ def set_vertices(vertices)
21
+ unless vertices.is_a?(Array) && vertices.length >= 3 && vertices.reject { |v| v.is_a?(Vector) && v.size == 3 }.empty?
22
+ raise ArgumentError.new("set_vertices() expects an array of at least three 3D Vectors")
23
+ end
24
+ @vertices.replace(vertices)
25
+ end
26
+
27
+ def get_global_vertices
28
+ tf = get_global_transform
29
+ @vertices.map { |v| Transform.transform_point(tf, v) }
30
+ end
31
+
32
+ def render(matrix)
33
+ global_vertices = @vertices.map { |v| Transform.transform_point(matrix, v) }
34
+ i = 2
35
+ while i < global_vertices.length
36
+ v0 = global_vertices[0]
37
+ v1 = global_vertices[i-1]
38
+ v2 = global_vertices[i]
39
+ @window.draw_triangle(
40
+ v0[0].to_f, v0[1].to_f, @color,
41
+ v1[0].to_f, v1[1].to_f, @color,
42
+ v2[0].to_f, v2[1].to_f, @color,
43
+ )
44
+ i += 1
45
+ end
46
+ end
47
+
48
+ def is_point_in_bounds(point)
49
+ Collision.is_point_in_shape?(point, self)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,38 @@
1
+ require_relative 'polygon.rb'
2
+
3
+ module Gosling
4
+ class Rect < Polygon
5
+ attr_reader :width, :height
6
+
7
+ def initialize(window)
8
+ super(window)
9
+ @width = 1
10
+ @height = 1
11
+ rebuild_vertices
12
+ end
13
+
14
+ def width=(val)
15
+ raise ArgumentError.new("set_width() expects a positive, non-zero number") if val <= 0
16
+ @width = val
17
+ rebuild_vertices
18
+ end
19
+
20
+ def height=(val)
21
+ raise ArgumentError.new("set_height() expects a positive, non-zero number") if val <= 0
22
+ @height = val
23
+ rebuild_vertices
24
+ end
25
+
26
+ def rebuild_vertices
27
+ vertices = [
28
+ Vector[ 0, 0, 0],
29
+ Vector[@width, 0, 0],
30
+ Vector[@width, @height, 0],
31
+ Vector[ 0, @height, 0],
32
+ ]
33
+ set_vertices(vertices)
34
+ end
35
+
36
+ private :set_vertices
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'rect.rb'
2
+
3
+ module Gosling
4
+ class Sprite < Rect
5
+ def initialize(window)
6
+ super(window)
7
+ @image = nil
8
+ @color = Gosu::Color.rgba(255, 255, 255, 255)
9
+ end
10
+
11
+ def get_image
12
+ @image
13
+ end
14
+
15
+ def set_image(image)
16
+ raise ArgumentError.new("Expected Image, but received #{image.inspect}!") unless image.is_a?(Gosu::Image)
17
+ @image = image
18
+ self.width = @image.width
19
+ self.height = @image.height
20
+ end
21
+
22
+ def render(matrix)
23
+ global_vertices = @vertices.map { |v| Transform.transform_point(matrix, v) }
24
+ @image.draw_as_quad(
25
+ global_vertices[0][0].to_f, global_vertices[0][1].to_f, @color,
26
+ global_vertices[1][0].to_f, global_vertices[1][1].to_f, @color,
27
+ global_vertices[2][0].to_f, global_vertices[2][1].to_f, @color,
28
+ global_vertices[3][0].to_f, global_vertices[3][1].to_f, @color,
29
+ 0
30
+ )
31
+ end
32
+
33
+ private :'width=', :'height='
34
+ end
35
+ end
@@ -0,0 +1,223 @@
1
+ require 'matrix'
2
+
3
+ module Gosling
4
+ class FastMatrix
5
+ @@multiply_buffer = []
6
+ @@cache = []
7
+
8
+ attr_reader :array
9
+ attr_accessor :row_count, :column_count
10
+
11
+ private
12
+
13
+ def initialize
14
+ @array = nil
15
+ reset
16
+ end
17
+
18
+ public
19
+
20
+ def self.create
21
+ if @@cache.empty?
22
+ FastMatrix.new
23
+ else
24
+ @@cache.pop.reset
25
+ end
26
+ end
27
+
28
+ def destroy
29
+ reset
30
+ @@cache.push(self)
31
+ nil
32
+ end
33
+
34
+ def reset
35
+ if @array
36
+ @array.clear
37
+ else
38
+ @array = []
39
+ end
40
+ @row_count = 0
41
+ @column_count = 0
42
+ self
43
+ end
44
+
45
+ def copy_from(matrix)
46
+ if matrix.is_a?(Matrix)
47
+ @array = matrix.to_a.flatten
48
+ @row_count = matrix.row_size
49
+ @column_count = matrix.column_size
50
+ elsif matrix.is_a?(FastMatrix)
51
+ @array = matrix.array
52
+ @row_count = matrix.row_count
53
+ @column_count = matrix.column_count
54
+ else
55
+ raise ArgumentError.new("Cannot copy from #{matrix.inspect}!")
56
+ end
57
+ self
58
+ end
59
+
60
+ def to_matrix
61
+ rows = (0...@row_count).map do |i|
62
+ @array[(@column_count * i)...(@column_count * (i + 1))]
63
+ end
64
+ Matrix.rows(rows)
65
+ end
66
+
67
+ def fast_multiply(mat2, result = nil)
68
+ raise ArgumentError.new() unless mat2.is_a?(FastMatrix) && result.is_a?(FastMatrix)
69
+ Matrix.Raise ErrDimensionMismatch if @column_count != mat2.row_count
70
+
71
+ i = 0
72
+ while i < @row_count do
73
+ j = 0
74
+ while j < mat2.column_count do
75
+ k = 0
76
+ sum = 0
77
+ while k < @column_count do
78
+ sum += @array[i * @column_count + k] * mat2.array[k * mat2.column_count + j]
79
+ k += 1
80
+ end
81
+ @@multiply_buffer[i * @column_count + j] = sum
82
+ j += 1
83
+ end
84
+ i += 1
85
+ end
86
+
87
+ result ||= FastMatrix.create
88
+ result.array.replace(@@multiply_buffer)
89
+ result.row_count = @row_count
90
+ result.column_count = mat2.column_count
91
+ result
92
+ end
93
+
94
+ def self.combine_matrices(*matrices)
95
+ raise ArgumentError.new("Transform.combine_matrices expects one or more matrices") unless matrices.reject { |m| m.is_a?(Matrix) }.empty?
96
+
97
+ fast_matrices = matrices.map { |mat| FastMatrix.create.copy_from(mat) }
98
+ result = nil
99
+ fast_matrices.each do |fast_matrix|
100
+ if result
101
+ result.fast_multiply(fast_matrix, result)
102
+ else
103
+ result = fast_matrix
104
+ end
105
+ end
106
+ result
107
+ end
108
+ end
109
+
110
+ class Transform
111
+ def self.rational_sin(r)
112
+ r = r % (2 * Math::PI)
113
+ return case r
114
+ when 0.0
115
+ 0.to_r
116
+ when Math::PI / 2
117
+ 1.to_r
118
+ when Math::PI
119
+ 0.to_r
120
+ when Math::PI * 3 / 2
121
+ -1.to_r
122
+ else
123
+ Math.sin(r).to_r
124
+ end
125
+ end
126
+
127
+ def self.rational_cos(r)
128
+ r = r % (2 * Math::PI)
129
+ return case r
130
+ when 0.0
131
+ 1.to_r
132
+ when Math::PI / 2
133
+ 0.to_r
134
+ when Math::PI
135
+ -1.to_r
136
+ when Math::PI * 3 / 2
137
+ 0.to_r
138
+ else
139
+ Math.cos(r).to_r
140
+ end
141
+ end
142
+
143
+ attr_reader :center, :scale, :rotation, :translation
144
+
145
+ def initialize
146
+ set_center(Vector[0.to_r, 0.to_r, 0.to_r])
147
+ set_scale(Vector[1.to_r, 1.to_r])
148
+ set_rotation(0)
149
+ set_translation(Vector[0.to_r, 0.to_r, 0.to_r])
150
+ end
151
+
152
+ def set_center(v)
153
+ raise ArgumentError.new("Transform.set_center() requires a length 3 vector") unless v.is_a?(Vector) && v.size == 3
154
+ @center = Vector[v[0], v[1], 0.to_r]
155
+ @center_mat = Matrix[
156
+ [1.to_r, 0.to_r, -@center[0].to_r],
157
+ [0.to_r, 1.to_r, -@center[1].to_r],
158
+ [0.to_r, 0.to_r, 1.to_r]
159
+ ]
160
+ @is_dirty = true
161
+ end
162
+
163
+ def set_scale(v)
164
+ raise ArgumentError.new("Transform.set_scale() requires a length 2 vector") unless v.is_a?(Vector) && v.size == 2
165
+ @scale = v
166
+ @scale_mat = Matrix[
167
+ [@scale[0].to_r, 0.to_r, 0.to_r],
168
+ [0.to_r, @scale[1].to_r, 0.to_r],
169
+ [0.to_r, 0.to_r, 1.to_r]
170
+ ]
171
+ @is_dirty = true
172
+ end
173
+
174
+ def set_rotation(radians)
175
+ @rotation = radians
176
+ @rotate_mat = Matrix[
177
+ [Transform.rational_cos(@rotation), Transform.rational_sin(@rotation), 0.to_r],
178
+ [-Transform.rational_sin(@rotation), Transform.rational_cos(@rotation), 0.to_r],
179
+ [0.to_r, 0.to_r, 1.to_r]
180
+ ]
181
+ @is_dirty = true
182
+ end
183
+
184
+ def set_translation(v)
185
+ raise ArgumentError.new("Transform.set_translation() requires a length 3 vector") unless v.is_a?(Vector) && v.size == 3
186
+ @translation = Vector[v[0], v[1], 0.to_r]
187
+ @translate_mat = Matrix[
188
+ [1.to_r, 0.to_r, @translation[0].to_r],
189
+ [0.to_r, 1.to_r, @translation[1].to_r],
190
+ [0.to_r, 0.to_r, 1.to_r]
191
+ ]
192
+ @is_dirty = true
193
+ end
194
+
195
+ def to_matrix
196
+ return @matrix unless @is_dirty
197
+ #~ @matrix = @translate_mat * @rotate_mat * @scale_mat * @center_mat
198
+ @matrix = FastMatrix.combine_matrices(@translate_mat, @rotate_mat, @scale_mat, @center_mat).to_matrix
199
+ @is_dirty = false
200
+ @matrix
201
+ end
202
+
203
+ def self.transform_point(mat, v)
204
+ raise ArgumentError.new("Transform.transform_point() requires a length 3 vector") unless v.is_a?(Vector) && v.size == 3
205
+ result = mat * Vector[v[0], v[1], 1.to_r]
206
+ Vector[result[0], result[1], 0.to_r]
207
+ end
208
+
209
+ def transform_point(v)
210
+ Transform.transform_point(to_matrix, v)
211
+ end
212
+
213
+ def self.untransform_point(mat, v)
214
+ raise ArgumentError.new("Transform.transform_point() requires a length 3 vector") unless v.is_a?(Vector) && v.size == 3
215
+ result = mat.inverse * Vector[v[0], v[1], 1.to_r]
216
+ Vector[result[0], result[1], 0.to_r]
217
+ end
218
+
219
+ def untransform_point(v)
220
+ Transform.untransform_point(to_matrix, v)
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,3 @@
1
+ module Gosling
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,670 @@
1
+ describe Gosling::Actor do
2
+ before(:all) do
3
+ @window = Gosu::Window.new(640, 480, false)
4
+ @read_only_actor = Gosling::Actor.new(@window)
5
+ @parent = Gosling::Actor.new(@window)
6
+ @child = Gosling::Actor.new(@window)
7
+ end
8
+
9
+ describe '#new' do
10
+ it 'requires a Gosu::Window' do
11
+ expect { Gosling::Actor.new(@window) }.not_to raise_error
12
+ expect { Gosling::Actor.new() }.to raise_error(ArgumentError)
13
+ end
14
+ end
15
+
16
+ it 'has a transform' do
17
+ expect(@read_only_actor.transform).to be_instance_of(Gosling::Transform)
18
+ end
19
+
20
+ it 'can have a parent' do
21
+ expect { @read_only_actor.parent }.not_to raise_error
22
+ end
23
+
24
+ it "does not initialize with a parent" do
25
+ expect(@read_only_actor.parent).to be == nil
26
+ end
27
+
28
+ describe '#add_child' do
29
+ it "creates a two-way parent/child link" do
30
+ @parent.add_child(@child)
31
+ expect(@parent.has_child?(@child)).to be true
32
+ expect(@child.parent).to be == @parent
33
+ end
34
+ end
35
+
36
+ describe '#remove_child' do
37
+ it "severs the two-way parent/child link" do
38
+ @parent.add_child(@child)
39
+ @parent.remove_child(@child)
40
+ expect(@parent.has_child?(@child)).to be false
41
+ expect(@child.parent).to be == nil
42
+ end
43
+ end
44
+
45
+ it 'has a list of children' do
46
+ expect(@read_only_actor.children).to be_instance_of(Array)
47
+ end
48
+
49
+ it "starts with no children" do
50
+ expect(@read_only_actor.children.empty?).to be true
51
+ end
52
+
53
+ it "knows if it has a particular child or not" do
54
+ expect(@read_only_actor.has_child?(Gosling::Actor.new(@window))).to be false
55
+ end
56
+
57
+ it "will never add any of its ancestors as children" do
58
+ @parent.add_child(@child)
59
+ expect { @child.add_child(@parent) }.to raise_error(Gosling::InheritanceError)
60
+ end
61
+
62
+ it "will not add itself as its own child" do
63
+ expect { @child.add_child(@child) }.to raise_error(Gosling::InheritanceError)
64
+ end
65
+
66
+ context "when given a child" do
67
+ before do
68
+ @parent.add_child(@child)
69
+ end
70
+
71
+ it "it forms a two-way link with that child" do
72
+ expect(@parent.has_child?(@child)).to be true
73
+ expect(@child.parent).to be == @parent
74
+ end
75
+
76
+ it "cannot be given the same child more than once" do
77
+ @parent.add_child(@child)
78
+ expect(@parent.children.length).to be == 1
79
+ end
80
+
81
+ context "and then the child is removed" do
82
+ before do
83
+ @parent.remove_child(@child)
84
+ end
85
+
86
+ it "the parent/child link is broken" do
87
+ expect(@parent.has_child?(@child)).to be false
88
+ expect(@child.parent).to be == nil
89
+ end
90
+ end
91
+
92
+ context 'and then adopted by another actor' do
93
+ it 'automatically breaks the old child/parent link' do
94
+ parent2 = Gosling::Actor.new(@window)
95
+ parent2.add_child(@child)
96
+ expect(@parent.has_child?(@child)).to be false
97
+ expect(@child.parent).to be == parent2
98
+ end
99
+ end
100
+ end
101
+
102
+ it 'has shortcut methods for getting its x/y position' do
103
+ expect(@read_only_actor.x).to be_kind_of(Numeric)
104
+ expect(@read_only_actor.y).to be_kind_of(Numeric)
105
+ end
106
+
107
+ it 'has shortcut methods for setting its x/y position' do
108
+ @read_only_actor.x = 13
109
+ @read_only_actor.y = -7
110
+ expect(@read_only_actor.x).to be == 13
111
+ expect(@read_only_actor.y).to be == -7
112
+ end
113
+
114
+ it 'has shortcut methods for getting its x/y centerpoint' do
115
+ expect(@read_only_actor.center_x).to be_kind_of(Numeric)
116
+ expect(@read_only_actor.center_y).to be_kind_of(Numeric)
117
+ end
118
+
119
+ it 'has shortcut methods for setting its x/y centerpoint' do
120
+ @read_only_actor.center_x = 5
121
+ @read_only_actor.center_y = 15
122
+ expect(@read_only_actor.center_x).to be == 5
123
+ expect(@read_only_actor.center_y).to be == 15
124
+ end
125
+
126
+ it 'has shortcut methods for getting its x/y scaling' do
127
+ expect(@read_only_actor.scale_x).to be_kind_of(Numeric)
128
+ expect(@read_only_actor.scale_y).to be_kind_of(Numeric)
129
+ end
130
+
131
+ it 'has shortcut methods for setting its x/y scaling' do
132
+ @read_only_actor.scale_x = 2
133
+ @read_only_actor.scale_y = 3
134
+ expect(@read_only_actor.scale_x).to be == 2
135
+ expect(@read_only_actor.scale_y).to be == 3
136
+ end
137
+
138
+ it 'has a shortcut method for getting its rotation' do
139
+ expect(@read_only_actor.rotation).to be_kind_of(Numeric)
140
+ end
141
+
142
+ it 'has a shortcut method for setting its rotation' do
143
+ @read_only_actor.rotation = Math::PI
144
+ expect(@read_only_actor.rotation).to be == Math::PI
145
+ end
146
+
147
+ it 'has a visibility flag' do
148
+ actor = Gosling::Actor.new(@window)
149
+ actor.is_visible = true
150
+ expect(actor.is_visible).to be true
151
+ actor.is_visible = false
152
+ expect(actor.is_visible).to be false
153
+ end
154
+
155
+ it 'has a children visibility flag' do
156
+ actor = Gosling::Actor.new(@window)
157
+ actor.are_children_visible = true
158
+ expect(actor.are_children_visible).to be true
159
+ actor.are_children_visible = false
160
+ expect(actor.are_children_visible).to be false
161
+ end
162
+
163
+ it 'has a tangible flag' do
164
+ actor = Gosling::Actor.new(@window)
165
+ actor.is_tangible = true
166
+ expect(actor.is_tangible).to be true
167
+ actor.is_tangible = false
168
+ expect(actor.is_tangible).to be false
169
+ end
170
+
171
+ it 'has a children tangibile flag' do
172
+ actor = Gosling::Actor.new(@window)
173
+ actor.are_children_tangible = true
174
+ expect(actor.are_children_tangible).to be true
175
+ actor.are_children_tangible = false
176
+ expect(actor.are_children_tangible).to be false
177
+ end
178
+
179
+ it 'has a mask flag' do
180
+ actor = Gosling::Actor.new(@window)
181
+ actor.is_mask = true
182
+ expect(actor.is_mask).to be true
183
+ actor.is_mask = false
184
+ expect(actor.is_mask).to be false
185
+ end
186
+
187
+ describe '#render' do
188
+ it 'the method exists' do
189
+ expect { @read_only_actor.render(Matrix.identity(3)) }.not_to raise_error
190
+ end
191
+ end
192
+
193
+ describe '#draw' do
194
+ before(:all) do
195
+ @draw_actor = Gosling::Actor.new(@window)
196
+ @mat = Matrix.identity(3)
197
+ end
198
+
199
+ context 'when visible' do
200
+ it 'calls render on itself' do
201
+ @draw_actor.is_visible = true
202
+ expect(@draw_actor).to receive(:render).with(@mat).once
203
+ @draw_actor.draw(@mat)
204
+ end
205
+ end
206
+
207
+ context 'when not visible' do
208
+ it 'does not call render on itself' do
209
+ @draw_actor.is_visible = false
210
+ expect(@draw_actor).not_to receive(:render)
211
+ @draw_actor.draw(@mat)
212
+ end
213
+ end
214
+
215
+ context 'with children' do
216
+ before(:all) do
217
+ @child1 = Gosling::Actor.new(@window)
218
+ @child2 = Gosling::Actor.new(@window)
219
+ @draw_actor.add_child(@child1)
220
+ @draw_actor.add_child(@child2)
221
+ end
222
+
223
+ context 'when are_children_visible is false' do
224
+ it 'draws itself, but not its children' do
225
+ @draw_actor.are_children_visible = false
226
+ expect(@child1).not_to receive(:draw)
227
+ expect(@child2).not_to receive(:draw)
228
+ @draw_actor.draw(@mat)
229
+ @draw_actor.are_children_visible = true
230
+ end
231
+ end
232
+
233
+ it 'calls draw on each of its children' do
234
+ expect(@child1).to receive(:draw).with(@mat).once
235
+ expect(@child2).to receive(:draw).with(@mat).once
236
+ @draw_actor.draw(@mat)
237
+ end
238
+
239
+ it 'passes its children a comprehensive transformation matrix' do
240
+ parameter_mat = Matrix[
241
+ [1, 0, 10],
242
+ [0, 1, 20],
243
+ [0, 0, 1]
244
+ ]
245
+ self_mat = Matrix[
246
+ [2, 0, 0],
247
+ [0, 3, 0],
248
+ [0, 0, 1]
249
+ ]
250
+ result_mat = parameter_mat * self_mat
251
+
252
+ allow(@draw_actor.transform).to receive(:to_matrix).and_return(self_mat)
253
+
254
+ expect(@child1).to receive(:draw).with(result_mat).once
255
+ expect(@child2).to receive(:draw).with(result_mat).once
256
+ @draw_actor.draw(parameter_mat)
257
+ end
258
+
259
+ after(:all) do
260
+ @draw_actor.children.each { |child| @draw_actor.remove_child(@child) }
261
+ end
262
+ end
263
+ end
264
+
265
+ describe '#is_point_in_bounds' do
266
+ it 'returns false' do
267
+ expect(@read_only_actor.is_point_in_bounds(Vector[0,0,1])).to be false
268
+ end
269
+ end
270
+
271
+ describe '#get_actor_at' do
272
+ before(:all) do
273
+ @parent = Gosling::Actor.new(@window)
274
+ end
275
+
276
+ context 'when tangible is false' do
277
+ it 'returns nil even if hit' do
278
+ @parent.is_tangible = false
279
+ allow(@parent).to receive(:is_point_in_bounds).and_return(true)
280
+ expect(@parent.get_actor_at(Vector[0,0,1])).to be == nil
281
+ @parent.is_tangible = true
282
+ end
283
+ end
284
+
285
+ context 'with no children' do
286
+ it 'returns itself if the point is within its bounds' do
287
+ allow(@parent).to receive(:is_point_in_bounds).and_return(true)
288
+ expect(@parent.get_actor_at(Vector[0,0,1])).to be == @parent
289
+ end
290
+
291
+ it 'returns nil if point is not within its bounds' do
292
+ allow(@parent).to receive(:is_point_in_bounds).and_return(false)
293
+ expect(@parent.get_actor_at(Vector[0,0,1])).to be == nil
294
+ end
295
+ end
296
+
297
+ context 'with two children' do
298
+ before(:all) do
299
+ @child1 = Gosling::Actor.new(@window)
300
+ @child2 = Gosling::Actor.new(@window)
301
+ @parent.add_child(@child1)
302
+ @parent.add_child(@child2)
303
+ end
304
+
305
+ context 'when the children are not tangible' do
306
+ it 'does not hit test any children' do
307
+ @parent.are_children_tangible = false
308
+ allow(@parent).to receive(:is_point_in_bounds).and_return(false, true)
309
+ allow(@child1).to receive(:is_point_in_bounds).and_return(true)
310
+ allow(@child2).to receive(:is_point_in_bounds).and_return(true)
311
+
312
+ expect(@parent.get_actor_at(Vector[0,0,1])).to be == nil
313
+ expect(@parent.get_actor_at(Vector[0,0,1])).to be == @parent
314
+ @parent.are_children_tangible = true
315
+ end
316
+ end
317
+
318
+ it "returns the second child if such is hit" do
319
+ allow(@parent).to receive(:is_point_in_bounds).and_return(false, true, false, true)
320
+ allow(@child1).to receive(:is_point_in_bounds).and_return(false, false, true, true)
321
+ allow(@child2).to receive(:is_point_in_bounds).and_return(true)
322
+
323
+ 4.times { expect(@parent.get_actor_at(Vector[0,0,1])).to be == @child2 }
324
+ end
325
+
326
+ it "returns the first child if the second was not hit" do
327
+ allow(@parent).to receive(:is_point_in_bounds).and_return(false, true)
328
+ allow(@child1).to receive(:is_point_in_bounds).and_return(true)
329
+ allow(@child2).to receive(:is_point_in_bounds).and_return(false)
330
+
331
+ 2.times { expect(@parent.get_actor_at(Vector[0,0,1])).to be == @child1 }
332
+ end
333
+
334
+ it "returns itself if neither child was hit" do
335
+ allow(@parent).to receive(:is_point_in_bounds).and_return(true)
336
+ allow(@child1).to receive(:is_point_in_bounds).and_return(false)
337
+ allow(@child2).to receive(:is_point_in_bounds).and_return(false)
338
+
339
+ expect(@parent.get_actor_at(Vector[0,0,1])).to be == @parent
340
+ end
341
+
342
+ it 'returns nil if point is not within it or its children' do
343
+ allow(@parent).to receive(:is_point_in_bounds).and_return(false)
344
+ allow(@child1).to receive(:is_point_in_bounds).and_return(false)
345
+ allow(@child2).to receive(:is_point_in_bounds).and_return(false)
346
+
347
+ expect(@parent.get_actor_at(Vector[0,0,1])).to be == nil
348
+ end
349
+
350
+ context 'with a mask child' do
351
+ it 'returns the parent if the child is hit' do
352
+ @child1.is_mask = true
353
+ allow(@parent).to receive(:is_point_in_bounds).and_return(false)
354
+ allow(@child1).to receive(:is_point_in_bounds).and_return(true)
355
+ allow(@child2).to receive(:is_point_in_bounds).and_return(false)
356
+
357
+ expect(@parent.get_actor_at(Vector[0,0,1])).to be == @parent
358
+ @child1.is_mask = false
359
+ end
360
+ end
361
+
362
+ after(:all) do
363
+ @parent.children.each { |child| @parent.remove_child(child) }
364
+ end
365
+ end
366
+ end
367
+
368
+ describe '#get_actors_at' do
369
+ before(:all) do
370
+ @parent = Gosling::Actor.new(@window)
371
+ end
372
+
373
+ context 'when tangible is false' do
374
+ it 'returns an empty array even if hit' do
375
+ @parent.is_tangible = false
376
+ allow(@parent).to receive(:is_point_in_bounds).and_return(true)
377
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be_empty
378
+ @parent.is_tangible = true
379
+ end
380
+ end
381
+
382
+ context 'with no children' do
383
+ it 'returns an array containing itself if the point is within its bounds' do
384
+ allow(@parent).to receive(:is_point_in_bounds).and_return(true)
385
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@parent]
386
+ end
387
+
388
+ it 'returns an empty array if point is not within its bounds' do
389
+ allow(@parent).to receive(:is_point_in_bounds).and_return(false)
390
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be_empty
391
+ end
392
+ end
393
+
394
+ context 'with two children' do
395
+ before(:all) do
396
+ @child1 = Gosling::Actor.new(@window)
397
+ @child2 = Gosling::Actor.new(@window)
398
+ @parent.add_child(@child1)
399
+ @parent.add_child(@child2)
400
+ end
401
+
402
+ context 'when the children are not tangible' do
403
+ it 'ignores the children' do
404
+ @parent.are_children_tangible = false
405
+ allow(@parent).to receive(:is_point_in_bounds).and_return(false, true)
406
+ allow(@child1).to receive(:is_point_in_bounds).and_return(true)
407
+ allow(@child2).to receive(:is_point_in_bounds).and_return(true, false)
408
+
409
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == []
410
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@parent]
411
+ @parent.are_children_tangible = true
412
+ end
413
+ end
414
+
415
+ it "returns an array containing all actors hit" do
416
+ allow(@parent).to receive(:is_point_in_bounds).and_return(false, true, false, true, false, true, false, true)
417
+ allow(@child1).to receive(:is_point_in_bounds).and_return(false, false, true, true, false, false, true, true)
418
+ allow(@child2).to receive(:is_point_in_bounds).and_return(false, false, false, false, true, true, true, true)
419
+
420
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == []
421
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@parent]
422
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@child1]
423
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@child1, @parent]
424
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@child2]
425
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@child2, @parent]
426
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@child2, @child1]
427
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@child2, @child1, @parent]
428
+ end
429
+
430
+ context 'with a mask child' do
431
+ before(:all) do
432
+ @child1.is_mask = true
433
+ end
434
+
435
+ it 'returns the parent if the child is hit' do
436
+ allow(@parent).to receive(:is_point_in_bounds).and_return(false)
437
+ allow(@child1).to receive(:is_point_in_bounds).and_return(true)
438
+ allow(@child2).to receive(:is_point_in_bounds).and_return(false)
439
+
440
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@parent]
441
+ end
442
+
443
+ it 'returns the parent only once if both children are masks and are hit' do
444
+ @child2.is_mask = true
445
+ allow(@parent).to receive(:is_point_in_bounds).and_return(false)
446
+ allow(@child1).to receive(:is_point_in_bounds).and_return(true)
447
+ allow(@child2).to receive(:is_point_in_bounds).and_return(true)
448
+
449
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@parent]
450
+ @child2.is_mask = false
451
+ end
452
+
453
+ it 'returns the parent only once if both parent and child are hit' do
454
+ allow(@parent).to receive(:is_point_in_bounds).and_return(true)
455
+ allow(@child1).to receive(:is_point_in_bounds).and_return(true)
456
+ allow(@child2).to receive(:is_point_in_bounds).and_return(false)
457
+
458
+ expect(@parent.get_actors_at(Vector[0,0,1])).to be == [@parent]
459
+ end
460
+
461
+ after(:all) do
462
+ @child1.is_mask = false
463
+ end
464
+ end
465
+
466
+ after(:all) do
467
+ @parent.children.each { |child| @parent.remove_child(child) }
468
+ end
469
+ end
470
+ end
471
+
472
+ describe '#get_global_transform' do
473
+ it 'returns a 3x3 matrix' do
474
+ result = @parent.get_global_transform
475
+ expect(result).to be_instance_of(Matrix)
476
+ expect(result.row_size).to be == 3
477
+ expect(result.column_size).to be == 3
478
+ end
479
+
480
+ it 'is a composite of its own transform plus all of its ancestors' do
481
+ centered_view = Gosling::Actor.new(@window)
482
+ centered_view.center_x = 10
483
+ centered_view.center_y = 2
484
+
485
+ scaled_view = Gosling::Actor.new(@window)
486
+ scaled_view.scale_x = 3
487
+ scaled_view.scale_y = 2
488
+
489
+ rotated_view = Gosling::Actor.new(@window)
490
+ rotated_view.rotation = Math::PI / 4
491
+
492
+ translated_view = Gosling::Actor.new(@window)
493
+ translated_view.x = -50
494
+ translated_view.y = 10
495
+
496
+ centered_view.add_child(scaled_view)
497
+ scaled_view.add_child(rotated_view)
498
+ rotated_view.add_child(translated_view)
499
+ translated_view.add_child(@parent)
500
+
501
+ expected = Matrix.identity(3) * centered_view.transform.to_matrix * scaled_view.transform.to_matrix * rotated_view.transform.to_matrix * translated_view.transform.to_matrix * @parent.transform.to_matrix
502
+
503
+ result = @parent.get_global_transform
504
+ expect(result).to be == expected
505
+
506
+ translated_view.remove_child(@parent)
507
+ end
508
+ end
509
+
510
+ describe '#get_global_position' do
511
+ it 'returns a 3d vector' do
512
+ result = @parent.get_global_position
513
+ expect(result).to be_instance_of(Vector)
514
+ expect(result.size).to be == 3
515
+ end
516
+
517
+ context 'with a long ancestry' do
518
+ before do
519
+ @parent.x = 0
520
+ @parent.y = 0
521
+ @parent.center_x = 0
522
+ @parent.center_y = 0
523
+ @parent.scale_x = 1
524
+ @parent.scale_y = 1
525
+ @parent.rotation = 0
526
+
527
+ centered_view = Gosling::Actor.new(@window)
528
+ centered_view.center_x = 10
529
+ centered_view.center_y = 2
530
+
531
+ scaled_view = Gosling::Actor.new(@window)
532
+ scaled_view.scale_x = 3
533
+ scaled_view.scale_y = 2
534
+
535
+ rotated_view = Gosling::Actor.new(@window)
536
+ rotated_view.rotation = Math::PI / 2
537
+
538
+ translated_view = Gosling::Actor.new(@window)
539
+ translated_view.x = -50
540
+ translated_view.y = 10
541
+
542
+ centered_view.add_child(scaled_view)
543
+ scaled_view.add_child(rotated_view)
544
+ rotated_view.add_child(translated_view)
545
+ translated_view.add_child(@parent)
546
+
547
+ @ancestry = [
548
+ centered_view,
549
+ scaled_view,
550
+ rotated_view,
551
+ translated_view,
552
+ @parent
553
+ ]
554
+ end
555
+
556
+ it 'respects all ancestors' do
557
+ result = @parent.get_global_position
558
+ expect(result).to be == Vector[(0 + 10) * 3 - 10, (0 - 50) * -2 - 2, 0]
559
+ end
560
+
561
+ after do
562
+ @ancestry.each { |actor| actor.parent.remove_child(actor) if actor.parent }
563
+ end
564
+ end
565
+ end
566
+
567
+ describe '#alpha' do
568
+ it 'returns the alpha (transparency) value' do
569
+ expect(@parent.alpha).to be_kind_of(Numeric)
570
+ end
571
+
572
+ it 'sets the alpha (transparency) value' do
573
+ actor = Gosling::Actor.new(@window)
574
+
575
+ actor.alpha = 128
576
+ expect(actor.alpha).to be == 128
577
+ end
578
+
579
+ it 'forces numeric values to be between 0 and 255' do
580
+ actor = Gosling::Actor.new(@window)
581
+
582
+ actor.alpha = -128
583
+ expect(actor.alpha).to be == 0
584
+ actor.alpha = -1
585
+ expect(actor.alpha).to be == 0
586
+ actor.alpha = 256
587
+ expect(actor.alpha).to be == 255
588
+ actor.alpha = 1024
589
+ expect(actor.alpha).to be == 255
590
+ end
591
+ end
592
+
593
+ describe '#red' do
594
+ it 'returns the red value' do
595
+ expect(@parent.red).to be_kind_of(Numeric)
596
+ end
597
+
598
+ it 'sets the red value' do
599
+ actor = Gosling::Actor.new(@window)
600
+
601
+ actor.red = 128
602
+ expect(actor.red).to be == 128
603
+ end
604
+
605
+ it 'forces numeric values to be between 0 and 255' do
606
+ actor = Gosling::Actor.new(@window)
607
+
608
+ actor.red = -128
609
+ expect(actor.red).to be == 0
610
+ actor.red = -1
611
+ expect(actor.red).to be == 0
612
+ actor.red = 256
613
+ expect(actor.red).to be == 255
614
+ actor.red = 1024
615
+ expect(actor.red).to be == 255
616
+ end
617
+ end
618
+
619
+ describe '#green' do
620
+ it 'returns the green value' do
621
+ expect(@parent.green).to be_kind_of(Numeric)
622
+ end
623
+
624
+ it 'sets the green value' do
625
+ actor = Gosling::Actor.new(@window)
626
+
627
+ actor.green = 128
628
+ expect(actor.green).to be == 128
629
+ end
630
+
631
+ it 'forces numeric values to be between 0 and 255' do
632
+ actor = Gosling::Actor.new(@window)
633
+
634
+ actor.green = -128
635
+ expect(actor.green).to be == 0
636
+ actor.green = -1
637
+ expect(actor.green).to be == 0
638
+ actor.green = 256
639
+ expect(actor.green).to be == 255
640
+ actor.green = 1024
641
+ expect(actor.green).to be == 255
642
+ end
643
+ end
644
+
645
+ describe '#blue' do
646
+ it 'returns the blue value' do
647
+ expect(@parent.blue).to be_kind_of(Numeric)
648
+ end
649
+
650
+ it 'sets the blue value' do
651
+ actor = Gosling::Actor.new(@window)
652
+
653
+ actor.blue = 128
654
+ expect(actor.blue).to be == 128
655
+ end
656
+
657
+ it 'forces numeric values to be between 0 and 255' do
658
+ actor = Gosling::Actor.new(@window)
659
+
660
+ actor.blue = -128
661
+ expect(actor.blue).to be == 0
662
+ actor.blue = -1
663
+ expect(actor.blue).to be == 0
664
+ actor.blue = 256
665
+ expect(actor.blue).to be == 255
666
+ actor.blue = 1024
667
+ expect(actor.blue).to be == 255
668
+ end
669
+ end
670
+ end