(* > 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 yDis10) 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 yDisMaxLongInt 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 ADD AX,x MOV DI,AX CLD MOV AL,c MOV DX,320 @l1: MOV ES:[DI],AL ADD DI,DX 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; ch:=ReadKey; 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.