Ascii FPS Ratracing
From https://www.youtube.com/watch?v=xW8skO7MFYw
This example will use an ascii terminal to render the ratracing algorithm for 2D projected into 3D.
I studied this (Wolfenstein) logic while trying to recreate the Doom engine in Python.
I have removed the portions for how to render to a console directly, cell by cell.
pX = 8
pY = 8
pAngle = 0
pFoV = pi/4
mapW = 16
mapH = 16
map = [...] # walls are hashes '#', and floor is '.'
screenW = 120
screenH = 40
maxDepth = 16
while gameLoop
# pseudo input stuff i can do later
if turnLeft
pAngle -= turnAngle
if turnRight
...
foreach xcol on screen:
# - half fov on left + half vov on right
rayAngle = (pAngle - pFoV / 2) + (xcol / screenW) * pFoV
# this is expensive and where the raytacing occurs
distToWall = 0
# unit vector to repesent way player is looking
eyeX = sin(rayAngle)
eyeY = cos(rayAngle)
while !hitwall and distToWall < maxDepth:
distToWall += .01
testX = pX + eyeX * distToWall # you can cast these to int to test against a tile in world space
testY = pY + eyeY * distToWall # ..
# test ray is out of bounds of map
if testX < 0 or testX > mapW or testY < 0 or testY > mapH:
hitWall = true
distToWAll = depth
# ray in bounds test to see if at wall
else
# if our cell is a wall
if map[ testY * mapW + testX] == '#':
hitWall = true
# calculate distance to ceiling and floor (vertical scren height)
# as distance get larger, subtraction gets smaller so it gets taller
#
# using screenH / distToWall means that when the distance is equal
# to screenHeight it will be on unit (pixel or character depending
# on how you display)
# This is a bit arbitrary but I would think we would want to define
# a different vanishing point and get our ratio of wallDistance to
# vanishingPoint, then set that ratio against the screenHeight
ceiling = (screenH / 2) - screenH / distToWall
floor = screenH - ceiling
# draw into screen column
for y in screenH
# if we are above our ceiling draw nothing
if y < ceiling
draw(xCol, y, ' ')
# if we are between ciling and floor draw wall
else if y < ceiling and y > floor
# draw the wall
draw(xCol, y, 'W')
# VERSION 2 - shade based on distance
shadeChar = getShadeBasedOnDist(distToWall) # basically if elses based on rules on how far
draw(xCol, y, shadeChar)
# if below floor so draw nothing
else
draw(xCol, y, ' ')
In regards to our Doom engine we are not raytracing from screen X coords to distant walls
Instead we have divided the game into lines and use a BSP to figure out which walls to render
Those walls are chosen based on your position and don't care about camera rotation or FoV etc
Because of this we need to be able to determine if a wall is within the view of the camera
If it is then we need to determine where our view intersects with that wall and render it
If we know we are rendering a wall facing the camera
- We get the left intersection
- This intersection point will render at some X coordinate on the screen based on the X and Y of the position in the camra
- This is also where I get tripped up because that X coord wants to be projected with linear algebra
- Because in this raytracing example we already know which X coord we are rendering and finding the wall segment with a ray
- In the doom one we are trying to reverse this by taking a wall position and reverse finding the X coord
- If the camera was facing directly up or down (in 2d) then it would be a straight X world to X screen coord
- But as the camera rotates the Y and X must be combined to map to a screen X coord
I think this tutorial can span that knowledge gap: https://medium.com/@btco_code/writing-a-retro-3d-fps-engine-from-scratch-b2a9723e6b06
It talks about projection of wall coords to screen coords and I think it can be redone