/
detector.js
executable file
·209 lines (184 loc) · 7.66 KB
/
detector.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
import * as SAT from "./sat.js";
import ResponseObject from "./response.js";
import Vector2d from "./../math/vector2.js";
import Bounds from "./bounds.js";
// a dummy object when using Line for raycasting
let dummyObj = {
pos : new Vector2d(0, 0),
ancestor : {
_absPos : new Vector2d(0, 0),
getAbsolutePosition : function () {
return this._absPos;
}
}
};
// some cache bounds object used for collision detection
let boundsA = new Bounds();
let boundsB = new Bounds();
/**
* the Detector class contains methods for detecting collisions between bodies using a broadphase algorithm.
*/
export default class Detector {
/**
* @param {Container} world - the physic world this detector is bind to
*/
constructor(world) {
// @ignore
this.world = world;
/**
* the default response object used for collisions
* (will be automatically populated by the collides functions)
* @type {ResponseObject}
*/
this.response = new ResponseObject();
}
/**
* determine if two objects should collide (based on both respective objects body collision mask and type).<br>
* you can redefine this function if you need any specific rules over what should collide with what.
* @param {Renderable|Container|Entity|Sprite|NineSliceSprite} a - a reference to the object A.
* @param {Renderable|Container|Entity|Sprite|NineSliceSprite} b - a reference to the object B.
* @returns {boolean} true if they should collide, false otherwise
*/
shouldCollide(a, b) {
let bodyA = a.body,
bodyB = b.body;
return (
(typeof bodyA === "object" && typeof bodyB === "object") &&
a !== b &&
a.isKinematic !== true && b.isKinematic !== true &&
bodyA.shapes.length > 0 && bodyB.shapes.length > 0 &&
!(bodyA.isStatic === true && bodyB.isStatic === true) &&
(bodyA.collisionMask & bodyB.collisionType) !== 0 &&
(bodyA.collisionType & bodyB.collisionMask) !== 0
);
}
/**
* detect collision between two bodies.
* @param {Body} bodyA - a reference to body A.
* @param {Body} bodyB - a reference to body B.
* @returns {boolean} true if colliding
*/
collides(bodyA, bodyB, response = this.response) {
// for each shape in body A
for (let indexA = bodyA.shapes.length, shapeA; indexA--, (shapeA = bodyA.shapes[indexA]);) {
// for each shape in body B
for (let indexB = bodyB.shapes.length, shapeB; indexB--, (shapeB = bodyB.shapes[indexB]);) {
// full SAT collision check
if (SAT["test" + shapeA.type + shapeB.type].call(
this,
bodyA.ancestor, // a reference to the object A
shapeA,
bodyB.ancestor, // a reference to the object B
shapeB,
// clear response object before reusing
response.clear()) === true
) {
// set the shape index
response.indexShapeA = indexA;
response.indexShapeB = indexB;
return true;
}
}
}
return false;
}
/**
* find all the collisions for the specified object using a broadphase algorithm
* @ignore
* @param {Renderable|Container|Entity|Sprite|NineSliceSprite} objA - object to be tested for collision
* @returns {boolean} in case of collision, false otherwise
*/
collisions(objA) {
let collisionCounter = 0;
// retreive a list of potential colliding objects from the game world
let candidates = this.world.broadphase.retrieve(objA);
boundsA.addBounds(objA.getBounds(), true);
boundsA.addBounds(objA.body.getBounds());
candidates.forEach((objB) => {
// check if both objects "should" collide
if (this.shouldCollide(objA, objB)) {
boundsB.addBounds(objB.getBounds(), true);
boundsB.addBounds(objB.body.getBounds());
// fast AABB check if both bounding boxes are overlaping
if (boundsA.overlaps(boundsB)) {
if (this.collides(objA.body, objB.body)) {
// we touched something !
collisionCounter++;
// execute the onCollision callback
if (objA.onCollision && objA.onCollision(this.response, objB) !== false && objA.body.isStatic === false) {
objA.body.respondToCollision.call(objA.body, this.response);
}
if (objB.onCollision && objB.onCollision(this.response, objA) !== false && objB.body.isStatic === false) {
objB.body.respondToCollision.call(objB.body, this.response);
}
}
}
}
});
// we could return the amount of objects we collided with ?
return collisionCounter > 0;
}
/**
* Checks for object colliding with the given line
* @ignore
* @param {Line} line - line to be tested for collision
* @param {Array.<Renderable>} [result] - a user defined array that will be populated with intersecting physic objects.
* @returns {Array.<Renderable>} an array of intersecting physic objects
* @example
* // define a line accross the viewport
* let ray = new me.Line(
* // absolute position of the line
* 0, 0, [
* // starting point relative to the initial position
* new me.Vector2d(0, 0),
* // ending point
* new me.Vector2d(me.game.viewport.width, me.game.viewport.height)
* ]);
*
* // check for collition
* result = me.collision.rayCast(ray);
*
* if (result.length > 0) {
* // ...
* }
*/
rayCast(line, result = []) {
let collisionCounter = 0;
// retrieve a list of potential colliding objects from the game world
let candidates = this.world.broadphase.retrieve(line);
for (let i = candidates.length, objB; i--, (objB = candidates[i]);) {
// fast AABB check if both bounding boxes are overlaping
if (objB.body && line.getBounds().overlaps(objB.getBounds())) {
// go trough all defined shapes in B (if any)
const bLen = objB.body.shapes.length;
if (objB.body.shapes.length === 0) {
continue;
}
let shapeA = line;
// go through all defined shapes in B
let indexB = 0;
do {
let shapeB = objB.body.getShape(indexB);
// full SAT collision check
if (SAT["test" + shapeA.type + shapeB.type]
.call(
this,
dummyObj, // a reference to the object A
shapeA,
objB, // a reference to the object B
shapeB
)) {
// we touched something !
result[collisionCounter] = objB;
collisionCounter++;
}
indexB++;
} while (indexB < bLen);
}
}
// cap result in case it was not empty
result.length = collisionCounter;
// return the list of colliding objects
return result;
}
}