Raycaster Competition

BlitzMax Forums/BlitzMax Programming/Raycaster Competition

Who wants to have a raycaster competition? I've never written one before and would like to. This could be a great excuse to write an old school game.

What should the requirements be?

-Walls
-Floor & Ceiling
-?

nazis?

LOL!

question: what's a raycaster competition?

could be:

smallest raycast code (only walking/walls, no enemies)

@z80jim : http://en.wikipedia.org/wiki/Ray_casting

Raycasting is an old way of drawing 3D scenes, which was used by pseudo-3D games on older computers. Like this:



Back then, the game drew the world by casting one ray per vertical line of the screen, to find out how far it was to the nearest wall, which were aligned to a grid in the case of Wolf3D in order to make it fast enough. Then it drew a scaled vertical segment of the appropriate texture, which could be done fast because no divisions needed to be done per pixel to adjust for perspective, because all pixels in that vertical segment were the same distance from the camera.

Nowadays, computers are fast enough that you don't have to reprent the level with a grid or a 2D map of line segments. You can use polygons. And you can cast one ray per pixel on the screen.

But nowadays 3D cards can draw a polygon much faster than it could be calculated by the CPU, so you might be limited in the resolution, and unless you support mipmaps you're gonna see a lot of aliasing.

OH MAN I have just had an amazing idea.

Do tell!

So what'll it be? Smallest code? Best looking?

I already have a working raycaster test in Blitzmax so I would surely participate depending on the competition goal.

sSwift:
Nowadays, computers are fast enough that you don't have to reprent the level with a grid or a 2D map of line segments. You can use polygons. And you can cast one ray per pixel on the screen.

This would actually be 'Ray tracing' no? Although this would be an interesting idea for the competition. I am very unsure about the performance of a realtime raytracer even on today's machine. Intriguing..

I did one in C years ago, just never got rid of the fish-eye-lens problem :)

Funny you should say that, I was going to say earlier today that I think I'll just sit back and wait for the first people to start asking how to get rid of the fish eye. :-)


Since I remember how frustrating that problem was, I offer this hint to those who might try to implement one of these:

The back of your eye is curved. To remove the fish eye effect, you must adjust the distance you calculate for each wall by taking this curvature into account. In other words you'll need to incorporate some sort of multiplication of the distance by sin or cos to cancel out the curvature that you would see if your retina were flat like the sensor of a camera.

Remz:
Realtime raytracing is possible, and I saw demos years and years ago which did it at low resolution. But those were with simple environments. Obviously the more detailed the environment the more costly it will be to raytrace, and I don't know what the state of the art in that area is right now.

Hey you need some sort of fish-eye powrup it would look quite cool.

sswift

http://www.openrt.de/ I saw this a while back, havent checked it out in a while.

http://blogs.intel.com/research/2007/10/real_time_raytracing_the_end_o.php


nice article on the benefits of raytracing in case anyone was actually interested.

I can explain how to remove the fish eye effect.
In fact, the Fish-eye effect is caused when your raycasting algorithm is not correct:
In a tradionnal fish-eye raycaster, you basically setup a field-of-view (FOV), say 90 degrees, and you then cast one ray per screen column, say 320, with a calculated delta degree, 90 / 320 = 0.28125 degree per column.

That's not the right way: it will always give a 'fish eye' effect, more visible if the FOV is large. You can try to do a 'fish eye correction' with some acos() stuff but it doesn't work.

I might sketch up something to demonstrate the right way I'm talking about..

There we go, I wiped up some pics, hope that helps!
This is the traditional 'fish eye' raytracing:


and here's the correct way to do it:


I'm not entering the competition, but this is a snippet I came up with a while ago. It uses 'cheating math' because I was too bored to actually read a tutorial, so I came up with my own ways. It is a 3d raycaster not a 2.5d.

