Skip to content

Commit

Permalink
Merge pull request #1147 from starwed/better-hit
Browse files Browse the repository at this point in the history
Optimize hit() and _SAT()
  • Loading branch information
starwed authored Jul 6, 2017
2 parents c91649c + 890dbc8 commit 602340b
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 70 deletions.
105 changes: 59 additions & 46 deletions src/spatial/collision.js
Original file line number Diff line number Diff line change
Expand Up @@ -425,36 +425,51 @@ Crafty.c("Collision", {
* @comp Collision
* @kind Method
*
* @sign public Array .hit(String component)
* @sign public Array .hit(String component[, Array results])
* @param component - Check collision with entities that have this component
* applied to them.
* @param results - If a results array is supplied, any collisions will be appended to it
* @return `null` if there is no collision. If a collision is detected,
* returns an Array of collision data objects (see below).
* If the results parameter was passed, it will be used as the return value.
*
* Tests for collisions with entities that have the specified component
* applied to them.
* If a collision is detected, data regarding the collision will be present in
* the array returned by this method.
* If no collisions occur, this method returns `null`.
* Tests for collisions with entities that have the specified component applied to them.
* If a collision is detected, data regarding the collision will be present in the array
* returned by this method. If no collisions occur, this method returns `null`.
*
* When testing for collisions, if both entities have the `Collision` component, then
* the collision test will use the Separating Axis Theorem (SAT), and provide more detailed
* information about the collision. Otherwise, it will be a simple test of whether the
* minimal bounding rectangles (MBR) overlap.
*
* Following is a description of a collision data object that this method may
* return: The returned 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]
* overlap: [number],
* nx: [number],
* ny: [number]
* }]
* ~~~
*
* All collision results will have these properties:
* - **obj:** The entity with which the collision occured.
* - **type:** Collision detection method used. One of:
* - *MBR:* Standard axis aligned rectangle intersection (`.intersect` in the 2D component).
* - *SAT:* Collision between any two convex polygons. Used when both colliding entities have the `Collision` component applied to them.
* - **overlap:** If SAT collision was used, this will signify the overlap percentage between the colliding entities.
*
* If the collision result type is **SAT** then there will be three additional properties, which
* represent the minimum translation vector (MTV) -- the direction and distance of the minimal translation
* that will result in non-overlapping entities.
* - **overlap:** The magnitude of the translation vector.
* - **nx:** The x component of the MTV.
* - **ny:** The y component of the MTV.
*
* Keep in mind that both entities need to have the `Collision` component, if you want to check for `SAT` (custom hitbox) collisions between them.
* These additional properties (returned only when both entities have the "Collision" component)
* are useful when providing more natural collision resolution.
*
* If you want more fine-grained control consider using `Crafty.map.search()`.
*
Expand All @@ -471,8 +486,8 @@ Crafty.c("Collision", {
* hitData = hitDatas[0]; // resolving collision for just one collider
* if (hitData.type === 'SAT') { // SAT, advanced collision resolution
* // move player back by amount of overlap
* this.x -= hitData.overlap * hitData.normal.x;
* this.y -= hitData.overlap * hitData.normal.y;
* this.x -= hitData.overlap * hitData.nx;
* this.y -= hitData.overlap * hitData.ny;
* } else { // MBR, simple collision resolution
* // move player to position before he moved (on respective axis)
* this[evt.axis] = evt.oldValue;
Expand All @@ -483,54 +498,54 @@ Crafty.c("Collision", {
*
* @see Crafty.map#Crafty.map.search
*/
hit: function (component) {
var area = this._cbr || this._mbr || this,
results = Crafty.map.unfilteredSearch(area),
i = 0,
l = results.length,
dupes = {},
id, obj, oarea, key,
overlap = Crafty.rectManager.overlap,
hasMap = ('map' in this && 'containsPoint' in this.map),
finalresult = [];

_collisionHitDupes: [],
_collisionHitResults: [],
hit: function (component, results) {
var area = this._cbr || this._mbr || this;
var searchResults = this._collisionHitResults;
searchResults.length = 0;
searchResults = Crafty.map.unfilteredSearch(area, searchResults);
var l = searchResults.length;
if (!l) {
return null;
}
var i = 0,
dupes = this._collisionHitDupes,
id, obj;

results = results || [];
dupes.length = 0;

for (; i < l; ++i) {
obj = results[i];
oarea = obj._cbr || obj._mbr || obj; //use the mbr
obj = searchResults[i];

if (!obj) continue;
id = obj[0];

//check if not added to hash and that actually intersects
if (!dupes[id] && this[0] !== id && obj.__c[component] && overlap(oarea, area))
if (!dupes[id] && this[0] !== id && obj.__c[component]){
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 (obj.map) {
var SAT = this._SAT(this.map, obj.map);
if (SAT) {
results.push(SAT);
SAT.obj = obj;
SAT.type = "SAT";
}
} else if (Crafty.rectManager.overlap(area, this._cbr || this._mbr || this)){
results.push({
obj: obj,
type: "MBR"
});
}
}
}

if (!finalresult.length) {
if (!results.length) {
return null;
}

return finalresult;
return results;
},

/**@
Expand Down Expand Up @@ -934,10 +949,8 @@ Crafty.c("Collision", {

return {
overlap: MTV,
normal: {
x: MNx,
y: MNy
}
nx: MNx,
ny: MNy
};
}
});
48 changes: 24 additions & 24 deletions tests/unit/spatial/sat.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
var o = e._SAT(poly1, poly2);
_.notEqual(o, false, "Overlap exists");
_.strictEqual(o.overlap, -1, "Overlap by 1 unit");
_.strictEqual(o.normal.x, -1, "normal.x is -1");
_.strictEqual(o.normal.y, 0, "normal.y is 0");
_.strictEqual(o.nx, -1, "nx is -1");
_.strictEqual(o.ny, 0, "ny is 0");

// order 2
o = e._SAT(poly2, poly1);
_.notEqual(o, false, "Overlap exists");
_.strictEqual(o.overlap, -1, "Overlap by 1 unit");
_.strictEqual(o.normal.x, 1, "normal.x is 1");
_.strictEqual(o.normal.y, 0, "normal.y is 0");
_.strictEqual(o.nx, 1, "nx is 1");
_.strictEqual(o.ny, 0, "ny is 0");

});

Expand All @@ -32,15 +32,15 @@
var o = e._SAT(poly1, poly2);
_.notEqual(o, false, "Overlap exists");
_.strictEqual(o.overlap, -1, "Overlap by 1 unit");
_.strictEqual(o.normal.y, -1, "normal.y is -1");
_.strictEqual(o.normal.x, 0, "normal.x is 0");
_.strictEqual(o.ny, -1, "ny is -1");
_.strictEqual(o.nx, 0, "nx is 0");

// order 2
o = e._SAT(poly2, poly1);
_.notEqual(o, false, "Overlap exists");
_.strictEqual(o.overlap, -1, "Overlap by 1 unit");
_.strictEqual(o.normal.y, 1, "normal.y is 1");
_.strictEqual(o.normal.x, 0, "normal.x is 0");
_.strictEqual(o.ny, 1, "ny is 1");
_.strictEqual(o.nx, 0, "nx is 0");

});

Expand All @@ -53,15 +53,15 @@
var o = e._SAT(poly1, poly2);
_.notEqual(o, false, "Overlap exists");
_.strictEqual(o.overlap, -1, "Overlap by 1 unit");
_.strictEqual(o.normal.x, -1, "normal.x is -1");
_.strictEqual(o.normal.y, 0, "normal.y is 0");
_.strictEqual(o.nx, -1, "nx is -1");
_.strictEqual(o.ny, 0, "ny is 0");

// order 2
o = e._SAT(poly2, poly1);
_.notEqual(o, false, "Overlap exists");
_.strictEqual(o.overlap, -1, "Overlap by 1 unit");
_.strictEqual(o.normal.x, 1, "normal.x is 1");
_.strictEqual(o.normal.y, 0, "normal.y is 0");
_.strictEqual(o.nx, 1, "nx is 1");
_.strictEqual(o.ny, 0, "ny is 0");

});

Expand All @@ -72,15 +72,15 @@
var o = e._SAT(poly1, poly2);
_.notEqual(o, false, "Overlap exists");
_.strictEqual(o.overlap, -1, "Overlap by 1 unit");
_.strictEqual(o.normal.y, -1, "normal.y is -1");
_.strictEqual(o.normal.x, 0, "normal.x is 0");
_.strictEqual(o.ny, -1, "ny is -1");
_.strictEqual(o.nx, 0, "nx is 0");

// order 2
o = e._SAT(poly2, poly1);
_.notEqual(o, false, "Overlap exists");
_.strictEqual(o.overlap, -1, "Overlap by 1 unit");
_.strictEqual(o.normal.y, 1, "normal.y is 1");
_.strictEqual(o.normal.x, 0, "normal.x is 0");
_.strictEqual(o.ny, 1, "ny is 1");
_.strictEqual(o.nx, 0, "nx is 0");
});

test("overlap with non parallel faces, but axis-aligned normal", function(_){
Expand All @@ -90,15 +90,15 @@
var o = e._SAT(poly1, poly2);
_.notEqual(o, false, "Overlap exists");
_.strictEqual(o.overlap, -1, "Overlap by 1 unit");
_.strictEqual(o.normal.x, -1, "normal.x is -1");
_.strictEqual(o.normal.y, 0, "normal.x is 0");
_.strictEqual(o.nx, -1, "nx is -1");
_.strictEqual(o.ny, 0, "nx is 0");

// order 2
o = e._SAT(poly2, poly1);
_.notEqual(o, false, "Overlap exists");
_.strictEqual(o.overlap, -1, "Overlap by 1 unit");
_.strictEqual(o.normal.x, 1, "normal.x is 1");
_.strictEqual(o.normal.y, 0, "normal.x is 0");
_.strictEqual(o.nx, 1, "nx is 1");
_.strictEqual(o.ny, 0, "nx is 0");
});

test("overlap with non parallel faces, non-axis-aligned normal", function(_){
Expand All @@ -112,15 +112,15 @@
var o = e._SAT(poly1, poly2);
_.notEqual(o, false, "Overlap exists");
_.ok(is_inverse_sqrt2(-o.overlap), "Overlap by 1/sqrt(2)");
_.ok( is_inverse_sqrt2(-o.normal.x), "normal.x is -1/sqrt(2)");
_.ok( is_inverse_sqrt2(-o.normal.y), "normal.y is -1/sqrt(2)");
_.ok( is_inverse_sqrt2(-o.nx), "nx is -1/sqrt(2)");
_.ok( is_inverse_sqrt2(-o.ny), "ny is -1/sqrt(2)");


// order 2
o = e._SAT(poly2, poly1);
_.notEqual(o, false, "Overlap exists");
_.ok(is_inverse_sqrt2(-o.overlap), "Overlap by 1/sqrt(2)");
_.ok( is_inverse_sqrt2(o.normal.x), "normal.x is +1/sqrt(2)");
_.ok( is_inverse_sqrt2(o.normal.y), "normal.y is +1/sqrt(2)");
_.ok( is_inverse_sqrt2(o.nx), "nx is +1/sqrt(2)");
_.ok( is_inverse_sqrt2(o.ny), "ny is +1/sqrt(2)");
});
})();

0 comments on commit 602340b

Please sign in to comment.