-
-
Notifications
You must be signed in to change notification settings - Fork 640
/
collision.js
855 lines (790 loc) · 36.5 KB
/
collision.js
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
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2014 Olivier Biot, Jason Oster, Aaron McLeod
* http://www.melonjs.org
*
* Separating Axis Theorem implementation, based on the SAT.js library by Jim Riecken <jimr@jimr.ca>
* Available under the MIT License - https://github.com/jriecken/sat-js
*/
(function () {
/**
* Constants for Vornoi regions
* @ignore
*/
var LEFT_VORNOI_REGION = -1;
/**
* Constants for Vornoi regions
* @ignore
*/
var MIDDLE_VORNOI_REGION = 0;
/**
* Constants for Vornoi regions
* @ignore
*/
var RIGHT_VORNOI_REGION = 1;
/**
* A pool of `Vector` objects that are used in calculations to avoid allocating memory.
* @type {Array.<Vector>}
*/
var T_VECTORS = [];
for (var v = 0; v < 10; v++) { T_VECTORS.push(new me.Vector2d()); }
/**
* A pool of arrays of numbers used in calculations to avoid allocating memory.
* @type {Array.<Array.<number>>}
*/
var T_ARRAYS = [];
for (var a = 0; a < 5; a++) { T_ARRAYS.push([]); }
/**
* 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.
* @param {Array.<Vector>} points The points to flatten.
* @param {Vector} normal The unit vector axis to flatten on.
* @param {Array.<number>} result An array. After calling this function,
* result[0] will be the minimum value,
* result[1] will be the maximum value.
*/
function flattenPointsOn(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].dotProduct(normal);
if (dot < min) { min = dot; }
if (dot > max) { max = dot; }
}
result[0] = min;
result[1] = max;
}
/**
* Check whether two convex polygons are separated by the specified
* axis (must be a unit vector).
* @param {Vector} aPos The position of the first polygon.
* @param {Vector} bPos The position of the second polygon.
* @param {Array.<Vector>} aPoints The points in the first polygon.
* @param {Array.<Vector>} bPoints The points in the second polygon.
* @param {Vector} axis The axis (unit sized) to test against. The points of both polygons
* will be projected onto this axis.
* @param {Response=} response A Response object (optional) which will be populated
* if the axis is not a separating 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.
*/
function isSeparatingAxis(aPos, bPos, aPoints, bPoints, axis, response) {
var rangeA = T_ARRAYS.pop();
var rangeB = T_ARRAYS.pop();
// The magnitude of the offset between the two polygons
var offsetV = T_VECTORS.pop().copy(bPos).sub(aPos);
var projectedOffset = offsetV.dotProduct(axis);
// Project the polygons onto the axis.
flattenPointsOn(aPoints, axis, rangeA);
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]) {
T_VECTORS.push(offsetV);
T_ARRAYS.push(rangeA);
T_ARRAYS.push(rangeB);
return true;
}
// This is not a separating axis. If we're calculating a response, calculate the overlap.
if (response) {
var overlap = 0;
// A starts further left than B
if (rangeA[0] < rangeB[0]) {
response.aInB = false;
// A ends before B does. We have to pull A out of B
if (rangeA[1] < rangeB[1]) {
overlap = rangeA[1] - rangeB[0];
response.bInA = false;
// B is fully inside A. Pick the shortest way out.
} else {
var option1 = rangeA[1] - rangeB[0];
var option2 = rangeB[1] - rangeA[0];
overlap = option1 < option2 ? option1 : -option2;
}
// B starts further left than A
} else {
response.bInA = false;
// B ends before A ends. We have to push A out of B
if (rangeA[1] > rangeB[1]) {
overlap = rangeA[0] - rangeB[1];
response.aInB = false;
// A is fully inside B. Pick the shortest way out.
} else {
var option11 = rangeA[1] - rangeB[0];
var option22 = rangeB[1] - rangeA[0];
overlap = option11 < option22 ? option11 : -option22;
}
}
// 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();
}
}
}
T_VECTORS.push(offsetV);
T_ARRAYS.push(rangeA);
T_ARRAYS.push(rangeB);
return false;
}
/**
* Calculates which Vornoi region a point is on a line segment. <br>
* It is assumed that both the line and the point are relative to `(0,0)`<br>
* <br>
* | (0) |<br>
* (-1) [S]--------------[E] (1)<br>
* | (0) |<br>
*
* @ignore
* @param {Vector} line The line segment.
* @param {Vector} point The point.
* @return {number} LEFT_VORNOI_REGION (-1) if it is the left region,
* MIDDLE_VORNOI_REGION (0) if it is the middle region,
* RIGHT_VORNOI_REGION (1) if it is the right region.
*/
function vornoiRegion(line, point) {
var len2 = line.length2();
var dp = point.dotProduct(line);
if (dp < 0) {
// If the point is beyond the start of the line, it is in the
// left vornoi region.
return LEFT_VORNOI_REGION;
} else if (dp > len2) {
// If the point is beyond the end of the line, it is in the
// right vornoi region.
return RIGHT_VORNOI_REGION;
} else {
// Otherwise, it's in the middle one.
return MIDDLE_VORNOI_REGION;
}
}
/**
* A singleton for managing collision detection (and projection-based collision response) of 2D shapes.<br>
* Based on the Separating Axis Theorem and supports detecting collisions between simple Axis-Aligned Boxes, convex polygons and circles based shapes.
* @namespace
* @property {Singleton} collision
* @memberOf me
*/
me.collision = (function () {
// hold public stuff in our singleton
var api = {};
/*
* PUBLIC STUFF
*/
/**
* the world quadtree used for the collision broadphase
* @name quadTree
* @memberOf me.collision
* @public
* @type {me.QuadTree}
*/
api.quadTree = null;
/**
* The maximum number of levels that the quadtree will create. Default is 4.
* @name maxDepth
* @memberOf me.collision
* @public
* @type {number}
* @see me.collision.quadTree
*
*/
api.maxDepth = 4;
/**
* The maximum number of children that a quadtree node can contain before it is split into sub-nodes. Default is 8.
* @name maxChildren
* @memberOf me.collision
* @public
* @type {boolean}
* @see me.collision.quadTree
*/
api.maxChildren = 8;
/**
* bounds of the physic world.
* @name bounds
* @memberOf me.collision
* @public
* @type {me.Rect}
*/
api.bounds = null;
/**
* Enum for collision type values. <br>
* Possible values are : <br>
* - <b>`NO_OBJECT`</b> (to disable collision check) <br>
* - <b>`PLAYER_OBJECT`</b> <br>
* - <b>`NPC_OBJECT`</b> <br>
* - <b>`ENEMY_OBJECT`</b> <br>
* - <b>`COLLECTABLE_OBJECT`</b> <br>
* - <b>`ACTION_OBJECT`</b> <br>
* - <b>`PROJECTILE_OBJECT`</b> <br>
* - <b>`WORLD_SHAPE`</b> (for collision check with collision shapes/tiles) <br>
* - <b>`ALL_OBJECT`</b> <br>
* @readonly
* @enum {number}
* @name types
* @memberOf me.collision
* @see me.body.setCollisionMask
* @see me.body.collisionType
* @example
* // set the entity body collision type
* myEntity.body.setCollisionType = me.collision.types.PLAYER_OBJECT;
* // filter collision detection with collision shapes, enemies and collectables
* myEntity.body.setCollisionMask(me.collision.types.WORLD_SHAPE | me.collision.types.ENEMY_OBJECT | me.collision.types.COLLECTABLE_OBJECT);
*/
api.types = {
NO_OBJECT : 0,
/**
* Default object type constant for collision filtering
* @constant
* @name PLAYER_OBJECT
* @memberOf me.collision.types
*/
PLAYER_OBJECT : 1,
/**
* Default object type constant for collision filtering
* @constant
* @name NPC_OBJECT
* @memberOf me.collision.types
*/
NPC_OBJECT : 2,
/**
* Default object type constant for collision filtering
* @constant
* @name ENEMY_OBJECT
* @memberOf me.collision.types
*/
ENEMY_OBJECT : 4,
/**
* Default object type constant for collision filtering
* @constant
* @name COLLECTABLE_OBJECT
* @memberOf me.collision.types
*/
COLLECTABLE_OBJECT : 8,
/**
* Default object type constant for collision filtering
* @constant
* @name ACTION_OBJECT
* @memberOf me.collision.types
*/
ACTION_OBJECT : 16, // door, etc...
/**
* Default object type constant for collision filtering
* @constant
* @name PROJECTILE_OBJECT
* @memberOf me.collision.types
*/
PROJECTILE_OBJECT : 32, // missiles, etc...
/**
* Default object type constant for collision filtering
* @constant
* @name WORLD_SHAPE
* @memberOf me.collision.types
*/
WORLD_SHAPE : 64, // walls, etc...
/**
* Default object type constant for collision filtering
* @constant
* @name ALL_OBJECT
* @memberOf me.collision.types
*/
ALL_OBJECT : 0xFFFFFFFF // all objects
};
/**
* Initialize the collision/physic world
* @ignore
*/
api.init = function () {
// default bounds to the game viewport
api.bounds = me.game.viewport.clone();
// initializa the quadtree
api.quadTree = new me.QuadTree(api.bounds, api.maxChildren, api.maxDepth);
// reset the collision detection engine if a TMX level is loaded
me.event.subscribe(me.event.LEVEL_LOADED, function () {
// default bounds to game world
me.collision.bounds = me.game.world.clone();
// reset the quadtree
me.collision.quadTree.clear(me.collision.bounds);
});
};
/**
* An object representing the result of an intersection, contains: <br>
* - <b>`a`</b> and <b>`b`</b> {me.Entity} : The two objects participating in the intersection <br>
* - <b>`overlap`</b> {number} : Magnitude of the overlap on the shortest colliding axis. <br>
* - <b>`overlapV`</b> {me.vector2d}: The overlap vector (i.e. `overlapN.scale(overlap, overlap)`). If this vector is subtracted from the position of a, a and b will no longer be colliding <br>
* - <b>`overlapN`</b> {me.vector2d}: The shortest colliding axis (unit-vector) <br>
* - <b>`aInB`</b>, <b>`bInA`</b> {boolean} : Whether the first object is entirely inside the second, and vice versa. <br>
* - <b>`indexShapeA</b> {number} : the index of the colliding shape for the object a body. <br>
* - <b>`indexShapeB</b> {number} : the index of the colliding shape for the object b body. <br>
* - <b>`clear()`</b> {function} : Set some values of the response back to their defaults. Call this between tests if you are going to reuse a single Response object for multiple intersection tests <br>
* @name ResponseObject
* @memberOf me.collision
* @public
* @type {external:Object}
* @see me.collision.check
*/
api.ResponseObject = function () {
this.a = null;
this.b = null;
this.overlapN = new me.Vector2d();
this.overlapV = new me.Vector2d();
this.aInB = true;
this.bInA = true;
this.indexShapeA = -1;
this.indexShapeB = -1;
this.overlap = Number.MAX_VALUE;
};
/**
* Set some values of the response back to their defaults. <br>
* Call this between tests if you are going to reuse a single <br>
* Response object for multiple intersection tests <br>
* (recommended as it will avoid allocating extra memory) <br>
* @name clear
* @memberOf me.collision.ResponseObject
* @public
* @function
*/
api.ResponseObject.prototype.clear = function () {
this.aInB = true;
this.bInA = true;
this.overlap = Number.MAX_VALUE;
this.indexShapeA = -1;
this.indexShapeB = -1;
return this;
};
/**
* a global instance of a response object used for collision detection <br>
* this object will be reused amongst collision detection call if not user-defined response is specified
* @name response
* @memberOf me.collision
* @public
* @type {me.collision.ResponseObject}
*/
api.response = new api.ResponseObject();
/**
* a callback used to determine if two objects should collide (based on both respective objects collision mask and type).<br>
* you can redefine this function if you need any specific rules over what should collide with what.
* @name shouldCollide
* @memberOf me.collision
* @public
* @function
* @param {me.Entity} a a reference to the object A.
* @param {me.Entity} b a reference to the object B.
* @return {Boolean} true if they should collide, false otherwise
*/
api.shouldCollide = function (a, b) {
return (
a.body && b.body &&
(a.body.collisionMask & b.body.collisionType) !== 0 &&
(a.body.collisionType & b.body.collisionMask) !== 0
);
};
/**
* Checks if the specified entity collides with others entities
* @name check
* @memberOf me.collision
* @public
* @function
* @param {me.Entity} obj entity to be tested for collision
* @param {me.collision.ResponseObject} [respObj=me.collision.response] a user defined response object that will be populated if they intersect.
* @return {Boolean} in case of collision, false otherwise
* @example
* update : function (dt) {
* ...
* // check for collision between this object and all others
* me.collision.check(this);
* ...
* };
*
* onCollision : function (response) {
* if (response.b.body.collisionType === me.collision.types.ENEMY_OBJECT) {
* // makes the other entity solid, by substracting the overlap vector to the current position
* this.pos.sub(response.overlapV);
* this.hurt();
* } else {
* ...
* }
* };
*/
api.check = function (objA, responseObject) {
var collision = 0;
var response = responseObject || api.response;
// retreive a list of potential colliding objects
var candidates = api.quadTree.retrieve(objA);
for (var i = candidates.length, objB; i--, (objB = candidates[i]);) {
if (objB.inViewport || objB.alwaysUpdate) {
// check if both objects "should" collide
if ((objB !== objA) && api.shouldCollide(objA, objB)) {
// fast AABB check if both bounding boxes are overlaping
if (objA.getBounds().overlaps(objB.getBounds())) {
// go trough all defined shapes in A
var indexA = 0;
do {
var shapeA = objA.body.getShape(indexA);
// go through all defined shapes in B
var indexB = 0;
do {
var shapeB = objB.body.getShape(indexB);
// full SAT collision check
if (api["test" + shapeA.shapeType + shapeB.shapeType]
.call(
this,
objA, // a reference to the object A
shapeA,
objB, // a reference to the object B
shapeB,
// clear response object before reusing
response.clear()) === true
) {
// we touched something !
collision++;
// set the shape index
response.indexShapeA = indexA;
response.indexShapeB = indexB;
// execute the onCollision callback
if (objA.onCollision(response, objB) !== false) {
objA.body.respondToCollision.call(objA.body, response);
}
if (objB.onCollision(response, objA) !== false) {
objB.body.respondToCollision.call(objB.body, response);
}
}
indexB++;
} while (indexB < objB.body.shapes.length);
indexA++;
} while (indexA < objA.body.shapes.length);
}
}
}
}
// we could return the amount of objects we collided with ?
return collision > 0;
};
/**
* Checks whether polygons collide.
* @ignore
* @param {me.Entity} a a reference to the object A.
* @param {me.Polygon} polyA a reference to the object A Polygon to be tested
* @param {me.Entity} b a reference to the object B.
* @param {me.Polygon} polyB a reference to the object B Polygon to be tested
* @param {Response=} response Response object (optional) that will be populated if they intersect.
* @return {boolean} true if they intersect, false if they don't.
*/
api.testPolygonPolygon = function (a, polyA, b, polyB, response) {
// specific point for
var aPoints = polyA.points;
var aLen = aPoints.length;
var bPoints = polyB.points;
var bLen = bPoints.length;
// aboslute shape position
var posA = T_VECTORS.pop().copy(a.pos).add(polyA.pos);
var posB = T_VECTORS.pop().copy(b.pos).add(polyB.pos);
var i;
// If any of the edge normals of A is a separating axis, no intersection.
for (i = 0; i < aLen; i++) {
if (isSeparatingAxis(posA, posB, aPoints, bPoints, polyA.normals[i], response)) {
T_VECTORS.push(posA);
T_VECTORS.push(posB);
return false;
}
}
// If any of the edge normals of B is a separating axis, no intersection.
for (i = 0;i < bLen; i++) {
if (isSeparatingAxis(posA, posB, aPoints, bPoints, polyB.normals[i], response)) {
T_VECTORS.push(posA);
T_VECTORS.push(posB);
return false;
}
}
// Since none of the edge normals of A or B are a separating axis, there is an intersection
// and we've already calculated the smallest overlap (in isSeparatingAxis). Calculate the
// final overlap vector.
if (response) {
response.a = a;
response.b = b;
response.overlapV.copy(response.overlapN).scale(response.overlap);
}
T_VECTORS.push(posA);
T_VECTORS.push(posB);
return true;
};
/**
* Check if two Ellipse collide.
* @ignore
* @param {me.Entity} a a reference to the object A.
* @param {me.Ellipse} ellipseA a reference to the object A Ellipse to be tested
* @param {me.Entity} b a reference to the object B.
* @param {me.Ellipse} ellipseB a reference to the object B Ellipse to be tested
* @param {Response=} response Response object (optional) that will be populated if
* the circles intersect.
* @return {boolean} true if the circles intersect, false if they don't.
*/
api.testEllipseEllipse = function (a, ellipseA, b, ellipseB, response) {
// Check if the distance between the centers of the two
// circles is greater than their combined radius.
var differenceV = T_VECTORS.pop().copy(b.pos).add(ellipseB.pos).sub(a.pos).add(ellipseA.pos);
var radiusA = ellipseA.radius;
var radiusB = ellipseB.radius;
var totalRadius = radiusA + radiusB;
var totalRadiusSq = totalRadius * totalRadius;
var distanceSq = differenceV.length2();
// If the distance is bigger than the combined radius, they don't intersect.
if (distanceSq > totalRadiusSq) {
T_VECTORS.push(differenceV);
return false;
}
// They intersect. If we're calculating a response, calculate the overlap.
if (response) {
var dist = Math.sqrt(distanceSq);
response.a = a;
response.b = b;
response.overlap = totalRadius - dist;
response.overlapN.copy(differenceV.normalize());
response.overlapV.copy(differenceV).scale(response.overlap);
response.aInB = radiusA <= radiusB && dist <= radiusB - radiusA;
response.bInA = radiusB <= radiusA && dist <= radiusA - radiusB;
}
T_VECTORS.push(differenceV);
return true;
};
/**
* Check if a polygon and an ellipse collide.
* @ignore
* @param {me.Entity} a a reference to the object A.
* @param {me.Polygon} polyA a reference to the object A Polygon to be tested
* @param {me.Entity} b a reference to the object B.
* @param {me.Ellipse} ellipseB a reference to the object B Ellipse to be tested
* @param {Response=} response Response object (optional) that will be populated if they intersect.
* @return {boolean} true if they intersect, false if they don't.
*/
api.testPolygonEllipse = function (a, polyA, b, ellipseB, response) {
// Get the position of the circle relative to the polygon.
var circlePos = T_VECTORS.pop().copy(b.pos).add(ellipseB.pos).sub(a.pos).add(polyA.pos);
var radius = ellipseB.radius;
var radius2 = radius * radius;
var points = polyA.points;
var len = points.length;
var edge = T_VECTORS.pop();
var point = T_VECTORS.pop();
var dist = 0;
// For each edge in the polygon:
for (var i = 0; i < len; i++) {
var next = i === len - 1 ? 0 : i + 1;
var prev = i === 0 ? len - 1 : i - 1;
var overlap = 0;
var overlapN = null;
// Get the edge.
edge.copy(polyA.edges[i]);
// Calculate the center of the circle relative to the starting point of the edge.
point.copy(circlePos).sub(points[i]);
// If the distance between the center of the circle and the point
// is bigger than the radius, the polygon is definitely not fully in
// the circle.
if (response && point.length2() > radius2) {
response.aInB = false;
}
// Calculate which Vornoi region the center of the circle is in.
var region = vornoiRegion(edge, point);
// If it's the left region:
if (region === LEFT_VORNOI_REGION) {
// We need to make sure we're in the RIGHT_VORNOI_REGION of the previous edge.
edge.copy(polyA.edges[prev]);
// Calculate the center of the circle relative the starting point of the previous edge
var point2 = T_VECTORS.pop().copy(circlePos).sub(points[prev]);
region = vornoiRegion(edge, point2);
if (region === RIGHT_VORNOI_REGION) {
// It's in the region we want. Check if the circle intersects the point.
dist = point.length();
if (dist > radius) {
// No intersection
T_VECTORS.push(circlePos);
T_VECTORS.push(edge);
T_VECTORS.push(point);
T_VECTORS.push(point2);
return false;
} else if (response) {
// It intersects, calculate the overlap.
response.bInA = false;
overlapN = point.normalize();
overlap = radius - dist;
}
}
T_VECTORS.push(point2);
// If it's the right region:
} else if (region === RIGHT_VORNOI_REGION) {
// We need to make sure we're in the left region on the next edge
edge.copy(polyA.edges[next]);
// Calculate the center of the circle relative to the starting point of the next edge.
point.copy(circlePos).sub(points[next]);
region = vornoiRegion(edge, point);
if (region === LEFT_VORNOI_REGION) {
// It's in the region we want. Check if the circle intersects the point.
dist = point.length();
if (dist > radius) {
// No intersection
T_VECTORS.push(circlePos);
T_VECTORS.push(edge);
T_VECTORS.push(point);
return false;
} else if (response) {
// It intersects, calculate the overlap.
response.bInA = false;
overlapN = point.normalize();
overlap = radius - dist;
}
}
// Otherwise, it's the middle region:
} else {
// Need to check if the circle is intersecting the edge,
// Change the edge into its "edge normal".
var normal = edge.perp().normalize();
// Find the perpendicular distance between the center of the
// circle and the edge.
dist = point.dotProduct(normal);
var distAbs = Math.abs(dist);
// If the circle is on the outside of the edge, there is no intersection.
if (dist > 0 && distAbs > radius) {
// No intersection
T_VECTORS.push(circlePos);
T_VECTORS.push(normal);
T_VECTORS.push(point);
return false;
} else if (response) {
// It intersects, calculate the overlap.
overlapN = normal;
overlap = radius - dist;
// If the center of the circle is on the outside of the edge, or part of the
// circle is on the outside, the circle is not fully inside the polygon.
if (dist >= 0 || overlap < 2 * radius) {
response.bInA = false;
}
}
}
// If this is the smallest overlap we've seen, keep it.
// (overlapN may be null if the circle was in the wrong Vornoi region).
if (overlapN && response && Math.abs(overlap) < Math.abs(response.overlap)) {
response.overlap = overlap;
response.overlapN.copy(overlapN);
}
}
// Calculate the final overlap vector - based on the smallest overlap.
if (response) {
response.a = a;
response.b = b;
response.overlapV.copy(response.overlapN).scale(response.overlap);
}
T_VECTORS.push(circlePos);
T_VECTORS.push(edge);
T_VECTORS.push(point);
return true;
};
/**
* Check if an ellipse and a polygon collide. <br>
* **NOTE:** This is slightly less efficient than testPolygonEllipse as it just
* runs testPolygonEllipse and reverses the response at the end.
* @ignore
* @param {me.Entity} a a reference to the object A.
* @param {me.Ellipse} ellipseA a reference to the object A Ellipse to be tested
* @param {me.Entity} a a reference to the object B.
* @param {me.Polygon} polyB a reference to the object B Polygon to be tested
* @param {Response=} response Response object (optional) that will be populated if
* they intersect.
* @return {boolean} true if they intersect, false if they don't.
*/
api.testEllipsePolygon = function (a, ellipseA, b, polyB, response) {
// Test the polygon against the circle.
var result = api.testPolygonEllipse(b, polyB, a, ellipseA, response);
if (result && response) {
// Swap A and B in the response.
var resa = response.a;
var aInB = response.aInB;
response.overlapN.reverse();
response.overlapV.reverse();
response.a = response.b;
response.b = resa;
response.aInB = response.bInA;
response.bInA = aInB;
}
return result;
};
/**
* Checks whether line segments collide.
* @ignore
* @param {me.Entity} a a reference to the object A.
* @param {me.Line} lineA a reference to the object A Line to be tested
* @param {me.Entity} b a reference to the object B.
* @param {me.Line} lineB a reference to the object B Line to be tested
* @param {Response=} response Response object (optional) that will be populated if they intersect.
* @return {boolean} true if they intersect, false if they don't.
*/
api.testLineLine = function (a, LineA, b, lineB, response) {
return this.testPolygonPolygon(a, LineA, b, lineB, response);
};
/**
* Check if a line segment and an ellipse collide.
* @ignore
* @param {me.Entity} a a reference to the object A.
* @param {me.Line} lineA a reference to the object A Line to be tested
* @param {me.Entity} b a reference to the object B.
* @param {me.Ellipse} ellipseB a reference to the object B Ellipse to be tested
* @param {Response=} response Response object (optional) that will be populated if they intersect.
* @return {boolean} true if they intersect, false if they don't.
*/
api.testLineEllipse = function (a, LineA, b, EllipseB, response) {
return this.testPolygonEllipse(a, LineA, b, EllipseB, response);
};
/**
* Check if an ellipse and a line segment collide. <br>
* **NOTE:** This is slightly less efficient than testLineEllipse as it just
* runs testLineEllipse and reverses the response at the end.
* @ignore
* @param {me.Entity} a a reference to the object A.
* @param {me.Ellipse} ellipseA a reference to the object A Ellipse to be tested
* @param {me.Entity} b a reference to the object B.
* @param {me.Line} lineB a reference to the object B Line to be tested
* @param {Response=} response Response object (optional) that will be populated if they intersect.
* @return {boolean} true if they intersect, false if they don't.
*/
api.testEllipseLine = function (a, ellipseA, b, lineB, response) {
return this.testEllipsePolygon(a, ellipseA, b, lineB, response);
};
/**
* Check if a line segment and a polygon collide.
* @ignore
* @param {me.Entity} a a reference to the object A.
* @param {me.Line} lineA a reference to the object A Line to be tested
* @param {me.Entity} b a reference to the object B.
* @param {me.Polygon} polygonB a reference to the object B Polygon to be tested
* @param {Response=} response Response object (optional) that will be populated if they intersect.
* @return {boolean} true if they intersect, false if they don't.
*/
api.testLinePolygon = function (a, lineA, b, polygonB, response) {
return this.testPolygonPolygon(a, lineA, b, polygonB, response);
};
/**
* Check if a polygon and a line segment collide. <br>
* **NOTE:** This is slightly less efficient than testLinePolygon as it just
* runs testLinePolygon and reverses the response at the end.
* @ignore
* @param {me.Entity} a a reference to the object A.
* @param {me.Polygon} polygonA a reference to the object A Polygon to be tested
* @param {me.Entity} b a reference to the object B.
* @param {me.Line} lineB a reference to the object B Line to be tested
* @param {Response=} response Response object (optional) that will be populated if they intersect.
* @return {boolean} true if they intersect, false if they don't.
*/
api.testPolygonLine = function (a, polygonA, b, lineB, response) {
return this.testPolygonPolygon(a, polygonA, b, lineB, response);
};
// return our object
return api;
})();
})();