Computer Science Canada

Best way to implement friction in a pool game?

Author:  Reality Check [ Thu Jun 07, 2007 8:42 am ]
Post subject:  Best way to implement friction in a pool game?

code:

// The "PoolGame" class.
import java.awt.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.event.*;
import java.applet.*;

public class PoolGame extends Frame
{
    final int NUMOFBALLS = 16;
    Ball[] ball = new Ball [NUMOFBALLS];
    double t; //Amount of time until a collision occurs between the balls
    final int DELAY = 50;
    final int LEFTWALL = -18;
    final int RIGHTWALL = 1000;
    final int BOTTOMWALL = 500;
    int checker = 0;
    int check = 1;
    int turn = 1;
    final int TOPWALL = 1;
    final double CLOSESTPOSSIBLE = 0.000000001; //Maximum distance the two balls can be apart and still be colliding (to stop it from getting stuck)
    final int STARTX = 300;
    final int STARTY = 230;
    final int TOPBORDER = 13;
    final int BOTTOMBORDER = -23;
    final int RIGHTBORDER = -23;
    final int LEFTBORDER = 12;
    final double WALLFRICTION = 0.7;
    final double FRICTION =0.03;
    Image bg= Toolkit.getDefaultToolkit().getImage ("BG.JPG");
    MediaTracker tracker=new MediaTracker (this);
   

    public PoolGame ()
    {
        super ("PoolGame");     // Set the frame's name
        setSize (RIGHTWALL, BOTTOMWALL);     // Set the frame's size
        show ();
        for (int count = 0 ; count < NUMOFBALLS ; count++)
        {
            //     //Initializes the balls
            ball [count] = new Ball ();
            ball [count].setX (((int) (Math.random () * (378 - 20 + 1))) + 1);
            ball [count].setY (((int) (Math.random () * (378 - 20 + 1))) + 1);
            //     ball [count].setVX (((int) (Math.random () * (18))) - 5);
            //     ball [count].setVY (((int) (Math.random () * (18))) - 5);
            //     ball [count].setVY1 (ball [count].getVY ());
            //     ball [count].setVX1 (ball [count].getVX ());
            ball [count].setVX1 (0);
            ball [count].setVX (0);
            ball [count].setVY (0);
            ball [count].setVY1 (0);
            ball [count].setRadius (20);
            ball [count].setMass (Math.PI * (Math.pow (ball [count].getRadius (), 2)));
        }

        //
        ball [1].setX (STARTX);
        ball [1].setY (STARTY);
        //
        ball [2].setX (STARTX - 35);
        ball [2].setY (STARTY - 20);
        ball [3].setX (STARTX - 35);
        ball [3].setY (STARTY + 20);
        //
        ball [4].setX (STARTX - 70);
        ball [4].setY (STARTY);
        ball [5].setX (STARTX - 70);
        ball [5].setY (STARTY - 40);
        ball [6].setX (STARTX - 70);
        ball [6].setY (STARTY + 40);
        //
        ball [7].setX (STARTX - 105);
        ball [7].setY (STARTY + 20);
        ball [8].setX (STARTX - 105);
        ball [8].setY (STARTY + 60);
        ball [9].setX (STARTX - 105);
        ball [9].setY (STARTY - 20);
        ball [10].setX (STARTX - 105);
        ball [10].setY (STARTY - 60);
        //
        ball [11].setX (STARTX - 140);
        ball [11].setY (STARTY);
        ball [12].setX (STARTX - 140);
        ball [12].setY (STARTY + 40);
        ball [13].setX (STARTX - 140);
        ball [13].setY (STARTY - 40);
        ball [14].setX (STARTX - 140);
        ball [14].setY (STARTY + 80);
        ball [15].setX (STARTX - 140);
        ball [15].setY (STARTY - 80);







        ball [0].setX (STARTX + 200);
        ball [0].setY (STARTY);
    }