SuperStrict; AppTitle = "Raytracer"
Const GfxWidth:Int = 200,GfxHeight:Int = 200,PlanetCount:Int = 3,CameraSize:Float=10
Const CameraWidth:Float = Float(GfxWidth)/Float(GfxHeight)*CameraSize; Const CameraHeight:Float = CameraSize
Const CameraMax:Float = 5.0,RayStep:Float=0.25; Const Spheres:Int=5
Graphics GfxWidth,GfxHeight,0
Type Point
	Field X:Float,Y:Float,Z:Float
End Type
Type Sphere Extends Point
	Field Radius:Float
	Field R:Int,G:Int,B:Int
	Global List:TList
	Method New()
		ListAddLast(List,Self)
	End Method
End Type
Sphere.List = CreateList()

Local Buffer:TPixmap = CreatePixmap(GfxWidth,GfxHeight,PF_RGBA8888); Buffer.ClearPixels(0)
Local DrawFPS:Int,FPSCounter:Int,FPSTimer:Int,Camera:Point = New Point; Camera.Z = -10

For Local t:Int = 1 To Spheres
	Local S:Sphere = New Sphere
	S.X = Rnd(-10,10); S.Y = Rnd(-10,10); S.Z = Rnd(-10,10); S.Radius = Rnd(1,5); S.R = Rand(0,255); S.G = Rand(0,255); S.B = Rand(0,255)
Next

Repeat
	If KeyDown(KEY_LEFT) Then Camera.X:-3
	If KeyDown(KEY_RIGHT) Then Camera.X:+3
	If KeyDown(KEY_UP) Then Camera.Z:+1
	If KeyDown(KEY_DOWN) Then Camera.Z:-1

	Buffer.ClearPixels(0)
	For Local y:Int = 0 To GfxHeight-1
		For Local x:Int = 0 To GfxWidth-1
			Local Ray:Point = New Point
			Ray.X = Camera.X+((x-GfxWidth*0.5)/GfxWidth)*CameraWidth
			Ray.Y = Camera.Y+((y-GfxHeight*0.5)/GfxHeight)*CameraHeight
			Ray.Z = Camera.Z
			Local S:Sphere
			Local StepX:Float = ((x-GfxWidth*0.5)/GfxWidth)*CameraWidth*0.05
			Local StepY:Float = ((y-GfxHeight*0.5)/GfxHeight)*CameraHeight*0.05
			Local X1:Float,Y1:Float,Z1:Float,FoundS:Sphere,StartZ:Float=Ray.Z
			S = Null
			Repeat
				Ray.X:+StepX
				Ray.Y:+StepY
				Ray.Z = Ray.Z+RayStep
				For Local FS:Sphere = EachIn Sphere.List
					X1 = Ray.X-FS.X; Y1 = Ray.Y-FS.Y; Z1 = Ray.Z-FS.Z
					If Sqr(X1*X1+Y1*Y1+Z1*Z1) <= FS.Radius Then
						S = FS
						Exit
					EndIf
				Next
			Until S<>Null Or Ray.Z>CameraMax
			Local R:Int = S.R-10*(Ray.Z-StartZ); If R<0 Then R=0
			Local G:Int = S.G-10*(Ray.Z-StartZ); If G<0 Then G=0
			Local B:Int = S.B-10*(Ray.Z-StartZ); If B<0 Then B=0
			If S<>Null Then Buffer.WritePixel(x,y,(R Shl 16) | (G Shl 8) | B)
		Next
	Next	

	DrawPixmap Buffer,0,0; FPSCounter:+1
	If MilliSecs()>FPSTimer+999 Then
		FPSTimer = MilliSecs(); DrawFPS = FPSCounter; FPSCounter = 0
	EndIf
	DrawText "FPS: "+DrawFPS,0,0
	Flip
	'Cls
Until KeyHit(KEY_ESCAPE)


Nawi, I get a whopping 2 FPS on my 4-year-old 1.5 GHz laptop :)

And for some reason I get a runtime error for accessing a null object when compiling in debug mode.

