Billiard table physics

Started by Vincent, Sun 04/10/2015 19:25:52

Previous topic - Next topic

Vincent

I have been trying to implement a billiard table into my game but I am having a few problems with the actual physics of the table and balls.
I have been trying to move the balls with a variable speed for x and y for the ball objects but I can't seem to find a good way of stopping them when they collide with another ball or edges of the table.
Also, I am having trouble with the rebound directions the balls take when they collide with the edges of the table or the other balls.
I have tried many different ways of achieving the physics, or the feel of actual physics, in my billiard game I seem to be unable to make it all work how I would like.
If anyone could help me to start building a physics engine for my billiard game, then I would be most grateful. :)

Grazie mille

Slasher

#1
Quotebut I can't seem to find a good way of stopping them when they collide with another ball or edges of the table.
Have you tried PPCollide module? Having the edges as separate objects perhaps?

Obviously hardness, speed and angle will predetermine angle when it hits something.

http://www.real-world-physics-problems.com/physics-of-billiards.html

Khris......... where is 'yer (laugh)




Snarky

There have been some attempts to implement physics into AGS, so you might be able to build on some of that. Also, I'm sure there's a great deal of documentation of how to do physics engines in general online. But just as a starter:

To do the movement, you definitely want to store velocity vectors for each ball, and keep the positions and velocities as floats that you convert to int just for display purposes. This lets you do friction quite easily, and you could extend it to add spin later on.

To check whether two equal-size balls are colliding, just calculate the pythagorean distance between them, and compare to their radius*2; if smaller, you have a collision. To calculate whether a ball hits the wall, you can just check the x and y coordinates, assuming the table is aligned with the coordinate system.

To work out the angles of reflection given a collision, you need a little bit of trigonometry, and to observe the conservation of momentum. Bouncing off a wall is pretty easy (you just reflect the movement vector by the line of the wall);  I'm not sure off the top of my head how to do the collision between balls, but slasher's link seems to have all the details.

CaptainD

Kweepa (SteveMcCrea) might be able to help?  (Since he made Space Pool Alpha and I seem to remember the physics in that being pretty good).
 

RetroJay

#4
Hi All good Peeps.

AH HA !!

I now see what I have been doing wrong, with a very similar problem.
I have been spending days trying to create a "Pool Game" ...with ... Limited success.

All I had to do was ...
Do THIS with THAT and multiply IT by THAT... then times IT with WHAT and then WHAT again to equals ... WHAT !! (laugh)

Could anyone give us a clue as where to start scripting something like this, please.
Maybe then we can look at our scripts and understand what the hell we are doing so wrong.

I would love to get my "Billiard Game" Working... I don't think I ever will... BUT...

Any help would be greatly appreciated.

Jay.

AnasAbdin

I think the physics is surely possible with AGS with the Maths commands. I was toying with the idea a year ago and got stuck at the beginning when I wanted a dotted line to be drawn when the mouse is clicked but not released yet, the line was supposed to present the direction and the strength of the hit, drawn between the white ball and the mouse cursor while moving pre-release. Something like this:


Snarky

#6
Here's a sketch of the logic I would use. This is by no means complete, and is untested. Barring bugs, it should be able to simulate ball movements with bounces off the bumpers (sides). Ball-to-ball collisions are detected, but the deflections have not been implemented; that would be the next step, followed by pockets (which I'd just do as special-case collisions with "balls" fixed at certain positions).

