Help with "bounce ball off wall code"

Miscellaneous Forums/General Discussion/Help with "bounce ball off wall code"

OK so I want to view a flat plane from above and have a ball rolling around that can bang into walls. I want to make it bounce off the walls realistically.

I know that I need to make the angle of incidence equal the angle of reflection. Well that's all very good in theory but how to actually implement something like that.

For a start I have to work out how to collide a ball and a wall. I can use BMax pixel perfect collision detection on both but that will only tell me that there was a collision, not a which point on the ball.

So maybe I should test a ring of pixels around the rim of the ball, then when one collides I know where it hit the wall (relative to it's centre). I guess though that more than one point could collide if it was moving fast enough and the code had moved the ball partially into the wall. I expect theres some clever code that could avoid this.

Or I could simply move the ball by it's X speed and if I detect a collision I know that I simply have to reverse the X speed. Also the same for Y speed. Are there any problems with this method?

Certainly this would only work for walls that are Horizontal or Vertical. What if the wall is at an angle, I can't just reverse the X or Y, I need to calculate the angle of incidence and reflection. Sure I can do that if I know the ANGLE of the wall. So should I have each wall as an object which has a field for storing its angle? Sounds sensible but what if I just place a rotated block in the playfield? This has 4 walls, each at different angles. I want to draw it as a single block, but from a collision point of 4 it's 4 walls. How would I deal with that?

Any help is very much appreciated, thanks in advance! :-)

If your walls are not vertical or horiztonal you need to
a) Find the vector describing the line.
b) Find the normal vector
c) Nomalise it (and reverse it I think).
d) Find the dot product
e) Calculate the final velocity.

Thanks Tony. Yeah I know I need the normal vector to work out the angle of reflection, but I'm the best way to store they vectors for the graphics I may be drawing. Perhaps they'll hve to be totally different things, so a) graphics that draw b) some kind of vector map which is invisibly overlaid over the graphics.

OK so how should I detect the ball colliding with the vector in the first place?

A vector map with circle/line collision tests.

I guess I should google for circle line collion tests...

Concave/convex polygon collisions and other useful functions. or consider Physlite.

Cool I'll check it out. Meanwhile I made this:

Strict

Graphics 800,600,0

Global c:TCircle = TCircle.Create(0,0,50)
c.color = TColor.Create(255,255,255)
Global l:TLine = TLine.Create(100,100,400,400)
l.CalcLength()

While Not KeyHit(key_escape)
	'Place cicle where mouse is
	c.x = MouseX()
	c.y = MouseY()
	'Change colour depending on collision status 
	If ccCircleLineIntersect(c,l) Then
		c.color.set(255,0,0)		
	Else
		c.color.set(0,255,0)		
	EndIf
	
	'Draw the scene
	Cls
	c.Draw()
	l.draw()
	Flip	
Wend

'----------------------------------------------------
Type TCircle
	Field x#
	Field y#
	Field r# 'radius
	Field Color:TColor
	
	Function Create:TCircle(x,y,r)
		c:TCircle = New TCircle
		c.x=x
		c.y=y
		c.r=r
		Return c
	End Function
	
	Method Draw()
		If Color<>Null Then Color.Apply
		DrawOval(x-r,y-r,r*2,r*2)
		If Color<>Null Then Color.Restore
	End Method
End Type

Type TLine
	Field sx#
	Field sy#
	Field ex#
	Field ey#
	Field length#
	Field Color:TColor
	
	Function Create:TLine(sx,sy,ex,ey)
		l:TLine = New TLine
		l.sx=sx
		l.sy=sy
		l.ex=ex
		l.ey=ey
		Return l
	End Function

	Method CalcLength#()
		length = Sqr((ex-sx)*(ex-sx)+(ey-sy)*(ey-sy))
	End Method
	
	Method Draw(DrawLastPixel=True)
		If Color<>Null Then Color.Apply
		DrawLine(sx,sy,ex,ey,DrawLastPixel)
		If Color<>Null Then Color.Restore
	End Method

End Type

