I've been trying to wrap my head around this for some time now.
I wrote an implementation of AABB sweep collision detection. I though I understood the idea but it didn't work as expected. I rewrote it from scratch following the first part of this article but adapting for two moving objects (relative velocity instead of velocity of one of them) and normalized time (divide time by dt to know if collisions happen on this frame) but it still detects collisions wrong.
A similar question was asked on Stack Overflow last year but went without answers.
¿How wrong?
This is a stationary state of the simulation. Red rectangles represent hitboxes. What's happening here is that the green cube is "resting on top" of the black pillar to its right after a vertical fall.
If the pillar was right under the green block, it would detect it anyway and all would be allright but, as it is, it's giving a false positive.
I believe my last if condition might be responsible for this but still I don't think I'm fully grasping the meaning of AABB sweeping.
if (entryTime > exitTime
|| (xEntry < 0.0
&& yEntry < 0.0)
|| xEntry > 1.0
|| yEntry > 1.0) {
// there's no collision
return false;
}
So... As I understand, I'm checking that:
- The entry happens before the exit. (this is not supposed to ever happen)
- xEntry and yEntry haven't both happened yet (time left for them isn't negative)
- Neither xEntry nor yEntry are going to happen after the frame is over. (?)
That said, here are some proper questions (in descendant priority):
- Is the lack of horizontal velocity the problem? Should I add an explicit check if I know some axis collision is not going to happen, to see if the object is in collision range in that axis or not? This sounds like the way to go but I would like to avoid unnecesary special cases. (It's also very surprising to me that such an obvious problem wouldn't be taken into account).
- Else, how is AABB sweep supposed to detect A is hitting B and not just passing by as it's the case?
- Is the problem caused by any of my two "fixes" (namely, relative velocity and normalized time?)
Here's the relevant code:
function aabb_sweep(e1, e2, dt) {
var p1 = e1.pos;
var size1 = e1.hitbox;
var p2 = e2.pos;
var size2 = e2.hitbox;
// DEVIATION ONE: RELATIVE VELOCITY
var rel_vel = e1.vel.add(e2.vel.scale(-1));
var xInvEntry, xInvExit; // distance between edges
var yInvEntry, yInvExit;
if (rel_vel.x() > 0.0) {
xInvEntry = (p2.x() - size2.x()) - (p1.x() + size1.x());
xInvExit = (p2.x() + size2.x()) - (p1.x() - size1.x());
} else {
xInvEntry = (p2.x() + size2.x()) - (p1.x() - size1.x());
xInvExit = (p2.x() - size2.x()) - (p1.x() + size1.x());
};
if (rel_vel.y() > 0.0) {
yInvEntry = (p2.y() - size2.y()) - (p1.y() + size1.y());
yInvExit = (p2.y() + size2.y()) - (p1.y() - size1.y());
} else {
yInvEntry = (p2.y() + size2.y()) - (p1.y() - size1.y());
yInvExit = (p2.y() - size2.y()) - (p1.y() + size1.y());
};
var xEntry, xExit; // entry time
var yEntry, yExit;
// in case edge distance is 0, to avoid NaN.
if (rel_vel.x() == 0.0) {
xEntry = -Infinity;
xExit = Infinity;
} else {
// DEVIATION TWO: NORMALIZED TIME
xEntry = xInvEntry / rel_vel.x() / dt; // divide by amount of time in the frame
xExit = xInvExit / rel_vel.x() / dt; // to know if it happens this frame
};
if (rel_vel.y() == 0.0) {
yEntry = -Infinity;
yExit = Infinity;
} else {
yEntry = yInvEntry / rel_vel.y() / dt;
yExit = yInvExit / rel_vel.y() / dt;
};
// erliest collision time
var entryTime = Math.max(xEntry, yEntry);
var exitTime = Math.min(xExit, yExit);
if (entryTime > exitTime ||
(xEntry < 0.0 &&
yEntry < 0.0) ||
xEntry > 1.0 ||
yEntry > 1.0) {
// there's no collision
return false;
} else {
// there's a collision
var normalx = (xEntry < 1.0) && (xEntry >= yEntry) ? 1 : 0;
if (rel_vel.x() > 0.0) normalx *= -1;
var normaly = (yEntry < 1.0) && (yEntry >= xEntry) ? 1 : 0;
if (rel_vel.y() > 0.0) normaly *= -1;
var normal = V(normalx, normaly);
var time = entryTime;
var distance = (xEntry > yEntry) ? xInvEntry : yInvEntry;
console.log(e1.name + ' collided with ' + e2.name);
console.log('e1.pos' + String(e1.pos));
console.log('e2.pos' + String(e2.pos));
console.log('e1.hitbox' + String(e1.hitbox));
console.log('e2.hitbox' + String(e2.hitbox));
console.log('xEntry ' + xEntry);
console.log('yEntry ' + yEntry);
console.log('normal ' + normal);
console.log('time ' + time);
console.log('distance ' + distance);
console.log('velocity ' + rel_vel);
e1.collisions.push({
against: e2,
normal: normal,
time: time,
distance: distance,
rel_vel: rel_vel,
});
return true;
};
};
Thank you for your attention.