The code for colliding marbles is built on top of earlier changes to the

DDEX3 sample. Look

here,

here and

here for more information. There are a few changes I made in the code for making the marbles collide and I hope you have already gone through Prof. Fu-Kwun Hwang

post on how to make stuff bounce off each other.

Create a structure to store the information about a marble:

/*

This structure stores the info related to a ball.

Its center (xPos, yPos)

Its velocity along both axes (dwVelX, dwVelY)

Its radius in pixels

*/

typedef struct _ballinfo

{

int xPos;

int yPos;

int dwVelX;

int dwVelY;

int radius;

} BALLINFO;

And here are the globals we use in the source file:

#define MAX_BALLS 3

//we’ll be bouncing 3 balls around here.

BALLINFO g_BallInfo[MAX_BALLS];

//to store the rects of each ball

RECT g_Rect[MAX_BALLS];

To add more marbles to the program you just need to change the MAX_BALLS macro and recompile the code. Well, the terms balls and marbles are used interchangeably (:

We initialize our global data in InitApp() function, we call InitializeData() just before InitApp() returns:

void InitializeData()

{

int radius = 22;

for (int i=0; i < MAX_BALLS; i++)

{

g_BallInfo[i].xPos = RandInt(radius, 216);

//switch on i to decide on yPos so that the balls dont overlap when they are created

switch(i)

{

//dividing the screen into 3 horizontal sections with one ball in each

case 0:

g_BallInfo[i].yPos = RandInt(radius, 100-radius);

break;

case 1:

g_BallInfo[i].yPos = RandInt(100+radius, 200-radius);

break;

case 2:

g_BallInfo[i].yPos = RandInt(200+radius, 300-radius);

break;

default:

g_BallInfo[i].yPos = RandInt(radius, 320-radius);

}

g_BallInfo[i].dwVelX = RandInt(-6, 6);

g_BallInfo[i].dwVelY = RandInt(-6, 6);

g_BallInfo[i].radius = radius;

printf("Ball %d details:\n", i+1);

printf("X=%d Y=%d VelX=%d VelY=%d\r\n", g_BallInfo[i].xPos, g_BallInfo[i].yPos,

g_BallInfo[i].dwVelX, g_BallInfo[i].dwVelY);

}

}

Here we initialize all the data related to each marble, its center, velocity and the radius. The switch case makes sure that the balls do not overlap initially, currently I am only checking for three marbles in the switch. So if there are more than 3, there is still some possibility of the balls overlapping. I’ll leave that part for later and lets focus on more important things.

The RandInt() function is simple, it returns a random value between the two ranges specified:

DWORD RandInt(DWORD low, DWORD high)

{

DWORD range = high – low;

DWORD num = Random() % range;

return (num + low);

}

Next, we look at the UpdateFrame() function:

static void UpdateFrame(HWND hWnd)

{

HRESULT hRet;

DDBLTFX ddbltfx;

int i = 0, j = 0;

memset(&ddbltfx, 0, sizeof(ddbltfx));

ddbltfx.dwSize = sizeof(ddbltfx);

ddbltfx.dwFillColor = 0;

ddbltfx.dwROP = SRCCOPY;

//clear the back buffer (color fil with black)

g_pDDSBack->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAITNOTBUSY, &ddbltfx);

for (i = 0; i < MAX_BALLS; i++)

{

g_Rect[i].top = g_BallInfo[i].yPos – 24;

g_Rect[i].left = g_BallInfo[i].xPos – 24;

g_Rect[i].bottom = g_BallInfo[i].yPos + 24;

g_Rect[i].right = g_BallInfo[i].xPos + 24;

}

ddbltfx.ddckSrcColorkey.dwColorSpaceLowValue = RGB(0, 0, 0);

ddbltfx.ddckSrcColorkey.dwColorSpaceHighValue = RGB(0, 0, 0);

//blt all the balls on the screen

for (i = 0; i<MAX_BALLS; i++)

{

while (TRUE)

{

hRet = g_pDDSBack->Blt(&g_Rect[i], g_pDDSOne, NULL, DDBLT_ROP | DDBLT_KEYSRCOVERRIDE, &ddbltfx);

if (hRet == DD_OK)

break;

if (hRet == DDERR_SURFACELOST)

{

hRet = RestoreAll();

if (hRet != DD_OK)

break;

}

if (hRet != DDERR_WASSTILLDRAWING)

break;

}

}

//update the balls positions and check for bounds

for (i = 0; i<MAX_BALLS; i++)

{

g_BallInfo[i].xPos = g_BallInfo[i].xPos + g_BallInfo[i].dwVelX;

g_BallInfo[i].yPos = g_BallInfo[i].yPos + g_BallInfo[i].dwVelY;

if(g_BallInfo[i].xPos <= 24)

{

g_BallInfo[i].xPos = 24;

g_BallInfo[i].dwVelX = -g_BallInfo[i].dwVelX;

}

if(g_BallInfo[i].xPos >= (240 – 24))

{

g_BallInfo[i].xPos = 240 – 24;

g_BallInfo[i].dwVelX = -g_BallInfo[i].dwVelX;

}

if(g_BallInfo[i].yPos <= 24)

{

g_BallInfo[i].yPos = 24;

g_BallInfo[i].dwVelY = -g_BallInfo[i].dwVelY;

}

if (g_BallInfo[i].yPos >= (320 – 24))

{

g_BallInfo[i].yPos = 320 – 24;

g_BallInfo[i].dwVelY = -g_BallInfo[i].dwVelY;

}

}

//check for collisions between all the balls

for (i = 0; i < MAX_BALLS-1; i++)

{

for (j = i+1; j < MAX_BALLS; j++)

{

if(Collision(&g_BallInfo[i], &g_BallInfo[j]))

{

printf("Balls HIT!\n");

CalculateNewVelocities(&g_BallInfo[i], &g_BallInfo[j]);

MessageBeep(MB_OK);

}

}

}

}

