Heres a simple 2D Snooker/Pool example I converted to Max ...
The main Circle<>Circle collision checking is done via the UpdateBallPhysics() function.
You might want to play around with the FRICTION, BALL_MASS, and BALL_RADIUS constants too.
Rem
Title: Billiards Style Collision Physics
Description: 2D circle collision response which
can be used where 2 objects collide in 2 dimensions
2D COLLISION CODE
By Joseph 'Phish' Humfrey
This Type of collision response isn't just useful For pool and billiard style
games, And in fact, I didn't write it for that reason at all. I wrote it because I
needed To have collision for the space ships in my game 'Unity'.
When their shields are up, they use this relatively simple method.
It can be used For almost anything - player character bouncing off enemies in
a platform game, to space ship collision, to a game which does actually involve
balls. The basic algorithm which I used would work for both 2D and 3D, so If you
would like to see it, email me at phish@...
End Rem
Const sw=800 , sh=600
Const FRICTION# = 0.982
Const BALL_MASS# = 160.0
Const BALL_RADIUS# = 15.0
SetGraphicsDriver GLMax2DDriver()
Graphics sw,sh,0,60
SetClsColor 16,172,78
SeedRnd MilliSecs()
Global dx#=-1.0 , dy#=0 , power#=0.0
Global ballList:TList=New TList
Type ballType
Field x#, y# ' position
Field dx#, dy# ' x and y speeds
Field radius# ' radius of ball
Field mass# ' mass of ball
Field r%,g%,b% ' ball color
End Type
SetupTriangle
Global cueball:ballType=New ballType
SetupCueBall
' #############################################################
' MAIN LOOP
Repeat
Global mx%,my%
SetClsColor 60,140,80
Cls
UpdateBallPhysics
RenderBalls
' shoot
If MouseDown(1)
power=power+0.3+(power*0.04)
If power>50.0 power=50.0
Else
If cueball.dx=0 And cueball.dy=0 ' ball stopped
FlushMouse
ang=angle(cueball.x,cueball.y,mx,my)
cueball.dx=Sin(ang)*power ; cueball.dy=-Cos(ang)*power
power=0
EndIf
EndIf
' reset
If MouseHit(2)
ballList.Clear
SetupTriangle
SetupCueBall
End If
' show shoot line when balls are stationary
' (currently, only checks for cue ball stopped)
If cueball.dx=0 And cueball.dy=0
SetColor 180,180,180
SetAlpha 0.4 ; SetBlend ALPHABLEND
mx%=MouseX() ; my%=MouseY()
dx=(mx-cueball.x)/20 ; dy=(my-cueball.y)/20
DrawLine cueball.x,cueball.y,mx,my
DrawOval mx-cueball.radius, my-cueball.radius, cueball.radius*2, cueball.radius*2
DrawOval mx-cueball.radius+1, my-cueball.radius+1, cueball.radius*2-2, cueball.radius*2-2
SetAlpha 1.0
EndIf
' draw power bar
SetColor 0,0,0 ; SetAlpha 0.7
DrawRect sw*0.3-1,sh-26,50*4+2,18
SetColor 170,251,206
DrawRect sw*0.3,sh-25,power*4,16
SetAlpha 1.0 ; SetBlend MASKBLEND
SetColor 200,200,200
DrawText "[LMB] Shoot [RMB] Reset [Esc] Exit",10,10
Flip
Until KeyHit(KEY_ESCAPE)
End
' #############################################################
Function SetupTriangle()
Const ballTriangleSize%=5
For Local xloop% = ballTriangleSize To 1 Step -1
For Local yloop% = 1 To xloop
Local c:ballType = New ballType
ballList.AddLast c
c.radius = BALL_RADIUS
c.mass = BALL_MASS
c.x = (5-xloop)*27 + 200
c.y = (yloop*31) - (xloop*31)/2.0 + (sh/2)
c.r=Rand(255) ; c.g=Rand(255) ; c.b=Rand(255)
Next
Next
End Function
Function SetupCueBall()
cueball.x = sw-BALL_RADIUS-8
cueball.y = sh/2
cueball.dx=0.0 ; cueball.dy=0.0
cueball.radius = BALL_RADIUS
cueball.mass = BALL_MASS
cueball.r=255 ; cueball.g=255 ; cueball.b=255
ballList.Addlast cueball
End Function
Function UpdateBallPhysics()
' This is the main physics function for the
' balls. It contains the very basic movement
' physics as well as the collision response code
For Local c:ballType = EachIn ballList
' update positions
c.x=c.x+c.dx
c.y=c.y+c.dy
' gradually slow down
c.dx=c.dx*FRICTION
c.dy=c.dy*FRICTION
' stop completely when below a certain speed
If Abs(c.dx)<0.068 Then c.dx=0
If Abs(c.dy)<0.068 Then c.dy=0
' COLLISION CHECKING
' Check each ball in the loop against
' every other (c against c2)
For Local c2:ballType = EachIn ballList
Local collisionDistance# = c.radius+c2.radius
Local actualDistance# = Sqr((c2.x-c.x)^2+(c2.y-c.y)^2)
' collided or not?
If actualDistance<collisionDistance Then
Local collNormalAngle#=ATan2(c2.y-c.y, c2.x-c.x)
' position exactly touching, no intersection
Local moveDist1#=(collisionDistance-actualDistance)*(c2.mass/Float((c.mass+c2.mass)))
Local moveDist2#=(collisionDistance-actualDistance)*(c.mass/Float((c.mass+c2.mass)))
c.x=c.x + moveDist1*Cos(collNormalAngle+180)
c.y=c.y + moveDist1*Sin(collNormalAngle+180)
c2.x=c2.x + moveDist2*Cos(collNormalAngle)
c2.y=c2.y + moveDist2*Sin(collNormalAngle)
' COLLISION RESPONSE
' n = vector connecting the centers of the balls.
' we are finding the components of the normalised vector n
Local nX#=Cos(collNormalAngle)
Local nY#=Sin(collNormalAngle)
' now find the length of the components of each movement vectors
' along n, by using dot product.
Local a1# = c.dx*nX + c.dy*nY
Local a2# = c2.dx*nX + c2.dy*nY
' optimisedP = 2(a1 - a2)
' ----------
' m1 + m2
Local optimisedP# = (2.0 * (a1-a2)) / (c.mass + c2.mass)
' now find out the resultant vectors
'' Local r1% = c1.v - optimisedP * mass2 * n
c.dx = c.dx - (optimisedP*c2.mass*nX)
c.dy = c.dy - (optimisedP*c2.mass*nY)
'' Local r2% = c2.v - optimisedP * mass1 * n
c2.dx = c2.dx + (optimisedP*c.mass*nX)
c2.dy = c2.dy + (optimisedP*c.mass*nY)
End If
Next
' Simple bouncing off walls.
If c.x<c.radius
c.x=c.radius
c.dx=c.dx*-0.9
End If
If c.x>GraphicsWidth()-c.radius
c.x=GraphicsWidth()-c.radius
c.dx=c.dx*-0.9
End If
If c.y<c.radius
c.y=c.radius
c.dy=c.dy*-0.9
End If
If c.y>GraphicsHeight()-c.radius
c.y=GraphicsHeight()-c.radius
c.dy=c.dy*-0.9
End If
Next
End Function
Function RenderBalls()
For Local c:ballType = EachIn ballList
SetColor c.r,c.g,c.b
DrawOval c.x-c.radius, c.y-c.radius, c.radius*2, c.radius*2
Next
End Function
Function dist%(x1#, y1#, x2#, y2#)
Local xd% = Abs(x1# - x2#) , yd% = Abs(y1# - y2#)
Return Sqr(xd * xd + yd * yd)
End Function
Function angle%(x1#,y1#,x2#,y2#)
Local angle%=ATan2(x2-x1,y2-y1)
Return 180-angle
End Function