    //lets us know if 2 balls are moving towards each other (to add efficiency and not be forced to check for collision each time)
    public boolean movingTowards (Ball b1, Ball b2)
    {
        //Formula found on the net that tells me if they are moving towards each other.
        return (b2.getX () - b1.getX ()) * (b1.getVX () - b2.getVX ()) + (b2.getY () - b1.getY ()) * (b1.getVY () - b2.getVY ()) > 0;
    }


    public double timeToCollide ()  //tells me the lowest number of frames until a collision occurs.
    {
        double t = Integer.MAX_VALUE;
        double a, b, c, d, discriminent; //this is done to break up a very large formula for t.

        //loops through every pair of balls
        for (int i = 0 ; i < ball.length ; i++)
        {
            for (int o = 1 + i ; o < ball.length ; o++)
            {
                if (movingTowards (ball [i], ball [o]))
                {
                    //A formula found on the net that calculates when two balls will collide
                    a = Math.pow (ball [i].getVX (), 2) + Math.pow (ball [i].getVY (), 2) - 2 * ball [i].getVX () * ball [o].getVX () + Math.pow (ball [o].getVX (), 2) - 2 * ball [i].getVY () * ball [o].getVY () + Math.pow (ball [o].getVY (), 2);
                    b = -ball [i].getX () * ball [i].getVX () - ball [i].getY () * ball [i].getVY () + ball [i].getVX () * ball [o].getX () + ball [i].getVY () * ball [o].getY () + ball [i].getX () * ball [o].getVX () -
                        ball [o].getX () * ball [o].getVX () + ball [i].getY () * ball [o].getVY () - ball [o].getY () * ball [o].getVY ();
                    c = Math.pow (ball [i].getVX (), 2) + Math.pow (ball [i].getVY (), 2) - 2 * ball [i].getVX () * ball [o].getVX () + Math.pow (ball [o].getVX (), 2) - 2 * ball [i].getVY () * ball [o].getVY () + Math.pow (ball [o].getVY (), 2);
                    d = Math.pow (ball [i].getX (), 2) + Math.pow (ball [i].getY (), 2) - Math.pow (ball [i].getRadius (), 2) - 2 * ball [i].getX () * ball [o].getX () + Math.pow (ball [o].getX (), 2) - 2 * ball [i].getY () * ball [o].getY () +
                        Math.pow (ball [o].getY (), 2) - 2 * ball [i].getRadius () * ball [o].getRadius () - Math.pow (ball [o].getRadius (), 2);
                    discriminent = Math.pow ((-2 * b), 2) - 4 * c * d;

                    //If our final discriminent is > 0 it means a collision will occur and we will compare the current t to our new t.
                    //We update it to the lowest possible t to get the smallest amount of time.
                    if (discriminent >= 0)
                        t = Math.min (Math.min (t, 0.5 * (2 * b - Math.sqrt (discriminent)) / a), 0.5 * (2 * b + Math.sqrt (discriminent)) / a);
                }
            }
        }
        return t;
    }


    public boolean mouseDown (Event event, int x, int y)
    {
        ball [0].setVX ((ball [0].getX () + 20 - x) / 5);
        ball [0].setVX1 ((ball [0].getX () + 20 - x) / 5);
        ball [0].setVY1 ((ball [0].getY () + 20 - y) / 5);
        ball [0].setVY ((ball [0].getY () + 20 - y) / 5);
        if (turn == 1)
            turn = 2;
        else
            turn = 1;
        checker = 0;
        repaint ();
        return true;
    }


    public double update ()  // updates t and sets the new X and Y of the balls
    {
        //should t be bigger then 1, we want the maximum frame incrememnts to be 1.
        double t = Math.min (1, timeToCollide ());

        for (int count = 0 ; count < ball.length ; count++)
        {
            ball [count].setX (ball [count].getX () + ball [count].getVX () * t);
            ball [count].setY (ball [count].getY () + ball [count].getVY () * t);
        }
        return t;
    }


