``````(*

> Can anyone recomend any books/source/reference/people that would be
> necessary to read/talk to to learn how to do 3 dimensional graphics for
> 1st person game programming similar to the Wolfenstein/Doom/Flight
> simulator type games.

If  you  can read "C", you should look out for ACK3D (a "3D" shareware
graphic  engine)  which  comes  with  some sources. Note however, that
neither  ACK3D nor Wolfenstein or Doom are true 3D games: They all use
a clever technique called "raycasting".

The  underlying  idea  of raycasting is quite simple: use a 2D program
and  just  paste in a 3rd dimension, only for the screen display. Just
take  a  2nd  look  at  DOOM  at you'll notice that there is no single
location  in the map where you may have two different height-positions
throughout  the  game.  Or more mathematically: There is no coordinate
(x,y) in the plane for which there are more than one z-coordinate. The
basic  map  of  such games is a normal 2D map: the player moves within
this  plane  (which  is divided by a grid into basic "blocks") and his
view  will  be computed for each animation frame. For example, say his
angle  of view is 60 degrees. Then from his actual position, imaginary
rays  will  be  sent out from -30..+30 degrees through the 2D (!) map.
Each  ray  is  traced until it hits a block of the grid. The according
object  will  then  be drawn to the screen at the proper position; the
length  of  the  ray  from the player's position to the hit-point is a
measurement  how  far  the object is from the viewer and thus, how big
(and bright) the object should be drawn.
As the player only moves within the (x,y) plane, there is no change of
information  in  the  z-coordinate. Therefore, for each screen column,
there is only one single computation necessary! (Even small changes in
the z-coordinate could be simulated easily by shifting the column data
a  bit). The profits are dramatic: for a screen resolution of 320x200,
you  only  have  320  computations  (instead  of  64000)  --that's  an
improvement of 99.5%!
The problem with this method is the exact detection where the rays hit
the  grid blocks. Instead of decreasing the mesh size of the grid, one
may  think  that  the  original  blocks  consist of a second "subgrid"
(ACK3D  uses  a  64x64  subgrid for each block). If you start counting
blocks  row-wise,  starting  with zero, then you can compute the block
number  and  the  relative  coordinates  (rx1,ry1)  of  a point P with
absolute corrdinates (x1,y1) by

b = (y1 DIV 64)*gridwidth_in_blocks + (x1 DIV 64)
rx1 = x1 MOD 64
ry1 = y1 MOD 64

See the profits of powers of two for the subgrid size here?:
b = (y1 SHR 6)*gridwidth_in_blocks + (x1 AND 63)
rx1 = x1 AND 63
ry1 = y1 AND 63

Computing the collision points rays <-> blocks is pure trigonometry:
Given are the position of the player P = (x1,y1) and his viewing angle
alpha  (measured  from the x-axis, for example). If he is looking with
angle of 20 degrees, one would have to compute all rays from 20-30=-10
degrees  up  to  20+30=50  degrees.  If your screen has a width of 320
columns, then a simple algorithm would look something like this:

column:=0
FOR beta:=alpha-30 TO alpha+30 STEP 60/320
cast ray from P=(x1,y1) with angle beta
IF ray hits nontransparent block b
THEN draw block columns which we hit
at screen position "column";
compute distance to hit location and
darken graphic accordingly
ELSEIF ray leaves the plane edge
THEN draw complete column black
ELSE {ray runs through empty/transparent block}
trace ray further
INC(column)
ENDFOR

As  we  do  have a fixed size of the game area, we do know the maximum
length  of  a  ray  in before. If the ray's length is bigger than this
maximum, you may stop tracing the ray any further. Using a bit college
math  trigometry,  one  can  optimize this even further to compute the
last   point   Q   =  (x2,y2)  on  the  ray  which  must  be  checked:
x2:=x1+cos(beta)*diag;    y2:=y1+sin(beta)*diag;    (diag:=length   of
diagonal of the (x,y) plane).

Now  just  use  some  standard line-algorithm like Brensenham's on the
line  between  P and Q and check the points if they hit a block and if
so,  where.  Note  that  you don't have to check every point, but only
those which lie on the grid edges!

> Can this type of program be created with Pascal or are they Assembler/C
> only areas?
I  once  saw  a  small piece of (buggy) PASCAL code, but didn't try to
debug it more than necessary; however, it should suffice for a start:
*)

