Image based collision detection and response

Blitz3D Forums/Blitz3D Programming/Image based collision detection and response

dmc(Posted 2010) [#1]
*** UPDATED *** NEW VERSION OF CODE
CHANGES: improved performance, improved stability but still can crash if to much energy is put into a particle, much improved smoothness but some particles still tend to jump a little, and lastly incorporated multiple particles.

CHANGES THAT STILL NEED TO BE MADE: particle to particle collision. and eventually constraint based structures for player vehicles etc.

dunno if anybody has already posted something like this.... but here it is if anybody is interested. free to use for whatever.
;Image based collision detection and response by dmc
Global width=1024,height=768
Graphics width,height,32,2
SetBuffer BackBuffer()

Global world=CreateImage(width,height)
Global fps#,fpst#,fpsc# ;FPS variables
Global sim_delta#=.12	;sim delta time
Global worldbank=CreateBank(width * height)

Type p						;type to define a particle
	Field xpos#[2] 			;0=prior position, 1=last calculated position, 2=next position. 
	Field ypos#[2]			;0=prior position, 1=last calculated position, 2=next position.
	Field xaccel#			;acceleration for particle x vector
	Field yaccel#			;acceleration for particle y vector
	Field size				;size of particle (radius in pixels)
	Field vx#				;vector x
	Field vy#				;vector y
	Field tan_vx#			;ground scan tangent for x vector
	Field tan_vy#			;ground scan tangent for y vector
	Field nor_vx#			;ground scan normal for x vector
	Field nor_vy#			;ground scan normal for y vector
	Field player			;identifier for particle image
	Field scansize			;how large the area scanned is for collision vector calculation
	Field roll_damp#		;dampening for rolling on surface
	Field bounce_damp#		;dampening for bounce
	Field collided			;true if particle is collided
	Field c[361]			;array to hold pixel locations for scan of the particle				
End Type

Global p.p
For count=1 To 300 ; how many marticles to spawn
	p.p=New p
	p\xpos[0] = Rnd(50,950)		;x position prior
	p\xpos[1] = p\xpos[0] 		;x position current
	p\ypos[0] = 75				;y position prior
	p\ypos[1] = p\ypos[0] 		;y position current
	p\size = 15					;MUST ALWAYS BE AN ODD NUMBER! (1,3,5 TO DIMENSION SIZE OF ARRAY C) 
	p\yaccel= 9.806				;gravity accelerations 	(this is the only player input for a particle)
	p\xaccel= 0					;any other acceleration (this is the only player input for a particle)
	p\scansize=2				;how large of an area to scan outside the particles size in pixels. zero is a valid number.
	p\roll_damp=.99				;dampening for rolling on surface
	p\bounce_damp=Rnd(.35,.75)	;dampening for bounce
	makeball(p.p,200,Rnd(0,128),Rnd(0,64))
	makecollision(p.p)
Next

Function drawworld()
	SetBuffer FrontBuffer()
	Color 0,0,255
	Rect 0,0,width,height,1
	Color 0,0,0
	Rect 20,20,width-40,height-40,1
	Color 0,255,0
	Text 250,5,"DRAW SOME ROLLING HILLS ON THE GROUND WITH THE MOUSE THEN PRESS SPACE"
	MoveMouse 0,500
	Color 0,0,255
	While Not KeyHit(57)
	Oval MouseX(),MouseY(),50,50,1
	Wend
	CopyRect 0,0,width,height,0,0,FrontBuffer(),ImageBuffer(world)
	SetBuffer BackBuffer()
	Color 255,255,255
End Function


Function makeworldbank(imgtmp,w,h) ;scan bitmap to bank
	SetBuffer ImageBuffer(imgtmp)
	LockBuffer
	For x=0 To w - 1
		For y=0 To h - 1
			temprgb=ReadPixelFast(x, y,ImageBuffer(imgtmp))
			If temprgb=-16777216 Then
				PokeByte worldbank, offset, 0
			Else
				PokeByte worldbank, offset, 255
			End If
			offset=offset+1
		Next
	Next
	UnlockBuffer
	SetBuffer BackBuffer()
End Function

Function makeball(obj.p,red,green,blue) ; simple ball to represetn particle
	obj\player=CreateImage(obj\size,obj\size)
	SetBuffer ImageBuffer(obj\player)
	Color red, green, blue
	Oval 0,0,obj\size,obj\size,1
	SetBuffer BackBuffer()
	Color 255,255,255
End Function

Function makecollision(obj.p) ; make collision envelope based on size and scansize
	inner=1
        outer=2
        blank=0
        tempcount=0
	imgtmp=CreateImage(obj\size+(obj\scansize*2),obj\size+(obj\scansize*2))
	SetBuffer ImageBuffer(imgtmp)
	Color 0, 255, 0
	Oval 0,0,obj\size+(obj\scansize*2),obj\size+(obj\scansize*2),1
	Color 255,0,0
	Oval obj\scansize,obj\scansize,obj\size,obj\size,1
	LockBuffer
	For x= 0 To obj\size+(obj\scansize*2)-1
		For y= 0 To obj\size+(obj\scansize*2)-1
			temprgb=ReadPixelFast(x, y)
			If temprgb=-16777216 Then obj\c[tempcount]=blank 
			If temprgb=-65536 Then obj\c[tempcount]=inner
			If temprgb=-16711936 Then obj\c[tempcount]=outer
			tempcount=tempcount+1
		Next
	Next
	UnlockBuffer
	SetBuffer BackBuffer()
	Color 255,255,255
End Function

Function uncollide_detect(obj.p) ;final collide check. back out particle by small increments
	inner=1
        outer=2
        blank=0
        tempcount=0 
	fromx=obj\xpos[2]-((obj\size-1) /2)-obj\scansize
	tox=obj\xpos[2]+(obj\size-1)-((obj\size-1) /2)+obj\scansize
	fromy=obj\ypos[2]-((obj\size-1) /2)-obj\scansize
	toy=obj\ypos[2]+(obj\size-1)-((obj\size-1) /2)+obj\scansize
	obj\collided = False
	For x=fromx To tox
		For y=fromy To toy
		If obj\c[tempcount] = inner Then
			temprgb=PeekByte (worldbank,(x*height)+y) 
			If temprgb = 255 Then obj\collided=True
		End If
		tempcount=tempcount+1
		Next
	Next
End Function

Function groundscan(obj.p) ;scans world pixels and generates normal and tangent vectors
	obj\collided=False
	Local inner=1,outer=2,blank=0,tempcount=0
	fromx=obj\xpos[2]-((obj\size-1) /2)-obj\scansize
	tox=obj\xpos[2]+(obj\size-1)-((obj\size-1) /2)+obj\scansize
	fromy=obj\ypos[2]-((obj\size-1) /2)-obj\scansize
	toy=obj\ypos[2]+(obj\size-1)-((obj\size-1) /2)+obj\scansize
	For x= fromx To tox
		For y= fromy To toy 
			If obj\c[tempcount] = inner Or obj\c[tempcount] =outer Then
				temprgb=PeekByte (worldbank,(x*height)+y) 
				tempx=Int(obj\xpos[2])
				tempy=Int(obj\ypos[2])
				If temprgb=0 Then 
					;scan returns blank
					tan_y1=tan_y1 + x - tempx;tangent
					tan_x1=tan_x1 - y + tempy;tangent
				
					nor_y1=nor_y1 + y - tempy;normal
					nor_x1=nor_x1 + x - tempx;normal
				Else
					;scan returns filled
					If obj\c[tempcount] = inner And temprgb =255 Then obj\collided=True
					tan_y2=tan_y2 + x - tempx;tangent
					tan_x2=tan_x2 - y + tempy;tangent
		
					nor_y2=nor_y2 + y - tempy;normal
					nor_x2=nor_x2 + x - tempx;normal
				End If
			End If
			tempcount=tempcount+1
		Next
	Next
	
	If obj\collided=True Then
		obj\tan_vx=tan_x1-tan_x2								; sets tangent vector for ground
		obj\tan_vy=tan_y1-tan_y2								; sets tangent vector for ground
		mag#=Sqr(obj\tan_vx * obj\tan_vx + obj\tan_vy * obj\tan_vy)	; sets tangent magnitutde for ground
		obj\tan_vx = obj\tan_vx / mag							; normalize tangent vector for ground
		obj\tan_vy = obj\tan_vy / mag							; normalize tangent vector for ground
	
		obj\nor_vx=nor_x1-nor_x2								; sets normal vector for ground
		obj\nor_vy=nor_y1-nor_y2								; sets normal vector for ground
		mag#=Sqr(obj\nor_vx * obj\nor_vx + obj\nor_vy * obj\nor_vy)	; sets normal magnitude for ground
		obj\nor_vx = obj\nor_vx / mag							; normalize normal vector for ground
		obj\nor_vy = obj\nor_vy / mag							; normalize normal vector for ground
	End If
End Function

Function do_collide(obj.p,xaccel#,yaccel#,image)
;**** begin collision response ************
	inner=1
        outer=2
        blank=0
	groundscan(obj.p) ;ground scan, normal/tangent stuff
	If obj\collided Then
		obj\vx=obj\xpos[2]-obj\xpos[1] ; sets vector for particle
		obj\vy=obj\ypos[2]-obj\ypos[1] ; sets vector for particle
		
		dotprod#=obj\vx * obj\tan_vx + obj\vy * obj\tan_vy ; collision dot product between ground and particle
	
		proj_x# = dotprod * obj\tan_vx ; project tangent particle vector component onto ground vector
		proj_y# = dotprod * obj\tan_vy ; project tangent particle vector component onto ground vector

		tan_bounce_vx# = obj\xpos[2] + (proj_x * obj\roll_damp); calculate bounce tangent x component
		tan_bounce_vy# = obj\ypos[2] + (proj_y * obj\roll_damp); calculate bounce tangent y component
		nor_bounce_vx# = ((obj\xpos[2] - proj_x) - obj\xpos[1]) * obj\bounce_damp ; calculate bounce normal x component
		nor_bounce_vy# = ((obj\ypos[2] - proj_y) - obj\ypos[1]) * obj\bounce_damp ; calculate bounce normal y component
	
		bounce_output_vx# = (obj\xpos[2] - nor_bounce_vx) - (obj\xpos[2] - tan_bounce_vx) ; calculate bounce result vector
		bounce_output_vy# = (obj\ypos[2] - nor_bounce_vy) - (obj\ypos[2] - tan_bounce_vy) ; calculate bounce result vector

		obj\xpos[2] = bounce_output_vx - (obj\xpos[1] - obj\xpos[0]) + ((obj\xaccel*0) + tempxaccel) * (sim_delta * sim_delta);move particle after bounce
		obj\ypos[2] = bounce_output_vy - (obj\ypos[1] - obj\ypos[0]) + ((obj\yaccel*0) + tempyaccel) * (sim_delta * sim_delta);move particle after bounce
		
		groundscan(obj.p) ;recheck for new collision after move
		While obj\collided
			uncollide_detect(obj.p)
			obj\xpos[2]=obj\xpos[2]+(obj\nor_vx/4);slowly back out particle
			obj\ypos[2]=obj\ypos[2]+(obj\nor_vy/4);slowly back out particle
		Wend
	End If ;end collision repsonse

	;update position array to final position
	obj\xpos[0]=obj\xpos[1]
	obj\xpos[1]=obj\xpos[2]
	obj\ypos[0]=obj\ypos[1]
	obj\ypos[1]=obj\ypos[2]
End Function

;******* main sim loop ********************************
Function do_sim(obj.p,xaccel#,yaccel#)
	obj\xpos[2] = obj\xpos[1] + (obj\xpos[1] - obj\xpos[0]) + (obj\xaccel + xaccel) * (sim_delta * sim_delta) ;calculate new x position for particle
	obj\ypos[2] = obj\ypos[1] + (obj\ypos[1] - obj\ypos[0]) + (obj\yaccel + yaccel) * (sim_delta * sim_delta) ;calculate new y position for particle
End Function

;************* fps function ******************
Global new_time=MilliSecs(), old_time=MilliSecs()
Function get_fps(new_time)
	fpst = fpst + (new_time - old_time) * 0.001
	fpsc=fpsc+1
	If fpst > 1 Then 
		fpst=0
		fps=fpsc
		fpsc=0
	End If
	old_time = new_time
End Function 


;********* main loop ************************

get_fps(MilliSecs())
drawworld()
makeworldbank(world,width,height)

While Not KeyHit(1) ;escape to exit
	timestart=MilliSecs()
	get_fps(MilliSecs())
	
	tempxaccel=0
	tempyaccel=0
	If KeyDown(203) Then tempxaccel=-10 ;arrow keys to control all particles
	If KeyDown(205) Then tempxaccel=10 
	If KeyDown(200) Then tempyaccel=-15
	If KeyDown(208) Then tempyaccel=15 
		
	For p.p = Each p
		do_sim(p.p,tempxaccel,tempyaccel)
		do_collide(p.p,tempxaccel,tempyaccel,world)
	Next
	
	Cls
	DrawImage world,0,0
	
	For p.p =Each p
		DrawImage p\player,p\xpos[2]-(p\size / 2),p\ypos[2]-(p\size / 2)
	Next
	timeend=MilliSecs()
	
	tempcount=tempcount+1
	calctime#=calctime+Abs(timestart-timeend)*.001
	If tempcount=550 Then 
		avgtime#=calctime
		tempcount=0
		calctime=0
	End If
	
	Text 40,40,"FPS: " + fps
	Text 40,60,"Arrow keys for control"
	Text 40,80,"Milliseconds Elapsed Per Frame: " + avgtime
	Flip 1
Wend

End



jfk EO-11110(Posted 2010) [#2]
Thanks for sharing.


Adam Novagen(Posted 2010) [#3]
Yeah, this is cool. I'll warn you of something though; Blitz's arrays aren't the fastest things in the world. I'm not complaining, just stating a fact. You're probably aware that 16(and two thirds, technically)ms is the longest time a single frame can take for your program to run at 60FPS. Well, writing to each element of, say, a 320x240 array can take well over 20ms on average.


Kryzon(Posted 2010) [#4]
'Been a while since you last posted, Adam :)


Adam Novagen(Posted 2010) [#5]
'Been a while since you last posted, Adam :)


Yes it has, and thanks for the greet; I'm amazed anyone remembers me around here. XD As long as I stay clear of the off-topic forums and stick to coding, there don't seem to be too many tards and trolls about right now, so I may stick around for a while again. :D


dmc(Posted 2010) [#6]
yeah this code i posted is proof of concept. i continue to work on it and have made some big improvements in stability and speed.

best run so far (on my machine) is 1500 particles at 60fps.

next step is to get the particles to collide with each other. thats going to take a while.


dmc(Posted 2010) [#7]
updated july 23rd with new code. see orignal post description.


Serpent(Posted 2010) [#8]
MAV - Blitz Array out of bounds.

Screenshot (from debug mode):


EDIT: after changing the size of the p.c array to 10000 I had no problems :P
however, setting the size to 1000 wasn't enough.


dmc(Posted 2010) [#9]
thanks for the pic serpent. bummer it doesnt do it on my end. i think it might be declaring tempcount as local and assigned to zero. i think ill just delete the local declaration and hopefully it fixes the issue.

edit added: i just updated the code. see if it still bombs on ya serpent if ya happen to see this post.


Serpent(Posted 2010) [#10]
Problem's now fixed. Everything seems to be working nicely.