forked from craftyjs/Crafty
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcollision.js
249 lines (217 loc) · 6.7 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
/**@
* #Collision
* @category 2D
* Component to detect collision between any two convex polygons.
*/
Crafty.c("Collision", {
init: function() {
this.requires("2D");
},
/**@
* #.collision
* @comp Collision
* @sign public this .collision([Crafty.Polygon polygon])
* @param polygon - Crafty.Polygon object that will act as the hit area
* Constructor takes a polygon to use as the hit area. If left empty,
* will create a rectangle polygon based on the x, y, w, h dimensions.
*
* This must be called before any .hit() or .onhit() methods.
*
* The hit area (polygon) must be a convex shape and not concave
* for the collision detection to work.
* @example
* ~~~
* Crafty.e("2D, Collision").collision(
* new Crafty.polygon([50,0], [100,100], [0,100])
* );
* ~~~
* @see Crafty.Polygon
*/
collision: function(poly) {
var area = this._mbr || this;
//if no polygon presented, create a square
if(!poly) {
poly = new Crafty.polygon([0,0],[area._w,0],[area._w,area._h],[0,area._h]);
}
this.map = poly;
this.attach(this.map);
this.map.shift(area._x, area._y);
return this;
},
/**@
* #.hit
* @comp Collision
* @sign public Boolean/Array hit(String component)
* @param component - Check collision with entities that has this component
* @return `false` if no collision. If a collision is detected, returns an Array of objects that are colliding.
* Takes an argument for a component to test collision for. If a collision is found, an array of
* every object in collision along with the amount of overlap is passed.
*
* If no collision, will return false. The return collision data will be an Array of Objects with the
* type of collision used, the object collided and if the type used was SAT (a polygon was used as the hitbox) then an amount of overlap.
* ~~~
* [{
* obj: [entity],
* type "MBR" or "SAT",
* overlap: [number]
* }]
* ~~~
* `MBR` is your standard axis aligned rectangle intersection (`.intersect` in the 2D component).
* `SAT` is collision between any convex polygon.
* @see .onHit, 2D
*/
hit: function(comp) {
var area = this._mbr || this,
results = Crafty.map.search(area, false),
i = 0, l = results.length,
dupes = {},
id, obj, oarea, key,
hasMap = ('map' in this && 'containsPoint' in this.map),
finalresult = [];
if(!l) {
return false;
}
for(;i<l;++i) {
obj = results[i];
oarea = obj._mbr || obj; //use the mbr
if(!obj) continue;
id = obj[0];
//check if not added to hash and that actually intersects
if(!dupes[id] && this[0] !== id && obj.__c[comp] &&
oarea._x < area._x + area._w && oarea._x + oarea._w > area._x &&
oarea._y < area._y + area._h && oarea._h + oarea._y > area._y)
dupes[id] = obj;
}
for(key in dupes) {
obj = dupes[key];
if(hasMap && 'map' in obj) {
var SAT = this._SAT(this.map, obj.map);
SAT.obj = obj;
SAT.type = "SAT";
if(SAT) finalresult.push(SAT);
} else {
finalresult.push({obj: obj, type: "MBR"});
}
}
if(!finalresult.length) {
return false;
}
return finalresult;
},
/**@
* #.onHit
* @comp Collision
* @sign public this .onHit(String component, Function hit[, Function noHit])
* @param component - Component to check collisions for
* @param hit - Callback method to execute when collided with component
* @param noHit - Callback method executed once as soon as collision stops
* Creates an enterframe event calling .hit() each time and if collision detected will invoke the callback.
* @see .hit
*/
onHit: function(comp, fn, fnOff) {
var justHit = false;
this.bind("EnterFrame", function() {
var hitdata = this.hit(comp);
if(hitdata) {
justHit = true;
fn.call(this, hitdata);
} else if(justHit) {
if (typeof fnOff == 'function') {
fnOff.call(this);
}
justHit = false;
}
});
return this;
},
_SAT: function(poly1, poly2) {
var points1 = poly1.points,
points2 = poly2.points,
i = 0, l = points1.length,
j, k = points2.length,
normal = {x: 0, y: 0},
length,
min1, min2,
max1, max2,
interval,
MTV = null,
MTV2 = 0,
MN = null,
dot,
nextPoint,
currentPoint;
//loop through the edges of Polygon 1
for(;i<l;i++) {
nextPoint = points1[(i==l-1 ? 0 : i+1)];
currentPoint = points1[i];
//generate the normal for the current edge
normal.x = -(nextPoint[1] - currentPoint[1]);
normal.y = (nextPoint[0] - currentPoint[0]);
//normalize the vector
length = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
normal.x /= length;
normal.y /= length;
//default min max
min1 = min2 = -1;
max1 = max2 = -1;
//project all vertices from poly1 onto axis
for(j = 0; j < l; ++j) {
dot = points1[j][0] * normal.x + points1[j][1] * normal.y;
if(dot > max1 || max1 === -1) max1 = dot;
if(dot < min1 || min1 === -1) min1 = dot;
}
//project all vertices from poly2 onto axis
for(j = 0; j < k; ++j) {
dot = points2[j][0] * normal.x + points2[j][1] * normal.y;
if(dot > max2 || max2 === -1) max2 = dot;
if(dot < min2 || min2 === -1) min2 = dot;
}
//calculate the minimum translation vector should be negative
interval = (min1 < min2) ? min2 - max1 : min1 - max2;
//exit early if positive
if(interval > 0) {
return false;
}
if(interval > MTV || MTV === null) MTV = interval;
}
//loop through the edges of Polygon 1
for(i=0;i<k;i++) {
nextPoint = points2[(i==k-1 ? 0 : i+1)];
currentPoint = points2[i];
//generate the normal for the current edge
normal.x = -(nextPoint[1] - currentPoint[1]);
normal.y = (nextPoint[0] - currentPoint[0]);
//normalize the vector
length = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
normal.x /= length;
normal.y /= length;
//default min max
min1 = min2 = -1;
max1 = max2 = -1;
//project all vertices from poly1 onto axis
for(j = 0; j < l; ++j) {
dot = points1[j][0] * normal.x + points1[j][1] * normal.y;
if(dot > max1 || max1 === -1) max1 = dot;
if(dot < min1 || min1 === -1) min1 = dot;
}
//project all vertices from poly2 onto axis
for(j = 0; j < k; ++j) {
dot = points2[j][0] * normal.x + points2[j][1] * normal.y;
if(dot > max2 || max2 === -1) max2 = dot;
if(dot < min2 || min2 === -1) min2 = dot;
}
//calculate the minimum translation vector should be negative
interval = (min1 < min2) ? min2 - max1 : min1 - max2;
//exit early if positive
if(interval > 0) {
return false;
}
if(interval > MTV || MTV === null) MTV = interval;
if (interval < MTV2) {
MTV2 = interval;
MN = {x: normal.x, y: normal.y};
}
}
return {overlap: MTV, normal: MN};
}
});