PROGRAM RayCast2;
USES DOS, CRT;
TYPE
{ Map : 10 * 10 squares - 1 square consists of 64 * 64 units !! }
TMap = ARRAY[1..10] OF ARRAY[1..10] OF BYTE;
TPlayer= OBJECT
x            ,
y            : SHORTINT;
ViewPoint  : INTEGER;
MapX       ,
Mapy       : SHORTINT;
PROCEDURE Init;
END;
HeightTab = ARRAY[0..90] OF BYTE;
WallBmp= ARRAY[0..63] OF BYTE;

CONST Up   = 1;
Down = 2;
Right =3;
Left  =4;

VAR Map : TMap;
P     : TPlayer;
ch    : CHAR;
xAtt  ,
yAtt  : BYTE;
Angle : REAL;
Height ,
Width  : BYTE;
Distance: INTEGER;
yDis   ,
xDis   ,
Hyp    : REAL;
DeltaX ,
DeltaY : REAL;
DeltaKX,
DeltaKY: LONGINT;
WMapX,
WMapY: INTEGER;
Column ,
XColumn,
YColumn: SHORTINT;
HeightTabTable : HeightTab;
Wall        : WallBmp;
sinus, cosinus:ARRAY[0..359] OF REAL;
i:INTEGER;

PROCEDURE Modus(m:WORD); ASSEMBLER;
ASM
MOV AX,m
INT \$10
END;

PROCEDURE TPlayer.Init;
BEGIN
MapX:=2; MapY:=2;
x:=32; y:=32;
Angle:=0;
END;

FUNCTION init:BOOLEAN;
VAR x,y : BYTE;
HeightTabF : FILE OF HeightTab;
BEGIN
Modus(\$13);
{ Erase Map }
FOR x:=1 TO 10 DO
FOR y:=1 TO 10 DO
Map[x,y]:=0;

{ Draw a few walls }
FOR x:=1 TO 10 DO BEGIN
Map[x,1]:=129;
Map[x,10]:=129;
END;
FOR y:=1 TO 10 DO BEGIN
Map[1,y]:=129;
Map[4,y]:=129;
Map[10,y]:=129;
END;
Map[2,3]:=129;
{ Build Character }
P.Init;
FOR x:=0 TO 90 DO
HeightTabTable[x]:=90-x;

{ Different Palette }
{ JansPalette; }
{ Init Wall Bitmap }
FOR x:=0 TO 31 DO
Wall[x]:=100+x;
FOR x:=32 TO 63 DO
Wall[x]:=100-(x-32);
END;

PROCEDURE Clear; ASSEMBLER;
ASM
CLD
MOV AX,\$A000
MOV ES,AX
MOV CX,32000
XOR AX,AX
REP STOSW
END;

PROCEDURE deInit;
BEGIN
Modus( 3 );
END;

PROCEDURE DownProc;  { looking down }
BEGIN
yDis:=907;
{ Step through each square }
FOR Height:=P.MapY+1 TO 10 DO BEGIN
Distance:= 65-P.Y + 64 * (Height-P.MapY-1);
Hyp    := Distance / cosinus[Round(angle)];
DeltaX := sinus[Round(angle)] * Hyp;
IF DeltaX>MaxLongInt THEN DeltaX:=MaxLongInt;
{ Subtract when looking to the right , else add}
IF xAtt=Right THEN
DeltaKX:= (P.MapX-1)*64+P.X - Round(DeltaX)
ELSE
DeltaKX:= (P.MapX-1)*64+P.X + Round(DeltaX);

WMapX:= DeltaKX DIV 64+1;
XColumn:= DeltaKX MOD 64;
WMapY:= Height;
Hyp:=Abs(Hyp);
{ If out of bounds or wall found }
IF (WMapX<1) OR (WMapX>10) OR (Hyp>906) OR (Map[WMapX,WMapY]>128) THEN
BEGIN
yDis:=Hyp;
Height:=10;
END
END;
IF (xDis>906) AND (yDis>906) THEN BEGIN
Distance:=906;
Column :=0;
END ELSE
IF yDis<xDis THEN BEGIN
Distance:=Round(yDis);
Column:= xColumn;
END ELSE BEGIN
Distance:=Round(xDis);
Column :=yColumn;
END;
END;

