Raycaster

Miscellaneous Forums/Blitz Showcase/Raycaster

Hey guys,

in the last few weeks I was experimentating with the raycaster algorithm and this is what came out :D
Raycasting is an alternative way to render 3D - sceneries, similar to raytracing; games like Wolfenstein 3D or Doom used it.
Event though my implementation isn't really complex, it's still able to handle floor, ceilings, textured walls and sprites.
Especially the sprites take quite long to render, since I couldn't figure out a fast visibility check.

There are still bugs I couldn't fix yet.
Sometimes rays fly through walls and things behind the wall are being rendered.

Mocing with arrow keys, + and - on numpad to look up/down.

You can use the code in your games, if you want to - even though I don't expect it :D

You can download the file at http://www.noobody.org/BBP/Raycaster.rar

PS: Please excuse my bad english - I'm still working on it ;)

EDIT: Oh noes, I forgot the screenshots!



EDIT2: Hrm, I can't figure out that part with the image- and url tags...

Do it like this, but put a space between img and http:
[imghttp://www.noobody.org/Data/Screenie.PNG]
[imghttp://www.noobody.org/Data/Screenshot.PNG]

Oh, thanks alot :D

not bad!
it is kind of slow for 320 x 200. isn't it?
anyway..

just did a quick glance at your code and I think I fixed a couple of your errors:
Const MAPSIZE = 16
Const GRIDSIZE = 64
Const GRIDSHIFTS = 6
Const GWIDTH = 320
Const GHEIGHT = 200
Const ANGLEOFVIEW = 60
Const CASTINGSTEP# = ANGLEOFVIEW/Float( GWIDTH )
Const TILES = 2
Const TEXTURESIZE = 64
Const SCALEFACTOR# = TEXTURESIZE/GRIDSIZE
Const FLOORTILE = 2
Const CEILINGTILE = 2
Const HD = ( SCALEFACTOR# <> 1 )
Const CEILING = True

Graphics GWIDTH, GHEIGHT, 32, 2

Global CamX# = 5*GRIDSIZE + GRIDSIZE/2, CamY# = 5*GRIDSIZE + GRIDSIZE/2, ProjectionDistance# = ( GWIDTH/2 )/Tan( ANGLEOFVIEW/2 )
Global CameraAngle = 270, PlayerHeight = 32, PlaneDisplacement

Dim MapData( MAPSIZE - 1, MAPSIZE - 1 )
Dim TextureData( TILES*( TEXTURESIZE)*( TEXTURESIZE ) )
Dim Distances#( GWIDTH - 1 )

For i = 0 To Tiles - 1
	Texture = LoadImage( "Tile" + ( i + 1 ) + ".png" )
	SetBuffer ImageBuffer( Texture )
	LockBuffer ImageBuffer( Texture )
	For x = 0 To TEXTURESIZE - 1
		For y = 0 To TEXTURESIZE - 1
			TextureData( i*TEXTURESIZE*TEXTURESIZE + ( x*TEXTURESIZE + y ) ) = ReadPixelFast( x, y )
		Next
	Next
	
	FreeImage Texture
Next

Type Sprite
	Field X#
	Field Y#
	Field Height
	Field Width
	Field ImageData[ 256*256 ]
End Type

Dummy = LoadImage( "Alien.png" )
LockBuffer ImageBuffer( Dummy )
For i = 1 To 10
	Drink.Sprite = New Sprite
		Drink\X# = Rnd( 0, MAPSIZE*GRIDSIZE )
		Drink\Y# = Rnd( 0, MAPSIZE*GRIDSIZE )
		Drink\Height = ImageHeight( Dummy )
		Drink\Width = ImageWidth( Dummy )
		
		For x = 0 To Drink\Width - 1
			For y = 0 To Drink\Height - 1
				Drink\ImageData[ x*Drink\Width + y ] = ReadPixelFast( x, y, ImageBuffer( Dummy ) )
			Next
		Next
	
	If MapData( Drink\X# Shr GRIDSHIFTS, Drink\Y# Shr GRIDSHIFTS ) Then i = i - 1
Next

FreeImage( Dummy )

SetBuffer BackBuffer()

Restore MapData
For i = 0 To MAPSIZE - 1
	For t = 0 To MAPSIZE - 1
		Read MapData( t, i )
	Next
Next

Timer = CreateTimer( 60 )

DisplayFPS = 0
FPS = 0
Counter = MilliSecs()

While Not KeyHit( 1 )
	Cls
	
	LockBuffer BackBuffer()
	RenderScreen()
	RenderSprites()
	UnlockBuffer BackBuffer()
	
	MoveCamera()
	
	If MilliSecs() - Counter > 1000 Then
		DisplayFPS = FPS
		Counter = MilliSecs()
		FPS = 0
	EndIf
	
	Text 0, 0, DisplayFPS
	
	Flip 0
	WaitTimer Timer
	
	FPS = FPS + 1
Wend

End

Function MoveCamera()
	XSpeed# = -Sgn( KeyDown( 208 ) - KeyDown( 200 ) )*Cos( CameraAngle )
	YSpeed# = Sgn( KeyDown( 208 ) - KeyDown( 200 ) )*Sin( CameraAngle )
	
	TileX = ( CamX# + XSpeed# + Sgn( XSpeed# ) ) Shr GRIDSHIFTS
	TileY = ( CamY# + YSpeed# + Sgn( YSpeed# ) ) Shr GRIDSHIFTS
	OldTileX = CamX# Shr GRIDSHIFTS
	OldTileY = CamY# Shr GRIDSHIFTS
	
	If Not MapData( OldTileX, TileY ) Then CamY# = CamY# + YSpeed#
	If Not MapData( TileX, OldTileY ) Then CamX# = CamX# + XSpeed#
	
	CameraAngle = CameraAngle - KeyDown( 203 ) + KeyDown( 205 )
	
	PlaneDisplacement = PlaneDisplacement - KeyDown( 74 ) + KeyDown( 78 )
End Function

Function RenderScreen()
	For i = 0 To GWIDTH - 2
		Angle# = ( CameraAngle - ANGLEOFVIEW/2 + CASTINGSTEP#*i ) Mod 360
		Angle# = ( 360 + Angle# ) Mod 360
		
		;Wird der Strahl nach oben oder unten geschickt?
		YDirection = 1 - ( Angle# < 180 )*2
		
		;Wird der Strahl nach links oder rechts geschickt?
		XDirection = -1 + ( Angle# > 270 Or Angle# < 90 )*2
		
		;Horizontale Kollision
		Ya# = CamY# Mod GRIDSIZE
		If YDirection = 1 Then Ya# = GRIDSIZE - Ya#
		Xa# = Ya#/Tan( Angle# )
		
		IntersectionY# = CamY# + YDirection*Ya#
		IntersectionX# = CamX# - YDirection*Xa#
		
		TileY = ( IntersectionY# + YDirection ) Shr GRIDSHIFTS
		TileX = IntersectionX# Shr GRIDSHIFTS
		
		If TileX >= 0 And TileX < MAPSIZE And TileY >= 0 And TileY < MAPSIZE Then
			If MapData( TileX, TileY ) Then 
				WallTile = MapData( TileX, TileY )
			Else
				Ya# = YDirection Shl GRIDSHIFTS
				Xa# = Ya#/Tan( Angle# )
				Repeat
					IntersectionX# = IntersectionX# - Xa#
					IntersectionY# = IntersectionY# + Ya#
					TileY = ( IntersectionY# + YDirection ) Shr GRIDSHIFTS
					TileX = IntersectionX# Shr GRIDSHIFTS
					If TileX >= 0 And TileX < MAPSIZE And TileY >= 0 And TileY < MAPSIZE Then
						If MapData( TileX, TileY ) Then
							WallTile = MapData( TileX, TileY )
							Exit
						EndIf
					Else
						Exit
					EndIf
				Forever
			EndIf
		EndIf

		WallX# = IntersectionX#
		WallY# = IntersectionY#
		
		;Vertikale Kollision
		Xa# = CamX# Mod GRIDSIZE
		If XDirection = 1 Then Xa# = GRIDSIZE - Xa#
		Ya# = Xa#*Tan( Angle# )
		
		IntersectionX# = CamX# + XDirection*Xa#
		IntersectionY# = CamY# - XDirection*Ya#
		
		TileY = IntersectionY# Shr GRIDSHIFTS
		TileX = ( IntersectionX# + XDirection ) Shr GRIDSHIFTS
		
		If TileX >= 0 And TileX < MAPSIZE And TileY >= 0 And TileY < MAPSIZE Then
			If MapData( TileX, TileY ) Then
				IntersectionTile = MapData( TileX, TileY )
			Else
				Xa# = XDirection Shl GRIDSHIFTS
				Ya# = Xa#*Tan( Angle# )
				Repeat
					IntersectionX# = IntersectionX# + Xa#
					IntersectionY# = IntersectionY# - Ya#
					TileY = IntersectionY# Shr GRIDSHIFTS
					TileX = ( IntersectionX# + XDirection ) Shr GRIDSHIFTS
					If TileX >= 0 And TileX < MAPSIZE And TileY >= 0 And TileY < MAPSIZE Then
						If MapData( TileX, TileY ) Then
							IntersectionTile = MapData( TileX, TileY )
							Exit
						EndIf
					Else
						Exit
					EndIf
				Forever
			EndIf
		EndIf
	
		DistanceVertically# = Abs( ( WallY# - CamY# )/Sin( Angle# ) )
		DistanceHorizontally# = Abs( ( IntersectionX# - CamX# )/Cos( Angle# ) )
		
		If DistanceHorizontally# < DistanceVertically# Then 
			SmallerDistance# = DistanceHorizontally#
			If WallTile > 0 Then
				If HD Then 
					TextureRow = Floor( ( WallX# Mod GRIDSIZE )*SCALEFACTOR# )
				Else
					TextureRow = Floor( WallX# ) Mod GRIDSIZE
				EndIf
			EndIf
			If HD Then
				TextureRow = Floor( ( IntersectionY# Mod GRIDSIZE )*SCALEFACTOR# )
			Else
				TextureRow = Floor( IntersectionY# ) Mod GRIDSIZE
			EndIf
		Else
			SmallerDistance# = DistanceVertically#
			If IntersectionTile > 0 Then
				If HD Then
					TextureRow = Floor( ( IntersectionY# Mod GRIDSIZE )*SCALEFACTOR# )
				Else
					TextureRow = Floor( IntersectionY# ) Mod GRIDSIZE
				EndIf
			EndIf
			If HD Then
				TextureRow = Floor( ( WallX# Mod GRIDSIZE )*SCALEFACTOR# )
			Else
				TextureRow = Floor( WallX# ) Mod GRIDSIZE
			EndIf
		EndIf
		
		WallHeight = ( GRIDSIZE/( SmallerDistance#*Cos( CASTINGSTEP#*i - ANGLEOFVIEW/2 ) ) )*ProjectionDistance#
		If WallTile = 0 And IntersectionTile > 0 Then WallTile = IntersectionTile
		
		TStart = ( GHEIGHT/2 + PlaneDisplacement ) - WallHeight/2
		TEnd = ( GHEIGHT/2 + PlaneDisplacement ) + WallHeight/2
		If TStart < 0 Then YCorrection = Abs( TStart )
		If TEnd >= GHEIGHT Then YCorrection2 = TEnd - GHEIGHT + 1
				
		For t = TStart + YCorrection To TEnd - YCorrection2
			If WallTile = 1 Then
				WritePixelFast i, t, $FFFFFFFF 
			Else
				ARGB = TextureData( ( WallTile - 2 )*TEXTURESIZE*TEXTURESIZE + ( TextureRow*TEXTURESIZE + ( t - GHEIGHT/2 + WallHeight/2 - PlaneDisplacement )*( TEXTURESIZE/Float( WallHeight ) ) ) )
				WritePixelFast i, t, ARGB
			EndIf
		Next
		
		;Floor - Casting
		TStart = ( GHEIGHT/2 + PlaneDisplacement ) + WallHeight/2
		TStart = TStart*( TStart >= 0 )
		TEnd = GHEIGHT
;********************************
		For t = TStart To TEnd-1 ;fixed
;********************************
			Length# = ( ( GRIDSIZE/2 )/( ( t - GHEIGHT/2 - PlaneDisplacement )/ProjectionDistance# ) )/Cos( CASTINGSTEP#*i - ANGLEOFVIEW/2 )
			FloorX = CamX# + Cos( Angle# )*Length#
			FloorY = CamY# - Sin( Angle# )*Length#
			TextureColumn = FloorY Mod TEXTURESIZE
			TextureRow = FloorX Mod TEXTURESIZE
			ARGB = TextureData( ( FLOORTILE - 1 )*TEXTURESIZE*TEXTURESIZE + ( TextureRow*TEXTURESIZE + TextureColumn ) )
			If i >= 320 Or t >= 200 Then Print i+" "+t+" third"
			WritePixelFast i, t, ARGB
		Next
				
		If CEILING Then
			;Ceil - Casting
			TStart = 0
			TEnd = ( GHEIGHT/2 + PlaneDisplacement ) - WallHeight/2
			TEnd = TEnd*( TEnd < GHEIGHT ) + ( GHEIGHT - 1 )*( TEnd >= GHEIGHT )
;*************************************
			For t = TStart To TEnd-1 ;fixed
;*************************************
				Length# = ( ( GRIDSIZE/2 )/( ( GHEIGHT/2 - t + PlaneDisplacement )/ProjectionDistance# ) )/Cos( CASTINGSTEP#*i - ANGLEOFVIEW/2 )
				CeilingX = CamX# + Cos( Angle# )*Length#
				CeilingY = CamY# - Sin( Angle# )*Length#
				TextureRow = CeilingX Mod TEXTURESIZE
				TextureColumn = CeilingY Mod TEXTURESIZE
				ARGB = TextureData( ( CEILINGTILE - 1 )*TEXTURESIZE*TEXTURESIZE + ( TextureRow*TEXTURESIZE + TextureColumn ) )
				WritePixelFast i, t, ARGB
			Next
		EndIf
		
		Distances#( i ) = SmallerDistance#
		
		TextureRow = 0
		IntersectionTile = 0
		WallTile = 0
		Saviour = 0
		YCorrection = 0
		YCorrection2 = 0
	Next
End Function

Function RenderSprites()
	;Sprites
	For Drink.Sprite = Each Sprite
		Distance# = Sqr( ( CamX# - Drink\X# )*( CamX# - Drink\X# ) + ( CamY# - Drink\Y# )*( CamY# - Drink\Y# ) )
		Height = Drink\Height - 1
		Width = Drink\Width
		SpriteHeight = ( Height/Distance# )*ProjectionDistance#
		SpriteWidth = Width*( SpriteHeight/Float( Height ) )
		
		Difference# = ATan( ( Drink\Y# - CamY# )/( Drink\X# - CamX# ) )
		If Drink\X# - CamX# < 0 Then Difference# = 180 + Difference#
		Angle# = ( -Difference# - CameraAngle + 360 ) Mod 360
		StartSlice = ( Angle# + ANGLEOFVIEW/2 )/CASTINGSTEP# - SpriteWidth/2
		If ( StartSlice < 0 Or StartSlice > GWIDTH ) And Abs( Angle# ) > 360 - ANGLEOFVIEW/2 - SpriteWidth/2 Then
			Angle# = Angle# - Sgn( Angle# )*360
			StartSlice = ( Angle# + ANGLEOFVIEW/2 )/CASTINGSTEP# - SpriteWidth/2
		EndIf
		
		XStart = StartSlice*( StartSlice >= 0 )
		XEnd = StartSlice + SpriteWidth - 1
		XEnd = XEnd*( XEnd < GWIDTH ) + ( GWIDTH - 1 )*( XEnd > GWIDTH )
		YStart = ( GHEIGHT/2 - Spriteheight/2 + PlaneDisplacement )*( GHEIGHT/2 - SpriteHeight/2 + PlaneDisplacement >= 0 ) 
		YEnd = GHEIGHT/2 + SpriteHeight/2 - 1 + PlaneDisplacement
		YEnd = YEnd*( YEnd <= GHEIGHT ) + GHEIGHT*( YEnd > GHEIGHT )
;**************************************		
		For x = XStart To XEnd-1    	;fixed maybe?
			For y = YStart To YEnd-1	;fixed maybe?
;*****************************************
				If Distances#( x ) > Distance# Then
					ARGB = Drink\ImageData[ Floor( ( x - StartSlice )*( Width/Float( SpriteWidth ) ) )*Drink\Width + ( y - GHEIGHT/2 + SpriteHeight/2 - PlaneDisplacement )*( Height/Float( SpriteHeight ) ) ]
					If ARGB - $FF000000 <> 0 Then
						If x >= 320 Or y >= 200 Then Print x+" "+y+" fifth"
						WritePixelFast x, y, ARGB
					EndIf
				EndIf
			Next
		Next
	Next
End Function

.MapData
Data 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
Data 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2
Data 1, 0, 2, 0, 2, 0, 2, 0, 2, 2, 2, 2, 2, 2, 0, 2
Data 2, 2, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 2
Data 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2
Data 2, 2, 2, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 2
Data 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 0, 2
Data 2, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 0, 2, 0, 2
Data 2, 2, 2, 0, 2, 2, 0, 2, 0, 0, 0, 2, 0, 2, 0, 2
Data 2, 2, 2, 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 0, 0, 2
Data 2, 0, 2, 0, 0, 0, 0, 2, 0, 2, 0, 2, 2, 2, 0, 2
Data 2, 0, 2, 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 0, 0, 2
Data 2, 0, 2, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 2, 0, 2
Data 2, 0, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2
Data 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
Data 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2


Thanks, Jesse, I really have to be dumb to do such simple mistakes :D

What I forgot to mention, if you take out the WaitTimer, it will be much faster (that's not an april fool ;) ).
On my crappy Intel Celeron FPS are between 100 and 200, which is imho acceptable.

not bad ;)