Bizarre results in pool sim

Started by
11 comments, last by OscarYang 11 years, 10 months ago
I've been following some of the maths posted here: http://www.gamasutra.com/view/feature/3015/pool_hall_lessons_fast_accurate_.php and trying to integrate collisions and response into my pool game, but I'm getting VERY strange results!

Please observe here:



The collision code and update code below!


void UpdateBalls(Ball* balls)
{
float frictionCoefficient = 0.15f;
for(int ball = 0; ball < TOTALBALLS; ball++)
{
if(balls[ball].currentVelocity.Length() > 0.0f)
{
balls[ball].position += balls[ball].currentVelocity;
//put something in here for friction
balls[ball].currentVelocity -= balls[ball].currentVelocity.Normalise() * frictionCoefficient;
//check if the magnitude of the velocity vector is less than 1.0f, if so make it stop.
if(balls[ball].currentVelocity.Length() < 1.0f)
{
balls[ball].currentVelocity = Vector3(0.0f,0.0f,0.0f);
}
}
}
}



void CheckForBallCollisions(Ball* balls)
{
for(int currentBall = 0; currentBall < TOTALBALLS-1; currentBall++)
{
for(int eachBall = currentBall + 1; eachBall < TOTALBALLS; eachBall++)
{
//check if there is possibility for collision, if not, continue
//get distance
float distance = (balls[currentBall].position - balls[eachBall].position).Length();

if(balls[currentBall].currentVelocity.Length() < distance - 2.0 * BALL_RADIUS)
continue;

//get vector between balls
Vector3 betweenVec = balls[eachBall].position - balls[currentBall].position;

//now test the dot product between the vector and movement
if(betweenVec.DotProduct(balls[currentBall].currentVelocity) <= 0)
continue;

//now use the dot product again to find the point on the velocity vector closest to the
//center of the eachBall by projecting the vector between the two balls onto the current balls'
//velocity vector
distance = betweenVec.DotProduct(balls[currentBall].currentVelocity.Normalise());

//using pythagoras get f, the distance from the center of the other ball being tested and the
//velocity vector of the currentball
float f = (betweenVec.Length() * betweenVec.Length()) - (distance * distance);

//if f is bigger than 2 * the ball radius squared they can't collide so go to the next ball
if( f > (BALL_DIAMETER * BALL_DIAMETER))
continue;

//find t, the value of the ball radius * 2 squared minus f
float t = BALL_DIAMETER * BALL_DIAMETER - f;

//if the distance to the other ball is more than the currentball's velocity then there is no way they will hit
float distanceToBall = distance - sqrt(t);

if(distanceToBall > balls[currentBall].currentVelocity.Length())
continue;

//balls[currentBall].position += balls[currentBall].currentVelocity.Normalise() * distance;

//now give them their new velocities
//find the normalized vector from ball 1 to ball2
Vector3 normal = balls[currentBall].position - balls[eachBall].position;
normal = normal.Normalise();

//find the components of the movement vectors along n
float a1;
float a2;

if(balls[eachBall].currentVelocity.Length() > 0.0f)
a2 = normal.DotProduct(balls[eachBall].currentVelocity.Normalise());
else
a2 = 0.0f;

if(balls[currentBall].currentVelocity.Length() > 0.0f)
a1 = normal.DotProduct(balls[currentBall].currentVelocity.Normalise());
else
a1 = 0.0f;

//find p, the impulse factor. The masses of the two balls are the same so m is omitted
//from this equation
float p = a1-a2;

//calculate new velocities
balls[currentBall].currentVelocity = balls[currentBall].currentVelocity + p * normal;
balls[eachBall].currentVelocity = balls[currentBall].currentVelocity - p * normal;

}
}
}


When I'm updating I call the collision code before the update position code

any ideas?

Thanks and hugs in advance
Advertisement
How is time handled in your simulation? I don't see a time-scaling factor anywhere, so variances in frame execution time will lead to destabilization of your physics. In particular the thing that strikes me as suspicious is your check to see if the velocity vector is too short to reach the destination ball; since you don't have a time scale in there, you don't know how far the ball is actually going to travel, so you might miss collisions if the time step is sufficiently short.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Hi ApochPiQ, thanks for replying. I'm using an engine from "Advanced 2D Game Programming" by Jonathan Harbour, in the engine instead of using a delta time value there is a 'stopwatch' class. Basically, a frame is executed every 14 ms or so, so each frame has balls travelling a fixed distance minus friction every frame. In my simple example, I am using the current velocity for this particular frame to test if the ball reaches the other ball, I hope that makes sense.

As an aside, I'm looking to rewrite my own engine very soon that works on a delta time value. The book presents a great way to build a library of things like vectors, particle emitters and so on but I don't use the engine's update functions as it is all obfuscated with various oop-style casts and inheritance etc etc.
Gotcha, that makes sense.

Have you checked to be sure that a pair of balls cannot collide more than once in a given frame? e.g. ball A hits ball B, then later in the loop, B hits A, and counteracts the collision incorrectly?


