gosling 2.3.0 → 2.3.2

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.
data/spec/circle_spec.rb CHANGED
@@ -1,47 +1,47 @@
1
- describe Gosling::Circle do
2
- before(:all) do
3
- @window = Gosu::Window.new(640, 480, false)
4
- @circle = Gosling::Circle.new(@window)
5
- end
6
-
7
- it 'has a radius' do
8
- expect(@circle.radius).to be_kind_of(Numeric)
9
- @circle.radius = 13
10
- expect(@circle.radius).to be == 13
11
- end
12
-
13
- it 'radius must be 0 or more' do
14
- expect { @circle.radius = 0 }.not_to raise_error
15
- expect { @circle.radius = -13 }.to raise_error(ArgumentError)
16
- end
17
-
18
- describe '#get_point_at_angle' do
19
- it 'accepts an angle in radians' do
20
- expect { @circle.get_point_at_angle(Math::PI) }.not_to raise_error
21
- expect { @circle.get_point_at_angle(-1) }.not_to raise_error
22
- expect { @circle.get_point_at_angle(0) }.not_to raise_error
23
- expect { @circle.get_point_at_angle(1) }.not_to raise_error
24
-
25
- expect { @circle.get_point_at_angle('PI') }.to raise_error(ArgumentError)
26
- expect { @circle.get_point_at_angle(:foo) }.to raise_error(ArgumentError)
27
- end
28
-
29
- it 'returns a size three vector' do
30
- result = @circle.get_point_at_angle(Math::PI)
31
- expect(result).to be_instance_of(Snow::Vec3)
32
- end
33
-
34
- it 'returns a point on this circle in local-space' do
35
- @circle.radius = 7
36
-
37
- angles = (0...16).map { |x| Math::PI * x / 8 }
38
- unit_vectors = angles.map { |a| Snow::Vec3[Math.cos(a), Math.sin(a), 0] }
39
-
40
- angles.each_index do |i|
41
- angle = angles[i]
42
- unit_vector = unit_vectors[i]
43
- expect(@circle.get_point_at_angle(angle)).to be == unit_vector * @circle.radius
44
- end
45
- end
46
- end
1
+ describe Gosling::Circle do
2
+ before(:all) do
3
+ @window = Gosu::Window.new(640, 480, false)
4
+ @circle = Gosling::Circle.new(@window)
5
+ end
6
+
7
+ it 'has a radius' do
8
+ expect(@circle.radius).to be_kind_of(Numeric)
9
+ @circle.radius = 13
10
+ expect(@circle.radius).to be == 13
11
+ end
12
+
13
+ it 'radius must be 0 or more' do
14
+ expect { @circle.radius = 0 }.not_to raise_error
15
+ expect { @circle.radius = -13 }.to raise_error(ArgumentError)
16
+ end
17
+
18
+ describe '#get_point_at_angle' do
19
+ it 'accepts an angle in radians' do
20
+ expect { @circle.get_point_at_angle(Math::PI) }.not_to raise_error
21
+ expect { @circle.get_point_at_angle(-1) }.not_to raise_error
22
+ expect { @circle.get_point_at_angle(0) }.not_to raise_error
23
+ expect { @circle.get_point_at_angle(1) }.not_to raise_error
24
+
25
+ expect { @circle.get_point_at_angle('PI') }.to raise_error(ArgumentError)
26
+ expect { @circle.get_point_at_angle(:foo) }.to raise_error(ArgumentError)
27
+ end
28
+
29
+ it 'returns a size three vector' do
30
+ result = @circle.get_point_at_angle(Math::PI)
31
+ expect(result).to be_instance_of(Snow::Vec3)
32
+ end
33
+
34
+ it 'returns a point on this circle in local-space' do
35
+ @circle.radius = 7
36
+
37
+ angles = (0...16).map { |x| Math::PI * x / 8 }
38
+ unit_vectors = angles.map { |a| Snow::Vec3[Math.cos(a), Math.sin(a), 0] }
39
+
40
+ angles.each_index do |i|
41
+ angle = angles[i]
42
+ unit_vector = unit_vectors[i]
43
+ expect(@circle.get_point_at_angle(angle)).to be == unit_vector * @circle.radius
44
+ end
45
+ end
46
+ end
47
47
  end
