FORTRAN Computer Games


Ray Casting

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.

ray casting in FORTRAN 77
Fig. 1: Ray casting in FORTRAN 77 on X11

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.

Functions & Subroutines

The FORTRAN program calls six subroutines for input control, movement, and rendering:

CALL INPUT(DONE)
Moves or rotates the view point of the player on keyboard input. The code of the pressed key is returned in IKEY.
CALL MOVE(SPEED)
Moves the player on the map in direction DX, DY by SPEED.
CALL RAYCST(IX)
Casts a ray in IX on the camera plane and renders a single column of pixels in the colour of the wall hit.
CALL RENDER()
Renders the scene by drawing the floor and casting 640 rays from the player’s view point, then copies the double buffer to screen.
CALL ROTATE(X, Y, A)
Rotates a vector by angle A.
CALL WINDOW()
Opens an X11 windows using the EGGX/ProCALL library.

Program Listing

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.

References


Home