Feeds:
Posts
Comments

Archive for May, 2011

Mario writes,

I wanted to ask you if among all those great blog-tutorials of yours, there is simple ball-to-ball collision resonse tutorial. Like billiard kind of 2D physics? I need it for a game Im working on that needs collision response between circular asteroids. (including mass)
If not, would you please consider writing one?

There probably is, but it’ll be buried somewhere on the blitzbasic.com forums. So, here’s a new one!

The key insight for ball-to-ball collisions is that, at the moment of impact, the distance between the two balls is the sum of their radii, and the repulsive force is applied along the line between their centres. I’m going to use a lot of vector maths here, because it makes the maths a lot clearer.

So, the first task to detect a collision is to get the distance between the centres of the two balls. If the distance is less than their radii added together, then the balls have collided.

I’ll build up what needs to happen next through a few example cases. I’m not going to really explain the mechanics in depth because there’s loads of messy algebra and I have better things to do, but this should give you the idea.

Case 1: Ball 1 is stationary, ball 2 is travelling directly at ball 1, both balls have the same mass.

When the balls collide, the total momentum needs to be preserved, and the forces applied to each ball need to be of the same size. I’ll also assume the collision is elastic, so the total kinetic energy of the system stays the same. The only solution is that all of ball 2’s momentum is transferred to ball 1. Ball 2 stops and ball 1 starts moving in the same direction and at the same speed as ball 2.

Case 2: Ball 1 is stationary, ball 2 is travelling directly at ball 2, different masses.

One of Newton’s laws of motion is that force  is equal to mass times acceleration:

F = ma

As the forces applied to each ball need to be equal, it follows that if the balls have different masses, the heavier ball accelerates less than the lighter one. In fact, the acceleration of ball 1 is proportional to

\frac{m_2}{m_1+m_2}

This way, the momentum is conserved:

\begin{array}{rl} m_1a_1 + m_2a_2 &= m_1 \left( \frac{m_2}{m_1+m_2} \right) + m_2 \left( \frac{-m_1}{m_1+m_2} \right) \\ &= \frac{m_1m_2}{m_1+m_2} - \frac{m_2m_1}{m_1+m_2} = 0 \end{array}

Case 3: Both balls moving, ball 2 is travelling directly at ball 1, equal masses.

When both balls are moving, you can make the calculation simpler by considering only the relative velocities of the balls. By subtracting ball 1’s velocity from ball 2’s, you can perform the calculation as if ball 1 is stationary, and the resulting force will be the same.

Case 4: Ball 1 stationary, ball 2 hits ball 1 at an angle, equal masses.

The force is applied along the line between the two balls, so when that line isn’t parallel to the relative velocity, the balls bounce off each other at an angle.

All those cases put together should be enough to solve collisions in general. Here’s some code:

Global gwidth=800,gheight=600

