Hello. I'm designing a game, and I'm trying to work out the collision detection and handling.
I made a test application with two balls; one that you move around with the arrow keys. I copied the algorithm described in
this article. Generally when you try to move one ball over the other it's made to only go far enough to touch it, preventing overlap. However, there's still some overlap at the left and right sides of the stationary ball, and sometimes when coming from just off the top or bottom the movable ball can be blocked even when a small gap is shown between the balls. Only when you try to collide from the top or bottom do you get good results.
This is my method for handling collisions:
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
| private boolean testForCollision(float Nx, float Ny) { float moveVecX = A.dx(); float moveVecY = A.dy(); float moveVecMag = (float)Math.sqrt((moveVecX * moveVecX) + (moveVecY * moveVecY)); double dist = Math.sqrt(((B.x() - A.x()) * (B.x() - A.x())) + ((B.x() - A.x()) * (B.x() - A.x()))) - (A.radius() + B.radius()); if(moveVecMag < dist) { return false; } Nx = moveVecX / moveVecMag; Ny = moveVecY / moveVecMag;
float Cx = B.x() - A.x(); float Cy = B.y() - A.y();
double D = (Nx * Cx) + (Ny * Cy);
if(D <= 0) { return false; } double lengthC = Math.sqrt((Cx * Cx) + (Cy * Cy));
double F = (lengthC * lengthC) - (D * D);
double sumRadiiSquared = (A.radius() + B.radius()) * (A.radius() + B.radius()); if(F >= sumRadiiSquared) { return false; } double T = sumRadiiSquared - F;
if(T < 0) { return false; } double distance = D - Math.sqrt(T);
if(moveVecMag < distance) { return false; } Nx *= distance; Ny *= distance; return true; } |
If testForCollision() returns false, the ball is translated by its normal dx and dy. Otherwise it's translated by Nx and Ny, which should bring it to the surface of the other ball.
Any help would be apprieciated.
UPDATEWell, I could never figure out what was wrong, so I ended up redoing it. It's based a lot on what I tried to do before, and I'm not sure I can pin-point what's different that made the fix. What I did change was that this time I find the
time when the balls collide, then translate them accordingly. This is what I came up with:
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
| private double calculateTranslation(Ball A, Ball B) { double dPx = B.x() - A.x(); double dPy = B.y() - A.y(); double dP = Math.sqrt((dPx * dPx) + (dPy * dPy)); double dVx = B.dx() - A.dx(); double dVy = B.dy() - A.dy(); double dV = Math.sqrt((dVx * dVx) + (dVy * dVy)); double minDist = (B.radius() + A.radius()); double dist = dP - minDist; if(dV < dist) return 1.0;
double dVNx = dVx / dV; double dVNy = dVy / dV; double dotProd = (dVNx * dPx) + (dVNy * dPy); if (dotProd >= 0) return 1.0; double Fsq = (dP * dP) - (dotProd * dotProd); double minDistSq = minDist * minDist; if (Fsq >= minDistSq) return 1.0;
double Tsq = minDistSq - Fsq; if (Tsq < 0) return 1.0; double distFinal = Math.abs(dotProd) - Math.sqrt(Tsq);
if (dV <= distFinal) return 1.0; double time = distFinal / dV; return time; } |
I'm going to keep this since I haven't encountered any problems like I did with the last method, and I prefer getting the time value anyway.