Spell presents:


////==\\\\ ||      || //========    |\      /|    //\\     //======\
    ||     ||      || ||            ||\    /||   //  \\   //
    ||     ||      || ||            || \  / ||  //    \\  ||
    ||     ||======|| ||====        ||  \/  ||  ||    ||  ||
    ||     ||      || ||            ||      ||  ||====||  ||    ==\\
    ||     ||      || ||            ||      ||  ||    ||  \\      ||
    ||     ||      || \\========    ||      ||  ||    ||   \\====//

                                                        Issue 11
                                                        24-2-97


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-


  Index:

        1. Introduction
          1.1. About the magazine
          1.2. About the author
          1.3. Distribution
          1.4. Subscribing
          1.5. Contribuitions
          1.6. Hellos and greets
        2. Mailroom
        3. Designing a text adventure - Part IV
          3.1. Monsters
          3.2. Special Rooms
        4. Some order in a 3d world, please !
          4.1. 3d polygons
          4.2. Sorting
            4.2.1. The Painter's Algorithm
            4.2.2. Z-Buffer
            4.2.3. BSP Trees
          4.3. Vector maths
            4.3.1. Basic concepts
            4.3.2. Multiplications
            4.3.3. Special vectors
          4.4. Backface removal
        5. Lights, please !
        6. The optimization bible
        7. How to do crossfading
          7.1. 16 color crossfading
          7.2. 256 color crossfading
        8. Sprites Part IV - Rotating and scaling
          8.1. Rotation
          8.2. Scaling
        9. Graphics Part X - Assembling it all
       10. Hints and tips
       11. Points of view
       12. The adventures of Spellcaster, part 11


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-


  1. Introduction

    1.1. About the magazine

    Well, it's time I start doing this issue... I should have released it
  today, but I just started now, so I think this issue is going to be a bit
  late... Curse school !! I hate school !!!
    But I think this is also going to be a big issue (maybe not as big as
  issue 10, but big anyway).
    I have lined for you some nice articles... We'll delve into the realms of
  3d to bring info on face sorting, backface removal and flatshading. I also
  have the continuation of the articles on text adventures... This time it
  is about monsters !! Then,  I have an article on optimization  (just a few
  pointers to get you going)... Also an article on crossfading.
    In the Sprites tutorial, we'll learn how to scale and rotate a bitmap, and
  in the Graphics section we'll have a look on how to use assembler to
  optimize the code...
    Accompanying this issue is a program called HorizScr.Pas, that demonstrates
  how smooth horizontal scrolling can be achieved in text mode. It was done
  Brent Hostetler, and it is so well commented that I don't even need to do
  an explanation... Thanks a lot, Brent...

    You probably can get it from the same place you got this issue, but if you
  can't, look in the BBS's listed somewhere in this issue, or look in my
  HomePage... It includes full source code, so you can change it, and see how
  it was done... :)
    This can only be probably seen with SpellView V1.4... Get it... It's
  better, bugfixed, etc...

    When you read this magazine, I'll assume some things. First, I assume you
  have Borland's Turbo Pascal, version 6 and upwards (and TASM for the assembly
  tutorials). I'll also think you have a 80386 (or 386 for short; a 486 would
  be even better), a load of patience and a sense of humor. This last is almost
  essencial, because I don't receive any money for doing this, so I must have
  fun doing it. I will also take for certain you have the 9th grade (or
  equivelent). Finally, I will assume that you have the last issues of
  'The Mag', and that you have grasped the concepts I tried to transmit. If
  you don't have the issues, you can get them by mail, writing to one of the
  adresses shown below (Snail mail and Email).

    Almost everything I know was learnt from painfull experience. If you
  re-re-re-read the article, and still can't understand it, just drop a line,
  by mail, or just plain forget it. Most of the things I try to teach here
  aren't linked to each other (unless I say so), so if you don't understand
  something, skip it and go back to it some weeks later. It should be clearer
  for you then. Likewise, if you see any terms or words you don't understand,
  follow the same measures as before.

    Ok, as I'm earing the Net gurus and other god-like creatures talking
  already, I'm just going to explain why I use Pascal.
  For starters, Pascal is a very good language, ideal for the beginner, like
  BASIC (yech!), but it's powerfull enough to make top-notch programms.
  Also, I'll will be using assembly language in later issues, and Pascal makes
  it so EASY to use.
  Finally, if you don't like my choice of language, you can stop whining. The
  teory behind each article is very simple, and common with any of the main
  languages (C, C++, Assembly - Yes, that's true... BASIC isn't a decent
  language).

    Just one last thing... The final part of the magazine is a little story
  made up by my distorted mind. It's just a little humor I like to write, and
  it hasn't got nothing to do with programming (well, it has a little), but,
  as I said before, I just like to write it.

    1.2. About the author

    Ok, so I'm a little egocentric, but tell me... If you had the trouble of
  writing hundreds of lines, wouldn't you like someone to know you, even by
  name ?

    My name is Diogo de Andrade, alias Spellcaster, and I'm the creator,
  editor and writer of this magazine.
    I live in a small town called Setubal, just near Lisbon, the capital of
  Portugal... If you don't know where it is, get an encyclopedia, and look for
  Europe. Then, look for Spain. Next to it, there's Portugal, and Setubal is in
  the middle.

    I'm 19 years old, and I'm in the second year in the university (if you do
  want to know, I'm in the Technical Institute of Lisbon, Portugal), so I'm not
  a God-Like creature, with dozens of years of practice (I only program by
  eight years now, and I started in a Spectrum, progressing later to an Amiga.
  I only program in the PC for two years or so), with a mega-computer (I own a
  386SX, 16 Mhz), that wear glasses with lens that look like the bottom of a
  bottle (I use glasses, but only sometimes), that has his head bigger than a
  pumpkin (I have a normal sized head) and with an IQ of over 220 (mine is
  actually something like -220 :) ). I can program in C, C++, Pascal, Assembly,
  Prolog, CaML, and even BASIC (yech!)... And due to school, this list is
  increasing... :)))

    So, if I am a normal person, why do I spend time writing this ?
  Well, because I have the insane urge to write thousands of words every now
  and then, and while I'm at it, I may do something productive, like teaching
  someone.

    Just one more thing, if you ever program anything, please send to me... I
  would love to see some work you got, maybe I could learn something with it.
  Also, give me a greet in your program/game/demo... I love seeing my name.

    1.3. Distribution

    I don't really know when can I do another issue, so, there isn't a fixed
  space of time between two issues. General rule, I will try to do one every
  month, maybe more, probably less (Eheheheh). This is getting to an issue
  every two months, so, I'll think I'll change the above text... :)
    'The Mag' is available by the following means:

    - Snail Mail : My address is below, in the Contributions seccion... Just
                   send me a disk and tell me what issues you want, and I
                   will send you them...

    - E-Mail : If you E-mail me and ask me for some issues, I will Email you
               back with the relevant issues attached.

    - Internet : You can access the Spellcaster page and take the issues out
                 of there in:

                 http://alfa.ist.utl.pt/~l42686

                 Follow the docs link...

    - Anonymous ftp : I've put this issue of 'The Mag' on the ftp.cdrom.com
                      site, in the pub/demos/incoming/code... It will probably
                      be moved to pub/demos/code or something like that.

    - BBS's : You can check out the BBS's list that will carry this and all
              the other issues of 'The Mag' in the file SPELL.DOC.

    1.4. Subscribing

    If you want, I'm starting "The Mag"'s subscription list... To get
  'The Mag' by Email every month, you just need to mail me and tell me so...
    Then, you will receive it every time a new issue is made...

    1.5. Contributions

    I as I stated before, I'm not a God... I do make mistakes, and I don't
  have (always) the best way of doing things. So, if you think you've spotted
  an error, or you have thought of a better way of doing things, let me know.
  I'll be happy to receive anything, even if it is just mail saying 'Keep it
  up'. As all human beings, I need incentive.

    Also, if you do like to write, please do... Send in articles, they will be
  welcome, and you will have the chance to see your names up in lights.
    They can be about anything, for a review of a book or program that can
  help a programmer, to a point of view or a moan.

    If anyone out there has a question or wants to see an article about
  something in particular, feel free to write... All letters will be answered,
  provided you give me your address.

    If you have a BBS and you want it to include this magazine, feel free to
  write me... I don't have a modem, so I can only send you 'The Mag' by Email.

    You can also contact me personally, if you study on the IST (if you don't
  know what the IST is, you don't study there). I'm the freshman with the
  black hair and dark-brown eyes... Yes, the one doesn't put a foot in a class
  for ages !

    My adress is:
                 Praceta Carlos Manito Torres, n4/6C
                 2900 Setbal
                 Portugal

    Email: spellcaster@bigfoot.com
           spellcaster@cryogen.com
           dgan@camoes.rnl.ist.utl.pt
           l42686@alfa.ist.utl.pt

    And if you want to contact me on the lighter side, get into the Lost Eden
  talker... To do that telnet to:

                        Alfa.Ist.Utl.Pt  : Port 1414

    I'm sometimes there... Not always... As you may have guessed already, my
  handle is Spellcaster (I wonder why...)...

    1.6. Hellos and greets

    I'll say hellos and thanks to all my friend, especially for those who put
  up with my not-so-constant whining.
    Special greets go to Scorpio, Denthor from Asphyxia (for the excelent VGA
  trainers), Draeden from VLA (for assembly tutorials), Dr.Shadow (Delta Team
  is still up), Joao Neves for sugestions, testing and BBS services, Garfield
  (for the 'The Mag' logo and general suport) and all the demo groups out
  there. Oh, and I almost forgot all the people at Infinity Network !
    I will also send greets to everybody that responded to my mag... Thank
  you very much !


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-

  2. Mailroom

    Well, I have two letters here... Both of them report errors... Let's see
  them:

--------------------------------------------------------------------------------

  In "Designing a text adventure, Part 2", in issue 8, more precisely in
4.2 (player input & command parser), you have writen:

    procedure parse(s:string;var parsed:ParsedType);
    .....
    .....
    .....
    while ((StringIndex <= Length(S)) and (ArrayIndex<11)) do

  Well, it should read:

    while ((StringIndex >= Length(S)) and (ArrayIndex<11)) do

    as it is in the FangLore.Pas program...

                                           Scorpio

--------------------------------------------------------------------------------

    You got me again... :) Now, another goof-up:

--------------------------------------------------------------------------------

Hello Spellcaster !

I read your mag, it's very good.
But i think i found 1 foult dad you made , it's this -->>
in your chart of hex numbers you stated that 30 in dec is = 1F in hex and
that 20 in dec is 32 in hex but i got this numbers

1F in hex is = 1 * 16 = 16 + F "15" = 31
20 in hex is = 2 * 16 = 32 + 0 * 1   = 32

correct me if i am wrong , but i just read your mag01 ?
Can you tell me what is best and easiest to use in demos and games
is it cel,pcx,spr, or raw picture files , and how i implement the code to
load the best file , in pascal 7.0 , help me please ?
Now i am using arrays but its very difficult to draw with 1's and 0'roes.
where on ftp i could fine one so please send me it please ?
i have searched four weeks and only find nothing, excpet one rmaster40.zip , but
hey , its a shareware so you could only use 16 colors , and i would pay to
get 256 but i dont got any money :( i am poor :( .
Well i send you a small demo i made , with skills from asphyxias trainer, its
not good , but i only programmed in about 3 weeks .
If you see anyone with the nick " sbviking " on irc  its probably me ,
so say hello !

Stefan Bergh

--------------------------------------------------------------------------------

    Well, for the first part, you got me again... :) That's what I get for
  writing the articles in a rush...
    As for the best file formats, I usually use PCX and RAW files, but that
  depends on the type of thing you're making... To load RAW files, just read
  the data straight to where you want to store it... PCX was explained in other
  issue...
    As for a program, I'm writing a small utility to do sprites, that enables
  you to grab them and draw them from scratch... It will take some time to
  finish, as I'm very busy in school now, so wait and see...
    As for your demos, you're one of the first to send me something, so I
  quite apreciated it... I think they are great, if you code for only 3 weeks.
  A couple of errors, but fine anyway... :)
    As for IRC, I can't get in it... :(((

  Thanks to everyone who wrote... Cya...

-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-

  3. Designing a text adventure - Part IV

    Well, this article is about monsters and special rooms in a text adventure.

    3.1. Monsters

    "What are monsters ?". This may seem a dumb question, but it isn't. In a
  text adventure a 'monster' is something that harms you... It can be a robot,
  or anything at all...
    In Fanglore, let's put three monsters:

                       1) A psychopatic gardner
                       2) A worm
                       3) A vampire

    The gardner has a special thing in it: it moves around in some predefined
  pattern, while the worm and the vampire will stay static in the kitchen and
  in the library, respectivelly.
    So, let's define a record for the monsters:

              Type MonsterType=Record
                                     Name:String;
                                     Pos:Byte;
                                     Index:Byte;
                                     Stamina:Byte;
                                     Strength:Byte;
                                     Positions:Array[1..MaxPos] Of Byte;
                               End;

    Name is the name or type of the monster.
    Pos is the current position of the monster.
    Index and Positions are variables that control the movement. More on this
  next.
    Stamina is the health points of monster. Let's define that taking in
  consideration that the sword (the only way to kill monsters in FangLore)
  take out 20 points of energy for blow (more a random value, the luck... More
  on this later).
    Strength is how hard the monster hits. We'll have to define:

                           Var Stamina:Integer;

    That defines how healthy we are. We start with 70 points, defined in

                           Const StartHealth=70;

    Don't forget to initialize Stamina with the value of StartHealth, in the
  Init procedure.

    As the only monster that moves in the game is the gardner, lets restrict
  his movements to the garden. So, as there are 14 garden positions, let's
  define MaxPos:

                           Const MaxPos=14;

    This is how the movement works. We fill the Positions array with the values
  of the rooms we want the monster to move in. Then we set Index as 1. Then,
  for every time-unit passed, we increase the Index in one and load the Pos
  variable with the value of the room indexed by Index. If the room indexed
  is equal to 0, then Index is reseted to 1. If the Index is bigger than the
  array size, the Index is also reseted to 1.
    We'll define a time-unit as the time for each command entered. We could
  define that as 1 second, 1 minute, every 5 commands, etc. That option is to
  be made. Some minor reprogramming is necessary for every kind of 'timer'. The
  hardest one to make is the real timer, that uses an interrupt or something
  like that.
    We just have to define the variables for the monsters:

                  Var Monsters:Array[1..MaxMons] Of MonsterType;

    where MaxMons is the number of monsters in the game:

                  Const MaxMons=3;

    Similarly as I have done with the Objects and Rooms, I made a program to
  input the values of the monster's records. I've created with it a file
  called Monster.Dat, and I read that file with the ReadMonsterData procedure,
  that is very similar to the ReadRoomData and ReadObjData procedures:

Procedure ReadMonsterData;     { Read from the disk the monster data }
Var F:Text;
    A,B:Byte;
    Flag:Boolean;