Global balls:TList=New TList
Type ball
	Field x#,y#		'position
	Field vx#,vy#		'velocity
	Field radius#
	Field mass#
	
	Method New()
		balls.addlast Self
	End Method
	
	Function Create:ball(x#,y#,radius#,mass#)
		b:ball=New ball
		b.x=x
		b.y=y
		b.radius=radius
		b.mass=mass
		b.vx=(gwidth/2-x)*.01
		b.vy=(gheight/2-y)*.01
		Return b
	End Function
	
	Method update()
		x:+vx
		y:+vy
		
		'wrap edges of screen around
		If x<0
			x:+gwidth
		EndIf
		If x>gwidth
			x:-gwidth
		EndIf
		If y<0
			y:+gheight
		EndIf
		If y>gheight
			y:-gheight
		EndIf
	End Method
	
	Function collideAll()
		l:TList=balls.Copy()
		While l.Count()>1
			b:ball=ball(l.removefirst())
			For b2:ball=EachIn l
			
				dx#=b2.x-b.x		'get relative positions of b2 with respect to b
				dy#=b2.y-b.y
				d#=Sqr(dx*dx+dy*dy)	'get square of distance between b and b2
				
				dd#=b.radius+b2.radius-d	'get amount balls overlap
				
				If dd>0	'if distance between balls is less than their radii added together, then they are overlapping
				
					dx:/d	'by dividing the difference between the two balls' positions, we get a unit vector pointing from b to b2
					dy:/d
					
					dvx#=b2.vx-b.vx	'get relative velocity of b2 with respect to b
					dvy#=b2.vy-b.vy
					
					f#=dvx*dx+dvy*dy		'calculate force of impact - project relative velocity vector onto line from b to b2
					f:*2/(b.mass+b2.mass)	
					
					b2.vx:-dx*f*b.mass	'push b2 away from b
					b2.vy:-dy*f*b.mass
					
					b.vx:+dx*f*b2.mass	'push b in the opposite direction
					b.vy:+dy*f*b2.mass
					
					b.x:-dd*dx*b2.mass/(b.mass+b2.mass)	'position the balls so they are just touching and no longer overlap
					b.y:-dd*dy*b2.mass/(b.mass+b2.mass)
					b2.x:+dd*dx*b.mass/(b.mass+b2.mass)
					b2.y:+dd*dy*b.mass/(b.mass+b2.mass)
					
					
				EndIf
				
			Next
		Wend
	End Function
	
	
	Method draw()
		shade#=mass*5
		SetColor shade,shade,shade
		DrawOval x-radius,y-radius,radius*2,radius*2
		
		'because the edges of the screen wrap round, draw again shifted by a screen's width/height to maintain the illusion
		DrawOval gwidth+x-radius,y-radius,radius*2,radius*2
		DrawOval x-radius,gheight+y-radius,radius*2,radius*2
		DrawOval -gwidth+x-radius,y-radius,radius*2,radius*2
		DrawOval x-radius,-gheight+y-radius,radius*2,radius*2
	End Method
	
End Type

Function changeState()
	balls=New TList
	state=(state+1) Mod 5
	Select state
	Case 0
		b1:ball=ball.Create(400,300,50,50)
		b1.vx=0
		b1.vy=0
		b2:ball=ball.Create(200,300,50,50)
		b2.vx=1
		b2.vy=0
	Case 1
		b1:ball=ball.Create(400,300,50,50)
		b1.vx=0
		b1.vy=0
		b2:ball=ball.Create(200,300,20,20)
		b2.vx=1
		b2.vy=0
	Case 2
		b1:ball=ball.Create(400,300,50,50)
		b1.vx=1
		b1.vy=0
		b2:ball=ball.Create(200,300,50,50)
		b2.vx=2
		b2.vy=0
		
		b3:ball=ball.Create(400,450,50,50)
		b3.vx=0
		b3.vy=0
		b4:ball=ball.Create(200,450,50,50)
		b4.vx=1
		b4.vy=0
	Case 3
		b1:ball=ball.Create(400,300,50,50)
		b1.vx=-1
		b1.vy=0
		b2:ball=ball.Create(200,250,50,50)
		b2.vx=1
		b2.vy=0
		
	Case 4
		For c=1 To 5
			radius#=Rnd(10,50)
			mass=Rnd(.5,1)*radius
			ball.Create Rand(radius,gwidth-radius),Rand(radius,gheight-radius),radius,mass
		Next
	End Select
End Function

Function drawlines(txt$,x,y)
	Local lines$[]=txt.split("~n")
	For i=0 To Len(lines)-1
		DrawText lines[i],0,y
		y:+13
	Next
End Function

Graphics gwidth,gheight,0
SeedRnd MilliSecs()

Global state=-1
Global statetxt$[]=[	"Ball 1 stationary~nBall 2 heading directly at ball 1~nEqual masses",..
					"Ball 1 stationary~nBall 2 heading directly at ball 1~nDifferent masses",..
					"Both balls moving~nBall 2 heading directly at ball 1~nEqual masses~nBall 1's frame of reference shown below",..
					"Ball 1 stationary~nBall 2 hits ball 1 at an angle~nEqual masses",..
					"Lots of balls all over the place!"]

changeState

While Not (KeyHit(KEY_ESCAPE) Or AppTerminate())

	If KeyHit(KEY_SPACE)
		changeState
	EndIf

	ball.collideAll

	For b:ball=EachIn balls
		b.update
	Next
	
	For b:ball=EachIn balls
		b.draw
	Next
	
	SetColor 255,255,255
	Drawlines statetxt[state],0,0
	
	Flip
	Cls
Wend

Read Full Post »