FORTRAN Computer Games


Shuttle

SHUTTLE.BAS is an early software renderer written in the 1980s for Microsoft GW-BASIC. The source code was shared on Bulletin Board Systems and also distributed on shareware collections of that time, such as PC-SIG Library (disk #445). The program renders the 3d model of a space shuttle in the orientation given by the user to screen, simply by drawing lines between perpectively projected vertices.

SHUTTLE.BAS
Fig. 1: SHUTTLE.BAS for GW-BASIC, running in EGA Mode 9 (Enhanced Color Display)

QB Cult Magazine (issue #2, 2000) describes SHUTTLE.BAS as:

One of the better 3D graphics programs ever uploaded to a GWBASIC BBS […]. This anciant program was one of the only 3D engines that anybody really took much notice of. With this release, GWBASIC programmers started to realise the potential for creating 3D graphics programs in their language.

The program reads rotation angles from user input, orientates the predefined vertices accordingly in 3d, applies a perspective projection, and finally connects the vertices with edges using the LINE intrinsic of GW-BASIC. The graphics mode can be changed line 160 to high-resolution EGA (fig. 1).

On modern operating systems, we can run DOSBox to execute the program, either with GW-BASIC or QuickBASIC:

C:\>GWBASIC SHUTTLE.BAS

The source code is simple enough for a quick port to FORTRAN 77, using the EGGX/ProCALL library. Instead of reading the orientation from user input, the remake just rotates the model in two angles, and renders the animation at 50 Hz. Keyboard controls could be added alternatively.

Functions & Subroutines

The FORTRAN 77 remake calls three procedures that are not part of the ANSI language standard:

RESULT = TIME()
Returns timestamp in seconds (INTEGER). Required for the initialisation of the pseudo-random number generator.
RESULT = RAND(I)
Returns the next random number (REAL).
CALL SRAND(SEED)
Initialises the pseudo-random number generator with given seed value (INTEGER).

Most modern compilers provide these procedures through extensions.

Program Listing

The FORTRAN 77 version is very similar to the original GW-BASIC implementation. All vertices and edges of the space shuttle are stored in VERTS and EDGES. The program first scales the vertices in X, Y, Z, and then opens a new X11 window, sets the keyboard input to non-blocking, selects the first canvas layer for double buffering, and changes the pen colour to yellow. The rotation angles are initialised with random values. They are named after their side opposites: perpendicular P, base B, and hypotenuse H.

shuttle.f
Fig. 2: Space shuttle in FORTRAN 77 on UNIX

The vertices are rotated in 3D, transformed using perspective projection, and then translated in screen coordinates. The edges are drawn as lines on layer 1 to create a wireframe look. At the end of each frame, the layer is copied to screen (fig. 2). The program stops once the user hits the escape key.

Some parameters of the renderer may be changed to custom values:

Copy and save the program as shuttle.f locally.

C     ******************************************************************
C
C     3-D SPACE SHUTTLE
C
C     PORT OF THE GW-BASIC PROGRAM "SHUTTLE.BAS" THAT DISPLAYS THE
C     WIREFRAME MODEL OF A SPACE SHUTTLE. WRITTEN BY PHILIPP ENGEL
C     (2021). REQUIRES EGGX/PROCALL LIBRARY.
C
C     ******************************************************************
      PROGRAM SHUTTLE
      INTEGER ICOL, IDELAY, IESC, IW, IH, NEDGES, NVERTS
      REAL    ALPHA, D, F, PI
      PARAMETER (ICOL=7, IDELAY=20, IESC=27, IW=640, IH=350)
      PARAMETER (NEDGES=259, NVERTS=124)
      PARAMETER (ALPHA=0.01, D=120.0, F=0.2, PI=ACOS(-1.0))

      EXTERNAL COPYLAYER, GCLOSEALL, GCLR, GGETCH, GOPEN, GSETBGCOLOR
      EXTERNAL GSETNONBLOCK, LAYER, LINETO, MOVETO, MSLEEP, NEWPENCOLOR
      EXTERNAL WINNAME

      INTEGER I, IKEY, IWIN
      REAL    B, H, P
      REAL    CH, SH, CP, SP, CB, SB
      REAL    AM, BM, CM, DM, EM, FM, GM, HM, IM
      REAL    XV, YV, ZV
      REAL    X, Y, Z
      REAL    X3, Y3, Z3
      REAL    U1, V1, U2, V2

      INTEGER EDGES(NEDGES)
      REAL    VERTS(NVERTS, 3)
C
C     SCALE THE VERTICES.
C
      DO 10 I = 1, NVERTS
      VERTS(I, 1) = VERTS(I, 1) * F
      VERTS(I, 2) = VERTS(I, 2) * F
      VERTS(I, 3) = VERTS(I, 3) * F
   10 CONTINUE
C
C     OPEN X11 WINDOW.
C
      CALL GOPEN(IW, IH, IWIN)
      CALL WINNAME(IWIN, 'FORTRAN SHUTTLE' // CHAR(0))
      CALL GSETNONBLOCK(1)
      CALL GSETBGCOLOR(IWIN, 'BLACK' // CHAR(0))
      CALL LAYER(IWIN, 0, 1)
      CALL GCLR(IWIN)
      CALL NEWPENCOLOR(IWIN, ICOL)
C
C     SET RANDOM ORIENTATION.
C
      CALL SRAND(TIME())

      B = RAND(0) * 2 * PI
      H = RAND(0) * 2 * PI
      P = RAND(0) * 2 * PI

      U2 = 0.0
      V2 = 0.0
C
C     RENDER LOOP.
C
   20 CONTINUE
      CALL GCLR(IWIN)
C
C     ROTATE VERTICES.
C
      P = MOD(P + ALPHA, 2 * PI)
      B = MOD(B + ALPHA, 2 * PI)

      CH = COS(H)
      SH = SIN(H)
      CP = COS(P)
      SP = SIN(P)
      CB = COS(B)
      SB = SIN(B)

      AM = CB * CH - SH * SP * SB
      BM = -CB * SH - SP * CH * SB
      CM = CP * SB
      DM = SH * CP
      EM = CP * CH
      FM = SP
      GM = -CH * SB - SH * SP * CB
      HM = SH * SB - SP * CH * CB
      IM = CP * CB

      XV = -D * CP * SH
      YV = -D * CP * CH
      ZV = -D * SP
C
C     DRAW EDGES.
C
      DO 30 I = 1, NEDGES
      X = VERTS(ABS(EDGES(I)), 1)
      Y = VERTS(ABS(EDGES(I)), 2)
      Z = VERTS(ABS(EDGES(I)), 3)

      X = X - XV
      Y = Y - YV
      Z = Z - ZV

      X3 = AM * X + BM * Y + CM * Z
      Y3 = DM * X + EM * Y + FM * Z
      Z3 = GM * X + HM * Y + IM * Z

      U1 = 135 + 13.5 * D * X3 / Y3
      V1 =  80 - 11.5 * D * Z3 / Y3

      IF (EDGES(I) .GT. 0) THEN
        CALL MOVETO(IWIN, U2 * 2 + 75, V2 + 100)
        CALL LINETO(IWIN, U1 * 2 + 75, V1 + 100)
      END IF

      U2 = U1
      V2 = V1
   30 CONTINUE
C
C     COPY LAYER TO SCREEN AND CHECK FOR KEYBOARD INPUT.
C
      CALL COPYLAYER(IWIN, 1, 0)
      CALL MSLEEP(IDELAY)
      CALL GGETCH(IKEY)
      IF (IKEY .NE. IESC) GOTO 20

      CALL GCLOSEALL()

      DATA VERTS /0.,1.5,2.2,1.7,0.,-1.7,-2.2,-1.5,0.,2.8,4.,3.,0.,-3.,
     &  -4.,-2.8,0.,4.6,5.8,4.,0.,-4.,-5.8,-4.6,0.,4.5,5.8,4.,0.,-4.,
     &  -5.8,-4.5,0.,3.5,7.8,8.,0.,-8.,-7.8,-3.5,0.,3.8,8.,8.,0.,-8.,
     &  -8.,-3.8,0.,4.7,8.,8.,0.,-8.,-8.,-4.7,0.,4.7,8.,8.,0.,-8.,-8.,
     &  -4.7,0.,4.7,8.,8.,0.,-8.,-8.,-4.7,0.,4.7,8.,8.,0.,-8.,-8.,-4.7,
     &  0.,4.7,8.,8.,0.,-8.,-8.,-4.7,0.,2.,8.8,9.,0.,-9.,-8.8,-2.,0.,2.,
     &  9.2,10.,0.,-10.,-9.2,-2.,8.7,15.,35.,35.,-8.7,-15.,-35.,-35.,0.,
     &  0.,0.,0.,6.,6.,11.,11.,-6.,-6.,-11.,-11.,-2.2,-2.6,-4.6,-6.5,
     &  -6.7,-6.5,-4.6,-2.6,-0.8,-1.5,-4.5,-7.2,-8.,-7.2,-4.5,-1.5,1.7,
     &  0.,-4.4,-8.2,-9.,-8.2,-4.4,0.,4.,1.,-4.6,-9.,-9.5,-9.,-4.6,1.,
     &  8.,7.,2.,-7.,-9.8,-7.,2.,7.,8.,7.5,3.,-8.,-9.8,-8.,3.,7.5,8.,7.,
     &  4.,-8.7,-10.,-8.7,4.,7.,8.,7.,4.,-8.7,-10.,-8.7,4.,7.,8.,7.,4.,
     &  -8.7,-10.,-8.7,4.,7.,8.,7.,4.,-8.7,-10.,-8.7,4.,7.,8.,7.,4.,
     &  -8.7,-10.,-8.7,4.,7.,9.,8.5,1.5,-10.,-10.8,-10.,1.5,8.5,9.5,9.3,
     &  1.5,-10.,-10.2,-10.,1.5,9.3,-8.7,-8.7,-10.,-10.,-8.7,-8.7,-10.,
     &  -10.,13.,33.,33.,14.,11.,11.,5.,5.,11.,11.,5.,5.,46.,46.,46.,
     &  46.,46.,46.,46.,46.,43.,43.,43.,43.,43.,43.,43.,43.,38.,38.,38.,
     &  38.,38.,38.,38.,38.,32.5,32.5,32.5,32.5,32.5,32.5,32.5,32.5,
     &  26.3,26.3,26.3,26.3,26.3,26.3,26.3,26.3,21.5,21.5,21.5,21.5,
     &  21.5,21.5,21.5,21.5,14.,14.,14.,14.,14.,14.,14.,14.,4.,4.,4.,4.,
     &  4.,4.,4.,4.,-12.,-12.,-12.,-12.,-12.,-12.,-12.,-12.,-27.3,-27.3,
     &  -27.3,-27.3,-27.3,-27.3,-27.3,-27.3,-35.6,-35.6,-35.6,-35.6,
     &  -35.6,-35.6,-35.6,-35.6,-43.,-43.,-43.,-43.,-43.,-43.,-43.,-43.,
     &  -48.,-48.,-48.,-48.,-48.,-48.,-48.,-48.,21.,-16.,-36.,-40.,21.,
     &  -16.,-36.,-40.,-37.,-60.,-69.,-60.,-43.,-48.,-43.,-48.,-43.,
     &  -48.,-43.,-48./
      DATA EDGES /-1,2,3,4,5,6,7,8,1,-9,10,11,12,13,14,15,16,9,-17,18,
     &  19,20,21,22,23,24,17,-25,26,27,28,29,30,31,32,25,-33,34,35,36,
     &  37,38,39,40,33,-41,42,43,44,45,46,47,48,41,-49,50,51,52,53,54,
     &  55,56,49,-57,58,59,60,61,62,63,64,57,-65,66,67,68,69,70,71,72,
     &  65,-73,74,75,76,77,78,79,80,73,-81,82,83,84,85,86,87,88,81,-89,
     &  90,91,92,93,94,95,96,89,-97,98,99,100,101,102,103,104,97,-1,9,
     &  17,25,33,41,49,57,65,73,81,89,97,-2,10,18,26,34,42,50,58,66,74,
     &  82,90,98,-3,11,19,27,35,43,51,59,67,75,83,91,99,-4,12,20,28,36,
     &  44,52,60,68,76,84,92,100,-5,13,21,29,37,45,53,61,69,77,85,93,
     &  101,-6,14,22,30,38,46,54,62,70,78,86,94,102,-7,15,23,31,39,47,
     &  55,63,71,79,87,95,103,-8,16,24,32,40,48,56,64,72,80,88,96,104,
     &  -44,105,106,107,108,92,-46,109,110,111,112,94,-81,113,114,115,
     &  116,89,-82,117,118,-83,119,120,-87,121,122,-88,123,124,-117,119,
     &  -121,123,-118,120,-122,124/
      END

The FORTRAN remake has to be linked against the static EGGX/ProCALL library libeggx.a:

$ gfortran -o shuttle shuttle.f libeggx.a -lX11 -lm
$ ./shuttle

References


Home