Begin
     { Prepares the text file for accessing }
     Assign(F,'Monster.Dat');
     Reset(F);
     { For every object in the game }
     For A:=1 To MaxMons Do
     Begin
          { Read the name of the objects }
          ReadLn(F,Monsters[A].Name);
          { Read the initial position of the objects }
          ReadLn(F,Monsters[A].Pos);
          { Clear the object's description }
          ReadLn(F,Monsters[A].Stamina);
          { Clear the object's description }
          ReadLn(F,Monsters[A].Strength);
          { Clear the object's description }
          For B:=1 To 5 Do Monsters[A].Positions[B]:=0;
          { Read the description of the room }
          Flag:=True;
          B:=1;
          While Flag Do
          Begin
               ReadLn(F,Monsters[A].Positions[B]);
               If (B=MaxPos) Or (Monsters[A].Positions[B]=0) Then Flag:=False;
               Inc(B);
          End;
          Monsters[A].Index:=1;
     End;
     Close(F);
End;

    And don't forget to call this procedure in the Init procedure.
    And now, we have to do a procedure that sees the monster. Why don't we
  just include the data in the look procedure ? Because we'll have to look at
  the monster lots of time during battle.
    We'll also use that procedure to make the monster kick our ass !!!! :)))
    Everytime the computer 'looks' at the monster, he strikes. Here we'll add
  the luck element. The monster will only hit a percentage of times. Only when
  the monster hits we'll suffer some damage.
    So, let's do the rotine:

Procedure SeeMonster;                   { Sees the monsters }
Var A,B:Byte;
    Damage:Byte;
Begin
     For A:=1 To MaxMons Do
     Begin
          { Check if the monster is in the room }
          If Monsters[A].Pos=CurrentRoom Then
          Begin
               { Describe monster }
               TextColor(LightRed);
               WriteLn;
               WriteLn('I see a ',Monsters[A].Name,' here !!!!!');
               WriteLn('He''s got ',Monsters[A].Stamina,' health points...');
               { Makes him hit us }
               WriteLn('He sees you !');
               Write('He tries to hit you...');
               { Check if he hits us. Dificulty is a predefined percentage
                 margin. The higher it his, the greater is our 'luck' }
               B:=Random(100);
               If B>Dificulty Then
               Begin
                    { He hits !!! }
                    WriteLn('and succeeds !!!!');
                    { Assess damages, taking in acount some luck or bad luck }
                    Damage:=Monsters[A].Strength+(Random(8)-5);
                    Stamina:=Stamina-Damage;
                    WriteLn('You only have ',Stamina,' health points !');
                    { Check if we're still alive }
                    If Stamina<=0 Then
                    Begin
                         { We have died... :( }
                         WriteLn('You have died...');
                         Write('The Valkries come to claim your body... But ');
                         WriteLn('you aren''t a warrior, so they leave');
                         Writeln('you to rot... ');
                         Writeln;
                         TextColor(Magenta);
                         Writeln('Press any key to exit FangLore...');
                         Repeat Until Keypressed;
                         Halt(0);
                    End;
               End
               Else
               Begin
                    { He misses !!! }
                    Writeln('and misses !!!');
                    WriteLn('You still have ',Stamina,' health points !');
               End;
          End;
     End;
End;

    We have taken in account luck, because the game is more exciting that way.
  We just have to define the Dificulty factor:

                         Const Dificulty=40;

    Just add a call to the SeeMonster procedure in the beggining of the play
  procedure, to get that time-unit we already discussed.
    I also added something to the Inventory procedure. Now, the inventory
  command also informs you of your health. That is done by adding the lines:

     WriteLn;
     TextColor(LightGreen);
     WriteLn('You have ',Stamina,' health points.');

    to the Inventory procedure.
  Now, let's make the monsters move... As I explained already, the movement
  is achieved by changing the position of the monster every time a command
  is issued. To achieve that, I add a call to the MoveMonster procedure in the
  end of the loop in the Play procedure.
    The MoveMonster procedure is rather simple:

          Procedure MoveMonster;
          Var A:Byte;
          Begin
               { Move every monster }
               For A:=1 To MaxMons Do
               Begin
                    { Reset the position index, if the position he's
                      pointing is equal to 0 }
                    If Monsters[A].Positions[Monsters[A].Index]=0 Then
                      Monsters[A].Index:=1;
                    Monsters[A].Pos:=Monsters[A].Positions[Monsters[A].Index];
                    Inc(Monsters[A].Index);
               End;
          End;

    This is the basic movement unit. But we can improve it a lot. One possible
  improvement is this: We don't want the monster to move if he is in the same
  room as the player. We can do that by simply adding three lines:

          Procedure MoveMonster;
          Var A:Byte;
          Begin
               { Move every monster }
               For A:=1 To MaxMons Do
               Begin
                    { Check if the monster is in the same room as the player }
                    If Monsters[A].Pos<>CurrentRoom Then
                    Begin
                         { Reset the position index, if the position he's
                           pointing is equal to 0 }
                         If Monsters[A].Positions[Monsters[A].Index]=0 Then
                           Monsters[A].Index:=1;
                         Monsters[A].Pos:=Monsters[A].Positions[Monsters[A].Index];
                         Inc(Monsters[A].Index);
                    End;
               End;
          End;

    Just to add a bit to the realism of the text, let's be nice to the player
  and when a monster enters the room the player's in, the computer writes a
  message saying so. Just add some more lines:

          Procedure MoveMonster;
          Var A:Byte;
          Begin
               { Move every monster }
               For A:=1 To MaxMons Do
               Begin
                    { Check if the monster is in the same room as the player }
                    If Monsters[A].Pos<>CurrentRoom Then
                    Begin
                         { Reset the position index, if the position he's
                           pointing is equal to 0 }
                         If Monsters[A].Positions[Monsters[A].Index]=0 Then
                           Monsters[A].Index:=1;
                         Monsters[A].Pos:=Monsters[A].Positions[Monsters[A].Index];
                         Inc(Monsters[A].Index);
                         { Check if the monster has entered the same room as
                           the player }
                         If Monsters[A].Pos=CurrentRoom Then
                         Begin
                              TextColor(LightCyan);
                              Write('The ',Monsters[A].Name,' has entered ');
                              Writeln('the room !');
                         End;
                    End;
               End;
          End;

    The procedure got pretty big (comparing to the first version), but it adds
  a lot to the realism.

    Now, we have be able to retaliate to the threat of the monsters. We do that
  by using the sword we find in the main hall of FangLore. We issue the command
  USE SWORD and our character tries to hit the monster with the sword (if it
  has it in his possession). Then, depending on luck, he hits or misses. The
  luck factor is based upon the Difficulty factor we defined for our luck in
  dodging the monsters blows. So, we need to add the following lines to the
  Use procedure, in the section regarding the usage of the sword:

               Flag:=True;
               Flag2:=False;
               { Check if there is any monsters in the room }
               For A:=1 To MaxMons Do If Monsters[A].Pos=CurrentRoom Then
                                         Flag2:=True;
               WriteLn;
               If Flag2=False Then
               Begin
                    TextColor(Green);
                    WriteLn('Use it on whom ???');
                    WriteLn;
               End;
               For A:=1 To MaxMons Do
               Begin
                    { Check if the monster is in the room }
                    If Monsters[A].Pos=CurrentRoom Then
                    Begin
                         { Try to hit monster }
                         Write('You swing the sword at the ',
                                Monsters[A].Name,'...');
                         { Check if you hits him. Dificulty is a predefined
                           percentage margin. The higher it his, the greater
                           is our 'luck' }
                         B:=Random(100);
                         If B>(Dificulty Div 2) Then
                         Begin
                              { You hits !!! }
                              WriteLn('and you hit !!!!');
                              { Assess damages, taking in acount some luck or
                                bad luck }
                              Damage:=12+(Random(8)-5);
                              Dec(Monsters[A].Stamina,Damage);
                              WriteLn('The monster has only ',
                                       Monsters[A].Stamina,
                                       ' health points !');
                              { Check if he is still alive }
                              If Monsters[A].Stamina<=0 Then
                              Begin
                                   { The monster died... :) }
                                   WriteLn('You killed the ',
                                            Monsters[A].Name,'!');
                                   Writeln;
                              End;
                         End
                         Else
                         Begin
                              { You miss... :( }
                              Writeln('and you miss !!!');
                              WriteLn('The monster still has ',
                                       Monsters[A].Stamina,
                                       ' health points !');
                         End;
                    End;
               End;
          End;

    I've discovered some stuff I forgot to do, while I did the previous parts
  of this tutorial. In the MonsterType definition, we must make Stamina a
  ShortInt, instead of a Byte, because a stupid thing that happens to bytes
  when you subtract... Check out Tricks and Tips in this issue so that you
  understand the reason why.
    Another thing I forgot is to check the monster's stamina in the SeeMonster
  procedure. The problem with it is that the monster still strikes you even if
  he is dead !!!!!! So, we have to add some three lines to the SeeMonster
  procedure that checks if the monster is alive before he tries to kick our
  ass... We have to add a check if the monster is alive before we can kick
  his ass. And finally, we can add something in the Look procedure that
  tells us that the corpse of the monster is in the room. Check out the
  complete listing of FangLore to see the changes. They are quite simple.

    3.2. Special Rooms

    A special room, as the name indicates, is a room that has anything special
  about it, something different from the other rooms. In FangLore, the only
  special room is the Gas Room.
    The Gas Room is a room that kills the player the moment he steps in, except
  if he is wearing a mask.
    To implement the special room, we just add some lines to the beggining of
  the Look procedure:

     { Check if the player is in the special room, i.e. the gas room }
     If RoomNumber=9 Then
     Begin
          TextColor(Yellow);
          Write('You have entered the gas room, ');
          { Check if the player is wearing the Gas Mask }
          If Mask=True Then WriteLn('but you are unharmed because of the gas
                                     mask.')
          Else
          Begin
               WriteLn('and you died, because of the poisoned gas...');
               Write('The Valkries come to claim your body... But ');
               WriteLn('you aren''t a warrior, so they leave');
               Writeln('you to rot... ');
               Writeln;
               TextColor(Magenta);
               Writeln('Press any key to exit FangLore...');
               Repeat Until Keypressed;
               Halt(0);
          End;
          WriteLn;
     End;

    That's it for this issue's section on text adventures. Next issue, we'll
  have an article on examination and manipulation of the gamescape, and we'll
  almost finish the text adventure... NOT ! :))))


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-

  4. Some order in a 3d world, please !

    This article has lots of info about how to order your objects in 3d world.
  It will cover face sorting (in several ways, including an overview of
  Z-Buffer and BSP trees) and backface removal.
    If you don't know what I'm talking, you'll get a pretty good idea soon...

    4.1. 3d polygons

    How can you draw a 3d polygon ??? Easy enough... To get an intuitive
  notion, get a disk... Any disk... Use one of those broken down, wasted disks
  you use to play frisby with your dog (unless your very rich and play with
  CD's, in which case I recomend you drop programming and hire something to do
  it for you :)) ).
    Now, look at it straigth forward... It will look something like this:

     1____2            ASCII art was never a strong point of mine... :)
     |    |            Now, draw some numbers on the disk (they are usefull
     |    |            now and later. Number them as the figure implies).
     3____4            Now, do something funny and hard: rotate the disk 45
                       degrees around the Z axis... You will get something
                       like this:
                                                  1
    As I said before, my ASCII art sucks.        / \
    Now, notice that the disk still has the     /   \
    same inherent proprities... There is       4     2
    still a line connecting point 1 to 2,       \   /
    and so on... Basically, only the base        \ /
    points change... The rest of the figure       3
    remains the same. And this is not only when
    the rotation is about the Z axis. For example, if you had rotated around
    the Y axis, you would get something like this:

    1       See ? The points rotate, but there is still a line connecting
    |\      them all. When you draw a 3d poly, it is just the same thing.
    | \2    You keep a record with the coordinates of the four points that
    |  |    define it (it can be more or less than four points. I just like
    |  |    working with four... Working with triangles is more flexible, but
    | /3    I'll let that for you... :) )
    |/      So, let's define a record to keep the 3d polys, and the procedure
    4       to draw, rotate and translate a 3d poly on the screen:

            Type Poly3d=Record
                              X1,Y1,Z1,
                              X2,Y2,Z2,
                              X3,Y3,Z3,
                              X4,Y4,Z4:Real;
                              Color:Byte;
                        End;

    We use reals for the coordinates, but for speed's sake, we should
  use longints and use fixed point math... I'll do an article on that on the
  next issue. Now, to draw a 3dpoly:

       Procedure Draw3dPoly(P:Poly3d;Where:Word);
       Var Tx1,Tx2,Tx3,Tx4,
           Ty1,Ty2,Ty3,Ty4:Integer;
       Begin
            Conv3d(P.X1,P.Y1,P.Z1,Tx1,Ty1);
            Conv3d(P.X2,P.Y2,P.Z2,Tx2,Ty2);
            Conv3d(P.X3,P.Y3,P.Z3,Tx3,Ty3);
            Conv3d(P.X4,P.Y4,P.Z4,Tx4,Ty4);
            FPoly(Tx1,Ty1,Tx2,Ty2,Tx3,Ty3,Tx4,Ty4,P.Color,Where);
       End;

    The later procedure uses a procedure we defined in the last article on 3d:

       Procedure Conv3d(X,Y,Z:Real;Var Xt,Yt:Integer);
       Begin
            Xt:=160+Trunc((X*256/Z));
            Yt:=100+Trunc((Y*256/Z));
       End;

    The Draw3dPoly just converts the 3d coordinates to 2d coordinates and
  draws a poly based on those coordinates.
    Now, let's do a rotine to rotate the 3dpoly. We'll use the formulas
  derived in issue 10. The rotation of a poly in this case is around the
  origin of the world. So, if you want to rotate the poly around the center,
  don't forget to move the center of the poly to the origin. More on this
  later...

       Procedure Rotate3dPoly(Var P:Poly3d;XAng,YAng,ZAng:Integer);
       Var Angle:Real;
           Temp:Real;
       Begin
            { Rotate all points of poly around Z axis }
            { Convertion to radians }
            Angle:=0.0175*ZAng;
            Temp:=P.X1;
            P.X1:=Temp*Cos(Angle)-P.Y1*Sin(Angle);
            P.Y1:=P.Y1*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X2;
            P.X2:=Temp*Cos(Angle)-P.Y2*Sin(Angle);
            P.Y2:=P.Y2*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X3;
            P.X3:=Temp*Cos(Angle)-P.Y3*Sin(Angle);
            P.Y3:=P.Y3*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X4;
            P.X4:=Temp*Cos(Angle)-P.Y4*Sin(Angle);
            P.Y4:=P.Y4*Cos(Angle)+Temp*Sin(Angle);
            { Rotate all points of poly around X axis }
            { Convertion to radians }
            Angle:=0.0175*XAng;
            Temp:=P.Z1;
            P.Z1:=Temp*Cos(Angle)-P.Y1*Sin(Angle);
            P.Y1:=P.Y1*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.Z2;
            P.Z2:=Temp*Cos(Angle)-P.Y2*Sin(Angle);
            P.Y2:=P.Y2*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.Z3;
            P.Z3:=Temp*Cos(Angle)-P.Y3*Sin(Angle);
            P.Y3:=P.Y3*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.Z4;
            P.Z4:=Temp*Cos(Angle)-P.Y4*Sin(Angle);
            P.Y4:=P.Y4*Cos(Angle)+Temp*Sin(Angle);
            { Rotate all points of poly around Y axis }
            { Convertion to radians }
            Angle:=0.0175*YAng;
            Temp:=P.X1;
            P.X1:=Temp*Cos(Angle)-P.Z1*Sin(Angle);
            P.Z1:=P.Z1*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X2;
            P.X2:=Temp*Cos(Angle)-P.Z2*Sin(Angle);
            P.Z2:=P.Z2*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X3;
            P.X3:=Temp*Cos(Angle)-P.Z3*Sin(Angle);
            P.Z3:=P.Z3*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X4;
            P.X4:=Temp*Cos(Angle)-P.Z4*Sin(Angle);
            P.Z4:=P.Z4*Cos(Angle)+Temp*Sin(Angle);
       End;

    This procedure is big, but it is just the aplication of the formulas given
  in the previous issue to every point of the polygon. Now, returning to the
  matter of the relativety of the rotation. Normally, we want to rotate the
  points around the center of the polygon. To achieve that, we will calculate
  the center of the polygon and store that value. And we'll rewrite the
  rotation procedure to reflect those changes.
    So, the new definition of Poly3d will be:

            Type Poly3d=Record
                              X1,Y1,Z1,
                              X2,Y2,Z2,
                              X3,Y3,Z3,
                              X4,Y4,Z4,
                              Xc,Yc,Zc:Real;
                              Color:Byte;
                        End;

    Here, Xc,Yc and Zc will be center points. Now, let's do a procedure that
  'generates' a poly. Given the coordinates of the four points, the procedure
  will store those points in the respective variables and calculate the
  center point. The center point will be the average of all the other points.
  So, we get:

         Procedure Gen3dPoly(X1,Y1,Z1,X2,Y2,Z2,
                             X3,Y3,Z3,X4,Y4,Z4:Real;
                             C:Byte;Var P:Poly3d);
         Begin
              P.X1:=X1; P.Y1:=Y1; P.Z1:=Z1;
              P.X2:=X2; P.Y2:=Y2; P.Z2:=Z2;
              P.X3:=X3; P.Y3:=Y3; P.Z3:=Z3;
              P.X4:=X4; P.Y4:=Y4; P.Z4:=Z4;
              P.Xc:=(X1+X2+X3+X4)/4;
              P.Yc:=(Y1+Y2+Y3+Y4)/4;
              P.Zc:=(Z1+Z2+Z3+Z4)/4;
              P.Color:=C;
         End;

    Now, we just have to change the Rotate3dPoly procedure so that all
  coordinates be expressed in terms of (Xc,Yc,Zc):

       Procedure Rotate3dPoly(Var P:Poly3d;XAng,YAng,ZAng:Integer);
       Var Angle:Real;
           Temp:Real;
       Begin
            { Make the coordinates of the points relative to the center }
            P.X1:=P.X1-P.Xc; P.Y1:=P.Y1-P.Yc; P.Z1:=P.Z1-P.Zc;
            P.X2:=P.X2-P.Xc; P.Y2:=P.Y2-P.Yc; P.Z2:=P.Z2-P.Zc;
            P.X3:=P.X3-P.Xc; P.Y3:=P.Y3-P.Yc; P.Z3:=P.Z3-P.Zc;
            P.X4:=P.X4-P.Xc; P.Y4:=P.Y4-P.Yc; P.Z4:=P.Z4-P.Zc;
            { Rotate all points of poly around Z axis }
            { Convertion to radians }
            Angle:=0.0175*ZAng;
            Temp:=P.X1;
            P.X1:=Temp*Cos(Angle)-P.Y1*Sin(Angle);
            P.Y1:=P.Y1*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X2;
            P.X2:=Temp*Cos(Angle)-P.Y2*Sin(Angle);
            P.Y2:=P.Y2*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X3;
            P.X3:=Temp*Cos(Angle)-P.Y3*Sin(Angle);
            P.Y3:=P.Y3*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X4;
            P.X4:=Temp*Cos(Angle)-P.Y4*Sin(Angle);
            P.Y4:=P.Y4*Cos(Angle)+Temp*Sin(Angle);
            { Rotate all points of poly around X axis }
            { Convertion to radians }
            Angle:=0.0175*XAng;
            Temp:=P.Z1;
            P.Z1:=Temp*Cos(Angle)-P.Y1*Sin(Angle);
            P.Y1:=P.Y1*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.Z2;
            P.Z2:=Temp*Cos(Angle)-P.Y2*Sin(Angle);
            P.Y2:=P.Y2*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.Z3;
            P.Z3:=Temp*Cos(Angle)-P.Y3*Sin(Angle);
            P.Y3:=P.Y3*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.Z4;
            P.Z4:=Temp*Cos(Angle)-P.Y4*Sin(Angle);
            P.Y4:=P.Y4*Cos(Angle)+Temp*Sin(Angle);
            { Rotate all points of poly around Y axis }
            { Convertion to radians }
            Angle:=0.0175*YAng;
            Temp:=P.X1;
            P.X1:=Temp*Cos(Angle)-P.Z1*Sin(Angle);
            P.Z1:=P.Z1*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X2;
            P.X2:=Temp*Cos(Angle)-P.Z2*Sin(Angle);
            P.Z2:=P.Z2*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X3;
            P.X3:=Temp*Cos(Angle)-P.Z3*Sin(Angle);
            P.Z3:=P.Z3*Cos(Angle)+Temp*Sin(Angle);
            Temp:=P.X4;
            P.X4:=Temp*Cos(Angle)-P.Z4*Sin(Angle);
            P.Z4:=P.Z4*Cos(Angle)+Temp*Sin(Angle);
            { Transform the coordinates again }
            P.X1:=P.X1+P.Xc; P.Y1:=P.Y1+P.Yc; P.Z1:=P.Z1+P.Zc;
            P.X2:=P.X2+P.Xc; P.Y2:=P.Y2+P.Yc; P.Z2:=P.Z2+P.Zc;
            P.X3:=P.X3+P.Xc; P.Y3:=P.Y3+P.Yc; P.Z3:=P.Z3+P.Zc;
            P.X4:=P.X4+P.Xc; P.Y4:=P.Y4+P.Yc; P.Z4:=P.Z4+P.Zc;
       End;

    It's simple to see that the center of a polygon isn't changed with the
  rotation.
    Now we just have to do translation:

       Procedure Translate(Var P:Poly3d;Xt,Yt,Zt:Real);
       Begin
            P.X1:=P.X1+Xt; P.Y1:=P.Y1+Yt; P.Z1:=P.Z1+Zt;
            P.X2:=P.X2+Xt; P.Y2:=P.Y2+Yt; P.Z2:=P.Z2+Zt;
            P.X3:=P.X3+Xt; P.Y3:=P.Y3+Yt; P.Z3:=P.Z3+Zt;
            P.X4:=P.X4+Xt; P.Y4:=P.Y4+Yt; P.Z4:=P.Z4+Zt;
            P.Xc:=P.Xc+Xt; P.Yc:=P.Yc+Yt; P.Zc:=P.Zc+Zt;
       End;

    It's easy enough to see that the center of a polygon _IS_ changed with
  the translation. Now, we are ready to do a bit of example code... This
  example is pretty simple. It just zooms in a poly, and then rotates it
  around, and around, and around... :))

          Program Polygon3d;

          Uses Crt,Mode13h;

          Type Poly3d=Record
                            X1,Y1,Z1,
                            X2,Y2,Z2,
                            X3,Y3,Z3,
                            X4,Y4,Z4,
                            Xc,Yc,Zc:Real;
                            Color:Byte;
                      End;

          Var Poly:Poly3d;

          Procedure Gen3dPoly(X1,Y1,Z1,X2,Y2,Z2,
                              X3,Y3,Z3,X4,Y4,Z4:Real;
                              C:Byte;Var P:Poly3d);
          Begin
               P.X1:=X1; P.Y1:=Y1; P.Z1:=Z1;
               P.X2:=X2; P.Y2:=Y2; P.Z2:=Z2;
               P.X3:=X3; P.Y3:=Y3; P.Z3:=Z3;
               P.X4:=X4; P.Y4:=Y4; P.Z4:=Z4;
               P.Xc:=(X1+X2+X3+X4)/4;
               P.Yc:=(Y1+Y2+Y3+Y4)/4;
               P.Zc:=(Z1+Z2+Z3+Z4)/4;
               P.Color:=C;
          End;

          Procedure Conv3d(X,Y,Z:Real;Var Xt,Yt:Integer);
          Begin
               Xt:=160+Trunc((X*256/Z));
               Yt:=100+Trunc((Y*256/Z));
          End;

          Procedure Draw3dPoly(P:Poly3d;Where:Word);
          Var Tx1,Tx2,Tx3,Tx4,
              Ty1,Ty2,Ty3,Ty4:Integer;
          Begin
               Conv3d(P.X1,P.Y1,P.Z1,Tx1,Ty1);
               Conv3d(P.X2,P.Y2,P.Z2,Tx2,Ty2);
               Conv3d(P.X3,P.Y3,P.Z3,Tx3,Ty3);
               Conv3d(P.X4,P.Y4,P.Z4,Tx4,Ty4);
               FPoly(Tx1,Ty1,Tx2,Ty2,Tx3,Ty3,Tx4,Ty4,P.Color,Where);
          End;

          Procedure Translate(Var P:Poly3d;Xt,Yt,Zt:Real);
          Begin
               P.X1:=P.X1+Xt; P.Y1:=P.Y1+Yt; P.Z1:=P.Z1+Zt;
               P.X2:=P.X2+Xt; P.Y2:=P.Y2+Yt; P.Z2:=P.Z2+Zt;
               P.X3:=P.X3+Xt; P.Y3:=P.Y3+Yt; P.Z3:=P.Z3+Zt;
               P.X4:=P.X4+Xt; P.Y4:=P.Y4+Yt; P.Z4:=P.Z4+Zt;
               P.Xc:=P.Xc+Xt; P.Yc:=P.Yc+Yt; P.Zc:=P.Zc+Zt;
          End;

          Procedure Rotate3dPoly(Var P:Poly3d;XAng,YAng,ZAng:Integer);
          Var Angle:Real;
              Temp:Real;
          Begin
               { Make the coordinates of the points relative to the center }
               P.X1:=P.X1-P.Xc; P.Y1:=P.Y1-P.Yc; P.Z1:=P.Z1-P.Zc;
               P.X2:=P.X2-P.Xc; P.Y2:=P.Y2-P.Yc; P.Z2:=P.Z2-P.Zc;
               P.X3:=P.X3-P.Xc; P.Y3:=P.Y3-P.Yc; P.Z3:=P.Z3-P.Zc;
               P.X4:=P.X4-P.Xc; P.Y4:=P.Y4-P.Yc; P.Z4:=P.Z4-P.Zc;
               { Rotate all points of poly around Z axis }
               { Convertion to radians }
               Angle:=0.0175*ZAng;
               Temp:=P.X1;
               P.X1:=Temp*Cos(Angle)-P.Y1*Sin(Angle);
               P.Y1:=P.Y1*Cos(Angle)+Temp*Sin(Angle);
               Temp:=P.X2;
               P.X2:=Temp*Cos(Angle)-P.Y2*Sin(Angle);
               P.Y2:=P.Y2*Cos(Angle)+Temp*Sin(Angle);
               Temp:=P.X3;
               P.X3:=Temp*Cos(Angle)-P.Y3*Sin(Angle);
               P.Y3:=P.Y3*Cos(Angle)+Temp*Sin(Angle);
               Temp:=P.X4;
               P.X4:=Temp*Cos(Angle)-P.Y4*Sin(Angle);
               P.Y4:=P.Y4*Cos(Angle)+Temp*Sin(Angle);
               { Rotate all points of poly around X axis }
               { Convertion to radians }
               Angle:=0.0175*XAng;
               Temp:=P.Z1;
               P.Z1:=Temp*Cos(Angle)-P.Y1*Sin(Angle);
               P.Y1:=P.Y1*Cos(Angle)+Temp*Sin(Angle);
               Temp:=P.Z2;
               P.Z2:=Temp*Cos(Angle)-P.Y2*Sin(Angle);
               P.Y2:=P.Y2*Cos(Angle)+Temp*Sin(Angle);
               Temp:=P.Z3;
               P.Z3:=Temp*Cos(Angle)-P.Y3*Sin(Angle);
               P.Y3:=P.Y3*Cos(Angle)+Temp*Sin(Angle);
               Temp:=P.Z4;
               P.Z4:=Temp*Cos(Angle)-P.Y4*Sin(Angle);
               P.Y4:=P.Y4*Cos(Angle)+Temp*Sin(Angle);
               { Rotate all points of poly around Y axis }
               { Convertion to radians }
               Angle:=0.0175*YAng;
               Temp:=P.X1;
               P.X1:=Temp*Cos(Angle)-P.Z1*Sin(Angle);
               P.Z1:=P.Z1*Cos(Angle)+Temp*Sin(Angle);
               Temp:=P.X2;
               P.X2:=Temp*Cos(Angle)-P.Z2*Sin(Angle);
               P.Z2:=P.Z2*Cos(Angle)+Temp*Sin(Angle);
               Temp:=P.X3;
               P.X3:=Temp*Cos(Angle)-P.Z3*Sin(Angle);
               P.Z3:=P.Z3*Cos(Angle)+Temp*Sin(Angle);
               Temp:=P.X4;
               P.X4:=Temp*Cos(Angle)-P.Z4*Sin(Angle);
               P.Z4:=P.Z4*Cos(Angle)+Temp*Sin(Angle);
               { Transform the coordinates again }
               P.X1:=P.X1+P.Xc; P.Y1:=P.Y1+P.Yc; P.Z1:=P.Z1+P.Zc;
               P.X2:=P.X2+P.Xc; P.Y2:=P.Y2+P.Yc; P.Z2:=P.Z2+P.Zc;
               P.X3:=P.X3+P.Xc; P.Y3:=P.Y3+P.Yc; P.Z3:=P.Z3+P.Zc;
               P.X4:=P.X4+P.Xc; P.Y4:=P.Y4+P.Yc; P.Z4:=P.Z4+P.Zc;
          End;

          Begin
               InitGraph;
               InitVirt;
               SetColor(1,63,63,0);
               Cls(0,Vp[1]);
               Gen3dPoly(0,-20,1500,20,0,1500,0,20,1500,-20,0,1500,1,Poly);
               Repeat
                     Cls(0,Vp[1]);
                     Draw3dPoly(Poly,Vp[1]);
                     CopyPage(Vp[1],VGA);
                     Translate(Poly,0,0,-15);
               Until (Keypressed) Or (Poly.Z1<=200);
               Repeat
                     Cls(0,Vp[1]);
                     Draw3dPoly(Poly,Vp[1]);
                     CopyPage(Vp[1],Vga);
                     Rotate3dPoly(Poly,3,2,4);
               Until Keypressed;
               CloseVirt;
               CloseGraph;
          End.

    We could speed up this very much, by using fixed point maths and a lookup
  table for the sines and cosines. Let's see the procedure transformed with
  that purpose:

       Procedure Rotate3dPoly(Var P:Poly3d;XAng,YAng,ZAng:Integer);
       Var Angle:Integer;
           Temp:Real;
       Begin
            { Transform negative angles into positive ones }
            If XAng<0 Then XAng:=XAng+360;
            If YAng<0 Then YAng:=YAng+360;
            If ZAng<0 Then ZAng:=ZAng+360;
            { Make the coordinates of the points relative to the center }
            P.X1:=P.X1-P.Xc; P.Y1:=P.Y1-P.Yc; P.Z1:=P.Z1-P.Zc;
            P.X2:=P.X2-P.Xc; P.Y2:=P.Y2-P.Yc; P.Z2:=P.Z2-P.Zc;
            P.X3:=P.X3-P.Xc; P.Y3:=P.Y3-P.Yc; P.Z3:=P.Z3-P.Zc;
            P.X4:=P.X4-P.Xc; P.Y4:=P.Y4-P.Yc; P.Z4:=P.Z4-P.Zc;
            { Rotate all points of poly around Z axis }
            Angle:=ZAng;
            Temp:=P.X1;
            P.X1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
            P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
            Temp:=P.X2;
            P.X2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
            P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
            Temp:=P.X3;
            P.X3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
            P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
            Temp:=P.X4;
            P.X4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
            P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
            { Rotate all points of poly around X axis }
            Angle:=XAng;
            Temp:=P.Z1;
            P.Z1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
            P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
            Temp:=P.Z2;
            P.Z2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
            P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
            Temp:=P.Z3;
            P.Z3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
            P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
            Temp:=P.Z4;
            P.Z4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
            P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
            { Rotate all points of poly around Y axis }
            Angle:=YAng;
            Temp:=P.X1;
            P.X1:=Temp*Cosines^[Angle]-P.Z1*Sines^[Angle];
            P.Z1:=P.Z1*Cosines^[Angle]+Temp*Sines^[Angle];
            Temp:=P.X2;
            P.X2:=Temp*Cosines^[Angle]-P.Z2*Sines^[Angle];
            P.Z2:=P.Z2*Cosines^[Angle]+Temp*Sines^[Angle];
            Temp:=P.X3;
            P.X3:=Temp*Cosines^[Angle]-P.Z3*Sines^[Angle];
            P.Z3:=P.Z3*Cosines^[Angle]+Temp*Sines^[Angle];
            Temp:=P.X4;
            P.X4:=Temp*Cosines^[Angle]-P.Z4*Sines^[Angle];
            P.Z4:=P.Z4*Cosines^[Angle]+Temp*Sines^[Angle];
            { Transform the coordinates again }
            P.X1:=P.X1+P.Xc; P.Y1:=P.Y1+P.Yc; P.Z1:=P.Z1+P.Zc;
            P.X2:=P.X2+P.Xc; P.Y2:=P.Y2+P.Yc; P.Z2:=P.Z2+P.Zc;
            P.X3:=P.X3+P.Xc; P.Y3:=P.Y3+P.Yc; P.Z3:=P.Z3+P.Zc;
            P.X4:=P.X4+P.Xc; P.Y4:=P.Y4+P.Yc; P.Z4:=P.Z4+P.Zc;
       End;

    This is much faster than the original one... But don't take my word for
  it... Time it... In fact, I'm gonna do that now:
    The procedure without the lookup tables achieved 6.1 FPS (Frames per
  Second) in my 386/16 Mhz (a good machine, in the age of the Industrial
  Revolution, in end of the 1800s). The procedure with the lookup tables
  achieved 7.8 FPS... That's what I call improvement. You can see the
  alterations I did to the program in order to time it in the file 3d4.Pas.
    Now, let's move on to 3d solids... 3d solids are nothing more than a
  collection of 3d polygons. Usually it is associated with a 3d solid a notion
  of fullness, that is, that it is... well, solid ! :))
    Let's define a structure for the 3d solid:

                 Type Solid3d=Record
                                    NumPolys:Word;
                                    Polys:Array[1..MaxPolys] Of Word;
                                    Xc,Yx,Zc:Real;
                              End;

    Cx, Cy and Cz are the coordinates of the center of the solid. This is
  important so that you can rotate the solid around it's center. Usually, the
  center is the average of the coordinates of the solid.
    Polys is an array that refers to the index of the following structure:

                 Var Polygons:Array[1..MaxPolys] Of Poly3d;

    This array stores all the polygons. It will become clear to you why we do
  this later. So, if the Polys array has the values [1 2 3 4], it means that
  the solid uses polygons 1, 2, 3 and 4. The NumPolys variable is the number
  of polygons the solid has.
    So, now let's construct something. Most of the 3d tutorials use a 3d cube
  for the examples. So, who am I to go against the flow ? The code's equal for
  every type of solid. It is just harder to define... But I bet you'll soon
  do a program to create the 3d solids (or convert them from a comercial
  program like 3d Studio). So, let's do an example...
    First, let's define the types and variables:

                 Type Poly3d=Record
                                   X1,Y1,Z1,
                                   X2,Y2,Z2,
                                   X3,Y3,Z3,
                                   X4,Y4,Z4,
                                   Xc,Yc,Zc:Real;
                                   Color:Byte;
                             End;

                 Type Solid3d=Record
                                    NumPolys:Word;
                                    Polys:Array[1..MaxPolys] Of Word;
                                    Xc,Yc,Zc:Real;
                              End;

                 Var Polygons:Array[1..MaxPolys] Of Poly3d;

    Now, let's define MaxPolys. That's simultaneously the maximum number of
  polygons per solid and the maximum number of polygons that can be in our
  3d world. You could use diferent constants for each one of them in order to
  save memory.

                 Const MaxPolys=6;

    I've defined as 6, because that's all we'll need for one cube.
    Now, let's define the diamond:

                 Var Cube:Solid3d;

    And put the values in it:

          Procedure InitCube;
          Var A:Byte;
          Begin
               { Define the polygons that will be part of the solid }
               Gen3dPoly(1,-30,-30,226,30,-30,226,30,30,226,-30,30,226,1);
               Gen3dPoly(2,30,-30,226,30,-30,286,30,30,286,30,30,226,2);
               Gen3dPoly(3,30,-30,286,-30,-30,286,-30,30,286,30,30,286,3);
               Gen3dPoly(4,-30,-30,286,-30,-30,226,-30,30,226,-30,30,286,4);
               Gen3dPoly(5,-30,-30,286,30,-30,286,30,-30,226,-30,-30,226,5);
               Gen3dPoly(6,-30,30,286,30,30,286,30,30,226,-30,30,226,6);
               { Define the cube solid }
               Cube.NumPolys:=6;
               For A:=1 To 6 Do Cube.Polys[A]:=A;
               { Calc the center point }
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Xc;
               Cube.Xc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Yc;
               Cube.Yc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Zc;
               Cube.Zc:=Average/6;
          End;

    In this procedure, I used the altered version of Gen3dPoly

          Procedure Gen3dPoly(N:Word;
                              X1,Y1,Z1,X2,Y2,Z2,
                              X3,Y3,Z3,X4,Y4,Z4:Real;
                              C:Byte);
          Begin
               Polygons[N].X1:=X1; Polygons[N].Y1:=Y1; Polygons[N].Z1:=Z1;
               Polygons[N].X2:=X2; Polygons[N].Y2:=Y2; Polygons[N].Z2:=Z2;
               Polygons[N].X3:=X3; Polygons[N].Y3:=Y3; Polygons[N].Z3:=Z3;
               Polygons[N].X4:=X4; Polygons[N].Y4:=Y4; Polygons[N].Z4:=Z4;
               Polygons[N].Xc:=(X1+X2+X3+X4)/4;
               Polygons[N].Yc:=(Y1+Y2+Y3+Y4)/4;
               Polygons[N].Zc:=(Z1+Z2+Z3+Z4)/4;
               Polygons[N].Color:=C;
          End;

    One thing you should be carefull with is to number your points clockwise
  (or anticlockwise... Just number them using the same standart... You'll
  understand why later). I'll explain how to do this now... Let's visualize
  the cube... Check out the picture Cube.Pcx, that has an explanation on how
  to the numbering. General rule (this is how I do it), number the points in
  clockwise manner if the polygon is facing you, and number it anti-clockwise
  if it faces the other way.
    Now, let's see the complete example (with an initialization, and a
  DrawSolid procedure, and a rotation procedure, that doesn't require
  explanation... It is just a rotation procedure that enables you to choose
  the center of rotation (in this case, the center of the cube):

          Program TestCube;

          Uses Mode13h,Crt;

          Const MaxPolys=6;

          Type Poly3d=Record
                            X1,Y1,Z1,
                            X2,Y2,Z2,
                            X3,Y3,Z3,
                            X4,Y4,Z4,
                            Xc,Yc,Zc:Real;
                            Color:Byte;
                       End;

          Type Solid3d=Record
                             NumPolys:Word;
                             Polys:Array[1..MaxPolys] Of Word;
                             Xc,Yc,Zc:Real;
                       End;

          Var Polygons:Array[1..MaxPolys] Of Poly3d;
              Cube:Solid3d;
              RotCentX,RotCentY,RotCentZ:Real;

          Procedure Gen3dPoly(N:Word;
                              X1,Y1,Z1,X2,Y2,Z2,
                              X3,Y3,Z3,X4,Y4,Z4:Real;
                              C:Byte);
          Begin
               Polygons[N].X1:=X1; Polygons[N].Y1:=Y1; Polygons[N].Z1:=Z1;
               Polygons[N].X2:=X2; Polygons[N].Y2:=Y2; Polygons[N].Z2:=Z2;
               Polygons[N].X3:=X3; Polygons[N].Y3:=Y3; Polygons[N].Z3:=Z3;
               Polygons[N].X4:=X4; Polygons[N].Y4:=Y4; Polygons[N].Z4:=Z4;
               Polygons[N].Xc:=(X1+X2+X3+X4)/4;
               Polygons[N].Yc:=(Y1+Y2+Y3+Y4)/4;
               Polygons[N].Zc:=(Z1+Z2+Z3+Z4)/4;
               Polygons[N].Color:=C;
          End;

          Procedure Rotate3dPoly(Var P:Poly3d;XAng,YAng,ZAng:Integer);
          { This rotates around a certain point, specified by the variables
            RotCentX, RotCentY and RotCentZ. We also rotate the center of
            the polygons, that change in order to the rotation point. }
          Var Angle:Integer;
              Temp:Real;
          Begin
               { Transform negative angles into positive ones }
               If XAng<0 Then XAng:=XAng+360;
               If YAng<0 Then YAng:=YAng+360;
               If ZAng<0 Then ZAng:=ZAng+360;
               { Make the coordinates of the points relative to the center }
               P.X1:=P.X1-RotCentX; P.Y1:=P.Y1-RotCentY;
               P.Z1:=P.Z1-RotCentZ;
               P.X2:=P.X2-RotCentX; P.Y2:=P.Y2-RotCentY;
               P.Z2:=P.Z2-RotCentZ;
               P.X3:=P.X3-RotCentX; P.Y3:=P.Y3-RotCentY;
               P.Z3:=P.Z3-RotCentZ;
               P.X4:=P.X4-RotCentX; P.Y4:=P.Y4-RotCentY;
               P.Z4:=P.Z4-RotCentZ;
               P.Xc:=P.Xc-RotCentX; P.Yc:=P.Yc-RotCentY;
               P.Zc:=P.Zc-RotCentZ;
               { Rotate all points of poly around Z axis }
               Angle:=ZAng;
               Temp:=P.X1;
               P.X1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
               P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X2;
               P.X2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
               P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X3;
               P.X3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
               P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X4;
               P.X4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
               P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xc;
               P.Xc:=Temp*Cosines^[Angle]-P.Yc*Sines^[Angle];
               P.Yc:=P.Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around X axis }
               Angle:=XAng;
               Temp:=P.Z1;
               P.Z1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
               P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z2;
               P.Z2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
               P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z3;
               P.Z3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
               P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z4;
               P.Z4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
               P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Zc;
               P.Zc:=Temp*Cosines^[Angle]-P.Yc*Sines^[Angle];
               P.Yc:=P.Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around Y axis }
               Angle:=YAng;
               Temp:=P.X1;
               P.X1:=Temp*Cosines^[Angle]-P.Z1*Sines^[Angle];
               P.Z1:=P.Z1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X2;
               P.X2:=Temp*Cosines^[Angle]-P.Z2*Sines^[Angle];
               P.Z2:=P.Z2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X3;
               P.X3:=Temp*Cosines^[Angle]-P.Z3*Sines^[Angle];
               P.Z3:=P.Z3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X4;
               P.X4:=Temp*Cosines^[Angle]-P.Z4*Sines^[Angle];
               P.Z4:=P.Z4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xc;
               P.Xc:=Temp*Cosines^[Angle]-P.Zc*Sines^[Angle];
               P.Zc:=P.Zc*Cosines^[Angle]+Temp*Sines^[Angle];
               { Transform the coordinates again }
               P.X1:=P.X1+RotCentX; P.Y1:=P.Y1+RotCentY;
               P.Z1:=P.Z1+RotCentZ;
               P.X2:=P.X2+RotCentX; P.Y2:=P.Y2+RotCentY;
               P.Z2:=P.Z2+RotCentZ;
               P.X3:=P.X3+RotCentX; P.Y3:=P.Y3+RotCentY;
               P.Z3:=P.Z3+RotCentZ;
               P.X4:=P.X4+RotCentX; P.Y4:=P.Y4+RotCentY;
               P.Z4:=P.Z4+RotCentZ;
               P.Xc:=P.Xc+RotCentX; P.Yc:=P.Yc+RotCentY;
               P.Zc:=P.Zc+RotCentZ;
          End;

          Procedure InitCube;
          Var A:Byte;
              Average:Real;
          Begin
               { Define the polygons that will be part of the solid }
               Gen3dPoly(1,-30,-30,226,30,-30,226,30,30,226,-30,30,226,1);
               Gen3dPoly(2,30,-30,226,30,-30,286,30,30,286,30,30,226,2);
               Gen3dPoly(3,30,-30,286,-30,-30,286,-30,30,286,30,30,286,3);
               Gen3dPoly(4,-30,-30,286,-30,-30,226,-30,30,226,-30,30,286,4);
               Gen3dPoly(5,-30,-30,286,30,-30,286,30,-30,226,-30,-30,226,5);
               Gen3dPoly(6,-30,30,286,30,30,286,30,30,226,-30,30,226,6);
               { Define the cube solid }
               Cube.NumPolys:=6;
               For A:=1 To 6 Do Cube.Polys[A]:=A;
               { Calc the center point }
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Xc;
               Cube.Xc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Yc;
               Cube.Yc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Zc;
               Cube.Zc:=Average/6;
          End;

          Procedure Conv3d(X,Y,Z:Real;Var Xt,Yt:Integer);
          Begin
               Xt:=160+Trunc((X*256/Z));
               Yt:=100+Trunc((Y*256/Z));
          End;

          Procedure Draw3dPoly(P:Poly3d;Where:Word);
          Var Tx1,Tx2,Tx3,Tx4,
              Ty1,Ty2,Ty3,Ty4:Integer;
          Begin
               Conv3d(P.X1,P.Y1,P.Z1,Tx1,Ty1);
               Conv3d(P.X2,P.Y2,P.Z2,Tx2,Ty2);
               Conv3d(P.X3,P.Y3,P.Z3,Tx3,Ty3);
               Conv3d(P.X4,P.Y4,P.Z4,Tx4,Ty4);
               FPoly(Tx1,Ty1,Tx2,Ty2,Tx3,Ty3,Tx4,Ty4,P.Color,Where);
          End;

          Procedure DrawSolid(S:Solid3d;Where:Word);
          Var A:Word;
          Begin
               For A:=1 To S.NumPolys Do
                 Draw3dPoly(Polygons[S.Polys[A]],Where);
          End;

          Procedure RotateSolid(S:Solid3d;XAng,YAng,ZAng:Integer);
          Var A:Word;
          Begin
               RotCentX:=S.Xc;
               RotCentY:=S.Yc;
               RotCentZ:=S.Zc;
               For A:=1 To S.NumPolys Do
                 Rotate3dPoly(Polygons[S.Polys[A]],XAng,YAng,ZAng);
          End;

          Begin
               InitGraph;
               InitVirt;
               InitTables;
               SetColor(0,0,0,0); SetColor(1,63,0,0); SetColor(2,63,63,0);
               SetColor(3,0,63,0); SetColor(4,0,63,63); SetColor(5,0,0,63);
               SetColor(6,63,32,0);
               Cls(0,VGA);
               InitCube;
               Repeat
                     Cls(0,Vp[1]);
                     DrawSolid(Cube,Vp[1]);
                     CopyPage(Vp[1],VGA);
                     RotateSolid(Cube,3,2,5);
               Until Keypressed;
               ClearTables;
               CloseGraph;
          End.

    After you run this program, and before you run to the definition of the
  cube to see what values have I made wrong, stop and think a bit... The error
  isn't in the definition... It is in the order by which the polygons are
  drawn on the screen... Confused ?!... Read on...

    4.2. Sorting

      4.2.1. The Painter's Algorithm

    Check out the following diagram... It represents the 3d universe seen
  from the top, with the cube in the middle:

                 ^z
                 |         Now, we know (by the way we've defined the cube),
              -------      that the polygon we seen in the lower part of the
              |  |  |      universe is polygon number 1. And that the polygon
           ---|--O--|--->  in the upper part is polygon 3. By the way we've
              |  |  |   x  defined the DrawSolid procedure, we first draw poly
              -------      1 and then we draw poly 3. It is obvious that poly
                 |         3 will obscure poly 1, but it should happen the
                           exact opposite !!!! But don't go running to the
       viewpoint ^         code changing the order, because if you rotate the
                 |         solid 180 degrees, you won't get this correct
                           that way !!!
    What we need it to sort the polygons by their Z's, so that the polygons
  that are further away are drawn first. This is known as the 'Painter's
  Algorythm', because that's the way painters paint... They draw first the
  things that are further away, and then the things that are nearer, and so
  forth.
    In the example program, I'll use a quicksort algorythm to do the sorting
  by the Z coordinates of the polygons. Check issue 10 of 'The Mag' to
  see what the hell I'm talking of. We aren't going to change the polygons
  from their normal order. We are just going to update an array that tells
  the order by which the polygons must be drawn (example, if the array Order
  has the values [1 2 6 3 2], the program will draw first poly 1, then 2,
  then 6, and so forth). We'll no longer use the DrawSolid procedure, exchanging
  it with the DrawPolys procedure, that draws all polygons, using the order
  suplied by the Order array:

          Program TestCube;

          Uses Mode13h,Crt;

          Const MaxPolys=6;

          Type Poly3d=Record
                            X1,Y1,Z1,
                            X2,Y2,Z2,
                            X3,Y3,Z3,
                            X4,Y4,Z4,
                            Xc,Yc,Zc:Real;
                            Color:Byte;
                       End;

          Type Solid3d=Record
                             NumPolys:Word;
                             Polys:Array[1..MaxPolys] Of Word;
                             Xc,Yc,Zc:Real;
                       End;

          Var Polygons:Array[1..MaxPolys] Of Poly3d;
              Cube:Solid3d;
              RotCentX,RotCentY,RotCentZ:Real;
              Order:Array[1..MaxPolys] Of Word;

          Procedure Gen3dPoly(N:Word;
                              X1,Y1,Z1,X2,Y2,Z2,
                              X3,Y3,Z3,X4,Y4,Z4:Real;
                              C:Byte);
          Begin
               Polygons[N].X1:=X1; Polygons[N].Y1:=Y1; Polygons[N].Z1:=Z1;
               Polygons[N].X2:=X2; Polygons[N].Y2:=Y2; Polygons[N].Z2:=Z2;
               Polygons[N].X3:=X3; Polygons[N].Y3:=Y3; Polygons[N].Z3:=Z3;
               Polygons[N].X4:=X4; Polygons[N].Y4:=Y4; Polygons[N].Z4:=Z4;
               Polygons[N].Xc:=(X1+X2+X3+X4)/4;
               Polygons[N].Yc:=(Y1+Y2+Y3+Y4)/4;
               Polygons[N].Zc:=(Z1+Z2+Z3+Z4)/4;
               Polygons[N].Color:=C;
          End;

          Procedure Rotate3dPoly(Var P:Poly3d;XAng,YAng,ZAng:Integer);
          { This rotates around a certain point, specified by the variables
            RotCentX, RotCentY and RotCentZ. We also rotate the center of
            the polygons, that change in order to the rotation point. }
          Var Angle:Integer;
              Temp:Real;
          Begin
               { Transform negative angles into positive ones }
               If XAng<0 Then XAng:=XAng+360;
               If YAng<0 Then YAng:=YAng+360;
               If ZAng<0 Then ZAng:=ZAng+360;
               { Make the coordinates of the points relative to the center }
               P.X1:=P.X1-RotCentX; P.Y1:=P.Y1-RotCentY;
               P.Z1:=P.Z1-RotCentZ;
               P.X2:=P.X2-RotCentX; P.Y2:=P.Y2-RotCentY;
               P.Z2:=P.Z2-RotCentZ;
               P.X3:=P.X3-RotCentX; P.Y3:=P.Y3-RotCentY;
               P.Z3:=P.Z3-RotCentZ;
               P.X4:=P.X4-RotCentX; P.Y4:=P.Y4-RotCentY;
               P.Z4:=P.Z4-RotCentZ;
               P.Xc:=P.Xc-RotCentX; P.Yc:=P.Yc-RotCentY;
               P.Zc:=P.Zc-RotCentZ;
               { Rotate all points of poly around Z axis }
               Angle:=ZAng;
               Temp:=P.X1;
               P.X1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
               P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X2;
               P.X2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
               P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X3;
               P.X3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
               P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X4;
               P.X4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
               P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xc;
               P.Xc:=Temp*Cosines^[Angle]-P.Yc*Sines^[Angle];
               P.Yc:=P.Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around X axis }
               Angle:=XAng;
               Temp:=P.Z1;
               P.Z1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
               P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z2;
               P.Z2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
               P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z3;
               P.Z3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
               P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z4;
               P.Z4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
               P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Zc;
               P.Zc:=Temp*Cosines^[Angle]-P.Yc*Sines^[Angle];
               P.Yc:=P.Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around Y axis }
               Angle:=YAng;
               Temp:=P.X1;
               P.X1:=Temp*Cosines^[Angle]-P.Z1*Sines^[Angle];
               P.Z1:=P.Z1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X2;
               P.X2:=Temp*Cosines^[Angle]-P.Z2*Sines^[Angle];
               P.Z2:=P.Z2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X3;
               P.X3:=Temp*Cosines^[Angle]-P.Z3*Sines^[Angle];
               P.Z3:=P.Z3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X4;
               P.X4:=Temp*Cosines^[Angle]-P.Z4*Sines^[Angle];
               P.Z4:=P.Z4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xc;
               P.Xc:=Temp*Cosines^[Angle]-P.Zc*Sines^[Angle];
               P.Zc:=P.Zc*Cosines^[Angle]+Temp*Sines^[Angle];
               { Transform the coordinates again }
               P.X1:=P.X1+RotCentX; P.Y1:=P.Y1+RotCentY;
               P.Z1:=P.Z1+RotCentZ;
               P.X2:=P.X2+RotCentX; P.Y2:=P.Y2+RotCentY;
               P.Z2:=P.Z2+RotCentZ;
               P.X3:=P.X3+RotCentX; P.Y3:=P.Y3+RotCentY;
               P.Z3:=P.Z3+RotCentZ;
               P.X4:=P.X4+RotCentX; P.Y4:=P.Y4+RotCentY;
               P.Z4:=P.Z4+RotCentZ;
               P.Xc:=P.Xc+RotCentX; P.Yc:=P.Yc+RotCentY;
               P.Zc:=P.Zc+RotCentZ;
          End;

          Procedure InitCube;
          Var A:Byte;
              Average:Real;
          Begin
               { Define the polygons that will be part of the solid }
               Gen3dPoly(1,-30,-30,226,30,-30,226,30,30,226,-30,30,226,1);
               Gen3dPoly(2,30,-30,226,30,-30,286,30,30,286,30,30,226,2);
               Gen3dPoly(3,30,-30,286,-30,-30,286,-30,30,286,30,30,286,3);
               Gen3dPoly(4,-30,-30,286,-30,-30,226,-30,30,226,-30,30,286,4);
               Gen3dPoly(5,30,-30,286,-30,-30,286,-30,-30,226,30,-30,226,6);
               Gen3dPoly(6,-30,30,286,30,30,286,30,30,226,-30,30,226,5);
               { Define the cube solid }
               Cube.NumPolys:=6;
               For A:=1 To 6 Do Cube.Polys[A]:=A;
               { Calc the center point }
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Xc;
               Cube.Xc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Yc;
               Cube.Yc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Zc;
               Cube.Zc:=Average/6;
          End;

          Procedure Conv3d(X,Y,Z:Real;Var Xt,Yt:Integer);
          Begin
               Xt:=160+Trunc((X*256/Z));
               Yt:=100+Trunc((Y*256/Z));
          End;

          Procedure Draw3dPoly(P:Poly3d;Where:Word);
          Var Tx1,Tx2,Tx3,Tx4,
              Ty1,Ty2,Ty3,Ty4:Integer;
          Begin
               Conv3d(P.X1,P.Y1,P.Z1,Tx1,Ty1);
               Conv3d(P.X2,P.Y2,P.Z2,Tx2,Ty2);
               Conv3d(P.X3,P.Y3,P.Z3,Tx3,Ty3);
               Conv3d(P.X4,P.Y4,P.Z4,Tx4,Ty4);
               FPoly(Tx1,Ty1,Tx2,Ty2,Tx3,Ty3,Tx4,Ty4,P.Color,Where);
          End;

          Procedure DrawPolys(Where:Word);
          Var A:Word;
          Begin
               { We draw the polys in reversed order because the sorting is
                 from the smallest to the biggest, and we are interested in
                 the reverse }
               For A:=MaxPolys DownTo 1 Do
                 Draw3dPoly(Polygons[Order[A]],Where);
          End;

          Procedure RotateSolid(S:Solid3d;XAng,YAng,ZAng:Integer);
          Var A:Word;
          Begin
               RotCentX:=S.Xc;
               RotCentY:=S.Yc;
               RotCentZ:=S.Zc;
               For A:=1 To S.NumPolys Do
                 Rotate3dPoly(Polygons[S.Polys[A]],XAng,YAng,ZAng);
          End;

          Procedure SortPolys;
          Var Flag:Boolean;
              I,J:Integer;
              X:Integer;
              N:Real;
              T:Real;

              Procedure SortSubArray(Left,Right:Byte);
              Begin
                   { Partition }
                   I:=Left;
                   J:=Right;
                   N:=Polygons[Order[(Left+Right) Div 2]].Zc;
                   Repeat
                         { Find first number from the left to be < N }
                         While Polygons[Order[I]].Zc<N Do Inc(I);
                         { Find first number from the right to be > N }
                         While Polygons[Order[J]].Zc>N Do Dec(J);
                         { Exchange }
                         If I<=J Then
                         Begin
                              X:=Order[J];
                              Order[J]:=Order[I];
                              Order[I]:=X;
                              Inc(I);
                              Dec(J);
                         End;
                   Until J<I;
                   { Order left and right subarrays }
                   If Left<J Then SortSubArray(Left,J);
                   If I<Right Then SortSubArray(I,Right);
              End;

          Begin
               For I:=1 To MaxPolys Do Order[I]:=I;
               SortSubArray(1,MaxPolys);
          End;


          Begin
               InitGraph;
               InitVirt;
               InitTables;
               SetColor(0,0,0,0); SetColor(1,63,0,0); SetColor(2,63,63,0);
               SetColor(3,0,63,0); SetColor(4,0,63,63); SetColor(5,0,0,63);
               SetColor(6,63,32,0);
               Cls(0,VGA);
               InitCube;
               Repeat
                     Cls(0,Vp[1]);
                     SortPolys;
                     DrawPolys(Vp[1]);
                     CopyPage(Vp[1],VGA);
                     RotateSolid(Cube,3,2,4);
               Until Keypressed;
               ClearTables;
               CloseGraph;
          End.

    See ?! Almost perfect sorting... The failures in the edges are due to
  the calculations that aren't so perfect as they should be (using TRUNC,
  instead of ROUND, I think). But they are of little importance.
    Just one thing I must say before moving on. All the concepts on 3d so far
  are assuming that the camera (our viewpoint) doesn't move around and it
  is looking down the positive Z's axis. The perspective equations and this
  type of sorting would have to be altered in order to suport moving cameras,
  but I'll leave that for a future issue. The rotations, translations, and
  the backface removal I'll speak of later aren't affected by this.
    Now, I'll give a quick overview of two other methods of sorting:

      4.2.2. Z-Buffer

    The Z-Buffer sorting method works in the following way: it keeps an array
  with the size of the screen.
    That array would store the Z-value of all the points on screen.
    Then, everytime you would draw a point to the screen, you would check if
  the point that is already in that position has a greater Z value than the
  point you want to plot. If yes, then the point you want to plot is closer
  to the viewer, and in that case it gets plotted. Otherwise, it is discarted.
    The advantage of this method is speed (I think)... The drawbacks is that
  it only works with fixed-cameras (but see below) and it spends lots of
  memory.
    Just one more thing... Usually the value that is stored is (1/Z), because
  of some speed and mathematical implications (I'm not sure of what they are,
  so I won't be explaining them until I know them well). The only difference
  is that now a point gets ploted if (1/Z) of the point is bigger that the
  value stored in the buffer (simple maths: 2<3 implies that (1/2)>(1/3)).
    There is a variant of this method, made to work with moving cameras. I
  think it is called the D-Buffer (or Distance-Buffer). Instead of storing the
  Z value, it stores the distance from the viewer. This method isn't very good,
  because calculting the distance implies getting a square root, a 3 squares,
  plus two addictions... So, the speed edge of Z-buffer get's lost... It is
  sometimes used with voxels (I'll do an article on them soon).

      4.2.3. BSP Trees

    BSP stands for Binary Space Partition.
    The BSP trees algorithm was conceived for several reasons:

        1) To provide a good algorithm for any viewpoint
        2) To solve problems that no sorting method could, like polygon
           overlapping/instersection.

    The ideia behind BSP Trees is to build a tree with the polygons, in which
  a relationship of order is implied from branch to branch.
    Confused ?! You should be, because I think that BSP Tree is one of the
  hardest sorting methods. To understand it fully, you must have a good
  knowledge of analitic geometry... I won't do here a complete guide on the
  workings of it, because I'm not suficiently at ease with the subject...
  Maybe one day I'll make an article on it...
    The disadvantages of BSP trees are:

        1) Lots of memory needed
        2) SLOW !!!!!
        3) Only really usefull for static scenes (most raytracers use BSP
           trees). BSP trees with moving objects are a nightmare to manage.

    So, BSP trees suck for demos and games... I just included it here to give
  you an ideia of what it is.

    Now, I'll delve into the world of backface removal. But before that, I'll
  delve into the world of...

    4.3. Vector maths

      4.3.1. Basic concepts

    Vectors are very necessary when it comes to 3d... I tried to steer away
  from them in the beggining, so that you could addapt to the basics of 3d
  without worring with the math underlying it.
    Before you start puking, just because I said 'maths', think of this: 'You
  only dislike what you don't need nor understand'... I hope you'll understand
  the concepts I'll try to transmit and you'll see the usefullness of them
  (specially when it comes to lighting).
    So, let's start this quick crash course on vectors. First of all, what is
  a vector. Well, we can define vector as a line joining the origin of the
  3d universe and a point. This isn't very correct, mathematically speaking,
  because of this (oh good, more ASCII !!):

          |
          |         Y    Vector A is defined as (3,3), that is the coordinate
        4 |   P    /     of point P. But vector A isn't diference of vector
        3 |  /    /B     B, that is defined by points X and Y, because if you
        2 | /A   /       translate B to the origin (if X overlaps O), you will
        1 |/    X        get vector A. So, a vector can be defined as the
          O____________  diference between two points. A size, a direction
           1 3 5 7 9     and an orientation caracterize a vector, but we'll
            2 4 6 8      get to that in just some instances. Notice that
                         vector B can be defined as Y-X=(9-6,4-1)=(3,3)=A !!

    Now, let's get to some more basic concepts. Let's define vector orientation
  and vector direction. The best way to explain this is by using an example:

             |                    B
             |          A        /
             |         /        /
             |        K        /        /
             |       /        W        V
             |      /        /        /
             |              /        C     --U--D
             |___________________________________

    Imagine four vectors, K, W, V and U, that point respectively to A, B, C
  and D.
    First, let's go to orientation. K, W and V have the same orientation, and
  U has a diferent orientation. Mathematically speaking, we can think of
  orientation has having a direct relationship with the angle the vector does
  with the axis (remember that the true position of the vector in space doesn't
  matter, it can be translated).
    Now, let's speak direction. K and W have the same direction, while V
  doesn't. You can empirically think of direction as 'where the vector points'.
  Just think of it like this. If you translate K and W to the origin shrink
  W in order for him to become of the same size as K, the vectors would point
  to the same point. Now, you couldn't do that with V... The direction of V
  is the oposite to the direction of K and W.
    Another thing to keep in mind is that direction and orientation are size
  independant.
    Now, let's get to size.
    The size of the vector is defined by an operation known as the module:

                       |X| = Sqrt(X^2+Y^2+Z^2)

    This definition is for 3d vectors. If the vector is defined by two points:

        X=P1-P2  =>  |X|= Sqrt((P1.X-P2.X)^2+(P1.Y-P2.Y)^2+(P1.Z-P2.Z)^2)

    Now, let's define some basic vector operations:

    |   ^....
    |   |  /.           Vector W is the sum vector of U and V.
    |   U W .           In a 2d case, imagine that U=(a,b) and V=(c,d). Then,
    |   |/  .           W=U+V=(a+c,b+d).
    |   |-V->           In a 3d case, imagine that U=(a,b,c) and V=(d,e,f).
    |______________     In that case, W=(a+d,b+e,c+f).

    Subtraction follows the same line of thinking. Now, let's get to some
  more complex vector maths:

      4.3.2. Multiplications

    There are three types of multiplications with vectors: the scalar product,
  the dot product (also known as external product) and the cross product (also
  known as internal product, or vector product).
    Their definitions are quite simple.
    The scalar product is the product between a vector and a scalar number (a
  scalar number is a normal number):

                      a*X=a*(X1,X2,X3)=(aX1,aX2,aX3)

    Example:
                       2.5*(10,5,2.5)=(25,12.5,6.25)

    Notice that division can be thought in the same way:

                           (10,8,6)
                           -------- = (5,4,3)
                               2

    The dot product returns a value:

                       A.B=(a,b,c).(d,e,f)= ad+be+cf

    The cross product returns a vector:

                 AxB=(a,b,c)x(d,e,f)=(bf-ec,af-cd,ae-bd)

    Weird, isn't it ? Notice that AxB isn't equal to BxA:

                 BxA=(d,e,f)x(a,b,c)=(ec-bf,cd-af,bd-ae)

    Actually, the two resulting vectors have the same orientation and size,
  but oposite direction, so:

                            AxB= -BxA

    Well, they are both usefull, as you'll see later.
    One interesting propretie of the cross product is that the resulting vector
  is perpendicular with the other two, that is, it makes 90 degrees with them
  both !! This is going to be very usefull.
    Another interesting thing is that the module can be expressed with the
  aid of the dot product:

                            |X|= Sqrt(A.A)

    The last (and maybe the more important) property is that you can find
  the angle between two vectors using the dot product. The relation is this:

                                       A.B
                       Cos (Angle) = -------
                                     |A|*|B|

    If the vectors are unitary (see below), the relation is even simpler:

                          Cos (Angle) = A.B

    which implies:
                         Angle = ArcCos(A.B)

    Now, let's move onto...

      4.3.3. Special vectors

    There are some special vectors that re known for several properties.
    There is the null vector, defined as:
                              _
                              0 = (0,0,0)

    Properties include the fact that any of the products is 0 if one of the
  multiplicands is the null vector. Also, the null vector doesn't affect
  addition or subtraction.
    Other special kind of vectors are unitary vectors. A unitary vector is
  a vector whose size is equal to 1, that is:

                                 |X|=1

    The process of transforming a vector of any size into a vector of unitary
  size is the normalization.
    You can normalize vector W like this:

                                  W
                            W'= -----
                                 |W|

    W' will be the normalized vector.
    Vectors in this category are many, but there are some that are very
  special.

          1) The canonic vectors: These vectors define a space. That means
             that any vector in that space can be expressed in terms of those
             two vectors. For example, the normal 2d space canonic base is
             vectors X and Y, that are equal to (1,0) and (0,1), respectively.
             For example, we want to define vector W in that space, W=(5,3).
             So, W= 5X+3Y...
             In 3d, the canonic vectors are (1,0,0);(0,1,0);(0,0,1).

          2) The normal vector: A normal vector (or normal for short) is a
             vector perpendicular to a plane (or a polygon, for usually it can
             be thought as a plane). It is very usefull for backface removal,
             and almost all kinds of shading and bumpmapping.

    Well, I think this quite covers it all, in respect to vector maths. If you
  didn't understood shit, mail me, or get a good 9th grade maths book (I think
  this is standart 9th grade stuff).
    Now, finally, on to:

    4.4. Backface removal

    Notice that in a cube, only 3 sides of it are visible at once ?
    Wouldn't it be great if you could only draw the 3 visibles ?
    Yes, it would... And it is easier to do than you thought.
    Notice that the sides that shouldn't be drawn are facing the away from
  you... If we could find out which ones are facing away and the ones who
  are facing towards us, we could draw just the ones that matter to us, and
  discard the others. This works not only with cubes, but also with any kind
  of solid that closes around himself !!!
    So, how we'll do this ?
    Using the NORMAL !!!
    If we calculate the normal vector of the polygon, we can know where the
  polygon's facing !!

   Z^                  U and V are the normals to sides 1 and 2, respectively.
    |    ^             Now it is becoming to get clear why we've defined the
    |    |V            points of the polygons in a clockwise manner (anti-
    |    |             clockwise to those who face away from us) ?
    |  --2--           No ?!
    |  |   |           Then I'll explain... To determine the normal to the
    |  --1--           polygon, we need 2 vectors belonging to the polygon.
    |    |             We'll choose the first three points of the four points
    |    |U            of the polygons, calling them a, b and c. So, if we
    |    |             call A and B to (a-b) and (a-c) respectively, and we
    |___________>      calculate the cross product AxB, we'll get a vector
                X      perpendicular (or normal) to the polygon, and facing
                       away from us if the points are anticlockwise, or facing
                       toward us if the points were defined clockwise.
    Now, we just find out the angle between the normal of the polygon and the
  view vector (in our case, (0,0,1)), and if the angle is between 90 and 270
  degrees, we display the polygon, otherwise, we do not.
    In practice, following the ideia that our camera is fixed and it is
  staring down the Z axis, we can just check if the normal's Z element is
  positive or not !!
    This gives a great speed up to the program, since some sides don't require
  to be drawn... You don't even have to calc the normals every frame !! It
  can be done in the beggining of the program, and then the normal is rotated
  with the rest of the points !
    Check out the following example... It was done based on the first example I
  showed (before the sorting example). I use only backface removal to render
  the cube correctly.
    Notice that the Poly3d structure was altered to include the normal, as
  well as the rotation procedures, and the initialization of the polygons
  (the Gen3dPoly procedure)...


          Program TestCube;

          Uses Mode13h,Crt;

          Const MaxPolys=6;

          Type Poly3d=Record
                            X1,Y1,Z1,
                            X2,Y2,Z2,
                            X3,Y3,Z3,
                            X4,Y4,Z4,
                            Xc,Yc,Zc:Real;
                            Xn,Yn,Zn:Real;
                            Color:Byte;
                       End;

          Type Solid3d=Record
                             NumPolys:Word;
                             Polys:Array[1..MaxPolys] Of Word;
                             Xc,Yc,Zc:Real;
                       End;

          Var Polygons:Array[1..MaxPolys] Of Poly3d;
              Cube:Solid3d;
              RotCentX,RotCentY,RotCentZ:Real;

          Procedure Gen3dPoly(N:Word;
                              X1,Y1,Z1,X2,Y2,Z2,
                              X3,Y3,Z3,X4,Y4,Z4:Real;
                              C:Byte);
          Var Module:Real;
          Begin
               Polygons[N].X1:=X1; Polygons[N].Y1:=Y1; Polygons[N].Z1:=Z1;
               Polygons[N].X2:=X2; Polygons[N].Y2:=Y2; Polygons[N].Z2:=Z2;
               Polygons[N].X3:=X3; Polygons[N].Y3:=Y3; Polygons[N].Z3:=Z3;
               Polygons[N].X4:=X4; Polygons[N].Y4:=Y4; Polygons[N].Z4:=Z4;
               Polygons[N].Xc:=(X1+X2+X3+X4)/4;
               Polygons[N].Yc:=(Y1+Y2+Y3+Y4)/4;
               Polygons[N].Zc:=(Z1+Z2+Z3+Z4)/4;
               Polygons[N].Color:=C;
               { Calc the normals }
               Polygons[N].Xn:=(Z2-Z1)*(Y3-Y1)-(Y2-Y1)*(Z3-Z1);
               Polygons[N].Yn:=(Z2-Z1)*(X3-X1)-(X2-X1)*(Z3-Z1);
               Polygons[N].Zn:=(Y2-Y1)*(X3-X1)-(X2-X1)*(Y3-Y1);
               { Normalize the normals }
               Module:=Sqrt(Sqr(Polygons[N].Xn)+Sqr(Polygons[N].Yn)+
                            Sqr(Polygons[N].Zn));
               Polygons[N].Xn:=Polygons[N].Xn/Module;
               Polygons[N].Yn:=Polygons[N].Yn/Module;
               Polygons[N].Zn:=Polygons[N].Zn/Module;
          End;

          Procedure Rotate3dPoly(Var P:Poly3d;XAng,YAng,ZAng:Integer);
          { This rotates around a certain point, specified by the variables
            RotCentX, RotCentY and RotCentZ. We also rotate the center of
            the polygons, that change in order to the rotation point,
            and the normal of the polygon. }
          Var Angle:Integer;
              Temp:Real;
          Begin
               { Transform negative angles into positive ones }
               If XAng<0 Then XAng:=XAng+360;
               If YAng<0 Then YAng:=YAng+360;
               If ZAng<0 Then ZAng:=ZAng+360;
               { Make the coordinates of the points relative to the center }
               P.X1:=P.X1-RotCentX; P.Y1:=P.Y1-RotCentY;
               P.Z1:=P.Z1-RotCentZ;
               P.X2:=P.X2-RotCentX; P.Y2:=P.Y2-RotCentY;
               P.Z2:=P.Z2-RotCentZ;
               P.X3:=P.X3-RotCentX; P.Y3:=P.Y3-RotCentY;
               P.Z3:=P.Z3-RotCentZ;
               P.X4:=P.X4-RotCentX; P.Y4:=P.Y4-RotCentY;
               P.Z4:=P.Z4-RotCentZ;
               P.Xc:=P.Xc-RotCentX; P.Yc:=P.Yc-RotCentY;
               P.Zc:=P.Zc-RotCentZ;
               { Rotate all points of poly around Z axis }
               Angle:=ZAng;
               Temp:=P.X1;
               P.X1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
               P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X2;
               P.X2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
               P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X3;
               P.X3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
               P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X4;
               P.X4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
               P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xc;
               P.Xc:=Temp*Cosines^[Angle]-P.Yc*Sines^[Angle];
               P.Yc:=P.Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xn;
               P.Xn:=Temp*Cosines^[Angle]-P.Yn*Sines^[Angle];
               P.Yn:=P.Yn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around X axis }
               Angle:=XAng;
               Temp:=P.Z1;
               P.Z1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
               P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z2;
               P.Z2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
               P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z3;
               P.Z3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
               P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z4;
               P.Z4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
               P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Zc;
               P.Zc:=Temp*Cosines^[Angle]-P.Yc*Sines^[Angle];
               P.Yc:=P.Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Zn;
               P.Zn:=Temp*Cosines^[Angle]-P.Yn*Sines^[Angle];
               P.Yn:=P.Yn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around Y axis }
               Angle:=YAng;
               Temp:=P.X1;
               P.X1:=Temp*Cosines^[Angle]-P.Z1*Sines^[Angle];
               P.Z1:=P.Z1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X2;
               P.X2:=Temp*Cosines^[Angle]-P.Z2*Sines^[Angle];
               P.Z2:=P.Z2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X3;
               P.X3:=Temp*Cosines^[Angle]-P.Z3*Sines^[Angle];
               P.Z3:=P.Z3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X4;
               P.X4:=Temp*Cosines^[Angle]-P.Z4*Sines^[Angle];
               P.Z4:=P.Z4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xc;
               P.Xc:=Temp*Cosines^[Angle]-P.Zc*Sines^[Angle];
               P.Zc:=P.Zc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xn;
               P.Xn:=Temp*Cosines^[Angle]-P.Zn*Sines^[Angle];
               P.Zn:=P.Zn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Transform the coordinates again }
               P.X1:=P.X1+RotCentX; P.Y1:=P.Y1+RotCentY;
               P.Z1:=P.Z1+RotCentZ;
               P.X2:=P.X2+RotCentX; P.Y2:=P.Y2+RotCentY;
               P.Z2:=P.Z2+RotCentZ;
               P.X3:=P.X3+RotCentX; P.Y3:=P.Y3+RotCentY;
               P.Z3:=P.Z3+RotCentZ;
               P.X4:=P.X4+RotCentX; P.Y4:=P.Y4+RotCentY;
               P.Z4:=P.Z4+RotCentZ;
               P.Xc:=P.Xc+RotCentX; P.Yc:=P.Yc+RotCentY;
               P.Zc:=P.Zc+RotCentZ;
          End;

          Procedure InitCube;
          Var A:Byte;
              Average:Real;
          Begin
               { Define the polygons that will be part of the solid }
               Gen3dPoly(1,-30,-30,226,30,-30,226,30,30,226,-30,30,226,1);
               Gen3dPoly(2,30,-30,226,30,-30,286,30,30,286,30,30,226,2);
               Gen3dPoly(3,30,-30,286,-30,-30,286,-30,30,286,30,30,286,3);
               Gen3dPoly(4,-30,-30,286,-30,-30,226,-30,30,226,-30,30,286,4);
               Gen3dPoly(5,30,-30,286,-30,-30,286,-30,-30,226,30,-30,226,6);
               Gen3dPoly(6,-30,30,286,30,30,286,30,30,226,-30,30,226,5);
               { Define the cube solid }
               Cube.NumPolys:=6;
               For A:=1 To 6 Do Cube.Polys[A]:=A;
               { Calc the center point }
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Xc;
               Cube.Xc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Yc;
               Cube.Yc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Zc;
               Cube.Zc:=Average/6;
          End;

          Procedure Conv3d(X,Y,Z:Real;Var Xt,Yt:Integer);
          Begin
               Xt:=160+Trunc((X*256/Z));
               Yt:=100+Trunc((Y*256/Z));
          End;

          Procedure Draw3dPoly(P:Poly3d;Where:Word);
          Var Tx1,Tx2,Tx3,Tx4,
              Ty1,Ty2,Ty3,Ty4:Integer;
          Begin
               Conv3d(P.X1,P.Y1,P.Z1,Tx1,Ty1);
               Conv3d(P.X2,P.Y2,P.Z2,Tx2,Ty2);
               Conv3d(P.X3,P.Y3,P.Z3,Tx3,Ty3);
               Conv3d(P.X4,P.Y4,P.Z4,Tx4,Ty4);
               FPoly(Tx1,Ty1,Tx2,Ty2,Tx3,Ty3,Tx4,Ty4,P.Color,Where);
          End;

          Procedure DrawSolid(S:Solid3d;Where:Word);
          Var A:Word;
          Begin
               For A:=1 To S.NumPolys Do
               { Draw if the normal is negative }
                 If (Polygons[S.Polys[A]].Zn<0) Then
                   Draw3dPoly(Polygons[S.Polys[A]],Where);
          End;

          Procedure RotateSolid(S:Solid3d;XAng,YAng,ZAng:Integer);
          Var A:Word;
          Begin
               RotCentX:=S.Xc;
               RotCentY:=S.Yc;
               RotCentZ:=S.Zc;
               For A:=1 To S.NumPolys Do
                 Rotate3dPoly(Polygons[S.Polys[A]],XAng,YAng,ZAng);
          End;

          Begin
               InitGraph;
               InitVirt;
               InitTables;
               SetColor(0,0,0,0); SetColor(1,63,0,0); SetColor(2,63,63,0);
               SetColor(3,0,63,0); SetColor(4,0,63,63); SetColor(5,0,0,63);
               SetColor(6,63,32,0);
               Cls(0,VGA);
               InitCube;
               Repeat
                     Cls(0,Vp[1]);
                     DrawSolid(Cube,Vp[1]);
                     CopyPage(Vp[1],VGA);
                     RotateSolid(Cube,5,5,5);
               Until Keypressed;
               ClearTables;
               CloseGraph;
          End.

    There are lots of gliches in the display, but that's due to errors in the
  calculation of the rotation of the normals. The way to mend this is fairly
  simple (altough it requires more memory). You define two Solid3d structures
  for every object in the world. In one of them, you have the object without
  any translations or rotations, while you affect the the other structure with
  this. I didn't explain myself too well... You use the accurate original
  values to calculate the rotations, and, instead of overwriting the accurate
  values, you transfer those values to another structure, and display that one.
  Then, in the next frame, instead of using the inacurate rotated structure,
  you use the original accurate one... The next example uses this theory.
    The theory behind the next example is to have several objects in the 3d
  world (three cubes, actually). And they rotate around themselfs, and one of
  them rotates around other cube. This example is fairly complex, but it is
  not hard to understand. It uses both backface removal and facesorting.
    But, before I code this one, I'm going to sleep a bit... It's 0:30 and I
  need my beauty rest... I hope I finish this tomorrow, and the lighting
  article, not to mention the crossfading one... This article is taking
  almost 9 hours to get writen!!! Am I slow or I spend this time doing
  something good ?! You tell me ! :)))
    Here I am again, at 11:30, preparing to code another masterpiece ! :)))
    When I was coding this masterpiece, I came up with an interesting problem.
  When the object move a lot around the screen, specially when it comes to
  edges, you should be able to see the polygons that previously faced away
  from you. But the program won't let you see them, because your normal is
  still negative. For this type of programs, you should (instead of just
  checking if it is positive or negative) check the angle between the view
  vector and the normal. If it is between 90 and 270, you display the poly,
  otherwise you discard it. Notice that the view vector for an object that
  is near an edge isn't (0,0,1) as we were putting it... It is the vector that
  points to that polygon. That would require the program to calculate
  distances and other stuff like that... And that's too heavy maths for an
  optimization, so we're gonna loose tha backface removal in this example...
  Too bad... :(( But never forget that the backface removal is very good when
  the object you want to render is in front of you !!! We'll keep the normals
  and that stuff, because they are needed when I adapt this program to include
  lightsourcing.

          Program TestCube;

          Uses Mode13h,Crt;

          Const MaxPolys=18;    { For 3 cubes }

          Type Poly3d=Record
                            X1,Y1,Z1,
                            X2,Y2,Z2,
                            X3,Y3,Z3,
                            X4,Y4,Z4,
                            Xc,Yc,Zc:Real;
                            Xn,Yn,Zn:Real;
                            Color:Byte;
                       End;

          Type Solid3d=Record
                             NumPolys:Word;
                             Polys:Array[1..MaxPolys] Of Word;
                             Xc,Yc,Zc:Real;
                             RotX,RotY,RotZ:Integer; { The orientation of the
                                                       solid }
                       End;

          Var Polygons:Array[1..MaxPolys] Of Poly3d;   { The accurate polys }
              TPolys:Array[1..MaxPolys] Of Poly3d;    { The inaccurate ones }
              RotCentX,RotCentY,RotCentZ:Real;
              Order:Array[1..MaxPolys] Of Word;

          Var Cube1,Cube2,Cube3:Solid3d;
              XInc:Integer;

          Procedure Gen3dPoly(N:Word;
                              X1,Y1,Z1,X2,Y2,Z2,
                              X3,Y3,Z3,X4,Y4,Z4:Real;
                              C:Byte);
          Var Module:Real;
          Begin
               Polygons[N].X1:=X1; Polygons[N].Y1:=Y1; Polygons[N].Z1:=Z1;
               Polygons[N].X2:=X2; Polygons[N].Y2:=Y2; Polygons[N].Z2:=Z2;
               Polygons[N].X3:=X3; Polygons[N].Y3:=Y3; Polygons[N].Z3:=Z3;
               Polygons[N].X4:=X4; Polygons[N].Y4:=Y4; Polygons[N].Z4:=Z4;
               Polygons[N].Xc:=(X1+X2+X3+X4)/4;
               Polygons[N].Yc:=(Y1+Y2+Y3+Y4)/4;
               Polygons[N].Zc:=(Z1+Z2+Z3+Z4)/4;
               Polygons[N].Color:=C;
               { Calc the normals }
               Polygons[N].Xn:=(Z2-Z1)*(Y3-Y1)-(Y2-Y1)*(Z3-Z1);
               Polygons[N].Yn:=(Z2-Z1)*(X3-X1)-(X2-X1)*(Z3-Z1);
               Polygons[N].Zn:=(Y2-Y1)*(X3-X1)-(X2-X1)*(Y3-Y1);
               { Normalize the normals }
               Module:=Sqrt(Sqr(Polygons[N].Xn)+Sqr(Polygons[N].Yn)+
                            Sqr(Polygons[N].Zn));
               Polygons[N].Xn:=Polygons[N].Xn/Module;
               Polygons[N].Yn:=Polygons[N].Yn/Module;
               Polygons[N].Zn:=Polygons[N].Zn/Module;
          End;

          Procedure Rotate3dPoly(N:Word;XAng,YAng,ZAng:Integer);
          { This rotates polygon N around a certain point, specified by the
            variables RotCentX, RotCentY and RotCentZ. We also rotate the
            center of the polygons, that change in order to the rotation
            point, and the normal of the polygon. }
          Var Angle:Integer;
              Temp:Real;
          Begin
               { Transform negative angles into positive ones }
               If XAng<0 Then XAng:=XAng+360;
               If YAng<0 Then YAng:=YAng+360;
               If ZAng<0 Then ZAng:=ZAng+360;
               { Make the coordinates of the points relative to the center }
               TPolys[N].X1:=TPolys[N].X1-RotCentX;
               TPolys[N].Y1:=TPolys[N].Y1-RotCentY;
               TPolys[N].Z1:=TPolys[N].Z1-RotCentZ;
               TPolys[N].X2:=TPolys[N].X2-RotCentX;
               TPolys[N].Y2:=TPolys[N].Y2-RotCentY;
               TPolys[N].Z2:=TPolys[N].Z2-RotCentZ;
               TPolys[N].X3:=TPolys[N].X3-RotCentX;
               TPolys[N].Y3:=TPolys[N].Y3-RotCentY;
               TPolys[N].Z3:=TPolys[N].Z3-RotCentZ;
               TPolys[N].X4:=TPolys[N].X4-RotCentX;
               TPolys[N].Y4:=TPolys[N].Y4-RotCentY;
               TPolys[N].Z4:=TPolys[N].Z4-RotCentZ;
               TPolys[N].Xc:=TPolys[N].Xc-RotCentX;
               TPolys[N].Yc:=TPolys[N].Yc-RotCentY;
               TPolys[N].Zc:=TPolys[N].Zc-RotCentZ;
               { Rotate all points of poly around Z axis }
               Angle:=ZAng;
               Temp:=TPolys[N].X1;
               TPolys[N].X1:=Temp*Cosines^[Angle]-TPolys[N].Y1*Sines^[Angle];
               TPolys[N].Y1:=TPolys[N].Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X2;
               TPolys[N].X2:=Temp*Cosines^[Angle]-TPolys[N].Y2*Sines^[Angle];
               TPolys[N].Y2:=TPolys[N].Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X3;
               TPolys[N].X3:=Temp*Cosines^[Angle]-TPolys[N].Y3*Sines^[Angle];
               TPolys[N].Y3:=TPolys[N].Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X4;
               TPolys[N].X4:=Temp*Cosines^[Angle]-TPolys[N].Y4*Sines^[Angle];
               TPolys[N].Y4:=TPolys[N].Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Xc;
               TPolys[N].Xc:=Temp*Cosines^[Angle]-TPolys[N].Yc*Sines^[Angle];
               TPolys[N].Yc:=TPolys[N].Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Xn;
               TPolys[N].Xn:=Temp*Cosines^[Angle]-TPolys[N].Yn*Sines^[Angle];
               TPolys[N].Yn:=TPolys[N].Yn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around X axis }
               Angle:=XAng;
               Temp:=TPolys[N].Z1;
               TPolys[N].Z1:=Temp*Cosines^[Angle]-TPolys[N].Y1*Sines^[Angle];
               TPolys[N].Y1:=TPolys[N].Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Z2;
               TPolys[N].Z2:=Temp*Cosines^[Angle]-TPolys[N].Y2*Sines^[Angle];
               TPolys[N].Y2:=TPolys[N].Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Z3;
               TPolys[N].Z3:=Temp*Cosines^[Angle]-TPolys[N].Y3*Sines^[Angle];
               TPolys[N].Y3:=TPolys[N].Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Z4;
               TPolys[N].Z4:=Temp*Cosines^[Angle]-TPolys[N].Y4*Sines^[Angle];
               TPolys[N].Y4:=TPolys[N].Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Zc;
               TPolys[N].Zc:=Temp*Cosines^[Angle]-TPolys[N].Yc*Sines^[Angle];
               TPolys[N].Yc:=TPolys[N].Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Zn;
               TPolys[N].Zn:=Temp*Cosines^[Angle]-TPolys[N].Yn*Sines^[Angle];
               TPolys[N].Yn:=TPolys[N].Yn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around Y axis }
               Angle:=YAng;
               Temp:=TPolys[N].X1;
               TPolys[N].X1:=Temp*Cosines^[Angle]-TPolys[N].Z1*Sines^[Angle];
               TPolys[N].Z1:=TPolys[N].Z1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X2;
               TPolys[N].X2:=Temp*Cosines^[Angle]-TPolys[N].Z2*Sines^[Angle];
               TPolys[N].Z2:=TPolys[N].Z2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X3;
               TPolys[N].X3:=Temp*Cosines^[Angle]-TPolys[N].Z3*Sines^[Angle];
               TPolys[N].Z3:=TPolys[N].Z3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X4;
               TPolys[N].X4:=Temp*Cosines^[Angle]-TPolys[N].Z4*Sines^[Angle];
               TPolys[N].Z4:=TPolys[N].Z4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Xc;
               TPolys[N].Xc:=Temp*Cosines^[Angle]-TPolys[N].Zc*Sines^[Angle];
               TPolys[N].Zc:=TPolys[N].Zc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Xn;
               TPolys[N].Xn:=Temp*Cosines^[Angle]-TPolys[N].Zn*Sines^[Angle];
               TPolys[N].Zn:=TPolys[N].Zn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Transform the coordinates again }
               TPolys[N].X1:=TPolys[N].X1+RotCentX;
               TPolys[N].Y1:=TPolys[N].Y1+RotCentY;
               TPolys[N].Z1:=TPolys[N].Z1+RotCentZ;
               TPolys[N].X2:=TPolys[N].X2+RotCentX;
               TPolys[N].Y2:=TPolys[N].Y2+RotCentY;
               TPolys[N].Z2:=TPolys[N].Z2+RotCentZ;
               TPolys[N].X3:=TPolys[N].X3+RotCentX;
               TPolys[N].Y3:=TPolys[N].Y3+RotCentY;
               TPolys[N].Z3:=TPolys[N].Z3+RotCentZ;
               TPolys[N].X4:=TPolys[N].X4+RotCentX;
               TPolys[N].Y4:=TPolys[N].Y4+RotCentY;
               TPolys[N].Z4:=TPolys[N].Z4+RotCentZ;
               TPolys[N].Xc:=TPolys[N].Xc+RotCentX;
               TPolys[N].Yc:=TPolys[N].Yc+RotCentY;
               TPolys[N].Zc:=TPolys[N].Zc+RotCentZ;
          End;

          Procedure CopySolid(Origin:Solid3d;Var Dest:Solid3d;StartPoly:Word);
          { This procedure copys the Origin solid to the Dest solid,
            copying the polygons belonging Origin to the polygons array (as
            every polygon must be stored there), from StartPoly }
          Var A:Byte;
          Begin
               Move(Origin,Dest,SizeOf(Origin));
               For A:=StartPoly To (StartPoly+Origin.NumPolys-1) Do
               Begin
                    Move(Polygons[A-StartPoly+1],Polygons[A],SizeOf(Poly3d));
                    Dest.Polys[A-StartPoly+1]:=A;
               End;
          End;

          Procedure TranslatePoly(Var P:Poly3d;Xt,Yt,Zt:Real);
          { This translates a polygon }
          Begin
               P.X1:=P.X1+Xt; P.Y1:=P.Y1+Yt; P.Z1:=P.Z1+Zt;
               P.X2:=P.X2+Xt; P.Y2:=P.Y2+Yt; P.Z2:=P.Z2+Zt;
               P.X3:=P.X3+Xt; P.Y3:=P.Y3+Yt; P.Z3:=P.Z3+Zt;
               P.X4:=P.X4+Xt; P.Y4:=P.Y4+Yt; P.Z4:=P.Z4+Zt;
               P.Xc:=P.Xc+Xt; P.Yc:=P.Yc+Yt; P.Zc:=P.Zc+Zt;
          End;

          Procedure TranslateSolid(Var S:Solid3d;Xt,Yt,Zt:Real);
          { This translates the solid }
          Var A:Byte;
          Begin
               For A:=1 To S.NumPolys Do
                 TranslatePoly(Polygons[S.Polys[A]],Xt,Yt,Zt);
               S.Xc:=S.Xc+Xt; S.Yc:=S.Yc+Yt; S.Zc:=S.Zc+Zt;
          End;

          Procedure ScaleSolid(Var S:Solid3d;Xt,Yt,Zt:Real);
          { This scales a solid }
          Var A:Byte;
              Xs,Ys,Zs:Real;
          Begin
               Xs:=S.Xc; Ys:=S.Yc; Zs:=S.Zc;
               For A:=1 To S.NumPolys Do
               Begin
                    With Polygons[S.Polys[A]] Do
                    Begin
                         X1:=((X1-Xs)*Xt)+Xs; Y1:=((Y1-Ys)*Yt)+Ys;
                         Z1:=((Z1-Zs)*Zt)+Zs;
                         X2:=((X2-Xs)*Xt)+Xs; Y2:=((Y2-Ys)*Yt)+Ys;
                         Z2:=((Z2-Zs)*Zt)+Zs;
                         X3:=((X3-Xs)*Xt)+Xs; Y3:=((Y3-Ys)*Yt)+Ys;
                         Z3:=((Z3-Zs)*Zt)+Zs;
                         X4:=((X4-Xs)*Xt)+Xs; Y4:=((Y4-Ys)*Yt)+Ys;
                         Z4:=((Z4-Zs)*Zt)+Zs;
                         Xc:=((Xc-Xs)*Xt)+Xs; Yc:=((Yc-Ys)*Yt)+Ys;
                         Zc:=((Zc-Zs)*Zt)+Zs;
                    End;
               End;
          End;

          Procedure InitCubes;
          Var A:Byte;
              Average:Real;
          Begin
               { Define the polygons that will be part of the first cube }
               Gen3dPoly(1,-30,-30,226,30,-30,226,30,30,226,-30,30,226,1);
               Gen3dPoly(2,30,-30,226,30,-30,286,30,30,286,30,30,226,2);
               Gen3dPoly(3,30,-30,286,-30,-30,286,-30,30,286,30,30,286,3);
               Gen3dPoly(4,-30,-30,286,-30,-30,226,-30,30,226,-30,30,286,4);
               Gen3dPoly(5,30,-30,286,-30,-30,286,-30,-30,226,30,-30,226,6);
               Gen3dPoly(6,-30,30,286,30,30,286,30,30,226,-30,30,226,5);
               { Define the main cube solid }
               Cube1.NumPolys:=6;
               For A:=1 To 6 Do Cube1.Polys[A]:=A;
               { Calc the center point }
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Xc;
               Cube1.Xc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Yc;
               Cube1.Yc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Zc;
               Cube1.Zc:=Average/6;
               Cube1.RotX:=0; Cube1.RotY:=0; Cube1.RotZ:=0;
               { Create the other two cubes }
               CopySolid(Cube1,Cube2,7); TranslateSolid(Cube2,100,0,0);
               ScaleSolid(Cube2,0.3,0.3,0.3);
               CopySolid(Cube1,Cube3,13); TranslateSolid(Cube3,-200,0,300);
               XInc:=10;
               { Initialize the rotation array }
               Move(Polygons,TPolys,SizeOf(Polygons));
          End;

          Procedure Conv3d(X,Y,Z:Real;Var Xt,Yt:Integer);
          Begin
               Xt:=160+Trunc((X*256/Z));
               Yt:=100+Trunc((Y*256/Z));
          End;

          Procedure Draw3dPoly(P:Poly3d;Where:Word);
          Var Tx1,Tx2,Tx3,Tx4,
              Ty1,Ty2,Ty3,Ty4:Integer;
          Begin
               Conv3d(P.X1,P.Y1,P.Z1,Tx1,Ty1);
               Conv3d(P.X2,P.Y2,P.Z2,Tx2,Ty2);
               Conv3d(P.X3,P.Y3,P.Z3,Tx3,Ty3);
               Conv3d(P.X4,P.Y4,P.Z4,Tx4,Ty4);
               FPoly(Tx1,Ty1,Tx2,Ty2,Tx3,Ty3,Tx4,Ty4,P.Color,Where);
          End;

          Procedure DrawPolys(Where:Word);
          Var A:Word;
          Begin
               For A:=MaxPolys DownTo 1 Do
                 Draw3dPoly(TPolys[Order[A]],Where);
          End;

          Procedure RotateSolid(Var S:Solid3d;XAng,YAng,ZAng:Integer);
          Var A:Word;
          Begin
               RotCentX:=S.Xc;
               RotCentY:=S.Yc;
               RotCentZ:=S.Zc;
               S.RotX:=S.RotX+XAng; If S.RotX>360 Then S.RotX:=S.RotX-360;
                                    If S.RotX<-360 Then S.RotX:=S.RotX+360;
               S.RotY:=S.RotY+YAng; If S.RotY>360 Then S.RotY:=S.RotY-360;
                                    If S.RotY<-360 Then S.RotY:=S.RotY+360;
               S.RotZ:=S.RotZ+ZAng; If S.RotZ>360 Then S.RotZ:=S.RotZ-360;
                                    If S.RotZ<-360 Then S.RotZ:=S.RotZ+360;
               { Put accurate values in the rotation array }
               For A:=1 To S.NumPolys Do
               Begin
                  Move(Polygons[S.Polys[A]],TPolys[S.Polys[A]],SizeOf(Poly3d));
                  Rotate3dPoly(S.Polys[A],S.RotX,S.RotY,S.RotZ);
               End;
          End;

          Procedure RotateSolidP(Var S:Solid3d;
                                 X,Y,Z:Real;
                                 XAng,YAng,ZAng:Integer);
          { This is similar to the RotateSolid procedure. The only
            diferences is that this one allows you to specify the rotation
            center, and the rotation is not stored in the rotation
            variables. }
          Var A:Word;
          Begin
               RotCentX:=X;
               RotCentY:=Y;
               RotCentZ:=Z;
               S.RotX:=S.RotX+XAng; If S.RotX>360 Then S.RotX:=S.RotX-360;
                                    If S.RotX<-360 Then S.RotX:=S.RotX+360;
               S.RotY:=S.RotY+YAng; If S.RotY>360 Then S.RotY:=S.RotY-360;
                                    If S.RotY<-360 Then S.RotY:=S.RotY+360;
               S.RotZ:=S.RotZ+ZAng; If S.RotZ>360 Then S.RotZ:=S.RotZ-360;
                                    If S.RotZ<-360 Then S.RotZ:=S.RotZ+360;
               { Put accurate values in the rotation array }
               For A:=1 To S.NumPolys Do
               Begin
                  Move(Polygons[S.Polys[A]],TPolys[S.Polys[A]],SizeOf(Poly3d));
                  Rotate3dPoly(S.Polys[A],S.RotX,S.RotY,S.RotZ);
               End;
          End;

          Procedure SortPolys;
          Var Flag:Boolean;
              I,J:Integer;
              X:Integer;
              N:Real;
              T:Real;

              Procedure SortSubArray(Left,Right:Byte);
              Begin
                   { Partition }
                   I:=Left;
                   J:=Right;
                   N:=TPolys[Order[(Left+Right) Div 2]].Zc;
                   Repeat
                         { Find first number from the left to be < N }
                         While Tpolys[Order[I]].Zc<N Do Inc(I);
                         { Find first number from the right to be > N }
                         While Tpolys[Order[J]].Zc>N Do Dec(J);
                         { Exchange }
                         If I<=J Then
                         Begin
                              X:=Order[J];
                              Order[J]:=Order[I];
                              Order[I]:=X;
                              Inc(I);
                              Dec(J);
                         End;
                   Until J<I;
                   { Order left and right subarrays }
                   If Left<J Then SortSubArray(Left,J);
                   If I<Right Then SortSubArray(I,Right);
              End;

          Begin
               For I:=1 To MaxPolys Do Order[I]:=I;
               SortSubArray(1,MaxPolys);
          End;

          Begin
               InitGraph;
               InitVirt;
               InitTables;
               SetColor(0,0,0,0); SetColor(1,63,0,0); SetColor(2,63,63,0);
               SetColor(3,0,63,0); SetColor(4,0,63,63); SetColor(5,0,0,63);
               SetColor(6,63,32,0);
               Cls(0,VGA);
               InitCubes;
               Repeat
                     Cls(0,Vp[1]);
                     SortPolys;
                     DrawPolys(Vp[1]);
                     CopyPage(Vp[1],VGA);
                     RotateSolid(Cube1,2,3,4);
                     { Rotate Cube 2 around Cube 1 }
                     RotateSolidP(Cube2,Cube1.Xc,Cube1.Yc,Cube1.Zc,0,5,0);
                     { Move Cube 3 in ping-pong fashion }
                     RotateSolid(Cube3,4,6,5);
                     TranslateSolid(Cube3,XInc,0,0);
                     If (Cube3.Xc>200) Or (Cube3.Xc<-200) Then XInc:=-Xinc;
               Until Keypressed;
               ClearTables;
               CloseGraph;
          End.

    You probably noted that the procedures were changed (some of them were
  changed a lot), but the changes are easy to understand, since they were made
  to provide an easier manipulation of the solids. This example uses only
  face sorting...
    This is a bit slow, and that is due to the usage of real numbers, instead
  of integers. In next issue, I'll teach you how to convert this code to
  fast code, without even using assembler !
    Well, I think I've finally finished this article !! Thank God... In the
  overall, it took 10:30 hours to be finished... Hope it is worth it...
    At least you should be able to start thinkering with a 3d system... Next
  issue, I'll probably delve into texture-mapping... Don't miss it ! :)


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-


  5. Lights, please !

    This article is about adding some lights to a 3d scene... We'll discuss
  plain light sourcing (also known as Lambert shading).
    You should read article 4 in this issue, the part of vector maths to
  understand this better.
    In this type of shading, a polygon only has one color (not too real, but
  this is just an introduction 3d shading).
    Picture a piece of metal... When you rotated around one axis, it becomes
  brigther when he's facing the Sun, or a lamp, and darker otherwise.
    I'll do an ASCII to explain you better what I'm heading you for:

    |
    |                  B /
    |                   /
    |   -------                <----- Direction of light and viewpoint
    |      A
    |                    |
    |                   C|
    |____________________|______

    It is obvious to see that C will be brigther than B, that will be brigther
  than A (that doesn't even get displayed !).
    Now, check out the following graphic... It's the same display, but the
  normals to the polygons are also displayed:

    |
    |      |           B /
    |      |            /\
    |   -------           \    <----- Direction of light and viewpoint
    |      A
    |                    |
    |                   C|--
    |____________________|______

    The angles between the normals to polygons A, B and C, and the direction
  of light are respectivelly 90, 225 and 180 degrees.
    From all that was said before, the intensity of light is greatest when
  the angle between the normal and the viewpoint is 180. In the angles that
  range from 0 to 90 and 270 to 360, there is no light falling on them, so
  they are black.
    We can use the dot product (and some other operations) to get the cosine
  of the angle between two vectors. We don't even need the angle itself...
  With some clever maths (not so clever, but...) we use just the cosine. And
  because the normal and the ligth vector are unitary, we just need the
  dot product. Notice that the cosine of the angle between the light vector
  and the displayable polygons (those who make an angle between 90 to 270)
  is always negative... Check that when you do the code...

    Now, if we set the first 16 colors of the palette to be tones of any
  color (I'll use greys in the first example), the code necessary to know
  the color of a grey polygon will be:

     CosineOfAngle:=(Light.X*Poly.Xn)+(Light.Y*Poly.Yn)+(Light.Z*Poly.Zn);
     If CosineOfAngle>=0 Then Color:=0
     Else Color:=-Round(CosineOfAngle*16);

    This works because of the proprieties of the cosine (it is never larger
  than 1). We must negate the color because the cosines of the displayable
  polygons are always negative.
    This is pseudo-code. Light is a record with three fields which indicate
  a vector with the light. Notice that in this kind of lighting, the true
  position of the lightsource doesn't matter. It just matters it's orientation
  and direction.
    So, let's do a bit of coding... This is one of the examples of article 4,
  rewriten to include the lightsourcing. It is actually the example of
  backface removal. In the beggining, the ligth will be fixed, and it will
  point towards the lower left corner of the screen, facing into the screen.
  Then, after you press a key, the cube stops rotating and the ligthsource
  will 'rotate' around the cube. In that part, a small graphic will appear
  in the lower left corner, showing the position of the light. The yellow dot
  in the center represents the cube.
    The Color field in the Poly3d structure is ignored in this example (but
  not in the next...).

          Program TestLambert;

          Uses Mode13h,Crt;

          Const MaxPolys=6;

          Type Poly3d=Record
                            X1,Y1,Z1,
                            X2,Y2,Z2,
                            X3,Y3,Z3,
                            X4,Y4,Z4,
                            Xc,Yc,Zc:Real;
                            Xn,Yn,Zn:Real;
                            Color:Byte;
                       End;

          Type Solid3d=Record
                             NumPolys:Word;
                             Polys:Array[1..MaxPolys] Of Word;
                             Xc,Yc,Zc:Real;
                       End;

          Var Polygons:Array[1..MaxPolys] Of Poly3d;
              Cube:Solid3d;
              RotCentX,RotCentY,RotCentZ:Real;
              Light:Record
                          X,Y,Z:Real;
                    End;
              A,Angle:Integer;
              C:Char;

          Procedure Gen3dPoly(N:Word;
                              X1,Y1,Z1,X2,Y2,Z2,
                              X3,Y3,Z3,X4,Y4,Z4:Real;
                              C:Byte);
          Var Module:Real;
          Begin
               Polygons[N].X1:=X1; Polygons[N].Y1:=Y1; Polygons[N].Z1:=Z1;
               Polygons[N].X2:=X2; Polygons[N].Y2:=Y2; Polygons[N].Z2:=Z2;
               Polygons[N].X3:=X3; Polygons[N].Y3:=Y3; Polygons[N].Z3:=Z3;
               Polygons[N].X4:=X4; Polygons[N].Y4:=Y4; Polygons[N].Z4:=Z4;
               Polygons[N].Xc:=(X1+X2+X3+X4)/4;
               Polygons[N].Yc:=(Y1+Y2+Y3+Y4)/4;
               Polygons[N].Zc:=(Z1+Z2+Z3+Z4)/4;
               Polygons[N].Color:=C;
               { Calc the normals }
               Polygons[N].Xn:=(Z2-Z1)*(Y3-Y1)-(Y2-Y1)*(Z3-Z1);
               Polygons[N].Yn:=(Z2-Z1)*(X3-X1)-(X2-X1)*(Z3-Z1);
               Polygons[N].Zn:=(Y2-Y1)*(X3-X1)-(X2-X1)*(Y3-Y1);
               { Normalize the normals }
               Module:=Sqrt(Sqr(Polygons[N].Xn)+Sqr(Polygons[N].Yn)+
                            Sqr(Polygons[N].Zn));
               Polygons[N].Xn:=Polygons[N].Xn/Module;
               Polygons[N].Yn:=Polygons[N].Yn/Module;
               Polygons[N].Zn:=Polygons[N].Zn/Module;
          End;

          Procedure Rotate3dPoly(Var P:Poly3d;XAng,YAng,ZAng:Integer);
          { This rotates around a certain point, specified by the variables
            RotCentX, RotCentY and RotCentZ. We also rotate the center of
            the polygons, that change in order to the rotation point,
            and the normal of the polygon. }
          Var Angle:Integer;
              Temp:Real;
          Begin
               { Transform negative angles into positive ones }
               If XAng<0 Then XAng:=XAng+360;
               If YAng<0 Then YAng:=YAng+360;
               If ZAng<0 Then ZAng:=ZAng+360;
               { Make the coordinates of the points relative to the center }
               P.X1:=P.X1-RotCentX; P.Y1:=P.Y1-RotCentY;
               P.Z1:=P.Z1-RotCentZ;
               P.X2:=P.X2-RotCentX; P.Y2:=P.Y2-RotCentY;
               P.Z2:=P.Z2-RotCentZ;
               P.X3:=P.X3-RotCentX; P.Y3:=P.Y3-RotCentY;
               P.Z3:=P.Z3-RotCentZ;
               P.X4:=P.X4-RotCentX; P.Y4:=P.Y4-RotCentY;
               P.Z4:=P.Z4-RotCentZ;
               P.Xc:=P.Xc-RotCentX; P.Yc:=P.Yc-RotCentY;
               P.Zc:=P.Zc-RotCentZ;
               { Rotate all points of poly around Z axis }
               Angle:=ZAng;
               Temp:=P.X1;
               P.X1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
               P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X2;
               P.X2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
               P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X3;
               P.X3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
               P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X4;
               P.X4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
               P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xc;
               P.Xc:=Temp*Cosines^[Angle]-P.Yc*Sines^[Angle];
               P.Yc:=P.Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xn;
               P.Xn:=Temp*Cosines^[Angle]-P.Yn*Sines^[Angle];
               P.Yn:=P.Yn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around X axis }
               Angle:=XAng;
               Temp:=P.Z1;
               P.Z1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
               P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z2;
               P.Z2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
               P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z3;
               P.Z3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
               P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z4;
               P.Z4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
               P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Zc;
               P.Zc:=Temp*Cosines^[Angle]-P.Yc*Sines^[Angle];
               P.Yc:=P.Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Zn;
               P.Zn:=Temp*Cosines^[Angle]-P.Yn*Sines^[Angle];
               P.Yn:=P.Yn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around Y axis }
               Angle:=YAng;
               Temp:=P.X1;
               P.X1:=Temp*Cosines^[Angle]-P.Z1*Sines^[Angle];
               P.Z1:=P.Z1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X2;
               P.X2:=Temp*Cosines^[Angle]-P.Z2*Sines^[Angle];
               P.Z2:=P.Z2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X3;
               P.X3:=Temp*Cosines^[Angle]-P.Z3*Sines^[Angle];
               P.Z3:=P.Z3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X4;
               P.X4:=Temp*Cosines^[Angle]-P.Z4*Sines^[Angle];
               P.Z4:=P.Z4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xc;
               P.Xc:=Temp*Cosines^[Angle]-P.Zc*Sines^[Angle];
               P.Zc:=P.Zc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xn;
               P.Xn:=Temp*Cosines^[Angle]-P.Zn*Sines^[Angle];
               P.Zn:=P.Zn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Transform the coordinates again }
               P.X1:=P.X1+RotCentX; P.Y1:=P.Y1+RotCentY;
               P.Z1:=P.Z1+RotCentZ;
               P.X2:=P.X2+RotCentX; P.Y2:=P.Y2+RotCentY;
               P.Z2:=P.Z2+RotCentZ;
               P.X3:=P.X3+RotCentX; P.Y3:=P.Y3+RotCentY;
               P.Z3:=P.Z3+RotCentZ;
               P.X4:=P.X4+RotCentX; P.Y4:=P.Y4+RotCentY;
               P.Z4:=P.Z4+RotCentZ;
               P.Xc:=P.Xc+RotCentX; P.Yc:=P.Yc+RotCentY;
               P.Zc:=P.Zc+RotCentZ;
          End;

          Procedure InitCube;
          Var A:Byte;
              Average:Real;
          Begin
               { Define the polygons that will be part of the solid }
               Gen3dPoly(1,-30,-30,226,30,-30,226,30,30,226,-30,30,226,1);
               Gen3dPoly(2,30,-30,226,30,-30,286,30,30,286,30,30,226,2);
               Gen3dPoly(3,30,-30,286,-30,-30,286,-30,30,286,30,30,286,3);
               Gen3dPoly(4,-30,-30,286,-30,-30,226,-30,30,226,-30,30,286,4);
               Gen3dPoly(5,30,-30,286,-30,-30,286,-30,-30,226,30,-30,226,6);
               Gen3dPoly(6,-30,30,286,30,30,286,30,30,226,-30,30,226,5);
               { Define the cube solid }
               Cube.NumPolys:=6;
               For A:=1 To 6 Do Cube.Polys[A]:=A;
               { Calc the center point }
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Xc;
               Cube.Xc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Yc;
               Cube.Yc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Zc;
               Cube.Zc:=Average/6;
          End;

          Procedure Conv3d(X,Y,Z:Real;Var Xt,Yt:Integer);
          Begin
               Xt:=160+Trunc((X*256/Z));
               Yt:=100+Trunc((Y*256/Z));
          End;

          Procedure Draw3dPoly(P:Poly3d;Where:Word);
          Var Tx1,Tx2,Tx3,Tx4,
              Ty1,Ty2,Ty3,Ty4:Integer;
              CosAngle:Real;
              C:Byte;
          Begin
               Conv3d(P.X1,P.Y1,P.Z1,Tx1,Ty1);
               Conv3d(P.X2,P.Y2,P.Z2,Tx2,Ty2);
               Conv3d(P.X3,P.Y3,P.Z3,Tx3,Ty3);
               Conv3d(P.X4,P.Y4,P.Z4,Tx4,Ty4);
               { Find out the color }
               CosAngle:=(Light.X*P.Xn)+(Light.Y*P.Yn)+(Light.Z*P.Zn);
               If CosAngle>0 Then C:=1 Else C:=-Trunc(CosAngle*15)+1;
               FPoly(Tx1,Ty1,Tx2,Ty2,Tx3,Ty3,Tx4,Ty4,C,Where);
          End;

          Procedure DrawSolid(S:Solid3d;Where:Word);
          Var A:Word;
          Begin
               For A:=1 To S.NumPolys Do
               { Draw if the normal is negative }
                 If (Polygons[S.Polys[A]].Zn<0) Then
                   Draw3dPoly(Polygons[S.Polys[A]],Where);
          End;

          Procedure RotateSolid(S:Solid3d;XAng,YAng,ZAng:Integer);
          Var A:Word;
          Begin
               RotCentX:=S.Xc;
               RotCentY:=S.Yc;
               RotCentZ:=S.Zc;
               For A:=1 To S.NumPolys Do
                 Rotate3dPoly(Polygons[S.Polys[A]],XAng,YAng,ZAng);
          End;

          Begin
               InitGraph;
               InitVirt;
               InitTables;
               { Set the greyscale... This must be done carefully, or
                 else the display will be very ugly. }
               SetColor(0,0,0,0);
               For A:=1 To 8 Do SetColor(A,18+A*2,18+A*2,18+A*2);
               For A:=9 To 15 Do SetColor(A,A*4,A*4,A*4);
               SetColor(16,63,0,0);
               SetColor(17,63,63,0);
               Cls(0,VGA);
               InitCube;
               { First part, fixed light }
               Light.X:=-0.58; Light.Y:=0.58; Light.Z:=0.58;
               Repeat
                     Cls(0,Vp[1]);
                     DrawSolid(Cube,Vp[1]);
                     CopyPage(Vp[1],VGA);
                     RotateSolid(Cube,5,5,5);
               Until Keypressed;
               { Second part, moving light }
               C:=ReadKey;
               Light.Y:=0.0;
               Angle:=0;
               Repeat
                     Cls(0,Vp[1]);
                     { Calc the light }
                     Light.X:=Cosines^[Angle];
                     Light.Z:=Sines^[Angle];
                     Inc(Angle,5); If Angle>360 Then Dec(Angle,360);
                     { Draw the graphic in the lower left corner }
                     PutPixel(30,170,17,Vp[1]);
                     PutPixel(30+Round(-Light.X*20),170+Round(Light.Z*20),
                              16,Vp[1]);
                     DrawSolid(Cube,Vp[1]);
                     CopyPage(Vp[1],VGA);
               Until Keypressed;
               ClearTables;
               CloseGraph;
          End.

    Now, I'll play some more with the concept of flat-shading. Now, let's
  add the color factor. Now, the Color field in the Poly3d structure defines
  the base color. That value is added to the Color finded with the algorithm
  we've used to give the correct colors. Now, we just have to adjust the
  palette correctly to give the impression of several colors.
    The next example does this. It is a variation of the last example in
  article 4. As in the previous examples, we have two parts... The first with
  the fixed light, and the in the other the light turns around with the
  orbiting cube).

          Program TestLambert;

          Uses Mode13h,Crt;

          Const MaxPolys=18;    { For 3 cubes }

          Type Poly3d=Record
                            X1,Y1,Z1,
                            X2,Y2,Z2,
                            X3,Y3,Z3,
                            X4,Y4,Z4,
                            Xc,Yc,Zc:Real;
                            Xn,Yn,Zn:Real;
                            Color:Byte;
                       End;

          Type Solid3d=Record
                             NumPolys:Word;
                             Polys:Array[1..MaxPolys] Of Word;
                             Xc,Yc,Zc:Real;
                             RotX,RotY,RotZ:Integer; { The orientation of the
                                                       solid }
                       End;

          Var Polygons:Array[1..MaxPolys] Of Poly3d;   { The accurate polys }
              TPolys:Array[1..MaxPolys] Of Poly3d;    { The inaccurate ones }
              RotCentX,RotCentY,RotCentZ:Real;
              Order:Array[1..MaxPolys] Of Word;
              Light:Record
                          X,Y,Z:Real;
                    End;

          Var Cube1,Cube2,Cube3:Solid3d;
              XInc:Integer;
              C:Char;

          Procedure Gen3dPoly(N:Word;
                              X1,Y1,Z1,X2,Y2,Z2,
                              X3,Y3,Z3,X4,Y4,Z4:Real;
                              C:Byte);
          Var Module:Real;
          Begin
               Polygons[N].X1:=X1; Polygons[N].Y1:=Y1; Polygons[N].Z1:=Z1;
               Polygons[N].X2:=X2; Polygons[N].Y2:=Y2; Polygons[N].Z2:=Z2;
               Polygons[N].X3:=X3; Polygons[N].Y3:=Y3; Polygons[N].Z3:=Z3;
               Polygons[N].X4:=X4; Polygons[N].Y4:=Y4; Polygons[N].Z4:=Z4;
               Polygons[N].Xc:=(X1+X2+X3+X4)/4;
               Polygons[N].Yc:=(Y1+Y2+Y3+Y4)/4;
               Polygons[N].Zc:=(Z1+Z2+Z3+Z4)/4;
               Polygons[N].Color:=C;
               { Calc the normals }
               Polygons[N].Xn:=(Z2-Z1)*(Y3-Y1)-(Y2-Y1)*(Z3-Z1);
               Polygons[N].Yn:=(Z2-Z1)*(X3-X1)-(X2-X1)*(Z3-Z1);
               Polygons[N].Zn:=(Y2-Y1)*(X3-X1)-(X2-X1)*(Y3-Y1);
               { Normalize the normals }
               Module:=Sqrt(Sqr(Polygons[N].Xn)+Sqr(Polygons[N].Yn)+
                            Sqr(Polygons[N].Zn));
               Polygons[N].Xn:=Polygons[N].Xn/Module;
               Polygons[N].Yn:=Polygons[N].Yn/Module;
               Polygons[N].Zn:=Polygons[N].Zn/Module;
          End;

          Procedure Rotate3dPoly(N:Word;XAng,YAng,ZAng:Integer);
          { This rotates polygon N around a certain point, specified by the
            variables RotCentX, RotCentY and RotCentZ. We also rotate the
            center of the polygons, that change in order to the rotation
            point, and the normal of the polygon. }
          Var Angle:Integer;
              Temp:Real;
          Begin
               { Transform negative angles into positive ones }
               If XAng<0 Then XAng:=XAng+360;
               If YAng<0 Then YAng:=YAng+360;
               If ZAng<0 Then ZAng:=ZAng+360;
               { Make the coordinates of the points relative to the center }
               TPolys[N].X1:=TPolys[N].X1-RotCentX;
               TPolys[N].Y1:=TPolys[N].Y1-RotCentY;
               TPolys[N].Z1:=TPolys[N].Z1-RotCentZ;
               TPolys[N].X2:=TPolys[N].X2-RotCentX;
               TPolys[N].Y2:=TPolys[N].Y2-RotCentY;
               TPolys[N].Z2:=TPolys[N].Z2-RotCentZ;
               TPolys[N].X3:=TPolys[N].X3-RotCentX;
               TPolys[N].Y3:=TPolys[N].Y3-RotCentY;
               TPolys[N].Z3:=TPolys[N].Z3-RotCentZ;
               TPolys[N].X4:=TPolys[N].X4-RotCentX;
               TPolys[N].Y4:=TPolys[N].Y4-RotCentY;
               TPolys[N].Z4:=TPolys[N].Z4-RotCentZ;
               TPolys[N].Xc:=TPolys[N].Xc-RotCentX;
               TPolys[N].Yc:=TPolys[N].Yc-RotCentY;
               TPolys[N].Zc:=TPolys[N].Zc-RotCentZ;
               { Rotate all points of poly around Z axis }
               Angle:=ZAng;
               Temp:=TPolys[N].X1;
               TPolys[N].X1:=Temp*Cosines^[Angle]-TPolys[N].Y1*Sines^[Angle];
               TPolys[N].Y1:=TPolys[N].Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X2;
               TPolys[N].X2:=Temp*Cosines^[Angle]-TPolys[N].Y2*Sines^[Angle];
               TPolys[N].Y2:=TPolys[N].Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X3;
               TPolys[N].X3:=Temp*Cosines^[Angle]-TPolys[N].Y3*Sines^[Angle];
               TPolys[N].Y3:=TPolys[N].Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X4;
               TPolys[N].X4:=Temp*Cosines^[Angle]-TPolys[N].Y4*Sines^[Angle];
               TPolys[N].Y4:=TPolys[N].Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Xc;
               TPolys[N].Xc:=Temp*Cosines^[Angle]-TPolys[N].Yc*Sines^[Angle];
               TPolys[N].Yc:=TPolys[N].Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Xn;
               TPolys[N].Xn:=Temp*Cosines^[Angle]-TPolys[N].Yn*Sines^[Angle];
               TPolys[N].Yn:=TPolys[N].Yn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around X axis }
               Angle:=XAng;
               Temp:=TPolys[N].Z1;
               TPolys[N].Z1:=Temp*Cosines^[Angle]-TPolys[N].Y1*Sines^[Angle];
               TPolys[N].Y1:=TPolys[N].Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Z2;
               TPolys[N].Z2:=Temp*Cosines^[Angle]-TPolys[N].Y2*Sines^[Angle];
               TPolys[N].Y2:=TPolys[N].Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Z3;
               TPolys[N].Z3:=Temp*Cosines^[Angle]-TPolys[N].Y3*Sines^[Angle];
               TPolys[N].Y3:=TPolys[N].Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Z4;
               TPolys[N].Z4:=Temp*Cosines^[Angle]-TPolys[N].Y4*Sines^[Angle];
               TPolys[N].Y4:=TPolys[N].Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Zc;
               TPolys[N].Zc:=Temp*Cosines^[Angle]-TPolys[N].Yc*Sines^[Angle];
               TPolys[N].Yc:=TPolys[N].Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Zn;
               TPolys[N].Zn:=Temp*Cosines^[Angle]-TPolys[N].Yn*Sines^[Angle];
               TPolys[N].Yn:=TPolys[N].Yn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around Y axis }
               Angle:=YAng;
               Temp:=TPolys[N].X1;
               TPolys[N].X1:=Temp*Cosines^[Angle]-TPolys[N].Z1*Sines^[Angle];
               TPolys[N].Z1:=TPolys[N].Z1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X2;
               TPolys[N].X2:=Temp*Cosines^[Angle]-TPolys[N].Z2*Sines^[Angle];
               TPolys[N].Z2:=TPolys[N].Z2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X3;
               TPolys[N].X3:=Temp*Cosines^[Angle]-TPolys[N].Z3*Sines^[Angle];
               TPolys[N].Z3:=TPolys[N].Z3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].X4;
               TPolys[N].X4:=Temp*Cosines^[Angle]-TPolys[N].Z4*Sines^[Angle];
               TPolys[N].Z4:=TPolys[N].Z4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Xc;
               TPolys[N].Xc:=Temp*Cosines^[Angle]-TPolys[N].Zc*Sines^[Angle];
               TPolys[N].Zc:=TPolys[N].Zc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=TPolys[N].Xn;
               TPolys[N].Xn:=Temp*Cosines^[Angle]-TPolys[N].Zn*Sines^[Angle];
               TPolys[N].Zn:=TPolys[N].Zn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Transform the coordinates again }
               TPolys[N].X1:=TPolys[N].X1+RotCentX;
               TPolys[N].Y1:=TPolys[N].Y1+RotCentY;
               TPolys[N].Z1:=TPolys[N].Z1+RotCentZ;
               TPolys[N].X2:=TPolys[N].X2+RotCentX;
               TPolys[N].Y2:=TPolys[N].Y2+RotCentY;
               TPolys[N].Z2:=TPolys[N].Z2+RotCentZ;
               TPolys[N].X3:=TPolys[N].X3+RotCentX;
               TPolys[N].Y3:=TPolys[N].Y3+RotCentY;
               TPolys[N].Z3:=TPolys[N].Z3+RotCentZ;
               TPolys[N].X4:=TPolys[N].X4+RotCentX;
               TPolys[N].Y4:=TPolys[N].Y4+RotCentY;
               TPolys[N].Z4:=TPolys[N].Z4+RotCentZ;
               TPolys[N].Xc:=TPolys[N].Xc+RotCentX;
               TPolys[N].Yc:=TPolys[N].Yc+RotCentY;
               TPolys[N].Zc:=TPolys[N].Zc+RotCentZ;
          End;

          Procedure CopySolid(Origin:Solid3d;Var Dest:Solid3d;StartPoly:Word);
          { This procedure copys the Origin solid to the Dest solid,
            copying the polygons belonging Origin to the polygons array (as
            every polygon must be stored there), from StartPoly }
          Var A:Byte;
          Begin
               Move(Origin,Dest,SizeOf(Origin));
               For A:=StartPoly To (StartPoly+Origin.NumPolys-1) Do
               Begin
                    Move(Polygons[A-StartPoly+1],Polygons[A],SizeOf(Poly3d));
                    Dest.Polys[A-StartPoly+1]:=A;
               End;
          End;

          Procedure TranslatePoly(Var P:Poly3d;Xt,Yt,Zt:Real);
          { This translates a polygon }
          Begin
               P.X1:=P.X1+Xt; P.Y1:=P.Y1+Yt; P.Z1:=P.Z1+Zt;
               P.X2:=P.X2+Xt; P.Y2:=P.Y2+Yt; P.Z2:=P.Z2+Zt;
               P.X3:=P.X3+Xt; P.Y3:=P.Y3+Yt; P.Z3:=P.Z3+Zt;
               P.X4:=P.X4+Xt; P.Y4:=P.Y4+Yt; P.Z4:=P.Z4+Zt;
               P.Xc:=P.Xc+Xt; P.Yc:=P.Yc+Yt; P.Zc:=P.Zc+Zt;
          End;

          Procedure TranslateSolid(Var S:Solid3d;Xt,Yt,Zt:Real);
          { This translates the solid }
          Var A:Byte;
          Begin
               For A:=1 To S.NumPolys Do
                 TranslatePoly(Polygons[S.Polys[A]],Xt,Yt,Zt);
               S.Xc:=S.Xc+Xt; S.Yc:=S.Yc+Yt; S.Zc:=S.Zc+Zt;
          End;

          Procedure ScaleSolid(Var S:Solid3d;Xt,Yt,Zt:Real);
          { This scales a solid }
          Var A:Byte;
              Xs,Ys,Zs:Real;
          Begin
               Xs:=S.Xc; Ys:=S.Yc; Zs:=S.Zc;
               For A:=1 To S.NumPolys Do
               Begin
                    With Polygons[S.Polys[A]] Do
                    Begin
                         X1:=((X1-Xs)*Xt)+Xs; Y1:=((Y1-Ys)*Yt)+Ys;
                         Z1:=((Z1-Zs)*Zt)+Zs;
                         X2:=((X2-Xs)*Xt)+Xs; Y2:=((Y2-Ys)*Yt)+Ys;
                         Z2:=((Z2-Zs)*Zt)+Zs;
                         X3:=((X3-Xs)*Xt)+Xs; Y3:=((Y3-Ys)*Yt)+Ys;
                         Z3:=((Z3-Zs)*Zt)+Zs;
                         X4:=((X4-Xs)*Xt)+Xs; Y4:=((Y4-Ys)*Yt)+Ys;
                         Z4:=((Z4-Zs)*Zt)+Zs;
                         Xc:=((Xc-Xs)*Xt)+Xs; Yc:=((Yc-Ys)*Yt)+Ys;
                         Zc:=((Zc-Zs)*Zt)+Zs;
                    End;
               End;
          End;

          Procedure InitCubes;
          Var A:Byte;
              Average:Real;
          Begin
               { Define the polygons that will be part of the first cube }
               Gen3dPoly(1,-30,-30,226,30,-30,226,30,30,226,-30,30,226,1);
               Gen3dPoly(2,30,-30,226,30,-30,286,30,30,286,30,30,226,17);
               Gen3dPoly(3,30,-30,286,-30,-30,286,-30,30,286,30,30,286,33);
               Gen3dPoly(4,-30,-30,286,-30,-30,226,-30,30,226,-30,30,286,49);
               Gen3dPoly(5,30,-30,286,-30,-30,286,-30,-30,226,30,-30,226,65);
               Gen3dPoly(6,-30,30,286,30,30,286,30,30,226,-30,30,226,81);
               { Define the main cube solid }
               Cube1.NumPolys:=6;
               For A:=1 To 6 Do Cube1.Polys[A]:=A;
               { Calc the center point }
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Xc;
               Cube1.Xc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Yc;
               Cube1.Yc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Zc;
               Cube1.Zc:=Average/6;
               Cube1.RotX:=0; Cube1.RotY:=0; Cube1.RotZ:=0;
               { Create the other two cubes }
               CopySolid(Cube1,Cube2,7); TranslateSolid(Cube2,100,0,0);
               ScaleSolid(Cube2,0.3,0.3,0.3);
               CopySolid(Cube1,Cube3,13); TranslateSolid(Cube3,-200,0,300);
               XInc:=10;
               { Initialize the rotation array }
               Move(Polygons,TPolys,SizeOf(Polygons));
          End;

          Procedure Conv3d(X,Y,Z:Real;Var Xt,Yt:Integer);
          Begin
               Xt:=160+Trunc((X*256/Z));
               Yt:=100+Trunc((Y*256/Z));
          End;

          Procedure Draw3dPoly(P:Poly3d;Where:Word);
          Var Tx1,Tx2,Tx3,Tx4,
              Ty1,Ty2,Ty3,Ty4:Integer;
              CosAngle:Real;
              C:Byte;
          Begin
               Conv3d(P.X1,P.Y1,P.Z1,Tx1,Ty1);
               Conv3d(P.X2,P.Y2,P.Z2,Tx2,Ty2);
               Conv3d(P.X3,P.Y3,P.Z3,Tx3,Ty3);
               Conv3d(P.X4,P.Y4,P.Z4,Tx4,Ty4);
               CosAngle:=(Light.X*P.Xn)+(Light.Y*P.Yn)+(Light.Z*P.Zn);
               If CosAngle>0 Then C:=P.Color
                             Else C:=P.Color+Trunc(-CosAngle*15);
               FPoly(Tx1,Ty1,Tx2,Ty2,Tx3,Ty3,Tx4,Ty4,C,Where);
          End;

          Procedure DrawPolys(Where:Word);
          Var A:Word;
          Begin
               For A:=MaxPolys DownTo 1 Do
                 Draw3dPoly(TPolys[Order[A]],Where);
          End;

          Procedure RotateSolid(Var S:Solid3d;XAng,YAng,ZAng:Integer);
          Var A:Word;
          Begin
               RotCentX:=S.Xc;
               RotCentY:=S.Yc;
               RotCentZ:=S.Zc;
               S.RotX:=S.RotX+XAng; If S.RotX>360 Then S.RotX:=S.RotX-360;
                                    If S.RotX<-360 Then S.RotX:=S.RotX+360;
               S.RotY:=S.RotY+YAng; If S.RotY>360 Then S.RotY:=S.RotY-360;
                                    If S.RotY<-360 Then S.RotY:=S.RotY+360;
               S.RotZ:=S.RotZ+ZAng; If S.RotZ>360 Then S.RotZ:=S.RotZ-360;
                                    If S.RotZ<-360 Then S.RotZ:=S.RotZ+360;
               { Put accurate values in the rotation array }
               For A:=1 To S.NumPolys Do
               Begin
                  Move(Polygons[S.Polys[A]],TPolys[S.Polys[A]],SizeOf(Poly3d));
                  Rotate3dPoly(S.Polys[A],S.RotX,S.RotY,S.RotZ);
               End;
          End;

          Procedure RotateSolidP(Var S:Solid3d;
                                 X,Y,Z:Real;
                                 XAng,YAng,ZAng:Integer);
          { This is similar to the RotateSolid procedure. The only
            diferences is that this one allows you to specify the rotation
            center, and the rotation is not stored in the rotation
            variables. }
          Var A:Word;
          Begin
               RotCentX:=X;
               RotCentY:=Y;
               RotCentZ:=Z;
               S.RotX:=S.RotX+XAng; If S.RotX>360 Then S.RotX:=S.RotX-360;
                                    If S.RotX<-360 Then S.RotX:=S.RotX+360;
               S.RotY:=S.RotY+YAng; If S.RotY>360 Then S.RotY:=S.RotY-360;
                                    If S.RotY<-360 Then S.RotY:=S.RotY+360;
               S.RotZ:=S.RotZ+ZAng; If S.RotZ>360 Then S.RotZ:=S.RotZ-360;
                                    If S.RotZ<-360 Then S.RotZ:=S.RotZ+360;
               { Put accurate values in the rotation array }
               For A:=1 To S.NumPolys Do
               Begin
                  Move(Polygons[S.Polys[A]],TPolys[S.Polys[A]],SizeOf(Poly3d));
                  Rotate3dPoly(S.Polys[A],S.RotX,S.RotY,S.RotZ);
               End;
          End;

          Procedure SortPolys;
          Var Flag:Boolean;
              I,J:Integer;
              X:Integer;
              N:Real;
              T:Real;

              Procedure SortSubArray(Left,Right:Byte);
              Begin
                   { Partition }
                   I:=Left;
                   J:=Right;
                   N:=TPolys[Order[(Left+Right) Div 2]].Zc;
                   Repeat
                         { Find first number from the left to be < N }
                         While Tpolys[Order[I]].Zc<N Do Inc(I);
                         { Find first number from the right to be > N }
                         While Tpolys[Order[J]].Zc>N Do Dec(J);
                         { Exchange }
                         If I<=J Then
                         Begin
                              X:=Order[J];
                              Order[J]:=Order[I];
                              Order[I]:=X;
                              Inc(I);
                              Dec(J);
                         End;
                   Until J<I;
                   { Order left and right subarrays }
                   If Left<J Then SortSubArray(Left,J);
                   If I<Right Then SortSubArray(I,Right);
              End;

          Begin
               For I:=1 To MaxPolys Do Order[I]:=I;
               SortSubArray(1,MaxPolys);
          End;

          Begin
               InitGraph;
               InitVirt;
               InitTables;
               InitColors; { I've added this procedure to the Mode13h unit,
                             since we'll need this type of initialization
                             in future issues }
               Cls(0,VGA);
               InitCubes;
               Light.X:=-0.58; Light.Y:=0.58; Light.Z:=0.58;
               Repeat
                     Cls(0,Vp[1]);
                     SortPolys;
                     DrawPolys(Vp[1]);
                     CopyPage(Vp[1],VGA);
                     RotateSolid(Cube1,2,3,4);
                     { Rotate Cube 2 around Cube 1 }
                     RotateSolidP(Cube2,Cube1.Xc,Cube1.Yc,Cube1.Zc,0,5,0);
                     { Move Cube 3 in ping-pong fashion }
                     RotateSolid(Cube3,4,6,5);
                     TranslateSolid(Cube3,XInc,0,0);
                     If (Cube3.Xc>200) Or (Cube3.Xc<-200) Then XInc:=-Xinc;
               Until Keypressed;
               C:=ReadKey;
               Light.Y:=0;
               Repeat
                     Cls(0,Vp[1]);
                     RotateSolid(Cube1,2,3,4);
                     { Rotate Cube 2 around Cube 1 }
                     RotateSolidP(Cube2,Cube1.Xc,Cube1.Yc,Cube1.Zc,0,5,0);
                     Light.X:=-Cosines^[Cube2.RotY];
                     Light.Z:=-Sines^[Cube2.RotY];
                     { Display light direction }
                     PutPixel(30,170,17,Vp[1]);
                     PutPixel(30+Round(-Light.X*20),170+Round(Light.Z*20),
                              16,Vp[1]);
                     { Move Cube 3 in ping-pong fashion }
                     RotateSolid(Cube3,4,6,5);
                     TranslateSolid(Cube3,XInc,0,0);
                     If (Cube3.Xc>200) Or (Cube3.Xc<-200) Then XInc:=-Xinc;
                     SortPolys;
                     DrawPolys(Vp[1]);
                     CopyPage(Vp[1],VGA);
               Until Keypressed;
               ClearTables;
               CloseGraph;
          End.

    This is fairly easy...
    Just one more thing before I wrap up this article: double lightsources !
  Well, if you have two light sources, you just have to sum their effects
  (making sure they do not cross a threshould).
    This is a short example. This is the first example, but with two
  lights. When the polygon gets light cyan, it means that the the light is
  very intense (the combination of the two... One light alone can never get
  the polygon cyan).

          Program TestLambertWithTwoLights;

          Uses Mode13h,Crt;

          Const MaxPolys=6;

          Type Poly3d=Record
                            X1,Y1,Z1,
                            X2,Y2,Z2,
                            X3,Y3,Z3,
                            X4,Y4,Z4,
                            Xc,Yc,Zc:Real;
                            Xn,Yn,Zn:Real;
                            Color:Byte;
                       End;

               Solid3d=Record
                             NumPolys:Word;
                             Polys:Array[1..MaxPolys] Of Word;
                             Xc,Yc,Zc:Real;
                       End;

               Light=Record
                           X,Y,Z:Real;
                     End;


          Var Polygons:Array[1..MaxPolys] Of Poly3d;
              Cube:Solid3d;
              RotCentX,RotCentY,RotCentZ:Real;
              Light1,Light2:Light;
              A,Angle:Integer;
              C:Char;

          Procedure Gen3dPoly(N:Word;
                              X1,Y1,Z1,X2,Y2,Z2,
                              X3,Y3,Z3,X4,Y4,Z4:Real;
                              C:Byte);
          Var Module:Real;
          Begin
               Polygons[N].X1:=X1; Polygons[N].Y1:=Y1; Polygons[N].Z1:=Z1;
               Polygons[N].X2:=X2; Polygons[N].Y2:=Y2; Polygons[N].Z2:=Z2;
               Polygons[N].X3:=X3; Polygons[N].Y3:=Y3; Polygons[N].Z3:=Z3;
               Polygons[N].X4:=X4; Polygons[N].Y4:=Y4; Polygons[N].Z4:=Z4;
               Polygons[N].Xc:=(X1+X2+X3+X4)/4;
               Polygons[N].Yc:=(Y1+Y2+Y3+Y4)/4;
               Polygons[N].Zc:=(Z1+Z2+Z3+Z4)/4;
               Polygons[N].Color:=C;
               { Calc the normals }
               Polygons[N].Xn:=(Z2-Z1)*(Y3-Y1)-(Y2-Y1)*(Z3-Z1);
               Polygons[N].Yn:=(Z2-Z1)*(X3-X1)-(X2-X1)*(Z3-Z1);
               Polygons[N].Zn:=(Y2-Y1)*(X3-X1)-(X2-X1)*(Y3-Y1);
               { Normalize the normals }
               Module:=Sqrt(Sqr(Polygons[N].Xn)+Sqr(Polygons[N].Yn)+
                            Sqr(Polygons[N].Zn));
               Polygons[N].Xn:=Polygons[N].Xn/Module;
               Polygons[N].Yn:=Polygons[N].Yn/Module;
               Polygons[N].Zn:=Polygons[N].Zn/Module;
          End;

          Procedure Rotate3dPoly(Var P:Poly3d;XAng,YAng,ZAng:Integer);
          { This rotates around a certain point, specified by the variables
            RotCentX, RotCentY and RotCentZ. We also rotate the center of
            the polygons, that change in order to the rotation point,
            and the normal of the polygon. }
          Var Angle:Integer;
              Temp:Real;
          Begin
               { Transform negative angles into positive ones }
               If XAng<0 Then XAng:=XAng+360;
               If YAng<0 Then YAng:=YAng+360;
               If ZAng<0 Then ZAng:=ZAng+360;
               { Make the coordinates of the points relative to the center }
               P.X1:=P.X1-RotCentX; P.Y1:=P.Y1-RotCentY;
               P.Z1:=P.Z1-RotCentZ;
               P.X2:=P.X2-RotCentX; P.Y2:=P.Y2-RotCentY;
               P.Z2:=P.Z2-RotCentZ;
               P.X3:=P.X3-RotCentX; P.Y3:=P.Y3-RotCentY;
               P.Z3:=P.Z3-RotCentZ;
               P.X4:=P.X4-RotCentX; P.Y4:=P.Y4-RotCentY;
               P.Z4:=P.Z4-RotCentZ;
               P.Xc:=P.Xc-RotCentX; P.Yc:=P.Yc-RotCentY;
               P.Zc:=P.Zc-RotCentZ;
               { Rotate all points of poly around Z axis }
               Angle:=ZAng;
               Temp:=P.X1;
               P.X1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
               P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X2;
               P.X2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
               P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X3;
               P.X3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
               P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X4;
               P.X4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
               P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xc;
               P.Xc:=Temp*Cosines^[Angle]-P.Yc*Sines^[Angle];
               P.Yc:=P.Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xn;
               P.Xn:=Temp*Cosines^[Angle]-P.Yn*Sines^[Angle];
               P.Yn:=P.Yn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around X axis }
               Angle:=XAng;
               Temp:=P.Z1;
               P.Z1:=Temp*Cosines^[Angle]-P.Y1*Sines^[Angle];
               P.Y1:=P.Y1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z2;
               P.Z2:=Temp*Cosines^[Angle]-P.Y2*Sines^[Angle];
               P.Y2:=P.Y2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z3;
               P.Z3:=Temp*Cosines^[Angle]-P.Y3*Sines^[Angle];
               P.Y3:=P.Y3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Z4;
               P.Z4:=Temp*Cosines^[Angle]-P.Y4*Sines^[Angle];
               P.Y4:=P.Y4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Zc;
               P.Zc:=Temp*Cosines^[Angle]-P.Yc*Sines^[Angle];
               P.Yc:=P.Yc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Zn;
               P.Zn:=Temp*Cosines^[Angle]-P.Yn*Sines^[Angle];
               P.Yn:=P.Yn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Rotate all points of poly around Y axis }
               Angle:=YAng;
               Temp:=P.X1;
               P.X1:=Temp*Cosines^[Angle]-P.Z1*Sines^[Angle];
               P.Z1:=P.Z1*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X2;
               P.X2:=Temp*Cosines^[Angle]-P.Z2*Sines^[Angle];
               P.Z2:=P.Z2*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X3;
               P.X3:=Temp*Cosines^[Angle]-P.Z3*Sines^[Angle];
               P.Z3:=P.Z3*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.X4;
               P.X4:=Temp*Cosines^[Angle]-P.Z4*Sines^[Angle];
               P.Z4:=P.Z4*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xc;
               P.Xc:=Temp*Cosines^[Angle]-P.Zc*Sines^[Angle];
               P.Zc:=P.Zc*Cosines^[Angle]+Temp*Sines^[Angle];
               Temp:=P.Xn;
               P.Xn:=Temp*Cosines^[Angle]-P.Zn*Sines^[Angle];
               P.Zn:=P.Zn*Cosines^[Angle]+Temp*Sines^[Angle];
               { Transform the coordinates again }
               P.X1:=P.X1+RotCentX; P.Y1:=P.Y1+RotCentY;
               P.Z1:=P.Z1+RotCentZ;
               P.X2:=P.X2+RotCentX; P.Y2:=P.Y2+RotCentY;
               P.Z2:=P.Z2+RotCentZ;
               P.X3:=P.X3+RotCentX; P.Y3:=P.Y3+RotCentY;
               P.Z3:=P.Z3+RotCentZ;
               P.X4:=P.X4+RotCentX; P.Y4:=P.Y4+RotCentY;
               P.Z4:=P.Z4+RotCentZ;
               P.Xc:=P.Xc+RotCentX; P.Yc:=P.Yc+RotCentY;
               P.Zc:=P.Zc+RotCentZ;
          End;

          Procedure InitCube;
          Var A:Byte;
              Average:Real;
          Begin
               { Define the polygons that will be part of the solid }
               Gen3dPoly(1,-30,-30,226,30,-30,226,30,30,226,-30,30,226,1);
               Gen3dPoly(2,30,-30,226,30,-30,286,30,30,286,30,30,226,2);
               Gen3dPoly(3,30,-30,286,-30,-30,286,-30,30,286,30,30,286,3);
               Gen3dPoly(4,-30,-30,286,-30,-30,226,-30,30,226,-30,30,286,4);
               Gen3dPoly(5,30,-30,286,-30,-30,286,-30,-30,226,30,-30,226,6);
               Gen3dPoly(6,-30,30,286,30,30,286,30,30,226,-30,30,226,5);
               { Define the cube solid }
               Cube.NumPolys:=6;
               For A:=1 To 6 Do Cube.Polys[A]:=A;
               { Calc the center point }
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Xc;
               Cube.Xc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Yc;
               Cube.Yc:=Average/6;
               Average:=0;
               For A:=1 To 6 Do Average:=Average+Polygons[A].Zc;
               Cube.Zc:=Average/6;
          End;

          Procedure Conv3d(X,Y,Z:Real;Var Xt,Yt:Integer);
          Begin
               Xt:=160+Trunc((X*256/Z));
               Yt:=100+Trunc((Y*256/Z));
          End;

          Procedure Draw3dPoly(P:Poly3d;Where:Word);
          Var Tx1,Tx2,Tx3,Tx4,
              Ty1,Ty2,Ty3,Ty4:Integer;
              CosAngle1,CosAngle2:Real;
              C:Byte;
          Begin
               Conv3d(P.X1,P.Y1,P.Z1,Tx1,Ty1);
               Conv3d(P.X2,P.Y2,P.Z2,Tx2,Ty2);
               Conv3d(P.X3,P.Y3,P.Z3,Tx3,Ty3);
               Conv3d(P.X4,P.Y4,P.Z4,Tx4,Ty4);
               { Find out the color }
               CosAngle1:=(Light1.X*P.Xn)+(Light1.Y*P.Yn)+(Light1.Z*P.Zn);
               CosAngle2:=(Light2.X*P.Xn)+(Light2.Y*P.Yn)+(Light2.Z*P.Zn);
               If CosAngle1>0 Then If CosAngle2>0 Then C:=1
                                   Else C:=-Trunc(CosAngle2*15)+1
               Else If CosAngle2>0 Then C:=-Trunc(CosAngle1*15)+1
                    Else C:=-Trunc(CosAngle1*15)-Trunc(CosAngle2*15)+1;
               If C>15 Then C:=18;
               FPoly(Tx1,Ty1,Tx2,Ty2,Tx3,Ty3,Tx4,Ty4,C,Where);
          End;

          Procedure DrawSolid(S:Solid3d;Where:Word);
          Var A:Word;
          Begin
               For A:=1 To S.NumPolys Do
               { Draw if the normal is negative }
                 If (Polygons[S.Polys[A]].Zn<0) Then
                   Draw3dPoly(Polygons[S.Polys[A]],Where);
          End;

          Procedure RotateSolid(S:Solid3d;XAng,YAng,ZAng:Integer);
          Var A:Word;
          Begin
               RotCentX:=S.Xc;
               RotCentY:=S.Yc;
               RotCentZ:=S.Zc;
               For A:=1 To S.NumPolys Do
                 Rotate3dPoly(Polygons[S.Polys[A]],XAng,YAng,ZAng);
          End;

          Begin
               InitGraph;
               InitVirt;
               InitTables;
               { Set the greyscale... This must be done carefully, or
                 else the display will be very ugly. }
               SetColor(0,0,0,0);
               For A:=1 To 8 Do SetColor(A,18+A*2,18+A*2,18+A*2);
               For A:=9 To 15 Do SetColor(A,A*4,A*4,A*4);
               SetColor(16,63,0,0);
               SetColor(17,63,63,0);
               SetColor(18,0,63,63);
               Cls(0,VGA);
               InitCube;
               { First part, fixed lights. The two lights are shining
                 from oposite directions along the X axis }
               Light1.X:=-1; Light1.Y:=0; Light1.Z:=0;
               Light2.X:=1; Light2.Y:=0; Light2.Z:=0;
               Repeat
                     Cls(0,Vp[1]);
                     DrawSolid(Cube,Vp[1]);
                     CopyPage(Vp[1],VGA);
                     RotateSolid(Cube,5,5,5);
               Until Keypressed;
               { Second part, one light moves, the other not }
               C:=ReadKey;
               Light1.Y:=0.0;
               Angle:=0;
               Repeat
                     Cls(0,Vp[1]);
                     { Calc the light }
                     Light1.X:=Cosines^[Angle];
                     Light1.Z:=Sines^[Angle];
                     Inc(Angle,5); If Angle>360 Then Dec(Angle,360);
                     { Draw the graphic in the lower left corner }
                     PutPixel(30,170,17,Vp[1]);
                     PutPixel(30+Round(-Light1.X*20),170+Round(Light1.Z*20),
                              16,Vp[1]);
                     DrawSolid(Cube,Vp[1]);
                     CopyPage(Vp[1],VGA);
               Until Keypressed;
               ClearTables;
               CloseGraph;
          End.

    Well, that's it for this article on flat-shading... This one took 2.5
  hours to get ready... Now, I'll do the article on crossfading... If the Gods
  are on my side, tomorrow this issue is ready to go to everyone out there...
  It is just 4 months late !! :)))


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-


  6. The optimization bible

    Hello, welcome to this article about optimization... In this article, I
  will teach a number of tricks to speed up the execution of your programs.
  Most of this tricks will only work properly in assembly, but don't let that
  stop you from using them in your programs.
    Notice that this isn't how to do nice little structured code... This is
  how to do fast code... This often colide...
    So, without further due, let's do it... :)

  (A) The powers of two are great for aritmetics in the computer. I already
      talked about the Shr/Shl trick in other issue, but I'll talk about it
      again now, and I'll extend the concept.
      In unsigned numbers, doing binary shifts left and right will multiply
      and divide by two the number, respectivelly.
      Example:
               100 shl 1 will shift the binary description of 100 one bit to
               the left, giving us 200... Let's see this in binary:

                            50 = 00110010
                      50 shl 1 = 01100100 = 100 !!!
          100 shl 1 = 50 shl 2 = 11001000 = 200 !!!

          See ? You can do the inverse (shift right):

                           200 = 11001000
                     200 shr 1 = 01100100 = 100 !!!
         100 shr 1 = 200 shr 2 = 00110010 =  50 !!!

         Cool, isn't it... The rule is :

                     X Shl N = X * 2^N
                     X Shr N = X Div 2^N

         Don't forget that the division is integer! You can't use this trick
      with real numbers... If you use it in Pascal directly (i.e. A:=2 Shl 1;),
      you can use signed numbers (i.e. A:=-5 Shl 2). In assembly, for signed
      numbers, you must use Sar to shift right, because that preserves the
      sign bit. The Shl can be used, altough you should check the carry flag
      afterwards... If it is set, an overflow has ocurred, because the carry
      flag stores the last bit shifted out.
      Signed numbers in a binary computer are stored in a special format. For
      example, a signed byte is stored as eight bits, the leftmost one storing
      the sign (0=positive, 1=negative), and the remaining seven bits store
      the numbers... That's why the signed byte (shortint type) have a range
      from -128 to 127, while a unsigned byte (byte type) has a range from 0
      to 255 (an extra bit in a numeric representation gives double the range).
        This isn't all you can do with the powers of two...
        Another neat trick to do is X Mod N, while N is a power of two. You can
      use the AND function and a little trick to do this VERY fast.
        For example:
                    X Mod 2  = X And 00000001 = X And 1
                    X Mod 4  = X And 00000011 = X And 3
                    X Mod 8  = X And 00000111 = X And 7
                    X Mod 16 = X And 00001111 = X And 15
                    .........................
        Got the ideia ? How this works ? Simple:
        If you use AND with normal numbers, you will get something that is
      sometimes refered as masking. With AND you can clear the bits you want
      from a number... Imagine the following number and operation:

                       50 = 00110010
                        3 = 00000011
                 50 And 3 = 00000010 = 2 = 50 Mod 4 !

        See ? You clear the bits that don't matter to the operation. The
      X Mod 4 = X And 3 trick is very usefull in ModeX operations, so don't
      forget this one...

  (B) Use pregenerated arrays and tables as much as you can. If you can precalc
      all the values before you start, your program will be internally just
      made of memory reads and writes ! Of course that's an ideal situation,
      an impossible one, but that can speed up things like hell! Of course that
      it takes large amounts of memory to do so, but who cares ? Almost everytime
      there is the speed/memory tradeoff ! You'll have to see what is more
      important in your programs. In games and demos, it is the speed, so screw
      the memory ! :)

  (C) Think about your calculations over and over again... There is speed gains
      to be made in clever thinking. For example, a trick I will be using in
      to speed up the PutPixel procedure. At a certain point, you'll have to
      do the following multiplication: 320*Y. How can you speed up mults ?
      Using shift left... But 320 isn't a power of two... But if you think a
      bit, you notice that 320=64+256. So, the mult will look like this:
      (64+256)*Y that is equal to 64*Y+256*Y, that is equal Y Shl 6+Y Shl 8.
      So, we speeded up the calculations a bit... :)

  (D) Do the calcs only once. Imagine the following piece of a program:

             For A:=1 To 50 Do
             Begin
                  For B:=1 To 100 Do
                  Begin
                       C:=A*10+B;
                       WriteLn(C);
                  End;
             End;

      This can be speeded up a bit by using an extra variable. Notice that the
      A*10 parcel of the calculus will only be changed after 100 changes in the
      B parcel. So, what you can use is this:

             For A:=1 To 50 Do
             Begin
                  D:=A*10;
                  For B:=1 To 100 Do
                  Begin
                       C:=D+B;
                       WriteLn(C);
                  End;
             End;

      In the original piece of code, you used 5000 mults. In the second piece,
      you use 50... That is what I call speeding ! :)

  (E) Cripple the code... By that, I mean that you can change the routine to
      be less flexible, but more efficient. For example, in a game I'm working
      on now, I have a routine that just draws a 30x30 sprites screen, given
      the offset. I do all the calculations regarding the position of the
      sprite using offsets because it is faster than calculating the offset
      everytime you need it.

  (F) Use fixed point maths... Floating point operations (i.e. those who use
      Pascal's Real type) are VERY slow, even with a FPU (Floating Point Unit).
      But, I hear you cry, I need the floating part of the number! What do I
      do with it ?
      Well, you keep it... I won't go too deep in the issue of fixed point
      maths, since a friend of mine wrote an whole article on the subject. That
      article will be released in issue 12. This is just enough to get you
      going...
      For example, take the number 9.5... This number has an integer part (9)
      and a fractional part (.5). So it is a real number. We have to convert
      this real number to integer, do the calculations with integers (it's
      many times faster) and then convert it back to real (or not... Depending
      on the aplication, you may choose to maintain the number in integer
      format). How do we convert the number ?
      Well, if you multiply the number by 10, you get the number 95. Now, let's
      multiply 95 by 2. You get 190. Now, let's divide that result by 10. You
      get 19, which is the right result for 9.5*2, but this was many times
      faster.
      If you multiply by 10, you get a 0.1 precision. That isn't enough for
      most purposes. And a multiplication by 10 is a slow operation even so.
      So, let's try another thing:
      Back to 9.5... Let's multiply the number by 256. We get 9.5*256=2432.
      Now, let's multiply that number by 3. We get 7296. Now, let's shift the
      bits of the number 8 steps to the right. We get 7296/256=28.5, that is
      equal to 9.5*3...
      But, although the operation itself is many times faster, the conversions
      aren't much.
      One way to get around this is never to convert back to real. Do all
      calculations with integer math. That include sines and cosines tables,
      that can be multiplied by 256 in the initialization stages, and you
      can use that value throughout the program. Usually you only need the
      integer part of the number in a game or demo, although you need to calc
      real stuff (like a rotation). Although you need an integer (x,y) pair
      of numbers in a rotation, you need to multiply by a sine or cosine,
      real numbers. If you convert those real numbers to integer, you get a
      BIG speed increase.
      Just one more thing... Why we use 256 in the multiplication. Well, that
      depends on the precision you want. With 256 you get something like a
      0.004 precision. I use 256 because if you just want the integer part of
      a number (as most times you do), you just have to do a fast shift right
      8 bits operation to get it. Much faster than a division by 256.
      Another thing... I think that Pentium processors can actually do floating
      point aritmetic faster than fixed point, because of the use of the FPU.
      But to use that in Pascal (or in ASM, for the matter of fact), you have
      to use some strange opcodes and stuff, and the programs wouldn't probably
      be compatible with 486 computers... But, if you want to learn more about
      this, read the newsgroups dedicated to hardware...


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-


  7. How to do crossfading

    Crossfading is the effect of an image fading out while other fades in.
  It has been used countless in movies, altough it isn't much used in games
  and demos.
    The ideia of crossfading is to have two pictures, and fade in the palette
  of one while fading out the palette of another. Obviously enough, the two
  pictures must be on screen at the same time... And this is when the troubles
  begin !!! :)
    In this article we will cover two types of crossfading (there are more,
  but most of them use what is known as ModeX. From next issue forth I will
  start a tutorial on ModeX. Stay tuned...): 16 color crossfading, and 256
  color crossfading.

    7.1. 16 color crossfading

    To display a 16 color picture, we just need to use 4 bits (4^2=16). If
  you think that the normal VGA screen uses 1 byte (=8 bits) to store a
  picture, you quickly arrive to the conclusion that you can store two 16
  color pictures on a normal VGA screen !
    So, how do we do this ?
    Well, for this tutorial, I've done two pictures, each one with 16 colors
  and diferent palettes. They are called (originally) Pic1.Pcx and Pic2.Pcx.
  Notice that I've only used the lower 16 colors (0-15), even though the files
  are 256 color PCX.
    Now, to be perfectly honest, I'm so fed up with this issue that I'm just
  going to tell you how it is done, without much explanation. Study it to
  understand it... You can do it ! ;)
    So, now you have to load the two pictures to the virtual screens, saving
  the palettes in diferent variables. Then, you have to combine the screens
  in a way that one of them occupy the lower 4 bits and the other the upper
  4 bits. Then, you readjust the palettes in this way.

    Palette of screen 1 (the one that occupies the lower bits):
    The 16 colors (0-15) must be copied to the other registers, like this:

           Color no. |  Color value
             0-15    |  A B C D E F G H I J K L M N O P
            16-31    |  A B C D E F G H I J K L M N O P
            32-47    |  A B C D E F G H I J K L M N O P
             ...........................................
          240-255    |  A B C D E F G H I J K L M N O P

    Palette of screen 2 (the one that occupies the higher bits):
    It has color 0 occupying positions 0-15, color 2 occupying positions
    16-31, etc, like this:

           Color no. |  Color value
             0-15    |  A A A A A A A A A A A A A A A A
            16-31    |  B B B B B B B B B B B B B B B B
            32-47    |  C C C C C C C C C C C C C C C C
            ............................................
          240-255    |  P P P P P P P P P P P P P P P P

    Now, all you have to do is to set palette 1, then fade it to palette 2,
  and then to palette 1 again, etc...
    Let's see the code:

           Program Crossfade16;

           Uses Mode13h,Crt;

           Var Pal1,Pal2:RgbList;

           Procedure InitAll;
           Var A,B,C,D:Word;
           Begin
                { Clear the VGA screen and put it's palette black }
                Cls(0,VGA);
                For A:=0 To 255 Do SetColor(A,0,0,0);
                { Load the two pictures and store their palettes }
                LoadPcx('Pic1.Pcx',Vp[1]);
                Move(PCXPal,Pal1,768);
                LoadPcx('Pic2.Pcx',Vp[2]);
                Move(PCXPal,Pal2,768);
                { Combine the two pictures in VGA screen }
                For A:=0 To 199 Do
                  For B:=0 To 319 Do
                  Begin
                       C:=GetPixel(B,A,Vp[1]);
                       D:=GetPixel(B,A,Vp[2]);
                       PutPixel(B,A,(D Shl 4)+C,VGA);
                  End;
                { Adjust palette 1 }
                For A:=1 To 15 Do
                  For B:=0 To 15 Do
                    Pal1[A*16+B]:=Pal1[B];
                { Adjust palette 2 }
                For A:=0 To 15 Do Pal2[A*16]:=Pal2[A];
                For A:=0 To 15 Do
                  For B:=0 To 15 Do Pal2[A*16+B]:=Pal2[A*16];
           End;

           Begin
                InitGraph;
                InitVirt;
                InitAll;
                Repeat
                      Fade(Pal1);
                      Fade(Pal2);
                Until Keypressed;
                CloseVirt;
                CloseGraph;
           End.

    Neat, isn't it ? If you have a slow video card, as I have, you'll notice
  LOTS of snow... That's because of the slownest of the computer in setting
  the colors. But with a faster computer and/or card, you won't notice it.
    So, let's move on:

    7.2. 256 color crossfading

    Well, this isn't actually 256 color crossfading. It's... some more colors
  crossfading... It is very slow (it requires some precalculation), but it
  enables to have more than 16 colors in both pictures (a maximum of 256
  when we combine them), as long as they don't overlap.
    The ideia is this:

    Screen 1      Screen 2     Combined Screen
    ...1....      ..2.....        ..12....
    ..3...4.      ..1..4..        ..3..45.
    ..2..3..      .111111.        .676636.

    The dot represents color 0.
    The colors that appear in the combined screen are a combination of the
  colors of the two screens:
                   ________________________________________
                   |  Color no. |  Screen 1  |  Screen 2  |
                   ----------------------------------------
                   |      0     |     0      |     0      |
                   |      1     |     0      |     2      |
                   |      2     |     1      |     0      |
                   |      3     |     3      |     1      |
                   |      4     |     0      |     4      |
                   |      5     |     4      |     0      |
                   |      6     |     0      |     1      |
                   |      7     |     2      |     1      |
                   ----------------------------------------

    What does this mean after all ? Well, we now have to create two palettes
  (the palettes we are going to fade around). In pallete 1, color 0 will have
  the same value as color 0 of screen 1, color 1 will have the same value
  as color 0 of screen 1, color 2 will have the same value of color 1 of
  screen 1, and so forth (compare the values with the table above). Palette 2
  will have color 0 equal to color 0 of screen 2, color 1 equal to color 2
  of screen 2, color 2 equal to color 0 of screen 2, etc...
    And this is it... This requires some computation in the beggining of the
  program, but you can even store the combined image and their palettes.
    When you use this type of crossfading, you should ensure that the images
  overlap as little as possible. There are three pictures I made to examplify
  this. Change the name of the files in the program to access them. They are
  called Pic3.Pcx, Pic4.Pcx and Pic5.Pcx. I made them in a way that both
  Pic3.Pcx and Pic4.Pcx have more than 100 colors, but they do not overlap,
  while Pic5.Pcx overlaps with any of the pictures, but has only 4 colors (so
  I can probably crossfade them). Here's the code:

        Program Crossfade256;

        Uses Mode13h,Crt;

        Var Dx,Dy,Dc:Word;
            Pal1,Pal2:Rgblist;
            Pal3,Pal4:rgblist;
            W:Word;
            R1,G1,B1,R2,G2,B2:Byte;
            Change:Boolean;
            P1,P2:Byte;
            F:File;

        Begin
             InitGraph;
             InitVirt;
             LoadPcx('Pic3.Pcx',Vp[1]);
             Move(PCXPal,Pal1,768);
             LoadPcx('Pic4.Pcx',Vp[2]);
             Move(PCXPal,Pal2,768);
             Cls(0,VGA);
             W:=1;
             FillChar(Pal3,768,0);
             FillChar(Pal4,768,0);
             For Dx:=0 To 319 Do
             Begin
                  For Dy:=0 To 199 Do
                  Begin
                       P1:=GetPixel(Dx,Dy,Vp[1]);
                       P2:=GetPixel(Dx,Dy,Vp[2]);
                       If (P1<>0) Or (P2<>0) Then
                       Begin
                            Change:=False;
                            R1:=Pal1[P1].R;
                            G1:=Pal1[P1].G;
                            B1:=Pal1[P1].B;
                            R2:=Pal2[P2].R;
                            G2:=Pal2[P2].G;
                            B2:=Pal2[P2].B;
                            { Check if this combination was already made }
                            For Dc:=1 To W Do
                              If (Pal3[Dc].R=R1) And
                                 (Pal3[Dc].G=G1) And
                                 (Pal3[Dc].B=B1) And
                                 (Pal4[Dc].R=R2) And
                                 (Pal4[Dc].G=G2) And
                                 (Pal4[Dc].B=B2) Then
                                 Begin
                                      Change:=True;
                                      PutPixel(Dx,Dy,Dc,VGA);
                                 End;
                            If Not Change Then
                            Begin
                                 Inc(W);
                                 If W=256 Then
                                 Begin
                                      CloseGraph;
                                      WriteLn('Too many colors');
                                      ReadLn;
                                      Halt;
                                 End;
                                 Putpixel(Dx,Dy,W,VGA);
                                 Pal3[W].R:=R1;
                                 Pal3[W].G:=G1;
                                 Pal3[W].B:=B1;
                                 Pal4[W].R:=R2;
                                 Pal4[W].G:=G2;
                                 Pal4[W].B:=B2;
                            End;
                       End;
                  End;
             End;
             Repeat
                   Fade(Pal4);
                   Fade(Pal3);
             Until Keypressed;
             CloseVirt;
             CloseGraph;
        End.

    Just change the name of the files to see the effects... :)) Don't try to
  crossfade with this method image Pic3.Pcx and Pic2.Pcx... It won't work...
    This is it for crossfading... In a future issue, I'll teach a diferent
  kind of crossfading, that requires ModeX...
    'till then... Adios...


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-

  8. Sprites Part IV - Rotating and scaling

    This is an article about two cool things to use in demos and games. They
  are quite simple to do, so this will be a fast article. :)

    8.1. Rotation

    In issue 10 of 'The Mag', in the 3d article, I derived that the equations
  for 2d rotation were:
                          X' = X*Cos(B) - Y*Sin(B)
                          Y' = Y*Cos(B) + X*Sin(B)

    You should check out the derivation, because that's important, if you want
  to be a good coder.
    Normally, you can use the following procedure to put a sprite (this
  procedure isn't the same we used in previous issues... It's just so I can
  demonstrate the rotation principle...):

              Procedure PutImage(X,Y:Word;Var Img:Pointer;Where:Word);
              Var Dx,Dy:Word;
                  A,B:Word;
                  Segm,Offs:Word;
                  Xp,Yp:Integer;
                  Color:Byte;
              Begin
                   Segm:=Seg(Img^);
                   Offs:=Ofs(Img^);
                   Move(Mem[Segm:Offs],Dx,2);
                   Move(Mem[Segm:Offs+2],Dy,2);
                   Offs:=Offs+4;
                   For A:=1 To Dy Do
                     For B:=1 To Dx Do
                     Begin
                          Xp:=X+B;
                          Yp:=Y+A;
                          Color:=Mem[Segm:Offs];
                          PutPixel(Xp,Yp,Color,Where);
                          Inc(Offs);
                     End;
              End;

    Basically, he will traverse the square region where the image must be and
  he places the right color there... The coordinates where to put the current
  pixel are (Xp,Yp).
    If we want to rotate the image, we should place the pixels in a different
  place, according to the angle... So, using the above formulas and using the
  code given above, we do the PutRotatedImage procedure:

      Procedure PutRotatedImage(X,Y,Angle:Word;Var Img:Pointer;Where:Word);
      Var Dx,Dy:Word;
          A,B:Word;
          Segm,Offs:Word;
          Xp,Yp:Integer;
          Xr,Yr:Integer;
          Color:Byte;
      Begin
           Segm:=Seg(Img^);
           Offs:=Ofs(Img^);
           Move(Mem[Segm:Offs],Dx,2);
           Move(Mem[Segm:Offs+2],Dy,2);
           Offs:=Offs+4;
           For A:=1 To Dy Do
             For B:=1 To Dx Do
             Begin
                  Xp:=X+B;
                  Yp:=Y+A;
                  { Calculate rotated coordinates }
                  Xr:=Trunc(Xp*Cos(Angle)-Yp*Sin(Angle));
                  Yr:=Trunc(Yp*Cos(Angle)+Xp*Sin(Angle));
                  Color:=Mem[Segm:Offs];
                  PutPixel(Xr,Yr,Color,Where);
                  Inc(Offs);
             End;
      End;

    The problem of this routine is that accounts that the rotation will be
  around point (0,0) of the screen ! That won't do... We must do the rotation
  according to the center of the object... To do so, we must use the coordinates
  relatively to the center of rotation. The center of rotation will be the
  center of the sprite: (X+Dx Div 2,Y+Dy Div 2). Then, Xp and Yp must be
  calculated in respect to those coordinates. And finally, after the rotated
  points are calculated, we must add the value of the center, to achieve the
  right rotation... The inner loop will look like this:

                  { Calculate relative coordinates }
                  Xp:=(X+B)-(X+Dx Div 2);
                  Yp:=(Y+A)-(Y+Dy Div 2);
                  { Calculate rotated coordinates }
                  Xr:=Trunc(Xp*Cos(Angle)-Yp*Sin(Angle));
                  Yr:=Trunc(Yp*Cos(Angle)+Xp*Sin(Angle));
                  { Add the value of the center }
                  Xr:=Xr+(X+Dx Div 2);
                  Yr:=Yr+(Y+Dy Div 2);
                  { Put the pixel }
                  Color:=Mem[Segm:Offs];
                  PutPixel(Xr,Yr,Color,Where);
                  Inc(Offs);

    Now this works fine... Let's now speed this up... The center of rotation
  is something used twice for loop... There's no use of calculating it time
  after time... Let's calc it once, in the beggining, save the result in two
  variables, and then use the variables... That's faster...
    Also, you can use lookup tables for the sines and cosines... In the end,
  the procedure will look like this:

      Procedure PutRotatedImage(X,Y,Angle:Word;Var Img:Pointer;Where:Word);
      Var Dx,Dy:Word;
          A,B:Word;
          Segm,Offs:Word;
          Xp,Yp:Integer;
          Xr,Yr:Integer;
          Xc,Yc:Integer;
          Color:Byte;
      Begin
           Segm:=Seg(Img^);
           Offs:=Ofs(Img^);
           Move(Mem[Segm:Offs],Dx,2);
           Move(Mem[Segm:Offs+2],Dy,2);
           Offs:=Offs+4;
           Xc:=X+(Dx Div 2);
           Yc:=Y+(Dy Div 2);
           For A:=1 To Dy Do
             For B:=1 To Dx Do
             Begin
                  { Calculate relative coordinates }
                  Xp:=(X+B)-Xc;
                  Yp:=(Y+A)-Yc;
                  { Calculate rotated coordinates }
                  Xr:=Trunc(Xp*Cosines^[Angle]-Yp*Sines^[Angle]);
                  Yr:=Trunc(Yp*Cosines^[Angle]+Xp*Sines^[Angle]);
                  { Add the value of the center }
                  Xr:=Xr+Xc;
                  Yr:=Yr+Yc;
                  { Put the pixel }
                  Color:=Mem[Segm:Offs];
                  PutPixel(Xr,Yr,Color,Where);
                  Inc(Offs);
             End;
      End;

    Don't forget to call the InitTables procedure before doing this, because
  it needs it... Check out the example program:

      Program TestRotation;

      Uses Crt,Mode13h,Sprites;

      Var F:File;
          Image:Pointer;
          A:Word;

      Procedure PutRotatedImage(X,Y,Angle:Word;Var Img:Pointer;Where:Word);
      Var Dx,Dy:Word;
          A,B:Word;
          Segm,Offs:Word;
          Xp,Yp:Integer;
          Xr,Yr:Integer;
          Xc,Yc:Integer;
          Color:Byte;
      Begin
           Segm:=Seg(Img^);
           Offs:=Ofs(Img^);
           Move(Mem[Segm:Offs],Dx,2);
           Move(Mem[Segm:Offs+2],Dy,2);
           Offs:=Offs+4;
           Xc:=X+(Dx Div 2);
           Yc:=Y+(Dy Div 2);
           For A:=1 To Dy Do
             For B:=1 To Dx Do
             Begin
                  { Calculate relative coordinates }
                  Xp:=(X+B)-Xc;
                  Yp:=(Y+A)-Yc;
                  { Calculate rotated coordinates }
                  Xr:=Trunc(Xp*Cosines^[Angle]-Yp*Sines^[Angle]);
                  Yr:=Trunc(Yp*Cosines^[Angle]+Xp*Sines^[Angle]);
                  { Add the value of the center }
                  Xr:=Xr+Xc;
                  Yr:=Yr+Yc;
                  { Put the pixel }
                  Color:=Mem[Segm:Offs];
                  PutPixel(Xr,Yr,Color,Where);
                  Inc(Offs);
             End;
      End;

      Begin
           { Init }
           InitGraph;
           InitTables;
           { Set the colors }
           SetColor(0,0,0,0); SetColor(1,0,0,63); SetColor(2,63,0,0);
           SetColor(3,63,63,0); SetColor(4,0,63,0); SetColor(5,0,63,63);
           { Load image }
           Assign(F,'Example.Img');
           Reset(F,1);
           LoadImage(F,Image);
           Close(F);
           { Rotate the sprite }
           For A:=0 To 359 Do
           Begin
                PutRotatedImage(160,100,A,Image,VGA);
           End;
           ReadKey;
           { Shut Down }
           KillImage(Image);
           ClearTables;
           CloseGraph;
      End.

    So, what are the advantages and disadvantages of this method ?
    Well, it is simpler to understand, it works with any sized images, it
  doesn't require extra memory, and it doesn't clips the images. On the other
  hand it is slow and leaves holes in the image.
    So, how can we speed this up and take out the holes ? Well, to speed up,
  we'll have to use something pregenarated. To fill up the holes we'll have
  to understand first why there are holes in the image...
    The holes appear because the rotated points don't fill up the image
  completely. So, how to prevent it ? We'll invert this thing !!!
    So, instead of calculating where the points will be, we'll calculate were
  the points should be !! Confusing ?! Think about this: you now have the
  starting coordinates of the points and you want to calculate the destination.
  So, know you'll have the destination points and you'll calculate the starting
  points, avoiding the holes. Notice that the inversion of the transformation
  equations is just the same thing, but with negated angles. As you are working
  with a pregenerated table for the sines and cosines, you'll have to convert
  the negative angles into positive ones. So, if the angle you want to rotate
  by is -x, the position in the table will be 360-x.
    So, to build the procedure, let's go by steps.
    First, let's assume you're plotting a rectangular sprite with corners that
  would be in coordinates (x1,y1)(x2,y2)(x3,y3)(x4,y4) if they weren't rotated.
  Don't forget that, if (x1,y1) is the upper-left coordinates of the sprite,
  they are given by the procedure call. So:

                 x2=x1+Xsize      x3=x1+Xsize      x4=x1
                 y2=y1            y3=y1+Ysize      y4=y1+Ysize

    The color of a point (x,y) inside that rectangle will be the value in the
  memory position [Seg(Image):(y*xsize)+x]. Now, let's imagine the following
  coordinates: (x'1,y'1),(x'2,y'2),(x'3,y'3),(x'4,y'4). They will be calculated
  by our procedure. They are the corners of the sprite after the rotation.
    Then, after this conversion, we calculate the maximum and minimum X and Y
  of these rotated coordinates, and then we do two For cicles from the minimums
  to the maximums and we do the inversed rotation, calculating the color to put
  in each of the points.
    I'm a tad late with this issue of 'The Mag' (about two or three months,
  actually), so I don't have time to do the code for this one, but with the
  info I gave you, you shouldn't have any problems. If you do have problems,
  mail me and I'll try to sort it out.
    On to...

    8.2. Scaling

    Scaling is the process of changing the size of something. In our case,
  a sprite.
    There a number of processes to do this, but the one I'll teach you is very
  simple and efficient, if well optimized.
    Imagine you have the following 2x2 sprite:             12
                                                           32

    If you scale it by two, you would get the following 4x4 sprite:

                                1122
                                1122
                                3322
                                3322

    So, what has happened ? Well, every pixel in the image became 4, not 2.
  Why ? Because you are scaling the X and Y factors. Now, let's imagine we
  wanted to fit that second image in a 3x3 square. We would get something like
  this:
            112  or this:   112  or this:   112  or... well, you get the
            132             112             332  picture... :)
            322             332             322

    How can we make this come true ? Good question...
    We'll have to use a step value for each the x and y coordinates. Confused ?
  No wonder... Let's go one step at a time.
    First, we need to do the definition of the procedure:

         Procedure Scale(Image:Pointer;DeltaX,DeltaY:Word;Var Target:Pointer);

    The Deltas are the size of the final sprite. Now, from the original sprite
  let's get the original size:

                         Segm1:=Seg(Image^);
                         Offs1:=Ofs(Image^);
                         Move(Mem[Segm1:Offs1],Xsize,2);
                         Move(Mem[Segm1:Offs1+2],Ysize,2);

    Now, we create the necessary space to store the scaled image:

                         GetMem(Target,DeltaX*DeltaY+4);

    And we store it's size in the first 4 bytes:

                         Segm2:=Seg(Target^);
                         Offs2:=Ofs(Target^);
                         Move(DeltaX,Mem[Segm2:Offs2],2);
                         Move(DeltaY,Mem[Segm2:Offs2+2],2);
                         Offs2:=Offs2+4;

    Then we find the step mentioned earlier... What is this step ? It is the
  ammount you must increase the pointer so... Well, nevermind, this would be
  complicated to explain by text with my limited english... :)
    Let's try an example... We have picture 1 and we want to double it's size:

    ab   <- This is picture 1                                      aabb
    cd                           This is the doubled picture 1 ->  aabb
                                                                   ccdd
                                                                   ccdd

    Now, let's have an index pointing to the first pixel in picture 1, and
  call it 'Joe'... :) Now, let's have another index pointing to the first pixel
  of the target memory area. Let's call that index 'John'.
    First, we copy the byte indexed by 'Joe' to the place 'John' points. Then,
  we increment John by one, and we increase Joe by 0.5 (the step in this case).
    Then we copy the byte indexed by Joe to the place John points. This is
  the real trick. We use only the integer part of Joe. In the above case, it
  copies the same thing ('a'), because the integer part is still 1, though
  Joe is really 1.5 !!! :)))
    We do this for the X and Y axis and we have a scalled image in no time !
    Let's get the ratio:

                         XRatio:=XSize/DeltaX;
                         YRatio:=YSize/DeltaY;

    Now, let's create 'Joe', and call it IndexX and IndexY (because there are
  two axis):
                         IndexX:=0;
                         IndexY:=0;

    And let's create 'John', and call it Dx and Dy (also two axis):

                         For Dy:=1 To DeltaY Do
                         Begin
                           For Dx:=1 To DeltaX Do
                           Begin

    Now, let's get the byte indexed by Joe:

                                Offs1:=Trunc(IndexY)*XSize+Trunc(IndexX)+4;
                                C:=Mem[Segm1:Offs1];

    And let's put that byte in John. As John is sequential (remember what was
  said about the storage of a sprite in the first parts of the Sprite Tut),
  we can do that without much fuss:

                                Mem[Segm2:Offs2]:=C;

    And let's update the variables:

                                Inc(Offs2);
                                IndexX:=IndexX+XRatio;
                           End;
                           IndexX:=0;
                           IndexY:=IndexY+YRatio;
                         End;

    And let's finish up the procedure:

                     End;

    And voil, a complete scaling procedure... Let's check out the whole
  code:

      Procedure Scale(Image:Pointer;DeltaX,DeltaY:Word;Var Target:Pointer);
      Var Segm1,Segm2,Offs1,Offs2:Word;
          XSize,YSize:Word;
          IndexX,IndexY,XRatio,YRatio:Real;
          Dx,Dy:Word;
          C:Byte;
      Begin
           Segm1:=Seg(Image^);
           Offs1:=Ofs(Image^);
           Move(Mem[Segm1:Offs1],Xsize,2);
           Move(Mem[Segm1:Offs1+2],Ysize,2);
           GetMem(Target,DeltaX*DeltaY+4);
           Segm2:=Seg(Target^);
           Offs2:=Ofs(Target^);
           Move(DeltaX,Mem[Segm2:Offs2],2);
           Move(DeltaY,Mem[Segm2:Offs2+2],2);
           Offs2:=Offs2+4;
           XRatio:=XSize/DeltaX;
           YRatio:=YSize/DeltaY;
           IndexX:=0;
           IndexY:=0;
           For Dy:=1 To DeltaY Do
           Begin
                For Dx:=1 To DeltaX Do
                Begin
                     Offs1:=Trunc(IndexY)*XSize+Trunc(IndexX)+4;
                     C:=Mem[Segm1:Offs1];
                     Mem[Segm2:Offs2]:=C;
                     Inc(Offs2);
                     IndexX:=IndexX+XRatio;
                End;
                IndexX:=0;
                IndexY:=IndexY+YRatio;
           End;
      End;

    This could have been done faster, if I used integers instead of reals,
  but I'll leave that for a future issue, when I cover fixed-point maths.
    A complete working example of this can be seen in the file SPRSCAL.PAS.
  I already included the rotation and scaling procedures in the SPRITE unit,
  so the programs don't include them.
    This is the end of this article, and I really don't know what I'm gonna
  do for the next one on Sprites... If no one asks me a specific matter, I may
  do an article on detecting collisions between sprites.


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-

  9. Graphics Part X - Assembling it all

    This article is the last of the standart Mode 13h series. it is about how
  you can convert your code to be faster and more efficient, by use of assembly
  and of some optimizations.
    To understand this issue, a basic knowledge of assembler is needed. That
  knowledge can be found in previous issues of 'The Mag'.
    So, let's get started: let's begin with the all time favorite, Mr.PutPixel !
    The original version was something like this:

        Procedure PutPixel(X,Y:word;Col:Byte;Where:Word);
        Begin
             Mem[Where:(y*320)+x]:=Col;
        End;

    We can do an assembler version. It looks like this (I will number the
  line so that it is easier to explain):

     1  Procedure PutPixel(X,Y:Word;Col:Byte;Where:Word); Assembler;
     2  Asm
     3     Mov Ax,[Where]
     4     Mov Es,Ax
     5     Mov Ax,[Y]
     6     Mov Bx,320
     7     Mul Bx
     8     Add Ax,[X]
     9     Mov Di,Ax
    10     Mov Al,[C]
    11     StosB
    12  End;

    This is quite simple. Let's see it in reverse. Line 11 is the line that
  really puts the pixel. It places the value stored in AL in the memory
  position Es:Di (Es is the Segment, Di is the Offset), and increments Di.
    So, we must initialize those three registers (AL,ES,DI).
    We initialize AL in line 10 (we load the color value in it).
    ES is initialized in lines 3 and 4. Why move to Ax in order to move to Es,
  and not move directly ? Because you can't. Because of the way the internal
  architecture of the PC is designed, you can't load memory positions into
  segment registers (ES,SS,CS,DS,FS and GS). You can only load registers into
  those registers. That is why we do two steps.
    Then, we must initialize DI. DI will be equal to (Y*320)+X.
    In line 7 we do the multiplication needed. The MUL opcode multiplies the
  contents of AX with the contents of operand given, and stores the result in
  AX. So, line 7 is really (in this case): AX=AX*BX.
    The explanation I gave on MUL isn't accurate. Check issue 5 of 'The Mag'
  for a more thorought explanation on the opcodes. I don't have time here to
  explain every opcode I'll use.
    So, lines 5 and 6 prepare the multiplication. And that's it for this
  procedure.
    But, we can still optimize it. How ? By using a nifty trick.
    Multiplications are slow, even if done in assembler. If it was a way to
  multiply without using the multiplication ?
    There is !!!! Using binary shifts. We can shift left to get a times two
  multiplication. But, 320 isn't a shiftable number, that is, there isn't any
  n such as 320=2^n. That is true, but look at this:

                     320 = 256+64
                   Y*320 = Y*(256+64) = Y*256+Y*64

    See ? We first shift Y by 8 (2^8=256) and store that result in a register.
  Then, we shift Y by 6 (2^6=64) and store that result in another register.
  Then, we add up the both of them, and we got Y*320 !! And faster (timing
  proves that this is almost 3 times faster !!! And that's a major improvement
  in speed, because a Putpixel is a very used routine (well, really not much,
  but it doesn't matter).
    So, the final procedure is like this:

        Procedure PutPixel(X,Y:Word;C:Byte;Too:Word); Assembler;
        Asm
           Mov Ax,[Too]
           Mov Es,Ax
           Mov Bx,[X]
           Mov Dx,[Y]
           Mov Di,Bx
           Mov Bx,Dx
           Shl Dx,8             { Shift Dx left 8 times (Dx=Dx*256) }
           Shl Bx,6             { Shift Bx left 6 times (Bx=Bx*64)  }
           Add Dx,Bx
           Add Di,Dx
           Mov Al,[C]
           Stosb
        End;

    This is about 6 times faster than the original putpixel.
    Now, let's move to... clearing the screen. This is very simple, actually.
  You just do it like this:

              Procedure Cls(Col:Byte;Where:Word); Assembler;
              Asm
                 Mov Ax,[Where]
                 Mov Es,Ax
                 Mov Al,[Col]
                 Mov Cx,64000
                 Mov Di,0
                 Rep StosB
              End;

    The REP prefix makes the following command to be repeat as many times as
  there are indicated. Actually, what that line does is something like this:

                 Loop:
                      Move value from AL to ES:DI
                      Increment Di
                      Decrement Cx
                      Is Cx is greater than 0 then jump to beggining of loop

    There are two things you can do to increase the speed of this code. One is
  exchange the Mov Di,0 instruction with Xor Di,Di. This clears the Di register
  in one clock tick (the clock tick is the unit of speed in assembler), while
  the Mov instruction used 4 clock ticks. This might not seem much, but after
  some frames of calculation, it can be pretty good.
    Another thing is using the StosW command, instead of the StosB. The StosW
  command is similar to the StosB, except in some aspects:

             -> It moves a word instead of a byte
             -> It increments Di by two, instead of one

    So, if you load AL with the Col value and the AH with that same value,
  and you use the StosW opcode, you only need to do the loop 32000 times.
    You may ask yourselfs why is this faster... The answear is rather simple.
  Because of the architecture of our friend, the 80x86 processor, it is ALWAYS
  faster to move and manipulate words than bytes. Everytime you can, use words.
    So, the final CLS procedure will look like this:

              Procedure Cls(Col:Byte;Where:Word); Assembler;
              Asm
                 Mov Ax,[Where]
                 Mov Es,Ax
                 Mov Al,[Col]
                 Mov Ah,Al
                 Mov Cx,32000
                 Xor Di,Di
                 Rep StosW
              End;

    Now, let's toy around with the procedure that sets the color of the
  screen palette: SetColor. The original is like this:

                  Procedure SetColor(Col,R,G,B:Byte);
                  Begin
                       Port[$3C8]:=Col;
                       Port[$3C9]:=R;
                       Port[$3C9]:=G;
                       Port[$3C9]:=B;
                  End;

    We'll do the same thing exactly, but in assembly language:

                  Procedure SetColor(Col,R,G,B:Byte); Assembler;
                  Asm
                     Mov Dx,3c8h         { Dx:=3c8h }
                     Mov Al,[Col]        { Al:=Col  }
                     Out Dx,Al           { Port[Dx]:=Al }
                     Inc Dx              { Dx:=Dx+1 (Dx=3c9h) }
                     Mov Al,[R]          { Al:=R }
                     Out Dx,Al           { Port[Dx]:=Al }
                     Mov Al,[G]          { Al:=G }
                     Out Dx,Al           { Port[Dx]:=Al }
                     Mov Al,[B]          { Al:=B }
                     Out Dx,Al           { Port[Dx]:=Al }
                  End;

    The comments in the side should get this right for you. It's fairly simple
  this one.
    Now, if we wanted to set the whole palette ? Normally, we would use the
  above procedure to do so. But we can take advantage of the linearity of
  memory in an array and set it all much faster.
    You just make a 'pointer' (this is not the correct name for it in asm)
  point to the array where the colors are stored, and then you blast the 768
  bytes directly into video memory:

    1   Procedure SetPalette(Var Palette:RgbList);Assembler;
    2   Label L1,L2;
    3   Asm
    4      Mov Dx,3dah
    5      L1:
    6         In Al,Dx
    7         And Al,08h
    8         Jnz L1
    9      L2:
   10         In Al,Dx
   11         And Al,08h
   12         Jz l2
   13      Push Ds
   14      Lds Si,Palette
   15      Mov Dx,3c8h
   16      Mov Al,0
   17      Out Dx,Al
   18      Inc dx
   19      Mov Cx,768
   20      Rep OutsB
   21      Pop Ds
   22   End;

    Lines 4 to 12 are to implement the WaitVBL procedure, to avoid the fuzz
  on the screen. Then, we save the content of the Ds register (never loose
  this one, or else you're f***** when you return to Pascal).
  Line 14 loads the 'pointer'. It makes DS:SI point to the beggining of the
  palette. In line 15/17, we tell the VGA board that we want to start setting
  colors from color 0. Then, you use line 19/20 to blast the data to the video
  port, using the Rep prefix with the OutsB, which is the equivelent to Stosb
  for ports. Then we restore the Ds register, because it points to Pascal's
  data segment, where we store all the variables.
    Let's move on to the filled polygon procedure. We aren't going to convert
  it to Pascal (is a bit complicated and I'm not up to it... :))). But I'll
  tell you how to do a small optimization that will really speed this up.
    When we draw the polygon, we have to draw lots of horizontal lines. For
  this we are using the general line procedure. But we don't need that !!!
  We now from the start that the line is horizontal, and by taking advantage
  of the memory linearity of mode 13h, we can do a special procedure to draw
  horizontal lines !!!!

             Procedure HLine(Y,X1,X2,Color,Where); Assembler;
             Asm
                Mov Ax,[Where]
                Mov Es,Ax
                Mov Dx,[Y]
                Mov Bx,Dx
                Shl Dx,8
                Shl Bx,6
                Add Dx,Bx
                Mov Bx,[X1]
                Mov Di,Bx
                Add Di,Dx       { Es:Di now points to the place where we must
                                  start drawing the horizontal line }
                Mov Cx,[X2]
                Sub Cx,Bx
                Mov Al,[C]
                Rep Stosb
             End;

    Now, you just change the code that draws the line in the FPoly procedure
  to get this working pretty fast compared to the older.
    We can speed up this, by putting down two pixels at a time. We just have
  to put the color value in Ah as well as in Al, and divide by two the number
  of loops (shift right Cx one time). We can only do this optimization, when
  we know that X2-X1 is an even number (we just need to check the last bit
  in Cx to do so, but I'll leave that to you).
    The same principle applies to the Filled Ellipse.
    NOTE: I've found an error when I was testing this SHIT !! :((( The FPoly
  procedure would not work in some cases. It was just a problem of typecasting.
  Change all variables that are not integer to integer type, in the FPoly
  procedure, and all will work out fine... Sorry about that...
    Now, one the most important things to be optimized in a program: the
  pagefliping procedure.
    For this one, we'll use the MovsW opcode, which moves the word indexed by
  Ds:Si to the memory location indexed by Es:Di. This is fairly easy also:

           Procedure CopyPage(From,Too:Word); Assembler;
           Asm
              Push Ds
              Mov Ds,[From]
              Mov Es,[Too]
              Xor Di,Di
              Xor Si,Si
              Mov Cx,32000
              Rep MovsW
              Pop Ds
           End;

    We transfer whole words at a time because (as I said earlier) it is faster
  that way. Did I mention that the VGA board is built in such a fashion that
  putting one pixel is twice as slow as putting two ? Effectively speaking,
  using words in graphics is 4 times faster than working with bytes !!!

    So, I guess this is the end of this article. We could do ALL the procedures
  in the Mode13h unit in Asm, but some of them (as the filled poly) are quite
  hard, and don't have the time to get it on... But try it yourself. If you
  succeed and have the time, make a small article explaining it and send it
  to me !! I would love it !! :))))
    Farewell, and don't forget to experiment... Probably I'll start a new
  series of graphic articles in the next issue, dedicated to ModeX !! Don't
  miss it !!! :)))


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-


  10. Hints and Tips

                *   - Begginners tip
                **  - Medium tip
                *** - Advanced tip

    -  Timing a routine (**)

       How many times did you asked yourself: "Is this routine faster than
       the other one ?", and you hurried to get the chronometer ?
       Well, there's a simple way to get the time a routine took to execute.
       First thing, we should have a variable... But a special variable, this
       one. This variable is addressed in a way that it overlaps the computer's
       timer.
       There is a position in memory that stores a long integer that every
       1/18th of a second increases it's value by one. You make a variable
       point to it, using the ABSOLUTE keyword. That keyword enables you to
       tell Turbo Pascal where to address the variable.
       So, you define a longint like this:

               Var MemClock:LongInt Absolute $40:$6C;

       $40:$6C is the memory position ocuppied by the computer's clock.
       So, let's time some routines:

                 Var Start,Finish:LongInt;
                     Time:LongInt;
                 .......................................
                 .......................................
                 Start:=MemClock;
                 .......................................
                 { Put the routine you want to time here... Usually it is
                   associated with a loop or something. }
                 .......................................
                 Finish:=MemClock;
                 Time:=(Finish-Start) Div 18;
                 Writeln('The routine took ',Time,' seconds...');

       We divide the value by 18 because the variable is updated 1/18th times
       a second.
       Just one word of caution. Although you can, you shouldn't change the
       MemClock variable, because it can screw up the time of your computer,
       until you reset it...
       Other use for this trick is to ensure that a game or demo runs at the
       same speed in all computers, but I'll let you figure out how to do
       it... Ehehhe... :)))

    -  Mapping a screen (**)

       As we saw in the previous tip, we can specify where in memory a variable
       will be. We can use this concept to 'map' a screen.
       That is used in the Smooth Scrolling in Text Mode program made by Brent
       Hostetler.
       In the beggining he has something like (this is adapted for simplicity's
       sake):

             Type VirSCR=Array[0..4999] Of Byte;
             Var VirtualScreen : VirSCR absolute $B800:0000;

       Now, anything that we send to the VirtualScreen variable will appear
       on screen. For example

                  VirtualScreen[0]:=Ord('A');

       will make an A appear on the top-left corner of the screen. And:

                  VirtualScreen[1]:=14;

       will transform that character from white to yellow. You can use this
       instead of pointers to simplify the operations. To see how the textmode
       screen works, go to issue 9, and there's an article that covers it in
       a ligth way.
       I think you can also map the Mode 13h screen, but I'm not sure, since
       I never tried it out... Try it... :)))

    -  Byte problem (*)

       In the text adventure article this issue, I had to change from a byte
       to a shortint. Why ? Because of this:
       If you define A as a byte, and you do this:

                       A:=5-10

       You won't get -5 as an answear, because byte only ranges from 0 to 255.
       You'll get 251. That isn't good for some applications, like the game.
       To use the same memory and maintain a similar range, redefine A as a
       shortint, that ranges from -128 to 127.


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-


  11. Points of View

    Well, this issue is finally done !!!
    It just... well... er... 4 months late... I bet you gave up on me... But
  I think it was worth the wait !! 260 Kb (give or take a bit) of information.
  My finger ache just thinking about it... Just think of this... By my calcu-
  lations, it took me about 10 hours to get article 4 ready. That's lot of
  work... But school and the exams also postponed this issue time and time
  again... Oh, well... I'll try to do the next issue faster (and it probably
  will, because 3 articles are already made, not by me).
    This issue was so late already that I didn't do the control file for
  SpellView... Sorry about that, but that takes more time than you think...
  I'm thinking of adopting another way of doing this, but I'll see that in
  the future... Maybe releasing this in PostScrip and Txt file type... What
  do you think ?
    Next issue we'll have the continuation of the Text Adventure saga, this
  time dedicated to interaction with the gamescape. We'll have in the 3d front
  an article about texture mapping, and how to combine this with flat-shading
  (and if I'm up to it, an article on Gourad shading). Also, the first article
  about the Soundblaster (by Viriato), an article on DMA (made by Eyal Lotem)
  and an article about fixed point maths (made by Gh8). In the Sprites tutorial,
  I'll maybe talk about colision detection, and in the Graphics tutorial, I'll
  start to murk about in ModeX land! I'm thinking also on including an article
  about interfaces (mouse or keyboard, you choose). Of course there will be
  more tricks and tips, adventures of Spellcaster, and all that... :)
    Well, this issue is late enough already, so I'll just bid you all
  farewell, 'till the next issue and all that crap...