Allow me to restate what Remz said in an easier to grasp manner:

If your screen is 640 pixels wide, and your FOV is 90, instead of casting one ray every 0.140625 degrees, what you need to do is imagine that the screen is suspended in front of the player, and cast the rays through the individual pixels in that screen.

If you do that, then the change in angle from one ray to the next will be greatest near the middle of the screen, and less towards the edges.


----

Now that you understand that...


Calculating the correct angle for each pixel is fairly simple if you know the rules dealing with how the lengths of the sides of a right triangle (a triangle with one corner that is 90 degrees) relates to the angles.

And those rules are:

SohCahToa

or

Sine = Opposite/Hypoteneuse
Cosine = Adjacent/Hypotenuse
Tangent = Opposite/Adjacent

Let's use the first equation as an example:

Sine = Opposite/Hypoteneuse

This means that the Sine of an angle (Which is a number you can convert into an angle) equals the length of the side across from it, divided by the hypoteneuse, which is the side across from the 90 degree corner.

(The adjacent side in the other equations is the side touching the corner you're trying to find the angle of.)

In other words:

|\
|_\

The side at an angle is the hypoteneuse. The side on the left and the side on the bottom could either be the opposite or the adjacent, depending on which corner you're trying to find the angle of.


Now let's flip that triangle over and get to calculating our angle:

``/
|/
P

P is our player. That corner there is the one we're trying to find the angle of.

H is the length of our hypoteneuse on the right. We don't know H.

A is the length of our adjacent side. For this example, that would be the left side. It is the side which is not the hypoteneuese which is touching the angle that we want to find. We do know the length of this side. Here, it is 1.

O is the length of our opposite side. This is the length of the top side, which is opposite the corner at the bottom of the triangle. We also know the length of this. Here it is 2, but this length will vary. It will be 0.5 for the centers of the first two columns of the screen to the left and right of the center of the screen which is between two pixels. And it will increase by 1 each pixel away from the center two pixels.

So, which of the three euqations will let us compute our angle with opposite and adjacent?

This one!

Tangent = Opposite/Adjacent

So we could just plug in our values into this and get the tangent of our angle, but we want an actual angle. To get that, you pass that value into a function called ArcTan.

So:
Angle = ArcTan(Opposite/Adjacent)

You can also use the ArcTan2 function, like so:

Angle = ArcTan2(Opposite, Adjacent)

ArcTan2 exists beccause adjacent approaches 0 as the angle approaches 90 degrees, and ArcTan2 can use the two values it is passed to compensate for this. (An adjacent approaching 0 causes the value passed to the ArcTan function to approach infinity if you just do the division method.)

You won't be going more than 45 degrees in either direction from center, and your adjacent length will always be 1, so this won't be an issue for you, but if you ever need to calculate the angle an object is at in relation to another object, ArcTan2 is your function.

I don't know which is faster, but I personally always use ArcTan2.

And so that's it. Now you have your angle, and you can use that to cast your ray.


Now, you might think "I don't need to do any of that angle stuff! I've gone as far as calculating the opposite and adjacent for a specific pixel, and I therefore know the X and Y of that pixel. And that means I know the endpoint of a very short vector that travels in exactly the same direction that I want my ray to go in. And that's all I need for my infinite ray!"


But that will only work if you can't turn in place at all.

So you do need the angles. You need to calculate the angle for each column, and store that in an array at the start of your program for quick access. Then at runtime, you add the current view angle to the angle for each column, and calculate your vector for your ray like so:

X = Radius * Cos(Angle)
Y = Radius * Sin(Angle)

You can just assume a radius of 1 and exclude that from the equations.

And that gives you your ray vectors, with no fisheye effect.

Ta da!

found this article on tinternet

http://student.kuleuven.be/~m0216922/CG/raycasting.html

and to help me make sense of it I blitzified it.. and added a topdown map view. Its quite messy code still and I have only taken the first part where the walls are just plain colors but it works.
Now I need to mess around to get my head around exactly what it does !
Anyway. some might find it useful. Not really all my code so I cant really enter it into the

Keys: cursor to move,q to quit

Edit : This version calcs the xpos (0-1) on the cell that the ray has hit. The 2d view draws all the rays now.
SuperStrict

' Based on code by Lode Vandevenne
' Original totorial and source code
'can be found at : <a href="http://student.kuleuven.be/~m0216922/CG/raycasting.html" target="_blank">http://student.kuleuven.be/~m0216922/CG/raycasting.html</a>

Global world:TWorld

main()
End

Function main()
	Local player:TPlayer

	world = New TWorld
	player = New TPlayer

	Graphics 640,400,0

	While Not KeyHit(KEY_Q)
		Cls
		player.Update()
		SetOrigin 0,0
		SetColor 100,100,100
		DrawLine 320,0,320,400
		DrawLine 0,200,320,200
		world.Draw()
		player.Draw()

		Local x:Int
		Local cameraX:Double
		Local rayPosX:Double
		Local rayPosY:Double
		Local rayDirX:Double
		Local rayDirY:Double

		For x=0 To 320

			' calculate ray position And direction 
			cameraX = -2.0 * Double(x) / 320.0 + 1.0 ' x-coordinate in camera space
			rayPosX = player.xPos
			rayPosY = player.yPos
			rayDirX = player.direction.x + player.camera.x * cameraX
			rayDirY = player.direction.y + player.camera.y * cameraX

			' which box of the map we're in  
			Local  mapX:Int = rayPosX
			Local mapY:Int = rayPosY

			' length of ray from current position To Next x Or y-side
			Local sideDistX:Double
			Local sideDistY:Double

			' length of ray from one x Or y-side To Next x Or y-side
			Local deltaDistX:Double = Sqr(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX))
			Local  deltaDistY:Double = Sqr(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY))
			Local perpWallDist:Double
       
			' what direction To Step in x Or y-direction (either +1 Or -1)
			Local stepX:Int
			Local stepY:Int

			Local hit:Int = 0 ' was there a wall hit?
			Local side:Int ' was a NS Or a EW wall hit?

			' calculate Step And initial sideDist
			If rayDirX < 0 Then
				stepX = -1
				sideDistX = (rayPosX - mapX) * deltaDistX
			Else
				stepX = 1
				sideDistX = (mapX + 1.0 - rayPosX) * deltaDistX
			End If

			If rayDirY < 0 Then
				stepY = -1
				sideDistY = (rayPosY - mapY) * deltaDistY;
			Else
				stepY = 1;
				sideDistY = (mapY + 1.0 - rayPosY) * deltaDistY;
			End If

			' perform DDA
			While hit = 0

				' jump To Next map square, Or in x-direction, Or in y-direction
				If sideDistX < sideDistY Then
					sideDistX :+ deltaDistX
					mapX :+ stepX
					side = 0
				Else
					sideDistY :+ deltaDistY
					mapY :+ stepY
					side = 1
				End If

				' Check If ray has hit a wall
				If world.cell[mapX,mapY] > 0 Then
					hit = 1
				End If
			Wend
			SetColor 255,255,0
			SetOrigin 320+12,0+12

			' calculate value of wallX
			Local wallX:Double  ' where exactly the wall was hit
			If side = 1 Then 
				wallX = rayPosX + ((mapY - rayPosY + (1.0 - stepY) / 2.0) / rayDirY) * rayDirX
			Else
				wallX = rayPosY + ((mapX - rayPosX + (1.0 - stepX) / 2.0) / rayDirX) * rayDirY
			End If

			wallX :- Floor((wallX));

			' Draw the ray for each screen column we are drawing
			Local drawMapX:Double
			Local drawMapY:Double

			drawMapX = mapX
			drawMapY = mapy
			SetColor 255,255,0
			If side = 1 Then
				If player.yPos > drawMapY Then
					drawMapY :+ 1
				End If			
				drawMapX = drawMapX + wallX
			Else
				If player.xPos > drawMapX Then
					drawMapX :+ 1
				End If
				drawMapY = drawMapY + wallX
			End If
			SetColor 0,255,255
			DrawLine player.xPos*12.0,player.yPos*12.0,drawMapX*12.0,drawMapY*12.0

			SetOrigin 0,0

			' Calculate distance projected on camera direction (oblique distance will give fisheye effect!)
			If side = 0
				perpWallDist = Abs((mapX - rayPosX + (1 - stepX) / 2) / rayDirX)
			Else
				perpWallDist = Abs((mapY - rayPosY + (1 - stepY) / 2) / rayDirY)
      		End If

			'Calculate height of line To draw on screen
			Local lineHeight:Int  = Abs(Int(200 / perpWallDist))
       
			' calculate lowest And highest pixel To fill in current stripe
			Local drawStart:Int = -lineHeight / 2 + 200 / 2

			If drawStart < 0 Then
				drawStart = 0
			End If

			Local drawEnd:Int = lineHeight / 2 + 200 / 2;
			If drawEnd >= 200 Then 
				drawEnd = 200 - 1
			End If
        
			' choose wall color
			Local colR:Int
			Local colG:Int
			Local colB:Int
			
			Select world.cell[mapX,mapY]
					Case world.CELL_OUTER_WALL
						colR = 200
						colG = 200
						colB = 200
					Case world.CELL_INNER_WALL1
						colR = 200
						colG = 100
						colB = 100
					Case world.CELL_OUTER_COLUMN
						colR = 200
						colG = 0
						colB = 200 
					Case world.CELL_INNER_WALL2
						colR = 100
						colG = 100
						colB = 200
					Case world.CELL_EXIT
						colR = 100
						colG = 200
						colB = 100
			End Select
       
			' give x And y sides different brightness
			If side = 1 Then 
				colR :/ 2
				colG :/ 2
				colB :/ 2
			End If

			SetColor colR, colG, colB

