Outils pour utilisateurs

Outils du site


back2root:archives:denthor:part-08

Différences

Ci-dessous, les différences entre deux révisions de la page.


Révision précédente
back2root:archives:denthor:part-08 [2024/08/27 09:04] (Version actuelle) – [Drawing a 3D point to screen] frater
Ligne 1: Ligne 1:
 +===== PART 08 : Optimization  ===== 
  
 +Hello everybody! Christmas is over, the last of the chocolates have been eaten, so it's time to get on with this, the eighth part of the ASPHYXIA Demo Trainer Series. This particular part is primarily about 3-D, but also includes a bit on optimisation.
 +
 +If you are already a 3-D guru, you may as well skip this text file, have a quick look at the sample program then go back to sleep, because I am going to explain in minute detail exactly how the routines work ;)
 +
 +If you would like to contact me, or the team, there are many ways you can do it : 
 +<code>
 +1) Write a message to Grant Smith/Denthor/Asphyxia in private mail on the ASPHYXIA BBS.
 +2) Write a message in the Programming conference on the For Your Eyes Only BBS (of which I am the Moderator ) This is preferred if you have a general programming query or problem others would benefit from.
 +4) Write to Denthor, EzE or Goth on Connectix.
 +5) Write to :  Grant Smith
 +               P.O.Box 270 Kloof
 +               3640
 +               Natal
 +6) Call me (Grant Smith) at (031) 73 2129 (leave a message if you call during varsity)
 +7) Write to mcphail@beastie.cs.und.ac.za on InterNet, and mention the word Denthor near the top of the letter.
 +</code>
 +
 +NB : If you are a representative of a company or BBS, and want ASPHYXIA to do you a demo, leave mail to me; we can discuss it.
 +
 +NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling quite lonely and want to meet/help out/exchange code with other demo groups. What do you have to lose? Leave a message here and we can work out how to transfer it. We really want to hear from you!
 +
 +==== Optimisation ====
 +
 +Before I begin with the note on 3-D, I would like to stress that many of these routines, and probably most of your own, could be sped up quite a bit with a little optimisation. 
 +
 +One must realise, however, that you must take a look at WHAT to optimise ... converting a routine that is only called once at startup into a tightly coded assembler routine may show off your merits as a coder, but does absolutely nothing to speed up your program.  
 + 
 +Something that is called often per frame is something that needs to be as fast as possible. For some, a much used procedure is the PutPixel procedure. Here is the putpixel procedure I gave you last week:
 +
 +<code pascal>
 +Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
 +BEGIN
 +  Asm
 +    push    ds                      { 14   clock ticks }
 +    push    es                      { 14 }
 +    mov     ax,[where]              { 8  }
 +    mov     es,ax                   { 2 }
 +    mov     bx,[X]                  { 8  }
 +    mov     dx,[Y]                  { 8  }
 +    push    bx                      { 15 }
 +    mov     bx, dx                  { 2  }
 +    mov     dh, dl                  { 2  }
 +    xor     dl, dl                  { 3  }
 +    shl     bx, 1                   { 2  }
 +    shl     bx, 1                   { 2  }
 +    shl     bx, 1                   { 2  }
 +    shl     bx, 1                   { 2  }
 +    shl     bx, 1                   { 2  }
 +    shl     bx, 1                   { 2  }
 +    add     dx, bx                  { 3  }
 +    pop     bx                      { 12 }
 +    add     bx, dx                  { 3  }
 +    mov     di, bx                  { 2 }
 +    xor     al,al                   { 3  }
 +    mov     ah, [Col]               { 8  }
 +    mov     es:[di],ah              { 10 }
 +    pop     es                      { 12 }
 +    pop     ds                      { 12 }
 +  End;
 +END;
 +</code>
 +
 +Total = 153 clock ticks
 +                            
 +<WRAP center round important>
 +Don't take my clock ticks as gospel, I probably got one or two wrong.
 +</WRAP>
 +
 +Right, now for some optimising. Firstly, if you have 286 instructions turned on, you may replace the 6 shl,1 with shl,6. 
 +
 +Secondly, the Pascal compiler automatically pushes and pops ES, so those two lines may be removed. DS:[SI] is not altered in this procedure, so we may remove those too. Also, instead of moving COL into ah, we move it into AL and call stosb (es:[di]:=al; inc di). Let's have a look at the routine now :
 +
 +<code pascal>
 +Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
 +BEGIN
 +  Asm
 +    mov     ax,[where]              { 8  }
 +    mov     es,ax                   { 2 }
 +    mov     bx,[X]                  { 8  }
 +    mov     dx,[Y]                  { 8  }
 +    push    bx                      { 15 }
 +    mov     bx, dx                  { 2  }
 +    mov     dh, dl                  { 2  }
 +    xor     dl, dl                  { 3  }
 +    shl     bx, 6                   { 8  }
 +    add     dx, bx                  { 3  }
 +    pop     bx                      { 12 }
 +    add     bx, dx                  { 3  }
 +    mov     di, bx                  { 2 }
 +    mov     al, [Col]               { 8  }
 +    stosb                           { 11 }
 +  End;
 +END;
 +</code>
 +Total = 95 clock ticks
 +
 +Now, let us move the value of BX directly into DI, thereby removing a costly push and pop. The MOV and the XOR of DX can be replaced by it's equivalent, **SHL DX,8**
 +
 +<code pascal>
 +Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); assembler;
 +asm
 +    mov     ax,[where]              { 8  }
 +    mov     es,ax                   { 2  }
 +    mov     bx,[X]                  { 8  }
 +    mov     dx,[Y]                  { 8  }
 +    mov     di,bx                   { 2  }
 +    mov     bx, dx                  { 2  }
 +    shl     dx, 8                   { 8  }
 +    shl     bx, 6                   { 8  }
 +    add     dx, bx                  { 3  }
 +    add     di, dx                  { 3  }
 +    mov     al, [Col]               { 8  }
 +    stosb                           { 11 }
 +end;
 +</code>
 +Total = 71 clock ticks
 +
 +As you can see, we have brought the clock ticks down from 153 ticks to 71 ticks ... quite an improvement. (The current ASPHYXIA putpixel takes 48 clock ticks).
 + 
 +As you can see, by going through your routines a few times, you can spot and remove unnecessary instructions, thereby greatly increasing the speed of your program.
 +
 +==== Defining a 3-D object ====
 +
 +Drawing an object in 3-D is not that easy. Sitting down and plotting a list of X,Y and Z points can be a time consuming business. So, let us first look at the three axes you are drawing them on :
 +
 +<code>
 +                    Y    Z
 +                   /|\  /
 +                    | /
 +             X<-----|----->
 +                    |
 +                   \|/
 +</code>
 +
 +X is the horisontal axis, from left to right. Y is the vertical axis, from top to bottom. Z is the depth, going straight into the screen.
 +
 +In this trainer, we are using lines, so we define 2 X,Y and Z coordinates, one for each end of the line. A line from far away, in the upper left of the X and Y axes, to close up in the bottom right of the X and Y axes, would look like this :
 +
 +<code>
 +{       x1 y1  z1   x2  y2 z2    }
 +    ( (-10,10,-10),(10,-10,10) )
 +</code>
 +
 +==== Rotating a point with matrixes ====
 +
 +<WRAP center round tip>
 +I thought that more then one matix are matrisese (sp), but my spellchecker insists it is matrixes, so I let it have it's way ;-)
 +</WRAP>
 +
 +Having a 3-D object is useless unless you can rotate it some way. For demonstration purposes, I will begin by working in two dimensions, X and Y.
 +
 +Let us say you have a point, A,B, on a graph.
 +
 +<code>
 +                      Y
 +                      |  /O1 (Cos (a)*A-Sin (a)*B , Sin (a)*A+Cos (a)*B)
 +                      |/      (A,B)
 +               X<-----|------O-->
 +                      |
 +                      |
 +</code>
 +
 +Now, let us say we rotate this point by 45 degrees anti-clockwise. The new A,B can be easily be calculated using sin and cos, by an adaption of our circle algorithm, ie. 
 +
 +<code pascal>
 +           A2:=Cos (45)*A - Sin (45)*B
 +           B2:=Sin (45)*A + Cos (45)*B
 +</code>
 +           
 +I recall that in standard 8 and 9, we went rather heavily into this in maths. If you have troubles, fine a 8/9/10 maths book and have a look; it will go through the proofs etc.
 +
 +Anyway, we have now rotated an object in two dimensions, AROUND THE Z AXIS. In matrix form, the equation looks like this :
 +
 +<code>
 +    Cos (a)   -Sin (a)      0        0        [  x ]
 +    Sin (a)    Cos (a)      0        0      . [  y ]
 +                        1        0        [  z ]
 +                        0        1        [  1 ]
 +</code>
 +
 +I will not go to deeply into matrixes math at this stage, as there are many books on the subject (it is not part of matric maths, however). To multiply a matrix, to add the products of the row of the left matrix and the column of the right matrix, and repeat this for all the columns of the left matrix. I don't explain it as well as my first year maths lecturer, but have a look at how I derived A2 and B2 above. Here are the other matrixes :
 +
 +Matrix for rotation around the Y axis :
 +
 +<code>
 +    Cos (a)      0       -Sin (a)    0        [  x ]
 +                        0        0      . [  y ]
 +    Sin (a)      0        Cos (a)    0        [  z ]
 +                        0        1        [  1 ]
 +</code>
 +
 +Matrix for rotation around the X axis :
 +<code>
 +                                        [  x ]
 +             Cos (a)   -Sin (a)    0      . [  y ]
 +             Sin (a)    Cos (a)    0        [  z ]
 +                        0        1        [  1 ]
 +</code>
 +
 +By putting all these matrixes together, we can translate out 3D points around the origin of 0,0,0. See the sample program for how we put them together.
 +
 +In the sample program, we have a constant, never changing base object.
 +This is rotated into a second variable, which is then drawn. I am sure many of you can thing of cool ways to change the base object, the effects of which will appear while the object is rotating. One idea is to "pulsate" a certain point of the object according to the beat of the music being played in the background. Be creative. If you feel up to it, you could make your own version of transformers ;)
 +
 +==== Drawing a 3D point to screen ====
 +
 +Having a rotated 3D object is useless unless we can draw it to screen.
 +But how do we show a 3D point on a 2D screen? The answer needs a bit of explaining. Examine the following diagram :
 +
 +<code>
 +              |         ________-------------
 +          ____|___------      o Object at X,Y,Z     o1 Object at X,Y,Z2
 + Eye -> O)____|___
 +              |   ------________
 +              |                 -------------- Field of vision
 +            Screen
 +</code>
 +
 +Let us pretend that the centre of the screen is the horizon of our little 3D world. If we draw a three dimensional line from object "o" to the centre of the eye, and place a pixel on the X and Y coordinates where it passes through the screen, we will notice that when we do the same with object o1, the pixel is closer to the horizon, even though their 3D X and Y coords are identical, but "o1"'s Z is larger then "o"'s. This means that the further away a point is, the closer to the horizon it is, or the smaller the object will appear. That sounds right, doesent it? But, I hear you cry, how do we translate this into a formula? The answer is quite simple. Divide your X and your Y by your Z.
 +Think about it. The larger the number you divide by, the closer to zero, or the horizon, is the result! This means, the bigger the Z, the further away is the object! Here it is in equation form :
 +
 +<code pascal>
 +       nx := 256*x div (z-Zoff)+Xoff
 +       ny := 256*y div (z-Zoff)+Yoff
 +</code>
 +
 +<WRAP center round info>
 +Zoff is how far away the entire object is, Xoff is the objects X value, and Yoff is the objects Y value. In the sample program, Xoff start off at 160 and Yoff starts off at 100, so that the object is in the middle of the screen.
 +</WRAP>
 +
 +The 256 that you times by is the perspective with which you are viewing. 
 +Changing this value gives you a "fish eye" effect when viewing the object. Anyway, there you have it! Draw a pixel at nx,ny, and viola! you are now doing 3D! Easy, wasn't it?
 +
 +==== Possible improvements ====
 +
 +This program is not the most optimised routine you will ever encounter (;-)) ... it uses 12 muls and 2 divs per point. (Asphyxia currently has 9 muls and 2 divs per point) Real math is used for all the calculations in the sample program, which is slow, so fixed point math should be implemented (I will cover fixed point math in a future trainer). The line routine currently being used is very slow. Chain-4 could be used to cut down on screen flipping times.
 +
 +Color values per line should be added, base object morphing could be put in, polygons could be used instead of lines, handling of more then one object should be implemented, clipping should be added instead of not drawing something if any part of it is out of bounds.
 +
 +In other words, you have a lot of work ahead of you ;)
 +
 +==== In closing ====
 +
 +There are a lot of books out there on 3D, and quite a few sample programs too. Have a look at them, and use the best bits to create your own, unique 3D engine, with which you can do anything you want. I am very interested in 3D (though EzE and Goth wrote most of ASPHYXIA'S 3D routines), and would like to see what you can do with it. Leave me a message through one of the means described above.
 +
 +I am delving into the murky world of texture mapping. If anyone out there has some routines on the subject and are interested in swapping, give me a buzz!
 +
 +What to do in future trainers? Help me out on this one! Are there any effects/areas you would like a bit of info on? Leave me a message! 
 +
 +I unfortunately did not get any messages regarding BBS's that carry this series, so the list that follows is the same one from last time. Give me your names, sysops!
 +
 +Aaaaargh!!! Try as I might, I can't think of a new quote. Next time, I promise! ;-)
 +
 +Bye for now,
 +Denthor
 +
 +These fine BBS's carry the ASPHYXIA DEMO TRAINER SERIES : (alphabetical)
 +
 +<code>
 +╔══════════════════════════╦════════════════╦═════╦═══╦════╦════╗
 +║BBS Name                  ║Telephone No.   ║Open ║Msg║File║Past║
 +╠══════════════════════════╬════════════════╬═════╬═══╬════╬════╣
 +║ASPHYXIA BBS #1           ║(031) 765-5312  ║ALL  ║ * ║ *  ║ *  ║
 +║ASPHYXIA BBS #2           ║(031) 765-6293  ║ALL  ║ * ║ *  ║ *  ║
 +║Connectix BBS             ║(031) 266-9992  ║ALL  ║ * ║    ║    ║
 +║For Your Eyes Only BBS    ║(031) 285-318   ║A/ ║ * ║ *  ║ *  ║
 +╚══════════════════════════╩════════════════╩═════╩═══╩════╩════╝
 +</code>
 +
 +Open = Open at all times or only A/H
 +Msg  = Available in message base
 +File = Available in file base
 +Past = Previous Parts available
 +
 +==== Code Source ====
 +
 +=== PASCAL ===
 +
 +<code pascal>
 +(*****************************************************************************)
 +(*                                                                           *)
 +(* TUT8.PAS - VGA Trainer Program 8 (in Pascal)                              *)
 +(*                                                                           *)
 +(* "The VGA Trainer Program" is written by Denthor of Asphyxia.  However it  *)
 +(* was limited to Pascal only in its first run.  All I have done is taken    *)
 +(* his original release, translated it to C++, and touched up a few things.  *)
 +(* I take absolutely no credit for the concepts presented in this code, and  *)
 +(* am NOT the person to ask for help if you are having trouble.  -Snowman    *)
 +(*                                                                           *)
 +(* Program Notes : This program presents the basis of 3D.                    *)
 +(*                                                                           *)
 +(* Author        : Grant Smith (Denthor)  - denthor@beastie.cs.und.ac.za     *)
 +(*                                                                           *)
 +(*****************************************************************************)
 +
 +{$X+}
 +USES Crt;
 +
 +CONST VGA = $A000;
 +      MaxLines = 12;
 +      Obj : Array [1..MaxLines,1..2,1..3] of integer =
 +        (
 +        ((-10,-10,-10),(10,-10,-10)),((-10,-10,-10),(-10,10,-10)),
 +        ((-10,10,-10),(10,10,-10)),((10,-10,-10),(10,10,-10)),
 +        ((-10,-10,10),(10,-10,10)),((-10,-10,10),(-10,10,10)),
 +        ((-10,10,10),(10,10,10)),((10,-10,10),(10,10,10)),
 +        ((-10,-10,10),(-10,-10,-10)),((-10,10,10),(-10,10,-10)),
 +        ((10,10,10),(10,10,-10)),((10,-10,10),(10,-10,-10))
 +        );  { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), }
 +            { (X2,Y2,Z2) ... for the two ends of a line }
 +
 +
 +Type Point = Record
 +               x,y,z:real;                { The data on every point we rotate}
 +             END;
 +     Virtual = Array [1..64000] of byte;  { The size of our Virtual Screen }
 +     VirtPtr = ^Virtual;                  { Pointer to the virtual screen }
 +
 +
 +VAR Lines : Array [1..MaxLines,1..2] of Point;  { The base object rotated }
 +    Translated : Array [1..MaxLines,1..2] of Point; { The rotated object }
 +    Xoff,Yoff,Zoff:Integer;               { Used for movement of the object }
 +    lookup : Array [0..360,1..2] of real; { Our sin and cos lookup table }
 +    Virscr : VirtPtr;                     { Our first Virtual screen }
 +    Vaddr  : word;                        { The segment of our virtual screen}
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
 +BEGIN
 +  asm
 +     mov        ax,0013h
 +     int        10h
 +  end;
 +END;
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure SetText;  { This procedure returns you to text mode.  }
 +BEGIN
 +  asm
 +     mov        ax,0003h
 +     int        10h
 +  end;
 +END;
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure Cls (Where:word;Col : Byte);
 +   { This clears the screen to the specified color }
 +BEGIN
 +     asm
 +        push    es
 +        mov     cx, 32000;
 +        mov     es,[where]
 +        xor     di,di
 +        mov     al,[col]
 +        mov     ah,al
 +        rep     stosw
 +        pop     es
 +     End;
 +END;
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure SetUpVirtual;
 +   { This sets up the memory needed for the virtual screen }
 +BEGIN
 +  GetMem (VirScr,64000);
 +  vaddr := seg (virscr^);
 +END;
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure ShutDown;
 +   { This frees the memory used by the virtual screen }
 +BEGIN
 +  FreeMem (VirScr,64000);
 +END;
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +procedure flip(source,dest:Word);
 +  { This copies the entire screen at "source" to destination }
 +begin
 +  asm
 +    push    ds
 +    mov     ax, [Dest]
 +    mov     es, ax
 +    mov     ax, [Source]
 +    mov     ds, ax
 +    xor     si, si
 +    xor     di, di
 +    mov     cx, 32000
 +    rep     movsw
 +    pop     ds
 +  end;
 +end;
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure Pal(Col,R,G,B : Byte);
 +  { This sets the Red, Green and Blue values of a certain color }
 +Begin
 +   asm
 +      mov    dx,3c8h
 +      mov    al,[col]
 +      out    dx,al
 +      inc    dx
 +      mov    al,[r]
 +      out    dx,al
 +      mov    al,[g]
 +      out    dx,al
 +      mov    al,[b]
 +      out    dx,al
 +   end;
 +End;
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Function rad (theta : real) : real;
 +  {  This calculates the degrees of an angle }
 +BEGIN
 +  rad := theta * pi / 180
 +END;
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure SetUpPoints;
 +  { This sets the basic offsets of the object, creates the lookup table and
 +    moves the object from a constant to a variable }
 +VAR loop1:integer;
 +BEGIN
 +  Xoff:=160;
 +  Yoff:=100;
 +  Zoff:=-256;
 +  For loop1:=0 to 360 do BEGIN
 +    lookup [loop1,1]:=sin (rad (loop1));
 +    lookup [loop1,2]:=cos (rad (loop1));
 +  END;
 +  For loop1:=1 to MaxLines do BEGIN
 +    Lines [loop1,1].x:=Obj [loop1,1,1];
 +    Lines [loop1,1].y:=Obj [loop1,1,2];
 +    Lines [loop1,1].z:=Obj [loop1,1,3];
 +    Lines [loop1,2].x:=Obj [loop1,2,1];
 +    Lines [loop1,2].y:=Obj [loop1,2,2];
 +    Lines [loop1,2].z:=Obj [loop1,2,3];
 +  END;
 +END;
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
 +  { This puts a pixel on the screen by writing directly to memory. }
 +BEGIN
 +  Asm
 +    mov     ax,[where]
 +    mov     es,ax
 +    mov     bx,[X]
 +    mov     dx,[Y]
 +    mov     di,bx
 +    mov     bx, dx                  {; bx = dx}
 +    shl     dx, 8
 +    shl     bx, 6
 +    add     dx, bx                  {; dx = dx + bx (ie y*320)}
 +    add     di, dx                  {; finalise location}
 +    mov     al, [Col]
 +    stosb
 +  End;
 +END;
 +
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure Line(a,b,c,d:integer;col:byte;where:word);
 +  { This draws a solid line from a,b to c,d in colour col }
 +  function sgn(a:real):integer;
 +  begin
 +       if a>0 then sgn:=+1;
 +       if a<0 then sgn:=-1;
 +       if a=0 then sgn:=0;
 +  end;
 +var i,s,d1x,d1y,d2x,d2y,u,v,m,n:integer;
 +begin
 +     u:= c - a;
 +     v:= d - b;
 +     d1x:= SGN(u);
 +     d1y:= SGN(v);
 +     d2x:= SGN(u);
 +     d2y:= 0;
 +     m:= ABS(u);
 +     n := ABS(v);
 +     IF NOT (M>N) then
 +     BEGIN
 +          d2x := 0 ;
 +          d2y := SGN(v);
 +          m := ABS(v);
 +          n := ABS(u);
 +     END;
 +     s := m shr 1;
 +     FOR i := 0 TO m DO
 +     BEGIN
 +          putpixel(a,b,col,where);
 +          s := s + n;
 +          IF not (s<m) THEN
 +          BEGIN
 +               s := s - m;
 +               a:= a + d1x;
 +               b := b + d1y;
 +          END
 +          ELSE
 +          BEGIN
 +               a := a + d2x;
 +               b := b + d2y;
 +          END;
 +     end;
 +END;
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure DrawLogo;
 +  { This draws 'ASPHYXIA' at the top of the screen in little balls }
 +CONST ball : Array [1..5,1..5] of byte =
 +         ((0,1,1,1,0),
 +          (1,4,3,2,1),
 +          (1,3,3,2,1),
 +          (1,2,2,2,1),
 +          (0,1,1,1,0));
 +
 +VAR Logo : Array [1..5] of String;
 +    loop1,loop2,loop3,loop4:integer;
 +BEGIN
 +  pal (13,0,63,0);
 +  pal (1,0,0,40);
 +  pal (2,0,0,45);
 +  pal (3,0,0,50);
 +  pal (4,0,0,60);
 +  Logo[1]:=' O  OOO OOO O O O O O O OOO  O ';
 +  Logo[2]:='O O O   O O O O O O O O  O  O O';
 +  Logo[3]:='OOO OOO OOO OOO  O      OOO';
 +  Logo[4]:='O O   O O   O O  O  O O  O  O O';
 +  Logo[5]:='O O OOO O   O O  O  O O OOO O O';
 +  For loop1:=1 to 5 do
 +    For loop2:=1 to 31 do
 +      if logo[loop1][loop2]='O' then
 +        For loop3:=1 to 5 do
 +          For loop4:=1 to 5 do
 +            putpixel (loop2*10+loop3,loop1*4+loop4,ball[loop3,loop4],vaddr);
 +END;
 +
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure RotatePoints (X,Y,Z:Integer);
 +  { This rotates object lines by X,Y and Z; then places the result in
 +    TRANSLATED }
 +VAR loop1:integer;
 +    temp:point;
 +BEGIN
 +  For loop1:=1 to maxlines do BEGIN
 +    temp.x:=lines[loop1,1].x;
 +    temp.y:=lookup[x,2]*lines[loop1,1].y - lookup[x,1]*lines[loop1,1].z;
 +    temp.z:=lookup[x,1]*lines[loop1,1].y + lookup[x,2]*lines[loop1,1].z;
 +
 +    translated[loop1,1]:=temp;
 +
 +    If y>0 then BEGIN
 +      temp.x:=lookup[y,2]*translated[loop1,1].x - lookup[y,1]*translated[loop1,1].y;
 +      temp.y:=lookup[y,1]*translated[loop1,1].x + lookup[y,2]*translated[loop1,1].y;
 +      temp.z:=translated[loop1,1].z;
 +      translated[loop1,1]:=temp;
 +    END;
 +
 +    If z>0 then BEGIN
 +      temp.x:=lookup[z,2]*translated[loop1,1].x + lookup[z,1]*translated[loop1,1].z;
 +      temp.y:=translated[loop1,1].y;
 +      temp.z:=-lookup[z,1]*translated[loop1,1].x + lookup[z,2]*translated[loop1,1].z;
 +      translated[loop1,1]:=temp;
 +    END;
 +
 +    temp.x:=lines[loop1,2].x;
 +    temp.y:=cos (rad(X))*lines[loop1,2].y - sin (rad(X))*lines[loop1,2].z;
 +    temp.z:=sin (rad(X))*lines[loop1,2].y + cos (rad(X))*lines[loop1,2].z;
 +
 +    translated[loop1,2]:=temp;
 +
 +    If y>0 then BEGIN
 +      temp.x:=cos (rad(Y))*translated[loop1,2].x - sin (rad(Y))*translated[loop1,2].y;
 +      temp.y:=sin (rad(Y))*translated[loop1,2].x + cos (rad(Y))*translated[loop1,2].y;
 +      temp.z:=translated[loop1,2].z;
 +      translated[loop1,2]:=temp;
 +    END;
 +
 +    If z>0 then BEGIN
 +      temp.x:=cos (rad(Z))*translated[loop1,2].x + sin (rad(Z))*translated[loop1,2].z;
 +      temp.y:=translated[loop1,2].y;
 +      temp.z:=-sin (rad(Z))*translated[loop1,2].x + cos (rad(Z))*translated[loop1,2].z;
 +      translated[loop1,2]:=temp;
 +    END;
 +  END;
 +END;
 +
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure DrawPoints;
 +  { This draws the translated object to the virtual screen }
 +VAR loop1:Integer;
 +    nx,ny,nx2,ny2:integer;
 +    temp:integer;
 +BEGIN
 +  For loop1:=1 to MaxLines do BEGIN
 +    If (translated[loop1,1].z+zoff<0) and (translated[loop1,2].z+zoff<0) then BEGIN
 +      temp:=round (translated[loop1,1].z+zoff);
 +      nx :=round (256*translated[loop1,1].X) div temp+xoff;
 +      ny :=round (256*translated[loop1,1].Y) div temp+yoff;
 +      temp:=round (translated[loop1,2].z+zoff);
 +      nx2:=round (256*translated[loop1,2].X) div temp+xoff;
 +      ny2:=round (256*translated[loop1,2].Y) div temp+yoff;
 +      If (NX > 0) and (NX < 320) and (NY > 25) and (NY < 200) and
 +         (NX2> 0) and (NX2< 320) and (NY2> 25) and (NY2< 200) then
 +           line (nx,ny,nx2,ny2,13,vaddr);
 +    END;
 +  END;
 +END;
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure ClearPoints;
 +  { This clears the translated object from the virtual screen ... believe it
 +    or not, this is faster then a straight "cls (vaddr,0)" }
 +VAR loop1:Integer;
 +    nx,ny,nx2,ny2:Integer;
 +    temp:integer;
 +BEGIN
 +  For loop1:=1 to MaxLines do BEGIN
 +    If (translated[loop1,1].z+zoff<0) and (translated[loop1,2].z+zoff<0) then BEGIN
 +      temp:=round (translated[loop1,1].z+zoff);
 +      nx :=round (256*translated[loop1,1].X) div temp+xoff;
 +      ny :=round (256*translated[loop1,1].Y) div temp+yoff;
 +      temp:=round (translated[loop1,2].z+zoff);
 +      nx2:=round (256*translated[loop1,2].X) div temp+xoff;
 +      ny2:=round (256*translated[loop1,2].Y) div temp+yoff;
 +      If (NX > 0) and (NX < 320) and (NY > 25) and (NY < 200) and
 +         (NX2> 0) and (NX2< 320) and (NY2> 25) and (NY2< 200) then
 +           line (nx,ny,nx2,ny2,0,vaddr);
 +    END;
 +  END;
 +END;
 +
 +
 +{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 +Procedure MoveAround;
 +  { This is the main display procedure. Firstly it brings the object towards
 +    the viewer by increasing the Zoff, then passes control to the user }
 +VAR deg,loop1:integer;
 +    ch:char;
 +BEGIN
 +  deg:=0;
 +  ch:=#0;
 +  Cls (vaddr,0);
 +  DrawLogo;
 +  For loop1:=-256 to -40 do BEGIN
 +    zoff:=loop1*2;
 +    RotatePoints (deg,deg,deg);
 +    DrawPoints;
 +    flip (vaddr,vga);
 +    ClearPoints;
 +    deg:=(deg+5) mod 360;
 +  END;
 +
 +  Repeat
 +    if keypressed then BEGIN
 +      ch:=upcase (Readkey);
 +      Case ch of 'A' : zoff:=zoff+5;
 +                 'Z' : zoff:=zoff-5;
 +                 ',' : xoff:=xoff-5;
 +                 '.' : xoff:=xoff+5;
 +                 'S' : yoff:=yoff-5;
 +                 'X' : yoff:=yoff+5;
 +      END;
 +    END;
 +    DrawPoints;
 +    flip (vaddr,vga);
 +    ClearPoints;
 +    RotatePoints (deg,deg,deg);
 +    deg:=(deg+5) mod 360;
 +  Until ch=#27;
 +END;
 +
 +
 +BEGIN
 +  SetUpVirtual;
 +  Writeln ('Greetings and salutations! Hope you had a great Christmas and New');
 +  Writeln ('year! ;-) ... Anyway, this tutorial is on 3-D, so this is what is');
 +  Writeln ('going to happen ... a wireframe square will come towards you.');
 +  Writeln ('When it gets close, you get control. "A" and "Z" control the Z');
 +  Writeln ('movement, "," and "." control the X movement, and "S" and "X"');
 +  Writeln ('control the Y movement. I have not included rotation control, but');
 +  Writeln ('it should be easy enough to put in yourself ... if you have any');
 +  Writeln ('hassles, leave me mail.');
 +  Writeln;
 +  Writeln ('Read the main text file for ideas on improving this code ... and');
 +  Writeln ('welcome to the world of 3-D!');
 +  writeln;
 +  writeln;
 +  Write ('  Hit any key to contine ...');
 +  Readkey;
 +  SetMCGA;
 +  SetUpPoints;
 +  MoveAround;
 +  SetText;
 +  ShutDown;
 +  Writeln ('All done. This concludes the eigth sample program in the ASPHYXIA');
 +  Writeln ('Training series. You may reach DENTHOR under the names of GRANT');
 +  Writeln ('SMITH/DENTHOR/ASPHYXIA on the ASPHYXIA BBS. I am also an avid');
 +  Writeln ('Connectix BBS user, and occasionally read RSAProg.');
 +  Writeln ('For discussion purposes, I am also the moderator of the Programming');
 +  Writeln ('newsgroup on the For Your Eyes Only BBS.');
 +  Writeln ('The numbers are available in the main text. You may also write to me at:');
 +  Writeln ('             Grant Smith');
 +  Writeln ('             P.O. Box 270');
 +  Writeln ('             Kloof');
 +  Writeln ('             3640');
 +  Writeln ('I hope to hear from you soon!');
 +  Writeln; Writeln;
 +  Write   ('Hit any key to exit ...');
 +  Readkey;
 +END.
 +</code>
 +
 +=== C ===
 +
 +<code c file:tut8.cpp>
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// TUTPROG8.CPP - VGA Trainer Program 8 (in Turbo C++ 3.0)                 //
 +//                                                                         //
 +// "The VGA Trainer Program" is written by Denthor of Asphyxia. However it //
 +// was limited to only Pascal in its first run.  All I have done is taken  //
 +// his original release, translated it to C++ and touched up a few things. //
 +// I take absolutely no credit for the concepts presented in this code and //
 +// am NOT the person to ask for help if you are having trouble.            //
 +//                                                                         //
 +// Program Notes : This program presents the basics of 3D.  Please note    //
 +//                 that the compiled C++ version of this program runs      //
 +//                 much faster than the Pascal version.  If you are        //
 +//                 a 486DX/33 or higher, you may wish to turn turbo off.   //
 +//                                                                         //
 +//                 If you are compiling this program from within the       //
 +//                 Turbo C++ environment, you must go under Options,       //
 +//                 Debugger, and change the "Program Heap Size" to a value //
 +//                 80 or greater.  If you are going to be fooling around   //
 +//                 with the code a bit, I suggest raising this to about    //
 +//                 100 just to be on the safe side.  You don't have to     //
 +//                 worry about this if you are compiling command line.     //
 +//                                                                         //
 +//                 Just for reference, this is what I use:                 //
 +//                                                                         //
 +//                    tcc -mc -a -G -2 -O tut8.cpp                         //
 +//                                                                         //
 +//                 The way things are set up, there is no need to compile  //
 +//                 or link tut8.cpp and gfx1.cpp seperately.               //
 +//                                                                         //
 +//                 The Compact memory model (-mc) seems to provide the     //
 +//                 best results for this tutorial.  Remember, use this     //
 +//                 memory model when you have little code (less than 64k)  //
 +//                 and lots of data.                                       //
 +//                                                                         //
 +// Author        : Grant Smith (Denthor) - denthor@beastie.cs.und.ac.za    //
 +// Translator    : Christopher G. Mann   - r3cgm@dax.cc.uakron.edu         //
 +//                                                                         //
 +// Last Modified : January 14, 1995                                        //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +//               //
 +// INCLUDE FILES //
 +//               //
 +
 +  #include <alloc.h>
 +                           // farcalloc()
 +  #include <conio.h>
 +                           // clrscr(), getch(), kbhit()
 +  #include <dos.h>
 +                           // FP_SEG, geninterrupt()
 +  #include <iostream.h>
 +                           // cout
 +  #include <math.h>
 +                           // sin(), cos()
 +  #include <stdlib.h>
 +                           // exit()
 +  #include "gfx1.cpp"
 +
 +//          //
 +// TYPEDEFS //
 +//          //
 +
 +  typedef unsigned char byte;
 +  typedef unsigned int  word;
 +
 +//           //
 +// CONSTANTS //
 +//           //
 +
 +  const MAXLINES = 12;     // the number of lines in our cube
 +
 +  // The 3-D coordinates of our object ... stored as {X1,Y1,Z1},
 +  // {X2,Y2,Z2} ... for the two ends of a line
 +  const int Obj[MAXLINES][2][3] =
 +    { {{-10,-10,-10}, { 10,-10,-10}}, //  0        .-----2----.
 +      {{-10,-10,-10}, {-10, 10,-10}}, //  1       /        /|
 +      {{-10, 10,-10}, { 10, 10,-10}}, //  2      9 |        A |
 +      {{ 10,-10,-10}, { 10, 10,-10}}, //  3     /  |       /  |
 +      {{-10,-10, 10}, { 10,-10, 10}}, //  4    .------6---.   3
 +      {{-10,-10, 10}, {-10, 10, 10}}, //  5    |        |   |
 +      {{-10, 10, 10}, { 10, 10, 10}}, //  6    |        7   |
 +      {{ 10,-10, 10}, { 10, 10, 10}}, //  7    |        |   |
 +      {{-10,-10, 10}, {-10,-10,-10}}, //  8    5   '----0-|---'
 +      {{-10, 10, 10}, {-10, 10,-10}}, //  9    |  /        /
 +      {{ 10, 10, 10}, { 10, 10,-10}}, //  A    | 8        | B
 +      {{ 10,-10, 10}, { 10,-10,-10}}  //  B    |/         |/
 +    };                                //       `-----4----'
 +
 +//                     //
 +// FUNCTION PROTOTYPES //
 +//                     //
 +
 +  // MEMORY ALLOCATION FUNCTIONS
 +  void SetUpVirtual ();
 +  void ShutDown     ();
 +
 +  // LOGO-FUNCTION
 +  void DrawLogo     ();
 +
 +  // 3D POINTS FUNCTIONS
 +  void SetUpPoints  ();
 +  void RotatePoints (int X, int Y, int Z);
 +  void DrawPoints   ();
 +  void ClearPoints  ();
 +
 +  // MID-LEVEL FUNCTION
 +  void MoveAround   ();
 +
 +//            //
 +// STRUCTURES //
 +//            //
 +
 +  // The data on every point we rotate
 +  struct Point {
 +    float    x;
 +    float    y;
 +    float    z;
 +  };
 +
 +//                              //
 +// GLOBAL VARIABLE DECLARATIONS //
 +//                              //
 +
 +  byte far *Virscr=NULL;           // Pointer to our virtual screen
 +  word Vaddr;                      // Segment of our virtual screen
 +  float Lookup[360][2];            // Our sin and cos lookup table
 +  int Xoff, Yoff, Zoff;            // Used for movement of the objects
 +  Point Lines[MAXLINES][2];        // The base object rotated
 +  Point Translated[MAXLINES][2];   // The rotated object
 +
 +
 +///////////////////////////////////////////////////////////////////////////////
 +//                                                                           //
 +//                                MAIN FUNCTION                              //
 +//                                                                           //
 +///////////////////////////////////////////////////////////////////////////////
 +
 +void main() {
 +
 +  SetUpVirtual();
 +  clrscr();
 +  cout
 +    << "Greetings and salutations! Hope you had a great Christmas and New\n"
 +    << "year! ;-) ... Anyway, this tutorial is on 3-D, so this is what is\n"
 +    << "going to happen ... a wireframe square will come towards you.\n"
 +    << "When it gets close, you get control. ""A"" and ""Z"" control the Z\n"
 +    << "movement, "","" and ""."" control the X movement, and ""S"" and ""X""\n"
 +    << "control the Y movement. I have not included rotation control, but\n"
 +    << "it should be easy enough to put in yourself ... if you have any\n"
 +    << "hassles, leave me mail.\n\n";
 +  cout
 +    << "Read the main text file for ideas on improving this code ... and\n"
 +    << "welcome to the world of 3-D!\n\n";
 +  cout << "Hit any key to contine ...\n";
 +  getch();
 +
 +  SetMCGA();
 +
 +  SetUpPoints();
 +  MoveAround();
 +
 +  ShutDown();
 +  SetText();
 +
 +  cout
 +    << "All done. This concludes the eigth sample program in the ASPHYXIA\n"
 +    << "Training series. You may reach DENTHOR under the names of GRANT\n"
 +    << "SMITH/DENTHOR/ASPHYXIA on the ASPHYXIA BBS. I am also an avid\n"
 +    << "Connectix BBS user, and occasionally read RSAProg.\n"
 +    << "For discussion purposes, I am also the moderator of the Programming\n"
 +    << "newsgroup on the For Your Eyes Only BBS.\n"
 +    << "The numbers are available in the main text. You may also write to me at:\n"
 +    << "             Grant Smith\n"
 +    << "             P.O. Box 270\n"
 +    << "             Kloof\n"
 +    << "             3640\n"
 +    << "I hope to hear from you soon!\n\n\n";
 +  cout << "Hit any key to exit ...\n";
 +  getch();
 +
 +}
 +
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// SetUpVirtual() - This sets up the memory needed for the virtual screen. //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void SetUpVirtual() {
 +
 +  Virscr = (byte far *) farcalloc(64000,1);
 +
 +  // always check to see if enough memory was allocated
 +  if (Virscr == NULL) {
 +    SetText();
 +    cout << "Insufficient memory for virtual screens, exiting...";
 +    exit(1);
 +  }
 +
 +  Vaddr = FP_SEG(Virscr);
 +
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// ShutDown() - This frees the memory used by the virtual screen.          //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void ShutDown() {
 +  free(Virscr);
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// DrawLogo() - This draws 'SNOWMAN' at the top of the screen in little    //
 +//              balls.                                                     //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void DrawLogo() {
 +
 +  const byte ball[5][5] = { 0,1,1,1,0,
 +                            1,4,3,2,1,
 +                            1,3,3,2,1,
 +                            1,2,2,2,1,
 +                            0,1,1,1,0
 +                          };
 +
 +  const char *Logo[5] = { {"OOO OOO OOO O O O  O O  OOO OOO"},
 +                          {"  O O O O O O O O O O O O O O"},
 +                          {"OOO O O O O O O O O O O OOO O O"},
 +                          {"  O O O O O O O O O O O O O O O"},
 +                          {"OOO O O OOO  O O  O O O O O O O"}
 +                        };
 +
 +  int loop1, loop2, loop3, loop4;
 +
 +  Pal(13, 0,63, 0); // set the color for the cube lines
 +  Pal( 1, 0, 0,40); // set the colors for the dots
 +  Pal( 2, 0, 0,45);
 +  Pal( 3, 0, 0,50);
 +  Pal( 4, 0, 0,60);
 +
 +  for (loop1=0; loop1<5; loop1++)        // for each line...
 +    for (loop2=0; loop2<31; loop2++)       // is it active?
 +      if (Logo[loop1][loop2] == 'O')
 +      for (loop3=0; loop3<5; loop3++)        // y coordinate of the ball
 +        for (loop4=0; loop4<5; loop4++)        // x coordinate of the ball
 +          Putpixel ((loop2+1)*10+loop3, (loop1+1)*4+loop4,
 +                    ball[loop3][loop4],Vaddr);
 +
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// SetUpPoints() - This sets the basic offsets of the object, creates the  //
 +//                 lookup table, and moves the object from a constant to a //
 +//                 variable.                                               //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void SetUpPoints() {
 +
 +  int loop1;
 +
 +  // set the starting offsets of the cube
 +  Xoff =  160;
 +  Yoff =  100;
 +  Zoff = -256;
 +
 +  // generate the sin() and cos() tables
 +  for (loop1=0; loop1<361; loop1++) {
 +    Lookup [loop1][0] = sin(rad(loop1));
 +    Lookup [loop1][1] = cos(rad(loop1));
 +  }
 +
 +  // move the Obj constant array into the Lines array
 +  for (loop1=0; loop1<MAXLINES; loop1++) {
 +    Lines[loop1][0].x = Obj[loop1][0][0];
 +    Lines[loop1][0].y = Obj[loop1][0][1];
 +    Lines[loop1][0].z = Obj[loop1][0][2];
 +    Lines[loop1][1].x = Obj[loop1][1][0];
 +    Lines[loop1][1].y = Obj[loop1][1][1];
 +    Lines[loop1][1].z = Obj[loop1][1][2];
 +  }
 +
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// RotatePoints() - This rotates object lines by X, Y, and Z.  Then it     //
 +//                  places the result in Translated.                       //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void RotatePoints (int X,int Y,int Z) {
 +
 +  int loop1;
 +  Point temp;
 +
 +  // for each line...
 +  for (loop1=0; loop1<MAXLINES; loop1++) {
 +
 +    // start point of line
 +
 +    temp.x = Lines[loop1][0].x;
 +    temp.y = Lookup[X][1]*Lines[loop1][0].y - Lookup[X][0]*Lines[loop1][0].z;
 +    temp.z = Lookup[X][0]*Lines[loop1][0].y + Lookup[X][1]*Lines[loop1][0].z;
 +    Translated[loop1][0] = temp;
 +
 +    if (Y > 0) {
 +      temp.x = Lookup[Y][1]*Translated[loop1][0].x - Lookup[Y][0]*Translated[loop1][0].y;
 +      temp.y = Lookup[Y][0]*Translated[loop1][0].x + Lookup[Y][1]*Translated[loop1][0].y;
 +      temp.z = Translated[loop1][0].z;
 +      Translated[loop1][0] =temp;
 +    }
 +
 +    if (Z > 0) {
 +      temp.x = Lookup[Z][1]*Translated[loop1][0].x + Lookup[Z][0]*Translated[loop1][0].z;
 +      temp.y = Translated[loop1][0].y;
 +      temp.z = (-Lookup[Z][0])*Translated[loop1][0].x + Lookup[Z][1]*Translated[loop1][0].z;
 +      Translated[loop1][0] = temp;
 +    }
 +
 +    // end point of line
 +
 +    temp.x = Lines[loop1][1].x;
 +    temp.y = cos(rad(X))*Lines[loop1][1].y - sin(rad(X))*Lines[loop1][1].z;
 +    temp.z = sin(rad(X))*Lines[loop1][1].y + cos(rad(X))*Lines[loop1][1].z;
 +    Translated[loop1][1] = temp;
 +
 +    if (Y > 0) {
 +      temp.x = cos(rad(X))*Translated[loop1][1].x - sin(rad(Y))*Translated[loop1][1].y;
 +      temp.y = sin(rad(Y))*Translated[loop1][1].x + cos(rad(Y))*Translated[loop1][1].y;
 +      temp.z = Translated[loop1][1].z;
 +      Translated[loop1][1] = temp;
 +    }
 +
 +    if (Z > 0) {
 +      temp.x = cos(rad(Z))*Translated[loop1][1].x + sin(rad(Z))*Translated[loop1][1].z;
 +      temp.y = Translated[loop1][1].y;
 +      temp.z = (-sin(rad(Z)))*Translated[loop1][1].x + cos(rad(Z))*Translated[loop1][1].z;
 +      Translated[loop1][1] = temp;
 +    }
 +  }
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// DrawPoints() - This draws the translated object to the virtual screen.  //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void DrawPoints() {
 +
 +  int loop1, nx, ny, nx2, ny2, temp;
 +
 +  for (loop1=0; loop1<MAXLINES; loop1++) {
 +    if ((Translated[loop1][0].z+Zoff<0) && (Translated[loop1][1].z+Zoff<0)) {
 +
 +      // start point of line
 +      temp = Translated[loop1][0].z + Zoff;
 +      nx   = ((256*Translated[loop1][0].x) / temp) + Xoff;
 +      ny   = ((256*Translated[loop1][0].y) / temp) + Yoff;
 +
 +      // end point of line
 +      temp = Translated[loop1][1].z + Zoff;
 +      nx2  = ((256*Translated[loop1][1].x) / temp) + Xoff;
 +      ny2  = ((256*Translated[loop1][1].y) / temp) + Yoff;
 +
 +      // check to make sure the line is within bounds
 +      if ((nx >-1) && (nx <320) && (ny >25) && (ny <200) &&
 +          (nx2>-1) && (nx2<320) && (ny2>25) && (ny2<200))
 +        Line(nx,ny,nx2,ny2,13,Vaddr);
 +    }
 +  }
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// ClearPoints() - This clears the translated object from the virtual      //
 +//                 screen ... believe it or not, this is faster than a     //
 +//                 straight cls(0,vaddr);                                  //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void ClearPoints() {
 +
 +  int loop1, nx, ny, nx2, ny2, temp;
 +
 +  for (loop1=0; loop1<MAXLINES; loop1++) {
 +    if ((Translated[loop1][0].z+Zoff<0) && (Translated[loop1][1].z+Zoff<0)) {
 +
 +      // start point of line
 +      temp = Translated[loop1][0].z + Zoff;
 +      nx   = ((256*Translated[loop1][0].x) / temp) + Xoff;
 +      ny   = ((256*Translated[loop1][0].y) / temp) + Yoff;
 +
 +      // end point of line
 +      temp = Translated[loop1][1].z + Zoff;
 +      nx2  = ((256*Translated[loop1][1].x) / temp) + Xoff;
 +      ny2  = ((256*Translated[loop1][1].y) / temp) + Yoff;
 +
 +      // check to make sure the line is within bounds
 +      if ((nx >-1) && (nx <320) && (ny >25) && (ny <200) &&
 +          (nx2>-1) && (nx2<320) && (ny2>25) && (ny2<200))
 +        Line(nx,ny,nx2,ny2,0,Vaddr);
 +    }
 +  }
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// MoveAround() - This is the main display function.  First it brings the  //
 +//                object towards the viewer by increasing the Zoff, then   //
 +//                it passes control to the user.                           //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void MoveAround() {
 +
 +  // For some reason, the values we defined Xoff, Yoff, and Zoff to be in
 +  // the function SetUpPoints() won't hold until this point.  If you know
 +  // the reason, please send it to r3cgm@dax.cc.uakron.edu
 +  Xoff =  160;  // redefined
 +  Yoff =  100;  // redefined
 +  Zoff = -256;  // redefined
 +
 +  int deg=0, loop1;
 +  byte ch=1; // assign a dummy value to ch
 +
 +  Cls(0,Vaddr);
 +
 +  DrawLogo();
 +
 +  for (loop1=(-256); loop1<(-39); loop1++) {
 +    Zoff = loop1 * 2;
 +    RotatePoints(deg,deg,deg);
 +    DrawPoints();
 +    Flip(Vaddr,VGA);
 +    ClearPoints();
 +    deg = (deg + 5) % 360;
 +  }
 +
 +  do {
 +
 +    if (kbhit()) {
 +      ch = getch();
 +      switch (ch) {
 +        // We are not going to use toupper() because if we did, we'd have
 +        // to include the whole ctype.h file.  This might take a little more
 +        // time, but the program will be smaller.  We already have 7 include
 +        // files, and its getting a bit rediculous.
 +        case 'A': case 'a': Zoff += 5; break;  // away
 +        case 'Z': case 'z': Zoff -= 5; break;  // toward
 +        case ',':           Xoff -= 5; break;  // left
 +        case '.':           Xoff += 5; break;  // right
 +        case 'S': case 's': Yoff -= 5; break;  // down
 +        case 'X': case 'x': Yoff += 5; break;  // up
 +      }
 +    }
 +    DrawPoints();
 +    Flip(Vaddr,VGA);
 +    ClearPoints();
 +    RotatePoints(deg,deg,deg);
 +    deg = (deg + 5) % 360;
 +
 +    // if the key pressed above was 0 (i.e. a control character) then
 +    // read the character code
 +    if (ch == 0) ch = getch();
 +
 +  } while (ch != 27); // if the escape code was 27 (escape key) then exit
 +}
 +</code>
 +
 +<code c file:gfx1.cpp>
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// GFX1.CPP - VGA Trainer Program secondary module containing graphics     //
 +//            functions.  Note: This module does not follow a lot of good  //
 +//            programming practices.  It was built to be used with the     //
 +//            VGA tutorial series.  If you are planning on using this      //
 +//            module with a different source file, some modifications may  //
 +//            be necessary.                                                //
 +//                                                                         //
 +// Author        : Grant Smith (Denthor) - denthor@beastie.cs.und.ac.za    //
 +// Translator    : Christopher G. Mann   - r3cgm@dax.cc.uakron.edu         //
 +//                                                                         //
 +// Last Modified : January 13, 1995                                        //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +//               //
 +// INCLUDE FILES //
 +//               //
 +
 +  #include <dos.h>
 +                           // geninterrupt()
 +  #include <math.h>
 +                           // abs()
 +
 +//         //
 +// DEFINES //
 +//         //
 +
 +  #if !defined(PI)
 +    #define PI 3.1415927
 +  #endif
 +
 +  #if !defined(VGA)
 +    #define VGA 0xA000
 +  #endif
 +
 +//          //
 +// TYPEDEFS //
 +//          //
 +
 +  typedef unsigned char byte;
 +  typedef unsigned int  word;
 +
 +//                     //
 +// FUNCTION PROTOTYPES //
 +//                     //
 +
 +  // MODE SETTING FUNCTIONS
 +  void  SetMCGA     ();
 +  void  SetText     ();
 +
 +  // PALLETTE FUNCTIONS
 +  void  Pal         (byte Col, byte  R, byte  G, byte  B);
 +  void  GetPal      (byte Col, byte &R, byte &G, byte &B);
 +
 +  // MATH-LIKE FUNCTIONS
 +  float rad         (float theta);
 +  int   sgn         (int a);
 +
 +  // DRAWING FUNCTIONS
 +  void  Putpixel    (word X, word Y, byte Col, word Where);
 +  void  Line        (int a, int b, int c, int  d, int col, word Where);
 +
 +  // VIDEO MEMORY FUNCTIONS
 +  void  Cls         (byte Col, word Where);
 +  void  Flip        (word source, word dest);
 +
 +
 +//--------------------------MODE SETTING FUNCTIONS-------------------------//
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// SetMCGA() - This function gets you into 320x200x256 mode.               //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void SetMCGA() {
 +  _AX = 0x0013;
 +  geninterrupt (0x10);
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// SetText() - This function gets you into text mode.                      //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void SetText() {
 +  _AX = 0x0003;
 +  geninterrupt (0x10);
 +}
 +
 +
 +//----------------------------PALLETTE FUNCTIONS---------------------------//
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// Pal() - This sets the Red, Green, and Blue values of a certain color.   //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void Pal (byte Col, byte  R, byte  G, byte  B) {
 +  asm {
 +    mov     dx, 0x3C8    // load DX with 3C8 (write pallette function)
 +    mov     al, [Col]    // move color to AL
 +    out     dx, al       // write DX to the VGA (tell VGA that we want to
 +                         //   work with the color in AL
 +    inc     dx           // load DX with 3C9 (write RGB colors)
 +    mov     al, [R]      // move Red   to AL
 +    out     dx, al       // write DX to VGA (tell VGA that we want to use
 +                         //   the Red value in AL
 +    mov     al, [G]      // move Green to AL
 +    out     dx, al       // write DX to VGA
 +    mov     al, [B]      // move Blue  to AL
 +    out     dx, al       // write DX to VGA
 +  }
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// GetPal() - This reads the values of the Red, Green, and Blue values of  //
 +//            a certain color.  This function uses pass-by-reference.      //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void GetPal (byte Col, byte &R, byte &G, byte &B) {
 +
 +  byte rr,gg,bb;
 +
 +  asm {
 +    mov     dx, 0x03C7   // load DX with 3C7 (read pallette function)
 +    mov     al, [Col]    // move color to AL
 +    out     dx, al       // write DX to the VGA (tell VGA that we want to
 +                         //   work with the color in AL
 +    add     dx, 2        // load DX with 3C9 (read RGB colors)
 +    in      al, dx       // read Red   to AL
 +    mov     [rr],al      // copy AL to rr
 +    in      al, dx       // read Green to AL
 +    mov     [gg],al      // copy AL to gg
 +    in      al, dx       // read Blue  to AL
 +    mov     [bb],al      // copy AL to bb
 +  }
 +
 +  R = rr;
 +  G = gg;
 +  B = bb;
 +
 +}
 +
 +
 +//----------------------------MATH-LIKE FUNCTIONS--------------------------//
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// rad() - This calculates the degrees of an angle.                        //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +float rad(float theta) {
 +  return ((theta * PI)/180);
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// sgn() - This checks the sign of an integer and returns a 1, -1, or 0.   //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +int sgn (int a) {
 +
 +  if (a > 0)  return +1;
 +  if (a < 0)  return -1;
 +  return 0;
 +}
 +
 +
 +//-----------------------------DRAWING FUNCTIONS---------------------------//
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// Putpixel() - This puts a pixel on the screen by writing directly to     //
 +//              memory.                                                    //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void Putpixel (word X, word Y, byte Col, word Where) {
 +  asm {
 +    push    ds           // save DS
 +    push    es           // save ES
 +    mov     ax, [Where]  // move segment of Where to AX
 +    mov     es, ax       // set ES to segment of Where
 +    mov     bx, [X]      // set BX to X
 +    mov     dx, [Y]      // set DX to Y
 +    push    bx           // save BX (our X value)
 +    mov     bx, dx       // now BX and DX are equal to Y
 +    mov     dh, dl       // copy DL to DH (multiply Y by 256)
 +    xor     dl, dl       // zero out DL
 +    shl     bx, 6        // shift BX left 6 places (multiply Y by 64).
 +    add     dx, bx       // add BX to DX (Y*64 + Y*256 = Y*320)
 +    pop     bx           // restore BX (X coordinate)
 +    add     bx, dx       // add BX to DX (Y*320 + X).  this gives you
 +                         //   the offset in memory you want
 +    mov     di, bx       // move the offset to DI
 +    xor     al, al       // zero out AL
 +    mov     ah, [Col]    // move value of Col into AH
 +    mov     es:[di], ah  // move Col to the offset in memory (DI)
 +    pop     es           // restore ES
 +    pop     ds           // restore DS
 +  }
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// Line() - This draws a line from a,b to c,d of color col on screne Where //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void Line(int a, int b, int c, int d, int col, word Where) {
 +
 +  int i,u,s,v,d1x,d1y,d2x,d2y,m,n;
 +
 +  u   = c-a;      // x2-x1
 +  v   = d-b;      // y2-y1
 +  d1x = sgn(u);   // d1x is the sign of u (x2-x1) (VALUE -1,0,1)
 +  d1y = sgn(v);   // d1y is the sign of v (y2-y1) (VALUE -1,0,1)
 +  d2x = sgn(u);   // d2x is the sign of u (x2-x1) (VALUE -1,0,1)
 +  d2y = 0;
 +  m   = abs(u);   // m is the distance between x1 and x2
 +  n   = abs(v);   // n is the distance between y1 and y2
 +
 +  if (m<=n) {     // if the x distance is greater than the y distance
 +    d2x = 0;
 +    d2y = sgn(v); // d2y is the sign of v (x2-x1) (VALUE -1,0,1)
 +    m   = abs(v); // m is the distance between y1 and y2
 +    n   = abs(u); // n is the distance between x1 and x2
 +  }
 +
 +  s = m / 2; // s is the m distance (either x or y) divided by 2
 +
 +  for (i=0;i<m+1;i++) { // repeat this loop until it
 +                 // is = to m (y or x distance)
 +    Putpixel(a,b,col,Where); // plot a pixel at the original x1, y1
 +    s += n;                  // add n (dis of x or y) to s (dis of x of y)
 +    if (s >= m) {            // if s is >= m (distance between y1 and y2)
 +      s -= m;
 +      a += d1x;
 +      b += d1y;
 +    }
 +    else {
 +      a += d2x;
 +      b += d2y;
 +    }
 +  }
 +
 +}
 +
 +
 +//--------------------------VIDEO MEMORY FUNCTIONS-------------------------//
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// Cls() - This clears the screen at location Where to color Col           //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void Cls(byte Col, word Where) {
 +  asm {
 +    push    es           // save ES
 +    mov     cx, 32000    // this is our loop counter.  we want to clear
 +                         //   64000 bytes of memory, so why do we use 32000?
 +                         //   1 word = 2 bytes, and we are moving a word at
 +                         //   a time
 +    mov     es, [Where]  // move address in Where to ES
 +    xor     di, di       // zero out DI
 +    mov     al, [Col]    // move color to AL
 +    mov     ah, al       // move color to AH (Remember, will be moving
 +                         //   a WORDS, so we need two copies
 +    rep     stosw        // copy AX to Where and drecrement CX by 1
 +                         //   until CX equals 0
 +    pop     es           // restore ES
 +  }
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////
 +//                                                                         //
 +// Flip() - This copies the entire screen at "source" to destination.      //
 +//                                                                         //
 +/////////////////////////////////////////////////////////////////////////////
 +
 +void Flip(word source, word dest) {
 +  asm {
 +    push    ds           // save DS
 +    mov     ax, [dest]   // copy segment of destination to AX
 +    mov     es, ax       // set ES to point to destination
 +    mov     ax, [source] // copy segment of source to AX
 +    mov     ds, ax       // set DS to point to source
 +    xor     si, si       // zero out SI
 +    xor     di, di       // zero out DI
 +    mov     cx, 32000    // set our counter to 32000
 +    rep     movsw        // move source to destination by words.  decrement
 +                         //   CX by 1 each time until CX is 0
 +    pop     ds           // restore DS
 +  }
 +}
 +</code>
 +
 +<nspages back2root/archives/denthor -simpleList -title -h1 -exclude:start -textPages="Denthor VGA Trainer">