-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x


  12. The adventures of Spellcaster, the rebel programmer of year 2018.

  Episode 11 - Salvation

  - Spell... What's with you ? - Kristie asked, approaching me from behind,
  while I gazed at the red sky above.
  - Nothing... Just thinking of my doings...
  - You did the right thing. You saved him!
  - Yes... But how long will he thank me ?!
  - How come ?
  - Don't you understand what went in there ? He lost his humanity... All of
    it... He lost everything except his mind and his feelings... He even
    lost his mortallity...
  - What ?!
  - He cannot die... Not even pulling the plug... His mind will be always stored
    in the HexaMind's EPROM.
  - So what ?!
  - How long can he stand life... This artificial life... - I told her, closing
  shut my eyes, as if I wanted to block out the darkness of my soul.
  - Well... I don't know... But at least he will be happy until we get our
    revenge...
  I turned to Kristie and looked her into her dark brown eyes. I wanted to
  speak, but words didn't come out...
  - Kristie, it's no secret for nobody why me and Karl want to strike against
    COMPTEL... But why do you want to get even ?
  - I'd rather not talk about it, Diogo...
  - Diogo ?!!! We're improving... - I said with a light laugh - The only person
    that calls me that is my mother...
  - Well, I'm not your mother !!!!!! - she yelled, turning her back on me and
    taking one hard step.
  I streched my arm and I grabbed hers. She looked at me with a mixed expression
  of fear and surprise. I pulled her towards me, and then, without thinking,
  I kissed her in the lips.
  She got loose and she looked at me, with a strange look on her face. I tried
  to unveil her emotions, but it was useless... I took a step towards her, and
  she also took one. Then, she punched me in the face... Really hard...


                                               See you in the next issue
                                              Diogo "SpellCaster" Andrade