-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathSatSolver.js
More file actions
800 lines (669 loc) · 25.6 KB
/
SatSolver.js
File metadata and controls
800 lines (669 loc) · 25.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
/**
* @author Chris Andrew <[email protected]>
* @copyright 2016-2021 Chris Andrew
* @license MIT
*/
/**
* Solves tile collisions using the Separating Axis Theorem.
*
* @class Phaser.Plugin.ArcadeSlopes.SatSolver
* @constructor
* @param {object} options - Options for the SAT solver.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver = function (options) {
/**
* Options for the SAT solver.
*
* @property {object} options
*/
this.options = Phaser.Utils.mixin(options || {}, {
// Whether to store debug data with all encountered physics bodies
debug: false,
// Whether to prefer the minimum Y offset over the smallest separation
preferY: false
});
/**
* A pool of arrays to use for calculations.
*
* @property {Array[]} arrayPool
*/
this.arrayPool = [];
for (var i = 0; i < 10; i++) {
this.arrayPool.push([]);
}
/**
* A pool of vectors to use for calculations.
*
* @property {SAT.Vector[]} vectorPool
*/
this.vectorPool = [];
for (i = 0; i < 20; i++) {
this.vectorPool.push(new SAT.Vector());
}
/**
* A pool of responses to use for collision tests.
*
* @property {SAT.Response[]} responsePool
*/
this.responsePool = [];
for (i = 0; i < 20; i++) {
this.responsePool.push(new SAT.Response());
}
};
/**
* Prepare the given SAT response by inverting the overlap vectors.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#prepareResponse
* @param {SAT.Response} response
* @return {SAT.Response}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prepareResponse = function (response) {
// Invert our overlap vectors so that we have them facing outwards
response.overlapV.scale(-1);
response.overlapN.scale(-1);
return response;
};
/**
* Reset the given SAT response's properties to their default values.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#resetResponse
* @param {SAT.Response} response
* @return {SAT.Response}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.resetResponse = function (response) {
response.overlapN.x = 0;
response.overlapN.y = 0;
response.overlapV.x = 0;
response.overlapV.y = 0;
response.clear();
return response;
};
/**
* Copy the values of one SAT response to another.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#copyResponse
* @param {SAT.Response} a - The source response.
* @param {SAT.Response} b - The target response.
* @return {SAT.Response}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.copyResponse = function (a, b) {
b.a = a.a;
b.b = a.b;
b.aInB = a.aInB;
b.bInA = a.bInA;
b.overlap = a.overlap;
b.overlapN.copy(a.overlapN);
b.overlapV.copy(a.overlapV);
return b;
};
/**
* Calculate the minimum X offset given an overlap vector.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#minimumOffsetX
* @param {SAT.Vector} vector - The overlap vector.
* @return {integer}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.minimumOffsetX = function (vector) {
return ((vector.y * vector.y) / vector.x) + vector.x;
};
/**
* Calculate the minimum Y offset given an overlap vector.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#minimumOffsetY
* @param {SAT.Vector} vector - The overlap vector.
* @return {integer}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.minimumOffsetY = function (vector) {
return ((vector.x * vector.x) / vector.y) + vector.y;
};
/**
* Determine whether the given body is moving against the overlap vector of the
* given response on the Y axis.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#movingAgainstY
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {SAT.Response} response - The SAT response.
* @return {boolean} - Whether the body is moving against the overlap vector.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.movingAgainstY = function (body, response) {
return (response.overlapV.y < 0 && body.velocity.y > 0) || (response.overlapV.y > 0 && body.velocity.y < 0);
};
// TODO: shouldPreferX()
/**
* Determine whether a body should be separated on the Y axis only, given an SAT
* response.
*
* Returns true if options.preferY is true, the overlap vector is non-zero
* for each axis and the body is moving against the overlap vector.
*
* TODO: Adapt for circle bodies, somehow. Disable for now?
* TODO: Would be amazing to check to ensure that there are no other surrounding collisions.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#shouldPreferY
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {SAT.Response} response - The SAT response.
* @return {boolean} - Whether to separate on the Y axis only.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.shouldPreferY = function (body, response) {
return (this.options.preferY || body.slopes.preferY) && // Enabled globally or on the body
response.overlapV.y !== 0 && response.overlapV.x !== 0 && // There's an overlap on both axes
Phaser.Plugin.ArcadeSlopes.SatSolver.movingAgainstY(body, response); // And we're moving into the shape
};
/**
* Separate a body from a tile using the given SAT response.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#separate
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {Phaser.Tile} tile - The tile.
* @param {SAT.Response} response - The SAT response.
* @param {boolean} force - Whether to force separation.
* @return {boolean} - Whether the body was separated.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.separate = function (body, tile, response, force) {
// Test whether we need to separate from the tile by checking its edge
// properties and any separation constraints
if (!force && !this.shouldSeparate(tile.index, body, tile, response)) {
return false;
}
// Run any custom tile callbacks, with local callbacks taking priority over
// layer level callbacks
if (tile.collisionCallback && !tile.collisionCallback.call(tile.collisionCallbackContext, body.sprite, tile)) {
return false;
} else if (tile.layer.callbacks[tile.index] && !tile.layer.callbacks[tile.index].callback.call(tile.layer.callbacks[tile.index].callbackContext, body.sprite, tile)) {
return false;
}
// Separate the body from the tile, using the minimum Y offset if preferred
if (this.shouldPreferY(body, response)) {
body.position.y += Phaser.Plugin.ArcadeSlopes.SatSolver.minimumOffsetY(response.overlapV);
} else {
body.position.x += response.overlapV.x;
body.position.y += response.overlapV.y;
}
return true;
};
/**
* Apply velocity changes (friction and bounce) to a body given a tile and
* SAT collision response.
*
* TODO: Optimize by pooling bounce and friction vectors.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#applyVelocity
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {Phaser.Tile} tile - The tile.
* @param {SAT.Response} response - The SAT response.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.applyVelocity = function (body, tile, response) {
// Project our velocity onto the overlap normal for the bounce vector (Vn)
var bounce = this.vectorPool.pop().copy(body.slopes.velocity).projectN(response.overlapN);
// Then work out the surface vector (Vt)
var friction = this.vectorPool.pop().copy(body.slopes.velocity).sub(bounce);
// Apply bounce coefficients
bounce.x = bounce.x * (-body.bounce.x);
bounce.y = bounce.y * (-body.bounce.y);
// Apply friction coefficients
friction.x = friction.x * (1 - body.slopes.friction.x - tile.slope.friction.x);
friction.y = friction.y * (1 - body.slopes.friction.y - tile.slope.friction.y);
// Now we can get our new velocity by adding the bounce and friction vectors
body.velocity.x = bounce.x + friction.x;
body.velocity.y = bounce.y + friction.y;
// Process collision pulling
this.pull(body, response);
// Recycle the vectors we used for bounce and friction
this.vectorPool.push(bounce, friction);
};
/**
* Update the position and velocity values of the slopes body.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#updateValues
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.updateValues = function (body) {
// Update the body polygon position
body.polygon.pos.x = body.x;
body.polygon.pos.y = body.y;
// Update the body's velocity vector
body.slopes.velocity.x = body.velocity.x;
body.slopes.velocity.y = body.velocity.y;
};
/**
* Update the flags of a physics body using a given SAT response.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#updateFlags
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {SAT.Response} response - The SAT response.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.updateFlags = function (body, response) {
// Set the touching values
body.touching.up = body.touching.up || response.overlapV.y > 0;
body.touching.down = body.touching.down || response.overlapV.y < 0;
body.touching.left = body.touching.left || response.overlapV.x > 0;
body.touching.right = body.touching.right || response.overlapV.x < 0;
body.touching.none = !body.touching.up && !body.touching.down && !body.touching.left && !body.touching.right;
// Set the blocked values
body.blocked.up = body.blocked.up || response.overlapV.x === 0 && response.overlapV.y > 0;
body.blocked.down = body.blocked.down || response.overlapV.x === 0 && response.overlapV.y < 0;
body.blocked.left = body.blocked.left || response.overlapV.y === 0 && response.overlapV.x > 0;
body.blocked.right = body.blocked.right || response.overlapV.y === 0 && response.overlapV.x < 0;
};
/**
* Pull the body into a collision response based on its slopes options.
*
* TODO: Don't return after any condition is met, accumulate values into a
* single SAT.Vector and apply at the end.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#pull
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {SAT.Response} response - The SAT response.
* @return {boolean} - Whether the body was pulled.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.pull = function (body, response) {
if (!body.slopes.pullUp && !body.slopes.pullDown && !body.slopes.pullLeft && !body.slopes.pullRight &&
!body.slopes.pullTopLeft && !body.slopes.pullTopRight && !body.slopes.pullBottomLeft && !body.slopes.pullBottomRight) {
return false;
}
// Clone and flip the overlap normal so that it faces into the collision
var overlapN = response.overlapN.clone().scale(-1);
if (body.slopes.pullUp && overlapN.y < 0) {
// Scale it by the configured amount
pullUp = overlapN.clone().scale(body.slopes.pullUp);
// Apply it to the body velocity
body.velocity.x += pullUp.x;
body.velocity.y += pullUp.y;
return true;
}
if (body.slopes.pullDown && overlapN.y > 0) {
pullDown = overlapN.clone().scale(body.slopes.pullDown);
body.velocity.x += pullDown.x;
body.velocity.y += pullDown.y;
return true;
}
if (body.slopes.pullLeft && overlapN.x < 0) {
pullLeft = overlapN.clone().scale(body.slopes.pullLeft);
body.velocity.x += pullLeft.x;
body.velocity.y += pullLeft.y;
return true;
}
if (body.slopes.pullRight && overlapN.x > 0) {
pullRight = overlapN.clone().scale(body.slopes.pullRight);
body.velocity.x += pullRight.x;
body.velocity.y += pullRight.y;
return true;
}
if (body.slopes.pullTopLeft && overlapN.x < 0 && overlapN.y < 0) {
pullUp = overlapN.clone().scale(body.slopes.pullTopLeft);
body.velocity.x += pullUp.x;
body.velocity.y += pullUp.y;
return true;
}
if (body.slopes.pullTopRight && overlapN.x > 0 && overlapN.y < 0) {
pullDown = overlapN.clone().scale(body.slopes.pullTopRight);
body.velocity.x += pullDown.x;
body.velocity.y += pullDown.y;
return true;
}
if (body.slopes.pullBottomLeft && overlapN.x < 0 && overlapN.y > 0) {
pullLeft = overlapN.clone().scale(body.slopes.pullBottomLeft);
body.velocity.x += pullLeft.x;
body.velocity.y += pullLeft.y;
return true;
}
if (body.slopes.pullBottomRight && overlapN.x > 0 && overlapN.y > 0) {
pullRight = overlapN.clone().scale(body.slopes.pullBottomRight);
body.velocity.x += pullRight.x;
body.velocity.y += pullRight.y;
return true;
}
return false;
};
/**
* Determine whether everything required to process a collision is available.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#shouldCollide
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {Phaser.Tile} tile - The tile.
* @return {boolean}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.shouldCollide = function (body, tile) {
return body.enable && body.polygon && body.slopes && tile.collides && tile.slope && tile.slope.polygon;
};
/**
* Flattens the specified array of points onto a unit vector axis,
* resulting in a one dimensional range of the minimum and
* maximum value on that axis.
*
* Copied verbatim from SAT.flattenPointsOn.
*
* @see SAT.flattenPointsOn
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#flattenPointsOn
* @param {SAT.Vector[]} points - The points to flatten.
* @param {SAT.Vector} normal - The unit vector axis to flatten on.
* @param {number[]} result - An array. After calling this,
* result[0] will be the minimum value,
* result[1] will be the maximum value.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.flattenPointsOn = function (points, normal, result) {
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
var len = points.length;
for (var i = 0; i < len; i++ ) {
// The magnitude of the projection of the point onto the normal
var dot = points[i].dot(normal);
if (dot < min) { min = dot; }
if (dot > max) { max = dot; }
}
result[0] = min; result[1] = max;
};
/**
* Determine whether two polygons are separated by a given axis.
*
* Tailored to only push out in the direction of the given axis.
*
* Adapted from SAT.isSeparatingAxis.
*
* @see {SAT.isSeparatingAxis}
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#isSeparatingAxis
* @param {SAT.Polygon} a - The first polygon.
* @param {SAT.Polygon} b - The second polygon.
* @param {SAT.Vector} axis - The axis (unit sized) to test against.
* The points of both polygons are projected
* onto this axis.
* @param {SAT.Response} response - The response to populate if the polygons are
* not separated by the given axis.
* @return {boolean} true if it is a separating axis, false otherwise. If false,
* and a response is passed in, information about how much overlap and
* the direction of the overlap will be populated.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.isSeparatingAxis = function (a, b, axis, response) {
var aPos = a.pos;
var bPos = b.pos;
var aPoints = a.calcPoints;
var bPoints = b.calcPoints;
var rangeA = this.arrayPool.pop();
var rangeB = this.arrayPool.pop();
// The magnitude of the offset between the two polygons
var offsetV = this.vectorPool.pop().copy(bPos).sub(aPos);
var projectedOffset = offsetV.dot(axis);
// Project the polygons onto the axis.
Phaser.Plugin.ArcadeSlopes.SatSolver.flattenPointsOn(aPoints, axis, rangeA);
Phaser.Plugin.ArcadeSlopes.SatSolver.flattenPointsOn(bPoints, axis, rangeB);
// Move B's range to its position relative to A.
rangeB[0] += projectedOffset;
rangeB[1] += projectedOffset;
// Check if there is a gap. If there is, this is a separating axis and we can stop
if (rangeA[0] >= rangeB[1] || rangeB[0] >= rangeA[1]) {
this.vectorPool.push(offsetV);
this.arrayPool.push(rangeA);
this.arrayPool.push(rangeB);
return true;
}
var option1, option2;
// This is not a separating axis. If we're calculating a response, calculate
// the overlap
var overlap = 0;
if (rangeA[0] < rangeB[0]) {
// A starts further left than B
response.aInB = false;
if (rangeA[1] < rangeB[1]) {
// A ends before B does. We have to pull A out of B
//overlap = rangeA[1] - rangeB[0];
response.bInA = false;
}// else {
// B is fully inside A. Pick the shortest way out.
//option1 = rangeA[1] - rangeB[0];
//option2 = rangeB[1] - rangeA[0];
//overlap = option1 < option2 ? option1 : -option2;
//}
} else {
// B starts further left than A
response.bInA = false;
if (rangeA[1] > rangeB[1]) {
// B ends before A ends. We have to push A out of B
overlap = rangeA[0] - rangeB[1];
response.aInB = false;
} else {
// A is fully inside B. Pick the shortest way out.
option1 = rangeA[1] - rangeB[0];
option2 = rangeB[1] - rangeA[0];
//overlap = option1 < option2 ? option1 : -option2;
if (option1 >= option2) {
overlap = -option2;
}
}
}
// If this is the smallest amount of overlap we've seen so far, set it
// as the minimum overlap.
var absOverlap = Math.abs(overlap);
if (absOverlap < response.overlap) {
response.overlap = absOverlap;
response.overlapN.copy(axis);
if (overlap < 0) {
response.overlapN.reverse();
}
}
this.vectorPool.push(offsetV);
this.arrayPool.push(rangeA);
this.arrayPool.push(rangeB);
return false;
};
/**
* Test whether two polygons overlap.
*
* Takes a response object that will be populated with the shortest
* viable separation vector. Ignores collision responses that don't oppose
* velocity enough.
*
* Returns true if there is a collision and false otherwise.
*
* Tailored to work with an AABB as the first polygon.
*
* Adapted from SAT.testPolygonPolygon.
*
* @see {SAT.testPolygonPolygon}
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#testPolygonPolygon
* @param {SAT.Polygon} a - The first polygon.
* @param {SAT.Polygon} b - The second polygon.
* @param {SAT.Response} response - The response object to populate with overlap information.
* @param {SAT.Vector} velocity - The velocity vector to ignore.
* @param {SAT.Vector[]} ignore - The axes to ignore.
* @return {boolean} - Whether the the two polygons overlap.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.testPolygonPolygon = function (a, b, response, velocity, ignore) {
var aPoints = a.calcPoints;
var aLen = aPoints.length;
var bPoints = b.calcPoints;
var bLen = bPoints.length;
var i, j, k;
var responses = this.arrayPool.pop();
var axes = this.arrayPool.pop();
responses.length = 0;
axes.length = 0;
// If any of the edge normals of A is a separating axis, no intersection
for (i = 0; i < aLen; i++) {
responses[i] = this.responsePool.pop();
responses[i].clear();
axes[i] = a.normals[i];
if (this.isSeparatingAxis(a, b, a.normals[i], responses[i])) {
for (k = 0; k < responses.length; k++) {
this.responsePool.push(responses[k]);
}
this.arrayPool.push(responses, axes);
return false;
}
}
// If any of the edge normals of B is a separating axis, no intersection
for (i = 0, j = aLen; i < bLen; i++, j++) {
responses[j] = this.responsePool.pop();
responses[j].clear();
axes[j] = b.normals[i];
if (this.isSeparatingAxis(a, b, b.normals[i], responses[j])) {
for (k = 0; k < responses.length; k++) {
this.responsePool.push(responses[k]);
}
this.arrayPool.push(responses, axes);
return false;
}
}
// Since none of the edge normals of A or B are a separating axis, there is
// an intersection
var viable = false;
var ignored = false;
var velocityTestVector = this.vectorPool.pop();
// Determine the shortest desirable and viable separation from the responses
for (i = 0; i < responses.length; i++) {
// Is the overlap in the range we want?
// TODO: Less than the max of tile width/height?
if (!(responses[i].overlap > 0 && responses[i].overlap < Number.MAX_VALUE)) {
continue;
}
// Is the overlap direction too close to that of the velocity direction?
if (velocity && velocityTestVector.copy(responses[i].overlapN).scale(-1).dot(velocity) > 0) {
continue;
}
ignored = false;
// Is the axis of the overlap in the extra ignore list?
for (j = 0; j < ignore.length; j++) {
if (axes[i].x === ignore[j].x && axes[i].y === ignore[j].y) {
ignored = true;
break;
}
}
// Skip this response if its normal is ignored
if (ignored) {
continue;
}
// Is this response's overlap shorter than that of the current?
if (responses[i].overlap < response.overlap) {
viable = true;
response.aInB = responses[i].aInB;
response.bInA = responses[i].bInA;
response.overlap = responses[i].overlap;
response.overlapN = responses[i].overlapN;
}
}
// Set the polygons on the response and calculate the overlap vector
if (viable) {
response.a = a;
response.b = b;
response.overlapV.copy(response.overlapN).scale(response.overlap);
}
// Recycle the temporary responses, arrays and vectors used for calculations
for (k = 0; k < responses.length; k++) {
this.responsePool.push(responses[k]);
}
this.arrayPool.push(responses, axes);
this.vectorPool.push(velocityTestVector);
return viable;
};
/**
* Separate the given body and tile from each other and apply any relevant
* changes to the body's velocity.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#collide
* @param {integer} i - The tile index.
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {Phaser.Tile} tile - The tile.
* @param {Phaser.TilemapLayer} tilemapLayer - The tilemap layer.
* @param {boolean} overlapOnly - Whether to only check for an overlap.
* @return {boolean} - Whether the body was separated.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.collide = function (i, body, tile, tilemapLayer, overlapOnly) {
// Update the body's polygon position and velocity vector
this.updateValues(body);
// Bail out if we don't have everything we need
if (!this.shouldCollide(body, tile)) {
return false;
}
// Cater for SAT.js requiring center-origin circles
if (body.isCircle) {
body.polygon.pos.x += body.halfWidth;
body.polygon.pos.y += body.halfHeight;
}
// Update the tile polygon position
tile.slope.polygon.pos.x = tile.worldX + tilemapLayer.getCollisionOffsetX();
tile.slope.polygon.pos.y = tile.worldY + tilemapLayer.getCollisionOffsetY();
// Create the body's response if it doesn't have one
body.slopes.sat.response = body.slopes.sat.response || new SAT.Response();
// Acquire a temporary response from the pool
var response = this.responsePool.pop();
Phaser.Plugin.ArcadeSlopes.SatSolver.resetResponse(response);
// Test for an overlap
var circleOverlap = body.isCircle && SAT.testCirclePolygon(body.polygon, tile.slope.polygon, response);
var polygonOverlap = !body.isCircle && this.testPolygonPolygon(body.polygon, tile.slope.polygon, response, body.slopes.velocity, tile.slope.ignormals);
// Bail if there isn't one, leaving the body's response as is
if (!circleOverlap && !polygonOverlap) {
this.responsePool.push(response);
return false;
}
// Invert our overlap vectors so that we have them facing outwards
Phaser.Plugin.ArcadeSlopes.SatSolver.prepareResponse(response);
// If we're only testing for the overlap, we can bail here
if (overlapOnly) {
Phaser.Plugin.ArcadeSlopes.SatSolver.copyResponse(response, body.slopes.sat.response);
this.responsePool.push(response);
return true;
}
// Bail out if no separation occurred
if (!this.separate(body, tile, response)) {
this.responsePool.push(response);
return false;
}
// Copy the temporary response into the body's response, then recycle it
Phaser.Plugin.ArcadeSlopes.SatSolver.copyResponse(response, body.slopes.sat.response);
this.responsePool.push(response);
response = body.slopes.sat.response;
// Update the overlap properties of the body
body.overlapX = response.overlapV.x;
body.overlapY = response.overlapV.y;
// Set the tile that the body separated from
body.slopes.tile = tile;
// Apply any velocity changes as a result of the collision
this.applyVelocity(body, tile, response);
// Update the touching and blocked flags of the physics body
this.updateFlags(body, response);
return true;
};
/**
* Determine whether to separate a body from a tile, given an SAT response.
*
* Checks against the tile's collision flags and slope edge flags.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#shouldSeparate
* @param {integer} i - The tile index.
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {Phaser.Tile} tile - The tile.
* @param {SAT.Response} response - The initial collision response.
* @return {boolean} - Whether to pursue the narrow phase.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.shouldSeparate = function (i, body, tile, response) {
// Bail if the body is disabled or there is no overlap
if (!(body.enable && response.overlap)) {
return false;
}
// Only separate if the body is moving into the collision
// if (response.overlapV.clone().scale(-1).dot(body.slopes.velocity) < 0) {
// return false;
// }
// Otherwise we should separate normally
return true;
};
/**
* Render the given SAT response as a set of lines from the given position.
*
* TODO: Actually maybe just collect the lines here for drawing later?
* Or, make this static and just something you can call in the
* context of a game, or game state.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#debug
* @param {Phaser.Point} position
* @param {SAT.Response} response
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.debug = function (position, response) {
// TODO: Implement.
};