'      //draw the pixels of the stripe as a vertical line
      DrawLine x, drawStart, x, drawEnd
 

		Next

		Flip
	Wend
End Function

Type TVector
	Field x:Float
	Field y:Float
End Type

Type Tplayer
	Field xPos:Float
	Field yPos:Float
	Field direction:TVector
	Field camera:TVector
	Const moveSpeed:Float= 5.0/30.0
	Const rotSpeed:Float = 60.0 / 30.0
	
	Method New()
		direction = New TVector
		camera = New TVector
		xPos = 22
		yPos = 12
		direction.x = -1
		direction.y = 0
		camera.x = 0
		camera.y = .66
	End Method

	Method Draw()
		' draw player
		SetColor 255,0,0
		DrawLine xPos*12-6,yPos*12-6,xPos*12+6,yPos*12+6
		DrawLine xPos*12-6,yPos*12+6,xPos*12+6,yPos*12-6
		' draw direction
		SetColor 255,255,255
		DrawLine xPos*12,yPos*12,xPos*12+direction.x*12,yPos*12+direction.y*12
		' drawcamera
		DrawLine xPos*12+direction.x*12, ..
			yPos*12+direction.y*12, ..
			xPos*12+direction.x*12 + camera.x*12, ..		
			yPos*12+direction.y*12 + camera.y*12		
		DrawLine xPos*12+direction.x*12, ..
			yPos*12+direction.y*12, ..
			xPos*12+direction.x*12 - camera.x*12, ..		
			yPos*12+direction.y*12 - camera.y*12
		' draw end rays
