Ascii FPS Ratracing
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