@@ -1,1755 +1,1755 @@
1
- require 'set'
2
-
3
- module Gosling
4
- class Collision
5
- def self.collision_buffer
6
- @@collision_buffer
7
- end
8
-
9
- def self.global_vertices_cache
10
- @@global_vertices_cache
11
- end
12
-
13
- def self.global_position_cache
14
- @@global_position_cache
15
- end
16
-
17
- def self.global_transform_cache
18
- @@global_transform_cache
19
- end
20
-
21
- public_class_method :reset_separation_axes, :separation_axes
22
- end
23
- end
24
-
25
- def angle_to_vector(angle)
26
- Snow::Vec3[Math.sin(angle).round(12), Math.cos(angle).round(12), 0]
27
- end
28
-
29
- def clean_actor(actor)
30
- actor.x = 0
31
- actor.y = 0
32
- actor.scale_x = 1
33
- actor.scale_y = 1
34
- actor.rotation = 0
35
- end
36
-
37
- def clean_shape(shape)
38
- clean_actor(shape)
39
- shape.center_x = 0
40
- shape.center_y = 0
41
- end
42
-
43
- def clean_rect(rect)
44
- clean_actor(rect)
45
- rect.center_x = 5
46
- rect.center_y = 5
47
- end
48
-
49
- def clean_sprite(sprite)
50
- clean_actor(sprite)
51
- sprite.center_x = 8
52
- sprite.center_y = 8
53
- end
54
-
55
- def create_inheritance_chain(ancestry)
56
- (1...ancestry.length).each do |i|
57
- ancestry[i-1].add_child(ancestry[i])
58
- end
59
- end
60
-
61
- def break_inheritance_chain(ancestry)
62
- ancestry.each do |actor|
63
- actor.parent.remove_child(actor) if actor.parent
64
- end
65
- end
66
-
67
- ANGLE_COUNT = 8 * 4
68
-
69
- describe Gosling::Collision do
70
- FLOAT_TOLERANCE = 0.000001
71
-
72
- before(:all) do
73
- @window = Gosu::Window.new(640, 480, false)
74
- @local_path = File.dirname(__FILE__)
75
- @image = Gosling::ImageLibrary.get(File.join(@local_path, 'images/nil.png'))
76
-
77
- @actor1 = Gosling::Actor.new(@window)
78
-
79
- @actor2 = Gosling::Actor.new(@window)
80
-
81
- @circle1 = Gosling::Circle.new(@window)
82
- @circle1.radius = 5
83
-
84
- @circle2 = Gosling::Circle.new(@window)
85
- @circle2.radius = 5
86
-
87
- @polygon1 = Gosling::Polygon.new(@window)
88
- @polygon1.set_vertices([
89
- Snow::Vec3[ 0, 5, 0],
90
- Snow::Vec3[ 5, -5, 0],
91
- Snow::Vec3[-5, -5, 0]
92
- ])
93
-
94
- @polygon2 = Gosling::Polygon.new(@window)
95
- @polygon2.set_vertices([
96
- Snow::Vec3[ 0, -5, 0],
97
- Snow::Vec3[ 5, 5, 0],
98
- Snow::Vec3[-5, 5, 0]
99
- ])
100
-
101
- @rect1 = Gosling::Rect.new(@window)
102
- @rect1.width = 10
103
- @rect1.height = 10
104
- @rect1.center_x = 5
105
- @rect1.center_y = 5
106
-
107
- @rect2 = Gosling::Rect.new(@window)
108
- @rect2.width = 10
109
- @rect2.height = 10
110
- @rect2.center_x = 5
111
- @rect2.center_y = 5
112
- @rect2.rotation = Math::PI / 4
113
-
114
- @sprite1 = Gosling::Sprite.new(@window)
115
- @sprite1.set_image(@image)
116
- @sprite1.center_x = 8
117
- @sprite1.center_y = 8
118
-
119
- @sprite2 = Gosling::Sprite.new(@window)
120
- @sprite2.set_image(@image)
121
- @sprite2.center_x = 8
122
- @sprite2.center_y = 8
123
-
124
- @center_actor = Gosling::Actor.new(@window)
125
- @center_actor.center_x = 5
126
- @center_actor.center_y = 5
127
-
128
- @scale_actor = Gosling::Actor.new(@window)
129
- @scale_actor.scale_x = 3.5
130
- @scale_actor.scale_y = 2.5
131
-
132
- @rotate_actor = Gosling::Actor.new(@window)
133
- @rotate_actor.rotation = Math::PI * -0.5
134
-
135
- @translate_actor = Gosling::Actor.new(@window)
136
- @translate_actor.x = 128
137
- @translate_actor.y = 256
138
-
139
- @angles = (0...ANGLE_COUNT).map { |i| Math::PI * 2 * i / ANGLE_COUNT }
140
- end
141
-
142
- before do
143
- Gosling::Collision.reset_separation_axes
144
- end
145
-
146
- context 'any actor vs. itself' do
147
- it 'never collides' do
148
- [@actor1, @circle1, @polygon1, @rect1, @sprite1].each do |actor|
149
- expect(Gosling::Collision.test(actor, actor)).to be false
150
- result = Gosling::Collision.get_collision_info(actor, actor)
151
- expect(result[:actors]).to include(actor)
152
- expect(result[:actors].length).to eq(2)
153
- expect(result[:colliding]).to be false
154
- expect(result[:overlap]).to be nil
155
- expect(result[:penetration]).to be nil
156
- end
157
- end
158
- end
159
-
160
- context 'actor vs. anything' do
161
- it 'never collides' do
162
- pairs = [
163
- [@actor1, @actor2],
164
- [@actor1, @circle1],
165
- [@actor1, @polygon1],
166
- [@actor1, @rect1],
167
- [@actor1, @sprite1]
168
- ]
169
- pairs.each do |pair|
170
- expect(Gosling::Collision.test(*pair)).to be false
171
- result = Gosling::Collision.get_collision_info(*pair)
172
- expect(result[:actors]).to include(*pair)
173
- expect(result[:colliding]).to be false
174
- expect(result[:overlap]).to be nil
175
- expect(result[:penetration]).to be nil
176
- end
177
- end
178
- end
179
-
180
- context 'circle vs. circle' do
181
- before do
182
- clean_shape(@circle1)
183
- @circle1.x = 0
184
- @circle1.y = 0
185
-
186
- clean_shape(@circle2)
187
- @circle2.x = 5
188
- @circle2.y = 5
189
- end
190
-
191
- it 'collides if the shapes are close enough' do
192
- expect(Gosling::Collision.test(@circle1, @circle2)).to be true
193
- result = Gosling::Collision.get_collision_info(@circle1, @circle2)
194
- expect(result[:actors]).to include(@circle1, @circle2)
195
- expect(result[:colliding]).to be true
196
- expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(10 - Math.sqrt(50))
197
- expect(result[:penetration]).to eq(Snow::Vec3[1, 1, 0].normalize * result[:overlap])
198
- end
199
-
200
- it 'returns a vector that separates the shapes' do
201
- @angles.each do |r|
202
- @circle1.x = 5 + Math.sin(r) * 5
203
- @circle1.y = 5 + Math.cos(r) * 5
204
- @circle2.x = 5
205
- @circle2.y = 5
206
-
207
- result = Gosling::Collision.get_collision_info(@circle1, @circle2)
208
- @circle2.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
209
- expect(Gosling::Collision.test(@circle1, @circle2)).to be false
210
- end
211
- end
212
-
213
- it 'always returns the vector that displaces shape b away from shape a' do
214
- @circle1.y = 10
215
- result = Gosling::Collision.get_collision_info(@circle1, @circle2)
216
- expect(result[:penetration]).to eq(Snow::Vec3[1, -1, 0].normalize * result[:overlap])
217
- end
218
-
219
- it 'does not collide if the shapes are far apart' do
220
- @circle2.x = 10
221
-
222
- expect(Gosling::Collision.test(@circle1, @circle2)).to be false
223
-
224
- result = Gosling::Collision.get_collision_info(@circle1, @circle2)
225
- expect(result[:actors]).to include(@circle1, @circle2)
226
- expect(result[:colliding]).to be false
227
- expect(result[:overlap]).to be nil
228
- expect(result[:penetration]).to be nil
229
- end
230
- end
231
-
232
- context 'circle vs. polygon' do
233
- before do
234
- clean_shape(@circle1)
235
- @circle1.x = 0
236
- @circle1.y = 0
237
-
238
- clean_shape(@polygon1)
239
- @polygon1.x = 5
240
- @polygon1.y = 5
241
- end
242
-
243
- it 'collides if the shapes are close enough' do
244
- expect(Gosling::Collision.test(@circle1, @polygon1)).to be true
245
- result = Gosling::Collision.get_collision_info(@circle1, @polygon1)
246
- expect(result[:actors]).to include(@circle1, @polygon1)
247
- expect(result[:colliding]).to be true
248
- expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(5)
249
- expect(result[:penetration]).to eq(Snow::Vec3[1, 1, 0].normalize * result[:overlap])
250
- end
251
-
252
- it 'returns a vector that separates the shapes' do
253
- result = Gosling::Collision.get_collision_info(@circle1, @polygon1)
254
- @polygon1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
255
- expect(Gosling::Collision.test(@circle1, @polygon1)).to be false
256
- end
257
-
258
- it 'does not collide if the shapes are far apart' do
259
- @polygon1.x = 10
260
- @polygon1.y = 10
261
-
262
- expect(Gosling::Collision.test(@circle1, @polygon1)).to be false
263
- result = Gosling::Collision.get_collision_info(@circle1, @polygon1)
264
- expect(result[:actors]).to include(@circle1, @polygon1)
265
- expect(result[:colliding]).to be false
266
- expect(result[:overlap]).to be nil
267
- expect(result[:penetration]).to be nil
268
- end
269
- end
270
-
271
- context 'circle vs. rect' do
272
- before do
273
- clean_shape(@circle1)
274
- @circle1.x = 0
275
- @circle1.y = 0
276
-
277
- clean_rect(@rect1)
278
- @rect1.x = 5
279
- @rect1.y = 5
280
- end
281
-
282
- it 'collides if the shapes are close enough' do
283
- expect(Gosling::Collision.test(@circle1, @rect1)).to be true
284
- result = Gosling::Collision.get_collision_info(@circle1, @rect1)
285
- expect(result[:actors]).to include(@circle1, @rect1)
286
- expect(result[:colliding]).to be true
287
- expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(5)
288
- expect(result[:penetration]).to eq(Snow::Vec3[1, 1, 0].normalize * result[:overlap])
289
- end
290
-
291
- it 'returns a vector that separates the shapes' do
292
- result = Gosling::Collision.get_collision_info(@circle1, @rect1)
293
- @rect1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
294
- expect(Gosling::Collision.test(@circle1, @rect1)).to be false
295
- end
296
-
297
- it 'does not collide if the shapes are far apart' do
298
- @rect1.x = 10
299
- @rect1.y = 10
300
-
301
- expect(Gosling::Collision.test(@circle1, @rect1)).to be false
302
- result = Gosling::Collision.get_collision_info(@circle1, @rect1)
303
- expect(result[:actors]).to include(@circle1, @rect1)
304
- expect(result[:colliding]).to be false
305
- expect(result[:overlap]).to be nil
306
- expect(result[:penetration]).to be nil
307
- end
308
- end
309
-
310
- context 'circle vs. sprite' do
311
- before do
312
- clean_shape(@circle1)
313
- @circle1.x = 0
314
- @circle1.y = 0
315
-
316
- clean_sprite(@sprite1)
317
- @sprite1.x = 8
318
- @sprite1.y = 8
319
- end
320
-
321
- it 'collides if the shapes are close enough' do
322
- expect(Gosling::Collision.test(@circle1, @sprite1)).to be true
323
- result = Gosling::Collision.get_collision_info(@circle1, @sprite1)
324
- expect(result[:actors]).to include(@circle1, @sprite1)
325
- expect(result[:colliding]).to be true
326
- expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(5)
327
- expect(result[:penetration]).to eq(Snow::Vec3[1, 1, 0].normalize * result[:overlap])
328
- end
329
-
330
- it 'returns a vector that separates the shapes' do
331
- result = Gosling::Collision.get_collision_info(@circle1, @sprite1)
332
- @sprite1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
333
- expect(Gosling::Collision.test(@circle1, @sprite1)).to be false
334
- end
335
-
336
- it 'does not collide if the shapes are far apart' do
337
- @sprite1.x = 16
338
- @sprite1.y = 16
339
-
340
- expect(Gosling::Collision.test(@circle1, @sprite1)).to be false
341
- result = Gosling::Collision.get_collision_info(@circle1, @sprite1)
342
- expect(result[:actors]).to include(@circle1, @sprite1)
343
- expect(result[:colliding]).to be false
344
- expect(result[:overlap]).to be nil
345
- expect(result[:penetration]).to be nil
346
- end
347
- end
348
-
349
- context 'polygon vs. polygon' do
350
- before do
351
- clean_shape(@polygon1)
352
- @polygon1.x = 0
353
- @polygon1.y = 0
354
-
355
- clean_shape(@polygon2)
356
- @polygon2.x = 0
357
- @polygon2.y = 5
358
- end
359
-
360
- it 'collides if the shapes are close enough' do
361
- expect(Gosling::Collision.test(@polygon1, @polygon2)).to be true
362
- result = Gosling::Collision.get_collision_info(@polygon1, @polygon2)
363
- expect(result[:actors]).to include(@polygon1, @polygon2)
364
- expect(result[:colliding]).to be true
365
- axis = Snow::Vec2[-10, -5].normalize
366
- a = Snow::Vec2[0, 0].dot_product(axis)
367
- b = Snow::Vec2[0, 5].dot_product(axis)
368
- expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(a - b)
369
- expect(result[:penetration]).to eq(Snow::Vec3[-2, 1, 0].normalize * result[:overlap])
370
- end
371
-
372
- it 'returns a vector that separates the shapes' do
373
- @polygon1.x = 0
374
- @polygon1.y = 0
375
-
376
- @angles.each do |r|
377
- @polygon2.x = 0
378
- @polygon2.y = 5
379
-
380
- @polygon1.rotation = r
381
- result = Gosling::Collision.get_collision_info(@polygon1, @polygon2)
382
- @polygon2.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
383
- expect(Gosling::Collision.test(@polygon1, @polygon2)).to be false
384
- @polygon1.rotation = 0
385
-
386
- @polygon2.x = Math.sin(r)
387
- @polygon2.y = Math.cos(r)
388
- result = Gosling::Collision.get_collision_info(@polygon1, @polygon2)
389
- @polygon2.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
390
- expect(Gosling::Collision.test(@polygon1, @polygon2)).to be false
391
- end
392
- end
393
-
394
- it 'always returns the vector that displaces shape b away from shape a' do
395
- @polygon1.y = 5
396
- @polygon2.y = 0
397
- result = Gosling::Collision.get_collision_info(@polygon1, @polygon2)
398
- expect(result[:penetration]).to eq(Snow::Vec3[0, -1, 0].normalize * result[:overlap])
399
- end
400
-
401
- it 'does not collide if the shapes are far apart' do
402
- @polygon2.x = 5
403
-
404
- expect(Gosling::Collision.test(@polygon1, @polygon2)).to be false
405
- result = Gosling::Collision.get_collision_info(@polygon1, @polygon2)
406
- expect(result[:actors]).to include(@polygon1, @polygon2)
407
- expect(result[:colliding]).to be false
408
- expect(result[:overlap]).to be nil
409
- expect(result[:penetration]).to be nil
410
- end
411
- end
412
-
413
- context 'polygon vs. rect' do
414
- before do
415
- clean_shape(@polygon1)
416
- @polygon1.x = 0
417
- @polygon1.y = 0
418
-
419
- clean_rect(@rect1)
420
- @rect1.x = 5
421
- @rect1.y = 5
422
- end
423
-
424
- it 'collides if the shapes are close enough' do
425
- expect(Gosling::Collision.test(@polygon1, @rect1)).to be true
426
- result = Gosling::Collision.get_collision_info(@polygon1, @rect1)
427
- expect(result[:actors]).to include(@polygon1, @rect1)
428
- expect(result[:colliding]).to be true
429
- axis = Snow::Vec2[-10, -5].normalize
430
- a = Snow::Vec2[0, 0].dot_product(axis)
431
- b = Snow::Vec2[0, 5].dot_product(axis)
432
- expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(a - b)
433
- expect(result[:penetration]).to eq(Snow::Vec3[2, 1, 0].normalize * result[:overlap])
434
- end
435
-
436
- it 'returns a vector that separates the shapes' do
437
- result = Gosling::Collision.get_collision_info(@polygon1, @rect1)
438
- @rect1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
439
- expect(Gosling::Collision.test(@polygon1, @rect1)).to be false
440
- end
441
-
442
- it 'does not collide if the shapes are far apart' do
443
- @rect1.x = 10
444
-
445
- expect(Gosling::Collision.test(@polygon1, @rect1)).to be false
446
- result = Gosling::Collision.get_collision_info(@polygon1, @rect1)
447
- expect(result[:actors]).to include(@polygon1, @rect1)
448
- expect(result[:colliding]).to be false
449
- expect(result[:overlap]).to be nil
450
- expect(result[:penetration]).to be nil
451
- end
452
- end
453
-
454
- context 'polygon vs. sprite' do
455
- before do
456
- clean_shape(@polygon1)
457
- @polygon1.x = 0
458
- @polygon1.y = 0
459
-
460
- clean_sprite(@sprite1)
461
- @sprite1.x = 8
462
- @sprite1.y = 8
463
- end
464
-
465
- it 'collides if the shapes are close enough' do
466
- expect(Gosling::Collision.test(@polygon1, @sprite1)).to be true
467
- result = Gosling::Collision.get_collision_info(@polygon1, @sprite1)
468
- expect(result[:actors]).to include(@polygon1, @sprite1)
469
- expect(result[:colliding]).to be true
470
- axis = Snow::Vec2[-10, -5].normalize
471
- a = Snow::Vec2[0, 0].dot_product(axis)
472
- b = Snow::Vec2[0, 5].dot_product(axis)
473
- expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(a - b)
474
- expect(result[:penetration]).to eq(Snow::Vec3[2, 1, 0].normalize * result[:overlap])
475
- end
476
-
477
- it 'returns a vector that separates the shapes' do
478
- result = Gosling::Collision.get_collision_info(@polygon1, @sprite1)
479
- @sprite1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
480
- expect(Gosling::Collision.test(@polygon1, @sprite1)).to be false
481
- end
482
-
483
- it 'does not collide if the shapes are far apart' do
484
- @sprite1.x = 13
485
-
486
- expect(Gosling::Collision.test(@polygon1, @sprite1)).to be false
487
- result = Gosling::Collision.get_collision_info(@polygon1, @sprite1)
488
- expect(result[:actors]).to include(@polygon1, @sprite1)
489
- expect(result[:colliding]).to be false
490
- expect(result[:overlap]).to be nil
491
- expect(result[:penetration]).to be nil
492
- end
493
- end
494
-
495
- context 'rect vs. rect' do
496
- before do
497
- clean_rect(@rect1)
498
- @rect1.x = 0
499
- @rect1.y = 0
500
-
501
- clean_rect(@rect2)
502
- @rect2.x = 5
503
- @rect2.y = 5
504
- end
505
-
506
- it 'collides if the shapes are close enough' do
507
- expect(Gosling::Collision.test(@rect1, @rect2)).to be true
508
- result = Gosling::Collision.get_collision_info(@rect1, @rect2)
509
- expect(result[:actors]).to include(@rect1, @rect2)
510
- expect(result[:colliding]).to be true
511
- expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(5)
512
- if result[:penetration].x == 0
513
- expect(result[:penetration]).to eq(Snow::Vec3[0, 1, 0].normalize * result[:overlap])
514
- else
515
- expect(result[:penetration]).to eq(Snow::Vec3[1, 0, 0].normalize * result[:overlap])
516
- end
517
- end
518
-
519
- it 'returns a vector that separates the shapes' do
520
- result = Gosling::Collision.get_collision_info(@rect1, @rect2)
521
- @rect2.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
522
- expect(Gosling::Collision.test(@rect1, @rect2)).to be false
523
- end
524
-
525
- it 'does not collide if the shapes are far apart' do
526
- @rect2.x = 11
527
-
528
- expect(Gosling::Collision.test(@rect1, @rect2)).to be false
529
- result = Gosling::Collision.get_collision_info(@rect1, @rect2)
530
- expect(result[:actors]).to include(@rect1, @rect2)
531
- expect(result[:colliding]).to be false
532
- expect(result[:overlap]).to be nil
533
- expect(result[:penetration]).to be nil
534
- end
535
- end
536
-
537
- context 'rect vs. sprite' do
538
- before do
539
- clean_rect(@rect1)
540
- @rect1.x = 0
541
- @rect1.y = 0
542
-
543
- clean_sprite(@sprite1)
544
- @sprite1.x = 8
545
- @sprite1.y = 8
546
- end
547
-
548
- it 'collides if the shapes are close enough' do
549
- expect(Gosling::Collision.test(@rect1, @sprite1)).to be true
550
- result = Gosling::Collision.get_collision_info(@rect1, @sprite1)
551
- expect(result[:actors]).to include(@rect1, @sprite1)
552
- expect(result[:colliding]).to be true
553
- expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(5)
554
- if result[:penetration].x == 0
555
- expect(result[:penetration]).to eq(Snow::Vec3[0, 1, 0].normalize * result[:overlap])
556
- else
557
- expect(result[:penetration]).to eq(Snow::Vec3[1, 0, 0].normalize * result[:overlap])
558
- end
559
- end
560
-
561
- it 'returns a vector that separates the shapes' do
562
- result = Gosling::Collision.get_collision_info(@rect1, @sprite1)
563
- @sprite1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
564
- expect(Gosling::Collision.test(@rect1, @sprite1)).to be false
565
- end
566
-
567
- it 'does not collide if the shapes are far apart' do
568
- @sprite1.x = 16
569
-
570
- expect(Gosling::Collision.test(@rect1, @sprite1)).to be false
571
- result = Gosling::Collision.get_collision_info(@rect1, @sprite1)
572
- expect(result[:actors]).to include(@rect1, @sprite1)
573
- expect(result[:colliding]).to be false
574
- expect(result[:overlap]).to be nil
575
- expect(result[:penetration]).to be nil
576
- end
577
- end
578
-
579
- context 'sprite vs. sprite' do
580
- before do
581
- clean_sprite(@sprite1)
582
- @sprite1.x = 0
583
- @sprite1.y = 0
584
-
585
- clean_sprite(@sprite2)
586
- @sprite2.x = 8
587
- @sprite2.y = 8
588
- end
589
-
590
- it 'collides if the shapes are close enough' do
591
- expect(Gosling::Collision.test(@sprite1, @sprite2)).to be true
592
- result = Gosling::Collision.get_collision_info(@sprite1, @sprite2)
593
- expect(result[:actors]).to include(@sprite1, @sprite2)
594
- expect(result[:colliding]).to be true
595
- expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(8)
596
- if result[:penetration].x == 0
597
- expect(result[:penetration]).to eq(Snow::Vec3[0, 1, 0].normalize * result[:overlap])
598
- else
599
- expect(result[:penetration]).to eq(Snow::Vec3[1, 0, 0].normalize * result[:overlap])
600
- end
601
- end
602
-
603
- it 'returns a vector that separates the shapes' do
604
- result = Gosling::Collision.get_collision_info(@sprite1, @sprite2)
605
- @sprite2.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
606
- expect(Gosling::Collision.test(@sprite1, @sprite2)).to be false
607
- end
608
-
609
- it 'does not collide if the shapes are far apart' do
610
- @sprite2.x = 17
611
-
612
- expect(Gosling::Collision.test(@sprite1, @sprite2)).to be false
613
- result = Gosling::Collision.get_collision_info(@sprite1, @sprite2)
614
- expect(result[:actors]).to include(@sprite1, @sprite2)
615
- expect(result[:colliding]).to be false
616
- expect(result[:overlap]).to be nil
617
- expect(result[:penetration]).to be nil
618
- end
619
- end
620
-
621
- describe '.is_point_in_shape?' do
622
- it 'expects a point and an actor' do
623
- expect { Gosling::Collision.is_point_in_shape?(Snow::Vec3[0, 0, 0], @actor1) }.not_to raise_error
624
-
625
- expect { Gosling::Collision.is_point_in_shape?(@actor1, Snow::Vec3[0, 0, 0]) }.to raise_error(ArgumentError)
626
- expect { Gosling::Collision.is_point_in_shape?(@actor1, :foo) }.to raise_error(ArgumentError)
627
- expect { Gosling::Collision.is_point_in_shape?(:bar, Snow::Vec3[0, 0, 0]) }.to raise_error(ArgumentError)
628
- expect { Gosling::Collision.is_point_in_shape?() }.to raise_error(ArgumentError)
629
- end
630
-
631
- context 'point vs. actor' do
632
- it 'never collides' do
633
- expect(Gosling::Collision.is_point_in_shape?(Snow::Vec3[0, 0, 0], @actor1)).to be false
634
- end
635
- end
636
-
637
- context 'point vs. circle' do
638
- before do
639
- clean_shape(@circle1)
640
- end
641
-
642
- it 'returns true if point is in shape' do
643
- points = [
644
- Snow::Vec3[0, 0, 0],
645
- Snow::Vec3[4, 0, 0],
646
- Snow::Vec3[-4, 0, 0],
647
- Snow::Vec3[0, 4, 0],
648
- Snow::Vec3[0, -4, 0],
649
- Snow::Vec3[5, 0, 0],
650
- Snow::Vec3[-5, 0, 0],
651
- Snow::Vec3[0, 5, 0],
652
- Snow::Vec3[0, -5, 0],
653
- ]
654
- points.each do |p|
655
- expect(Gosling::Collision.is_point_in_shape?(p, @circle1)).to be true
656
- end
657
- end
658
-
659
- it 'returns false if point is not in shape' do
660
- points = [
661
- Snow::Vec3[6, 0, 0],
662
- Snow::Vec3[-6, 0, 0],
663
- Snow::Vec3[0, 6, 0],
664
- Snow::Vec3[0, -6, 0],
665
- Snow::Vec3[4, 4, 0],
666
- Snow::Vec3[-4, 4, 0],
667
- Snow::Vec3[-4, -4, 0],
668
- Snow::Vec3[4, -4, 0],
669
- ]
670
- points.each do |p|
671
- expect(Gosling::Collision.is_point_in_shape?(p, @circle1)).to be false
672
- end
673
- end
674
- end
675
-
676
- context 'point vs. polygon' do
677
- before do
678
- clean_shape(@polygon1)
679
- end
680
-
681
- it 'returns true if point is in shape' do
682
- points = [
683
- Snow::Vec3[0, 0, 0],
684
- Snow::Vec3[0, 4, 0],
685
- Snow::Vec3[0, -4, 0],
686
- Snow::Vec3[4, -4, 0],
687
- Snow::Vec3[-4, -4, 0],
688
- Snow::Vec3[0, 5, 0],
689
- Snow::Vec3[0, -5, 0],
690
- Snow::Vec3[5, -5, 0],
691
- Snow::Vec3[-5, -5, 0],
692
- ]
693
- points.each do |p|
694
- expect(Gosling::Collision.is_point_in_shape?(p, @polygon1)).to be true
695
- end
696
- end
697
-
698
- it 'returns false if point is not in shape' do
699
- points = [
700
- Snow::Vec3[0, 6, 0],
701
- Snow::Vec3[0, -6, 0],
702
- Snow::Vec3[6, -6, 0],
703
- Snow::Vec3[-6, -6, 0],
704
- Snow::Vec3[4, 4, 0],
705
- Snow::Vec3[-4, 4, 0],
706
- ]
707
- points.each do |p|
708
- expect(Gosling::Collision.is_point_in_shape?(p, @polygon1)).to be false
709
- end
710
- end
711
- end
712
-
713
- context 'point vs. rect' do
714
- before do
715
- clean_rect(@rect1)
716
- end
717
-
718
- it 'returns true if point is in shape' do
719
- points = [
720
- Snow::Vec3[0, 0, 0],
721
- Snow::Vec3[-4, -4, 0],
722
- Snow::Vec3[0, -4, 0],
723
- Snow::Vec3[4, -4, 0],
724
- Snow::Vec3[4, 0, 0],
725
- Snow::Vec3[4, 4, 0],
726
- Snow::Vec3[0, 4, 0],
727
- Snow::Vec3[-4, 4, 0],
728
- Snow::Vec3[-4, 0, 0],
729
- Snow::Vec3[-5, -5, 0],
730
- Snow::Vec3[0, -5, 0],
731
- Snow::Vec3[5, -5, 0],
732
- Snow::Vec3[5, 0, 0],
733
- Snow::Vec3[5, 5, 0],
734
- Snow::Vec3[0, 5, 0],
735
- Snow::Vec3[-5, 5, 0],
736
- Snow::Vec3[-5, 0, 0],
737
- ]
738
- points.each do |p|
739
- expect(Gosling::Collision.is_point_in_shape?(p, @rect1)).to be true
740
- end
741
- end
742
-
743
- it 'returns false if point is not in shape' do
744
- points = [
745
- Snow::Vec3[-6, -6, 0],
746
- Snow::Vec3[0, -6, 0],
747
- Snow::Vec3[6, -6, 0],
748
- Snow::Vec3[6, 0, 0],
749
- Snow::Vec3[6, 6, 0],
750
- Snow::Vec3[0, 6, 0],
751
- Snow::Vec3[-6, 6, 0],
752
- Snow::Vec3[-6, 0, 0],
753
- ]
754
- points.each do |p|
755
- expect(Gosling::Collision.is_point_in_shape?(p, @rect1)).to be false
756
- end
757
- end
758
- end
759
-
760
- context 'point vs. sprite' do
761
- before do
762
- clean_sprite(@sprite1)
763
- end
764
-
765
- it 'returns true if point is in shape' do
766
- points = [
767
- Snow::Vec3[0, 0, 0],
768
- Snow::Vec3[-7, -7, 0],
769
- Snow::Vec3[0, -7, 0],
770
- Snow::Vec3[7, -7, 0],
771
- Snow::Vec3[7, 0, 0],
772
- Snow::Vec3[7, 7, 0],
773
- Snow::Vec3[0, 7, 0],
774
- Snow::Vec3[-7, 7, 0],
775
- Snow::Vec3[-7, 0, 0],
776
- Snow::Vec3[-8, -8, 0],
777
- Snow::Vec3[0, -8, 0],
778
- Snow::Vec3[8, -8, 0],
779
- Snow::Vec3[8, 0, 0],
780
- Snow::Vec3[8, 8, 0],
781
- Snow::Vec3[0, 8, 0],
782
- Snow::Vec3[-8, 8, 0],
783
- Snow::Vec3[-8, 0, 0],
784
- ]
785
- points.each do |p|
786
- expect(Gosling::Collision.is_point_in_shape?(Snow::Vec3[-8, 0, 0], @sprite1)).to be true
787
- end
788
- end
789
-
790
- it 'returns false if point is not in shape' do
791
- points = [
792
- Snow::Vec3[-9, -9, 0],
793
- Snow::Vec3[0, -9, 0],
794
- Snow::Vec3[9, -9, 0],
795
- Snow::Vec3[9, 0, 0],
796
- Snow::Vec3[9, 9, 0],
797
- Snow::Vec3[0, 9, 0],
798
- Snow::Vec3[-9, 9, 0],
799
- Snow::Vec3[-9, 0, 0],
800
- ]
801
- points.each do |p|
802
- expect(Gosling::Collision.is_point_in_shape?(Snow::Vec3[-9, 0, 0], @sprite1)).to be false
803
- end
804
- end
805
- end
806
- end
807
-
808
- describe '.get_normal' do
809
- it 'expects a 3d vector' do
810
- expect { Gosling::Collision.get_normal(Snow::Vec3[1, 0, 0]) }.not_to raise_error
811
- expect { Gosling::Collision.get_normal(Snow::Vec3[1, 0, 1, 0]) }.to raise_error(ArgumentError)
812
- expect { Gosling::Collision.get_normal(Snow::Vec3[1, 0]) }.to raise_error(ArgumentError)
813
- expect { Gosling::Collision.get_normal(:foo) }.to raise_error
814
- expect { Gosling::Collision.get_normal(nil) }.to raise_error
815
- end
816
-
817
- it 'returns a 3d vector' do
818
- result = Gosling::Collision.get_normal(Snow::Vec3[1, 0, 0])
819
- expect(result).to be_instance_of(Snow::Vec3)
820
- end
821
-
822
- it 'z value of returned vector is always 0' do
823
- [
824
- Snow::Vec3[1, 1, 0],
825
- Snow::Vec3[-1, -1, -1],
826
- Snow::Vec3[-22, -22, 0],
827
- Snow::Vec3[-11, 13, 34],
828
- Snow::Vec3[37, -4, -15],
829
- Snow::Vec3[34, 39, -16],
830
- Snow::Vec3[-48, 23, -32],
831
- Snow::Vec3[24, -39, 42],
832
- Snow::Vec3[49, 44, -15],
833
- Snow::Vec3[27, 23, 42],
834
- Snow::Vec3[33, -25, -20],
835
- Snow::Vec3[-46, -18, 48],
836
- ].each do |v|
837
- expect(Gosling::Collision.get_normal(v)[2]).to be == 0
838
- end
839
- end
840
-
841
- it 'raises an error when given a zero length vector' do
842
- expect { Gosling::Collision.get_normal(Snow::Vec3[0, 0, 0]) }.to raise_error(ArgumentError)
843
- end
844
-
845
- it 'returns a vector that is +/- 90 degrees from the original' do
846
- [
847
- Snow::Vec3[1, 1, 0],
848
- Snow::Vec3[-1, -1, -1],
849
- Snow::Vec3[-22, -22, 0],
850
- Snow::Vec3[-11, 13, 34],
851
- Snow::Vec3[37, -4, -15],
852
- Snow::Vec3[34, 39, -16],
853
- Snow::Vec3[-48, 23, -32],
854
- Snow::Vec3[24, -39, 42],
855
- Snow::Vec3[49, 44, -15],
856
- Snow::Vec3[27, 23, 42],
857
- Snow::Vec3[33, -25, -20],
858
- Snow::Vec3[-46, -18, 48],
859
- ].each do |v|
860
- norm_v = Gosling::Collision.get_normal(v)
861
- radians = Math.acos(v.dot_product(norm_v) / (v.magnitude * norm_v.magnitude))
862
- expect(radians.abs).to be == Math::PI / 2
863
- end
864
- end
865
- end
866
-
867
- describe '.get_polygon_separation_axes' do
868
- it 'expects an array of length 3 vectors' do
869
- good_vector_array = [
870
- Snow::Vec3[3, 1, 0],
871
- Snow::Vec3[4, 2, 0],
872
- Snow::Vec3[5, 3, 0],
873
- Snow::Vec3[1, 4, 0],
874
- Snow::Vec3[2, 5, 0]
875
- ]
876
- bad_vector_array = [
877
- Snow::Vec2[9, 11],
878
- Snow::Vec3[7, 12, 0],
879
- Snow::Vec4[5, 13, 1, 0],
880
- Snow::Vec2[3, 14],
881
- Snow::Vec2[1, 15]
882
- ]
883
- p = Gosling::Polygon.new(@window)
884
- expect { Gosling::Collision.get_polygon_separation_axes(good_vector_array) }.not_to raise_error
885
- expect { Gosling::Collision.get_polygon_separation_axes(bad_vector_array) }.to raise_error
886
- expect { Gosling::Collision.get_polygon_separation_axes(p.get_vertices) }.not_to raise_error
887
- expect { Gosling::Collision.get_polygon_separation_axes(p) }.to raise_error
888
- expect { Gosling::Collision.get_polygon_separation_axes(:foo) }.to raise_error
889
- end
890
-
891
- it 'returns an array of 3d vectors' do
892
- vertices = [
893
- Snow::Vec3[3, 1, 0],
894
- Snow::Vec3[4, 2, 0],
895
- Snow::Vec3[5, 3, 0],
896
- Snow::Vec3[1, 4, 0],
897
- Snow::Vec3[2, 5, 0]
898
- ]
899
- Gosling::Collision.get_polygon_separation_axes(vertices)
900
- result = Gosling::Collision.separation_axes
901
- expect(result).to be_instance_of(Array)
902
- expect(result.reject { |v| v.is_a?(Snow::Vec3) }).to be_empty
903
- end
904
-
905
- it 'skips length zero sides' do
906
- vertices = [
907
- Snow::Vec3[1, 1, 0],
908
- Snow::Vec3[1, 1, 0],
909
- Snow::Vec3[1, 2, 0],
910
- Snow::Vec3[2, 2, 0],
911
- Snow::Vec3[2, 2, 0]
912
- ]
913
- Gosling::Collision.get_polygon_separation_axes(vertices)
914
- result = Gosling::Collision.separation_axes
915
- expect(result.length).to be == 3
916
- end
917
-
918
- it 'returns correct values' do
919
- vertices = [
920
- Snow::Vec3[ 2, 1, 0],
921
- Snow::Vec3[ 1, -1, 0],
922
- Snow::Vec3[ 0, -2, 0],
923
- Snow::Vec3[-1, -1, 0],
924
- Snow::Vec3[-1, 2, 0]
925
- ]
926
- Gosling::Collision.get_polygon_separation_axes(vertices)
927
- result = Gosling::Collision.separation_axes
928
- expect(result).to match_array([
929
- Snow::Vec3[ 1, 3, 0].normalize,
930
- Snow::Vec3[ 2, -1, 0].normalize,
931
- Snow::Vec3[ 1, -1, 0].normalize,
932
- Snow::Vec3[-1, -1, 0].normalize,
933
- Snow::Vec3[-3, 0, 0].normalize
934
- ])
935
- end
936
- end
937
-
938
- describe '.get_circle_separation_axis' do
939
- before do
940
- clean_shape(@circle1)
941
- clean_shape(@circle2)
942
- end
943
-
944
- it 'expects two shape arguments' do
945
- expect { Gosling::Collision.get_circle_separation_axis(@circle1, @circle2) }.not_to raise_error
946
- expect { Gosling::Collision.get_circle_separation_axis(@circle1, @polygon1) }.not_to raise_error
947
- expect { Gosling::Collision.get_circle_separation_axis(@rect1, @circle2) }.not_to raise_error
948
-
949
- expect { Gosling::Collision.get_circle_separation_axis(@circle1, @circle2, Snow::Vec3.new) }.to raise_error(ArgumentError)
950
- expect { Gosling::Collision.get_circle_separation_axis(:foo, @circle2) }.to raise_error
951
- expect { Gosling::Collision.get_circle_separation_axis(@circle1) }.to raise_error(ArgumentError)
952
- expect { Gosling::Collision.get_circle_separation_axis() }.to raise_error(ArgumentError)
953
- expect { Gosling::Collision.get_circle_separation_axis(:foo) }.to raise_error(ArgumentError)
954
- end
955
-
956
- it 'returns a 3d vector' do
957
- @circle1.x = 0
958
- @circle1.y = 0
959
-
960
- @circle2.x = 10
961
- @circle2.y = -5
962
-
963
- Gosling::Collision.get_circle_separation_axis(@circle1, @circle2)
964
- result = Gosling::Collision.separation_axes
965
- expect(result.length).to eq(1)
966
- expect(result.first).to be_instance_of(Snow::Vec3)
967
- end
968
-
969
- it "returns nil if distance beween shape centers is 0" do
970
- @circle1.x = 0
971
- @circle1.y = 0
972
-
973
- @circle2.x = 0
974
- @circle2.y = 0
975
-
976
- result = Gosling::Collision.get_circle_separation_axis(@circle1, @circle2)
977
- expect(result).to be nil
978
- end
979
-
980
- it 'returns a correct unit vector' do
981
- @circle1.x = 5
982
- @circle1.y = -10
983
-
984
- @circle2.x = 10
985
- @circle2.y = -5
986
-
987
- Gosling::Collision.get_circle_separation_axis(@circle1, @circle2)
988
- result = Gosling::Collision.separation_axes
989
- expect(result.length).to eq(1)
990
- expect(result.first).to be == Snow::Vec3[1, 1, 0].normalize
991
- end
992
- end
993
-
994
- describe '.get_separation_axes' do
995
- it 'expects two shapes' do
996
- expect { Gosling::Collision.get_separation_axes(@circle1, @circle2) }.not_to raise_error
997
- expect { Gosling::Collision.get_separation_axes(@circle1, @polygon2) }.not_to raise_error
998
- expect { Gosling::Collision.get_separation_axes(@polygon1, @polygon2) }.not_to raise_error
999
- expect { Gosling::Collision.get_separation_axes(@polygon1, @rect2) }.not_to raise_error
1000
- expect { Gosling::Collision.get_separation_axes(@sprite1, @polygon2) }.not_to raise_error
1001
-
1002
- expect { Gosling::Collision.get_separation_axes(@actor1, @circle2) }.to raise_error(ArgumentError)
1003
- expect { Gosling::Collision.get_separation_axes(@circle1, @circle2, @polygon2) }.to raise_error
1004
- expect { Gosling::Collision.get_separation_axes(@circle1, 1) }.to raise_error(ArgumentError)
1005
- expect { Gosling::Collision.get_separation_axes(@polygon1, :foo) }.to raise_error(ArgumentError)
1006
- expect { Gosling::Collision.get_separation_axes(:foo) }.to raise_error(ArgumentError)
1007
- expect { Gosling::Collision.get_separation_axes() }.to raise_error(ArgumentError)
1008
- end
1009
-
1010
- it 'returns an array of 3d vectors' do
1011
- Gosling::Collision.get_separation_axes(@polygon1, @polygon2)
1012
- result = Gosling::Collision.separation_axes
1013
- expect(result).to be_instance_of(Array)
1014
- expect(result.reject { |v| v.is_a?(Snow::Vec3) }).to be_empty
1015
- end
1016
-
1017
- it 'returns only unit vectors' do
1018
- Gosling::Collision.get_separation_axes(@polygon1, @circle1)
1019
- result = Gosling::Collision.separation_axes
1020
- expect(result).to be_instance_of(Array)
1021
- result.each do |v|
1022
- expect(v).to be_instance_of(Snow::Vec3)
1023
- expect(v.magnitude).to be_between(0.99999999, 1.00000001)
1024
- end
1025
- end
1026
-
1027
- it 'returns only unique vectors' do
1028
- Gosling::Collision.get_separation_axes(@rect2, @polygon2)
1029
- result = Gosling::Collision.separation_axes
1030
- expect(result).to be_instance_of(Array)
1031
- expect(result.uniq.length).to be == result.length
1032
- end
1033
-
1034
- it 'is commutative' do
1035
- Gosling::Collision.get_separation_axes(@rect2, @polygon2)
1036
- result1 = Gosling::Collision.separation_axes.dup
1037
- Gosling::Collision.get_separation_axes(@polygon2, @rect2)
1038
- result2 = Gosling::Collision.separation_axes.dup
1039
- expect(result1.map { |v| v.to_s }).to match_array(result2.map { |v| v.to_s })
1040
- end
1041
-
1042
- it 'respects centering' do
1043
- clean_shape(@polygon1)
1044
- @polygon1.center_x = 10
1045
- @polygon1.center_y = 2
1046
-
1047
- clean_shape(@circle1)
1048
-
1049
- Gosling::Collision.get_separation_axes(@polygon1, @circle1)
1050
- result = Gosling::Collision.separation_axes
1051
- expect(result).to match_array([
1052
- Snow::Vec3[10, 5, 0].normalize,
1053
- Snow::Vec3[0, -10, 0].normalize,
1054
- Snow::Vec3[-10, 5, 0].normalize
1055
- ])
1056
- end
1057
-
1058
- it 'respects scaling' do
1059
- clean_shape(@polygon1)
1060
- @polygon1.scale_x = 3
1061
- @polygon1.scale_y = 2
1062
-
1063
- clean_shape(@circle1)
1064
-
1065
- Gosling::Collision.get_separation_axes(@polygon1, @circle1)
1066
- result = Gosling::Collision.separation_axes
1067
- expect(result).to match_array([
1068
- Snow::Vec3[20, 15, 0].normalize,
1069
- Snow::Vec3[0, -30, 0].normalize,
1070
- Snow::Vec3[-20, 15, 0].normalize
1071
- ])
1072
- end
1073
-
1074
- it 'respects rotation' do
1075
- clean_shape(@polygon1)
1076
- @polygon1.rotation = Math::PI / 2
1077
- Gosling::Collision.get_separation_axes(@polygon1, @circle1)
1078
- result = Gosling::Collision.separation_axes
1079
-
1080
- clean_shape(@circle1)
1081
-
1082
- expect(result).to match_array([
1083
- Snow::Vec3[5, -10, 0].normalize,
1084
- Snow::Vec3[-10, 0, 0].normalize,
1085
- Snow::Vec3[5, 10, 0].normalize
1086
- ])
1087
- end
1088
-
1089
- it 'respects translation' do
1090
- clean_shape(@polygon1)
1091
- @polygon1.x = -50
1092
- @polygon1.y = 10
1093
-
1094
- clean_shape(@circle1)
1095
-
1096
- Gosling::Collision.get_separation_axes(@polygon1, @circle1)
1097
- result = Gosling::Collision.separation_axes
1098
- expect(result).to match_array([
1099
- Snow::Vec3[10, 5, 0].normalize,
1100
- Snow::Vec3[0, -10, 0].normalize,
1101
- Snow::Vec3[-10, 5, 0].normalize,
1102
- Snow::Vec3[50, -10, 0].normalize
1103
- ])
1104
- end
1105
-
1106
- context 'with two polygons' do
1107
- it 'returns an array with no more axes than total vertices, and no less than two' do
1108
- [
1109
- [@polygon1, @polygon2],
1110
- [@polygon1, @rect1],
1111
- [@polygon1, @rect2],
1112
- [@polygon1, @sprite1],
1113
- [@polygon1, @sprite2],
1114
- [@polygon2, @rect1],
1115
- [@polygon2, @rect2],
1116
- [@polygon2, @sprite1],
1117
- [@polygon2, @sprite2],
1118
- [@rect1, @rect2],
1119
- [@rect1, @sprite1],
1120
- [@rect1, @sprite2],
1121
- [@rect2, @sprite1],
1122
- [@rect2, @sprite2],
1123
- [@sprite1, @sprite2]
1124
- ].each do |shapes|
1125
- Gosling::Collision.get_separation_axes(*shapes)
1126
- result = Gosling::Collision.separation_axes
1127
- vertex_count = 0
1128
- shapes.each { |s| vertex_count += s.get_vertices.length }
1129
- expect(result.length).to be_between(2, vertex_count).inclusive
1130
- end
1131
- end
1132
- end
1133
-
1134
- context 'with two circles' do
1135
- context 'when both circles have the same center' do
1136
- it 'returns an empty array' do
1137
- @circle1.x = 0
1138
- @circle1.y = 0
1139
- @circle2.x = 0
1140
- @circle2.y = 0
1141
- Gosling::Collision.get_separation_axes(@circle1, @circle2)
1142
- result = Gosling::Collision.separation_axes
1143
- expect(result).to be_instance_of(Array)
1144
- expect(result).to be_empty
1145
- end
1146
- end
1147
-
1148
- it 'returns an array with one axis' do
1149
- @circle1.x = 1
1150
- @circle1.y = 0
1151
- @circle2.x = 17
1152
- @circle2.y = -5
1153
- Gosling::Collision.get_separation_axes(@circle1, @circle2)
1154
- result = Gosling::Collision.separation_axes
1155
- expect(result).to be_instance_of(Array)
1156
- expect(result.length).to be == 1
1157
- end
1158
- end
1159
-
1160
- context 'with a polygon and a circle' do
1161
- it 'returns an array with no more axes than total vertices plus one, and no less than two' do
1162
- [
1163
- [@circle1, @polygon1],
1164
- [@circle2, @polygon2],
1165
- [@circle1, @rect1],
1166
- [@circle2, @rect2],
1167
- [@circle1, @sprite1],
1168
- [@circle2, @sprite2]
1169
- ].each do |shapes|
1170
- Gosling::Collision.get_separation_axes(*shapes)
1171
- result = Gosling::Collision.separation_axes
1172
- vertex_count = shapes[1].get_vertices.length
1173
- expect(result.length).to be_between(2, vertex_count + 1).inclusive
1174
- end
1175
- end
1176
- end
1177
- end
1178
-
1179
- describe '.project_onto_axis' do
1180
- it 'expects a shape and a 3d or better unit vector' do
1181
- axis = Snow::Vec3[1, 1, 0]
1182
-
1183
- expect { Gosling::Collision.project_onto_axis(@sprite1, axis) }.not_to raise_error
1184
- expect { Gosling::Collision.project_onto_axis(@rect1, axis) }.not_to raise_error
1185
- expect { Gosling::Collision.project_onto_axis(@circle1, axis) }.not_to raise_error
1186
- expect { Gosling::Collision.project_onto_axis(@polygon1, axis) }.not_to raise_error
1187
-
1188
- expect { Gosling::Collision.project_onto_axis(:foo, axis) }.to raise_error
1189
- expect { Gosling::Collision.project_onto_axis(@sprite1, Snow::Vec4[1, 1, 0, 2]) }.not_to raise_error
1190
- expect { Gosling::Collision.project_onto_axis(@rect1, Snow::Vec2[1, 1]) }.to raise_error(ArgumentError)
1191
- expect { Gosling::Collision.project_onto_axis(@polygon1, :foo) }.to raise_error(ArgumentError)
1192
- expect { Gosling::Collision.project_onto_axis(@circle1, @circle1, axis) }.to raise_error
1193
- expect { Gosling::Collision.project_onto_axis() }.to raise_error(ArgumentError)
1194
- end
1195
-
1196
- it 'returns an array of two numbers' do
1197
- axis = Snow::Vec3[1, 1, 0]
1198
- result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1199
- expect(result).to be_instance_of(Array)
1200
- expect(result.length).to be == 2
1201
- expect(result.reject { |x| x.is_a?(Numeric) }).to be_empty
1202
- end
1203
-
1204
- it 'works with four dimensional axes' do
1205
- clean_shape(@circle1)
1206
- @circle1.x = 1
1207
- @circle1.y = 1
1208
- @circle1.radius = 5
1209
-
1210
- axis = Snow::Vec4[-1, 1, 0, 0].normalize
1211
- result = Gosling::Collision.project_onto_axis(@circle1, axis)
1212
- expect(result).to be == [-5, 5]
1213
-
1214
- axis.z = 2
1215
- axis.w = -3
1216
- result = Gosling::Collision.project_onto_axis(@circle1, axis)
1217
- expect(result).to be == [-5, 5]
1218
- end
1219
-
1220
- context 'with a circle' do
1221
- before do
1222
- clean_shape(@circle1)
1223
- end
1224
-
1225
- it 'returns expected values' do
1226
- @circle1.x = 0
1227
- @circle1.y = 0
1228
- @circle1.radius = 5
1229
-
1230
- axis = Snow::Vec3[-1, 1, 0].normalize
1231
- result = Gosling::Collision.project_onto_axis(@circle1, axis)
1232
- expect(result).to be == [-5, 5]
1233
-
1234
- clean_shape(@circle1)
1235
- @circle1.x = 5
1236
- @circle1.y = 0
1237
- @circle1.radius = 5
1238
-
1239
- axis = Snow::Vec3[1, 0, 0].normalize
1240
- result = Gosling::Collision.project_onto_axis(@circle1, axis)
1241
- expect(result).to be == [0, 10]
1242
- end
1243
-
1244
- it 'respects centering' do
1245
- @circle1.center_x = 3
1246
- @circle1.center_y = 6
1247
-
1248
- axis = Snow::Vec3[1, 0, 0].normalize
1249
- result = Gosling::Collision.project_onto_axis(@circle1, axis)
1250
- expect(result).to be == [-8, 2]
1251
-
1252
- axis = Snow::Vec3[0, 1, 0].normalize
1253
- result = Gosling::Collision.project_onto_axis(@circle1, axis)
1254
- expect(result).to be == [-11, -1]
1255
- end
1256
-
1257
- it 'respects scaling' do
1258
- @circle1.scale_x = 2
1259
- @circle1.scale_y = 0.5
1260
-
1261
- axis = Snow::Vec3[1, 0, 0].normalize
1262
- result = Gosling::Collision.project_onto_axis(@circle1, axis)
1263
- expect(result).to be == [-10, 10]
1264
-
1265
- axis = Snow::Vec3[0, 1, 0].normalize
1266
- result = Gosling::Collision.project_onto_axis(@circle1, axis)
1267
- expect(result).to be == [-2.5, 2.5]
1268
- end
1269
-
1270
- it 'respects rotation' do
1271
- @circle1.rotation = Math::PI
1272
-
1273
- axis = Snow::Vec3[1, 0, 0].normalize
1274
- result = Gosling::Collision.project_onto_axis(@circle1, axis)
1275
- expect(result).to be == [-5, 5]
1276
- end
1277
-
1278
- it 'respects translation' do
1279
- @circle1.x = -12
1280
- @circle1.y = 23
1281
-
1282
- axis = Snow::Vec3[1, 0, 0].normalize
1283
- result = Gosling::Collision.project_onto_axis(@circle1, axis)
1284
- expect(result).to be == [-17, -7]
1285
-
1286
- axis = Snow::Vec3[0, 1, 0].normalize
1287
- result = Gosling::Collision.project_onto_axis(@circle1, axis)
1288
- expect(result).to be == [18, 28]
1289
- end
1290
-
1291
- it 'respects its entire ancestry of transforms' do
1292
- circle = Gosling::Circle.new(@window)
1293
- clean_shape(circle)
1294
- circle.radius = 10
1295
-
1296
- create_inheritance_chain([@center_actor, @scale_actor, @rotate_actor, @translate_actor, circle])
1297
-
1298
- axis = Snow::Vec3[1, 0, 0].normalize
1299
- result = Gosling::Collision.project_onto_axis(circle, axis)
1300
- expect(result).to be == [-936.0, -866.0]
1301
-
1302
- axis = Snow::Vec3[0, 1, 0].normalize
1303
- result = Gosling::Collision.project_onto_axis(circle, axis)
1304
- expect(result[0]).to be_within(FLOAT_TOLERANCE).of(290.0)
1305
- expect(result[1]).to be_within(FLOAT_TOLERANCE).of(340.0)
1306
-
1307
- axis = Snow::Vec3[1, 1, 0].normalize
1308
- result = Gosling::Collision.project_onto_axis(circle, axis)
1309
- expect(result[0]).to be_within(FLOAT_TOLERANCE).of(-443.1343965537543)
1310
- expect(result[1]).to be_within(FLOAT_TOLERANCE).of(-385.5947509968793)
1311
-
1312
- break_inheritance_chain([@center_actor, @scale_actor, @rotate_actor, @translate_actor, circle])
1313
- end
1314
- end
1315
-
1316
- context 'with a polygon' do
1317
- it 'returns expected values' do
1318
- axis = Snow::Vec3[1, 0, 0].normalize
1319
- clean_shape(@polygon2)
1320
- @polygon2.x = 0
1321
- @polygon2.y = 0
1322
- result = Gosling::Collision.project_onto_axis(@polygon2, axis)
1323
- expect(result).to be == [-5, 5]
1324
-
1325
- axis = Snow::Vec3[0, 1, 0].normalize
1326
- clean_shape(@polygon1)
1327
- @polygon1.x = 0
1328
- @polygon1.y = 5
1329
- result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1330
- expect(result).to be == [0, 10]
1331
-
1332
- axis = Snow::Vec3[1, -1, 0].normalize
1333
- clean_shape(@polygon1)
1334
- @polygon1.x = 0
1335
- @polygon1.y = 0
1336
- result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1337
- expect(result[0]).to be_within(0.00000001).of(-Math.sqrt(25 * 0.5))
1338
- expect(result[1]).to be_within(0.00000001).of(Math.sqrt(50))
1339
- end
1340
-
1341
- it 'respects centering' do
1342
- clean_shape(@polygon1)
1343
- @polygon1.center_x = 5
1344
- @polygon1.center_y = -1
1345
-
1346
- axis = Snow::Vec3[1, 0, 0].normalize
1347
- result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1348
- expect(result).to be == [-10, 0]
1349
-
1350
- axis = Snow::Vec3[0, 1, 0].normalize
1351
- result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1352
- expect(result).to be == [-4, 6]
1353
- end
1354
-
1355
- it 'respects scaling' do
1356
- clean_shape(@polygon1)
1357
- @polygon1.scale_x = 3
1358
- @polygon1.scale_y = 2
1359
-
1360
- axis = Snow::Vec3[1, 0, 0].normalize
1361
- result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1362
- expect(result).to be == [-15, 15]
1363
-
1364
- axis = Snow::Vec3[0, 1, 0].normalize
1365
- result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1366
- expect(result).to be == [-10, 10]
1367
- end
1368
-
1369
- it 'respects rotation' do
1370
- clean_shape(@polygon1)
1371
- @polygon1.rotation = Math::PI / 4
1372
-
1373
- axis = Snow::Vec3[1, 0, 0].normalize
1374
- result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1375
- expect(result).to be == [-7.0710678118654755, 3.5355339059327373]
1376
-
1377
- axis = Snow::Vec3[0, 1, 0].normalize
1378
- result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1379
- expect(result).to be == [-7.0710678118654755, 3.5355339059327378]
1380
- end
1381
-
1382
- it 'respects translation' do
1383
- clean_shape(@polygon1)
1384
- @polygon1.x = -7
1385
- @polygon1.y = 13
1386
-
1387
- axis = Snow::Vec3[1, 0, 0].normalize
1388
- result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1389
- expect(result).to be == [-12, -2]
1390
-
1391
- axis = Snow::Vec3[0, 1, 0].normalize
1392
- result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1393
- expect(result).to be == [8, 18]
1394
- end
1395
-
1396
- it 'respects its entire ancestry of transforms' do
1397
- polygon = Gosling::Polygon.new(@window)
1398
- clean_shape(polygon)
1399
- polygon.set_vertices(@polygon1.get_vertices)
1400
-
1401
- create_inheritance_chain([@center_actor, @scale_actor, @rotate_actor, @translate_actor, polygon])
1402
-
1403
- axis = Snow::Vec3[1, 0, 0].normalize
1404
- result = Gosling::Collision.project_onto_axis(polygon, axis)
1405
- expect(result).to be == [-918.5, -883.5]
1406
-
1407
- axis = Snow::Vec3[0, 1, 0].normalize
1408
- result = Gosling::Collision.project_onto_axis(polygon, axis)
1409
- expect(result[0]).to be_within(FLOAT_TOLERANCE).of(302.5)
1410
- expect(result[1]).to be_within(FLOAT_TOLERANCE).of(327.5)
1411
-
1412
- axis = Snow::Vec3[1, 1, 0].normalize
1413
- result = Gosling::Collision.project_onto_axis(polygon, axis)
1414
- expect(result[0]).to be_within(FLOAT_TOLERANCE).of(-426.7389424460814)
1415
- expect(result[1]).to be_within(FLOAT_TOLERANCE).of(-393.1513703397204)
1416
-
1417
- break_inheritance_chain([@center_actor, @scale_actor, @rotate_actor, @translate_actor, polygon])
1418
- end
1419
- end
1420
- end
1421
-
1422
- describe '.projections_overlap?' do
1423
- it 'accepts two length 2 arrays with numbers' do
1424
- expect { Gosling::Collision.projections_overlap?([0, 0], [0, 0]) }.not_to raise_error
1425
- expect { Gosling::Collision.projections_overlap?([1, 2], [3, -4]) }.not_to raise_error
1426
-
1427
- expect { Gosling::Collision.projections_overlap?([1, 2, 3], [4, 5, 6]) }.to raise_error(ArgumentError)
1428
- expect { Gosling::Collision.projections_overlap?([1], [4]) }.to raise_error(ArgumentError)
1429
- expect { Gosling::Collision.projections_overlap?([1, 2], [3, -4], [5, 6]) }.to raise_error(ArgumentError)
1430
- expect { Gosling::Collision.projections_overlap?([1, 2]) }.to raise_error(ArgumentError)
1431
- expect { Gosling::Collision.projections_overlap?([1, 2], :foo) }.to raise_error(ArgumentError)
1432
- expect { Gosling::Collision.projections_overlap?(nil, [1, 2]) }.to raise_error
1433
- end
1434
-
1435
- context 'when a and b do not overlap' do
1436
- it 'returns false' do
1437
- expect(Gosling::Collision.projections_overlap?([0, 10], [20, 30])).to be false
1438
- expect(Gosling::Collision.projections_overlap?([-20, -30], [0, 10])).to be false
1439
- end
1440
- end
1441
-
1442
- context 'when a contains b' do
1443
- it 'returns true' do
1444
- expect(Gosling::Collision.projections_overlap?([0, 40], [20, 30])).to be true
1445
- expect(Gosling::Collision.projections_overlap?([-40, 0], [-25, -15])).to be true
1446
- expect(Gosling::Collision.projections_overlap?([-2, 0], [-1, 0])).to be true
1447
- end
1448
- end
1449
-
1450
- context 'when b contains a' do
1451
- it 'returns true' do
1452
- expect(Gosling::Collision.projections_overlap?([5, 10], [0, 50])).to be true
1453
- expect(Gosling::Collision.projections_overlap?([-10, 10], [-25, 25])).to be true
1454
- expect(Gosling::Collision.projections_overlap?([5, 6], [5, 10])).to be true
1455
- end
1456
- end
1457
-
1458
- context 'when a overlaps b' do
1459
- it 'returns true' do
1460
- expect(Gosling::Collision.projections_overlap?([-10, 10], [0, 20])).to be true
1461
- expect(Gosling::Collision.projections_overlap?([-1000, 0], [-1, 314159])).to be true
1462
- end
1463
- end
1464
-
1465
- context 'when a touches b' do
1466
- it 'returns false' do
1467
- expect(Gosling::Collision.projections_overlap?([-10, 0], [0, 10])).to be false
1468
- expect(Gosling::Collision.projections_overlap?([-5, 30], [-17, -5])).to be false
1469
- end
1470
- end
1471
-
1472
- context 'when a just barely overlaps b' do
1473
- it 'returns false' do
1474
- expect(Gosling::Collision.projections_overlap?([-10, 0.0000001], [0, 10])).to be false
1475
- expect(Gosling::Collision.projections_overlap?([-4.999999999, 30], [-17, -5])).to be false
1476
- end
1477
- end
1478
- end
1479
-
1480
- describe '.get_overlap' do
1481
- it 'accepts two length 2 arrays with numbers' do
1482
- expect { Gosling::Collision.get_overlap([0, 0], [0, 0]) }.not_to raise_error
1483
- expect { Gosling::Collision.get_overlap([1, 2], [3, -4]) }.not_to raise_error
1484
-
1485
- expect { Gosling::Collision.get_overlap([1, 2, 3], [4, 5, 6]) }.to raise_error(ArgumentError)
1486
- expect { Gosling::Collision.get_overlap([1], [4]) }.to raise_error(ArgumentError)
1487
- expect { Gosling::Collision.get_overlap([1, 2], [3, -4], [5, 6]) }.to raise_error(ArgumentError)
1488
- expect { Gosling::Collision.get_overlap([1, 2]) }.to raise_error(ArgumentError)
1489
- expect { Gosling::Collision.get_overlap([1, 2], :foo) }.to raise_error(ArgumentError)
1490
- expect { Gosling::Collision.get_overlap(nil, [1, 2]) }.to raise_error
1491
- end
1492
-
1493
- context 'when a and b do not overlap' do
1494
- it 'returns nil' do
1495
- expect(Gosling::Collision.get_overlap([0, 10], [20, 30])).to be_nil
1496
- expect(Gosling::Collision.get_overlap([-20, -30], [0, 10])).to be_nil
1497
- end
1498
- end
1499
-
1500
- context 'when a contains b' do
1501
- it 'returns the length of b' do
1502
- expect(Gosling::Collision.get_overlap([0, 40], [20, 30])).to eq(10)
1503
- expect(Gosling::Collision.get_overlap([-40, 0], [-25, -15])).to eq(10)
1504
- expect(Gosling::Collision.get_overlap([-2, 0], [-1, 0])).to eq(1)
1505
- end
1506
- end
1507
-
1508
- context 'when b contains a' do
1509
- it 'returns the length of a' do
1510
- expect(Gosling::Collision.get_overlap([5, 10], [0, 50])).to eq(5)
1511
- expect(Gosling::Collision.get_overlap([-10, 10], [-25, 25])).to eq(20)
1512
- expect(Gosling::Collision.get_overlap([5, 6], [5, 10])).to eq(1)
1513
- end
1514
- end
1515
-
1516
- context 'when a overlaps b' do
1517
- it 'returns the length that overlaps' do
1518
- expect(Gosling::Collision.get_overlap([-10, 10], [0, 20])).to eq(10)
1519
- expect(Gosling::Collision.get_overlap([-1000, 0], [-1, 314159])).to eq(1)
1520
- end
1521
- end
1522
-
1523
- context 'when a touches b' do
1524
- it 'returns zero' do
1525
- expect(Gosling::Collision.get_overlap([-10, 0], [0, 10])).to eq(0)
1526
- expect(Gosling::Collision.get_overlap([-5, 30], [-17, -5])).to eq(0)
1527
- end
1528
- end
1529
-
1530
- context 'when a just barely overlaps b' do
1531
- it 'returns a very tiny value' do
1532
- expect(Gosling::Collision.get_overlap([-10, 0.0000001], [0, 10])).to eq(0.0000001)
1533
- expect(Gosling::Collision.get_overlap([-5, 30], [-17, -4.999999999])).to be_within(0.00000001).of(0)
1534
- end
1535
- end
1536
- end
1537
-
1538
- describe '.buffer_shapes' do
1539
- it 'accepts an array of actors' do
1540
- expect { Gosling::Collision.buffer_shapes([]) }.not_to raise_error
1541
- expect { Gosling::Collision.buffer_shapes([@actor1, @circle1, @polygon1, @rect1, @sprite1]) }.not_to raise_error
1542
-
1543
- expect { Gosling::Collision.buffer_shapes(@actor1) }.to raise_error(ArgumentError)
1544
- expect { Gosling::Collision.buffer_shapes(:foo) }.to raise_error(ArgumentError)
1545
- expect { Gosling::Collision.buffer_shapes(nil) }.to raise_error(ArgumentError)
1546
- end
1547
-
1548
- it 'resets the buffer iterators' do
1549
- expect(Gosling::Collision).to receive(:reset_buffer_iterators)
1550
- Gosling::Collision.buffer_shapes([@actor1])
1551
- end
1552
-
1553
- context 'when actors are initially buffered' do
1554
- before(:all) do
1555
- Gosling::Collision.clear_buffer
1556
- [@actor1, @circle1, @polygon1, @rect1, @sprite1].each { |a|
1557
- @scale_actor.add_child(a)
1558
- a.x = 0
1559
- a.y = 0
1560
- }
1561
- Gosling::Collision.buffer_shapes([@actor1, @circle1, @polygon1, @rect1, @sprite1])
1562
- end
1563
-
1564
- it 'adds those actors to the collision test set' do
1565
- [@circle1, @polygon1, @rect1, @sprite1].each do |actor|
1566
- expect(Gosling::Collision.collision_buffer).to include(actor)
1567
- end
1568
- end
1569
-
1570
- it 'caches computationally expensive information about each actor' do
1571
- expect(Gosling::Collision.global_vertices_cache.length).to eq(3)
1572
- expect(Gosling::Collision.global_position_cache.length).to eq(4)
1573
- expect(Gosling::Collision.global_transform_cache.length).to eq(4)
1574
-
1575
- [@actor1, @circle1, @polygon1, @rect1, @sprite1].each do |actor|
1576
- expect(actor).not_to receive(:get_global_vertices)
1577
- expect(actor).not_to receive(:get_global_position)
1578
- expect(actor).not_to receive(:get_global_transform)
1579
- end
1580
-
1581
- collisions = []
1582
- while true
1583
- info = Gosling::Collision.next_collision_info
1584
- break unless info
1585
- collisions << info
1586
- end
1587
- expect(collisions.length).to eq(6)
1588
- end
1589
-
1590
- it 'only caches info for children of the Actor class' do
1591
- [@actor1].each do |actor|
1592
- expect(Gosling::Collision.collision_buffer).not_to include(actor)
1593
- end
1594
- end
1595
-
1596
- context 'and then re-buffered' do
1597
- it 'updates info for already buffered actors' do
1598
- [@circle1, @circle2].each do |actor|
1599
- expect(actor).to receive(:get_global_position).once.and_call_original
1600
- expect(actor).to receive(:get_global_transform).twice.and_call_original
1601
- end
1602
- [@rect1].each do |actor|
1603
- expect(actor).to receive(:get_global_vertices).once.and_call_original
1604
- expect(actor).to receive(:get_global_transform).exactly(3).times.and_call_original
1605
- end
1606
-
1607
- Gosling::Collision.buffer_shapes([@circle1, @circle2, @rect1])
1608
-
1609
- [@circle1, @circle2, @rect1].each do |actor|
1610
- expect(Gosling::Collision.collision_buffer.select { |a| a == actor }.length).to eq(1)
1611
- end
1612
- expect(Gosling::Collision.global_vertices_cache.length).to eq(3)
1613
- expect(Gosling::Collision.global_position_cache.length).to eq(5)
1614
- expect(Gosling::Collision.global_transform_cache.length).to eq(5)
1615
- end
1616
- end
1617
-
1618
- after(:all) do
1619
- Gosling::Collision.clear_buffer
1620
- [@actor1, @circle1, @polygon1, @rect1, @sprite1].each { |a| @scale_actor.remove_child(a) }
1621
- end
1622
- end
1623
- end
1624
-
1625
- describe '.next_collision_info' do
1626
- before(:all) do
1627
- Gosling::Collision.clear_buffer
1628
- [@circle1, @polygon1, @rect1, @sprite1].each { |a| a.x = 0; a.y = 0 }
1629
- Gosling::Collision.buffer_shapes([@circle1, @polygon1, @rect1, @sprite1])
1630
- end
1631
-
1632
- it 'returns collision information for the next pair of colliding actors, then nil when done' do
1633
- info = Gosling::Collision.next_collision_info
1634
- expect(info[:actors]).to include(@polygon1, @circle1)
1635
- expect(info[:colliding]).to be true
1636
-
1637
- info = Gosling::Collision.next_collision_info
1638
- expect(info[:actors]).to include(@rect1, @circle1)
1639
- expect(info[:colliding]).to be true
1640
-
1641
- info = Gosling::Collision.next_collision_info
1642
- expect(info[:actors]).to include(@rect1, @polygon1)
1643
- expect(info[:colliding]).to be true
1644
-
1645
- info = Gosling::Collision.next_collision_info
1646
- expect(info[:actors]).to include(@sprite1, @circle1)
1647
- expect(info[:colliding]).to be true
1648
-
1649
- info = Gosling::Collision.next_collision_info
1650
- expect(info[:actors]).to include(@sprite1, @polygon1)
1651
- expect(info[:colliding]).to be true
1652
-
1653
- info = Gosling::Collision.next_collision_info
1654
- expect(info[:actors]).to include(@sprite1, @rect1)
1655
- expect(info[:colliding]).to be true
1656
-
1657
- expect(Gosling::Collision.next_collision_info).to be_nil
1658
- end
1659
-
1660
- after(:all) do
1661
- Gosling::Collision.clear_buffer
1662
- end
1663
- end
1664
-
1665
- describe '.peek_at_next_collision' do
1666
- before(:all) do
1667
- Gosling::Collision.clear_buffer
1668
- [@circle1, @polygon1, @rect1, @sprite1].each { |a| a.x = 0; a.y = 0 }
1669
- Gosling::Collision.buffer_shapes([@circle1, @polygon1, @rect1, @sprite1])
1670
- end
1671
-
1672
- it 'returns references to the next two buffered actors to be collision tested, if any' do
1673
- expect(Gosling::Collision.peek_at_next_collision).to eq([@polygon1, @circle1])
1674
- 2.times { Gosling::Collision.skip_next_collision }
1675
- expect(Gosling::Collision.peek_at_next_collision).to eq([@rect1, @polygon1])
1676
- 2.times { Gosling::Collision.skip_next_collision }
1677
- info = Gosling::Collision.next_collision_info
1678
- expect(info[:actors]).to include(@sprite1, @polygon1)
1679
- 2.times { Gosling::Collision.skip_next_collision }
1680
- expect(Gosling::Collision.peek_at_next_collision).to be_nil
1681
- end
1682
-
1683
- after(:all) do
1684
- Gosling::Collision.clear_buffer
1685
- end
1686
- end
1687
-
1688
- describe '.skip_next_collision' do
1689
- before(:all) do
1690
- Gosling::Collision.clear_buffer
1691
- [@circle1, @polygon1, @rect1, @sprite1].each { |a| a.x = 0; a.y = 0 }
1692
- Gosling::Collision.buffer_shapes([@circle1, @polygon1, @rect1])
1693
- end
1694
-
1695
- it 'moves the collision iterators forward without performing any collision testing' do
1696
- expect(Gosling::Collision.peek_at_next_collision).to eq([@polygon1, @circle1])
1697
- Gosling::Collision.skip_next_collision
1698
- expect(Gosling::Collision.peek_at_next_collision).to eq([@rect1, @circle1])
1699
- Gosling::Collision.skip_next_collision
1700
- info = Gosling::Collision.next_collision_info
1701
- expect(info[:actors]).to include(@rect1, @polygon1)
1702
- Gosling::Collision.skip_next_collision
1703
- expect(Gosling::Collision.peek_at_next_collision).to be_nil
1704
- end
1705
-
1706
- after(:all) do
1707
- Gosling::Collision.clear_buffer
1708
- end
1709
- end
1710
-
1711
- describe '.unbuffer_shapes' do
1712
- it 'accepts an array of actors' do
1713
- expect { Gosling::Collision.unbuffer_shapes([]) }.not_to raise_error
1714
- expect { Gosling::Collision.unbuffer_shapes([@actor1, @circle1, @polygon1, @rect1, @sprite1]) }.not_to raise_error
1715
-
1716
- expect { Gosling::Collision.unbuffer_shapes(@actor1) }.to raise_error(ArgumentError)
1717
- expect { Gosling::Collision.unbuffer_shapes(:foo) }.to raise_error(ArgumentError)
1718
- expect { Gosling::Collision.unbuffer_shapes(nil) }.to raise_error(ArgumentError)
1719
- end
1720
-
1721
- it 'resets the buffer iterators' do
1722
- expect(Gosling::Collision).to receive(:reset_buffer_iterators)
1723
- Gosling::Collision.unbuffer_shapes([@actor1])
1724
- end
1725
-
1726
- it 'removes those actors from the collision test list and related info from the caches' do
1727
- Gosling::Collision.buffer_shapes([@actor1, @circle1, @polygon1, @rect1, @sprite1])
1728
- Gosling::Collision.unbuffer_shapes([@actor1, @polygon1, @sprite1])
1729
- [@actor1, @polygon1, @sprite1].each do |actor|
1730
- expect(Gosling::Collision.collision_buffer).not_to include(actor)
1731
- end
1732
- expect(Gosling::Collision.global_vertices_cache.length).to eq(1)
1733
- expect(Gosling::Collision.global_position_cache.length).to eq(2)
1734
- expect(Gosling::Collision.global_transform_cache.length).to eq(2)
1735
- end
1736
- end
1737
-
1738
- describe '.clear_buffer' do
1739
- it 'removes all actors from the collision test list and related info from the caches' do
1740
- Gosling::Collision.buffer_shapes([@actor1, @circle1, @polygon1, @rect1, @sprite1])
1741
- Gosling::Collision.clear_buffer
1742
- [@actor1, @circle1, @polygon1, @rect1, @sprite1].each do |actor|
1743
- expect(Gosling::Collision.collision_buffer).not_to include(actor)
1744
- end
1745
- expect(Gosling::Collision.global_vertices_cache.length).to eq(0)
1746
- expect(Gosling::Collision.global_position_cache.length).to eq(0)
1747
- expect(Gosling::Collision.global_transform_cache.length).to eq(0)
1748
- end
1749
-
1750
- it 'resets the buffer iterators' do
1751
- expect(Gosling::Collision).to receive(:reset_buffer_iterators)
1752
- Gosling::Collision.clear_buffer
1753
- end
1754
- end
1755
- end
1
+ require 'set'
2
+
3
+ module Gosling
4
+ class Collision
5
+ def self.collision_buffer
6
+ @@collision_buffer
7
+ end
8
+
9
+ def self.global_vertices_cache
10
+ @@global_vertices_cache
11
+ end
12
+
13
+ def self.global_position_cache
14
+ @@global_position_cache
15
+ end
16
+
17
+ def self.global_transform_cache
18
+ @@global_transform_cache
19
+ end
20
+
21
+ public_class_method :reset_separation_axes, :separation_axes
22
+ end
23
+ end
24
+
25
+ def angle_to_vector(angle)
26
+ Snow::Vec3[Math.sin(angle).round(12), Math.cos(angle).round(12), 0]
27
+ end
28
+
29
+ def clean_actor(actor)
30
+ actor.x = 0
31
+ actor.y = 0
32
+ actor.scale_x = 1
33
+ actor.scale_y = 1
34
+ actor.rotation = 0
35
+ end
36
+
37
+ def clean_shape(shape)
38
+ clean_actor(shape)
39
+ shape.center_x = 0
40
+ shape.center_y = 0
41
+ end
42
+
43
+ def clean_rect(rect)
44
+ clean_actor(rect)
45
+ rect.center_x = 5
46
+ rect.center_y = 5
47
+ end
48
+
49
+ def clean_sprite(sprite)
50
+ clean_actor(sprite)
51
+ sprite.center_x = 8
52
+ sprite.center_y = 8
53
+ end
54
+
55
+ def create_inheritance_chain(ancestry)
56
+ (1...ancestry.length).each do |i|
57
+ ancestry[i-1].add_child(ancestry[i])
58
+ end
59
+ end
60
+
61
+ def break_inheritance_chain(ancestry)
62
+ ancestry.each do |actor|
63
+ actor.parent.remove_child(actor) if actor.parent
64
+ end
65
+ end
66
+
67
+ ANGLE_COUNT = 8 * 4
68
+
69
+ describe Gosling::Collision do
70
+ FLOAT_TOLERANCE = 0.000001
71
+
72
+ before(:all) do
73
+ @window = Gosu::Window.new(640, 480, false)
74
+ @local_path = File.dirname(__FILE__)
75
+ @image = Gosling::ImageLibrary.get(File.join(@local_path, 'images/nil.png'))
76
+
77
+ @actor1 = Gosling::Actor.new(@window)
78
+
79
+ @actor2 = Gosling::Actor.new(@window)
80
+
81
+ @circle1 = Gosling::Circle.new(@window)
82
+ @circle1.radius = 5
83
+
84
+ @circle2 = Gosling::Circle.new(@window)
85
+ @circle2.radius = 5
86
+
87
+ @polygon1 = Gosling::Polygon.new(@window)
88
+ @polygon1.set_vertices([
89
+ Snow::Vec3[ 0, 5, 0],
90
+ Snow::Vec3[ 5, -5, 0],
91
+ Snow::Vec3[-5, -5, 0]
92
+ ])
93
+
94
+ @polygon2 = Gosling::Polygon.new(@window)
95
+ @polygon2.set_vertices([
96
+ Snow::Vec3[ 0, -5, 0],
97
+ Snow::Vec3[ 5, 5, 0],
98
+ Snow::Vec3[-5, 5, 0]
99
+ ])
100
+
101
+ @rect1 = Gosling::Rect.new(@window)
102
+ @rect1.width = 10
103
+ @rect1.height = 10
104
+ @rect1.center_x = 5
105
+ @rect1.center_y = 5
106
+
107
+ @rect2 = Gosling::Rect.new(@window)
108
+ @rect2.width = 10
109
+ @rect2.height = 10
110
+ @rect2.center_x = 5
111
+ @rect2.center_y = 5
112
+ @rect2.rotation = Math::PI / 4
113
+
114
+ @sprite1 = Gosling::Sprite.new(@window)
115
+ @sprite1.set_image(@image)
116
+ @sprite1.center_x = 8
117
+ @sprite1.center_y = 8
118
+
119
+ @sprite2 = Gosling::Sprite.new(@window)
120
+ @sprite2.set_image(@image)
121
+ @sprite2.center_x = 8
122
+ @sprite2.center_y = 8
123
+
124
+ @center_actor = Gosling::Actor.new(@window)
125
+ @center_actor.center_x = 5
126
+ @center_actor.center_y = 5
127
+
128
+ @scale_actor = Gosling::Actor.new(@window)
129
+ @scale_actor.scale_x = 3.5
130
+ @scale_actor.scale_y = 2.5
131
+
132
+ @rotate_actor = Gosling::Actor.new(@window)
133
+ @rotate_actor.rotation = Math::PI * -0.5
134
+
135
+ @translate_actor = Gosling::Actor.new(@window)
136
+ @translate_actor.x = 128
137
+ @translate_actor.y = 256
138
+
139
+ @angles = (0...ANGLE_COUNT).map { |i| Math::PI * 2 * i / ANGLE_COUNT }
140
+ end
141
+
142
+ before do
143
+ Gosling::Collision.reset_separation_axes
144
+ end
145
+
146
+ context 'any actor vs. itself' do
147
+ it 'never collides' do
148
+ [@actor1, @circle1, @polygon1, @rect1, @sprite1].each do |actor|
149
+ expect(Gosling::Collision.test(actor, actor)).to be false
150
+ result = Gosling::Collision.get_collision_info(actor, actor)
151
+ expect(result[:actors]).to include(actor)
152
+ expect(result[:actors].length).to eq(2)
153
+ expect(result[:colliding]).to be false
154
+ expect(result[:overlap]).to be nil
155
+ expect(result[:penetration]).to be nil
156
+ end
157
+ end
158
+ end
159
+
160
+ context 'actor vs. anything' do
161
+ it 'never collides' do
162
+ pairs = [
163
+ [@actor1, @actor2],
164
+ [@actor1, @circle1],
165
+ [@actor1, @polygon1],
166
+ [@actor1, @rect1],
167
+ [@actor1, @sprite1]
168
+ ]
169
+ pairs.each do |pair|
170
+ expect(Gosling::Collision.test(*pair)).to be false
171
+ result = Gosling::Collision.get_collision_info(*pair)
172
+ expect(result[:actors]).to include(*pair)
173
+ expect(result[:colliding]).to be false
174
+ expect(result[:overlap]).to be nil
175
+ expect(result[:penetration]).to be nil
176
+ end
177
+ end
178
+ end
179
+
180
+ context 'circle vs. circle' do
181
+ before do
182
+ clean_shape(@circle1)
183
+ @circle1.x = 0
184
+ @circle1.y = 0
185
+
186
+ clean_shape(@circle2)
187
+ @circle2.x = 5
188
+ @circle2.y = 5
189
+ end
190
+
191
+ it 'collides if the shapes are close enough' do
192
+ expect(Gosling::Collision.test(@circle1, @circle2)).to be true
193
+ result = Gosling::Collision.get_collision_info(@circle1, @circle2)
194
+ expect(result[:actors]).to include(@circle1, @circle2)
195
+ expect(result[:colliding]).to be true
196
+ expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(10 - Math.sqrt(50))
197
+ expect(result[:penetration]).to eq(Snow::Vec3[1, 1, 0].normalize * result[:overlap])
198
+ end
199
+
200
+ it 'returns a vector that separates the shapes' do
201
+ @angles.each do |r|
202
+ @circle1.x = 5 + Math.sin(r) * 5
203
+ @circle1.y = 5 + Math.cos(r) * 5
204
+ @circle2.x = 5
205
+ @circle2.y = 5
206
+
207
+ result = Gosling::Collision.get_collision_info(@circle1, @circle2)
208
+ @circle2.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
209
+ expect(Gosling::Collision.test(@circle1, @circle2)).to be false
210
+ end
211
+ end
212
+
213
+ it 'always returns the vector that displaces shape b away from shape a' do
214
+ @circle1.y = 10
215
+ result = Gosling::Collision.get_collision_info(@circle1, @circle2)
216
+ expect(result[:penetration]).to eq(Snow::Vec3[1, -1, 0].normalize * result[:overlap])
217
+ end
218
+
219
+ it 'does not collide if the shapes are far apart' do
220
+ @circle2.x = 10
221
+
222
+ expect(Gosling::Collision.test(@circle1, @circle2)).to be false
223
+
224
+ result = Gosling::Collision.get_collision_info(@circle1, @circle2)
225
+ expect(result[:actors]).to include(@circle1, @circle2)
226
+ expect(result[:colliding]).to be false
227
+ expect(result[:overlap]).to be nil
228
+ expect(result[:penetration]).to be nil
229
+ end
230
+ end
231
+
232
+ context 'circle vs. polygon' do
233
+ before do
234
+ clean_shape(@circle1)
235
+ @circle1.x = 0
236
+ @circle1.y = 0
237
+
238
+ clean_shape(@polygon1)
239
+ @polygon1.x = 0
240
+ @polygon1.y = 6
241
+ end
242
+
243
+ it 'collides if the shapes are close enough' do
244
+ expect(Gosling::Collision.test(@circle1, @polygon1)).to be true
245
+ result = Gosling::Collision.get_collision_info(@circle1, @polygon1)
246
+ expect(result[:actors]).to include(@circle1, @polygon1)
247
+ expect(result[:colliding]).to be true
248
+ expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(4)
249
+ expect(result[:penetration]).to eq(Snow::Vec3[0, 1, 0].normalize * result[:overlap])
250
+ end
251
+
252
+ it 'returns a vector that separates the shapes' do
253
+ result = Gosling::Collision.get_collision_info(@circle1, @polygon1)
254
+ @polygon1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
255
+ expect(Gosling::Collision.test(@circle1, @polygon1)).to be false
256
+ end
257
+
258
+ it 'does not collide if the shapes are far apart' do
259
+ @polygon1.x = 10
260
+ @polygon1.y = 10
261
+
262
+ expect(Gosling::Collision.test(@circle1, @polygon1)).to be false
263
+ result = Gosling::Collision.get_collision_info(@circle1, @polygon1)
264
+ expect(result[:actors]).to include(@circle1, @polygon1)
265
+ expect(result[:colliding]).to be false
266
+ expect(result[:overlap]).to be nil
267
+ expect(result[:penetration]).to be nil
268
+ end
269
+ end
270
+
271
+ context 'circle vs. rect' do
272
+ before do
273
+ clean_shape(@circle1)
274
+ @circle1.x = 0
275
+ @circle1.y = 0
276
+
277
+ clean_rect(@rect1)
278
+ @rect1.x = 6
279
+ @rect1.y = 6
280
+ end
281
+
282
+ it 'collides if the shapes are close enough' do
283
+ expect(Gosling::Collision.test(@circle1, @rect1)).to be true
284
+ result = Gosling::Collision.get_collision_info(@circle1, @rect1)
285
+ expect(result[:actors]).to include(@circle1, @rect1)
286
+ expect(result[:colliding]).to be true
287
+ expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(3.585786437626905)
288
+ expect(result[:penetration]).to eq(Snow::Vec3[1, 1, 0].normalize * result[:overlap])
289
+ end
290
+
291
+ it 'returns a vector that separates the shapes' do
292
+ result = Gosling::Collision.get_collision_info(@circle1, @rect1)
293
+ @rect1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
294
+ expect(Gosling::Collision.test(@circle1, @rect1)).to be false
295
+ end
296
+
297
+ it 'does not collide if the shapes are far apart' do
298
+ @rect1.x = 10
299
+ @rect1.y = 10
300
+
301
+ expect(Gosling::Collision.test(@circle1, @rect1)).to be false
302
+ result = Gosling::Collision.get_collision_info(@circle1, @rect1)
303
+ expect(result[:actors]).to include(@circle1, @rect1)
304
+ expect(result[:colliding]).to be false
305
+ expect(result[:overlap]).to be nil
306
+ expect(result[:penetration]).to be nil
307
+ end
308
+ end
309
+
310
+ context 'circle vs. sprite' do
311
+ before do
312
+ clean_shape(@circle1)
313
+ @circle1.x = 0
314
+ @circle1.y = 0
315
+
316
+ clean_sprite(@sprite1)
317
+ @sprite1.x = 9
318
+ @sprite1.y = 9
319
+ end
320
+
321
+ it 'collides if the shapes are close enough' do
322
+ expect(Gosling::Collision.test(@circle1, @sprite1)).to be true
323
+ result = Gosling::Collision.get_collision_info(@circle1, @sprite1)
324
+ expect(result[:actors]).to include(@circle1, @sprite1)
325
+ expect(result[:colliding]).to be true
326
+ expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(3.585786437626905)
327
+ expect(result[:penetration]).to eq(Snow::Vec3[1, 1, 0].normalize * result[:overlap])
328
+ end
329
+
330
+ it 'returns a vector that separates the shapes' do
331
+ result = Gosling::Collision.get_collision_info(@circle1, @sprite1)
332
+ @sprite1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
333
+ expect(Gosling::Collision.test(@circle1, @sprite1)).to be false
334
+ end
335
+
336
+ it 'does not collide if the shapes are far apart' do
337
+ @sprite1.x = 16
338
+ @sprite1.y = 16
339
+
340
+ expect(Gosling::Collision.test(@circle1, @sprite1)).to be false
341
+ result = Gosling::Collision.get_collision_info(@circle1, @sprite1)
342
+ expect(result[:actors]).to include(@circle1, @sprite1)
343
+ expect(result[:colliding]).to be false
344
+ expect(result[:overlap]).to be nil
345
+ expect(result[:penetration]).to be nil
346
+ end
347
+ end
348
+
349
+ context 'polygon vs. polygon' do
350
+ before do
351
+ clean_shape(@polygon1)
352
+ @polygon1.x = 0
353
+ @polygon1.y = 0
354
+
355
+ clean_shape(@polygon2)
356
+ @polygon2.x = 0
357
+ @polygon2.y = 5
358
+ end
359
+
360
+ it 'collides if the shapes are close enough' do
361
+ expect(Gosling::Collision.test(@polygon1, @polygon2)).to be true
362
+ result = Gosling::Collision.get_collision_info(@polygon1, @polygon2)
363
+ expect(result[:actors]).to include(@polygon1, @polygon2)
364
+ expect(result[:colliding]).to be true
365
+ axis = Snow::Vec2[-10, -5].normalize
366
+ a = Snow::Vec2[0, 0].dot_product(axis)
367
+ b = Snow::Vec2[0, 5].dot_product(axis)
368
+ expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(a - b)
369
+ expect(result[:penetration]).to eq(Snow::Vec3[-2, 1, 0].normalize * result[:overlap])
370
+ end
371
+
372
+ it 'returns a vector that separates the shapes' do
373
+ @polygon1.x = 0
374
+ @polygon1.y = 0
375
+
376
+ @angles.each do |r|
377
+ @polygon2.x = 0
378
+ @polygon2.y = 5
379
+
380
+ @polygon1.rotation = r
381
+ result = Gosling::Collision.get_collision_info(@polygon1, @polygon2)
382
+ @polygon2.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
383
+ expect(Gosling::Collision.test(@polygon1, @polygon2)).to be false
384
+ @polygon1.rotation = 0
385
+
386
+ @polygon2.x = Math.sin(r)
387
+ @polygon2.y = Math.cos(r)
388
+ result = Gosling::Collision.get_collision_info(@polygon1, @polygon2)
389
+ @polygon2.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
390
+ expect(Gosling::Collision.test(@polygon1, @polygon2)).to be false
391
+ end
392
+ end
393
+
394
+ it 'always returns the vector that displaces shape b away from shape a' do
395
+ @polygon1.y = 5
396
+ @polygon2.y = 0
397
+ result = Gosling::Collision.get_collision_info(@polygon1, @polygon2)
398
+ expect(result[:penetration]).to eq(Snow::Vec3[0, -1, 0].normalize * result[:overlap])
399
+ end
400
+
401
+ it 'does not collide if the shapes are far apart' do
402
+ @polygon2.x = 5
403
+
404
+ expect(Gosling::Collision.test(@polygon1, @polygon2)).to be false
405
+ result = Gosling::Collision.get_collision_info(@polygon1, @polygon2)
406
+ expect(result[:actors]).to include(@polygon1, @polygon2)
407
+ expect(result[:colliding]).to be false
408
+ expect(result[:overlap]).to be nil
409
+ expect(result[:penetration]).to be nil
410
+ end
411
+ end
412
+
413
+ context 'polygon vs. rect' do
414
+ before do
415
+ clean_shape(@polygon1)
416
+ @polygon1.x = 0
417
+ @polygon1.y = 0
418
+
419
+ clean_rect(@rect1)
420
+ @rect1.x = 5
421
+ @rect1.y = 5
422
+ end
423
+
424
+ it 'collides if the shapes are close enough' do
425
+ expect(Gosling::Collision.test(@polygon1, @rect1)).to be true
426
+ result = Gosling::Collision.get_collision_info(@polygon1, @rect1)
427
+ expect(result[:actors]).to include(@polygon1, @rect1)
428
+ expect(result[:colliding]).to be true
429
+ axis = Snow::Vec2[-10, -5].normalize
430
+ a = Snow::Vec2[0, 0].dot_product(axis)
431
+ b = Snow::Vec2[0, 5].dot_product(axis)
432
+ expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(a - b)
433
+ expect(result[:penetration]).to eq(Snow::Vec3[2, 1, 0].normalize * result[:overlap])
434
+ end
435
+
436
+ it 'returns a vector that separates the shapes' do
437
+ result = Gosling::Collision.get_collision_info(@polygon1, @rect1)
438
+ @rect1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
439
+ expect(Gosling::Collision.test(@polygon1, @rect1)).to be false
440
+ end
441
+
442
+ it 'does not collide if the shapes are far apart' do
443
+ @rect1.x = 10
444
+
445
+ expect(Gosling::Collision.test(@polygon1, @rect1)).to be false
446
+ result = Gosling::Collision.get_collision_info(@polygon1, @rect1)
447
+ expect(result[:actors]).to include(@polygon1, @rect1)
448
+ expect(result[:colliding]).to be false
449
+ expect(result[:overlap]).to be nil
450
+ expect(result[:penetration]).to be nil
451
+ end
452
+ end
453
+
454
+ context 'polygon vs. sprite' do
455
+ before do
456
+ clean_shape(@polygon1)
457
+ @polygon1.x = 0
458
+ @polygon1.y = 0
459
+
460
+ clean_sprite(@sprite1)
461
+ @sprite1.x = 8
462
+ @sprite1.y = 8
463
+ end
464
+
465
+ it 'collides if the shapes are close enough' do
466
+ expect(Gosling::Collision.test(@polygon1, @sprite1)).to be true
467
+ result = Gosling::Collision.get_collision_info(@polygon1, @sprite1)
468
+ expect(result[:actors]).to include(@polygon1, @sprite1)
469
+ expect(result[:colliding]).to be true
470
+ axis = Snow::Vec2[-10, -5].normalize
471
+ a = Snow::Vec2[0, 0].dot_product(axis)
472
+ b = Snow::Vec2[0, 5].dot_product(axis)
473
+ expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(a - b)
474
+ expect(result[:penetration]).to eq(Snow::Vec3[2, 1, 0].normalize * result[:overlap])
475
+ end
476
+
477
+ it 'returns a vector that separates the shapes' do
478
+ result = Gosling::Collision.get_collision_info(@polygon1, @sprite1)
479
+ @sprite1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
480
+ expect(Gosling::Collision.test(@polygon1, @sprite1)).to be false
481
+ end
482
+
483
+ it 'does not collide if the shapes are far apart' do
484
+ @sprite1.x = 13
485
+
486
+ expect(Gosling::Collision.test(@polygon1, @sprite1)).to be false
487
+ result = Gosling::Collision.get_collision_info(@polygon1, @sprite1)
488
+ expect(result[:actors]).to include(@polygon1, @sprite1)
489
+ expect(result[:colliding]).to be false
490
+ expect(result[:overlap]).to be nil
491
+ expect(result[:penetration]).to be nil
492
+ end
493
+ end
494
+
495
+ context 'rect vs. rect' do
496
+ before do
497
+ clean_rect(@rect1)
498
+ @rect1.x = 0
499
+ @rect1.y = 0
500
+
501
+ clean_rect(@rect2)
502
+ @rect2.x = 5
503
+ @rect2.y = 5
504
+ end
505
+
506
+ it 'collides if the shapes are close enough' do
507
+ expect(Gosling::Collision.test(@rect1, @rect2)).to be true
508
+ result = Gosling::Collision.get_collision_info(@rect1, @rect2)
509
+ expect(result[:actors]).to include(@rect1, @rect2)
510
+ expect(result[:colliding]).to be true
511
+ expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(5)
512
+ if result[:penetration].x == 0
513
+ expect(result[:penetration]).to eq(Snow::Vec3[0, 1, 0].normalize * result[:overlap])
514
+ else
515
+ expect(result[:penetration]).to eq(Snow::Vec3[1, 0, 0].normalize * result[:overlap])
516
+ end
517
+ end
518
+
519
+ it 'returns a vector that separates the shapes' do
520
+ result = Gosling::Collision.get_collision_info(@rect1, @rect2)
521
+ @rect2.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
522
+ expect(Gosling::Collision.test(@rect1, @rect2)).to be false
523
+ end
524
+
525
+ it 'does not collide if the shapes are far apart' do
526
+ @rect2.x = 11
527
+
528
+ expect(Gosling::Collision.test(@rect1, @rect2)).to be false
529
+ result = Gosling::Collision.get_collision_info(@rect1, @rect2)
530
+ expect(result[:actors]).to include(@rect1, @rect2)
531
+ expect(result[:colliding]).to be false
532
+ expect(result[:overlap]).to be nil
533
+ expect(result[:penetration]).to be nil
534
+ end
535
+ end
536
+
537
+ context 'rect vs. sprite' do
538
+ before do
539
+ clean_rect(@rect1)
540
+ @rect1.x = 0
541
+ @rect1.y = 0
542
+
543
+ clean_sprite(@sprite1)
544
+ @sprite1.x = 8
545
+ @sprite1.y = 8
546
+ end
547
+
548
+ it 'collides if the shapes are close enough' do
549
+ expect(Gosling::Collision.test(@rect1, @sprite1)).to be true
550
+ result = Gosling::Collision.get_collision_info(@rect1, @sprite1)
551
+ expect(result[:actors]).to include(@rect1, @sprite1)
552
+ expect(result[:colliding]).to be true
553
+ expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(5)
554
+ if result[:penetration].x == 0
555
+ expect(result[:penetration]).to eq(Snow::Vec3[0, 1, 0].normalize * result[:overlap])
556
+ else
557
+ expect(result[:penetration]).to eq(Snow::Vec3[1, 0, 0].normalize * result[:overlap])
558
+ end
559
+ end
560
+
561
+ it 'returns a vector that separates the shapes' do
562
+ result = Gosling::Collision.get_collision_info(@rect1, @sprite1)
563
+ @sprite1.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
564
+ expect(Gosling::Collision.test(@rect1, @sprite1)).to be false
565
+ end
566
+
567
+ it 'does not collide if the shapes are far apart' do
568
+ @sprite1.x = 16
569
+
570
+ expect(Gosling::Collision.test(@rect1, @sprite1)).to be false
571
+ result = Gosling::Collision.get_collision_info(@rect1, @sprite1)
572
+ expect(result[:actors]).to include(@rect1, @sprite1)
573
+ expect(result[:colliding]).to be false
574
+ expect(result[:overlap]).to be nil
575
+ expect(result[:penetration]).to be nil
576
+ end
577
+ end
578
+
579
+ context 'sprite vs. sprite' do
580
+ before do
581
+ clean_sprite(@sprite1)
582
+ @sprite1.x = 0
583
+ @sprite1.y = 0
584
+
585
+ clean_sprite(@sprite2)
586
+ @sprite2.x = 8
587
+ @sprite2.y = 8
588
+ end
589
+
590
+ it 'collides if the shapes are close enough' do
591
+ expect(Gosling::Collision.test(@sprite1, @sprite2)).to be true
592
+ result = Gosling::Collision.get_collision_info(@sprite1, @sprite2)
593
+ expect(result[:actors]).to include(@sprite1, @sprite2)
594
+ expect(result[:colliding]).to be true
595
+ expect(result[:overlap]).to be_within(FLOAT_TOLERANCE).of(8)
596
+ if result[:penetration].x == 0
597
+ expect(result[:penetration]).to eq(Snow::Vec3[0, 1, 0].normalize * result[:overlap])
598
+ else
599
+ expect(result[:penetration]).to eq(Snow::Vec3[1, 0, 0].normalize * result[:overlap])
600
+ end
601
+ end
602
+
603
+ it 'returns a vector that separates the shapes' do
604
+ result = Gosling::Collision.get_collision_info(@sprite1, @sprite2)
605
+ @sprite2.pos += result[:penetration] * (1 + FLOAT_TOLERANCE)
606
+ expect(Gosling::Collision.test(@sprite1, @sprite2)).to be false
607
+ end
608
+
609
+ it 'does not collide if the shapes are far apart' do
610
+ @sprite2.x = 17
611
+
612
+ expect(Gosling::Collision.test(@sprite1, @sprite2)).to be false
613
+ result = Gosling::Collision.get_collision_info(@sprite1, @sprite2)
614
+ expect(result[:actors]).to include(@sprite1, @sprite2)
615
+ expect(result[:colliding]).to be false
616
+ expect(result[:overlap]).to be nil
617
+ expect(result[:penetration]).to be nil
618
+ end
619
+ end
620
+
621
+ describe '.is_point_in_shape?' do
622
+ it 'expects a point and an actor' do
623
+ expect { Gosling::Collision.is_point_in_shape?(Snow::Vec3[0, 0, 0], @actor1) }.not_to raise_error
624
+
625
+ expect { Gosling::Collision.is_point_in_shape?(@actor1, Snow::Vec3[0, 0, 0]) }.to raise_error(ArgumentError)
626
+ expect { Gosling::Collision.is_point_in_shape?(@actor1, :foo) }.to raise_error(ArgumentError)
627
+ expect { Gosling::Collision.is_point_in_shape?(:bar, Snow::Vec3[0, 0, 0]) }.to raise_error(ArgumentError)
628
+ expect { Gosling::Collision.is_point_in_shape?() }.to raise_error(ArgumentError)
629
+ end
630
+
631
+ context 'point vs. actor' do
632
+ it 'never collides' do
633
+ expect(Gosling::Collision.is_point_in_shape?(Snow::Vec3[0, 0, 0], @actor1)).to be false
634
+ end
635
+ end
636
+
637
+ context 'point vs. circle' do
638
+ before do
639
+ clean_shape(@circle1)
640
+ end
641
+
642
+ it 'returns true if point is in shape' do
643
+ points = [
644
+ Snow::Vec3[0, 0, 0],
645
+ Snow::Vec3[4, 0, 0],
646
+ Snow::Vec3[-4, 0, 0],
647
+ Snow::Vec3[0, 4, 0],
648
+ Snow::Vec3[0, -4, 0],
649
+ Snow::Vec3[5, 0, 0],
650
+ Snow::Vec3[-5, 0, 0],
651
+ Snow::Vec3[0, 5, 0],
652
+ Snow::Vec3[0, -5, 0],
653
+ ]
654
+ points.each do |p|
655
+ expect(Gosling::Collision.is_point_in_shape?(p, @circle1)).to be true
656
+ end
657
+ end
658
+
659
+ it 'returns false if point is not in shape' do
660
+ points = [
661
+ Snow::Vec3[6, 0, 0],
662
+ Snow::Vec3[-6, 0, 0],
663
+ Snow::Vec3[0, 6, 0],
664
+ Snow::Vec3[0, -6, 0],
665
+ Snow::Vec3[4, 4, 0],
666
+ Snow::Vec3[-4, 4, 0],
667
+ Snow::Vec3[-4, -4, 0],
668
+ Snow::Vec3[4, -4, 0],
669
+ ]
670
+ points.each do |p|
671
+ expect(Gosling::Collision.is_point_in_shape?(p, @circle1)).to be false
672
+ end
673
+ end
674
+ end
675
+
676
+ context 'point vs. polygon' do
677
+ before do
678
+ clean_shape(@polygon1)
679
+ end
680
+
681
+ it 'returns true if point is in shape' do
682
+ points = [
683
+ Snow::Vec3[0, 0, 0],
684
+ Snow::Vec3[0, 4, 0],
685
+ Snow::Vec3[0, -4, 0],
686
+ Snow::Vec3[4, -4, 0],
687
+ Snow::Vec3[-4, -4, 0],
688
+ Snow::Vec3[0, 5, 0],
689
+ Snow::Vec3[0, -5, 0],
690
+ Snow::Vec3[5, -5, 0],
691
+ Snow::Vec3[-5, -5, 0],
692
+ ]
693
+ points.each do |p|
694
+ expect(Gosling::Collision.is_point_in_shape?(p, @polygon1)).to be true
695
+ end
696
+ end
697
+
698
+ it 'returns false if point is not in shape' do
699
+ points = [
700
+ Snow::Vec3[0, 6, 0],
701
+ Snow::Vec3[0, -6, 0],
702
+ Snow::Vec3[6, -6, 0],
703
+ Snow::Vec3[-6, -6, 0],
704
+ Snow::Vec3[4, 4, 0],
705
+ Snow::Vec3[-4, 4, 0],
706
+ ]
707
+ points.each do |p|
708
+ expect(Gosling::Collision.is_point_in_shape?(p, @polygon1)).to be false
709
+ end
710
+ end
711
+ end
712
+
713
+ context 'point vs. rect' do
714
+ before do
715
+ clean_rect(@rect1)
716
+ end
717
+
718
+ it 'returns true if point is in shape' do
719
+ points = [
720
+ Snow::Vec3[0, 0, 0],
721
+ Snow::Vec3[-4, -4, 0],
722
+ Snow::Vec3[0, -4, 0],
723
+ Snow::Vec3[4, -4, 0],
724
+ Snow::Vec3[4, 0, 0],
725
+ Snow::Vec3[4, 4, 0],
726
+ Snow::Vec3[0, 4, 0],
727
+ Snow::Vec3[-4, 4, 0],
728
+ Snow::Vec3[-4, 0, 0],
729
+ Snow::Vec3[-5, -5, 0],
730
+ Snow::Vec3[0, -5, 0],
731
+ Snow::Vec3[5, -5, 0],
732
+ Snow::Vec3[5, 0, 0],
733
+ Snow::Vec3[5, 5, 0],
734
+ Snow::Vec3[0, 5, 0],
735
+ Snow::Vec3[-5, 5, 0],
736
+ Snow::Vec3[-5, 0, 0],
737
+ ]
738
+ points.each do |p|
739
+ expect(Gosling::Collision.is_point_in_shape?(p, @rect1)).to be true
740
+ end
741
+ end
742
+
743
+ it 'returns false if point is not in shape' do
744
+ points = [
745
+ Snow::Vec3[-6, -6, 0],
746
+ Snow::Vec3[0, -6, 0],
747
+ Snow::Vec3[6, -6, 0],
748
+ Snow::Vec3[6, 0, 0],
749
+ Snow::Vec3[6, 6, 0],
750
+ Snow::Vec3[0, 6, 0],
751
+ Snow::Vec3[-6, 6, 0],
752
+ Snow::Vec3[-6, 0, 0],
753
+ ]
754
+ points.each do |p|
755
+ expect(Gosling::Collision.is_point_in_shape?(p, @rect1)).to be false
756
+ end
757
+ end
758
+ end
759
+
760
+ context 'point vs. sprite' do
761
+ before do
762
+ clean_sprite(@sprite1)
763
+ end
764
+
765
+ it 'returns true if point is in shape' do
766
+ points = [
767
+ Snow::Vec3[0, 0, 0],
768
+ Snow::Vec3[-7, -7, 0],
769
+ Snow::Vec3[0, -7, 0],
770
+ Snow::Vec3[7, -7, 0],
771
+ Snow::Vec3[7, 0, 0],
772
+ Snow::Vec3[7, 7, 0],
773
+ Snow::Vec3[0, 7, 0],
774
+ Snow::Vec3[-7, 7, 0],
775
+ Snow::Vec3[-7, 0, 0],
776
+ Snow::Vec3[-8, -8, 0],
777
+ Snow::Vec3[0, -8, 0],
778
+ Snow::Vec3[8, -8, 0],
779
+ Snow::Vec3[8, 0, 0],
780
+ Snow::Vec3[8, 8, 0],
781
+ Snow::Vec3[0, 8, 0],
782
+ Snow::Vec3[-8, 8, 0],
783
+ Snow::Vec3[-8, 0, 0],
784
+ ]
785
+ points.each do |p|
786
+ expect(Gosling::Collision.is_point_in_shape?(Snow::Vec3[-8, 0, 0], @sprite1)).to be true
787
+ end
788
+ end
789
+
790
+ it 'returns false if point is not in shape' do
791
+ points = [
792
+ Snow::Vec3[-9, -9, 0],
793
+ Snow::Vec3[0, -9, 0],
794
+ Snow::Vec3[9, -9, 0],
795
+ Snow::Vec3[9, 0, 0],
796
+ Snow::Vec3[9, 9, 0],
797
+ Snow::Vec3[0, 9, 0],
798
+ Snow::Vec3[-9, 9, 0],
799
+ Snow::Vec3[-9, 0, 0],
800
+ ]
801
+ points.each do |p|
802
+ expect(Gosling::Collision.is_point_in_shape?(Snow::Vec3[-9, 0, 0], @sprite1)).to be false
803
+ end
804
+ end
805
+ end
806
+ end
807
+
808
+ describe '.get_normal' do
809
+ it 'expects a 3d vector' do
810
+ expect { Gosling::Collision.get_normal(Snow::Vec3[1, 0, 0]) }.not_to raise_error
811
+ expect { Gosling::Collision.get_normal(Snow::Vec3[1, 0, 1, 0]) }.to raise_error(ArgumentError)
812
+ expect { Gosling::Collision.get_normal(Snow::Vec3[1, 0]) }.to raise_error(ArgumentError)
813
+ expect { Gosling::Collision.get_normal(:foo) }.to raise_error
814
+ expect { Gosling::Collision.get_normal(nil) }.to raise_error
815
+ end
816
+
817
+ it 'returns a 3d vector' do
818
+ result = Gosling::Collision.get_normal(Snow::Vec3[1, 0, 0])
819
+ expect(result).to be_instance_of(Snow::Vec3)
820
+ end
821
+
822
+ it 'z value of returned vector is always 0' do
823
+ [
824
+ Snow::Vec3[1, 1, 0],
825
+ Snow::Vec3[-1, -1, -1],
826
+ Snow::Vec3[-22, -22, 0],
827
+ Snow::Vec3[-11, 13, 34],
828
+ Snow::Vec3[37, -4, -15],
829
+ Snow::Vec3[34, 39, -16],
830
+ Snow::Vec3[-48, 23, -32],
831
+ Snow::Vec3[24, -39, 42],
832
+ Snow::Vec3[49, 44, -15],
833
+ Snow::Vec3[27, 23, 42],
834
+ Snow::Vec3[33, -25, -20],
835
+ Snow::Vec3[-46, -18, 48],
836
+ ].each do |v|
837
+ expect(Gosling::Collision.get_normal(v)[2]).to be == 0
838
+ end
839
+ end
840
+
841
+ it 'raises an error when given a zero length vector' do
842
+ expect { Gosling::Collision.get_normal(Snow::Vec3[0, 0, 0]) }.to raise_error(ArgumentError)
843
+ end
844
+
845
+ it 'returns a vector that is +/- 90 degrees from the original' do
846
+ [
847
+ Snow::Vec3[1, 1, 0],
848
+ Snow::Vec3[-1, -1, -1],
849
+ Snow::Vec3[-22, -22, 0],
850
+ Snow::Vec3[-11, 13, 34],
851
+ Snow::Vec3[37, -4, -15],
852
+ Snow::Vec3[34, 39, -16],
853
+ Snow::Vec3[-48, 23, -32],
854
+ Snow::Vec3[24, -39, 42],
855
+ Snow::Vec3[49, 44, -15],
856
+ Snow::Vec3[27, 23, 42],
857
+ Snow::Vec3[33, -25, -20],
858
+ Snow::Vec3[-46, -18, 48],
859
+ ].each do |v|
860
+ norm_v = Gosling::Collision.get_normal(v)
861
+ radians = Math.acos(v.dot_product(norm_v) / (v.magnitude * norm_v.magnitude))
862
+ expect(radians.abs).to be == Math::PI / 2
863
+ end
864
+ end
865
+ end
866
+
867
+ describe '.get_polygon_separation_axes' do
868
+ it 'expects an array of length 3 vectors' do
869
+ good_vector_array = [
870
+ Snow::Vec3[3, 1, 0],
871
+ Snow::Vec3[4, 2, 0],
872
+ Snow::Vec3[5, 3, 0],
873
+ Snow::Vec3[1, 4, 0],
874
+ Snow::Vec3[2, 5, 0]
875
+ ]
876
+ bad_vector_array = [
877
+ Snow::Vec2[9, 11],
878
+ Snow::Vec3[7, 12, 0],
879
+ Snow::Vec4[5, 13, 1, 0],
880
+ Snow::Vec2[3, 14],
881
+ Snow::Vec2[1, 15]
882
+ ]
883
+ p = Gosling::Polygon.new(@window)
884
+ expect { Gosling::Collision.get_polygon_separation_axes(good_vector_array) }.not_to raise_error
885
+ expect { Gosling::Collision.get_polygon_separation_axes(bad_vector_array) }.to raise_error
886
+ expect { Gosling::Collision.get_polygon_separation_axes(p.get_vertices) }.not_to raise_error
887
+ expect { Gosling::Collision.get_polygon_separation_axes(p) }.to raise_error
888
+ expect { Gosling::Collision.get_polygon_separation_axes(:foo) }.to raise_error
889
+ end
890
+
891
+ it 'returns an array of 3d vectors' do
892
+ vertices = [
893
+ Snow::Vec3[3, 1, 0],
894
+ Snow::Vec3[4, 2, 0],
895
+ Snow::Vec3[5, 3, 0],
896
+ Snow::Vec3[1, 4, 0],
897
+ Snow::Vec3[2, 5, 0]
898
+ ]
899
+ Gosling::Collision.get_polygon_separation_axes(vertices)
900
+ result = Gosling::Collision.separation_axes
901
+ expect(result).to be_instance_of(Array)
902
+ expect(result.reject { |v| v.is_a?(Snow::Vec3) }).to be_empty
903
+ end
904
+
905
+ it 'skips length zero sides' do
906
+ vertices = [
907
+ Snow::Vec3[1, 1, 0],
908
+ Snow::Vec3[1, 1, 0],
909
+ Snow::Vec3[1, 2, 0],
910
+ Snow::Vec3[2, 2, 0],
911
+ Snow::Vec3[2, 2, 0]
912
+ ]
913
+ Gosling::Collision.get_polygon_separation_axes(vertices)
914
+ result = Gosling::Collision.separation_axes
915
+ expect(result.length).to be == 3
916
+ end
917
+
918
+ it 'returns correct values' do
919
+ vertices = [
920
+ Snow::Vec3[ 2, 1, 0],
921
+ Snow::Vec3[ 1, -1, 0],
922
+ Snow::Vec3[ 0, -2, 0],
923
+ Snow::Vec3[-1, -1, 0],
924
+ Snow::Vec3[-1, 2, 0]
925
+ ]
926
+ Gosling::Collision.get_polygon_separation_axes(vertices)
927
+ result = Gosling::Collision.separation_axes
928
+ expect(result).to match_array([
929
+ Snow::Vec3[ 1, 3, 0].normalize,
930
+ Snow::Vec3[ 2, -1, 0].normalize,
931
+ Snow::Vec3[ 1, -1, 0].normalize,
932
+ Snow::Vec3[-1, -1, 0].normalize,
933
+ Snow::Vec3[-3, 0, 0].normalize
934
+ ])
935
+ end
936
+ end
937
+
938
+ describe '.get_circle_separation_axis' do
939
+ before do
940
+ clean_shape(@circle1)
941
+ clean_shape(@circle2)
942
+ end
943
+
944
+ it 'expects two shape arguments' do
945
+ expect { Gosling::Collision.get_circle_separation_axis(@circle1, @circle2) }.not_to raise_error
946
+ expect { Gosling::Collision.get_circle_separation_axis(@circle1, @polygon1) }.not_to raise_error
947
+ expect { Gosling::Collision.get_circle_separation_axis(@rect1, @circle2) }.not_to raise_error
948
+
949
+ expect { Gosling::Collision.get_circle_separation_axis(@circle1, @circle2, Snow::Vec3.new) }.to raise_error(ArgumentError)
950
+ expect { Gosling::Collision.get_circle_separation_axis(:foo, @circle2) }.to raise_error
951
+ expect { Gosling::Collision.get_circle_separation_axis(@circle1) }.to raise_error(ArgumentError)
952
+ expect { Gosling::Collision.get_circle_separation_axis() }.to raise_error(ArgumentError)
953
+ expect { Gosling::Collision.get_circle_separation_axis(:foo) }.to raise_error(ArgumentError)
954
+ end
955
+
956
+ it 'returns a 3d vector' do
957
+ @circle1.x = 0
958
+ @circle1.y = 0
959
+
960
+ @circle2.x = 10
961
+ @circle2.y = -5
962
+
963
+ Gosling::Collision.get_circle_separation_axis(@circle1, @circle2)
964
+ result = Gosling::Collision.separation_axes
965
+ expect(result.length).to eq(1)
966
+ expect(result.first).to be_instance_of(Snow::Vec3)
967
+ end
968
+
969
+ it "returns nil if distance beween shape centers is 0" do
970
+ @circle1.x = 0
971
+ @circle1.y = 0
972
+
973
+ @circle2.x = 0
974
+ @circle2.y = 0
975
+
976
+ result = Gosling::Collision.get_circle_separation_axis(@circle1, @circle2)
977
+ expect(result).to be nil
978
+ end
979
+
980
+ it 'returns a correct unit vector' do
981
+ @circle1.x = 5
982
+ @circle1.y = -10
983
+
984
+ @circle2.x = 10
985
+ @circle2.y = -5
986
+
987
+ Gosling::Collision.get_circle_separation_axis(@circle1, @circle2)
988
+ result = Gosling::Collision.separation_axes
989
+ expect(result.length).to eq(1)
990
+ expect(result.first).to be == Snow::Vec3[1, 1, 0].normalize
991
+ end
992
+ end
993
+
994
+ describe '.get_separation_axes' do
995
+ it 'expects two shapes' do
996
+ expect { Gosling::Collision.get_separation_axes(@circle1, @circle2) }.not_to raise_error
997
+ expect { Gosling::Collision.get_separation_axes(@circle1, @polygon2) }.not_to raise_error
998
+ expect { Gosling::Collision.get_separation_axes(@polygon1, @polygon2) }.not_to raise_error
999
+ expect { Gosling::Collision.get_separation_axes(@polygon1, @rect2) }.not_to raise_error
1000
+ expect { Gosling::Collision.get_separation_axes(@sprite1, @polygon2) }.not_to raise_error
1001
+
1002
+ expect { Gosling::Collision.get_separation_axes(@actor1, @circle2) }.to raise_error(ArgumentError)
1003
+ expect { Gosling::Collision.get_separation_axes(@circle1, @circle2, @polygon2) }.to raise_error
1004
+ expect { Gosling::Collision.get_separation_axes(@circle1, 1) }.to raise_error(ArgumentError)
1005
+ expect { Gosling::Collision.get_separation_axes(@polygon1, :foo) }.to raise_error(ArgumentError)
1006
+ expect { Gosling::Collision.get_separation_axes(:foo) }.to raise_error(ArgumentError)
1007
+ expect { Gosling::Collision.get_separation_axes() }.to raise_error(ArgumentError)
1008
+ end
1009
+
1010
+ it 'returns an array of 3d vectors' do
1011
+ Gosling::Collision.get_separation_axes(@polygon1, @polygon2)
1012
+ result = Gosling::Collision.separation_axes
1013
+ expect(result).to be_instance_of(Array)
1014
+ expect(result.reject { |v| v.is_a?(Snow::Vec3) }).to be_empty
1015
+ end
1016
+
1017
+ it 'returns only unit vectors' do
1018
+ Gosling::Collision.get_separation_axes(@polygon1, @circle1)
1019
+ result = Gosling::Collision.separation_axes
1020
+ expect(result).to be_instance_of(Array)
1021
+ result.each do |v|
1022
+ expect(v).to be_instance_of(Snow::Vec3)
1023
+ expect(v.magnitude).to be_between(0.99999999, 1.00000001)
1024
+ end
1025
+ end
1026
+
1027
+ it 'returns only unique vectors' do
1028
+ Gosling::Collision.get_separation_axes(@rect2, @polygon2)
1029
+ result = Gosling::Collision.separation_axes
1030
+ expect(result).to be_instance_of(Array)
1031
+ expect(result.uniq.length).to be == result.length
1032
+ end
1033
+
1034
+ it 'is commutative' do
1035
+ Gosling::Collision.get_separation_axes(@rect2, @polygon2)
1036
+ result1 = Gosling::Collision.separation_axes.dup
1037
+ Gosling::Collision.get_separation_axes(@polygon2, @rect2)
1038
+ result2 = Gosling::Collision.separation_axes.dup
1039
+ expect(result1.map { |v| v.to_s }).to match_array(result2.map { |v| v.to_s })
1040
+ end
1041
+
1042
+ it 'respects centering' do
1043
+ clean_shape(@polygon1)
1044
+ @polygon1.center_x = 10
1045
+ @polygon1.center_y = 2
1046
+
1047
+ clean_shape(@circle1)
1048
+
1049
+ Gosling::Collision.get_separation_axes(@polygon1, @circle1)
1050
+ result = Gosling::Collision.separation_axes
1051
+ expect(result).to match_array([
1052
+ Snow::Vec3[10, 5, 0].normalize,
1053
+ Snow::Vec3[0, -10, 0].normalize,
1054
+ Snow::Vec3[-10, 5, 0].normalize
1055
+ ])
1056
+ end
1057
+
1058
+ it 'respects scaling' do
1059
+ clean_shape(@polygon1)
1060
+ @polygon1.scale_x = 3
1061
+ @polygon1.scale_y = 2
1062
+
1063
+ clean_shape(@circle1)
1064
+
1065
+ Gosling::Collision.get_separation_axes(@polygon1, @circle1)
1066
+ result = Gosling::Collision.separation_axes
1067
+ expect(result).to match_array([
1068
+ Snow::Vec3[20, 15, 0].normalize,
1069
+ Snow::Vec3[0, -30, 0].normalize,
1070
+ Snow::Vec3[-20, 15, 0].normalize
1071
+ ])
1072
+ end
1073
+
1074
+ it 'respects rotation' do
1075
+ clean_shape(@polygon1)
1076
+ @polygon1.rotation = Math::PI / 2
1077
+ Gosling::Collision.get_separation_axes(@polygon1, @circle1)
1078
+ result = Gosling::Collision.separation_axes
1079
+
1080
+ clean_shape(@circle1)
1081
+
1082
+ expect(result).to match_array([
1083
+ Snow::Vec3[5, -10, 0].normalize,
1084
+ Snow::Vec3[-10, 0, 0].normalize,
1085
+ Snow::Vec3[5, 10, 0].normalize
1086
+ ])
1087
+ end
1088
+
1089
+ it 'respects translation' do
1090
+ clean_shape(@polygon1)
1091
+ @polygon1.x = -50
1092
+ @polygon1.y = 10
1093
+
1094
+ clean_shape(@circle1)
1095
+
1096
+ Gosling::Collision.get_separation_axes(@polygon1, @circle1)
1097
+ result = Gosling::Collision.separation_axes
1098
+ expect(result).to match_array([
1099
+ Snow::Vec3[10, 5, 0].normalize,
1100
+ Snow::Vec3[0, -10, 0].normalize,
1101
+ Snow::Vec3[-10, 5, 0].normalize,
1102
+ Snow::Vec3[50, -10, 0].normalize
1103
+ ])
1104
+ end
1105
+
1106
+ context 'with two polygons' do
1107
+ it 'returns an array with no more axes than total vertices, and no less than two' do
1108
+ [
1109
+ [@polygon1, @polygon2],
1110
+ [@polygon1, @rect1],
1111
+ [@polygon1, @rect2],
1112
+ [@polygon1, @sprite1],
1113
+ [@polygon1, @sprite2],
1114
+ [@polygon2, @rect1],
1115
+ [@polygon2, @rect2],
1116
+ [@polygon2, @sprite1],
1117
+ [@polygon2, @sprite2],
1118
+ [@rect1, @rect2],
1119
+ [@rect1, @sprite1],
1120
+ [@rect1, @sprite2],
1121
+ [@rect2, @sprite1],
1122
+ [@rect2, @sprite2],
1123
+ [@sprite1, @sprite2]
1124
+ ].each do |shapes|
1125
+ Gosling::Collision.get_separation_axes(*shapes)
1126
+ result = Gosling::Collision.separation_axes
1127
+ vertex_count = 0
1128
+ shapes.each { |s| vertex_count += s.get_vertices.length }
1129
+ expect(result.length).to be_between(2, vertex_count).inclusive
1130
+ end
1131
+ end
1132
+ end
1133
+
1134
+ context 'with two circles' do
1135
+ context 'when both circles have the same center' do
1136
+ it 'returns an empty array' do
1137
+ @circle1.x = 0
1138
+ @circle1.y = 0
1139
+ @circle2.x = 0
1140
+ @circle2.y = 0
1141
+ Gosling::Collision.get_separation_axes(@circle1, @circle2)
1142
+ result = Gosling::Collision.separation_axes
1143
+ expect(result).to be_instance_of(Array)
1144
+ expect(result).to be_empty
1145
+ end
1146
+ end
1147
+
1148
+ it 'returns an array with one axis' do
1149
+ @circle1.x = 1
1150
+ @circle1.y = 0
1151
+ @circle2.x = 17
1152
+ @circle2.y = -5
1153
+ Gosling::Collision.get_separation_axes(@circle1, @circle2)
1154
+ result = Gosling::Collision.separation_axes
1155
+ expect(result).to be_instance_of(Array)
1156
+ expect(result.length).to be == 1
1157
+ end
1158
+ end
1159
+
1160
+ context 'with a polygon and a circle' do
1161
+ it 'returns an array with no more axes than total vertices plus one, and no less than two' do
1162
+ [
1163
+ [@circle1, @polygon1],
1164
+ [@circle2, @polygon2],
1165
+ [@circle1, @rect1],
1166
+ [@circle2, @rect2],
1167
+ [@circle1, @sprite1],
1168
+ [@circle2, @sprite2]
1169
+ ].each do |shapes|
1170
+ Gosling::Collision.get_separation_axes(*shapes)
1171
+ result = Gosling::Collision.separation_axes
1172
+ vertex_count = shapes[1].get_vertices.length
1173
+ expect(result.length).to be_between(2, vertex_count + 1).inclusive
1174
+ end
1175
+ end
1176
+ end
1177
+ end
1178
+
1179
+ describe '.project_onto_axis' do
1180
+ it 'expects a shape and a 3d or better unit vector' do
1181
+ axis = Snow::Vec3[1, 1, 0]
1182
+
1183
+ expect { Gosling::Collision.project_onto_axis(@sprite1, axis) }.not_to raise_error
1184
+ expect { Gosling::Collision.project_onto_axis(@rect1, axis) }.not_to raise_error
1185
+ expect { Gosling::Collision.project_onto_axis(@circle1, axis) }.not_to raise_error
1186
+ expect { Gosling::Collision.project_onto_axis(@polygon1, axis) }.not_to raise_error
1187
+
1188
+ expect { Gosling::Collision.project_onto_axis(:foo, axis) }.to raise_error
1189
+ expect { Gosling::Collision.project_onto_axis(@sprite1, Snow::Vec4[1, 1, 0, 2]) }.not_to raise_error
1190
+ expect { Gosling::Collision.project_onto_axis(@rect1, Snow::Vec2[1, 1]) }.to raise_error(ArgumentError)
1191
+ expect { Gosling::Collision.project_onto_axis(@polygon1, :foo) }.to raise_error(ArgumentError)
1192
+ expect { Gosling::Collision.project_onto_axis(@circle1, @circle1, axis) }.to raise_error
1193
+ expect { Gosling::Collision.project_onto_axis() }.to raise_error(ArgumentError)
1194
+ end
1195
+
1196
+ it 'returns an array of two numbers' do
1197
+ axis = Snow::Vec3[1, 1, 0]
1198
+ result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1199
+ expect(result).to be_instance_of(Array)
1200
+ expect(result.length).to be == 2
1201
+ expect(result.reject { |x| x.is_a?(Numeric) }).to be_empty
1202
+ end
1203
+
1204
+ it 'works with four dimensional axes' do
1205
+ clean_shape(@circle1)
1206
+ @circle1.x = 1
1207
+ @circle1.y = 1
1208
+ @circle1.radius = 5
1209
+
1210
+ axis = Snow::Vec4[-1, 1, 0, 0].normalize
1211
+ result = Gosling::Collision.project_onto_axis(@circle1, axis)
1212
+ expect(result).to be == [-5, 5]
1213
+
1214
+ axis.z = 2
1215
+ axis.w = -3
1216
+ result = Gosling::Collision.project_onto_axis(@circle1, axis)
1217
+ expect(result).to be == [-5, 5]
1218
+ end
1219
+
1220
+ context 'with a circle' do
1221
+ before do
1222
+ clean_shape(@circle1)
1223
+ end
1224
+
1225
+ it 'returns expected values' do
1226
+ @circle1.x = 0
1227
+ @circle1.y = 0
1228
+ @circle1.radius = 5
1229
+
1230
+ axis = Snow::Vec3[-1, 1, 0].normalize
1231
+ result = Gosling::Collision.project_onto_axis(@circle1, axis)
1232
+ expect(result).to be == [-5, 5]
1233
+
1234
+ clean_shape(@circle1)
1235
+ @circle1.x = 5
1236
+ @circle1.y = 0
1237
+ @circle1.radius = 5
1238
+
1239
+ axis = Snow::Vec3[1, 0, 0].normalize
1240
+ result = Gosling::Collision.project_onto_axis(@circle1, axis)
1241
+ expect(result).to be == [0, 10]
1242
+ end
1243
+
1244
+ it 'respects centering' do
1245
+ @circle1.center_x = 3
1246
+ @circle1.center_y = 6
1247
+
1248
+ axis = Snow::Vec3[1, 0, 0].normalize
1249
+ result = Gosling::Collision.project_onto_axis(@circle1, axis)
1250
+ expect(result).to be == [-8, 2]
1251
+
1252
+ axis = Snow::Vec3[0, 1, 0].normalize
1253
+ result = Gosling::Collision.project_onto_axis(@circle1, axis)
1254
+ expect(result).to be == [-11, -1]
1255
+ end
1256
+
1257
+ it 'respects scaling' do
1258
+ @circle1.scale_x = 2
1259
+ @circle1.scale_y = 0.5
1260
+
1261
+ axis = Snow::Vec3[1, 0, 0].normalize
1262
+ result = Gosling::Collision.project_onto_axis(@circle1, axis)
1263
+ expect(result).to be == [-10, 10]
1264
+
1265
+ axis = Snow::Vec3[0, 1, 0].normalize
1266
+ result = Gosling::Collision.project_onto_axis(@circle1, axis)
1267
+ expect(result).to be == [-2.5, 2.5]
1268
+ end
1269
+
1270
+ it 'respects rotation' do
1271
+ @circle1.rotation = Math::PI
1272
+
1273
+ axis = Snow::Vec3[1, 0, 0].normalize
1274
+ result = Gosling::Collision.project_onto_axis(@circle1, axis)
1275
+ expect(result).to be == [-5, 5]
1276
+ end
1277
+
1278
+ it 'respects translation' do
1279
+ @circle1.x = -12
1280
+ @circle1.y = 23
1281
+
1282
+ axis = Snow::Vec3[1, 0, 0].normalize
1283
+ result = Gosling::Collision.project_onto_axis(@circle1, axis)
1284
+ expect(result).to be == [-17, -7]
1285
+
1286
+ axis = Snow::Vec3[0, 1, 0].normalize
1287
+ result = Gosling::Collision.project_onto_axis(@circle1, axis)
1288
+ expect(result).to be == [18, 28]
1289
+ end
1290
+
1291
+ it 'respects its entire ancestry of transforms' do
1292
+ circle = Gosling::Circle.new(@window)
1293
+ clean_shape(circle)
1294
+ circle.radius = 10
1295
+
1296
+ create_inheritance_chain([@center_actor, @scale_actor, @rotate_actor, @translate_actor, circle])
1297
+
1298
+ axis = Snow::Vec3[1, 0, 0].normalize
1299
+ result = Gosling::Collision.project_onto_axis(circle, axis)
1300
+ expect(result).to be == [-936.0, -866.0]
1301
+
1302
+ axis = Snow::Vec3[0, 1, 0].normalize
1303
+ result = Gosling::Collision.project_onto_axis(circle, axis)
1304
+ expect(result[0]).to be_within(FLOAT_TOLERANCE).of(290.0)
1305
+ expect(result[1]).to be_within(FLOAT_TOLERANCE).of(340.0)
1306
+
1307
+ axis = Snow::Vec3[1, 1, 0].normalize
1308
+ result = Gosling::Collision.project_onto_axis(circle, axis)
1309
+ expect(result[0]).to be_within(FLOAT_TOLERANCE).of(-443.1343965537543)
1310
+ expect(result[1]).to be_within(FLOAT_TOLERANCE).of(-385.5947509968793)
1311
+
1312
+ break_inheritance_chain([@center_actor, @scale_actor, @rotate_actor, @translate_actor, circle])
1313
+ end
1314
+ end
1315
+
1316
+ context 'with a polygon' do
1317
+ it 'returns expected values' do
1318
+ axis = Snow::Vec3[1, 0, 0].normalize
1319
+ clean_shape(@polygon2)
1320
+ @polygon2.x = 0
1321
+ @polygon2.y = 0
1322
+ result = Gosling::Collision.project_onto_axis(@polygon2, axis)
1323
+ expect(result).to be == [-5, 5]
1324
+
1325
+ axis = Snow::Vec3[0, 1, 0].normalize
1326
+ clean_shape(@polygon1)
1327
+ @polygon1.x = 0
1328
+ @polygon1.y = 5
1329
+ result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1330
+ expect(result).to be == [0, 10]
1331
+
1332
+ axis = Snow::Vec3[1, -1, 0].normalize
1333
+ clean_shape(@polygon1)
1334
+ @polygon1.x = 0
1335
+ @polygon1.y = 0
1336
+ result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1337
+ expect(result[0]).to be_within(0.00000001).of(-Math.sqrt(25 * 0.5))
1338
+ expect(result[1]).to be_within(0.00000001).of(Math.sqrt(50))
1339
+ end
1340
+
1341
+ it 'respects centering' do
1342
+ clean_shape(@polygon1)
1343
+ @polygon1.center_x = 5
1344
+ @polygon1.center_y = -1
1345
+
1346
+ axis = Snow::Vec3[1, 0, 0].normalize
1347
+ result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1348
+ expect(result).to be == [-10, 0]
1349
+
1350
+ axis = Snow::Vec3[0, 1, 0].normalize
1351
+ result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1352
+ expect(result).to be == [-4, 6]
1353
+ end
1354
+
1355
+ it 'respects scaling' do
1356
+ clean_shape(@polygon1)
1357
+ @polygon1.scale_x = 3
1358
+ @polygon1.scale_y = 2
1359
+
1360
+ axis = Snow::Vec3[1, 0, 0].normalize
1361
+ result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1362
+ expect(result).to be == [-15, 15]
1363
+
1364
+ axis = Snow::Vec3[0, 1, 0].normalize
1365
+ result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1366
+ expect(result).to be == [-10, 10]
1367
+ end
1368
+
1369
+ it 'respects rotation' do
1370
+ clean_shape(@polygon1)
1371
+ @polygon1.rotation = Math::PI / 4
1372
+
1373
+ axis = Snow::Vec3[1, 0, 0].normalize
1374
+ result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1375
+ expect(result).to be == [-7.0710678118654755, 3.5355339059327373]
1376
+
1377
+ axis = Snow::Vec3[0, 1, 0].normalize
1378
+ result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1379
+ expect(result).to be == [-7.0710678118654755, 3.5355339059327378]
1380
+ end
1381
+
1382
+ it 'respects translation' do
1383
+ clean_shape(@polygon1)
1384
+ @polygon1.x = -7
1385
+ @polygon1.y = 13
1386
+
1387
+ axis = Snow::Vec3[1, 0, 0].normalize
1388
+ result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1389
+ expect(result).to be == [-12, -2]
1390
+
1391
+ axis = Snow::Vec3[0, 1, 0].normalize
1392
+ result = Gosling::Collision.project_onto_axis(@polygon1, axis)
1393
+ expect(result).to be == [8, 18]
1394
+ end
1395
+
1396
+ it 'respects its entire ancestry of transforms' do
1397
+ polygon = Gosling::Polygon.new(@window)
1398
+ clean_shape(polygon)
1399
+ polygon.set_vertices(@polygon1.get_vertices)
1400
+
1401
+ create_inheritance_chain([@center_actor, @scale_actor, @rotate_actor, @translate_actor, polygon])
1402
+
1403
+ axis = Snow::Vec3[1, 0, 0].normalize
1404
+ result = Gosling::Collision.project_onto_axis(polygon, axis)
1405
+ expect(result).to be == [-918.5, -883.5]
1406
+
1407
+ axis = Snow::Vec3[0, 1, 0].normalize
1408
+ result = Gosling::Collision.project_onto_axis(polygon, axis)
1409
+ expect(result[0]).to be_within(FLOAT_TOLERANCE).of(302.5)
1410
+ expect(result[1]).to be_within(FLOAT_TOLERANCE).of(327.5)
1411
+
1412
+ axis = Snow::Vec3[1, 1, 0].normalize
1413
+ result = Gosling::Collision.project_onto_axis(polygon, axis)
1414
+ expect(result[0]).to be_within(FLOAT_TOLERANCE).of(-426.7389424460814)
1415
+ expect(result[1]).to be_within(FLOAT_TOLERANCE).of(-393.1513703397204)
1416
+
1417
+ break_inheritance_chain([@center_actor, @scale_actor, @rotate_actor, @translate_actor, polygon])
1418
+ end
1419
+ end
1420
+ end
1421
+
1422
+ describe '.projections_overlap?' do
1423
+ it 'accepts two length 2 arrays with numbers' do
1424
+ expect { Gosling::Collision.projections_overlap?([0, 0], [0, 0]) }.not_to raise_error
1425
+ expect { Gosling::Collision.projections_overlap?([1, 2], [3, -4]) }.not_to raise_error
1426
+
1427
+ expect { Gosling::Collision.projections_overlap?([1, 2, 3], [4, 5, 6]) }.to raise_error(ArgumentError)
1428
+ expect { Gosling::Collision.projections_overlap?([1], [4]) }.to raise_error(ArgumentError)
1429
+ expect { Gosling::Collision.projections_overlap?([1, 2], [3, -4], [5, 6]) }.to raise_error(ArgumentError)
1430
+ expect { Gosling::Collision.projections_overlap?([1, 2]) }.to raise_error(ArgumentError)
1431
+ expect { Gosling::Collision.projections_overlap?([1, 2], :foo) }.to raise_error(ArgumentError)
1432
+ expect { Gosling::Collision.projections_overlap?(nil, [1, 2]) }.to raise_error
1433
+ end
1434
+
1435
+ context 'when a and b do not overlap' do
1436
+ it 'returns false' do
1437
+ expect(Gosling::Collision.projections_overlap?([0, 10], [20, 30])).to be false
1438
+ expect(Gosling::Collision.projections_overlap?([-20, -30], [0, 10])).to be false
1439
+ end
1440
+ end
1441
+
1442
+ context 'when a contains b' do
1443
+ it 'returns true' do
1444
+ expect(Gosling::Collision.projections_overlap?([0, 40], [20, 30])).to be true
1445
+ expect(Gosling::Collision.projections_overlap?([-40, 0], [-25, -15])).to be true
1446
+ expect(Gosling::Collision.projections_overlap?([-2, 0], [-1, 0])).to be true
1447
+ end
1448
+ end
1449
+
1450
+ context 'when b contains a' do
1451
+ it 'returns true' do
1452
+ expect(Gosling::Collision.projections_overlap?([5, 10], [0, 50])).to be true
1453
+ expect(Gosling::Collision.projections_overlap?([-10, 10], [-25, 25])).to be true
1454
+ expect(Gosling::Collision.projections_overlap?([5, 6], [5, 10])).to be true
1455
+ end
1456
+ end
1457
+
1458
+ context 'when a overlaps b' do
1459
+ it 'returns true' do
1460
+ expect(Gosling::Collision.projections_overlap?([-10, 10], [0, 20])).to be true
1461
+ expect(Gosling::Collision.projections_overlap?([-1000, 0], [-1, 314159])).to be true
1462
+ end
1463
+ end
1464
+
1465
+ context 'when a touches b' do
1466
+ it 'returns false' do
1467
+ expect(Gosling::Collision.projections_overlap?([-10, 0], [0, 10])).to be false
1468
+ expect(Gosling::Collision.projections_overlap?([-5, 30], [-17, -5])).to be false
1469
+ end
1470
+ end
1471
+
1472
+ context 'when a just barely overlaps b' do
1473
+ it 'returns false' do
1474
+ expect(Gosling::Collision.projections_overlap?([-10, 0.0000001], [0, 10])).to be false
1475
+ expect(Gosling::Collision.projections_overlap?([-4.999999999, 30], [-17, -5])).to be false
1476
+ end
1477
+ end
1478
+ end
1479
+
1480
+ describe '.get_overlap' do
1481
+ it 'accepts two length 2 arrays with numbers' do
1482
+ expect { Gosling::Collision.get_overlap([0, 0], [0, 0]) }.not_to raise_error
1483
+ expect { Gosling::Collision.get_overlap([1, 2], [3, -4]) }.not_to raise_error
1484
+
1485
+ expect { Gosling::Collision.get_overlap([1, 2, 3], [4, 5, 6]) }.to raise_error(ArgumentError)
1486
+ expect { Gosling::Collision.get_overlap([1], [4]) }.to raise_error(ArgumentError)
1487
+ expect { Gosling::Collision.get_overlap([1, 2], [3, -4], [5, 6]) }.to raise_error(ArgumentError)
1488
+ expect { Gosling::Collision.get_overlap([1, 2]) }.to raise_error(ArgumentError)
1489
+ expect { Gosling::Collision.get_overlap([1, 2], :foo) }.to raise_error(ArgumentError)
1490
+ expect { Gosling::Collision.get_overlap(nil, [1, 2]) }.to raise_error
1491
+ end
1492
+
1493
+ context 'when a and b do not overlap' do
1494
+ it 'returns nil' do
1495
+ expect(Gosling::Collision.get_overlap([0, 10], [20, 30])).to be_nil
1496
+ expect(Gosling::Collision.get_overlap([-20, -30], [0, 10])).to be_nil
1497
+ end
1498
+ end
1499
+
1500
+ context 'when a contains b' do
1501
+ it 'returns the length of b' do
1502
+ expect(Gosling::Collision.get_overlap([0, 40], [20, 30])).to eq(10)
1503
+ expect(Gosling::Collision.get_overlap([-40, 0], [-25, -15])).to eq(10)
1504
+ expect(Gosling::Collision.get_overlap([-2, 0], [-1, 0])).to eq(1)
1505
+ end
1506
+ end
1507
+
1508
+ context 'when b contains a' do
1509
+ it 'returns the length of a' do
1510
+ expect(Gosling::Collision.get_overlap([5, 10], [0, 50])).to eq(5)
1511
+ expect(Gosling::Collision.get_overlap([-10, 10], [-25, 25])).to eq(20)
1512
+ expect(Gosling::Collision.get_overlap([5, 6], [5, 10])).to eq(1)
1513
+ end
1514
+ end
1515
+
1516
+ context 'when a overlaps b' do
1517
+ it 'returns the length that overlaps' do
1518
+ expect(Gosling::Collision.get_overlap([-10, 10], [0, 20])).to eq(10)
1519
+ expect(Gosling::Collision.get_overlap([-1000, 0], [-1, 314159])).to eq(1)
1520
+ end
1521
+ end
1522
+
1523
+ context 'when a touches b' do
1524
+ it 'returns zero' do
1525
+ expect(Gosling::Collision.get_overlap([-10, 0], [0, 10])).to eq(0)
1526
+ expect(Gosling::Collision.get_overlap([-5, 30], [-17, -5])).to eq(0)
1527
+ end
1528
+ end
1529
+
1530
+ context 'when a just barely overlaps b' do
1531
+ it 'returns a very tiny value' do
1532
+ expect(Gosling::Collision.get_overlap([-10, 0.0000001], [0, 10])).to eq(0.0000001)
1533
+ expect(Gosling::Collision.get_overlap([-5, 30], [-17, -4.999999999])).to be_within(0.00000001).of(0)
1534
+ end
1535
+ end
1536
+ end
1537
+
1538
+ describe '.buffer_shapes' do
1539
+ it 'accepts an array of actors' do
1540
+ expect { Gosling::Collision.buffer_shapes([]) }.not_to raise_error
1541
+ expect { Gosling::Collision.buffer_shapes([@actor1, @circle1, @polygon1, @rect1, @sprite1]) }.not_to raise_error
1542
+
1543
+ expect { Gosling::Collision.buffer_shapes(@actor1) }.to raise_error(ArgumentError)
1544
+ expect { Gosling::Collision.buffer_shapes(:foo) }.to raise_error(ArgumentError)
1545
+ expect { Gosling::Collision.buffer_shapes(nil) }.to raise_error(ArgumentError)
1546
+ end
1547
+
1548
+ it 'resets the buffer iterators' do
1549
+ expect(Gosling::Collision).to receive(:reset_buffer_iterators)
1550
+ Gosling::Collision.buffer_shapes([@actor1])
1551
+ end
1552
+
1553
+ context 'when actors are initially buffered' do
1554
+ before(:all) do
1555
+ Gosling::Collision.clear_buffer
1556
+ [@actor1, @circle1, @polygon1, @rect1, @sprite1].each { |a|
1557
+ @scale_actor.add_child(a)
1558
+ a.x = 0
1559
+ a.y = 0
1560
+ }
1561
+ Gosling::Collision.buffer_shapes([@actor1, @circle1, @polygon1, @rect1, @sprite1])
1562
+ end
1563
+
1564
+ it 'adds those actors to the collision test set' do
1565
+ [@circle1, @polygon1, @rect1, @sprite1].each do |actor|
1566
+ expect(Gosling::Collision.collision_buffer).to include(actor)
1567
+ end
1568
+ end
1569
+
1570
+ it 'caches computationally expensive information about each actor' do
1571
+ expect(Gosling::Collision.global_vertices_cache.length).to eq(3)
1572
+ expect(Gosling::Collision.global_position_cache.length).to eq(4)
1573
+ expect(Gosling::Collision.global_transform_cache.length).to eq(4)
1574
+
1575
+ [@actor1, @circle1, @polygon1, @rect1, @sprite1].each do |actor|
1576
+ expect(actor).not_to receive(:get_global_vertices)
1577
+ expect(actor).not_to receive(:get_global_position)
1578
+ expect(actor).not_to receive(:get_global_transform)
1579
+ end
1580
+
1581
+ collisions = []
1582
+ while true
1583
+ info = Gosling::Collision.next_collision_info
1584
+ break unless info
1585
+ collisions << info
1586
+ end
1587
+ expect(collisions.length).to eq(6)
1588
+ end
1589
+
1590
+ it 'only caches info for children of the Actor class' do
1591
+ [@actor1].each do |actor|
1592
+ expect(Gosling::Collision.collision_buffer).not_to include(actor)
1593
+ end
1594
+ end
1595
+
1596
+ context 'and then re-buffered' do
1597
+ it 'updates info for already buffered actors' do
1598
+ [@circle1, @circle2].each do |actor|
1599
+ expect(actor).to receive(:get_global_position).once.and_call_original
1600
+ expect(actor).to receive(:get_global_transform).twice.and_call_original
1601
+ end
1602
+ [@rect1].each do |actor|
1603
+ expect(actor).to receive(:get_global_vertices).once.and_call_original
1604
+ expect(actor).to receive(:get_global_transform).exactly(3).times.and_call_original
1605
+ end
1606
+
1607
+ Gosling::Collision.buffer_shapes([@circle1, @circle2, @rect1])
1608
+
1609
+ [@circle1, @circle2, @rect1].each do |actor|
1610
+ expect(Gosling::Collision.collision_buffer.select { |a| a == actor }.length).to eq(1)
1611
+ end
1612
+ expect(Gosling::Collision.global_vertices_cache.length).to eq(3)
1613
+ expect(Gosling::Collision.global_position_cache.length).to eq(5)
1614
+ expect(Gosling::Collision.global_transform_cache.length).to eq(5)
1615
+ end
1616
+ end
1617
+
1618
+ after(:all) do
1619
+ Gosling::Collision.clear_buffer
1620
+ [@actor1, @circle1, @polygon1, @rect1, @sprite1].each { |a| @scale_actor.remove_child(a) }
1621
+ end
1622
+ end
1623
+ end
1624
+
1625
+ describe '.next_collision_info' do
1626
+ before(:all) do
1627
+ Gosling::Collision.clear_buffer
1628
+ [@circle1, @polygon1, @rect1, @sprite1].each { |a| a.x = 0; a.y = 0 }
1629
+ Gosling::Collision.buffer_shapes([@circle1, @polygon1, @rect1, @sprite1])
1630
+ end
1631
+
1632
+ it 'returns collision information for the next pair of colliding actors, then nil when done' do
1633
+ info = Gosling::Collision.next_collision_info
1634
+ expect(info[:actors]).to include(@polygon1, @circle1)
1635
+ expect(info[:colliding]).to be true
1636
+
1637
+ info = Gosling::Collision.next_collision_info
1638
+ expect(info[:actors]).to include(@rect1, @circle1)
1639
+ expect(info[:colliding]).to be true
1640
+
1641
+ info = Gosling::Collision.next_collision_info
1642
+ expect(info[:actors]).to include(@rect1, @polygon1)
1643
+ expect(info[:colliding]).to be true
1644
+
1645
+ info = Gosling::Collision.next_collision_info
1646
+ expect(info[:actors]).to include(@sprite1, @circle1)
1647
+ expect(info[:colliding]).to be true
1648
+
1649
+ info = Gosling::Collision.next_collision_info
1650
+ expect(info[:actors]).to include(@sprite1, @polygon1)
1651
+ expect(info[:colliding]).to be true
1652
+
1653
+ info = Gosling::Collision.next_collision_info
1654
+ expect(info[:actors]).to include(@sprite1, @rect1)
1655
+ expect(info[:colliding]).to be true
1656
+
1657
+ expect(Gosling::Collision.next_collision_info).to be_nil
1658
+ end
1659
+
1660
+ after(:all) do
1661
+ Gosling::Collision.clear_buffer
1662
+ end
1663
+ end
1664
+
1665
+ describe '.peek_at_next_collision' do
1666
+ before(:all) do
1667
+ Gosling::Collision.clear_buffer
1668
+ [@circle1, @polygon1, @rect1, @sprite1].each { |a| a.x = 0; a.y = 0 }
1669
+ Gosling::Collision.buffer_shapes([@circle1, @polygon1, @rect1, @sprite1])
1670
+ end
1671
+
1672
+ it 'returns references to the next two buffered actors to be collision tested, if any' do
1673
+ expect(Gosling::Collision.peek_at_next_collision).to eq([@polygon1, @circle1])
1674
+ 2.times { Gosling::Collision.skip_next_collision }
1675
+ expect(Gosling::Collision.peek_at_next_collision).to eq([@rect1, @polygon1])
1676
+ 2.times { Gosling::Collision.skip_next_collision }
1677
+ info = Gosling::Collision.next_collision_info
1678
+ expect(info[:actors]).to include(@sprite1, @polygon1)
1679
+ 2.times { Gosling::Collision.skip_next_collision }
1680
+ expect(Gosling::Collision.peek_at_next_collision).to be_nil
1681
+ end
1682
+
1683
+ after(:all) do
1684
+ Gosling::Collision.clear_buffer
1685
+ end
1686
+ end
1687
+
1688
+ describe '.skip_next_collision' do
1689
+ before(:all) do
1690
+ Gosling::Collision.clear_buffer
1691
+ [@circle1, @polygon1, @rect1, @sprite1].each { |a| a.x = 0; a.y = 0 }
1692
+ Gosling::Collision.buffer_shapes([@circle1, @polygon1, @rect1])
1693
+ end
1694
+
1695
+ it 'moves the collision iterators forward without performing any collision testing' do
1696
+ expect(Gosling::Collision.peek_at_next_collision).to eq([@polygon1, @circle1])
1697
+ Gosling::Collision.skip_next_collision
1698
+ expect(Gosling::Collision.peek_at_next_collision).to eq([@rect1, @circle1])
1699
+ Gosling::Collision.skip_next_collision
1700
+ info = Gosling::Collision.next_collision_info
1701
+ expect(info[:actors]).to include(@rect1, @polygon1)
1702
+ Gosling::Collision.skip_next_collision
1703
+ expect(Gosling::Collision.peek_at_next_collision).to be_nil
1704
+ end
1705
+
1706
+ after(:all) do
1707
+ Gosling::Collision.clear_buffer
1708
+ end
1709
+ end
1710
+
1711
+ describe '.unbuffer_shapes' do
1712
+ it 'accepts an array of actors' do
1713
+ expect { Gosling::Collision.unbuffer_shapes([]) }.not_to raise_error
1714
+ expect { Gosling::Collision.unbuffer_shapes([@actor1, @circle1, @polygon1, @rect1, @sprite1]) }.not_to raise_error
1715
+
1716
+ expect { Gosling::Collision.unbuffer_shapes(@actor1) }.to raise_error(ArgumentError)
1717
+ expect { Gosling::Collision.unbuffer_shapes(:foo) }.to raise_error(ArgumentError)
1718
+ expect { Gosling::Collision.unbuffer_shapes(nil) }.to raise_error(ArgumentError)
1719
+ end
1720
+
1721
+ it 'resets the buffer iterators' do
1722
+ expect(Gosling::Collision).to receive(:reset_buffer_iterators)
1723
+ Gosling::Collision.unbuffer_shapes([@actor1])
1724
+ end
1725
+
1726
+ it 'removes those actors from the collision test list and related info from the caches' do
1727
+ Gosling::Collision.buffer_shapes([@actor1, @circle1, @polygon1, @rect1, @sprite1])
1728
+ Gosling::Collision.unbuffer_shapes([@actor1, @polygon1, @sprite1])
1729
+ [@actor1, @polygon1, @sprite1].each do |actor|
1730
+ expect(Gosling::Collision.collision_buffer).not_to include(actor)
1731
+ end
1732
+ expect(Gosling::Collision.global_vertices_cache.length).to eq(1)
1733
+ expect(Gosling::Collision.global_position_cache.length).to eq(2)
1734
+ expect(Gosling::Collision.global_transform_cache.length).to eq(2)
1735
+ end
1736
+ end
1737
+
1738
+ describe '.clear_buffer' do
1739
+ it 'removes all actors from the collision test list and related info from the caches' do
1740
+ Gosling::Collision.buffer_shapes([@actor1, @circle1, @polygon1, @rect1, @sprite1])
1741
+ Gosling::Collision.clear_buffer
1742
+ [@actor1, @circle1, @polygon1, @rect1, @sprite1].each do |actor|
1743
+ expect(Gosling::Collision.collision_buffer).not_to include(actor)
1744
+ end
1745
+ expect(Gosling::Collision.global_vertices_cache.length).to eq(0)
1746
+ expect(Gosling::Collision.global_position_cache.length).to eq(0)
1747
+ expect(Gosling::Collision.global_transform_cache.length).to eq(0)
1748
+ end
1749
+
1750
+ it 'resets the buffer iterators' do
1751
+ expect(Gosling::Collision).to receive(:reset_buffer_iterators)
1752
+ Gosling::Collision.clear_buffer
1753
+ end
1754
+ end
1755
+ end