[edit] Nevermind, on closer reading of your code I think I see the problem. Specifically, consider this situation:

- Ball A is moving at 0 velocity in this frame
- Ball B is moving towards A at 10 units/sec
- You check ball A to see if it can hit Ball B
- Since A isn't moving, you early out and continue to ball B
- Ball B has nothing to check against since it is the last ball
- No collision
- B passes through A

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

I've now tried changing it to this...


for(int currentBall = 0; currentBall < TOTALBALLS-1; currentBall++)
{
for(int eachBall = 0; eachBall < TOTALBALLS; eachBall++)
{
if (eachBall == currentBall)
continue;
.
.
.


Now it acts really weird!
To be more specific....the balls tend to 'cluster together' even more...
Reading through your code it seems to me that your collision-response (or the lack of it) might cancel out further collision-detection, which might result in the clustering of the balls. Another point is be that you only consider the first level of collisions, so if a ball moves into another ball as a result of the collision this will not be treated right. The physical part of the response seems ok to me.

I would recommend that you split up the collision-detection and collision-response for easier debugging. Later on you can merge the code again if it is a performance issue.
First find all collisions and save them in a set (or list); then iterate over the list and calculate the responses.
Maybe it also helps if you minimize the amount of balls in play, it's much easier to determine how many collisions you have between 4 balls than between 17 balls.
There is a typical situation in collision detection and handling (I haven't read through your code) when objects collide, you mirror their velocities as you should, then in the next update the balls are still collision (because they haven't moved totally out of collision since you have a dumping feature to slow balls down), so their velocity will be mirrored again. And again in the next update. There you have your sticking balls. It's sometimes observed as "shaking" balls depending on the refresh rate of your simulation.

To overcome this you either have to make sure the objects get out of collision by explicitly moving them apart in the collision response. This can mean problems with multiple colliding objects.

Or you can use physics simulation instead, with forces (depending on the collision depth, like spring forces), acceleration, etc.
Ok thanks all, I've made a few changes to my code. First of all I've changed my update loop not to use a stopwatch but use a delta time value instead. I've also seperated the collision detection and response. It now goes like this: when updating the position of my balls I first check if this frame causes a collision, if so step back and seperate the balls, THEN give them their new velocities. The clustering isn't as bad now at all, no more overlapping of balls, but instead the balls stick end to end!


void UpdateBalls(Ball* balls,float dt)
{
float frictionCoefficient = 0.05f;
float distance = 0.0f;
Ball* ballPtr = NULL;
for(int ball = 0; ball < TOTALBALLS; ball++)
{
if(balls[ball].currentVelocity.Length() > 0.0f)
{
//put something in here for friction
balls[ball].currentVelocity -= balls[ball].currentVelocity.Normalise() * frictionCoefficient;
//check if the magnitude of the velocity vector is less than 1.0f, if so make it stop.

if(balls[ball].currentVelocity.Length() < 1.0f)
{
balls[ball].currentVelocity = Vector3(0.0f,0.0f,0.0f);
}

//check if ball can move forward without hitting another ball

if(!CheckCollisions(balls,ball,&distance,&ballPtr,dt))
{
balls[ball].position += balls[ball].currentVelocity * dt;
}
else
{
//balls[ball].currentVelocity[X] = 0.0f;
//balls[ball].currentVelocity[Y] = 0.0f;
CollisionResponse(&balls[ball],ballPtr,dt);
}
}
}
}



void CollisionResponse(Ball* ball1,Ball* ball2,float dt)
{
//find the normalized vector from ball 1 to ball2
Vector3 normal = ball1->position - ball2->position;
normal = normal.Normalise();

//find the components of the movement vectors along n
float a1;
float a2;

if(ball2->currentVelocity.Length() * dt > 0.0f)
a2 = normal.DotProduct(ball2->currentVelocity.Normalise());
else
a2 = 0.0f;

if(ball1->currentVelocity.Length() * dt > 0.0f)
a1 = normal.DotProduct(ball1->currentVelocity.Normalise());
else
a1 = 0.0f;

//find p, the impulse factor. The masses of the two balls are the same so m is omitted
//from this equation
float p = a1-a2;

//calculate new velocities
ball1->currentVelocity = ball1->currentVelocity + p * normal;
ball2->currentVelocity = ball1->currentVelocity - p * normal;

//seperate the balls

ball1->position += ball1->currentVelocity.Normalise();
ball2->position += ball2->currentVelocity.Normalise();
}


Video soon

//calculate new velocities
ball1->currentVelocity = ball1->currentVelocity + p * normal;
ball2->currentVelocity = ball1->currentVelocity - p * normal;

//seperate the balls

ball1->position += ball1->currentVelocity.Normalise();
ball2->position += ball2->currentVelocity.Normalise();




I think you have a mistake in the second line where you calculate the current velocity of the balls. You're using the currentVelocity() of of ball1 instead of ball2. I also think that you should not normalize the current velocity at the end, because not just the direction but also the magnitude of the velocity matters in this context.

This topic is closed to new replies.

Advertisement