Hi!
I'm writing a pool simulation and have an awful problem with my ball collisions (elastic collisions).
The theory (in terms of vectors) is that when two balls collide you take the line between their centers and the velocity components which are perpendicular to this line are unaffected, while the parallel ones are calculated from the formula:
v1' = [2*m2v2-v1(m2-m1)]/[m1+m2]
v2' = [2*m1v1-v2(m1-m2)]/[m1+m2]
What I do is when I find that the balls are in touch I find a vector which is the line joining the centers. I then use atan2(y,x) to get the polar angle (in radians).
I then premultiply the velocity vectors by the (counter-clockwise) rotation matrix:
1st row(cos(angle) sin(angle))
2nd row(-sin(angle) cos(angle))
Then I take the x components (which are now the components parallel to the line joining centers) And apply the formulas given above. Now I have new x components in the new basis and the old y components in the new basis.
So I rotate everything back and I should get the right velocity components?
The simulation looks ok, it even works fine for a head-on collision. But When I print out the balls velocities I find that the momentum wasn't conserved. Eg. balls of same mass, one ball is initially stationary, other going at 80pix/s:
After collision I find that the velocity magnitudes are 77 and 19 :(
That's way too much :( I initially thought it might be the problem that the balls penetrate each other slightly as it changes "angle" slightly, but that is surely not the case, as I checked for balls exactly touching). I'm also sure that the equations for parallel velocity components given are correct, and that the error appears right after calling my collision method. This means that it is a conceptual mistake with the rotation matrix or sth.
if anyone can find the mistake I'd be really grateful as I'm really stuck at this point.
Here is my collision method code:

/// Method for collision of two balls- sets their' new velocities
	public static void Collide(Ball A, Ball B){
		Vector2d joining = LineBetweenCentres(A, B);
		System.out.println(joining.GetX() + "  " +joining.GetY() + "  "+ joining.magnitude());
		/// angle is the angle of line joining centres in terms of polar coordinates
		double angle = Math.atan2(joining.GetY(), joining.GetX());
		/// rotate the velocity vectors of ball A and B in terms of the new basis (so that joining is "not tilted")
		double AvelXrotated = A.GetVel().GetX()*Math.cos(angle) + A.GetVel().GetY()*Math.sin(angle);
		double AvelYrotated = -1.0*A.GetVel().GetX()*Math.sin(angle) + A.GetVel().GetY()*Math.cos(angle);
		double BvelXrotated = B.GetVel().GetX()*Math.cos(angle) + B.GetVel().GetY()*Math.sin(angle);
		double BvelYrotated = -1.0*B.GetVel().GetX()*Math.sin(angle) + B.GetVel().GetY()*Math.cos(angle);
		/// perpendicular (y) velocity components are not affected, the new (x) components (in terms of new basis) are calculated:
		double AvelXnew = (2.0*B.GetMass()*BvelXrotated - AvelXrotated*(B.GetMass() - A.GetMass()))/(A.GetMass() + B.GetMass());
		double BvelXnew = (2.0*A.GetMass()*AvelXrotated - BvelXrotated*(A.GetMass() - B.GetMass()))/(A.GetMass() + B.GetMass());
		/// now the vectors need to be rotated back to the standard basis:
		double AvelXrerotated = AvelXnew*Math.cos(-1.0*angle) + AvelYrotated*Math.sin(-1.0*angle);
		double AvelYrerotated = -1.0*AvelXnew*Math.sin(-1.0*angle) + AvelYrotated*Math.cos(-1.0*angle);
		double BvelXrerotated = BvelXnew*Math.cos(-1.0*angle) + BvelYrotated*Math.sin(-1.0*angle);
		double BvelYrerotated = -1.0*BvelXnew*Math.sin(-1.0*angle) + BvelYrotated*Math.cos(-1.0*angle);
		/// set the new velocity components to the balls
		A.SetVelocity(AvelXrerotated, AvelYrerotated);
		B.SetVelocity(BvelXrerotated, BvelYrerotated);
		/// This part is responsible for making the balls go away from each other after collision so that they don't collide again straight away
		do{
			line = Ball.LineBetweenCentres(A, B);
			A.UpdatePosition();
			B.UpdatePosition();
		}
		while(line.magnitude() < A.GetRadius()+B.GetRadius());


	}

If you want I can send you the whole program code on the mail, just give me your mail or write to trelek2@gmail.com

Hi!

The simulation looks ok, it even works fine for a head-on collision. But When I print out the balls velocities I find that the momentum wasn't conserved. Eg. balls of same mass, one ball is initially stationary, other going at 80pix/s:
After collision I find that the velocity magnitudes are 77 and 19 :(
That's way too much

That sounds about right to me for energy, but you're right, it doesn't conserve momentum. For easy math, make each ball have a mass of one kilogram. For collision you have:

Kinetic energy = mv^2 = 1 * v^2 = v^2.

Pre-collision
Ball 1 (vel. = 80) : KE = 80^2 = 6,400
Ball 2 (vel. = 0) : KE = 0^2 = 0
Total Kinetic Energy = 6,400 + 0 = 6,400

Post-collision
Ball 1 (vel. = 77) : KE = 77^2 =5,929
Ball 2 (vel. = 19) : KE = 19^2 = 361
Total Kinetic Energy = 5,929 + 361 = 6,290

Assuming some round-off and a perfectly elastic collision, this seems like a reasonable calculation. However, you're right, it doesn't conserve momentum. It needs to be 80 and 0 afterwards, just that the ball that was moving before is stationary afterwards and vice versa.

We can't use the function without the program since it has a whole bunch of functions, but perhaps you can provide us with a stripped down Ball class that just has the mass, location, and velocity attributes and the get and set functions (or just make them public or something) along with some sample data/drivers so we can test it out?

It really would be easier if you could run the whole thing. Please give me your mail.

But actually what you said (after collision one ball stops, while other starts moving with the velocity of the first one) is true only if it is a head on collision. If the the moving ball doesn't hit the stationary ball exactly in the center both balls should start moving. And both momentum and Kinetic energy should be conserved for an elastic collision. just type elastic collision in wikipedia, there are even some animated gifs at the bottom. i still can't get the math to work right:(

It really would be easier if you could run the whole thing. Please give me your mail.

But actually what you said (after collision one ball stops, while other starts moving with the velocity of the first one) is true only if it is a head on collision. If the the moving ball doesn't hit the stationary ball exactly in the center both balls should start moving. And both momentum and Kinetic energy should be conserved for an elastic collision. just type elastic collision in wikipedia, there are even some animated gifs at the bottom. i still can't get the math to work right:(

Right, that's true about the centers needing to be lined up. You mentioned only the single-dimensional numbers and the magnitudes, so I was guessing they were, though re-reading it, you didn't actually say that. My bad. We're not supposed to do things by private e-mail on the forum if we can help it (it's the "Keep it public" rule), so can you upload it to the site as a zip file or something?

I didn't notice it's possible here:), I assume you don't have the ptolemy output package so I'll just comment these bits out. I'll upload it in a few mins.

Alright here it is. I hope it'll compile on a machine without ptolemy plot. I tried to comment out these parts, and I didn't include the class responsible for plotting.

Well, you have way too may balls going way too fast to do any real checking, so I changed your Input class to ignore the file and set up two balls:

public class Input {

	public static void main(String[] args) throws IOException, FileNotFoundException {

		/// initializing general parameters

		int tablewidth = 0;
		int tablelength = 0;
		int pocketnumber = 0;
		int pocketradius = 0;
		int ballnumber = 0;

		/// initializing ball parameters

		double ballXpos = 0;
		double ballYpos = 0;
		double ballVmag = 0;
		double ballVangle = 0;
		double ballmass = 0;
		int ballcolour = 0;
		int ballradius = 0;
		double ballmju = 0;



			tablewidth = 600;
			if(tablewidth < 100 || tablewidth > 650){
				System.out.println("Unallowed table width, simulation terminated");
				System.exit(0);
			}

			tablelength = 900;
			if(tablelength < 200 || tablelength > 1200){
				System.out.println("Unallowed table length, simulation terminated");
				System.exit(0);
			}


			pocketnumber = 6;
			if(pocketnumber != 4 && pocketnumber != 6){
				System.out.println("Unallowed number of pockets, simulation terminated");
				System.exit(0);
			}

			pocketradius = 50;
			if(pocketradius > 100){
				System.out.println("Pocket size out of bounds, simulation terminated");
				System.exit(0);
			}

			ballnumber = 2;

			///create array of balls for the specified number of balls
			Ball balls[] = new Ball[ballnumber]; 
			double VelX, VelY;
            Vector2d ballposition;


				ballXpos = 50;
				ballYpos = 50;
				// creating the vector corresponding to the initial position of each ball
				ballposition = new Vector2d (ballXpos, ballYpos);
				ballVmag = 50;
				ballVangle = 45;
				// calculating the components of initial velocity
				VelY = ballVmag*Math.sin(ballVangle*(Math.PI/180.0));
				VelX = ballVmag*Math.cos(ballVangle*(Math.PI/180.0));
				/// create vector with the initial velocity of wach ball
				Vector2d ballvelocity = new Vector2d(VelX, VelY);
				ballmass = 1;
				ballradius = 10;
				ballcolour = 2;
				ballmju = 0;
				balls[0] = new Ball(ballposition, ballvelocity, ballmass, ballradius, ballcolour, ballmju);

				ballXpos = 200;
				ballYpos = 220;
				// creating the vector corresponding to the initial position of each ball
				ballposition = new Vector2d (ballXpos, ballYpos);
				ballVmag = 0;
				ballVangle = 0;
				// calculating the components of initial velocity
				VelY = ballVmag*Math.sin(ballVangle*(Math.PI/180.0));
				VelX = ballVmag*Math.cos(ballVangle*(Math.PI/180.0));
				/// create vector with the initial velocity of wach ball
				ballvelocity = new Vector2d(VelX, VelY);
				ballmass = 1;
				ballradius = 10;
				ballcolour = 1;
				ballmju = 0;
				balls[1] = new Ball(ballposition, ballvelocity, ballmass, ballradius, ballcolour, ballmju);




			Table pooltable = new Table(balls, tablewidth, tablelength, pocketnumber, pocketradius);

			/// perform the simulation
			pooltable.SimulatePool();
	}
}

Both have mass 1, radius 10. One starts stationary, centered at (200, 220), the other starts at (50, 50) and moves towards the the upper left direction at a 45 degree angle at a velocity of 50. At (200, 200), the balls collide, sending the originally stationary ball straight upwards with a velocity of slightly over 35, which is correct for that collision. The other ball goes straight right at velocity 35. For this collision, both energy and momentum are conserved.

You may be getting problems at other angles or masses or other scenarios, but it passed this one perfectly, so keep testing. But you have too many balls to test with now (you only want two and you want them in places where you've already worked out in advance the exact collision mathematically, so you can check). If you want to report back on a scenario with two balls that fails, I'll try it out. Please provide the corresponding 2 ball input file.

You should also probably write functions that take two balls as parameters and compute their overall combined momentum and their overall combined energy. It'll be much easier to test the before and after to see if there is truly anything wrong.

Also, what is mju in the ball class? Some friction coefficient? And is that a calculation regarding collision elasticity or friction between ball and pool table felt (the latter, I imagine). I set it to 0.

Comments
very helpful

Yep, mju is the rolling friction, it works fine and does not affect the collisions. Oh, sorry, i sent you the wrong input file. The collision you tested is a collision at an angle but the balls collide exactly at centers so it works. Try for example putting one ball stationary. And the other one going at a velocity with only the x component positive, so angle 0. But if position of one of the balls is 50,50 set the other one to 15, 45.
Now you will see that after the collision the momentum rises and that the math in the collide method fails.

Yep, mju is the rolling friction, it works fine and does not affect the collisions. Oh, sorry, i sent you the wrong input file. The collision you tested is a collision at an angle but the balls collide exactly at centers so it works. Try for example putting one ball stationary. And the other one going at a velocity with only the x component positive, so angle 0. But if position of one of the balls is 50,50 set the other one to 15, 45.
Now you will see that after the collision the momentum rises and that the math in the collide method fails.

No, I don't see that. Here is the Ball class that I used, along with the tests. It's your code with some tests added (lines 131, 132, 164 - 170, and lines 174 onward is what I added. Line 169 never displays. See if it does for you.)

No guarantee that my tests are correct, but they looked OK to me. Write your own if you wish and test for several scenarios. I don't see any similar test functions from the code you wrote, so I'm not sure where you test them or why you are concluding that the collisions are inaccurate.

package pool;


class Ball {

	// initializing parameters
	private Vector2d position, velocity;
	static Vector2d line;
	private double mass, mju;
	private int radius, colour;

	// constructor for ball object
	public Ball(Vector2d position, Vector2d velocity, double mass, int radius, int colour, double mju){

		this.position = position;
		this.velocity = velocity;
		this.mass = mass;
		this.radius = radius;
		this.colour = colour;
		this.mju = mju;
	}

	// constructor for ball from another ball
	public Ball(Ball ballobject){

		this.position = ballobject.GetPos();
		this.velocity = ballobject.GetVel();
		this.mass = ballobject.GetMass();
		this.radius = ballobject.GetRadius();
		this.colour = ballobject.GetColour();
		this.mju = ballobject.GetMju();
	}

	/// method to set velocity of ball
	public void SetVelocity(double a, double b){
		this.velocity = new Vector2d(a,b);
	}

	/// method to set position of ball
	public void SetPosition(double Xpos, double Ypos){
		this.position = new Vector2d(Xpos,Ypos);
	}

	/// to string method to enable easy printing of ball content
	public String toString(){
		String ballcontent = "position " + this.position.GetX() +" " + this.position.GetY() + " velocity " + this.velocity.GetX() + " " + this.velocity.GetY() + " mass " + this.mass + " radius " + this.radius + " colour " + this.colour + " friction coeff. " + this.mju;
		return ballcontent;
	}

	/// Getter method for position vector
	public Vector2d GetPos(){
		return this.position;
	}

	/// Getter method for velocity vector
	public Vector2d GetVel(){
		return this.velocity;
	}

	/// Getter method for mass
	public double GetMass(){
		return this.mass;
	}

	/// Getter method for radius
	public int GetRadius(){
		return this.radius;
	}

	/// Getter method for friction coefficient mju
	public double GetMju(){
		return this.mju;
	}

	/// Getter method for position vector
	public int GetColour(){
		return this.colour;
	}

	/// method to update position of ball in normal circumstances, or put the ball to a stop if calculated that the velocity magnitude is smaller than the magnitude by which it is to be decreased
	public void UpdatePosition(){ 
		if(velocity.magnitude() < Vector2d.scaleVector(((-1.0/10000.0)*9.81*mju/velocity.magnitude()), velocity).magnitude()){
			position = Vector2d.addVector(position, Vector2d.scaleVector((1.0/10000.0), velocity));
			velocity.SetVector(0.0, 0.0);

		}
		else if(velocity.magnitude() > Vector2d.scaleVector(((-1.0/10000.0)*9.81*mju/velocity.magnitude()), velocity).magnitude()){
			position = Vector2d.addVector(position, Vector2d.scaleVector((1.0/10000.0), velocity));
			velocity = Vector2d.addVector(velocity, Vector2d.scaleVector(((-1.0/10000.0)*9.81*mju/velocity.magnitude()), velocity));
		}
	}

	/// method to bounce of cushion along length of table
	public void BounceTableLength(int tablewidth){
		double Y = -1.0*velocity.GetY();
		double X = velocity.GetX();
		velocity.SetVector(X,Y);
		/// correction for the fact that the ball penetrates the cushion
		if(position.GetY() < tablewidth/2.0){
			position.SetVector(position.GetX(), 0.0 + radius);
		}
		if(position.GetY() > tablewidth/2.0){
			position.SetVector(position.GetX(), tablewidth - radius);
		}
	}

	/// method to bounce of cushion along width of table
	public void BounceTableWidth(int tablelength){
		double Y = velocity.GetY();
		double X = -1.0*velocity.GetX();
		velocity.SetVector(X,Y);
		/// correction for the fact that the ball penetrates the cushion
		if(position.GetX() < tablelength/2.0){
			position.SetVector(0.0 + radius, position.GetY());
		}
		if(position.GetX() > tablelength/2.0){
			position.SetVector(tablelength - radius, position.GetY());
		}
	}

	/// method for a vector joining the centres of two balls in the direction of ball A
	public static Vector2d LineBetweenCentres(Ball A, Ball B){
		double newX = A.GetPos().GetX() - B.GetPos().GetX();
		double newY = A.GetPos().GetY() - B.GetPos().GetY();
		return new Vector2d(newX, newY);
	}

	/// Method for collision of two balls- sets their' new velocities
	public static void Collide(Ball A, Ball B){

        double energyPre = Ball.GetTotalEnergy(A, B);
        Vector2d momentumPre = Ball.GetTotalMomentum(A, B);

		Vector2d joining = LineBetweenCentres(A, B);
//		System.out.println(joining.GetX() + "  " +joining.GetY() + "  "+ joining.magnitude());
		/// angle is the angle of line joining centres in terms of polar coordinates
		double angle = Math.atan2(joining.GetY(), joining.GetX());
//		System.out.println(angle);
		/// rotate the velocity vectors of ball A and B in terms of the new basis (so that joining is "not tilted")
		double AvelXrotated = A.GetVel().GetX()*Math.cos(angle) + A.GetVel().GetY()*Math.sin(angle);
		double AvelYrotated = -A.GetVel().GetX()*Math.sin(angle) + A.GetVel().GetY()*Math.cos(angle);
		double BvelXrotated = B.GetVel().GetX()*Math.cos(angle) + B.GetVel().GetY()*Math.sin(angle);
		double BvelYrotated = -B.GetVel().GetX()*Math.sin(angle) + B.GetVel().GetY()*Math.cos(angle);
		/// perpendicular (y) velocity components are not affected, the new (x) components (in terms of new basis) are calculated:
		double AvelXnew = (2.0*B.GetMass()*BvelXrotated - AvelXrotated*(B.GetMass() - A.GetMass()))/(A.GetMass() + B.GetMass());
		double BvelXnew = (2.0*A.GetMass()*AvelXrotated - BvelXrotated*(A.GetMass() - B.GetMass()))/(A.GetMass() + B.GetMass());
		/// now the vectors need to be rotated back to the standard basis:
		double AvelXrerotated = AvelXnew*Math.cos(-1.0*angle) + AvelYrotated*Math.sin(-1.0*angle);
		double AvelYrerotated = -AvelXnew*Math.sin(-1.0*angle) + AvelYrotated*Math.cos(-1.0*angle);
		double BvelXrerotated = BvelXnew*Math.cos(-1.0*angle) + BvelYrotated*Math.sin(-1.0*angle);
		double BvelYrerotated = -BvelXnew*Math.sin(-1.0*angle) + BvelYrotated*Math.cos(-1.0*angle);
		/// set the new velocity components to the balls
		A.SetVelocity(AvelXrerotated, AvelYrerotated);
		B.SetVelocity(BvelXrerotated, BvelYrerotated);
		/// This part is responsible for making the balls go away from each other after collision so that they don't collide again straight away
		do{
			line = Ball.LineBetweenCentres(A, B);
			A.UpdatePosition();
			B.UpdatePosition();
		}
		while(line.magnitude() < A.GetRadius()+B.GetRadius());


        double energyPost = Ball.GetTotalEnergy(A, B);
        Vector2d momentumPost = Ball.GetTotalMomentum(A, B);

        if (!Ball.ComparePreAndPost(energyPre, energyPost, momentumPre, momentumPost))
        {
            System.out.println ("Collision calculation has a problem.");
        }
	}
    
    
    public static double GetTotalEnergy (Ball A, Ball B)
    {
        double energyA = A.GetMass () * (Math.pow (A.GetVel().GetX(), 2.0) + Math.pow (A.GetVel().GetY(), 2.0));
        double energyB = B.GetMass () * (Math.pow (B.GetVel().GetX(), 2.0) + Math.pow (B.GetVel().GetY(), 2.0));
        return energyA + energyB;
    }
    
    
    public static Vector2d GetTotalMomentum (Ball A, Ball B)
    {
        return new Vector2d (GetTotalXMomentum (A, B), GetTotalYMomentum (A, B));
    }


    public static double GetTotalXMomentum (Ball A, Ball B)
    {
        return A.GetMass() * A.GetVel().GetX() + B.GetMass() * B.GetVel().GetX();
    }


    public static double GetTotalYMomentum (Ball A, Ball B)
    {
        return A.GetMass() * A.GetVel().GetY() + B.GetMass() * B.GetVel().GetY();
    }


    public static boolean ComparePreAndPost (double energyPre, double energyPost, Vector2d momentumPre, Vector2d momentumPost)
    {
        double TOLERANCE = 0.00001;  // round-off error allowable fudge
        if (Math.abs (energyPre - energyPost) > TOLERANCE)
            return false;
        if (Math.abs (momentumPre.GetX() - momentumPost.GetX()) > TOLERANCE)
            return false;
        if (Math.abs (momentumPre.GetY() - momentumPost.GetY()) > TOLERANCE)
            return false;

        return true;
    }
}
This article has been dead for over six months. Start a new discussion instead.