Prerequisites!
Having played a few games of pool (or billiards) doesn't hurt. ;)
The math of billiards
We will be focusing on collisions between billiard balls. We'll also simulate friction between rolling balls and table, as well as simulating roll.
We will completely ignore things like English (side spin), slipping (zero friction), spin transfer, as well as just about everything else. Oh, no jump shots either (gravity).
You can find an excellent discussion on Billiard Physics here.
OK, are you ready to dust off some vector algebra? Collisions between two objects in 3 dimensions... Conservation of kinetic energy and conservation of momentum gives us the following velocities post impact:
v1 = v1i - ((m2*c)/(m1 + m2))*(1 + Cr)*n
v2 = v2i + ((m1*c)/(m1 + m2))*(1 + Cr)*n
where:
- v1, v2 = Velocity vector of Ball 1 and Ball 2 just after collision
- v1i, v2i = Initial velocity vectors of Ball 1 and Ball 2 just before collision
- m1, m2 = mass of Ball 1 and Ball 2
- Cr = Coefficient of restitution (Cr = 1 for elastic collision)
- n = normal unit vector from Ball 1 to Ball 2
- c = dot-product (n, v1i - v2i)
In Delphi this becomes:
normal := Vector3DSubtract(Ball1.Position.Vector, Ball2.Position.Vector);
normal := Vector3DNormalize(normal);
c := Vector3DDotProduct(normal,Vector3DSubtract(Ball1.Velocity,Ball2.Velocity));
Ball1.Velocity := Vector3DSubtract(Ball1.Velocity, Vector3DScale(normal,Ball2.Mass*c/(Ball1.Mass+Ball2.Mass)*(1+Cr)));
Ball2.Velocity := Vector3DAdd(Ball2.Velocity, Vector3DScale(normal,Ball1.Mass*c/(Ball1.Mass+Ball2.Mass)*(1+Cr)));
OK, so that takes care of the collisions between balls. Collision with the rail can be (over)simplified as simply reversing the component of the velocity vector that is perpendicular to the rail in question.
Rolling, rolling, rolling...
If we ignored roll (on the table) we would have a bunch of billiard balls that didn't roll and the simulation would look really weird. I couldn't quite figure out how to do roll correctly, but the best I came up with was the following relationship:
TDummyZ
TDummyX
TDummyY
TBall
This allows me to rotate around each axis independently and avoids complicated vector rotations (please tell me how to do it if you know how).
What we need to do is to rotate the ball in the direction of travel. The direction of travel (in degrees) is given by:
180/Pi*ArcTan2(Velocity.Y,Velocity.X);
In Delphi:
NewZ := 180/Pi*ArcTan2(Ball[n].Velocity.Y,Ball[n].Velocity.X);
Ball[n].DummyZ.RotationAngle.Z := NewZ;
Ball[n].DummyX.RotationAngle.Z := -NewZ;
rX := Ball[n].RotationAngle.X;
rY := Ball[n].DummyY.RotationAngle.Y;
Ball[n].DummyY.RotationAngle.Y := rY - Ball[n].Velocity.X*2/Pi;
Ball[n].RotationAngle.X := rX + Ball[n].Velocity.Y*2/Pi;
Generating the ball textures
The coolest part besides collision handling is making the billiard balls appear real. The following piece of Delphi code generates a texture that we use for our billiard balls:
function MakeBallTexture(n : Integer) : TBitmap;
var
BMP : TBitmap;
C : TCanvas;
Color : TAlphaColor;
begin
BMP := TBitmap.Create(200,100);
C := BMP.Canvas;
C.BeginScene(nil);
case n mod 8 of
0 : Color := TAlphaColorRec.Yellow;
1 : Color := TAlphaColorRec.Blue;
2 : Color := TAlphaColorRec.Red;
3 : Color := TAlphaColorRec.Violet;
4 : Color := TAlphaColorRec.Orange;
5 : Color := TAlphaColorRec.Green;
6 : Color := TAlphaColorRec.Brown;
7 : Color := TAlphaColorRec.Black;
end;
C.Fill.Color := Color;
C.Stroke.Color := Color;
C.FillRect(RectF(0,30,200,70),0,0,[],1);
if n div 8 > 0 then begin
C.Fill.Color := TAlphaColorRec.White;
C.Stroke.Color := TAlphaColorRec.White;
end;
C.FillRect(RectF(0,0,200,30),0,0,[],1);
C.FillRect(RectF(0,70,200,100),0,0,[],1);
C.Fill.Color := TAlphaColorRec.White;
C.Stroke.Color := TAlphaColorRec.Black;
C.FillEllipse(RectF(35,35,65,65),1);
C.FillEllipse(RectF(135,35,165,65),1);
C.Fill.Color := TAlphaColorRec.Black;
C.Stroke.Color := TAlphaColorRec.Black;
C.Font.Size := 24;
C.FillText(RectF(135,35,165,65),IntToStr(n+1),False,1,[],TTextAlign.taCenter);
C.FillText(RectF(35,35,65,65),IntToStr(n+1),False,1,[],TTextAlign.taCenter);
C.EndScene;
Result := BMP;
end;
In case you're wondering, this produces the following 8-ball texture...

And the following 15-ball texture...

Screen shots
The screen shots below show the demo application. Before hitting the cue ball, final stage of simulation of a perfect scenario, as well as the final stage rotated around.



Demo application
You can find my demo application in CodeCentral. You can play around with a lot of things in this demo. Feel free to elevate the cue ball above the table and watch the collision happen in 3 dimensions (velocity in Z-axis as well).
Contact
Please feel free to email me with feedback to aohlsson at embarcadero dot com.
Connect with Us