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(MAPY, MW, MH, POSX, POSY, DX, DY, PLANEX, PLANEY, IKEY)
IKEY
.CALL MOVE(MAPY, MW, MH, X, Y, DX, DY, SPEED)
DX
, DY
by
SPEED
.CALL RAYCST(MAPY, MW, MH, IWIN, IW, IH, IX, POSX, POSY, DX, DY, PLANEX, PLANEY)
IX
on the camera plane and renders a single
column of pixels in the colour of the wall hit.CALL RENDER(MAPY, MW, MH, IWIN, IW, IH, POSX, POSY, DX, DY, PLANEX, PLANEY)
CALL ROTATE(X, Y, A)
A
.CALL XWIN(IWIN, IW, IH)
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 GCLOSE, INPUT, MSLEEP, RENDER, XWIN
INTEGER IDELAY, IESC, IW, IH, MW, MH
PARAMETER (IDELAY=20, IESC=27, IW=640, IH=480, MW=12, MH=12)
INTEGER IKEY, IWIN
INTEGER MAPY(MW, MH)
REAL DX, DY, PLANEX, PLANEY, POSX, POSY
C
C SET INITIAL POSITION AND DIRECTION.
C
POSX = 10.5
POSY = 10.5
DX = -1.0
DY = 0.0
PLANEX = 0.0
PLANEY = 0.66
C
C OPEN NEW X11 WINDOW.
C
CALL XWIN(IWIN, IW, IH)
C
C MAIN LOOP, RUNS UNTIL USER HITS ESCAPE.
C
10 CONTINUE
CALL INPUT(MAPY, MW, MH, POSX, POSY, DX, DY, PLANEX, PLANEY, IKEY)
CALL RENDER(MAPY, MW, MH, IWIN, IW, IH, POSX, POSY, DX, DY,
& PLANEX, PLANEY)
CALL MSLEEP(IDELAY)
IF (IKEY .NE. IESC) GOTO 10
CALL GCLOSE(IWIN)
C
C MAP DATA.
C
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
C ******************************************************************
SUBROUTINE INPUT(MAPY, MW, MH, POSX, POSY, DX, DY,
& PLANEX, PLANEY, IKEY)
C
C PROCESS KEYBOARD INPUT AND MOVES/ROTATES PLAYER.
C
EXTERNAL GGETCH, MOVE, ROTATE
INTEGER IDOWN, ILEFT, IRIGHT, IUP
REAL ALPHA, SPEED
PARAMETER (IDOWN=31, ILEFT=29, IRIGHT=28, IUP=30)
PARAMETER (ALPHA=0.1, SPEED=0.15)
INTEGER MW, MH, MAPY(MW, MH), IKEY
REAL POSX, POSY, DX, DY, PLANEX, PLANEY
CALL GGETCH(IKEY)
IF (IKEY .EQ. IUP) THEN
C
C MOVE FORWARD.
C
CALL MOVE(MAPY, MW, MH, POSX, POSY, DX, DY, SPEED)
ELSE IF (IKEY .EQ. IDOWN) THEN
C
C MOVE BACKWARD.
C
CALL MOVE(MAPY, MW, MH, POSX, POSY, DX, DY, -SPEED)
ELSE IF (IKEY .EQ. ILEFT) THEN
C
C TURN LEFT.
C
CALL ROTATE(DX, DY, ALPHA)
CALL ROTATE(PLANEX, PLANEY, ALPHA)
ELSE IF (IKEY .EQ. IRIGHT) THEN
C
C TURN RIGHT.
C
CALL ROTATE(DX, DY, -ALPHA)
CALL ROTATE(PLANEX, PLANEY, -ALPHA)
END IF
END
C ******************************************************************
SUBROUTINE MOVE(MAPY, MW, MH, X, Y, DX, DY, SPEED)
C
C MOVES PLAYER IN DX, DY.
C
INTEGER MW, MH, MAPY(MW, MH)
REAL X, Y, DX, DY, SPEED
REAL NX, NY
NX = X + DX * SPEED
NY = Y + 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(Y) + 1) .EQ. 0) X = NX
IF (MAPY(INT(X) + 1, INT(NY) + 1) .EQ. 0) Y = NY
END
C ******************************************************************
SUBROUTINE RAYCST(MAPY, MW, MH, IWIN, IW, IH, IX, POSX, POSY,
& DX, DY, PLANEX, PLANEY)
C
C RENDERS SINGLE COLUMN OF PIXELS AT IX.
C
EXTERNAL DRAWLINE, NEWPENCOLOR
INTEGER MW, MH, MAPY(MW, MH), IWIN, IW, IH, IX
REAL POSX, POSY, DX, DY, PLANEX, PLANEY
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(MAPY, MW, MH, IWIN, IW, IH, POSX, POSY, DX, DY,
& PLANEX, PLANEY)
C
C RENDERS THE SCENE.
C
EXTERNAL COPYLAYER, GCLR, FILLRECT, NEWPENCOLOR, RAYCST
INTEGER MW, MH, MAPY(MW, MH), IWIN, IW, IH
REAL POSX, POSY, DX, DY, PLANEX, PLANEY
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(MAPY, MW, MH, IWIN, IW, IH, IX, POSX, POSY, DX, DY,
& PLANEX, PLANEY)
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 X0
X0 = X
X = X * COS(A) - Y * SIN(A)
Y = X0 * SIN(A) + Y * COS(A)
END
C ******************************************************************
SUBROUTINE XWIN(IWIN, IW, IH)
C
C OPENS X11 WINDOW USING EGGX/PROCALL.
C
EXTERNAL GOPEN, GSETBGCOLOR, GSETNONBLOCK, LAYER, WINNAME
INTEGER 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
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:
xwin:
$ 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.