Multi collision circles

Monkey Forums/Monkey Programming/Multi collision circles

RENGAC(Posted 2013) [#1]
Hi!. I'm starting with Monkey and I have designed a collision method wich uses multiple "circle to circle" collision to define complex sprite shapes. Use and modify it for free if you like it. I'm sure that this method can be optimized, but as I'm not an expert programmer is the best I can do.

Multi Collision Circles Demo
(Choose Slow Download)

P.D. I'm sure also that this method or similar was designed before, but I haven't found it trough the forums.

P.P.D. Not sure why it doesn't works in "debug" mode o_o' --> Change the code of the file with the one below this post.


RENGAC(Posted 2013) [#2]
Ok, try this code instead. Download the file anyway for the sprite:

'Code by R.Garcia 2013
' This is an example of checking collisions between objects with complex shapes defined 
' by multiple circle collisions. The objects must have at least two collision circles, 
' one that covers all the object, and then as many circles as needed to define the shape of the object.
' Three-Four circles are usually enough for me, but performance depends on how many circles are used.
' For SURE this code can be optimized and better written, as I'm a noobie with Monkey Language 
' and not an expert programmer ;D

Strict

Import mojo

Global MyGame:Game
Global Actors:Actor[50]                      'All "Actors" (or entities or objects,as you wish to call them) are stored here
Global Imagen:Image                        'Sprite used in the example
'============================================================================================================

Function Main:Int ()                       'The "Main" function
	MyGame=New Game()
	Return 0
End

'============================================================================================================

Class Game Extends App 
		
	Field fpsShow:FpsCounter
	
    Method OnCreate:Int ()
					
		SetUpdateRate (60)
		
		Imagen=LoadImage("helikop.png",1,1)
		Imagen.SetHandle(50,50)
		
		For Local i:Int=0 Until 50             'Generating 50 Actors
                    Actors[i]=New Actor()	
                    Actors[i].id=i	
                    Actors[i].X=Rnd(10,630)
                    Actors[i].Y=Rnd(10,470)
		Next
		
		fpsShow=New FpsCounter	            'FPS displayer 
		
		Return 0
	End


	
	Method OnUpdate:Int ()  
        Actors[0].X=MouseX()                'This Actor will follow the mouse
        Actors[0].Y=MouseY()    
               
        For Local i:Int = 0 Until Actors.Length()
            'Updating the "Actors"
            Actors[i].Update()
        next
    
        'Collisions
        For Local i:Int = 0 Until (Actors.Length()-1)
        'Each actor checks collision versus the other actors and then is ignored in the next checkings
            For Local k:Int=i+1 Until (Actors.Length())
                'At first we check the "big" collision circle that encapsulates the Actor.
                'If there isn't collision, the next collision checkings are skipped. 
                'This speeds up the collision routine.
                 Local dx:Float = Actors[k].colCircles [0][0]-Actors[i].colCircles [0][0]
                 Local dy:Float = Actors[k].colCircles [0][1]-Actors[i].colCircles [0][1]
                 Local radii:Float = Actors[k].colCircles [0][2]+Actors[i].colCircles [0][2]
                 If ((dx*dx)+(dy*dy)<radii*radii)
                    'Now we calculate collisions between all the other collision circles of the actors.
                    For Local ii:Int=1 Until Actors[i].colCircles.Length
                        For Local kk:Int=1 Until Actors[k].colCircles.Length
                            Local sdx:Float = Actors[k].colCircles [kk][0]-Actors[i].colCircles [ii][0]
                            Local sdy:Float = Actors[k].colCircles [kk][1]-Actors[i].colCircles [ii][1]
                            Local sradii:Float = Actors[k].colCircles [kk][2]+Actors[i].colCircles [ii][2]
                                If ((sdx*sdx)+(sdy*sdy)<sradii*sradii) 
                                    'In case of collision, we can insert here the subsequent actions to be taken.
                                    'As this is an example, it only change the color of the collision circle.
                                    Actors[i].colCircles [ii][5]=1
                                    Actors[k].colCircles [kk][5]=1
                                Endif
                        Next    
                    Next       
                Endif
            Next    
        Next
		
		Return 0
	End


	
	Method OnRender:Int()
		
		Cls (25,25,25)
				
		For Local i:Int = 0 Until Actors.Length()
		     Actors[i].Render()
		Next
					
		fpsShow.Update()
		fpsShow.Render()
		
		Return 0
	End
End

'============================================================================================================
Class Actor
	
	Field id:Int
	Field X:Float
	Field Y:Float
	Field Xant:Float
	Field Yant:Float
	Field direccion:Float
	Field dirAnterior:Float
	Field cosQuad:Int=1
	Field sinQuad:Int=1
	Field colCircles:Float[][]  
	Field velocidad:Float

    Method New()
        Xant=X
	Yant=Y
	direccion=0
	dirAnterior=1
	velocidad=0.1
		
	colCircles=colCircles.Resize(5) 				' Number of circles that will define the Actor (The first is the encapsulating circle)
	For Local i:Int = 0 Until colCircles.Length()	' Resizing the table that will store the collision circle's data
		colCircles[i]=New Float[6]					' Data of each circle : X from center,Y from center, radius, precalculated distance, precalculated angle, collision check
	Next
	colCircles [0] = [X+10,Y,50,0,0,0]              'These are the data of the circles. A graphical editor could be useful to define them.
	colCircles [1] = [X+8,Y+6,22,0,0,0]
	colCircles [2] = [X+16,Y-24,24,0,0,0]
	colCircles [3] = [X+16,Y+36,4,0,0,0]
	colCircles [4] = [X+44,Y-24,4,0,0,0]
	For Local i:Int = 0 Until colCircles.Length()	' Precalculating distance and angle
		colCircles [i][3]=Sqrt((Pow(colCircles [i][0]-X,2))+(Pow(colCircles [i][1]-Y,2)))
		colCircles [i][4]=ATan2(colCircles [i][0]-X,colCircles [i][1]-Y)					
	Next
    End

	Method Update:Int ()
	    
	'Actor's movement
	X-=(velocidad*Cos(direccion))
        Y+=(velocidad*Sin(direccion))
        direccion+=0.1
                  
        'Maintaining angle between 0-360 degrees
        If direccion>=360 
            direccion=direccion-360
        Elseif direccion<0 
            direccion=direccion+360
        Endif		
                
        'Calculating the rotation of the collision circles
        'This should be done only if the angle of the actor has changed form the last update.
        If dirAnterior<>direccion Then
            dirAnterior=direccion 'Storing last direction
            For Local i:Int = 0 Until colCircles.Length()
                Local distancia:Float = colCircles [i][3]
                Local angulo:Float = colCircles [i][4]
                Local x:Float=distancia*Cos(direccion+angulo)
                Local y:Float=distancia*Sin(direccion+angulo)*-1 
                colCircles [i][0] = x+X
                colCircles [i][1] = y+Y
            Next
        Else
            For Local i:Int = 0 Until colCircles.Length()
                Local distX:Float = X-Xant
                Local distY:Float = Y-Yant
                colCircles [i][0] = colCircles [i][0]+distX
                colCircles [i][1] = colCircles [i][1]+distY
            Next
        EndIf
            
        Xant=X  
        Yant=Y    
        
        Return 0
	End



	Method Render:Int ()
        DrawImage(Imagen,X,Y,direccion,1.0,1.0,0)
            For Local i:Int = 0 Until colCircles.Length()
                If i=0                              'First collision circle
                    SetColor (100,50,50)
                    SetAlpha (0.25)
                Elseif Self.colCircles [i][5]=0    'Not collided circle
                    SetColor (50,100,50)
                Else
                    SetColor (50,50,100)            'Collided circle
                Endif
                SetAlpha (0.5)
                DrawCircle (colCircles[i][0], colCircles[i][1], colCircles[i][2])
            Next
            SetColor (255,255,255)
            SetAlpha (1)
            For Local i:Int = 0 Until colCircles.Length()
                Self.colCircles [i][5]=0        'Marking all circles as "uncollided"
            Next
	    Return 0
	End

End

'===============================================================================================================================

Class FpsCounter 

	Field fpsCount:Int
	Field startTime:Int
	Field CurrentRate:Int

	Method Update:Void()
		If (Millisecs() - startTime) >= 1000
			CurrentRate = fpsCount
			fpsCount = 0
			startTime = Millisecs()
		Else
			fpsCount += 1
		End
	End
	
	Method Render:Void()
		SetColor (255, 255, 255)
		SetAlpha (1)
		DrawText ("FPS >> " + CurrentRate, 4, 4)
	End

End
'===============================================================================================================================



Shinkiro1(Posted 2013) [#3]
I have seen this technique in an article before.
Ideally the algorithm should be able to figure out the inner circles automatically.
I think a tree like structure in combination with recursion would be the way to go here and the only thing left to the programmer would be to specify how deep the tree should maximally go (at best the algorithm can figure this out too).


AdamRedwoods(Posted 2013) [#4]
Ideally the algorithm should be able to figure out the inner circles automatically.

yup, that's what i did here:
http://monkeycoder.co.nz/Community/post.php?topic=5703&post=66282


RENGAC(Posted 2013) [#5]
These techniques are beyond my knowledge, I'm a "X=X+1" coder :D


zoqfotpik(Posted 2013) [#6]
You could use gridwise bins as your culling structure, then within a bin do circle collisions, then if a circle collision is detected check for pixel collisions on sprites. What you ar doing here seems unnecessarily complex to me.

Most of the time bounding boxes work just fine.


Xaron(Posted 2013) [#7]

These techniques are beyond my knowledge, I'm a "X=X+1" coder :D



"X+=1" :D