'		SetColor 0,200,0
'		Local endX:Float
'		Local endY:Float
'
'		endX = (direction.x + camera.x) * 25
'		endY = (direction.y + camera.y) * 25
'		DrawLine xPos*12,yPos*12,xPos*12+endX*12, yPos*12+endY * 12		
'		endX = (direction.x - camera.x) * 25
'		endY = (direction.y - camera.y) * 25
'		DrawLine xPos*12,yPos*12,xPos*12+endX*12, yPos*12+endY * 12		
	End Method

	Method Update()
		' move Forwards
		If KeyDown(Key_UP)
			If world.cell[(xPos + direction.x * moveSpeed),Int(yPos)] = 0 Then
				xPos :+ direction.x * moveSpeed
			End If
			If world.cell[(xPos),Int(yPos + direction.y * moveSpeed)] = 0 Then 
				yPos :+ direction.y * moveSpeed
			End If
		End If

		' move Backwards
		If KeyDown(Key_DOWN)
			If world.cell[(xPos - direction.x * moveSpeed),Int(yPos)] = 0 Then
				xPos :- direction.x * moveSpeed
			End If
			If world.cell[(xPos),Int(yPos - direction.y * moveSpeed)] = 0 Then 
				yPos :- direction.y * moveSpeed
			End If
		End If

		' rotate left
		If KeyDown(KEY_LEFT)
			Local temp:Float
		
			temp = direction.x
			direction.x = direction.x * Cos(-rotSpeed) - direction.y * Sin(-rotSpeed)
			direction.y = temp*Sin(-rotSpeed) + direction.y * Cos(-rotSpeed)
			temp = camera.x
			camera.x = camera.x * Cos(-rotSpeed) - camera.y * Sin(-rotSpeed)
			camera.y = temp*Sin(-rotSpeed) + camera.y * Cos(-rotSpeed)
		End If

		' rotate right
		If KeyDown(KEY_RIGHT)
			Local temp:Float
		
			temp = direction.x
			direction.x = direction.x * Cos(rotSpeed) - direction.y * Sin(rotSpeed)
			direction.y = temp*Sin(rotSpeed) + direction.y * Cos(rotSpeed)
			temp = camera.x
			camera.x = camera.x * Cos(rotSpeed) - camera.y * Sin(rotSpeed)
			camera.y = temp*Sin(rotSpeed) + camera.y * Cos(rotSpeed)
		End If

	End Method

