1
\$\begingroup\$

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?

enter image description here

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));
    }
}
\$\endgroup\$
1
\$\begingroup\$

There was something wrong with my collisionNormal method, where the left and right vectors were actually flipped. This made collisions along the x axis actually push the objects into each other.

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);
    }
}

turned into

if (wy > hx) {
    if (wy > -hx) {
        /* collision at the top */
        return new Vec2D(0, -1);
    } else {
        /* on the left */
        return new Vec2D(1, 0); // flipped
    }
} else {
    if (wy > -hx) {
        /* on the right */
        return new Vec2D(-1, 0); // flipped
    } else {
        /* at the bottom */
        return new Vec2D(0, 1);
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.