This is more or less the same as before except that the same things are done for all the three marbles here. Calculate the RECTs for each marble in g_Rect[]. Blt the marbles, update the marbles’ position and check for collisions between marbles. This is the interesting part.

Let me digress a bit here. How do you find out if the marbles are colliding? The presence of

RECTs tricks your mind into thinking that you can check if the two

RECTs overlap, which is what I did initially and which was really dumb, really. I used

IntersectRect() api to find out if the two

RECTs overlap, and if they did I decided its a collision! The problem here is that the two

RECTs may overlap but there might actually be no collision for example when only the corners of the two

RECTs intersect. And that happens way too many times, and watching the program run like that leaves you baffled, things dont make sense! It didn’t take me long to figure out a better way, instead of checking for overlapping

RECTs, you had to check if the two circles (marbles) overlapped. Let the radius of the two circles be

R1 and

R2, calculate the distance between the centers of the two marbles, lets call it

D. The marbles collide when

D <= R1 + R2. Well, they actually collide when

D = R1 + R2, but because our program updates the frames by a certain delta everytime we need to check for

D <= R1 + R2.

The Collision() function takes two BALLINFO structures as input and determines if they collide, returns TRUE if they do and FALSE otherwise:

BOOL Collision(const BALLINFO *ball1, const BALLINFO *ball2)

{

int distance = 0, temp = 0;

int MinDistance = ball1->radius + ball2->radius;

//calculate the distance between the centers of the two balls

int dx = ball2->xPos – ball1->xPos;

int dy = ball2->yPos – ball1->yPos;

temp = dx*dx + dy*dy;

distance = sqrt(temp);

//printf("COLLISION: distance=%d, 2*R=%d\n", distance, MinDistance);

if(distance <= MinDistance)

{

return TRUE;

}

return FALSE;

}

We use the standard formula to calculate the distance between two points, Distance = SQRT((x2-x1)^{2} + (y2-y1)^{2}). If the marbles do collide then we have to calculate the new velocities for the marbles, and this is how we do it:

void CalculateNewVelocities(BALLINFO *ball1, BALLINFO *ball2)