    public void collide (Ball b1, Ball b2)
    {
        //adjusts velocity according to mass of the ball, and radius.  Formula was found on the net.
        double nx = (b1.getX () - b2.getX ()) / (b1.getRadius () + b2.getRadius ());
        double ny = (b1.getY () - b2.getY ()) / (b1.getRadius () + b2.getRadius ());
        double a1 = b1.getVX () * nx + b1.getVY () * ny;
        double a2 = b2.getVX () * nx + b2.getVY () * ny;
        double p = 2 * (a1 - a2) / (b1.getMass () + b2.getMass ());

        b1.setVX1 (b1.getVX () - p * nx * b2.getMass ());
        b1.setVY1 (b1.getVY () - p * ny * b2.getMass ());

        b2.setVX1 (b2.getVX () + p * nx * b1.getMass ());
        b2.setVY1 (b2.getVY () + p * ny * b1.getMass ());
    }


    //the method that checks for collision between balls and collision off the wall.
    public void checkForCollision ()
    {
        //loops through every pair of balls
        for (int i = 0 ; i < ball.length ; i++)
        {
            for (int o = 1 + i ; o < ball.length ; o++)
            {
                if (movingTowards (ball [i], ball [o]) && Math.pow ((ball [o].getX () - ball [i].getX ()), 2) + Math.pow ((ball [o].getY () - ball [i].getY ()), 2) <= (Math.pow ((ball [i].getRadius () + ball [o].getRadius () + CLOSESTPOSSIBLE), 2)))
                {
                    //calls the following if a collision has occured.
                    collide (ball [i], ball [o]);
                }
            }
            //deals with the collision of the walls.
            if ((ball [i].getX () - ball [i].getRadius () + CLOSESTPOSSIBLE) < LEFTWALL + LEFTBORDER)
            {
                ball [i].setVX1 (ball [i].getVX1 () * -1);
                ball [i].setX (LEFTWALL - CLOSESTPOSSIBLE + ball [i].getRadius () + LEFTBORDER);
                ball [i].setVX (ball [i].getVX () * WALLFRICTION);
                ball [i].setVX1 (ball [i].getVX1 () * WALLFRICTION);
                ball [i].setVY (ball [i].getVY () * WALLFRICTION);
                ball [i].setVY1 (ball [i].getVY1 () * WALLFRICTION);
            }
            else if ((ball [i].getX () + ball [i].getRadius () + CLOSESTPOSSIBLE) > RIGHTWALL + RIGHTBORDER)
            {
                ball [i].setVX1 (ball [i].getVX1 () * -1);
                ball [i].setX (RIGHTWALL - CLOSESTPOSSIBLE - ball [i].getRadius () + RIGHTBORDER);
                ball [i].setVX (ball [i].getVX () * WALLFRICTION);
                ball [i].setVX1 (ball [i].getVX1 () * WALLFRICTION);
                ball [i].setVY (ball [i].getVY () * WALLFRICTION);
                ball [i].setVY1 (ball [i].getVY1 () * WALLFRICTION);
            }
            if ((ball [i].getY () - ball [i].getRadius () - CLOSESTPOSSIBLE) < TOPWALL + TOPBORDER)
            {
                ball [i].setVY1 (ball [i].getVY1 () * -1);
                ball [i].setY (TOPWALL + CLOSESTPOSSIBLE + ball [i].getRadius () + TOPBORDER);
                ball [i].setVX (ball [i].getVX () * WALLFRICTION);
                ball [i].setVX1 (ball [i].getVX1 () * WALLFRICTION);
                ball [i].setVY (ball [i].getVY () * WALLFRICTION);
                ball [i].setVY1 (ball [i].getVY1 () * WALLFRICTION);
            }
            else if ((ball [i].getY () + ball [i].getRadius () + CLOSESTPOSSIBLE) > BOTTOMWALL + BOTTOMBORDER)
            {
                ball [i].setVY1 (ball [i].getVY1 () * -1);
                ball [i].setY (BOTTOMWALL + CLOSESTPOSSIBLE - ball [i].getRadius () + BOTTOMBORDER);
                ball [i].setVX (ball [i].getVX () * WALLFRICTION);
                ball [i].setVX1 (ball [i].getVX1 () * WALLFRICTION);
                ball [i].setVY (ball [i].getVY () * WALLFRICTION);
                ball [i].setVY1 (ball [i].getVY1 () * WALLFRICTION);
            }
        }

        //VX1 and VY1 are temporary velocity storage because I run into problems otherwise so I re-set the current velocity to the value of VX1 and VY1
        for (int count = 0 ; count < ball.length ; count++)
        {
            ball [count].setVX (ball [count].getVX1 ());
            ball [count].setVY (ball [count].getVY1 ());
        }
    }


