gosling 1.0.0

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.
@@ -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