End Type

Type TWorld
	Field width:Int
	Field height:Int
	Field cell:Int[,]

	Const CELL_EMPTY:Int = 0
	Const CELL_OUTER_WALL:Int = 1
	Const CELL_INNER_WALL1:Int = 2
	Const CELL_OUTER_COLUMN:Int = 3
	Const CELL_INNER_WALL2:Int = 4
	Const CELL_EXIT:Int = 5

	Method New()
		RestoreData WorldData

		ReadData width,height
		cell = New Int[width,height]

		Local x:Int
		Local y:Int

		For y = 0 Until height
			For x = 0 Until width
				ReadData cell[x,y]
			Next
		Next
	End Method

	Method Draw()
		Local x:Int
		Local y:Int
		
		SetOrigin 320+12,0+12

		For y = 0 Until height
			For x = 0 Until width
				Select cell[x,y]
					Case CELL_EMPTY
						SetColor 0,0,0
					Case CELL_OUTER_WALL
						SetColor 200,200,200
					Case CELL_INNER_WALL1
						SetColor 200,100,100
					Case CELL_OUTER_COLUMN
						SetColor 200,0,200 
					Case CELL_INNER_WALL2
						SetColor 100,100,200
					Case CELL_EXIT
						SetColor 100,200,100
				End Select

				DrawRect x*12,y*12,11,11
			Next
		Next
	End Method
End Type

#WorldData
DefData 24,24
DefData 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
DefData 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1
DefData 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1
DefData 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1
DefData 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
DefData 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1


@QuietBloke - Good find. That's a pretty fast raycaster. If it had textured walls with floor and ceiling it would stellar.

It would be impressive to add HDR lighting and depth of field to a raycaster. We'll seeeee :)