    public void paint (Graphics g)
    {
        //loops through until all balls have come to a halt
        while (checker < ball.length)
        {
            checker = 0;
            g.setColor (Color.white);
            g.fillRect (0, 0, RIGHTWALL, BOTTOMWALL);

            for (int count = 0 ; count < ball.length ; count++)
            {
                if (count == 0)
                    g.setColor (Color.red);
                else
                    g.setColor (Color.blue);
                g.fillOval ((int) Math.round (ball [count].getX ()), (int) Math.round (ball [count].getY ()), 41, 41); //draws the balls
            }
            checkForCollision ();
            t = update ();
            Delay.stop ((int) Math.round (DELAY * t));
            for (int count = 0 ; count < ball.length ; count++)
            {
                if (ball [count].getVX () > 0)
                {
                    ball [count].setVX (ball [count].getVX () - FRICTION);
                    ball [count].setVX1 (ball [count].getVX1 () - FRICTION);
                }
                else if (ball [count].getVX () < 0)
                {
                    ball [count].setVX (ball [count].getVX () + FRICTION);
                    ball [count].setVX1 (ball [count].getVX1 () + FRICTION);
                }
                if (ball [count].getVY () > 0)
                {
                    ball [count].setVY (ball [count].getVY () - FRICTION);
                    ball [count].setVY1 (ball [count].getVY1 () - FRICTION);
                }
                else if (ball [count].getVY () < 0)
                {
                    ball [count].setVY (ball [count].getVY () + FRICTION);
                    ball [count].setVY1 (ball [count].getVY1 () + FRICTION);
                }
                if (ball [count].getVX () <= FRICTION && ball [count].getVY () <= FRICTION && ball [count].getVX () >= -FRICTION && ball [count].getVY () >= -FRICTION)
                {
                    checker += 1;

                }
            }
        }
        for (int count = 0 ; count < ball.length ; count++)
        {
            if (count == 0)
                g.setColor (Color.red);
            else
                g.setColor (Color.blue);
            g.fillOval ((int) Math.round (ball [count].getX ()), (int) Math.round (ball [count].getY ()), 41, 41); //draws the balls
            g.setColor (Color.green);

        }
    }



    public static void main (String[] args)
    {
        new PoolGame ();        // Create a PoolGame frame
    } // main method
}


//ball class and contains the attributes of the individual balls.
class Ball
{
    private double x;
    private double y;
    private double vx;
    private double vy;
    private double vx1;
    private double vy1;
    private double radius;
    private double mass;

    public Ball ()  //double x, double y, double vx, double vy, double radius, double mass)
    {
        // this.x = x;
        // this.y = y;
        // this.vx = vx;
        // this.vy = vy;
        // this.radius = radius;
        // this.mass = mass;
    }


    public void setX (double x)
    {
        this.x = x;
    }


    public void setY (double y)
    {
        this.y = y;
    }


    public void setVX (double vx)
    {
        this.vx = vx;
    }


    public void setVY (double vy)
    {
        this.vy = vy;
    }


    public void setVY1 (double vy1)
    {
        this.vy1 = vy1;
    }


    public void setRadius (double radius)
    {
        this.radius = radius;
    }


    public void setMass (double mass)
    {
        this.mass = mass;
    }


    public void setVX1 (double vx1)
    {
        this.vx1 = vx1;
    }