PROCEDURE UpProc;
BEGIN
yDis:=907;
FOR Height:=P.MapY-1 DOWNTO 1 DO BEGIN
Distance:= -P.Y - 64 * (P.MapY-Height-1);
Hyp    := Distance / cosinus[Round(angle)];
DeltaX := sinus[Round(angle)] * Hyp;
DeltaKX:= (P.MapX-1)*64+P.X- Round(DeltaX);
WMapX:= DeltaKX DIV 64+1;
XColumn:= DeltaKY MOD 64;
WMapY:= Height;
Hyp    :=ABs(Hyp);
{ If out of bounds, or wall struck }
IF (WMapX<1) OR (WMapX>10) OR (Hyp>906) OR (Map[WMapX,WMapY]>128) THEN
BEGIN      yDis:=Hyp;
Height:=1;
END
END;
IF (xDis>906) AND (yDis>906) THEN BEGIN
Distance:=906;
Column:=0;
END ELSE
IF yDis<xDis THEN BEGIN
Distance:=Round(yDis);
Column:=xColumn;
END ELSE BEGIN
Distance:=Round(xDis);
Column:=YColumn;
END;

END;

PROCEDURE RightProc(angle:WORD);
BEGIN
xDis:=907;
FOR Width:=P.MapX+1 TO 10 DO BEGIN
Distance:= 65-P.X + 64 * (Width-P.MapX-1);
Hyp    := Distance / cosinus[angle];
DeltaY := sinus[angle] * Hyp;
IF DeltaY>MaxLongInt THEN DeltaY:=MaxLongInt;
DeltaKY:= (P.MapY-1)*64 +P.Y -Round(DeltaY);

WMapX:= Width;
WMapY:= DeltaKY DIV 64 +1;
YColumn:= DeltaKY MOD 64;
Hyp    := Abs(Hyp);

{ If out of bounds, or wall struck }
IF (WMapY<1) OR (WMapY>10) OR (Hyp>906) OR (Map[WMapX,WMapY]>128) THEN
BEGIN      Width:=10;
xDis:=Hyp;
END;
END;
{ Now check yRay }
CASE yAtt OF
Down : DownProc;
Up  : UpProc;
END;
END;

PROCEDURE LeftProc(angle:WORD);
BEGIN
xDis:=907;
FOR Width:=P.MapX-1 DOWNTO 1 DO BEGIN
Distance:= -P.X - 64 * (P.MapX-Width-1);
Hyp    := Distance / cosinus[angle];
DeltaY := sinus[angle] * Hyp;
DeltaKY:= (P.MapY-1)*64 + P.Y - Round(DeltaY);

WMapX:= Width;
WMapY:= DeltaKY DIV 64 +1;
YColumn:= DeltaKY MOD 64;
Hyp    := Abs(Hyp);
{ If out of bounds, or wall struck }
IF (WMapY<1) OR (WMapY>10) OR (Hyp>906) OR (Map[WMapX,WMapY]>128) THEN
BEGIN      Width:=1;
xDis:=Hyp;
END;
END;
{ Now check yRay }
CASE yAtt OF
Down : DownProc;
Up  : UpProc;
END;
END;

PROCEDURE Proc0; { Angle = 0 degrees }
BEGIN
WMapY:=P.MapY;
Column:= 0;
Distance:=907;
FOR Width:=P.MapX+1 TO 10 DO BEGIN
WMapX:=Width;
IF Map[WMapX,WMapY]>128 THEN BEGIN
Distance:=65-P.x + 64 * (Width-P.MapX-1);
Width:=10;
Column:=P.y;
END;
END;
END;