{

int ed = 1, mass1 = 20, mass2 = 20;

double velX1, velY1, velX2, velY2;

double dx = ball2->xPos – ball1->xPos;

double dy = ball2->yPos – ball1->yPos;

//calculate the distance between their centers

double distance = sqrt(dx*dx+dy*dy);

double vp1 = ball1->dwVelX*dx/distance + ball1->dwVelY*dy/distance;

double vp2 = ball2->dwVelX*dx/distance + ball2->dwVelY*dy/distance;

double dt = (ball1->radius + ball2->radius – distance)/(vp1 – vp2);

//printf("CENTERS BEFORE: X1=%d, Y1=%d X2=%d, Y2=%d\n", xPos1, yPos1, xPos2, yPos2);

//the centers of the ball when they actually collided

ball1->xPos = ball1->xPos – floor(ball1->dwVelX*dt + 0.5);

ball1->yPos = ball1->yPos – floor(ball1->dwVelY*dt + 0.5);

ball2->xPos = ball2->xPos – floor(ball2->dwVelX*dt + 0.5);

ball2->yPos = ball2->yPos – floor(ball2->dwVelY*dt + 0.5);

//now calulate the distance between centers (this should be very close to the sum of their radii)

dx = ball2->xPos – ball1->xPos;

dy = ball2->yPos – ball1->yPos;

distance = sqrt(dx*dx+dy*dy);

// Unit vector in the direction of the collision

double ax = dx/distance, ay=dy/distance;

// Projection of the velocities in these axes

double va1 = (ball1->dwVelX*ax + ball1->dwVelY*ay);

double vb1 = (-ball1->dwVelX*ay + ball1->dwVelY*ax);

double va2 = (ball2->dwVelX*ax + ball2->dwVelY*ay);

double vb2 = (-ball2->dwVelX*ay + ball2->dwVelY*ax);

// New velocities in these axes (after collision): ed<=1, for elastic collision ed=1

double vaP1 = va1 + (1+ed)*(va2-va1)/(1 + mass1/mass2);

double vaP2 = va2 + (1+ed)*(va1-va2)/(1 + mass2/mass1);

// Undo the projections

velX1 = vaP1*ax-vb1*ay; velY1 = vaP1*ay+vb1*ax;// new vx,vy for ball 1 after collision

velX2 = vaP2*ax-vb2*ay; velY2 = vaP2*ay+vb2*ax;// new vx,vy for ball 2 after collision

//printf("CENTERS AFTER: X1=%d, Y1=%d X2=%d, Y2=%d\n", xPos1, yPos1, xPos2, yPos2);

//printf("Old Vel: velX1=%d, velY1=%d velX2=%d, velY2=%d\n", dwVelX1, dwVelY1, dwVelX2, dwVelY2);

//new velocities of the balls

ball1->dwVelX = floor(velX1 + 0.5);

ball1->dwVelY = floor(velY1 + 0.5);

ball2->dwVelX = floor(velX2 + 0.5);

ball2->dwVelY = floor(velY2 + 0.5);

//undo the correction done before

ball1->xPos = ball1->xPos + floor(ball1->dwVelX*dt + 0.5);

ball1->yPos = ball1->yPos + floor(ball1->dwVelY*dt + 0.5);

ball2->xPos = ball2->xPos + floor(ball2->dwVelX*dt + 0.5);

ball2->yPos = ball2->yPos + floor(ball2->dwVelY*dt + 0.5);

//printf("New Vel: velX1=%d, velY1=%d velX2=%d, velY2=%d\n", dwVelX1, dwVelY1, dwVelX2, dwVelY2);

}

This function is taken directly from

Professor Fu-Kwun Hwang’s explanation on 2D collisions. Changes made to the velocities and positions in

CalculateNewVelocities() will be reflected back in the caller.

Note: I ran this program quite many times and sometimes it does fail. Maybe something like once in 30 runs. The marbles stick to each other and don’t let go. The problem is in the calculation of ‘dt‘, where it tries to calculate where the balls would have been when they were about to collide (i.e. D = R1 + R2), but since even this is an approximation, it fails sometimes. One fix for this problem is to check if the balls collide after calculating the new position and velocities. If they do then adjust the position values such that new D > R1 + R2. The problem mentioned above happens when after calculating the new positions and velocities for both marbles they still overlap.