This is very naive, as it just waits for things to intersect and then changes their direction of movement, so balls will slightly pass through each other and into the bumpers (but that's probably OK, since it has a bit of give). If possible, I would run more than one update per frame: with e.g. ten updates per frame it should be pretty good.

Code: ags
// Header
#define BALL_COUNT 16 // 8-ball pool (15 balls + white)

enum BallType
{
  eBallStripe,
  eBallColor,
  eBallWhite,
  eBallBlack
};

struct BilliardBall
{
  int id;
  BallType type;

  float position_x;
  float position_y;
  float old_position_x;
  float old_position_y;

  float velocity_x;
  float velocity_y;

  import static void init8Ball();
  import static void updateAll();
};

import BilliardBall billiardBall[BALL_COUNT];

// Script
#define TABLE_TOP 0.0
#define TABLE_BOTTOM 500.0
#define TABLE_LEFT 0.0
#define TABLE_RIGHT 250.0

BilliardBall billiardBall[BALL_COUNT];

float friction_coeff_dyn = 0.999;  // How much of the speed dissipates per tick (1 - this number) 
float friction_limit_stat = 0.005; // Minimum movement speed before it stops
float bounce_coeff_bumper = 0.85;  // How much speed is preserved in a bounce off the wall
float radius 5.0;                  // Let's assume all the balls have the same radius (check proportional to table size)

float abs(float f)
{
  if(f<0)
    return -f;
  return f;
}

static void BilliardBall::init8Ball()
{
  int i=0;
  while(i<BALL_COUNT)
  {
    if(i<8)
      billiardBall[i].type = eBallColor;
    else
      billiardBall[i].type = eBallStripe;

    billiardBall[i].id =i;
    i++;
  }
  billiardBall[0].type = eBallWhite;
  billiardBall[8].type = eBallBlack;

  // TODO: Setup initial positions
}

void BilliardBall::moveNaive()
{
  this.old_position_x = this.position_x;
  this.old_position_y = this.position_y;

  this.velocity_x = this.velocity_x * friction_coeff_dyn;
  this.velocity_y = this.velocity_y * friction_coeff_dyn;

  if(this.velocity_x * this.velocity_x + this.velocity_y * this.velocity_y > friction_limit_stat * friction_limit_stat)
  {
    this.position_x = this.this.position_x + this.velocity_x;
    this.position_y = this.position_y + this.velocity_y;
  }
  else
  {
    this.velocity_x = 0.0;
    this.velocity_y = 0.0;
  }
}

// This is very simple & sloppy
static void BilliardBall::checkCollisions()
{
  float d = radius + radius;
  int i=0;
  while(i<BALL_COUNT)
  {
    if( (billiardBall[i].position_x - radius < TABLE_LEFT) || (billiardBall[i].position_x + radius > TABLE_RIGHT) )
    {
      billiardBall[i].velocity_x = -(billiardBall[i].velocity_x * bounce_coeff_bumper);
      billiardBall[i].velocity_y =  (billiardBall[i].velocity_y * bounce_coeff_bumper);
    }
    if( (billiardBall[i].position_y - radius < TABLE_TOP) || (billiardBall[i].position_y + radius > TABLE_BOTTOM) )
    {
      billiardBall[i].velocity_x =  (billiardBall[i].velocity_x * bounce_coeff_bumper);
      billiardBall[i].velocity_y = -(billiardBall[i].velocity_y * bounce_coeff_bumper);
    }
    int j=i+1;
    while(j<BALL_COUNT)
    {
      dX = billiardBall[i].position_x - billiardBall[j].position_x;
      dY = billiardBall[i].position_y - billiardBall[j].position_y;
      // Simple test (hit box)
      if( (abs(dX) < d) && (abs(dY) < d)
      {
        // Accurate test
        if(dX*dX + dY*dY < d*d)
        {
          // TODO: Calculate new velocity vectors, based on http://www.real-world-physics-problems.com/physics-of-billiards.html
        }
      }
    }
  }
}

static void BilliardBall::updateAll()
{
  int i=0;
  while(i<BALL_COUNT)
  {
    billiardBall[i].moveNaive();
    i++;
  }
  BilliardBall.checkCollisions();
}


Edit: Fixed a couple of bugs

RetroJay

Hi Snarky.

This is AWESOME !!

Thank you for taking the time to do this and explaining what it does.
I think that this may be of great help to us. (nod)
A starting block, if you will, to build on.;-D

It is very much appreciated.

Jay.

Vincent

Hello guys, thanks so much for your replies they have been very helpful :)
Many thanks to Snarky too for showing a correct way to achieve this.
I roughly managed to get something nice along with your example,
I was able to simulate the ball movements with bounces off the sides and Ball-to-ball collisions work just GREAT!!!(nod)
The hard thing now is to give the right direction/angle where to go if the ball collide each other or hit the table :X

Snarky

#9
Here's some math to calculate the result of a collision between two balls, based on the link above. First, let's do the case where one ball (A) is at rest, and is hit by the other (B).

First we work out how the balls hit each other, by taking the vector between the centers of the balls:

Code: ags
float dx = A.position_x - B.position_x;
float dy = A.position_y - B.position_y;


Then we calculate the angle between this vector and the movement of B, using the formula for the angle between two vectors u and v: cos(theta) = dotproduct(u,v)/(length(u)*length(v))

Code: ags
float dotprod = B.velocity_x*dx + B.velocity_y*dy;
float velocity_length = Maths.Sqrt(B.velocity_x*B.velocity_x + B.velocity_y*B.velocity_y);
float dist_length = Maths.Sqrt(dx*dx + dy*dy);
float theta = Maths.ArcCos(dotprod/(velocity_length*dist_length));


(Note that this theta is not the same as in the diagrams on the web page, but perpendicular to it.)
Now if theta is 0, we hit the ball straight on, and it's very simple: the movement of B just gets transferred to A, and B stops:

Code: ags
if(theta == 0.0)
{
  A.velocity_x = B.velocity_x;
  A.velocity_y = B.velocity_y;
  B.velocity_x = 0;
  B.velocity_y = 0;
}


If theta is not 0, we hit it at an angle, and they'll both glance off each other. As explained here, the angles are parallel and perpendicular to the angle between the balls at the point of collision: (dx,dy) for A and (dy,-dx) for B, because reasons, and the magnitudes are such that the vectors form a right-angled triangle with the initial velocity of B. That means that we can calculate the magnitudes using sin and cos:

Code: ags
else
{
  A.velocity_x =  dx * Maths.Cos(theta) * velocity_length / dist_length;
  A.velocity_y =  dy * Maths.Cos(theta) * velocity_length / dist_length;
  B.velocity_x =  dy * Maths.Sin(theta) * velocity_length / dist_length;    // TODO: Check what happens if theta < 0. (Wrong sign, so direction is possibly flipped?) 
  B.velocity_y = -dx * Maths.Sin(theta) * velocity_length / dist_length;
}


To do two moving balls, I would just change the frame of reference to one where ball A is at rest for our calculations (subtract the velocity of ball A from the velocity vectors at the start, and add it back at the end):

Code: ags
float vA_x = A.velocity_x;
float vA_y = A.velocity_y;

// Subtract the velocity of A
A.velocity_x = 0.0;
A.velocity_y = 0.0;
B.velocity_x -= vA_x;
B.velocity_y-= vA_y;

// Do all the other stuff here
// ...

// Add the velocity of A back
A.velocity_x += vA_x;
A.velocity_y += vA_y;
B.velocity_x += vA_x;
B.velocity_y += vA_y;


These calculations could be optimized by a lot, by the way (instead of calculating theta with arccos, just calculate cos(theta), only calculate velocity_length/dist_length once, etc.), but I wanted to keep it as close as possible to the logic of the problem for clarity. I haven't tested any of this, so there might be mistakes in the math or the code.

SMF spam blocked by CleanTalk