Type TColor 'I'm using American spelling to keep it more like the Blitz commands.
	Field R=0
	Field G=0
	Field B=0
	Field Alpha#=1
	
	Function Create:TColor(r,g,b,alpha#=1)
		Local c:TColor = New TColor
		c.Set(r,g,b,alpha)
		Return c
	End Function
	
	Method Set(tR,tG,tB,talpha#=1)
		R = tR
		G = tG
		B = tB
		Alpha = tAlpha
	End Method

	Method Get(tR Var,tG Var,tB Var)
		tR = R
		tG = G
		tB = B
	End Method
	
	Method GetFull(tR Var,tG Var,tB Var, tAlpha# Var)
		tR = R
		tG = G
		tB = B
		tAlpha = Alpha
	End Method
	
	Method Apply()
		SetColor R,G,B
		SetAlpha Alpha
	End Method
	
	Method ApplyNoAlpha()
		SetColor R,G,B
	End Method

	Method Copy(c:TColor)
		R=c.R
		G=c.G
		B=c.B
		Alpha=c.Alpha
	End Method
	
	Method Restore()
		SetColor 255,255,255
		SetAlpha 1
	End Method
End Type

Function ccCircleLineIntersect%(c:TCircle,l:TLine)
	'Make sure you precalc the line length
	Local x0# = c.x
	Local y0# = c.y
	Local x1# = l.sx
	Local y1# = l.sy
	Local x2# = l.ex
	Local y2# = l.ey
	Local n# = Abs((x2-x1)*(y1-y0)-(x1-x0)*(y2-y1))	
	Local d# = l.length
	Local dist# = n/d
	If dist > c.r Return False
	Local d1# = Sqr((x0-x1)*(x0-x1)+(y0-y1)*(y0-y1))
	If d1-c.r > d Return False
	Local d2# = Sqr((x0-x2)*(x0-x2)+(y0-y2)*(y0-y2))
	If d2-c.r > d Return False
	Return True
End Function

The circle goes red when it's over the line. I do have a bit of a problem with the circle near the end points though. Perhaps the line needs to be "infinite" in length? But then I couldn't make a square :-(

Now I have to work out the angle of reflection...

Fixed the problem with the line end points with another method using vectors:

Strict

Graphics 800,600,0

Global c:TCircle = TCircle.Create(0,0,50)
c.color = TColor.Create(255,255,255)
Global l:TLine = TLine.Create(100,100,400,400)
l.CalcLength()

While Not KeyHit(key_escape)
	'Place cicle where mouse is
	c.x = MouseX()
	c.y = MouseY()
	'Change colour depending on collision status 
	If ccCircleLineIntersect2(c,l) Then
		c.color.set(255,0,0)		
	Else
		c.color.set(0,255,0)		
	EndIf
	
	'Draw the scene
	Cls
	c.Draw()
	l.draw()
	Flip	
Wend

'----------------------------------------------------
Type TCircle
	Field x#
	Field y#
	Field r# 'radius
	Field Color:TColor
	
	Function Create:TCircle(x,y,r)
		c:TCircle = New TCircle
		c.x=x
		c.y=y
		c.r=r
		Return c
	End Function
	
	Method Draw()
		If Color<>Null Then Color.Apply
		DrawOval(x-r,y-r,r*2,r*2)
		If Color<>Null Then Color.Restore
	End Method
End Type

Type TLine
	Field sx#
	Field sy#
	Field ex#
	Field ey#
	Field length#
	Field Color:TColor
	
	Function Create:TLine(sx,sy,ex,ey)
		l:TLine = New TLine
		l.sx=sx
		l.sy=sy
		l.ex=ex
		l.ey=ey
		Return l
	End Function

	Method CalcLength#()
		length = Sqr((ex-sx)*(ex-sx)+(ey-sy)*(ey-sy))
	End Method
	
	Method Draw(DrawLastPixel=True)
		If Color<>Null Then Color.Apply
		DrawLine(sx,sy,ex,ey,DrawLastPixel)
		If Color<>Null Then Color.Restore
	End Method

End Type

Type TColor 'I'm using American spelling to keep it more like the Blitz commands.
	Field R=0
	Field G=0
	Field B=0
	Field Alpha#=1
	
	Function Create:TColor(r,g,b,alpha#=1)
		Local c:TColor = New TColor
		c.Set(r,g,b,alpha)
		Return c
	End Function
	
	Method Set(tR,tG,tB,talpha#=1)
		R = tR
		G = tG
		B = tB
		Alpha = tAlpha
	End Method

	Method Get(tR Var,tG Var,tB Var)
		tR = R
		tG = G
		tB = B
	End Method
	
	Method GetFull(tR Var,tG Var,tB Var, tAlpha# Var)
		tR = R
		tG = G
		tB = B
		tAlpha = Alpha
	End Method
	
	Method Apply()
		SetColor R,G,B
		SetAlpha Alpha
	End Method
	
	Method ApplyNoAlpha()
		SetColor R,G,B
	End Method

	Method Copy(c:TColor)
		R=c.R
		G=c.G
		B=c.B
		Alpha=c.Alpha
	End Method
	
	Method Restore()
		SetColor 255,255,255
		SetAlpha 1
	End Method
End Type

Type TVector
	Field x#
	Field y#

	Function Create:TVector(x,y)
		Local v:TVector = New TVector
		v.x=x
		v.y=y
		Return v
	End Function
	
	Method AddVector(v:TVector)
		x:+v.x
		y:+v.y
	End Method

	Method CopyFrom(source:TVector)
		x=source.x
		y=source.y
	End Method
	
	Method DotProduct#(v2:TVector)
		Return x*v2.x + y*v2.y
	End Method
	
	Method Multiply(multiplier#)
		x:*multiplier
		y:*multiplier
	End Method

	Method SubtractVector(v:TVector)
		x:-v.x
		y:-v.y
	End Method
End Type

Function ccCircleLineIntersect%(c:TCircle,l:TLine)
	'Make sure you precalc the line length
	Local x0# = c.x
	Local y0# = c.y
	Local x1# = l.sx
	Local y1# = l.sy
	Local x2# = l.ex
	Local y2# = l.ey
	Local n# = Abs((x2-x1)*(y1-y0)-(x1-x0)*(y2-y1))	
	Local d# = l.length
	Local dist# = n/d
	If dist > c.r Return False
	Local d1# = Sqr((x0-x1)*(x0-x1)+(y0-y1)*(y0-y1))
	If d1-c.r > d Return False
	Local d2# = Sqr((x0-x2)*(x0-x2)+(y0-y2)*(y0-y2))
	If d2-c.r > d Return False
	Return True
End Function

Function ccCircleLineIntersect2%(c:TCircle,l:TLine)
	Local cv:TVector = TVector.Create(c.x,c.y)
	Local p1:TVector = TVector.Create(l.sx,l.sy)
	Local p2:TVector = TVector.Create(l.ex,l.ey)

    Local dir:TVector = ccVectorSubtract(p2,p1)
    Local diff:TVector = ccVectorSubtract(cv,p1)
   	Local t# = diff.DotProduct(dir) / dir.DotProduct(dir)
    If t < 0 Then t = 0
    If t > 1 Then t = 1
    Local closest:TVector = ccVectorAdd(p1, ccVectorMultiply(dir,t))
    Local d:TVector = ccVectorSubtract(cv,closest)
    Local distsqr# = d.DotProduct(d)
    Return distsqr <= c.r * c.r
End Function

Function ccVectorAdd:TVector(v1:TVector,v2:TVector)
	Local result:TVector = New TVector
	result.CopyFrom(v1)
	result.AddVector(v2)	
	Return result
End Function

Function ccVectorSubtract:TVector(v1:TVector,v2:TVector)
	Local result:TVector = New TVector
	result.CopyFrom(v1)
	result.SubtractVector(v2)	
	Return result
End Function

Function ccVectorMultiply:TVector(v:TVector,multiplier#)
	Local result:TVector = New TVector
	result.CopyFrom(v)
	result.Multiply(multiplier)	
	Return result
End Function