
To see how the final example will look, check out the flash version at http://www.furi.dk/raycaster
Note: Code is for 4.30 version of PureBasic (latest version atm)
Code: Select all
; Raycasting example 1 (Solid colored walls only)
EnableExplicit
; Some general stuff
Global mapWidth.l = 24
Global mapHeight.l = 24
Global screenWidth.l = 640
Global screenHeight.l = 480
; Map container
Global Dim worldMap.b(mapWidth, mapHeight)
; Declare render funcs
Declare RenderSolidWalls()
; Read map
Global x.l = 0
Global y.l = 0
For y.l = 0 To mapHeight-1
For x.l = 0 To mapWidth-1
Read.b worldMap(x, y)
Next x
Next y
; Open window screen + init keyboard/graphics screen (No error checking as this is an example of raycasting)
InitKeyboard()
InitSprite()
OpenWindow(0, 0, 0, screenWidth, screenHeight, "Raycast Example #1", #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget)
OpenWindowedScreen(WindowID(0), 0, 0, screenWidth, screenHeight, 0, 0, 0)
; Start position
Global posX.f = 22
Global posY.f = 12
; Initial direction vector
Global dirX.f = -1
Global dirY.f = 0
; 2D camera plane
Global planeX.f = 0
Global planeY.f = 0.66
; Frame timing
Global time.f = 0
Global oldTime.f = 0
; Main loop
Repeat
; Pump window events
Global Event.l = WindowEvent()
; Start rendering
StartDrawing(ScreenOutput())
; Render solid walls
RenderSolidWalls()
; End rendering
StopDrawing()
; Flip buffers
FlipBuffers()
; Check keyboard + timed movement
ExamineKeyboard()
; Calculate frame timing
oldTime = time;
time = ElapsedMilliseconds();
Global frameTime.f = (time - oldTime) / 1000.0
; Set FPS in title bar (wonky in PB. Dunno why. Works in C++/Flash version ;)
SetWindowTitle(0, "Raycast Example #1 (FPS: " + Str(Int(1.0 / frameTime)) + ")")
; Calculate speed modifiers (constant values are squares/second and radians/second)
Global moveSpeed.f = frameTime * 5.0
Global rotSpeed.f = frameTime * 3.0
; Move forward?
If KeyboardPushed(#PB_Key_Up)
If worldMap(Int(posX + dirX * moveSpeed), Int(posY)) = 0
posX = posX + (dirX * moveSpeed)
EndIf
If worldMap(Int(posX), Int(posY + dirY * moveSpeed)) = 0
posY = posY + (dirY * moveSpeed)
EndIf
EndIf
; Move back?
If KeyboardPushed(#PB_Key_Down)
If worldMap(Int(posX - dirX * moveSpeed),Int(posY)) = 0
posX = posX - (dirX * moveSpeed)
EndIf
If worldMap(Int(posX),Int(posY - dirY * moveSpeed)) = 0
posY = posY - (dirY * moveSpeed)
EndIf
EndIf
; Rotate right? (Both camera and camera plane needs to be rotated)
If KeyboardPushed(#PB_Key_Right)
Global oldDirX.f = dirX
dirX = dirX * Cos(-rotSpeed) - dirY * Sin(-rotSpeed)
dirY = oldDirX * Sin(-rotSpeed) + dirY * Cos(-rotSpeed)
Global oldPlaneX.f = planeX
planeX = planeX * Cos(-rotSpeed) - planeY * Sin(-rotSpeed)
planeY = oldPlaneX * Sin(-rotSpeed) + planeY * Cos(-rotSpeed)
EndIf
; Rotate left? (Both camera and camera plane needs to be rotated)
If KeyboardPushed(#PB_Key_Left)
Global oldDirX.f = dirX
dirX = dirX * Cos(rotSpeed) - dirY * Sin(rotSpeed)
dirY = oldDirX * Sin(rotSpeed) + dirY * Cos(rotSpeed)
Global oldPlaneX.f = planeX
planeX = planeX * Cos(rotSpeed) - planeY * Sin(rotSpeed)
planeY = oldPlaneX * Sin(rotSpeed) + planeY * Cos(rotSpeed)
EndIf
Until Event = #PB_Event_CloseWindow Or KeyboardPushed(#PB_Key_Escape)
; Render solid walls
Procedure RenderSolidWalls()
; Misc vars
Protected x.l = 0
; Draw some boxes
Box(0,0,screenWidth, screenHeight/2, RGB(10,10,10))
Box(0,screenHeight/2,screenWidth,screenHeight/2 , RGB(50,50,50))
; Render wall strips for each vertical screen row (640 in this case. 320 in good old Wolfenstein)
For x.l = 0 To screenWidth
; Calculate ray position and direction
Protected cameraX.f = 2 * x / screenWidth-1
Protected rayPosX.f = posX
Protected rayPosY.f = posY
Protected rayDirX.f = dirX + planeX * cameraX
Protected rayDirY.f = dirY + planeY * cameraX
; Determine map tile we're in
Protected mapX.l = Int(rayPosX)
Protected mapY.l = Int(rayPosY)
; Length of ray from current position to next x or y-side
Protected sideDistX.f = 0
Protected sideDistY.f = 0
; Length of ray from one x or y-side to next x or y-side
Protected deltaDistX.f = Sqr(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX))
Protected deltaDistY.f = Sqr(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY))
Protected perpWallDist.f
; What direction to step in x or y-direction (either +1 or -1)
Protected stepX.l
Protected stepY.l
; Was a wall hit, and if so which side
Protected hit.l = 0
Protected side.l
; Calculate step and initial sideDist
If rayDirX < 0
stepX = -1
sideDistX = (rayPosX - mapX) * deltaDistX
Else
stepX = 1
sideDistX = (mapX + 1.0 - rayPosX) * deltaDistX
EndIf
If (rayDirY < 0)
stepY = -1
sideDistY = (rayPosY - mapY) * deltaDistY
Else
stepY = 1
sideDistY = (mapY + 1.0 - rayPosY) * deltaDistY
EndIf
; Perform DDA algorithm for true hit detection
While hit = 0
; Jump to next map square depending on direction
If sideDistX < sideDistY
sideDistX = sideDistX + deltaDistX
mapX = mapX + stepX
side = 0
Else
sideDistY = sideDistY + deltaDistY
mapY = mapY + stepY
side = 1
EndIf
; See if ray has hit a wall
If worldMap(mapX, mapY) > 0
hit = 1
EndIf
Wend
; Calculate distance projected onto camera direction (includes fisheye removal)
If side = 0
perpWallDist = Abs((mapX - rayPosX + (1 - stepX) / 2) / rayDirX)
Else
perpWallDist = Abs((mapY - rayPosY + (1 - stepY) / 2) / rayDirY)
EndIf
; Calculate height of line to draw onto screen
Protected lineHeight.l = Abs(Int(screenHeight / perpWallDist))
; Calculate lowest and highest pixel for current line strip
Protected drawStart.l = -lineHeight / 2 + (screenHeight / 2)
If drawStart < 0
drawStart = 0
EndIf
Protected drawEnd.l = lineHeight / 2 + (screenHeight / 2)
If drawEnd >= screenHeight
drawEnd = screenHeight - 1
EndIf
; Color to based on current wall type
Protected color.l
Select worldMap(mapX, mapY)
Case 1
color = RGB(255,0,0) ; Red
Case 2
color = RGB(0,255,0) ; Green
Case 3
color = RGB(0,0,255) ; Blue
Case 4
color = RGB(255,255,255) ; White
Default
color = RGB(255,255,0) ; Yellow
EndSelect
; Shade color based on wall side hit
If side = 1
color = (color >> 1) & 8355711;
EndIf
; Draw line
LineXY(x, drawStart, x, drawEnd, color)
Next
EndProcedure
; Map data
DataSection
Data.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1
Data.b 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1
Data.b 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
EndDataSection
A couple of notes: No floor function? WTF? Also gave up on direct pixel access. Plot is pretty optimized tho, so it runs quite well in non-debug

Later when I've done floor, cieling + objects I'll have a go at optimizing it.
Also shows simple texture generation pattern. (bitwise or pattern)
Code: Select all
; Raycasting example 2 (Textured walls)
EnableExplicit
; Some general stuff
Global mapWidth.l = 24
Global mapHeight.l = 24
Global screenWidth.l = 640
Global screenHeight.l = 480
Global texWidth.l = 64
Global texHeight.l = 64
; Map container
Global Dim worldMap.b(mapWidth, mapHeight)
; Texture
Global Dim tex.l(texWidth,texWidth)
; Declare render funcs
Declare RenderTexturedWalls()
; Read map
Global x.l = 0
Global y.l = 0
For y.l = 0 To mapHeight-1
For x.l = 0 To mapWidth-1
Read.b worldMap(x, y)
Next x
Next y
; Open window screen + init keyboard/graphics screen (No error checking as this is an example of raycasting)
InitKeyboard()
InitSprite()
OpenWindow(0, 0, 0, screenWidth, screenHeight, "Raycast Example #2", #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget)
OpenWindowedScreen(WindowID(0), 0, 0, screenWidth, screenHeight, 0, 0, 0)
; Create texture image using bitwise or (|) texture generation
; Just a dim for now, since I gave up on the direct pixel access for now :)
Global tx.l = 0
Global ty.l = 0
For ty = 0 To texWidth-1
For tx = 0 To texWidth-1
Global temp.b = (tx | ty) * 3 ; Times 3 for brighter texture :)
tex(tx,ty) = RGB(temp,temp,temp)
Next
Next
; Start position
Global posX.f = 22
Global posY.f = 12
; Initial direction vector
Global dirX.f = -1
Global dirY.f = 0
; 2D camera plane
Global planeX.f = 0
Global planeY.f = 0.66
; Frame timing
Global time.f = 0
Global oldTime.f = 0
; Main loop
Repeat
; Pump window events
Global Event.l = WindowEvent()
; Start rendering
StartDrawing(ScreenOutput())
; Render textured walls
RenderTexturedWalls()
; End rendering
StopDrawing()
; Flip buffers
FlipBuffers()
; Check keyboard + timed movement
ExamineKeyboard()
; Calculate frame timing
oldTime = time;
time = ElapsedMilliseconds();
Global frameTime.f = (time - oldTime) / 1000.0
; Set FPS in title bar (wonky in PB. Dunno why. Works in C++/Flash version ;)
SetWindowTitle(0, "Raycast Example #1 (FPS: " + Str(Int(1.0 / frameTime)) + ")")
; Calculate speed modifiers (constant values are squares/second and radians/second)
Global moveSpeed.f = frameTime * 5.0
Global rotSpeed.f = frameTime * 3.0
; Move forward?
If KeyboardPushed(#PB_Key_Up)
If worldMap(Int(posX + dirX * moveSpeed), Int(posY)) = 0
posX = posX + (dirX * moveSpeed)
EndIf
If worldMap(Int(posX), Int(posY + dirY * moveSpeed)) = 0
posY = posY + (dirY * moveSpeed)
EndIf
EndIf
; Move back?
If KeyboardPushed(#PB_Key_Down)
If worldMap(Int(posX - dirX * moveSpeed),Int(posY)) = 0
posX = posX - (dirX * moveSpeed)
EndIf
If worldMap(Int(posX),Int(posY - dirY * moveSpeed)) = 0
posY = posY - (dirY * moveSpeed)
EndIf
EndIf
; Rotate right? (Both camera and camera plane needs to be rotated)
If KeyboardPushed(#PB_Key_Right)
Global oldDirX.f = dirX
dirX = dirX * Cos(-rotSpeed) - dirY * Sin(-rotSpeed)
dirY = oldDirX * Sin(-rotSpeed) + dirY * Cos(-rotSpeed)
Global oldPlaneX.f = planeX
planeX = planeX * Cos(-rotSpeed) - planeY * Sin(-rotSpeed)
planeY = oldPlaneX * Sin(-rotSpeed) + planeY * Cos(-rotSpeed)
EndIf
; Rotate left? (Both camera and camera plane needs to be rotated)
If KeyboardPushed(#PB_Key_Left)
Global oldDirX.f = dirX
dirX = dirX * Cos(rotSpeed) - dirY * Sin(rotSpeed)
dirY = oldDirX * Sin(rotSpeed) + dirY * Cos(rotSpeed)
Global oldPlaneX.f = planeX
planeX = planeX * Cos(rotSpeed) - planeY * Sin(rotSpeed)
planeY = oldPlaneX * Sin(rotSpeed) + planeY * Cos(rotSpeed)
EndIf
Until Event = #PB_Event_CloseWindow Or KeyboardPushed(#PB_Key_Escape)
; Render textured walls
Procedure RenderTexturedWalls()
; Misc vars
Protected x.l = 0
; Draw some boxes
Box(0,0,screenWidth, screenHeight/2, RGB(10,10,10))
Box(0,screenHeight/2,screenWidth,screenHeight/2 , RGB(50,50,50))
; Render wall strips for each vertical screen row (640 in this case. 320 in good old Wolfenstein)
For x.l = 0 To screenWidth-1
; Calculate ray position and direction
Protected cameraX.f = 2 * x / screenWidth-1
Protected rayPosX.f = posX
Protected rayPosY.f = posY
Protected rayDirX.f = dirX + planeX * cameraX
Protected rayDirY.f = dirY + planeY * cameraX
; Determine map tile we're in
Protected mapX.l = Int(rayPosX)
Protected mapY.l = Int(rayPosY)
; Length of ray from current position to next x or y-side
Protected sideDistX.f = 0
Protected sideDistY.f = 0
; Length of ray from one x or y-side to next x or y-side
Protected deltaDistX.f = Sqr(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX))
Protected deltaDistY.f = Sqr(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY))
Protected perpWallDist.f
; What direction to step in x or y-direction (either +1 or -1)
Protected stepX.l
Protected stepY.l
; Was a wall hit, and if so which side
Protected hit.l = 0
Protected side.l
; Calculate step and initial sideDist
If rayDirX < 0
stepX = -1
sideDistX = (rayPosX - mapX) * deltaDistX
Else
stepX = 1
sideDistX = (mapX + 1.0 - rayPosX) * deltaDistX
EndIf
If (rayDirY < 0)
stepY = -1
sideDistY = (rayPosY - mapY) * deltaDistY
Else
stepY = 1
sideDistY = (mapY + 1.0 - rayPosY) * deltaDistY
EndIf
; Perform DDA algorithm for true hit detection
While hit = 0
; Jump to next map square depending on direction
If sideDistX < sideDistY
sideDistX = sideDistX + deltaDistX
mapX = mapX + stepX
side = 0
Else
sideDistY = sideDistY + deltaDistY
mapY = mapY + stepY
side = 1
EndIf
; See if ray has hit a wall
If worldMap(mapX, mapY) > 0
hit = 1
EndIf
Wend
; Calculate distance projected onto camera direction (includes fisheye removal)
If side = 0
perpWallDist = Abs((mapX - rayPosX + (1 - stepX) / 2) / rayDirX)
Else
perpWallDist = Abs((mapY - rayPosY + (1 - stepY) / 2) / rayDirY)
EndIf
; Calculate height of line to draw onto screen
Protected lineHeight.l = Abs(Int(screenHeight / perpWallDist))
; Calculate lowest and highest pixel for current line strip
Protected drawStart.l = -lineHeight / 2 + (screenHeight / 2)
If drawStart < 0
drawStart = 0
EndIf
Protected drawEnd.l = lineHeight / 2 + (screenHeight / 2)
If drawEnd >= screenHeight
drawEnd = screenHeight - 1
EndIf
; Using plot here. Since plot is optimized it's fine for a tutorial since I gave up on the direct pixel access in PB for now :)
; Determine exact point where wall was hit for texture generation
Protected wallX.f
If side = 1
wallX = rayPosX + ((mapY - rayPosY + (1 - stepY) / 2) / rayDirY) * rayDirX
Else
wallX = rayPosY + ((mapX - rayPosX + (1 - stepX) / 2) / rayDirX) * rayDirY
EndIf
wallX = wallX - Int(wallX); What no floor? Also added abs because of this. Line should be 'wallX = wallX - Floor(wallX)'
wallX = Abs(wallX) ; Should not be here, but since we got no floor :)
; Calculate x coordinate on the texture
Protected texX.l = Int(wallX * texWidth);
If side = 0 And rayDirX > 0
texX = texWidth - texX - 1
EndIf
If side = 1 And rayDirY < 0
texX = texWidth - texX - 1
EndIf
; Render textured strip
Protected y.l = 0
For y = drawStart To drawEnd
Protected d.l = y * 256 - screenHeight * 128 + lineHeight * 128; Avoid floats
Protected texY.l = ((d * texHeight) / lineHeight) / 256;
Protected color.l = tex(texX, texY)
; Again shift to darken color depending on side
If side = 1
color = (color >> 1) & 8355711
EndIf
; Draw pixel
Plot(x,y,color)
Next
Next
EndProcedure
; Map data
DataSection
Data.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1
Data.b 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1
Data.b 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
EndDataSection