Search code examples
javalibgdx2dcollision-detection

Detect collision with lines and limit movement


I'm making a game with libGDX in Java. I'm trying to make a collision detection. As you can see in the image, I have a line which is a wall and a player with specified radius. The desired position is the next location which the player is trying to be in. But because there is a wall, he's placed in the Actual Position which is on the Velocity vector, but more closer to the prev location. I'm trying to figure out how can I detect that closer position? enter image description here

My attempt:

private void move(float deltaTime) {
    float step;
    beginMovementAltitude();
    if (playerComponent.isWalking())
        step = handleAcceleration(playerComponent.getSpeed() + playerComponent.getAcceleration());
    else step = handleDeacceleration(playerComponent.getSpeed(), playerComponent.getAcceleration());
    playerComponent.setSpeed(step);
    if (step == 0) return;
    takeStep(deltaTime, step, 0);
}
private void takeStep(float deltaTime, float step, int rotate) {
    Vector3 position = playerComponent.getCamera().position;
    float x = position.x;
    float y = position.y;
    int radius = playerComponent.getRadius();
    auxEnvelope.init(x, x + radius, y, y + radius);
    List<Line> nearbyLines = lines.query(auxEnvelope);
    float theta;
    int numberOfIntersections = 0;
    float angleToMove = 0;
    Gdx.app.log(step + "", "");
    for (Line line : nearbyLines) {
        VertexElement src = line.getSrc();
        VertexElement dst = line.getDst();
        auxVector3.set(playerComponent.getCamera().direction);
        auxVector3.rotate(Vector3.Z, rotate);
        float nextX = x + (step * deltaTime) * (auxVector3.x);
        float nextY = y + (step * deltaTime) * playerComponent.getCamera().direction.y;
        float dis = Intersector.distanceLinePoint(src.getX(), src.getY(), dst.getX(), dst.getY(), nextX, nextY);
        boolean bodyIntersection = dis <= 0.5f;
        auxVector21.set(src.getX(), src.getY());
        auxVector22.set(dst.getX(), dst.getY());
        auxVector23.set(nextX, nextY);
        if (bodyIntersection) {
            numberOfIntersections++;
            if (numberOfIntersections > 1) {
                return;
            }
            theta = auxVector22.sub(auxVector21).nor().angle();
            float angle = (float) (180.0 / MathUtils.PI * MathUtils.atan2(auxVector23.y - position.y, auxVector23.x - position.x));
            if (angle < 0) angle += 360;
            float diff = (theta > angle) ? theta - angle : angle - theta;
            if (step < 0) step *=-1;
            angleToMove = (diff > 90) ? theta + 180 : theta;
        }
    }
    if (numberOfIntersections == 0) {
        moveCameraByWalking(deltaTime, step, rotate);
    } else {
        moveCameraInDirection(deltaTime, step, angleToMove);
    }
}

Solution

  • The idea is to find intersection of path of object center and the line moved by radius of the circle, see that picture.

    this is the idea

    At first, you need to find a normal to the line. How to do it, depends on how the line is defined, if it's defined by two points, the formula is

    nx = ay - by
    ny = bx - ax
    

    If the line is defined by canonical equation, then coefficients at x and y define normal, if I remembered correctly.

    When normal is found, we need to normalize it - set length to 1 by dividing coordinates by vector length. Let it be n.

    Then, we will project starting point, desired point and randomly chosen point on line to n, treating them as radius vectors.

    Projection of vector a to vector b is

    project (a, b) = scalar_product (a, b) / length (b)**2 * b
    

    but since b is n which length equals 1, we will not apply division, and also we want to only find length of the result, we do not multiply by b. So we only compute scalar product with n for each of three aforementioned points, getting three numbers, let s be the result for starting point, d for desired point, l for chosen point on the line.

    Then we should modify l by radius of the circle:

    if      (s < d) l -= r;
    else if (s > d) l += r;
    

    If s = d, your object moves in parallel along the line, so line can't obstruct its movement. It's highly improbable case but should be dealt with.

    Also, that's important, if l was initially between s and d but after modifying is no longer between then, it's a special case you may want to handle (restrict object movement for example)

    corner case

    Ather that, you should compute (d - s) / (l - s).

    If the result is greater or equals 1, the object will not reach the line.
    If the result is between 0 and 1, the line obstructs movement and the result indicates part of the path the object will complete. 0.5 means that object will stop halfway.
    If the result is negative, it means the line is behind the object and will not obstruct movement.

    Note that when using floating point numbers the result will not be perfectly precise, that's why we handle that special case. If you want to prevent this from happening at all, organize loop and try approximations until needed precision is reached.