Ray casting is a technique in computer graphics to render a 3-D environment from 2-D map data in software, based on perspective projection. Early computer and video games, like Wolfenstein 3-D or Ken’s Labyrinth, use ray casting to create a first-person view. Due to the limitations of the approach, the rendered world is not truely in 3-D, but often seen as 2.5-D instead.
In FORTRAN, an additional library is required to render to the screen.
The EGGX/ProCALL library provides basic drawing
routines for X11. The following example program allows the player to move freely
around in a room made of blocks (fig. 1), with simple collision detection
applied. The map array MAPY
of size MW
,
MH
stores the position and colour of each block. The shade of the
colour is chosen depending on the orientation of a particular wall.
The FORTRAN program calls six subroutines for input control, movement, and rendering:
CALL INPUT(DONE)
IKEY
.CALL MOVE(SPEED)
DX
, DY
by
SPEED
.CALL RAYCST(IX)
IX
on the camera plane and renders a single
column of pixels in the colour of the wall hit.CALL RENDER()
CALL ROTATE(X, Y, A)
A
.CALL WINDOW()
We can alter the parameters IW
and IH
to
change the size of the X11 window. Use the arrows key for movement. Copy and
save the program as file raycast.f
locally.
C ******************************************************************
C
C SIMPLE 2.5-D RAY CASTING ENGINE FOR X11 IN FORTRAN 77, USING THE
C EGGX/PROCALL LIBRARY.
C
C ******************************************************************
PROGRAM RAYCAST
EXTERNAL GCLOSEALL, INPUT, MSLEEP, RENDER, WINDOW
INTEGER IDELAY
PARAMETER (IDELAY=20)
LOGICAL DONE
CALL WINDOW()
10 CONTINUE
CALL INPUT(DONE)
CALL RENDER()
CALL MSLEEP(IDELAY)
IF (.NOT. DONE) GOTO 10
CALL GCLOSEALL()
END
C ******************************************************************
SUBROUTINE INPUT(DONE)
C
C PROCESS KEYBOARD INPUT AND MOVES/ROTATES PLAYER.
C
EXTERNAL GGETCH, MOVE, ROTATE
INTEGER IDOWN, IESC, ILEFT, IRIGHT, IUP
REAL ALPHA, SPEED
PARAMETER (IDOWN=31, IESC=27, ILEFT=29, IRIGHT=28, IUP=30)
PARAMETER (ALPHA=0.1, SPEED=0.15)
REAL DX, DY, PLANEX, PLANEY, POSX, POSY
COMMON /VEC/ DX, DY, PLANEX, PLANEY, POSX, POSY
LOGICAL DONE
INTEGER IKEY
DONE = .FALSE.
CALL GGETCH(IKEY)
IF (IKEY .EQ. IESC) THEN
C QUIT.
DONE = .TRUE.
ELSE IF (IKEY .EQ. IUP) THEN
C MOVE FORWARD.
CALL MOVE(SPEED)
ELSE IF (IKEY .EQ. IDOWN) THEN
C MOVE BACKWARD.
CALL MOVE(-SPEED)
ELSE IF (IKEY .EQ. ILEFT) THEN
C TURN LEFT.
CALL ROTATE(DX, DY, ALPHA)
CALL ROTATE(PLANEX, PLANEY, ALPHA)
ELSE IF (IKEY .EQ. IRIGHT) THEN
C TURN RIGHT.
CALL ROTATE(DX, DY, -ALPHA)
CALL ROTATE(PLANEX, PLANEY, -ALPHA)
END IF
END
C ******************************************************************
SUBROUTINE MOVE(SPEED)
C
C MOVES PLAYER IN DX, DY.
C
REAL SPEED
INTEGER MAPY(12, 12), MW, MH
REAL DX, DY, PLANEX, PLANEY, POSX, POSY
COMMON /MAP/ MAPY, MW, MH
COMMON /VEC/ DX, DY, PLANEX, PLANEY, POSX, POSY
REAL NX, NY
NX = POSX + DX * SPEED
NY = POSY + DY * SPEED
IF (NX .LE. 1 .OR. NX .GE. MW .OR.
& NY .LE. 1 .OR. NY .GE. MH) RETURN
IF (MAPY(INT(NX) + 1, INT(POSY) + 1) .EQ. 0) POSX = NX
IF (MAPY(INT(POSX) + 1, INT(NY) + 1) .EQ. 0) POSY = NY
END
C ******************************************************************
SUBROUTINE RAYCST(IX)
C
C RENDERS SINGLE COLUMN OF PIXELS AT IX.
C
EXTERNAL DRAWLINE, NEWPENCOLOR
INTEGER IX
INTEGER IWIN, IW, IH, MAPY(12, 12), MW, MH
REAL DX, DY, PLANEX, PLANEY, POSX, POSY
COMMON /WIN/ IWIN, IW, IH
COMMON /MAP/ MAPY, MW, MH
COMMON /VEC/ DX, DY, PLANEX, PLANEY, POSX, POSY
INTEGER ISIDE, ISTEPX, ISTEPY, IWALL, LENGTH, MX, MY, IH2, IW2
LOGICAL DONE
REAL CAMX, DIST, DDISTX, DDISTY, RAYX, RAYY
REAL SIDEX, SIDEY, X, Y1, Y2
IW2 = IW / 2
IH2 = IH / 2
CAMX = 2 * IX / REAL(IW) - 1
RAYX = DX + PLANEX * CAMX
RAYY = DY + PLANEY * CAMX
MX = INT(POSX)
MY = INT(POSY)
DDISTX = ABS(1 / RAYX)
DDISTY = ABS(1 / RAYY)
IF (RAYX .LT. 0) THEN
ISTEPX = -1
SIDEX = (POSX - MX) * DDISTX
ELSE
ISTEPX = 1
SIDEX = (MX + 1.0 - POSX) * DDISTX
END IF
IF (RAYY .LT. 0) THEN
ISTEPY = -1
SIDEY = (POSY - MY) * DDISTY
ELSE
ISTEPY = 1
SIDEY = (MY + 1.0 - POSY) * DDISTY
END IF
IWALL = 0
DONE = .FALSE.
10 CONTINUE
IF (SIDEX .LT. SIDEY) THEN
SIDEX = SIDEX + DDISTX
MX = MX + ISTEPX
ISIDE = 0
ELSE
SIDEY = SIDEY + DDISTY
MY = MY + ISTEPY
ISIDE = 1
END IF
IF (MAPY(MX + 1, MY + 1) .GT. 0) THEN
IWALL = MAPY(MX + 1, MY + 1)
DONE = .TRUE.
END IF
IF (.NOT. DONE) GOTO 10
IF (IWALL .EQ. 0) RETURN
IF (ISIDE .EQ. 0) THEN
DIST = SIDEX - DDISTX
ELSE
DIST = SIDEY - DDISTY
END IF
IF (ISIDE .EQ. 1) IWALL = IWALL + 8
LENGTH = INT(IH / DIST) / 2
X = REAL(IX)
Y1 = REAL(MAX(0, -LENGTH + IH2))
Y2 = REAL(MIN(IH, LENGTH + IH2))
CALL NEWPENCOLOR(IWIN, IWALL)
CALL DRAWLINE(IWIN, X, Y1, X, Y2)
END
C ******************************************************************
SUBROUTINE RENDER()
C
C RENDERS THE SCENE.
C
EXTERNAL COPYLAYER, GCLR, FILLRECT, NEWPENCOLOR, RAYCST
INTEGER IWIN, IW, IH
COMMON /WIN/ IWIN, IW, IH
INTEGER IX
CALL GCLR(IWIN)
CALL NEWPENCOLOR(IWIN, 8)
CALL FILLRECT(IWIN, 0.0, 0.0, REAL(IW), REAL(IH / 2))
DO 10 IX = 0, IW - 1
CALL RAYCST(IX)
10 CONTINUE
CALL COPYLAYER(IWIN, 1, 0)
END
C ******************************************************************
SUBROUTINE ROTATE(X, Y, A)
C
C ROTATES X, Y BY ANGLE A [RAD].
C
REAL X, Y, A
REAL T
T = X
X = X * COS(A) - Y * SIN(A)
Y = T * SIN(A) + Y * COS(A)
END
C ******************************************************************
SUBROUTINE WINDOW()
C
C OPENS X11 WINDOW USING EGGX/PROCALL.
C
EXTERNAL GOPEN, GSETBGCOLOR, GSETNONBLOCK, LAYER, WINNAME
INTEGER IWIN, IW, IH
COMMON /WIN/ IWIN, IW, IH
CALL GOPEN(IW, IH, IWIN)
CALL WINNAME(IWIN, 'RAY CASTING' // CHAR(0))
CALL GSETNONBLOCK(1)
CALL GSETBGCOLOR(IWIN, 'BLACK' // CHAR(0))
CALL LAYER(IWIN, 0, 1)
END
C ******************************************************************
BLOCK DATA
C
C GLOBAL VARIABLES AND MAP DATA.
C
INTEGER IWIN, IW, IH, MAPY(12, 12), MW, MH
REAL DX, DY, PLANEX, PLANEY, POSX, POSY
COMMON /WIN/ IWIN, IW, IH
COMMON /MAP/ MAPY, MW, MH
COMMON /VEC/ DX, DY, PLANEX, PLANEY, POSX, POSY
DATA IW, IH /640,480/
DATA DX, DY, PLANEX, PLANEY, POSX, POSY /-1.,0.,0.,0.66,10.5,10.5/
DATA MW, MH /12,12/
DATA MAPY /02,07,07,03,07,03,07,03,07,03,07,01,
& 02,00,00,00,00,00,00,00,00,00,00,01,
& 02,00,00,00,00,00,00,00,00,00,00,02,
& 04,00,07,02,00,00,00,00,00,00,00,01,
& 02,00,07,00,00,00,00,00,00,00,00,02,
& 04,00,00,00,02,02,02,00,00,00,00,01,
& 02,00,00,00,02,03,03,00,00,00,00,02,
& 02,00,00,00,00,00,00,00,00,00,00,01,
& 02,00,00,00,00,00,00,00,00,05,00,02,
& 02,00,04,04,04,00,00,00,00,05,00,01,
& 02,00,00,00,00,00,00,00,00,00,00,02,
& 02,03,03,03,03,03,03,03,03,03,03,01/
END
Link the program against EGGX/ProCALL and X11:
$ gfortran -o raycast raycast.f libeggx.a -lX11 -lm
$ ./raycast
We can even compile the program with f2c:
$ f2c raycast.f
raycast.f:
MAIN raycast:
input:
move:
raycst:
render:
rotate:
window:
$ cc -I/usr/local/include -L/usr/local/lib -o raycast \
raycast.c libeggx.a -lX11 -lf2c -lm
$ ./raycast
Screen tearing may be visible, as X11 is not able to render the scene fast enough.