gosling 2.3.0 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
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