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.
- checksums.yaml +7 -0
- data/lib/gosling.rb +4 -0
- data/lib/gosling/actor.rb +235 -0
- data/lib/gosling/circle.rb +48 -0
- data/lib/gosling/collision.rb +126 -0
- data/lib/gosling/image_library.rb +17 -0
- data/lib/gosling/inheritance_error.rb +4 -0
- data/lib/gosling/polygon.rb +52 -0
- data/lib/gosling/rect.rb +38 -0
- data/lib/gosling/sprite.rb +35 -0
- data/lib/gosling/transform.rb +223 -0
- data/lib/gosling/version.rb +3 -0
- data/spec/actor_spec.rb +670 -0
- data/spec/circle_spec.rb +48 -0
- data/spec/collision_spec.rb +1183 -0
- data/spec/image_library_spec.rb +15 -0
- data/spec/images/bg2.png +0 -0
- data/spec/images/cursor.png +0 -0
- data/spec/images/display_case.png +0 -0
- data/spec/images/full_icons_07_18.png +0 -0
- data/spec/images/key.png +0 -0
- data/spec/images/nil.png +0 -0
- data/spec/polygon_spec.rb +213 -0
- data/spec/rect_spec.rb +87 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/sprite_spec.rb +43 -0
- data/spec/transform_spec.rb +328 -0
- metadata +98 -0
@@ -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
|
data/lib/gosling/rect.rb
ADDED
@@ -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
|
data/spec/actor_spec.rb
ADDED
@@ -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
|