PROCEDURE Proc18; { Angle = 180 degrees }
BEGIN
WMapY:=P.MapY;
Column :=0;
Distance:=907;
FOR Width:=P.MapX-1 DOWNTO 1 DO BEGIN
WMapX:=Width;
IF Map[WMapX,WMapY]>128 THEN BEGIN
Distance:=Abs( -P.X - 64 * (P.MapX-Width-1) );
Width:=1;
Column:=P.Y;
END;
END;
END;

PROCEDURE Proc27; { Angle = 270 degrees }
BEGIN
WMapX:=P.MapX;
Distance:=907;
Column :=0;
FOR Height:=P.MapY+1 TO 10 DO BEGIN
WMapY:=Height;
IF Map[WMapX,WMapY]>128 THEN BEGIN
Distance:=65-P.y + 64 * (Height-P.MapY-1);
Height:=10;
Column:=P.Y;
END;
END;
END;

PROCEDURE Proc90; { Angle = 90 degrees }
BEGIN
WMapX:=P.MapX;
Column:=0;
Distance:=907;
FOR Height:=P.MapX-1 DOWNTO 1 DO BEGIN
WMapY:=Height;
IF Map[WMapX,WMapY]>128 THEN BEGIN
Distance:=Abs( -P.Y - 64 * (P.MapY-Height-1)  );
Height:=1;
Column:=p.Y;
END;
END;
END;

PROCEDURE VLine(x,y1,y2:WORD; c:BYTE); ASSEMBLER;
ASM
MOV CX,y2
MOV DI,y1
SUB CX,DI
JCXZ @ende
JG @doit
NEG CX
MOV DI,y2
@doit:
MOV AX,320
MUL DI
MOV DI,AX
CLD
MOV AL,c
MOV DX,320
@l1:
MOV ES:[DI],AL
LOOP @l1
@ende:
END;

PROCEDURE Draw;
VAR counter: INTEGER;
intAngle:WORD;
BEGIN
{ 70 degree viewfield (not just 60) }
angle:=(70*160 / 320) + P.ViewPoint;
IF angle>=360 THEN angle:=angle-360;

FOR counter:=0 TO 319 DO BEGIN { loop each column }
intAngle:=Round(angle);
IF intAngle=90 THEN             { xDirection }
xAtt:=90
ELSE
IF intAngle=270 THEN
xAtt:=27
ELSE
IF (intAngle<90) OR (intAngle>270) THEN
xAtt:=Right
ELSE
xAtt:=Left;
IF intAngle=180 THEN            { y Direction }
xAtt:=18
ELSE
IF intAngle=0 THEN
xAtt:=0
ELSE
IF intAngle<180 THEN
yAtt:=Up
ELSE
yAtt:=Down;

CASE xAtt OF
Right : RightProc(intAngle);
Left  : LeftProc(intAngle);
0      : Proc0;
18     : Proc18;
90     : Proc90;
27     : Proc27;
END;

{ Draw Line }
VLine(counter,99,99-HeightTabtable[Distance DIV 10],Wall[Abs(Column)]);

{ decrease angle }
angle:=angle- (70 / 320);
IF angle<0 THEN angle:=angle+360;
END; { next column }
END;

BEGIN
FOR i:=0 TO 359 DO
BEGIN
cosinus[i]:=cos(i*Pi/180);
sinus[i]  :=sin(i*Pi/180);
END;
ch:=#1;
IF Init THEN BEGIN
P.ViewPoint:=90;
REPEAT   { Loop until ESC pressed }
Clear;
Draw;
CASE ch OF
'a' : BEGIN  { Left turn }
Inc(P.ViewPoint,5);
IF P.ViewPoint>359 THEN P.ViewPoint:=P.ViewPoint-360;
END;
's' : BEGIN {right turn }
Dec(P.ViewPoint,5);
IF P.ViewPoint<0 THEN P.ViewPoint:=P.ViewPoint+360;
END;
'w' : BEGIN { Move forward }
Dec(P.y,2);
IF P.y<1 THEN BEGIN
Dec(P.MapY);
P.y:=64+P.y;
END;
END;
'y' : BEGIN {Move backward }
Inc(P.y,2);
IF P.y>64 THEN BEGIN
Inc(P.MapY);
P.y:=P.y-64;
END;
END;
END;

UNTIL ch=#27;
DeInit;
END;
END.

``````