    public double getX ()
    {
        return x;
    }


    public double getY ()
    {
        return y;
    }


    public double getVX ()
    {
        return vx;
    }


    public double getVY ()
    {
        return vy;
    }


    public double getVY1 ()
    {
        return vy1;
    }


    public double getVX1 ()
    {
        return vx1;
    }


    public double getRadius ()
    {
        return radius;
    }


    public double getMass ()
    {
        return mass;
    }
} // PoolGame class


I know there are obvious problems but all will be fixed (flickering for example). Anyways, I got the basis of pool working. Just position the mouse behind the red ball and the further the mouse, the more powerful the shot. I have friction done but as you see, in every loop I simply just subtract a bit from the Y and X velocity. Is there a good way to do it because in the cases where my X velocity is greater than the Y velocity or vice versa, the ball straightens out and curves because one of the velocities reach 0 faster. Any suggestion? By the way I must thank zylum for his great tutorial on perfect circular collision!

Author:  Skynet [ Thu Jun 07, 2007 11:50 am ]
Post subject:  Re: Best way to implement friction in a pool game?

If your two velocity terms are vx and vy, the magnitude of the velocity is v = sqrt(vx^2 + vy^2). Taking friction into account, your new velocity will be v - delta_v, where delta_v is your change in speed due to friction. If you want to convert that back to its x and y components, you need to use trig.

This is without friction:
Trigonometry wrote:
vx = v * cos(theta)
vy = v * sin(theta)


And subtracting a delta_v term:
Trigonometry wrote:
vx = (v - delta_v) * cos(theta)
vy = (v - delta_v) * sin(theta)


Since we're just subtracting delta_v from an existing v:
Pseudocode wrote:
vx -= delta_v * cos(theta)
vy -= delta_v * sin(theta)

This also has the benefit of not requiring the square root stuff.

Since theta is dependent on your vx and vy:
Trigonometry wrote:
theta = tan(vy/vx)

Author:  richcash [ Thu Jun 07, 2007 1:51 pm ]
Post subject:  Re: Best way to implement friction in a pool game?

Skynet @ Thu Jun 07, 2007 11:50 am wrote:

Since theta is dependent on your vx and vy:
Trigonometry wrote:
theta = tan(vy/vx)

Wouldn't it be this?
code:
theta = arctan(vy/vx)

Author:  Skynet [ Thu Jun 07, 2007 7:59 pm ]
Post subject:  Re: Best way to implement friction in a pool game?

Yes. Thanks.

Author:  Reality Check [ Mon Jun 11, 2007 8:34 am ]
Post subject:  Re: Best way to implement friction in a pool game?

I did the theta calculation simply enough and then I simply just subtracted my current X velocity by a delta v constant multiplied by cos (theta). I did the same for the Y velocity but did sin (theta) instead. I'm probably doing something wrong, is my delta v suppose to be a constant? The balls speed up at times and slow down other times.

Author:  Skynet [ Mon Jun 11, 2007 9:16 am ]
Post subject:  Re: Best way to implement friction in a pool game?

Yes, delta v will be a constant.

Things to check:
1: You should use atan2 for your arctan call.
2: This approach should handle all cases - no need to reverse things if vx or vy are negative (the atan2 takes care of that)

If neither are those are the case, what are the conditions under which the balls speed up?

Author:  Reality Check [ Mon Jun 11, 2007 2:05 pm ]
Post subject:  Re: Best way to implement friction in a pool game?

Yea I was pretty sure I wouldn't need to check whether the velocity is negative. Although, to get theta I used Math.atan (vy/vx). I didn't know I had to use atan2. I'll try Math.atan2 out.

Author:  Reality Check [ Mon Jun 11, 2007 2:06 pm ]
Post subject:  Re: Best way to implement friction in a pool game?

Yea I was pretty sure I wouldn't need to check whether the velocity is negative. Although, to get theta I used Math.atan (vy/vx). I didn't know I had to use atan2. I'll try Math.atan2 out.


: