I'm writing my own physics engine right now (following this tutorial), but I'm running into some problems. Sometimes on collision, objects will just phase through each other instead of bouncing off of each other.
Is there any way I can fix this?
public static void fixCollision(final RectObject a, final RectObject b) {
final Vec2D normal = collisionNormal(a, b);
// Calculate relative velocity
final Vec2D rv = b.velocity.minus(a.velocity);
// Calculate relative velocity in terms of the normal direction
final float velAlongNormal = rv.dotProduct(normal);
// Do not resolve if velocities are separating
if (velAlongNormal > 0) {
return;
}
// Calculate restitution
final float e = Math.min(a.restitution, b.restitution);
// Calculate impulse scalar
float j = -(1 + e) * velAlongNormal;
j /= a.getInvMass() + b.getInvMass();
// Apply impulse
final Vec2D impulse = normal.multiply(j);
a.velocity = a.velocity.minus(impulse.multiply(a.getInvMass()));
b.velocity = b.velocity.plus(impulse.multiply(b.getInvMass()));
positionalCorrection(a, b);
}
private static void positionalCorrection(final RectObject a, final RectObject b) {
final Rectangle2D r = new Rectangle2D.Float(a.min.x, a.min.y, a.width(), a.height()).createIntersection(new Rectangle2D.Float(b.min.x, b.min.y,
b.width(), b.height()));
final Vec2D normal = collisionNormal(a, b);
// penetration is the min resolving distance
final float penetration = (float) Math.min(r.getWidth(), r.getHeight());
final float percent = 0.01f; // usually .2 to .8
final float slop = 0.1f; // usually 0.01 to 0.1
final Vec2D correction = normal.multiply(Math.max(penetration - slop, 0.0f) / (a.getInvMass() + b.getInvMass()) * percent);
a.min = a.min.minus(correction.multiply(a.getInvMass()));
a.max = a.max.minus(correction.multiply(a.getInvMass()));
b.min = b.min.plus(correction.multiply(b.getInvMass()));
b.max = b.max.plus(correction.multiply(b.getInvMass()));
}
private static Vec2D collisionNormal(final RectObject a, final RectObject b) {
final float w = 0.5f * (a.width() + b.width());
final float h = 0.5f * (a.height() + b.height());
final float dx = a.center().x - b.center().x;
final float dy = a.center().y - b.center().y;
if (Math.abs(dx) <= w && Math.abs(dy) <= h) {
/* collision! */
final float wy = w * dy;
final float hx = h * dx;
if (wy > hx) {
if (wy > -hx) {
/* collision at the top */
return new Vec2D(0, -1);
} else {
/* on the left */
return new Vec2D(-1, 0);
}
} else {
if (wy > -hx) {
/* on the right */
return new Vec2D(1, 0);
} else {
/* at the bottom */
return new Vec2D(0, 1);
}
}
}
throw new IllegalArgumentException("Rectangles must be colliding");
}
RectObject
public class RectObject extends GameObject {
// upper left
public Vec2D min;
// bottom right
public Vec2D max;
public Vec2D center() {
return new Vec2D((max.x + min.x) / 2, (max.y + min.y) / 2);
}
@Override
public void update() {
min = min.plus(velocity);
max = max.plus(velocity);
}
public float width() {
return max.x - min.x;
}
public float height() {
return max.y - min.y;
}
}
GameObject
public abstract class GameObject {
public static final float INFINITE_MASS = 0;
private float invMass;
public Vec2D velocity;
public float restitution;
public abstract void update();
public float getMass() {
if (invMass == 0) {
return INFINITE_MASS;
}
return 1 / invMass;
}
public float getInvMass() {
return invMass;
}
public void setMass(final float mass) {
if (mass == INFINITE_MASS) {
invMass = 0;
} else {
invMass = 1 / mass;
}
}
public void applyForce(final Vec2D force) {
velocity = velocity.plus(force.multiply(invMass));
}
}