Search code examples
javacollisiongame-physics

Reflection circle / rectangle problems


I am trying to recreate breakout but the interaction betweeen the corner of the blocks and the ball is giving me a massive headache. Bouncing off the edges of the window and the blocks works just fine and bouncing off the windows' corners works perfectly too. For simplicity, all blocks are axis-aligned. Swapping x and y movement only works as intended for 2 out of 8 cases. For the other 6, one or two of the vectors have to be inverted for the collision to be correct, but I can't figure out a way to do this in a simple way. Writing 8 if-statements can't be correct and is prone to cause bugs.

The Ball class is an extension of the Ellipse2D.Double class.
The Block class is an extension of the Rectangle class.

Every question / answer / tutorial I took a look at would either be overcomplicated (not AA rects, rect not on a fixed position), simplified (handle as if two rects collided) or completely off-topic (detect collision / overlap, ball to ball handling).

Here's what I got:

public void move(Paddle p, List<Block> b) {
    //called every frame

    this.x += dx;
    this.y += dy;

    checkCollisionsWalls();
    // checkCollisionPaddle(p);
    checkCollisionBlocks(b);

    if (this.dir == 90 || this.dir == 270)
        this.dir++;
    // make sure ball wont get stuck between two walls of window

}

private void checkCollisionBlocks(List<Block> b) {

    for (Block block : b) {

        if (this.intersects(block)) {

            if (this.x + this.width / 2 >= block.x && this.x + this.width / 2 <= block.x + block.width) {

                System.out.println("Top / Bot reflect");
                this.dy *= -1;
                break;
            }

            if (this.y + this.height / 2 >= block.y && this.y + this.height / 2 <= block.y + block.height) {
                System.out.println("Left / Right reflect");
                this.dx *= -1;
                break;
            }

            // if execution got here we must have hit a corner
            reflectCorner();

        }
    }
}

private void reflectCorner() {

    if (dx > dy) {
        this.dy *= -1;
    } else {
        this.dx *= -1;
    }

    if (Math.abs(dx) == Math.abs(dy)) {
        this.dy *= -1;
        this.dx *= -1;
    }

    double temp = this.dx;
    this.dx = this.dy;
    this.dy = temp;
    temp = 0;

}

dx and dy are global vars in the Ball class, in fact all of the code is (still) in the Ball class.

EDIT: Sorry for looking over the part of asking a clear question, I'm a little worked up about it...

What is happening with this current code: Reflection works perfectly when hitting the bottom-right corner from any direction.
Hitting the top left corner results in the ball getting stuck inside the rect. It then wanders along the edges to the bottom-right corner
When touching the top-right corner while moving parallel to the x-axis and when touching the bottom-left corner while moving parallel to the y-axis, the ball reflects twice, inching in either positive y or positive x direction one pixel per bounce (the axis orthogonally it is moving to).
All other reflections work fine.

What should happen: The reflectCorner() method should work for all corners like it does for the bottom-right one and not produce results as decribed above.

Question: How do I do that without overcomplicated if-structures?

If necessary, i can provide the testing setup I used.


Solution

  • I finally managed to make it work on all sides. First I calculated which corner was hit by checking if the distance between the ball's center and any corner was smaller / equal to the ball's radius. Then I'd swap the x and y movement. For two corners that's all that was needed, for the second two I had to invert one of them, depending on corner and direction of the ball.

    Finally:

    private void reflectCorner(Rectangle rect) {
    
        double temp = this.dx;
        this.dx = this.dy;
        this.dy = temp;
        temp = 0;
    
        if (Math.abs(dx) == Math.abs(dy)) {
            this.dy *= -1;
            this.dx *= -1;
            return;
        }
    
        if (Math.sqrt(Math.pow(Math.abs(this.getCenterX() - rect.x), 2)
                + Math.pow(Math.abs(this.getCenterY() - rect.y), 2)) <= this.width / 2) {
            System.out.println("topL");
            if (dx < dy) {
                this.dy *= -1;
            } else {
                this.dx *= -1;
            }
            return;
        }
            if (Math.sqrt(Math.pow(Math.abs(this.getCenterX() - (rect.x + rect.width)), 2)
               + Math.pow(Math.abs(this.getCenterY() - (rect.y + rect.height)), 2)) <= this.width / 2) {
            System.out.println("botR");
            if (dx > dy) {
                this.dy *= -1;
            } else {
                this.dx *= -1;
            }
            return;
        }
    
    }
    

    More i not necissary, and it's relatively compact. Still, if someone has an even simpler idea, you can still answer and I'll test it.