Spell presents:


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

                                                        Issue 10
                                                        14-9-96


-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. Putting order into chaos
          2.1. Introduction
          2.2. BubbleSort
          2.3. ShellSort
          2.4. QuickSort
        3. The fire effect
        4. Designing a text adventure - Part III
          4.1. Creating objects
          4.2. Seeing the objects
          4.3. Interacting with objects
        5. The wonderfull world of scrolies !
          5.1. Introduction
          5.2. Simple scroller
          5.3. Sine scroller
          5.4. Mirror and water scroller
        6. First steps into virtual reality
          6.1. Wireframe graphics
          6.2. Basic transformations
          6.3. A 3D starfield
          6.4. VectorBalls
        7. Sprites Part III - Lots of fun stuff
          7.1. Transparency
          7.2. Moving over a background
          7.3. Flipping a sprite
        8. Graphics Part IX - Polygons
          8.1. Simple polygons
          8.2. Filled polygons
          8.3. Ellipses and Arcs
          8.4. Filled Ellipses
        9. Hints and tips
       10. Points of view
       11. The adventures of Spellcaster, part 10


-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

    Welcome to super-special issue number 10 !! Why is this issue so special ?
  Well, because it's number 10... I never thought I would get this far, and I
  almost gave up on the 'The Mag' project (see large delay between issues 5
  and 6). But, here it is, to last... :)
    As usual, this is brought by Spellcaster, alias known as Diogo de Andrade.
    What do we have this issue ? Let me see... An article by Scorpio, about
  the fire effect. In the graphics section, I'll talk about polygons and other
  draw tools (I know I said assembler, but I'll leave that for next issue).
  In the Sprites section, I'll talk about transparency, bitmap rotation and
  moving over a background. In 'Designing a text adventure', I'll write about
  objects and object interaction. I'll also do an article on 3D basic
  transformation and some simple effects. Also, an article on different kinds
  of scrollies. On the agenda, I also have an article on sorting...

    You should be reading this issue in the first of the SpellUtilities,
  SpellView ! It's a program designed to view text with some features, like
  colors and other neat stuff... :)
    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...

    This magazine is dedicated to all the programmers and would-be programmers
  out there, especially to those that can't access the Net easily to get
  valuable information, to those who wish to learn how to program anything,
  from demos to games, passing through utilities and all sort of thing your
  mind can think of, and to those that can't find the right information.

    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).

    As I stated above, this magazine will be made especially for those who don't
  know where to get information, or want it all in the same place, and to those
  who want to learn how to program, so I'll try to build knowledge, building up
  your skills issue by issue. If you sometimes fail to grasp some concept, don't
  despair; try to work it out.
    That's what I did... 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 18 years old, and I just made it in to 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 a year 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 180-190). I can program in C, C++, Pascal, Assembly
  and even BASIC (yech!).

    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. I may be young, but I know a lot about computers (how humble I am;
  I know, modesty isn't one of my qualities).

    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... I don't know if they'll accept it there, because
                      that's a demo only site, and my mag doesn't cover only
                      demos, but anyways, try it out... It has lots of source
                      code of demos.

    - 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. I'm specially interested in
  articles explaining XMS, EMS, DMA and Soundblaster/GUS.

    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 that in all talkers at the
  same time... :)

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

    Email: 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

    If that server is down, try the Cital talker, in

                        Zeus.Ci.Ua.PT    : Port 6969

    I'm almost always there in the afternoon... 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.
    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. Putting order into chaos

    2.1. Introduction

    Welcome to another article by your friend Spellcaster... This article is
  about sorting an array... What's sorting, you may ask ?
  [ Everyone asks what's sorting !! ]
    Well, sorting is organizing data in a data structure... Get it ?
  [ Everybody says no... ]
    Ok, imagine you have an array like this:

                A[1]:=221;
                A[2]:=321;
                A[3]:=831;
                A[4]:=432;
                A[5]:=721;

    Sorting is converting that array into:

                A[1]:=221;
                A[2]:=321;
                A[3]:=432;
                A[4]:=721;
                A[5]:=831;

    See ? Sorting is the same as ordering...
    There are _LOTS_ of methods for doing this, but there are some that are
  'standart'... Each one has it's advantages and disadvantages... I'll talk
  about three methods... I'll start with:

    2.2. BubbleSort

    BubbleSort is the slowest and easiest to program method, and it is sometimes
  the best one for small and desordered arrays.
    The ideia of BubbleSort is to order an array by swapping the contents of
  two adjacent memory positions (if they are out of order). A flag assures that
  the array is ordered. Execution example:

    We want to sort the array [ 7 4 2 3 5 1 6 ]. Let's see:

    Ŀ
     Step  Array          Comment                                    
    Ĵ
       0   7 4 2 3 5 1 6  The beggining                              
       1   4 7 2 3 5 1 6  Because 4<7, then 4 and 7 are exchanged    
       2   4 2 7 3 5 1 6  Because 2<7, then 2 and 7 are exchanged    
       3   4 2 3 7 5 1 6  Because 3<7, then 3 and 7 are exchanged    
       4   4 2 3 5 7 1 6  Because 5<7, then 5 and 7 are exchanged    
       5   4 2 3 5 1 7 6  Because 1<7, then 1 and 7 are exchanged    
       6   4 2 3 5 1 6 7  Because 6<7, then 6 and 7 are exchanged    
                          As the array isn't ordered yet, the process
                          restarts:                                  
       7   2 4 3 5 1 6 7  Because 2<4, then 2 and 4 are exchanged    
       8   2 3 4 5 1 6 7  Because 3<4, then 3 and 4 are exchanged    
       9   2 3 4 5 1 6 7  Because 5>4, the array remains the same    
      10   2 3 4 1 5 6 7  Because 1<5, then 1 and 5 are exchanged    
      ..   .............  .........................................  
           1 2 3 4 5 6 7                                             
    

    You should get the ideia by now... So, let's do a procedure that
  implements the above explained algorithm. The example program orders an array
  with 40 random inserted elements, generated from a seed (read this issue's
  hints and tips). The program also keeps tracks on how many comparissons and
  exchanges it does, so we can compare this type of sort with the others,
  because that's the way to evaluate the algorithm's overall eficiency.
  So, without further due, here's the code:

                Program BubbleSort;

                Uses Crt;

                Type DataType=Array[1..40] Of Integer;

                Var Data:DataType;
                    A:Byte;
                    Compare:Word;
                    XChange:Word;

                Procedure FillData;
                Begin
                     RandSeed:=122217;        { Random seed }
                     For A:=1 To 40 Do Data[A]:=Random(1000);
                End;

                Procedure WriteArray;
                Var B:Byte;
                Begin
                     For A:=0 To 3 Do
                     Begin
                          For B:=1 To 10 Do
                          Begin
                               GotoXY(B*6+5,A+10);
                               Write(Data[A*10+B]);
                          End;
                          Writeln;
                     End;
                End;

                Procedure Sort;
                Var Flag:Boolean;
                    C:Integer;
                Begin
                     Compare:=0;
                     XChange:=0;
                     Repeat
                           Flag:=True;
                           For A:=1 To 39 Do
                           Begin
                                If Data[A]>Data[A+1] Then
                                Begin
                                     { Exchange data }
                                     C:=Data[A];
                                     Data[A]:=Data[A+1];
                                     Data[A+1]:=C;
                                     Flag:=False;
                                     { Increase exchanges counter }
                                     Inc(XChange);
                                End;
                                { Increase comparisons counter }
                                Inc(Compare);
                           End;
                     Until Flag;

                End;

                Begin
                     Clrscr;
                     FillData;
                     GotoXY(13,8);
                     Writeln('Array before BubbleSort:');
                     WriteArray;
                     ReadLn;
                     Sort;
                     Clrscr;
                     GotoXY(13,8);
                     WriteLn('Array after BubbleSort:');
                     WriteArray;
                     GotoXY(13,16);
                     WriteLn('Compares=',Compare);
                     GotoXY(13,17);
                     Writeln('Exchanges=',XChange);
                     ReadLn;
                End.

    Well, this is the easiest kind of sort... Let's move on to the next:

    2.3. ShellSort

    ShellSort is the most complex (for me, at least) way of sorting...
    The problem with BubbleSort is that it exchanges values to near to each
  other... If you check the example array [ 7 4 2 3 5 1 6 ], notice that
  the number 7 must traverse almost the whole array to reach it's final
  position. Wouldn't it be great to make it go to it's position in 2 steps,
  instead of the 6 it requires ?
    Well, the ideia of ShellSort is to have an incremental factor... In reality,
  ShellSort is a special case of BubbleSort, a case where the elements that
  are compared/exchanged aren't so near to each other. The number of
  comparissons will sometimes be the same or bigger than BubbleSort, but the
  number of exchanges will be less... So, let's see an example:
    The increment selected is 4:

    Ŀ
     Step  Array          Comment                                    
    Ĵ
       0   7 4 2 3 5 1 6  The beggining                              
       1   5 4 2 3 7 1 6  Because 5<7, 5 and 7 are exchanged         
       2   5 1 2 3 7 4 6  Because 1<4, 1 and 4 are exchanged         
       3   5 1 2 3 7 4 6  Because 2<6, the array remains unaltered   
    

    If you look at the above example and think 'This demonstration isn't
  ended !!', you are wrong... This demonstration is more than finished.
  ShellSort requires several diferent increments, finalizing with the
  increment of 1. So, let's now adapt the array we've obtained with ShellSort
  with increment of 2:

    Ŀ
     Step  Array          Comment                                    
    Ĵ
       0   5 1 2 3 7 4 6  The beggining                              
       1   2 1 5 3 7 4 6  Because 2<5, 2 and 5 are exchanged         
       2   2 1 5 3 7 4 6  Because 1<3, the array remains unaltered   
       3   2 1 5 3 7 4 6  Because 5<7, the array remains unaltered   
       4   2 1 5 3 7 4 6  Because 3<4, the array remains unaltered   
       5   2 1 5 3 6 4 7  Because 6<7, 6 and 7 are exchenged         
    

    Well, the array isn't ordered already, but notice that the number 7 is
  already in the correct position... Now, you should apply ShellSort with
  increment of 1 to the array obtained from the previous ShellSorts. That is
  equal of applying BubbleSort. So, let's check out an example program...
  Again it keeps tracks of the numbers of comparissons and exchanges made:

                Program ShellSort;

                Uses Crt;

                Type DataType=Array[1..40] Of Integer;

                Var Data:DataType;
                    A:Byte;
                    Compare:Word;
                    XChange:Word;

                Procedure FillData;
                Begin
                     RandSeed:=122217;        { Random seed }
                     For A:=1 To 40 Do Data[A]:=Random(1000);
                End;

                Procedure WriteArray;
                Var B:Byte;
                Begin
                     For A:=0 To 3 Do
                     Begin
                          For B:=1 To 10 Do
                          Begin
                               GotoXY(B*6+5,A+10);
                               Write(Data[A*10+B]);
                          End;
                          Writeln;
                     End;
                End;

                Procedure Sort(H:Integer);
                Var Flag:Boolean;
                    B,C:Integer;
                Begin
                     For A:=1+H To 40 Do
                     Begin
                          C:=Data[A];
                          B:=A;
                          While (B-H>0) And (Data[B-H]>C) Do
                          Begin
                               Data[B]:=Data[B-H];
                               B:=B-H;
                               Inc(Compare,2);
                               Inc(XChange);
                          End;
                          Data[B]:=C;
                     End;
                End;

                Begin
                     Clrscr;
                     FillData;
                     GotoXY(13,8);
                     Writeln('Array before ShellSort:');
                     WriteArray;
                     ReadLn;
                     Compare:=0;
                     XChange:=0;
                     Sort(4);
                     Clrscr;
                     GotoXY(13,8);
                     WriteLn('Array after ShellSort (increment 4):');
                     WriteArray;
                     ReadLn;
                     Clrscr;
                     Sort(2);
                     Clrscr;
                     GotoXY(13,8);
                     WriteLn('Array after ShellSort (increment 2):');
                     WriteArray;
                     ReadLn;
                     Clrscr;
                     Sort(1);
                     Clrscr;
                     GotoXY(13,8);
                     WriteLn('Array after ShellSort (increment 1):');
                     WriteArray;
                     GotoXY(13,16);
                     WriteLn('Compares=',Compare);
                     GotoXY(13,17);
                     Writeln('Exchanges=',XChange);
                     ReadLn;
                End.

    Just compare the difference between the two sorts... If you are a little
  confused by the example program, reread it, keeping in mind that:

           1) The program goes from end to beggining of the array
           2) The program finds the best position for one value. For example:

                  Original Array: [ 7 2 3 1 5 4 ]

              If you use the original algorithm I talked about, then the array
              would look like this after a step (increment 2):

                                  [ 3 2 7 1 5 4 ]

              While the new algorithm makes the array like this:

                                  [ 3 2 5 1 7 4 ]

              In just one step, because it 'sticks' to number 7 until it can
              move it no further...

    Well, but what is the best sequence of increments ? There's just one rule
  for that... They shouldn't be multiples of each others... If it's a small
  array, that doesn't influenciate, but if it is a large array (i.e. more than
  5000 positions), it does make a very big difference.
    This is lots of times faster than BubbleSort, but still it is slower than:

    2.4. QuickSort

    This is harder to compreend for the begginer, because it is better done in
  a recursive form (see this issue's tricks and tips).
    So, QuickSort plays around with the ideia of partitioning the array is
  several smaller arrays, putting some order in each of the partitions.
    QuickSort doesn't really split the array in other arrays (that would need
  more memory). It just works with a part of the array at a time.
    To exemplify this one, I have to aquire another way to make the program's
  trace. So, assume that I and J as two variables that traverse the array, and
  imagine that the array we want to order is [ 44 55 12 42 94 18 6 67 ]
    So, the first thing to do in QuickSort is to select any number. Usually
  the number is in the array, altough this is not necessary. Let's choose the
  number 26. So, let's order the array in a way that all numbers smaller than
  26 go to the beggining of the array (the left) and all the numbers that are
  bigger go to the end of the array (the right). So:

         Step 0:   44 55 12 42 94 18 06 67
                   ^                  ^
                   I                  J

    So, what the algorithm does is to put I pointing to the first number it
  founds from the beggining of the array, larger than the select number (in
  this case 26). So, I points to 44. And we point J to the first number smaller
  than 26, counting from the right. In this case, J will point to 06.
    Then it switches the contents of both positions:

         Step 1:   06 55 12 42 94 18 44 67
                      ^            ^
                      I            J

    And I and J find the next numbers than fullfill the caracteristics we've
  discussed previously. So, step after step:

         Step 2:   06 18 12 42 94 55 44 67
                          ^ ^
                          J I

    When J is smaller than I, that means that we have a 'final' array:

                      [ 6 18 12 42 94 55 44 67 ]

    That we can think as two arrays:

                      [ 6 18 12 ] + [ 42 94 55 44 67 ]

    One with elements smaller than 26 and the other with elements bigger than
  26. So, we apply the same method to each one of the sub-arrays, until we
  reach subarrays with only one element. Then, if you 'sum' them all up, you'll
  get an ordered array. Check out the example program. In the example program,
  we'll choose the base number (the 26 in our previous example) as beeing the
  number halfway in the array (in the above example, it would be number 42 or
  94):

                Program QuickSort;

                Uses Crt;

                Type DataType=Array[1..40] Of Integer;

                Var Data:DataType;
                    A:Byte;
                    Compare:Word;
                    XChange:Word;

                Procedure FillData;
                Begin
                     RandSeed:=122217;        { Random seed }
                     For A:=1 To 40 Do Data[A]:=Random(1000);
                End;

                Procedure WriteArray;
                Var B:Byte;
                Begin
                     For A:=0 To 3 Do
                     Begin
                          For B:=1 To 10 Do
                          Begin
                               GotoXY(B*6+5,A+10);
                               Write(Data[A*10+B]);
                          End;
                          Writeln;
                     End;
                End;

                Procedure Sort;
                Var Flag:Boolean;
                    I,J:Integer;
                    X,N:Integer;

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

                Begin
                     SortSubArray(1,40);
                End;

                Begin
                     Clrscr;
                     FillData;
                     GotoXY(13,8);
                     Writeln('Array before QuickSort:');
                     WriteArray;
                     ReadLn;
                     Compare:=0;
                     XChange:=0;
                     Sort;
                     Clrscr;
                     GotoXY(13,8);
                     WriteLn('Array after QuickSort:');
                     WriteArray;
                     GotoXY(13,16);
                     WriteLn('Compares=',Compare);
                     GotoXY(13,17);
                     Writeln('Exchanges=',XChange);
                     ReadLn;
                End.

    QuickSort is only good when the value selected as the 'medium value' is
  good... Or else, it sucks ! :)
    So, let's compare the three sorts:

                           BubbleSort     ShellSort     QuickSort

              Compares        1404           404           498
              Exchanges        430           202            78

    See ?! Altough ShellSort does fewer comparisons, it does almost 3 times
  more exchanges, which is a heavier operation. So, QuickSort is in fact
  a lot faster.

    Sorts are very important for programs, for demos and games... You'll see
  an aplication of QuickSort in one of the articles this month (the 3d article,
  the part about vectorballs). So, see you in another article... :)


-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. The fire effect

    Welcome to this fine article written by Scorpio... I, Spellcaster, will
  make any comments between square brackets. Oh, by the way, I changed
  Scorpio's code a bit, in order to be compatible with the Mode13h Unit I'm
  giving you issue by issue... Sorry, Scorpio... I also cleaned up a bit his
  comments... But the code was made by him... :) Take the lead, Scorpio...

    Hi there. It's Scorpio again (??). This time, I am explaining you how you
  can burn down your monitor... Yep, I know... You can always dump gas on it
  and then light a match... But I want it to be a GOOD fire... one that doesn't
  actually burn your monitor, but one that actually will be so close to reality
  that'll make you feel hotter just being near the monitor... :)))))
    Enough of this... OK... There are LOTS of fires around the world that are
  FASTER, SMALLER (check #coderz 256 byte fire compo) and more beautifull than
  the one I'll show you, but mine is A LOT EASIER to understand!!! Let's hear
  (read) some stuff on the fire algorythm.

  o The palette

    To have a good-looking fire, you NEED a good palette... One that goes
    from black to orange and then to red...(256 color pal, of courz)

    [ You can also use a pallete that goes from black to white, then to yellow,
      then to orange and finally to red... Scorpio's original palette is in
      the file FIRE.PAL, but I've made another one, and put it in the
      FIRE2.PAL file... Try it out... Just change the name in the source to
      see it... ]

  o The Get/Putpixel functions

    Well... These functions will be used A LOT in the fire... Get yours
    instead of the one I have in the sources...(YEAH, I know that yourz
    can do 5.000.000.000 put/getpixel in 0.0000001 msec.. :-) )). Just
    use one that is fast enough.

  o The average-for-some-but-not-all-points-around-the-current function

    This is just a function that calculates the average of the colors on
    SOME (let's say 4) pixels around one pixel...

    OK... It seems I have a lot of explaining to do... First thing comes first
  The palette has got to have some colours that are usually on a fire, 'cos if
  you make a fire with green and blue it won't look like a fire, will it? Try
  to make a 'gradient' from black to orange and then to red that looks smooth,
  with no violent color-transitions... This way, the fire will look like a fire.
    Now, the get/putpixel functions... I have a SIMPLE MEM get/putpixel function
  'cos EVERYBODY knows it and so there won't be any misunderstanding 'cos of a
  get/putpixel... To have a nicer effect, simply cut'n'paste YOUR get/putpixel
  functions to the source and it'll work just fine...
    Last but not least, comes the average function...This is a function that
  received 4 (see explanation below) numbers and gives the average of those
  4 numbers.

    Finally... Yeah...I'm writting this at 3 a.m. and my fingers are
  starting to complain... :) How does this work??? First of all, get to
  mode 13h (??). Okay... No, I'm not joking... Now here goes a pseudo-lang
  piece of code:

  TOP-OF-LOOP:    put-some-random-colours in bottom line of fire
                  from Y:=bottom-line to upper-line do
                    from X:=left-side-of-fire to right-side-of-fire do
                      AVG:=average(point1,point2,point3,point4);
                      putpixel(X,Y-1,AVG);
                  return to TOP-OF-LOOP


    "Okay... that doesn't seem too hard to to... but WHAT are point1..point4",
  you might say... And my answer is: This is the magic of the fire algorythm...
  In the source, you'll see that I chose the point itself, the point at its
  right,the point at its left and the point above it... Why did I choose these
  4 points? These ones seem to give me fine results... Try some other ones...
    Why do I store the average ABOVE (y-1) the current pixel?? I do it so that
  the fire goes up, i.e. the fire seems to go up but fading a little (the flames
  in the bottom are brighter).
    "HEY! I checked your source and it has different positions to put the
  average of the current pixel..." Yes, it has... Please notice that the fire
  at its upper part starts to have a bit more of movement. You can see something
  like this in the sources:

                   MEM[VGA:320*y+x+random(3)-1]:=colour

    This gives a little bit of movement to the fire, and it looks G*R*E*A*T!!
  Try it yourself, and choose one that looks good to what you want to do. You
  have some more ways to do it in the source... It is V*E*R*Y well documented.

  [ What happened to modesty ???? :) ]

    It looks like it is the end of this article... As always (IM), if you have
  any problems with it, contact me at si17899@ci.uminho.pt.. If you want to
  tell me that my code sucks, email me at logname@hostname :-).


-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. Designing a text adventure - Part III

    In this issue's tutorial in text adventures, we'll delve into the amazing
  and essencial world of objects !!!
    No text adventure would be complete without 100000 objects lying around
  the gamescape! For FangLore, we'll be more modest, and we'll just have four
  objects... These are:

             -> Shovel   - Needed to knock the door down
             -> Mask     - Needed to pass the room with the gas
             -> Sword    - Needed to kill the monsters
             -> Treasure - Guess ?!

    You can include other redundanct objects, without any purpose, except to
  confuse, or maybe amuse, the player... Let's put in the game 2 redundact
  objects:
             -> Lazer gun     - A prop from a previous game
             -> A strange key - No purpose at all !

    So, lets create the objects...

    4.1. Creating objects

    To do so, we can use the program suplied (OBJGEN.PAS). It is very similar
  to the ROOMGEN.PAS program I gave with issue 8, except that this one creates
  rooms, while the OBJGEN.PAS program creates the objects.
    So, what do we need to store ?
    Well, it's name, it's position (we can move objects from a room to another,
  or be carrying it) and it's description... I'll explain the description part
  latter. So, let's define a record like this:

              Type ObjType=Record
                                 Name:String[80];
                                 Pos:Byte;
                                 Desc:Array[1..5] Of String[80];
                           End;

    And let's define the number of objects in the game:

              Const NumberObjs=6;

    And, finally, let's define the array that will store the objects:

              Var Objects:Array[1..NumberObjs] of ObjType;

    So, after we've defined these data structures and made a file called
  OBJ.DAT (with the OBJGEN program) with the data, let's read the data, with
  a procedure similar in every respect with the ReadRoomData procedure that
  I gave in the first article of this series:

         Procedure ReadObjData;     { Read from the disk the object data }
         Var F:Text;
             A,B:Byte;
             Flag:Boolean;
         Begin
              { Prepares the text file for accessing }
              Assign(F,'Obj.Dat');
              Reset(F);
              { For every object in the game }
              For A:=1 To NumberObjs Do
              Begin
                   { Read the name of the objects }
                   ReadLn(F,Objects[A].Name);
                   { Read the initial position of the objects }
                   ReadLn(F,Objects[A].Pos);
                   { Clear the object's description }
                   For B:=1 To 5 Do Objects[A].Desc[B]:='';
                   { Read the description of the room }
                   Flag:=True;
                   B:=1;
                   While Flag Do
                   Begin
                        ReadLn(F,Objects[A].Desc[B]);
                        If (B=5) Or (Objects[A].Desc[B]='*') Then Flag:=False;
                        Inc(B);
                   End;
              End;
              Close(F);
         End;

    Just a note... If the POS field of the any object is equal to 0, that means
  that we are carrying the object, and if it is equal to 255, that means that
  the object is hidden (case of the gas mask, that only appears if the oven is
  open).

    4.2. Seeing the objects

    An object is worthless, unless you can use it... And you can only use it
  if you can see it, to know it is there... So, there are two 'kinds' of
  objects you can see... Objects you own and objects that are lying around...
  A good game has different descriptions for both the 'kinds', but Fanglore
  just does the same thing in either case: spit out the description.
    So, there is a basic command that is equal in _EVERY_ text adventure in the
  world: EXAMINE !
    The examine command has a parameter following, that is, it accepts (and
  requires) that you type another thing... That thing is the thing you want
  to examine... For example, if you wanted to examine the sword, you would
  type:
                                EXAMINE SWORD

    So, EXAMINE is the command, and SWORD is the parameter. But notice that
  you aren't limited to just examine objects... You can examine anything in
  the game... For example, the oven in the kitchen... But I'll talk about that
  later, in another article of this serie...
    Also, notice that you can only examine objects that you have or that are
  in the same room as you... So, let's check out the EXAMINE procedure:

         Procedure Examine(Param:String);
         Var Flag:Boolean;
             A,B:Byte;
         Begin
              Flag:=True;
              { ***************************************************** }
              { Here we'll include code for things like the oven, etc }
              { ***************************************************** }
              { Search the object in the object list }
              A:=0;
              Repeat
                   Inc(A);
                   If Objects[A].Name=Param Then Flag:=True;
              Until Flag Or (A>NumberObjs);
              If Flag Then
              Begin
                   { The object exists }
                   { Check if it is in the room or in your possession }
                   If (Objects[A].Pos=0) Or (Objects[A].Pos=CurrentRoom) Then
                   Begin
                        { The object is 'visible'... Write description }
                        Writeln;
                        B:=1;
                        TextColor(Yellow);
                        While (B<6) And (Objects[A].Desc[B]<>'*') Do
                        Begin
                             Writeln(Objects[A].Desc[B]);
                             Inc(B);
                        End;
                        Writeln;
                   End
                   Else
                   Begin
                        { The object exists, but it's not visible }
                        Writeln;
                        TextColor(Yellow);
                        WriteLn('I can''t see the ',Param,' here...');
                        WriteLn;
                   End;
              End
              Else
              Begin
                   { The specified parameter doesn't represent any existing }
                   { object...                                              }
                   WriteLn;
                   TextColor(Yellow);
                   WriteLn('Sorry, but I don''t even know what is a ',Param);
                   WriteLn;
              End;
         End;

    Of course you'll have to do the routine that calls this procedure... You
  must put it (as usual with all commands) in the Play procedure. The routine
  is like this:

                If Parsed[1]='EXAMINE' Then
                Begin
                     Valid:=True;
                     Examine(Parsed[2]);
                End;

    Simple, isn't it... But this is not over... If you remember what I told
  you about parsing, you know that the phrases are splitted in words. But,
  Gas Mask has two words in it's name... So, the program can't cope with
  objects with composite names. How can we make him accept it ?
  Well, we have to join the words again... Like this:

                If Parsed[1]='EXAMINE' Then
                Begin
                     Valid:=True;
                     { Join the words again }
                     D:=3;
                     E:=Parsed[2];
                     While Parsed[D]<>'' Do
                     Begin
                          E:=E+' '+Parsed[D];
                          Inc(D);
                     End;
                     Examine(E);
                End;

    You must define E as a string and D as a Byte types of variables.
    Altough this works with commands that only require one parammeter, this
  fails with other commands were multiple parameters are required... So,
  general rule, don't use objects with names that have spaces...

    But this section isn't finished yet... We still have to know what objects
  are in the room, and what objects do you carry... But I'll leave that for
  tomorrow, because I had 2 hours sleep last night... And I'm VERY tired...
    Good night... Zzzzzzzzzzzzzzzzzzzzzzzz..........

    Ok... I'm back ! :) Ready for more... So... The problem this moment is
  knowing what are the objects we can interact. That's easy. When you enter
  a room, you make the program print out a list of visible objects. To do
  so, just add this code to the Look procedure:

               TextColor(LightCyan);
               Writeln('Visible objects:');
               Flag:=False;
               For A:=1 To NumberObjs Do If Objects[A].Pos=RoomNumber Then
               Begin
                    Writeln('         ',Objects[A].Name);
                    Flag:=True;
               End;
               If Flag=False Then Writeln('          None');

    Don't forget do define the Flag variable as a Boolean. It is used to know
  if there is any objects in the room... If it is false after the For loop
  ends, that means that there aren't any objects in the room, so the program
  writes down that it doesn't see any objects...

    To know what objects you are carrying, you can use second best-known
  command in text adventures... The INVENTORY command !
    To do the inventory command, you just check all the objects in the game.
    If their position (the POS field) is equal to zero, then you are carrying
  it... Code:

             Procedure Inventory;
             Var A:Byte;
                 Flag:Boolean;
             Begin
                  Flag:=False;
                  TextColor(LightBlue);
                  WriteLn;
                  WriteLn('Objects carried:');
                  For A:=1 To NumberObjs Do If Objects[A].Pos=0 Then
                  Begin
                       WriteLn('                ',Objects[A].Name);
                       Flag:=True;
                  End;
                  If Flag=False Then Writeln('                 None');
                  WriteLn;
             End;

    As usual, you have to add some code to the Play procedure, that calls the
  above procedure:

                  If Parsed[1]='INVENTORY' Then
                  Begin
                       Valid:=True;
                       Inventory;
                  End;

    So, now you look at objects... You just have to do some interaction...

    4.3. Interacting with objects

    An object is useless unless you can you interact somehow with it... There
  are three common interactions you can do with objects: Get, Drop and Use.
    Let's start with the easy ones: Get and Drop
    To get an object, all the program has to do is to set the POS field of
  the desired object to zero... After we verify if the object exists and if
  it is in the room, and if it isn't in your position already.
    To drop an object, you just have have to verify if you have that object
  and set the POS field of the object to the number of the room you're on...
    Coding wise:

                Procedure Get(O:String);
                Var A:Byte;
                    Flag:Boolean;
                Begin
                     Flag:=False;
                     A:=0;
                     Repeat
                           Inc(A);
                           If O=Objects[A].Name Then Flag:=True;
                     Until (A>NumberObjs) Or (Flag=True);
                     If Flag=True Then
                     Begin
                          { The object exists }
                          If Objects[A].Pos=CurrentRoom Then
                          Begin
                               { The object is in the current room }
                               { Get the object }
                               Objects[A].Pos:=0;
                               TextColor(LightCyan);
                               WriteLn;
                               WriteLn('You get the ',O);
                               WriteLn;
                          End
                          Else
                          Begin
                               If Objects[A].Pos=0 Then
                               Begin
                                    { The object is already in the possession }
                                    { of the player...                        }
                                    TextColor(LightCyan);
                                    WriteLn;
                                    WriteLn('You already have the ',O);
                                    WriteLn;
                               End
                               Else
                               Begin
                                    WriteLn('You don''t see the ',O);
                                    WriteLn;
                               End;
                          End;
                     End
                     Else
                     Begin
                          { The object doesn't exist }
                          WriteLn;
                          TextColor(LightRed);
                          Writeln('What are you talking about ?');
                          Writeln;
                     End;
                End;

    As usual, you have to put a call to this procedure in the Play procedure...
  This command has the same problem as the examine command, so we must do the
  same thing that you did in the examine command: join the parsed array in
  a string that has the name of the object:

                If Parsed[1]='GET' Then
                Begin
                     Valid:=True;
                     { Join the words again }
                     D:=3;
                     E:=Parsed[2];
                     While Parsed[D]<>'' Do
                     Begin
                          E:=E+' '+Parsed[D];
                          Inc(D);
                     End;
                     Get(E);
                End;

    The drop procedure is similar, altough is a bit simpler:

                Procedure Drop(O:String);
                Var A:Byte;
                    Flag:Boolean;
                Begin
                     Flag:=False;
                     A:=0;
                     Repeat
                           Inc(A);
                           If (O=Upper(Objects[A].Name)) And
                              (Objects[A].Pos=0) Then Flag:=True;
                     Until (A>NumberObjs) Or (Flag=True);
                     If Flag=True Then
                     Begin
                          { The object is in the player's possession }
                          Objects[A].Pos:=CurrentRoom;
                          TextColor(LightCyan);
                          WriteLn;
                          WriteLn('You drop the ',O);
                          WriteLn;
                     End
                     Else
                     Begin
                          { The object doesn't exist or it isn't in the }
                          { player's possession... }
                          TextColor(LightRed);
                          WriteLn;
                          WriteLn('You don''t have the ',O);
                          WriteLn;
                     End;
                End;

    Again, add the following code to the Play procedure... You already know
  what it does:

                If Parsed[1]='DROP' Then
                Begin
                     Valid:=True;
                     { Join the words again }
                     D:=3;
                     E:=Parsed[2];
                     While Parsed[D]<>'' Do
                     Begin
                          E:=E+' '+Parsed[D];
                          Inc(D);
                     End;
                     Drop(E);
                End;

    So, Get and Drop are ready... Now, let's go to the third 'class' of
  object manipulation commands: The use commands. The use commands are lots
  of commands, but they all do the same thing: Manipulate the object. For
  example... You can use the Gas Mask by typing USE MASK... But let's do things
  harder... For example, USE MASK can be thought as breaking it! You are
  using it... So, let's do the WEAR command... :))) It is harder that way for
  the player know what to do... The uses commands are just lines and lines of
  IFs, because every object has a different effect. Some types of commands
  require more than one parameter. For example, to use the sword, you must type
  USE SWORD ON MONSTER...
    In FangLore we have two use commands... The USE command and the WEAR
  command. The wear command is only used for the gas mask... The use command
  is for the others. The theory is simple... According to the object you
  select, you must do a block of instruction that define what happens.
  For example, when you use the shovel with the door in room 20, the door
  to FangLore opens... Code for the WEAR command... This is so small and
  simple that we'll include it in the Play procedure.

                    If Parsed[1]='WEAR' Then
                    Begin
                         { Join the words again }
                         D:=3;
                         E:=Parsed[2];
                         While Parsed[D]<>'' Do
                         Begin
                              E:=E+' '+Parsed[D];
                              Inc(D);
                         End;
                         If E='GAS MASK' Then
                         Begin
                              Valid:=True;
                              { Check if the player has the gas mask     }
                              { We know that the mask is object number 2 }
                              If Objects[2].Pos=0 Then
                              Begin
                                   Mask:=True;
                                   TextColor(LightCyan);
                                   WriteLn;
                                   WriteLn('You wear the gas mask...');
                                   WriteLn;
                              End
                              Else
                              Begin
                                   TextColor(LightRed);
                                   WriteLn;
                                   WriteLn('You don''t have the gas mask.');
                                   WriteLn;
                              End;
                         End;
                    End;

    The Mask variable is a global variable of type Boolean. It should be
  initialized to False in the Init procedure. If it is true, that means that
  we are using it. We'll see later how that can be used...

    Now, let's move on to the USE command... There are the following
  possibilities:

                USE SHOVEL ON DOOR
                USE SWORD ON MONSTER

    The parser should eliminate the ON prenom... Do that with the EliminPrenoms
  procedure (see details in the first article of this serie).
    So, let's do the USE procedure:

                 Procedure Use(Arg:ParsedType);
                 Var A:Byte;
                     Flag:Boolean;
                 Begin
                      Flag:=False;
                      A:=0;
                      Repeat
                            Inc(A);
                            If (Arg[2]=Upper(Objects[A].Name)) And
                               (Objects[A].Pos=0) Then Flag:=True;
                      Until (A>NumberObjs) Or (Flag=True);
                      If Flag=True Then
                      Begin
                           { The object is "usable" }
                           Flag:=False;
                           { Check if player want to use the shovel }
                           If Arg[2]='SHOVEL' Then
                           Begin
                                Flag:=True;
                                { Check if it is used with the door }
                                If Arg[3]='DOOR' Then
                                Begin
                                     { Check if the player is in the proper }
                                     { location...                          }
                                     If CurrentRoom=20 Then
                                     Begin
                                          { The player is in the right place }
                                          { Open passage between the garden  }
                                          { and the mansion...               }
                                          Rooms[20].North:=15;
                                          TextColor(LightCyan);
                                          WriteLn;
                                          WriteLn('You knock the door down...');
                                          WriteLn;
                                     End
                                     Else
                                     Begin
                                          { The player is someplace else }
                                          TextColor(LightRed);
                                          WriteLn;
                                          WriteLn('I don''t see a door...');
                                          WriteLn;
                                     End;
                                End
                                Else
                                Begin
                                     { The player didn't used the shovel with }
                                     { the door...                            }
                                     TextColor(LightRed);
                                     WriteLn;
                                     WriteLn('Use it with what ?!');
                                     WriteLn;
                                End;
                           End;
                           If Arg[2]='SWORD' Then
                           Begin
                                Flag:=True;
                                { I'll include the code here in a future  }
                                { issue, when I'll talk about monsters... }
                           End;
                      End;
                      If Flag=False Then
                      Begin
                           { No "legal" objects were used }
                           TextColor(LightRed);
                           WriteLn;
                           WriteLn('Can't use that...');
                           WriteLn;
                      End;
                 End;

    We've must passe the entire parsed array as a parameter to the procedure,
  because objects like the shovel and the sword need another 'object' to work
  with... Remember that you are not limited to USE objects... You can use,
  for instance, a lever... It is not an object because you can't carry it
  around, but you can use it... But the code must be changed slightly... In
  the case of FangLore, we can only use objects... :)
    Notice also that in the case of the USE command, we can't use the trick
  we did with the Examine, Get, Drop and Wear commands, the trick of joining
  the parsed array together to form the name of the objects with more than
  one word (i.e. GAS MASK)... So, it is good to use as the name of the objects
  single words, or else lot's of manipulations are needed... In the case of
  FangLore there isn't a problem, but in other games it might be...
    Again, you must add the following code to the Play procedure:

                    If Parsed[1]='USE' Then
                    Begin
                         Valid:=True;
                         Use(Parsed);
                    End;

    So, this article is finished... Next article of this serie will cover
  monsters and special rooms... 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. The wonderfull world of scrolies !

    5.1. Introduction

    Welcome to another article by your good (??) friend Spellcaster...
    This one is about scrollers... Or scrollies... :)
    So, what are they ?
    Scrollers are simply a piece of text that scrolls by the screen... Almost
  every demo in the world has at least one... There are billions of types of
  scrollers. They all revolve around the same principle... Memory...
    Scrollers are simple alterations of memory... If you move the memory the
  right way, you achieve the effect...
    Graphical scrollers are very similar to text scrollers... The only
  difference is that in text scrollers the text appears a character at a time,
  and in graphical scrollers, the text appears one collumn at a time... For
  example, if the font you are using is 16 pixels width, a character appears
  after 16 calls to the procedure that scrolls... Got it ?

    This article makes heavy use of the Font unit that is supplied with this
  issue... All the theory behind it was explained in last issue. Read the
  source code for more details... I've also included a color font,but is
  fairly limited... I hate drawing fonts, so I used one I've made for an
  old game of mine.
    Just remember one thing about the routine... Because of a small error in
  the conception of the font, the position in the array is not denoted as
  (x,y) but (y,x)... That is, if you want to know the color of the first
  pixel of the second line of character 'A', and store that value in variable
  C, you'll have to do:

                    C:=Font^['A',1,2]

    Sorry about the confusing explanation...

    There is one routine that will be used in some of the example programs...
  It is a procedure that draws a column of a character in the right edge of
  the screen... All the scrollers in the examples scroll from right to left,
  because it's easier to read that way. So, here's the code for that routine:

                Procedure PutVertLine(C:Char;Index,Y:Byte;Where:Word);
                Var B:Byte;
                Begin
                     For B:=1 To 16 Do
                       PutPixel(319,Y+B,Font^[C,B,Index],Where);
                End;

    Y is the line in which the procedure starts to draw the character...
    Also, all the scrollers start in the middle of the screen...
    So let's do it...

    5.2. Simple scroller

    The simple scroller is so simple I shouldn't waste time describing it... :)
    The ideia is just this: You write part of a character (with the PutVertLine
  procedure given above) and you scroll the screen to the left. Code:

                Program Simple_Scroller;

                Uses Mode13h,CFont,Crt;

                Var Colors:RgbList;
                    Txt:String;
                    Ch:Byte;
                    A:Byte;

                Procedure PutVertLine(C:Char;Index,Y:Byte;Where:Word);
                Var B:Byte;
                Begin
                     For B:=1 To 16 Do
                       PutPixel(319,Y+B,Font^[C,B,Index],Where);
                End;

                Procedure ScrollLeft;
                Var B:Byte;
                Begin
                     WaitVbl;
                     For B:=90 To 110 Do
                     Move(Mem[VGA:(320*B+1)],Mem[VGA:(320*B)],319);
                End;

                Begin
                     { Initialization }
                     InitGraph;
                     LoadFont('Font.Fnt');
                     LoadPal('Font.Pal',Colors);
                     SetPalette(Colors);
                     Txt:='SPELLCASTER CODED THIS  ';
                     Txt:=Txt+'THIS SUCKS  ';
                     Txt:=Txt+'SO, SPELLCASTER SUCKS !!';
                     Txt:=Txt+'      ';
                     Ch:=1;
                     { Scrolling }
                     Repeat
                           For A:=1 To 16 Do
                           Begin
                                PutVertLine(Txt[Ch],A,92,VGA);
                                ScrollLeft;
                           End;
                           Inc(Ch);
                           If Ch>Length(Txt) Then Ch:=1;
                     Until Keypressed;
                     { Shutting down }
                     CloseGraph;
                End.

    This is simple... The ScrollLeft procedure was taught in the graphics
  section of issue 7 of 'The Mag'... The only difference is that this procedure
  only scrolls lines 90 through 110... It does that to speed up the program.
  This is a fairly quick program, so we didn't use any virtual screens.
  This should be easy to understand. We write do the screen the column indexed
  by variable A, then we increment it (the FOR cicle), then we scroll the
  screen to the left. We do this 16 times, and then we go on to the next
  character... When we finish with the string, we start all over again...

    5.3. Sine scroller

    This is when the thing becomes tougher... What's a sine scroller ?
    It's a scroller than goes up and down, like a sine wave... If you don't
  know what a sine wave is, go to a 8th grade book... It should come there.
    There are lot's of ways to do this, and the one I'm thinking about is a
  little bit complicated. I'll draw a bit of ASCII:

     |
     |AAA                BBBBBB                CCCCCC                D....
     |   AA            BB      BB            CC      CD            DD ....
     |     A          B          B          C          D          D   ....
     |      AA      AA            BB      CC            DD      DD    ....
     |        AAAAAA                BBCCCC                DDDDDD      ....
     |

     ^ That is the left edge of the screen. The 'A' character represents a
       character's pixels, the 'B' character represents another character's
       pixels, etc...
       Only the top line of a character is shown, for simplicity stake...
       That is the first frame of movement... Let's see the second and third
       frame of movement:

     |
     |AAA                BBBBBB                CCCCCC                E....
     |   AA            BB      BB            CC      DD            DD ....
     |     A          B          B          C          D          D   ....
     |      AA      AB            BB      CC            DD      DD    ....
     |        AAAAAA                BCCCCC                DDDDDD      ....
     |

     |
     |AAA                BBBBBB                CCCCCD                E....
     |   AA            BB      BB            CC      DD            DE ....
     |     A          B          B          C          D          D   ....
     |      AA      BB            BB      CC            DD      DD    ....
     |        AAAAAA                CCCCCC                DDDDDD      ....
     |

    Notice that the absolute Y values are the same for every X in the screen,
  independent of the character that occupies that position...
    So, the ideia is to draw a part of the string in the screen, from X=0 to
  X=319, changing the Y according to a sine calculation, that is a function
  of X... This is harder than it looks. For example, in the first frame we
  have the 'plotting' of the string starting in character 'A' (presumably the
  first character of the string). Well, in the second frame, it also starts
  with character 'A'... So, what's the difference ?
    Well, in the second frame, the 'plotting' starts in the character's
  second column, and in the third frame,  it starts on the third column, and
  so forth, until it arrives to the 16th column... There it switches to the
  first column of the next character in the string...
    The code looks awfully messy, but it works fine, and if you look carefully
  at it, it will be as clear as a day in the Carabeean... :)
    Notice that there is no need now for the ScrollLeft procedure, and that
  the PutVirtLine procedure was changed in order to enable the us to specify
  in which X coordinate to draw the column...

                Program Sine_Scroller;

                Uses Mode13h,CFont,Crt;

                Var Colors:RgbList;
                    Txt:String;
                    Ch,Ch1:Byte;
                    A,Y,Index:Byte;
                    X:Integer;

                Procedure PutVertLine(C:Char;Index,X,Y,Where:Word);
                Var B:Byte;
                Begin
                     For B:=1 To 16 Do
                       PutPixel(X,Y+B,Font^[C,B,Index],Where);
                End;

                Begin
                     { Initialization }
                     InitGraph;
                     InitTables;
                     LoadFont('Font.Fnt');
                     LoadPal('Font.Pal',Colors);
                     SetPalette(Colors);
                     Txt:='SPELLCASTER CODED THIS  ';
                     Txt:=Txt+'THIS SUCKS  ';
                     Txt:=Txt+'SO, SPELLCASTER SUCKS !!';
                     Txt:=Txt+'      ';
                     Ch:=1;
                     { Scrolling }
                     Repeat
                           For A:=1 To 16 Do
                           Begin
                                Index:=A;
                                X:=0;
                                Ch1:=Ch;
                                WaitVbl;
                                Repeat
                                      Repeat
                                            Y:=100+Trunc(20*Sines^[X]);
                                            PutVertLine(Txt[Ch1],Index,X,Y,VGA);
                                            Inc(Index);
                                            Inc(X);
                                      Until (Index>16) Or (X>319);
                                      Index:=1;
                                      Inc(Ch1);
                                      If Ch1>Length(Txt) Then Ch1:=0;
                                Until X>319;
                           End;
                           Inc(Ch);
                     Until Keypressed;
                     { Shutting down }
                     CloseGraph;
                End.

    This looks bad, but after some days looking at it, it will be simple to
  understand... :) Any doubts, mail me... :)
    This could be speeded up a bit by using a precalculated table for the Y
  values... We are already using a pregenerated array for the sines. This
  could also be speeded up by using assembler... But I think you can manage
  that, if you really want... The next issue, I'll probably put more ASM code
  in the graphics section...

    5.4. Mirror and water scroller

    Well, these two are fairly easy... Why ? Because they are effects upon
  the scrollers... So, you can have a sine-water scroller... Or a normal-water
  scroller. For the examples, I'll use the simple scroller as a basis...

    First, let's do the mirror scroller... That's the simpler one... The ideia
  is to flip what is scrolling around the X axis, that is, draw the image
  upside down... You could to this by drawing the scroller again, but this time
  with Y coordinates flipped... But that would be too slow to combine with a
  sine scroller... So, what I do is to grab the image that is already drawn
  and copy it below, but inverted... To do so, here's the DoMirror procedure:

                Procedure DoMirror;
                Var B:Byte;
                Begin
                     WaitVbl;
                     For B:=0 To 20 Do
                     Move(Mem[VGA:(320*(90+B))],Mem[VGA:(320*(130-B))],320);
                End;

    It copies the 20 lines ranging from 90 to 110 (where the original scroller
  is) and puts it on the 20 lines ranging from 130 to 110 (respectivelly).
    You can addapt this procedure to mirror any effect... A nice touch is to
  dim a bit the inverted image, but I'll leave that to a future issue...
    Don't forget to call this procedure after you draw every frame. Here is a
  complete example:

                Program Mirror_Scroller;

                Uses Mode13h,CFont,Crt;

                Var Colors:RgbList;
                    Txt:String;
                    Ch:Byte;
                    A:Byte;

                Procedure PutVertLine(C:Char;Index,Y:Byte;Where:Word);
                Var B:Byte;
                Begin
                     For B:=1 To 16 Do
                       PutPixel(319,Y+B,Font^[C,B,Index],Where);
                End;

                Procedure ScrollLeft;
                Var B:Byte;
                Begin
                     WaitVbl;
                     For B:=90 To 110 Do
                     Move(Mem[VGA:(320*B+1)],Mem[VGA:(320*B)],319);
                End;

                Procedure DoMirror;
                Var B:Byte;
                Begin
                     WaitVbl;
                     For B:=0 To 20 Do
                     Move(Mem[VGA:(320*(90+B))],Mem[VGA:(320*(130-B))],320);
                End;

                Begin
                     { Initialization }
                     InitGraph;
                     LoadFont('Font.Fnt');
                     LoadPal('Font.Pal',Colors);
                     SetPalette(Colors);
                     Txt:='SPELLCASTER CODED THIS  ';
                     Txt:=Txt+'THIS SUCKS  ';
                     Txt:=Txt+'SO, SPELLCASTER SUCKS !!';
                     Txt:=Txt+'      ';
                     Ch:=1;
                     { Scrolling }
                     Repeat
                           For A:=1 To 16 Do
                           Begin
                                PutVertLine(Txt[Ch],A,92,VGA);
                                DoMirror;
                                ScrollLeft;
                           End;
                           Inc(Ch);
                           If Ch>Length(Txt) Then Ch:=1;
                     Until Keypressed;
                     { Shutting down }
                     CloseGraph;
                End.

    So, here's for the mirror scroller... Now, let's move to the water
  scroller... This is a variation of the mirror scroller... The only diference
  is that the water scroller has some 'ripples' in the inverted mirror...
  How can we do that ? Simple... Instead of just copying the scroller image to
  other part of the screen, we shake it a bit, by using a random value...
    So, the DoMirror procedure is replaced by teh DoWater procedure:

                Procedure DoWater;
                Var B:Byte;
                Begin
                     WaitVbl;
                     For B:=0 To 20 Do
                     Move(Mem[VGA:(320*(90+B))],
                          Mem[VGA:(320*(130-B)+Random(3))],316);
                End;

    The rest remains the same... Look at the complete program:

                Program Water_Scroller;

                Uses Mode13h,CFont,Crt;

                Var Colors:RgbList;
                    Txt:String;
                    Ch:Byte;
                    A:Byte;

                Procedure PutVertLine(C:Char;Index,Y:Byte;Where:Word);
                Var B:Byte;
                Begin
                     For B:=1 To 16 Do
                       PutPixel(319,Y+B,Font^[C,B,Index],Where);
                End;

                Procedure ScrollLeft;
                Var B:Byte;
                Begin
                     WaitVbl;
                     For B:=90 To 110 Do
                     Move(Mem[VGA:(320*B+1)],Mem[VGA:(320*B)],319);
                End;

                Procedure DoWater;
                Var B:Byte;
                Begin
                     WaitVbl;
                     For B:=0 To 20 Do
                     Move(Mem[VGA:(320*(90+B))],
                          Mem[VGA:(320*(130-B)+Random(3))],316);
                End;

                Begin
                     { Initialization }
                     InitGraph;
                     LoadFont('Font.Fnt');
                     LoadPal('Font.Pal',Colors);
                     SetPalette(Colors);
                     Txt:='SPELLCASTER CODED THIS  ';
                     Txt:=Txt+'THIS SUCKS  ';
                     Txt:=Txt+'SO, SPELLCASTER SUCKS !!';
                     Txt:=Txt+'      ';
                     Ch:=1;
                     { Scrolling }
                     Repeat
                           For A:=1 To 16 Do
                           Begin
                                PutVertLine(Txt[Ch],A,92,VGA);
                                DoWater;
                                ScrollLeft;
                           End;
                           Inc(Ch);
                           If Ch>Length(Txt) Then Ch:=1;
                     Until Keypressed;
                     { Shutting down }
                     CloseGraph;
                End.

    Instead of using random values, you can use some sort of pregenerated
  table or sine wave to give a much smoother look...
    Ok, this article is finally finished... :)
    Scrollers are a pain in the ass in a lot of demos, but if you add some
  cool effects, you can make them cool things... So try to do a 3d-texture-
  -and-bump-mapped-phong-shaded-faster-than-light scroller... :)))


-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. First steps into virtual reality

    Welcome to the first article about 3d on 'The Mag'... Nowadays, everyone
  is excited with virtual reality and other stuff like that. Everytime I
  read the rec.games.programmer, I read tons of articles that say 'I wanna make
  a DOOM like game'... And in comp.sys.ibm.pc.demos, I read articles that say
  'I wanna do a texture-mapped phong-shaded duck rotating in a voxel
  landscape !'... So, 3d is everywhere ! But let's move on...

    Let's think about the principle behind 3d. The ideia is to have a model of
  some kind and convert to an image. A model is a set of data that describe the
  virtual 'world'.
    As everyone knows, 3d stands for three-dimensions... But the computer screen
  only has 2 dimensions, so, every object in the computer can and is identified
  with 2 coordinates, X and Y, while everything in the real is described with
  3 coordinates, X, Y and Z... It's fairly easy to estabilish the relation
  Z/Depth... But that's not always true... You can make Y your depth, or even
  an arbitrary axis... But it is convencioned that X is horizontal, Y is
  vertical and Z is the depth... So let's play with that...
    So, the main problem with 3d is to transform (x,y,z) points, in (x,y)
  points... So, how can we do that ?
    It's easier than you think... As you know the further away is an object,
  the closer to the horizon's center it is... So, the greater the Z coordinate,
  the closer it will be to the center of the screen. If we make all the
  coordinates relative to the center of the screen, the bigger the Z, the
  smaller will be X and Y... What's the operation that does this ? Division...
  So, given the (X,Y,Z) that describe a point in 3d space, the transformed
  coordinates (Xt,Yt) will be:

                 Xt= X*256                     Yt= Y*256
                     -----                         -----
                       Z                             Z

    Why is that multiplication by 256 there ? Well, that's the camera factor.
  If we don't multiply, all would look pretty ugly, because the number would
  be too small... Why did I choose 256 ? For two reasons... First, that number
  looks good (if you use other numbers, you can even get a fish-eyed lens
  effect). Second, multiplication by 256 can be achieve by a lightning-fast
  shift-left operation, if we shift 8 bits... So, the procedure that converts
  3 dimensions in 2 dimensions is the following:

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

    We use reals for coordinates because of precision of rotations...
    There are problem with this routine if the Z is 0... But you don't need
  to compute points that have the Z equal to 0, because they are on top of
  the camera. Don't forget that the X,Y and Z are relative to the center of
  the virtual 'world', that is, relative to the camera. That 160 and 100 on
  the procedure are the center of the screen.
    When you plot a point in the screen, don't forget to check if the point
  lies inside the screen! I forgot that when I was coding an example program
  and it was giving me an headache... Here is a routine that plots a 3d point
  on the screen:

               Procedure Plot3d(X,Y,Z:Real;Color:Byte;Where:Word);
               Var Xt,Yt:Integer;
               Begin
                    Xt:=160+Trunc(X*256/Z);
                    If (Xt<0) Or (Xt>319) Then Exit;
                    Xt:=100+Trunc(Y*256/Z);
                    If (Yt<0) Or (Yt>199) Then Exit;
                    Mem[Where:(320*Yt+Xt)]:=Color;
               End;

    Exit is a nifty command that makes Pascal exit the block it is in... If
  you do Exit inside a function or a procedure, the program goes back to where
  that function or procedure was called... If you do Exit in the main program,
  the program will end imediately. Why do we calculate the Xt and check if
  it is in the screen ? Because it is faster, if the point is outside the
  screen... If the point is outside the screen, there's no use for the Yt
  coordinate...
    Now that we've already drawn a 3d point, we can move on to the next
  section...

    6.1. Wireframe graphics

    What are wireframe graphics ?
    Well, wireframe graphics are a kind of 3d graphics only made of points
  and lines connecting points, without any kind of shading, texturing or
  hidden-surface removal...
    So, to do wireframe graphics, we need a kind of structure that allows us
  to store the point's data and the information of what points are connect to
  each others... In our example, we'll use a static structure, but you could
  use a dinamic one... First, let's define the maximum number of points and
  lines an object can have:

                      Const MaxPoints=30;
                            MaxLines=30;

    Now, let's define the structures necessary:

                   Type Point3d=Record
                                      X,Y,Z:Real;
                                End;

                        Object3d=Record
                                       NumberPoints:Byte;
                                       NumberLines:Byte;
                                       Pt:Array[1..MaxPoints] Of Point3d;
                                       Lines:Array[1..MaxLines,1..2] Of Byte;
                                 End;

    So, now we need to create an object:

                       Var Car:Object3d;

    And afterwards, we need to load some data into the object... To do so, I
  made a small utility that enables you to type in 3d coordinates and to type
  which points connect, and store that data in a file. Then, you can read it
  back to Object3d variable with the LoadData procedure... The code for that
  procedure is below... The utility includes the source, so that you can change
  it anyway you see fit. The program is called 3DGEN.PAS. Notice that the
  program only uses integer numbers, integer's that are loaded into reals.

            Procedure Load3d(Filename:String;Var Obj:Object3d);
            Var F:Text;
                A:Byte;
            Begin
                 Assign(F,Filename);
                 Reset(F);
                 ReadLn(F,Obj.NumberPoints);
                 ReadLn(F,Obj.NumberLines);
                 For A:=1 To Obj.NumberPoints Do
                   ReadLn(F,Obj.Pt[A].X,Obj.Pt[A].Y,Obj.Pt[A].Z);
                 For A:=1 To Obj.NumberLines Do
                   ReadLn(F,Obj.Lines[A,1],Obj.Lines[A,2]);
                 Close(F);
            End;

    Now, all we have to do is to join the points that are to be joined.
    Let's check out how this is done:

               Procedure Draw3d(Obj:Object3d;XOff,YOff,ZOff:Integer;
                                Color:Byte;Where:Word);
               Var A:Byte;
                   Pt1,Pt2:Byte;
                   X1,Y1,X2,Y2:Integer;
               Begin
                    For A:=1 To Obj.NumberLines Do
                    Begin
                         Pt1:=Obj.Lines[A,1];
                         Pt2:=Obj.Lines[A,2];
                         Conv3d(Obj.Pt[Pt1].X+XOff,
                                Obj.Pt[Pt1].Y+YOff,
                                Obj.Pt[Pt1].Z+ZOff,
                                X1,Y1);
                         Conv3d(Obj.Pt[Pt2].X+XOff,
                                Obj.Pt[Pt2].Y+YOff,
                                Obj.Pt[Pt2].Z+ZOff,
                                X2,Y2);
                         LineC(X1,Y1,X2,Y2,Color,Where);
                    End;
               End;

    So, what's the deal here ? Well, simple... You have two lists in a
  structure. One has the coordinates of all the points in the object, and the
  other has the list the lines there are, stored by number of the point. For
  example, if
                Ln[1,1]:=1;
                Ln[1,2]:=5;

    That would mean that point 1 would connect to point 5... So, we traverse
  all the Lines array and connect the points to form the image... The XOff,
  YOff and ZOff variables are the offsets of the object... The object can be
  drawn in any region of 3d space. The LineC procedure that is called is a
  clipped version of the Line procedure I gave you in issue 4 of 'The Mag'.
  Check it's source in the Mode13h unit... The only diference between LineC
  and Line is that LineC checks if the point is within the borders of the
  screen.
    Let's see the complete example:

               Program WireFrame;

               Uses Mode13h,Crt;

               Const MaxPoints=30;
                     MaxLines=30;

               Type Point3d=Record
                                  X,Y,Z:Real;
                            End;

                    Object3d=Record
                                   NumberPoints:Byte;
                                   NumberLines:Byte;
                                   Pt:Array[1..MaxPoints] Of Point3d;
                                   Lines:Array[1..MaxLines,1..2] Of Byte;
                             End;

               Var A:Integer;
                   Car:Object3d;
                   D:Char;

               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 Load3d(Filename:String;Var Obj:Object3d);
               Var F:Text;
                   A:Byte;
               Begin
                    Assign(F,Filename);
                    Reset(F);
                    ReadLn(F,Obj.NumberPoints);
                    ReadLn(F,Obj.NumberLines);
                    For A:=1 To Obj.NumberPoints Do
                      ReadLn(F,Obj.Pt[A].X,Obj.Pt[A].Y,Obj.Pt[A].Z);
                    For A:=1 To Obj.NumberLines Do
                      ReadLn(F,Obj.Lines[A,1],Obj.Lines[A,2]);
                    Close(F);
               End;

               Procedure Draw3d(Obj:Object3d;XOff,YOff,ZOff:Integer;
                                Color:Byte;Where:Word);
               Var A:Byte;
                   Pt1,Pt2:Byte;
                   X1,Y1,X2,Y2:Integer;
               Begin
                    For A:=1 To Obj.NumberLines Do
                    Begin
                         Pt1:=Obj.Lines[A,1];
                         Pt2:=Obj.Lines[A,2];
                         Conv3d(Obj.Pt[Pt1].X+XOff,
                                Obj.Pt[Pt1].Y+YOff,
                                Obj.Pt[Pt1].Z+ZOff,
                                X1,Y1);
                         Conv3d(Obj.Pt[Pt2].X+XOff,
                                Obj.Pt[Pt2].Y+YOff,
                                Obj.Pt[Pt2].Z+ZOff,
                                X2,Y2);
                         LineC(X1,Y1,X2,Y2,Color,Where);
                    End;
               End;

               Begin
                    Initgraph;
                    Load3d('Car.3d',Car);
                    SetColor(1,63,63,0);
                    For A:=-300 To 300 Do
                    Begin
                    Draw3d(Car,-20,0,A,1,VGA);
                    Draw3d(Car,-20,0,A,0,VGA);
                    End;
                    D:=Readkey;
                    Closegraph;
               End.

    6.2. Basic transformations

    Everything in 3d should be done with matrixes, since they ease up a lot
  of calculations... I will explain latter why is this true.
    Matrixes are a very important part of algebra. So, I'll start this section
  by explaining basic matrix calculus. Imagine an 3x3 array and a 1x3 array:

                                      
                        1 2 3        1
                     A= 4 5 6     B= 2
                        7 8 9        3
                                      

    Matrixes are identified by a uppercase letter. B is also called a vector,
  and because it has 3 numbers, it is called a three-dimensional vector. Notice
  that you can think B as three coordinates to a point in 3d: X=1, Y=2 and Z=3.
    With this in mind, let's see matrix multiplication. Let's multiply A by
  B:

                                                
                          1 2 3 1 1*1+2*2+3*3 14
                     A*B= 4 5 6*2=4*1+5*2+6*3=32
                          7 8 9 3 7*1+8*2+9*3 50
                                                

    This is an example, only... The rule is:

                                          
                          a b c x ax+by+cz
                     A*B= d e f*y=dx+ey+fz
                          g h i z gx+hy+iz
                                          

    You can multiply matrixes of any size, but we only need to know how to
  multiply a 3x3 matrix by a 1x3 matrix and a 4x4 matrix by a 1x4 matrix.
  There's just one rule to matrix multiplication: The number of columns of
  the first matrix must be equal to the number of lines of the second matrix.
    Here's the rule for 4x4 by 1x4 multiplication:

                                               
                          a b c d v ax+by+cz+dv
                     A*B= e f g h*x=ex+fy+gz+hv
                          i j k l y ix+jy+kz+lv
                          m n o p z mx+ny+oz+pv
                                               

    Other thing you should know is the identity matrix:

      3x3 identity matrix:          4x4 identity matrix:        
                           1 0 0                         1 0 0 0
                           0 1 0                         0 1 0 0
                           0 0 1                         0 0 1 0
                                                         0 0 0 1
                                                                  

    Plainly put, the identity matrix is a matrix with all the elements zero,
  except the elements in the main diagonal. The identity matrix has the property
  of not afecting the multiplication. So:

                                              
                         1 0 0 5 1*5+0*7+0*8 5
                         0 1 0*7=0*5+1*7+0*8=7
                         0 0 1 8 0*5+0*7+1*8 8
                                              

    Other thing that you need to know is multiplying two 3x3 matrixes and
  two 4x4 matrix:

                                                       
                 a b c j k l aj+bm+cp ak+bn+cq al+bo+cr
                 d e f*m n o=dj+em+fp dk+en+fq dl+eo+fr
                 g h i p q r gj+hm+ip gk+hn+iq gl+ho+ir
                                                       

    Multiplying two 4x4 matrixes is similar. The rules for multiplying
  matrixes of any size are the following:

           - The number of columns of the first matrix must be equal to
             the number of lines of the second matrix.
             Example: You want to multiply two matrixes, with dimensions
                      AxB and CxD (where A and C are the number of columns).
                      You can only multiply if A and D are equal.
           - The result matrix has the same number of columns of the second
             matrix and the same number of lines as the number of columns of
             the first matrix.
             Example: The result matrix of multiplying the above matrixes
                      would have C columns and A lines.
           - The item (item is one of the factors of the matrix) in position
             (x,y) of the matrix will be equal to the multiplication of line
             number Y with columns number X.
           - Matrix multiplication is NOT comutative, that is, you can not
             change the order of the operands !

    This is the basic stuff on matrixes... If you want to know more, go to
  any good algebra book... If I forgot something, I'll tell you in the
  explanation.
    Keep this in mind, because we'll use this later... Now, let's move on to
  the theory of the diferent transformations... Let's start on translation...
  Translation is the easiest operation of them all. Translating an object means
  moving it from it's current position to other position. To translate an
  object, you just have to add to it's coordinates a number. So, let's do a
  translation procedure:

              Procedure Translate(Var Obj:Object3d;XOff,YOff,ZOff:Integer);
              Var A:Byte;
              Begin
                   For A:=1 To Obj.NumberPoints Do
                   Begin
                        Obj.Pt[A].X:=Obj.Pt[A].X+XOff;
                        Obj.Pt[A].Y:=Obj.Pt[A].Y+YOff;
                        Obj.Pt[A].Z:=Obj.Pt[A].Z+ZOff;
                   End;
              End;

    Easy, isn't it ?
    Now, let's do something you'll think it's pretty useless, but that can have
  it's uses... Let's put the translation in a matrix form:

    The formula used for translation is as follows:

                     X = Xo+XOff
                     Y = Yo+YOff
                     Z = Zo+ZOff

    Where X, Y and Z are the translated coordinates; Xo, Yo and Zo are the
  original coordinates and XOff, YOff and ZOff are the offsets of the
  coordinates. Putting this in matrix form:

                                                  
                X     1    0    0   0 Xo   Xo+XOff
           P =  Y  =  0    1    0   0 Yo = Yo+YOff
                Z     0    0    1   0 Zo   Zo+ZOff
                1    XOff YOff ZOff 1  1      1   
                                                  

    We'll see later the use of this... Don't forget you can discard that last
  one in the result column... :)
    So, let's move on to other basic transformation: Scaling ! Let's see a
  2d example... The 3d theory is equal.
    Scaling is the operation that transforms this:

                                   
                                   
                                     +----+
      +--+             in this:      |    |
      |  |                           |    |
      +--+                           +----+
                    

    This is a scaling by two... To do so, we just have to multiply every point
  by two, right ?
    WRONG! If you multiply each point by two, all you'll get is a translation:

                                           
                                           
                                               +--+
                  +--+             *2 =        |  |
                  |  |                         +--+
                  +--+                     
                            

    So, what do we need to do ?
    Check out this case:

                                                 
                                                 
                                               +---+
                  +-+                           |  |
          ||    *2=     ||
                  +-+                           |  |
                                               +---+
                                                 
                                                 

    Have you figured out what you have to do already ?
    You have to move the object to the center of the world (point [0,0,0]), to
  be able to scale it properly... Procedure to scale:

                   Procedure Scale(Var Obj:Object3d;XScl,YScl,ZScl:Real);
                   Var A:Byte;
                   Begin
                        For A:=1 To Obj.NumberPoints Do
                        Begin
                             Obj.Pt[A].X:=Trunc(Obj.Pt[A].X*XScl);
                             Obj.Pt[A].Y:=Trunc(Obj.Pt[A].Y*YScl);
                             Obj.Pt[A].Z:=Trunc(Obj.Pt[A].Z*ZScl);
                        End;
                   End;

    This procedure can have other uses... You can shrink an image, by
  specifying a scaling factor smaller than 1. If you specify a negative number,
  the object will be inverted. If you don't want to scale a certain axis,
  specify 1, not 0, because 0 would nulify that coordinates... Now, let's see
  scaling in a matrix form:

                     X = Xo*XScl
                     Y = Yo*YScl
                     Z = Zo*ZScl

    Where X, Y and Z are the scaled coordinates; Xo, Yo and Zo are the original
  coordinates and XScl, YScl and ZScl are the scaling factors... Putting this
  in matrix form:

                                                 
                X     XScl  0    0   Xo   Xo*XScl
           P =  Y  =   0   YScl  0   Yo = Yo*YScl
                Z      0    0   ZScl Zo   Zo*ZScl
                                                 

    See ? Easy... :)
    Now we can see the use for matrixes. Imagine that some transformation
  involved translation AND scaling. You can do both in just one step... All
  you have to do is multiply the matrixes that do these transformations. So,
  matrix for this operation is:

                                                                        
   1    0    0   0   XScl  0    0   0     XScl        0         0     0 
   0    1    0   0    0   YScl  0   0       0       YScl        0     0 
   0    0    1   0 *  0    0   ZScl 0 =     0         0       ZScl    0 
  XOff YOff ZOff 1    0    0    0   1   XOff*XScl YOff*YScl ZOff*ZScl 1 
                                                                        

    Applying a vector:

                                                                          
   XScl        0         0     0 Xo                Xo*XScl                
     0       YScl        0     0 Yo                Yo*YScl                
     0         0       ZScl    0 Zo=               Zo*ZScl                
 XOff*XScl YOff*YScl ZOff*ZScl 1 1  Zo*XOff*ZScl+Yo*YOff*ZScl+Zo*ZOff*ZScl
                                                                          

    In equation formula:

                X=(Xo+XOff)*XScl;
                Y=(Yo+YOff)*YScl;
                Z=(Zo+ZOff)*ZScl;

    This is what you expected to found... In this case, matrix multiplication
  isn't very useful, but in cases with lots of transformations, including
  rotations and other things like that, this becomes very useful !
    Now, let's move on to the other type of transformation, and probably one
  of the most importants... It can be even more important than translation.
  But it is also harder... Yep, I'm talking about rotations...
    We'll first analise the case of planar rotation, that is, a rotation that
  is in order to a plane, or (if you prefer) an axis... I'll explain later how
  to generalize this to 3d rotation...

    So, in a rotation, you want to transform point (x,y) in a point (x',y').
  Below is an example of a 45 degrees rotation:

                Y                           Y
                                           
                                           O (x',y')
                   O (x,y)                 |
                  /                 -->    |
                 /                         |
                /alpha                     |beta
                X           X

    Alpha is the angle the point makes with the X axis... In this case, it is
  45 degrees. Beta is the angle the transformed point makes with the X axis,
  in this case, 90 degrees. Notice that if this was a 3d rotation, it would be
  a rotation around the Z axis... That means that the Z coordinate of the points
  is never changed. Let's do some formulas to get to the final one...
    We know (or if you don't know, get an 8th grade book and read the trigono-
  metry part) that a point can be expressed by an (X,Y) ordinated pair or
  a (P,A) pair, in which p is the distance of the point to the axis and a is
  the angle the point makes with one of the other axis. We also know the
  relations between the two systems of coordinates:

                          X = P*Cos(A)
                          Y = P*Sin(A)

                          P = Sqrt(X^2+Y^2)
                          A = ArcCos(X/P) = ArcSin(Y/P)

    Sqrt is the square root...
    The (X,Y) coordinate system is called the carthesian system, and the (P,A)
  system is called the polar coordinates system.
    Let's imagine we have point A, with carthesian coordinates (X,Y). That would
  convert in polar coordinates (P1,A1), like this:

                          P1 = Sqrt(X^2+Y^2)
                          A1 = ArcCos(X/P1) = ArcSin(Y/P1)

  and we want to rotate the point by an angle B. That would get us point C, with
  polar coordinates (P2,A2). As we can see from the above example (and from simple
  maths), P1 is equal to P2. Now, we want to obtain the carthesian coordinates
  of point C. So, from the above formulas we know that the carthesian coordinates
  of point C are:
                           X' = P1*Cos(A2)
                           Y' = P1*Sin(A2)

    But, we also know that A2=A1+B, because B is the angle we rotated and A1 is
  the original angle. So:
                           X' = P1*Cos(A1+B)
                           Y' = P1*Sin(A1+B)

    Again, with a simple knowledge of trigonometry, we know that:

                         Sin(U+V) = Sin(U)*Cos(V) + Cos(U)*Sin(V)
                         Cos(U+V) = Cos(U)*Cos(V) - Sin(U)*Sin(V)

    So, applying to the formula above:

                        X' = P1*[Cos(A1)*Cos(B)-Sin(A1)*Sin(B)]
                        Y' = P1*[Sin(A1)*Cos(B)+Cos(A1)*Sin(B)]

    From above, we know that:

                        A1 = ArcCos(X/P1) = ArcSin(Y/P1)

    And from that, we derive that:

                            Cos(A1) = X/P1
                            Sin(A1) = Y/P1

    So, applying in the other formula:

                    X' = P1*[(X/P1)*Cos(B)-(Y/P1)*Sin(B)]
                    Y' = P1*[(Y/P1)*Cos(B)+(X/P1)*Sin(B)]

    And again, applying simple maths, we get the final formula:

                          X' = X*Cos(B) - Y*Sin(B)
                          Y' = Y*Cos(B) + X*Sin(B)

    See ? It is fairly easy to calculate... Now, let's do a simple procedure:

               Procedure RotateZ(Var Obj:Object3d;Deg:Integer);
               Var A:Byte;
                   Angle:Real;
                   XTemp:Real;
               Begin
                    Angle:=0.0175*Deg;
                    For A:=1 To Obj.NumberPoints Do
                      With Obj.Pt[A] Do
                      Begin
                           XTemp:=X;
                           X:=XTemp*Cos(Angle)-Y*Sin(Angle);
                           Y:=Y*Cos(Angle)+XTemp*Sin(Angle);
                      End;
               End;

    Angle is the equivalent to Deg, but expressed in radians. We need that
  because the Sin and Cos functions use radians. We must use a temporary
  variable, because the X value is altered in the first expression, but we
  need X's old value for the second expression...
    As usual, let's move this to matrix form... We need a 2x2 matrix:

                          X' = X*Cos(Ang) + Y*Sin(Ang)
                          Y' = Y*Cos(Ang) - X*Sin(Ang)

    The matrix corresponding to these formulas is:

                                               
                           Cos(Ang)  -Sin(Ang) 
                           Sin(Ang)   Cos(Ang) 
                                               

    Adapting this to a 3d rotation, we'll get:

                                                  
                           Cos(Ang)  -Sin(Ang)  0 
                           Sin(Ang)   Cos(Ang)  0 
                              0          0      1 
                                                  

    Remember what I said about the Z coordinate being unaltered by the
  rotation ? That's why the Z line isn't altered (the Z column is the line),
  and Z doesn't enter any of the calculations (the Z collumn isn't altered).
    Now, if you change the name of the axis, you can get the formulas for the
  rotation around the other axis... The formulas, matrixes and procedures that
  achieve these effects follow...

                Around the X axis:

                          Z' = Z*Cos(Ang) + Y*Sin(Ang)
                          Y' = Y*Cos(Ang) - Z*Sin(Ang)

                                                  
                           1     0          0     
                           0  Cos(Ang)  -Sin(Ang) 
                           0  Sin(Ang)   Cos(Ang) 
                                                  

               Procedure RotateX(Var Obj:Object3d;Deg:Integer);
               Var A:Byte;
                   Angle:Real;
                   ZTemp:Real;
               Begin
                    Angle:=0.0175*Deg;
                    For A:=1 To Obj.NumberPoints Do
                      With Obj.Pt[A] Do
                      Begin
                           ZTemp:=Z;
                           Z:=ZTemp*Cos(Angle)-Y*Sin(Angle);
                           Y:=Y*Cos(Angle)+ZTemp*Sin(Angle);
                      End;
               End;


                Around the Y axis:

                          X' = X*Cos(Ang) + Z*Sin(Ang)
                          Z' = Z*Cos(Ang) - X*Sin(Ang)

                                                  
                           Cos(Ang)  0  -Sin(Ang) 
                              0      1      0     
                           Sin(Ang)  0   Cos(Ang) 
                                                  

               Procedure RotateY(Var Obj:Object3d;Deg:Integer);
               Var A:Byte;
                   Angle:Real;
                   XTemp:Real;
               Begin
                    Angle:=0.0175*Deg;
                    For A:=1 To Obj.NumberPoints Do
                      With Obj.Pt[A] Do
                      Begin
                           XTemp:=X;
                           X:=XTemp*Cos(Angle)-Z*Sin(Angle);
                           Z:=Z*Cos(Angle)+XTemp*Sin(Angle);
                      End;
               End;

    You can see in the file 3D1.PAS an example program of all the rotations
  being made, one at a time.
    Notice that we have to move the objects to the origin of the world, in
  order for the rotation work correctly... That's because the origin (0,0,0)
  is the center of rotation... If you want another point to be the origin,
  you have to calculate the coordinates of the points of the object relative to
  that point, rotate those relative coordinates, and then calculate those
  coordinates relatively to the origin of the world. That's easier to do than
  it sounds... Just some subtractions and addictions...
    Now, imagine you wanted to rotate the object in the three axis... You just
  rotated around the various axis, using the formulas... To do so, you could
  use the following procedure:

               Procedure Rotate(Var Obj:Object3d;XRot,YRot,ZRot:Integer);
               Begin
                    RotateX(Obj,XRot);
                    RotateY(Obj,XRot);
                    RotateZ(Obj,XRot);
               End;

    In 3D2.PAS you can see an example of this...
    Another way you can do the three rotations is by joining the three matrixes
  of rotation together... Like this:

                                                                   
      Cos(a)  -Sin(a)  0   Cos(a)  0  -Sin(a)   1    0        0    
      Sin(a)   Cos(a)  0 *   0     1     0    * 0  Cos(a)  -Sin(a) 
        0        0     1   Sin(a)  0   Cos(a)   0  Sin(a)   Cos(a) 
                                                                   

    I'm not in the mood for arithmetic now... So, do the calculations
  yourself... It's a good exercice...
    To speed up the routines a bit, you can consider precalculating the
  values of the Sin(a) and Cos(a), because they will be very used in the
  routine. Other thing you can do to speed up the calculations is to use a
  pregenerated sine/cosine table... See 3D3.PAS to see an example...
    Now, let's move on to another subject...

    6.3. A 3D starfield

    The 3d starfield is one of the more used effects in yesterdays demos... As
  the matter of fact, there are lot's of demos that include starfields nowadays,
  with some new twist... Well, let's move on... What's a 3d starfield ?
    A 3d starfield is a starfield similar to the one I showed you in a previous
  issue of 'The Mag', but instead of just moving sideways, in the 3d starfield
  you are moving into it... You see the stars passing by...
    This is so simple to do... You just have an array of points, and you move
  them (using a translation), while you can rotate it around the Z axis, in
  order to get a cooler effect... Other thing you can do is to change the
  color of the point... The point can be darker if it is further away from the
  viewer and it gets brighter as it comes nearer.
    Other 3d starfields move around inside the starfield, but that's simple to
  do... It's just movement...
    This is so easy to make, that I'm gonna just splat the code here... Look
  at it and it will be clear...

               Program Starfield;

               Uses Mode13h,Crt;

               Const Points=200;
                     Speed=15;
                     RotateSpeed=1;

               Type Point3d=Record
                                  X,Y,Z:Real;
                            End;

               Var Stars:Array[1..Points] of Point3d;
                   A:Integer;
                   X,Y:Integer;
                   Color:Byte;
                   D:Char;

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

               Procedure RotateZ(Deg:Integer);
               Var Angle:Real;
                   XTemp:Real;
                   S,C:Real;
               Begin
                    Angle:=0.0175*Deg;
                    S:=Sin(Angle);
                    C:=Cos(Angle);
                    For A:=1 To Points Do
                    Begin
                         XTemp:=Stars[A].X;
                         Stars[A].X:=XTemp*C-Stars[A].Y*S;
                         Stars[A].Y:=Stars[A].Y*C+XTemp*S;
                    End;
               End;

               Begin
                    { Setup graphics }
                    InitGraph;
                    InitVirt;
                    Cls(0,VGA);
                    Cls(0,VP[1]);
                    { Setup grayscale }
                    For A:=0 To 15 Do SetColor(A,A*4,A*4,A*4);
                    { Setup stars }
                    For A:=1 To Points Do
                    Begin
                         Stars[A].X:=Random(640)-320.0;
                         Stars[A].Y:=Random(400)-200.0;
                         Stars[A].Z:=Random(800);
                    End;
                    { Main cicle }
                    Repeat
                          { Move stars }
                          For A:=1 To Points Do Stars[A].Z:=Stars[A].Z-Speed;
                          { Rotate stars }
                          RotateZ(RotateSpeed);
                          { Check if any stars are very near of the viewer...
                            If they are, reset them... }
                          For A:=1 To Points Do If Stars[A].Z<=100 Then
                                                  Stars[A].Z:=800;
                          { Clear virtual screen }
                          Cls(0,VP[1]);
                          { Draw stars in virtual screen }
                          For A:=1 To Points Do
                          Begin
                               Conv3d(Stars[A],X,Y);
                               { Determine color }
                               Color:=15-(Trunc((16*Stars[A].Z)) Div 1000);
                               { The following procedure is equal to the
                                 normal putpixel, except that this check if
                                 the point is in the boundaries of the
                                 screen }
                               PutClippedPixel(X,Y,Color,VP[1]);
                          End;
                          { Copy virtual screen to VGA screen }
                          CopyPage(VP[1],VGA);
                    Until Keypressed;
                    { Shutdown }
                    CloseVirt;
                    Closegraph;
               End.

    This is easy to understand... All the concepts were given back in this
  article... Instead of drawing lines, you just put points... This could be
  speeded up by using assembler... It is so easy to code a starfield in ASM !
    But let's move on to another cool thing...

    6.4. VectorBalls

    What are vector balls ?
    Well, I could explain what are vector balls, but it's easier to show.
  Execute program VECTOR1.PAS and you'll see...
    It's a cool effect... It's name comes from the fact that they are balls,
  and that they use vectors... What is a vector ? A vector is a set of
  coordinates (in this case, three) that define a point in space or a
  direction... This is not a 'by the book' explanation, but it will have to
  do.
    Vectorballs have (x,y,z) coordinates that represent they're position in
  space. All you have to do is convert it's 3d coordinates to 2d coordinates
  and draw the ball... We use 2 diferent colors for the balls just to achieve
  an cooler effect...
    The only catch of vectorballs is that you must sort the vectorballs before
  drawing, so that the balls that are further away are draw first, so that
  those that are in front overwrite. If you don't do this, the effect wouldn't
  look like 3d at all... Try removing the call to the Sort procedure in the
  DrawBalls procedure... We used QuickSort, because it's the faster method,
  and speed is important in this effect.
    This is a fairly easy to do program, and it is all comented, so you
  shouldn't have dificulty in understanding it...
    If you wanted to do objects with more than two colors, it is better to
  use a base sprite and add the number of the color. If you don't understand
  what I'm talking about, check out the VECTOR2.PAS program... Check the
  DrawSprite routine in that one. That program also uses a procedure called
  LoadVector, that reads for disk a file containing the data for the color
  and base coordinates of the vectorballs. That data is generated by the
  VECTGEN.PAS program.
    You can also add movement, by using the Move procedure... Movement in
  the procedure is cool...
    This is the end of the first article on 3d in 'The Mag'... I don't know
  when I'll do the next part, but it will be about poligons and sorting...
  And maybe a little bit of lighting... But try to figure this out for
  yourself... :)


-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. Sprites Part III - Lots of fun stuff

    Hello again... Let's get going with sprites in this article... This is
  gonna be a quick one, because I want to wrap this issue of 'The Mag' quickly,
  'cos this is more than 120Kb already...

    7.1. Transparency

    Have you noticed that when you put down a sprite, someparts of it obscures
  what it was there before ?
    Well, to solve that, we use transparency... I already talked about
  transparency in the Fonts article I did last issue... I will just go over
  the algorithm... The ideia is this: if the color of the pixel to place is 0,
  then don't put down that pixel, in the other case, put it down. Simple and
  easy... You aren't limited to color 0... It can be any color... 0 just became
  the standart... Procedure talking:

      Procedure PutImage_T(X,Y:Word;Var Img:Pointer;Where:Word);
      Var Dx,Dy:Word;
          A,B:Word;
          Segm,Offs:Word;
      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:=Y To Y+Dy-1 Do
           Begin
                For B:=X To X+Dx-1 Do
                Begin
                     If Mem[Segm:Offs]<>0 Then
                       PutPixel(B,A,Mem[Segm:Offs],Where);
                     Inc(Offs);
                End;
           End;
      End;

      Procedure PutImage_CT(X,Y:Integer;Var Img:Pointer;Where:Word);
      Var Dx,Dy:Word;
          A,B:Word;
          Segm,Offs:Word;
      Begin
           Segm:=Seg(Img^);
           Offs:=Ofs(Img^);
           Move(Mem[Segm:Offs],Dx,2);
           Move(Mem[Segm:Offs+2],Dy,2);
           Offs:=Offs+4;
           A:=Y;
           While (A<=Y+DY-1) And (A<MaxY) Do
           Begin
                B:=X;
                While (B<=X+DX-1) And (B<MaxX) Do
                Begin
                     If (X>=MinX) And (Y>=MinY) And (Mem[Segm:Offs]<>0) Then
                       PutPixel(B,A,Mem[Segm:Offs],Where);
                     Inc(Offs);
                     Inc(B);
                End;
                Inc(A);
           End;
      End;

    There are two procedures, as you may already founded out... PutImage_T is
  for putting images using transparency and no-clipping. PutImage_CT is
  identically, but it performs clipping.
    The disadvantages of using transparency is that the procedures become
  slower. In order of speed:

                      PutImage       - No clipping / No transparency
                      PutImage_T     - No clipping / Transparency
                      PutImage_C     - Clipping / No transparency
                      PutImage_CT    - Clipping / Transparency

    You should choose carefully the type of PutImage you use, for the sake of
  speed... For example, to put down tiles, use PutImage, because you don't
  need transparency... Just use your head, before using the procedures.
    Just a note... We shouldn't call this transparency, but masking...
  Transparency is another thing, I will cover that in a future issue...

    7.2. Moving over a background

    In almost every game sprites are required... Usually they move above a
  background of some sort (take for example the mouse pointer... That can
  be thought as a sprite). Wouldn't it be great that the sprite could be moved
  without destroying the background ? Of course it would... In some computers
  (like the AMIGA), there is a chip that does this for us... In the PC, we'll
  have to do it ourselfs... This is easy...
    All you have to do is to grab a piece of image of the same size as the
  sprite, in the same location you will put the sprite down. Then, you put the
  sprite down... Every time you erase the sprite, instead of blacking the zone
  were the sprite was, you put the image you previously grabbed, so that
  everything remains the same. Then, you start all over again... Let's check
  some code:

                Program SpritesOverBackground;

                Uses Crt,Sprites,Mode13h;

                Type Sprite=Record
                                  Img:Pointer;
                                  Back:Pointer;
                                  X,Y:Integer;
                            End;

                Var F:File;
                    Ship:Sprite;
                    C:Char;

                Begin
                     { Load sprite image }
                     Assign(F,'Ship.Img');
                     Reset(F,1);
                     LoadImage(F,Ship.Img);
                     Close(F);
                     { Init graphics and set palette }
                     InitGraph;
                     InitVirt;
                     SetColor(1,63,0,0);
                     SetColor(2,63,40,0);
                     SetColor(3,63,63,0);
                     { Load a background }
                     LoadPCX('Planet.Pcx',VP[1]);
                     SetPalette(PCXPal);
                     { Init ship }
                     Ship.X:=0;
                     Ship.Y:=100;
                     { Move the ship }
                     Repeat
                           { Get a 27x10 square of the place were the ship is }
                           GetImage(Ship.X,Ship.Y,Ship.X+27,Ship.Y+10,
                                    Ship.Back,VP[1]);
                           { Put sprite of ship, using masking }
                           PutImage_T(Ship.X,Ship.Y,Ship.Img,VP[1]);
                           { Copy virtual screen to VGA screen }
                           CopyPage(VP[1],VGA);
                           { Restore background }
                           PutImage(Ship.X,Ship.Y,Ship.Back,VP[1]);
                           KillImage(Ship.Back);
                           { Move ship }
                           Ship.X:=Ship.X+2;
                     Until (Ship.X>292) Or (KeyPressed);
                     C:=ReadKey;
                     KillImage(Ship.Img);
                     CloseVirt;
                     CloseGraph;
                End.

    Notice the use of virtual screens... Almost everything that involves sprites
  must use virtual screens, in order to smooth the movements... Try not using
  virtual screens in the above program and see the crappy result...

    7.3. Flipping a sprite

    Wouldn't it be cool if the ship in the end would get back and forth ?
    Of course it would... But that would require two images, one pointing
  right and the other pointing left... Unless you use flipping... Flipping is
  a routine that inverts an image, like a mirror... This is easy to do, just
  think a bit... The first column would be the last column, the second column
  would be the 2nd last column, etc... This is horizontal flipping... For
  vertical flipping, the 1st line would become the last line, etc...
    Coding the horizontal flipping is easy:

                Procedure FlipHoriz(Var Img:Pointer);
                Var Dx,Dy:Word;
                    S1,O1:Word;
                    S2,O2:Word;
                    Tmp:Pointer;
                    A,B:Word;
                Begin
                     { Get X and Y sizes }
                     S1:=Seg(Img^);
                     O1:=Ofs(Img^);
                     Move(Mem[S1:O1],Dx,2);
                     Move(Mem[S1:O1+2],Dy,2);
                     { Create temporary sprite }
                     GetMem(Tmp,Dx*Dy+4);
                     S2:=Seg(Tmp^);
                     O2:=Ofs(Tmp^);
                     { Put the size of the sprite in the temporary sprite }
                     Move(Mem[S1:O1],Mem[S2:O2],4);
                     { Move the columns }
                     For A:=0 To Dx-1 Do
                       For B:=0 To Dy-1 Do
                         Move(Mem[S1:O1+(B*Dx+A+4)],
                              Mem[S2:O2+(B*Dx+(Dx-A-1)+4)],1);
                     { Kill old image }
                     KillImage(Img);
                     { Copy new image to old one }
                     Img:=Tmp;
                End;

    The vertical flipping is even easier:

                Procedure FlipVert(Var Img:Pointer);
                Var Dx,Dy:Word;
                    S1,O1:Word;
                    S2,O2:Word;
                    Tmp:Pointer;
                    A:Word;
                Begin
                     { Get X and Y sizes }
                     S1:=Seg(Img^);
                     O1:=Ofs(Img^);
                     Move(Mem[S1:O1],Dx,2);
                     Move(Mem[S1:O1+2],Dy,2);
                     { Create temporary sprite }
                     GetMem(Tmp,Dx*Dy+4);
                     S2:=Seg(Tmp^);
                     O2:=Ofs(Tmp^);
                     { Put the size of the sprite in the temporary sprite }
                     Move(Mem[S1:O1],Mem[S2:O2],4);
                     { Move the lines }
                     For A:=0 To Dy-1 Do
                       Move(Mem[S1:O1+(A*Dx+4)],
                            Mem[S2:O2+((Dy-1-A)*Dx+4)],Dx);
                     { Kill old image }
                     KillImage(Img);
                     { Copy new image to old one }
                     Img:=Tmp;
                End;

    All these routines are included in the Sprites unit... You can see a test
  program that makes the ship go right and left above a background in the file
  FLYSHIP.PAS.
    Cya in the next article...


-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. Graphics Part IX - Polygons

    Polygons is a comes from a greek word that means 'many angles'. Speaking in
  general terms, a polygon is a planar set of points joined by lines that is
  closed. By closed, I mean that the last point joins with the first point.
    Circles and ellipses are special polygons, because they have an infinite
  number of points.
    Arcs are just here because I want to explain them. It's just a derivation
  of ellipses.
    For now forth, when I talk about polygons, I'm talking about 4 sided
  polygons, but that concept can be extended to any number of sides. For three
  sided polygons (triangles) this gets easier, and usually more than four sides
  aren't used.

    8.1. Simple polygons

    Four sided polygons are constructed of 4 points, each one of them having
  a (x,y) coordinate. Point 1 is joined with a line to point 2, point 2 to
  point 3, point 3 to point 4 and finally, point 4 to point 1. This is easy
  to do:

       Procedure Poly(X1,Y1,X2,Y2,X3,Y3,X4,Y4:Word;Color:Byte;Where:Word);
       Begin
            Line(X1,Y1,X2,Y2,Color,Where);
            Line(X2,Y2,X3,Y3,Color,Where);
            Line(X3,Y3,X4,Y4,Color,Where);
            Line(X4,Y4,X1,Y1,Color,Where);
       End;

    See how easy it was ? Now, let's move on to something more complex...

    8.2. Filled polygons

    The ideia behind filled polygons is fairly simple. You just draw horizontal
  lines between the edges of the polygon. Like this:

                    2        Check out the horizontal lines.
                    /\       The hardest thing in this way of filling polygons
                   /--\3     (because they are several different ways of doing
                  /---/      this) is to determine the minimum and maximum
                 1\--/       X coordinates, which we use to draw the horizontal
                   \/        line.
                    4

    In the 8th grade (and in a previous issue) you probably learned that any
  line can be defined by the equation:

                             (y-y0)=m(x-x0)

    As we want to know the X coordinate, let's solve the equation in order to
  X:
                               (y-y0)
                           x=  ------ + x0
                                 m

    As you probably know, the m is the tangent of the line, and it is defined
  as:
                           x1-x0
                        m= -----
                           y1-y0

    So, the final formula is:

                             (y-y0)*(x1-x0)
                          x= -------------- + x0
                                 (y1-y0)

    So, for any line in the polygon (in the above example, from the Y coordinate
  of point 2 to the Y coordinate of point 4), you must determine which is the
  line from which you will derive the X coordinate, to see if it is the maximum
  and minimum. To know which is the line, you simply compare see if the Y
  coordinate you are checking is between the Y coordinates of the various
  points. For example, imagine that the points in the example above had the
  following coordinates:

                          1: X1=5  ; Y1=25
                          2: X2=20 ; Y2=10
                          3: X3=30 ; Y3=20
                          4: X4=15 ; Y4=35

    And you wanted to know the maximmum and minimum X for line 17.
    Comparing the Y values, you know that line 17 will intersect the line
  between points 1 and 2, and the line between points 2 and 3. So, using the
  above coordinate, you know that the X coordinates of line 17 in those lines
  will be respectively 13 and 27. Now, you just had to draw a horizontal line
  between (13,17) to (27,17). The procedure to do the polygon follows:

       Procedure FPoly(X1,Y1,X2,Y2,X3,Y3,X4,Y4:Word;Color:Byte;Where:Word);
       Var MnY,MxY:Word;
           DeltaX1,DeltaX2,DeltaX3,DeltaX4:Integer;
           DeltaY1,DeltaY2,DeltaY3,DeltaY4:Integer;
           Y:Word;
           MnX,MxX:Integer;
           X:Integer;
       Begin
            { Find out the lines that are to be scanned }
            MnY:=Y1;
            MxY:=Y1;
            If MnY>Y2 Then MnY:=Y2;
            If MnY>Y3 Then MnY:=Y3;
            If MnY>Y4 Then MnY:=Y4;
            If MxY<Y2 Then MxY:=Y2;
            If MxY<Y3 Then MxY:=Y3;
            If MxY<Y4 Then MxY:=Y4;
            { Vertical clipping }
            If MnY<0 Then MnY:=0;
            If MxY>199 Then MxY:=199;
            If MnY>199 Then Exit;
            If MxY<0 Then Exit;
            { Precalculate the (x1-x0) and (y1-y0) needed later, because they
              remain the same for all the routine. They are needed to calculate
              the M. We don't calculate the M, because that would involve real
              maths that is slower than calculating in an integer form later
              in the code }
            DeltaX1:=(X1-X4); DeltaY1:=(Y1-Y4);
            DeltaX2:=(X2-X1); DeltaY2:=(Y2-Y1);
            DeltaX3:=(X3-X2); DeltaY3:=(Y3-Y2);
            DeltaX4:=(X4-X3); DeltaY4:=(Y4-Y3);
            { Main loop }
            For Y:=MnY To MnX Do
            Begin
                 { Find out the minummum and maximmum X coord }
                 MnX:=319;
                 MxX:=-1;
                 { Check if the line intersects line (X1,Y1)->(X2,Y2) }
                 If (Y>=Y1) Or (Y>=Y2) Then
                   If (Y<=Y1) Or (Y<=Y2) Then
                     { Insure that the points don't have the same Y coord }
                     If Not(Y1=Y2) Then
                     Begin
                          { Point of intersection }
                          X:=(Y-Y1)*DeltaX2 Div DeltaY2 + X1;
                          If X<MnX Then MnX:=X;
                          If X>MxX Then MxX:=X;
                     End;
                 { Check if the line intersects line (X2,Y2)->(X3,Y3) }
                 If (Y>=Y2) Or (Y>=Y3) Then
                   If (Y<=Y2) Or (Y<=Y3) Then
                     { Insure that the points don't have the same Y coord }
                     If Not(Y2=Y3) Then
                     Begin
                          { Point of intersection }
                          X:=(Y-Y2)*DeltaX3 Div DeltaY3 + X2;
                          If X<MnX Then MnX:=X;
                          If X>MxX Then MxX:=X;
                     End;
                 { Check if the line intersects line (X3,Y3)->(X4,Y4) }
                 If (Y>=Y3) Or (Y>=Y4) Then
                   If (Y<=Y3) Or (Y<=Y4) Then
                     { Insure that the points don't have the same Y coord }
                     If Not(Y3=Y4) Then
                     Begin
                          { Point of intersection }
                          X:=(Y-Y3)*DeltaX4 Div DeltaY4 + X3;
                          If X<MnX Then MnX:=X;
                          If X>MxX Then MxX:=X;
                     End;
                 { Check if the line intersects line (X4,Y4)->(X1,Y1) }
                 If (Y>=Y4) Or (Y>=Y1) Then
                   If (Y<=Y4) Or (Y<=Y1) Then
                     { Insure that the points don't have the same Y coord }
                     If Not(Y4=Y1) Then
                     Begin
                          { Point of intersection }
                          X:=(Y-Y4)*DeltaX1 Div DeltaY1 + X4;
                          If X<MnX Then MnX:=X;
                          If X>MxX Then MxX:=X;
                     End;
                 { Horizontal range checking }
                 If MnX<0 Then MnX:=0;
                 If MxX>319 Then MxX:=319;
                 { Draw the line }
                 If MnX<MxX Then Line(MnX,Y,MxX,Y,Color,Where);
            End;
       End;

    This looks very complicated, but it isn't... It's just confusing... This
  could be speeded up by using fixed point maths (I'll explain that in the
  next issue).

    8.3. Ellipses and Arcs

    Ellipses are a variation of circles... This is not a correct statement.
  Circles are a variation of ellipses is more correct.
    Ellipses are curved regions of space that have an horizontal and a vertical
  diameter. Circles are when these two dimension are identical. The only
  difference between the Ellipse and Circle procedures is that the X and Y
  factors are multiplied by different factors in the Ellipse procedure.
  Check the code out:

            Procedure Ellipse(X,Y,RH,RV:Integer;Col:Byte;Where:Word);
            Var Px,Py:Integer;
                Deg:Word;
            Begin
                 For Deg:=0 to TableElements Do
                 Begin
                      Px:=Trunc(RH*Sines^[Deg]+X);
                      Py:=Trunc(RV*Cosines^[Deg]+Y);
                      PutPixel(Px,Py,Col,Where);
                 End;
            End;

    It's this easy... Don't forget to initialize the sine/cosine tables.
    Arcs are a variation of this. Here you specify a start and finish angle.

            Procedure Arc(X,Y,RH,RV:Integer;SAngle,EAngle:Integer;
                          Col:Byte;Where:Word);
            Var Px,Py:Integer;
                Deg:Word;
            Begin
                 { Convert the angles to table positions }
                 SAngle:=Trunc(TableElements/360 * SAngle);
                 EAngle:=Trunc(TableElements/360 * EAngle);
                 { Draw the arc }
                 For Deg:=SAngle to EAngle Do
                 Begin
                      Px:=Trunc(RH*Sines^[Deg]+X);
                      Py:=Trunc(RV*Cosines^[Deg]+Y);
                      PutPixel(Px,Py,Col,Where);
                 End;
            End;

    Again, this is easy... Now, let's move on to:

    8.4. Filled Ellipses

    This is very easy to do... Just follow the theory behind the polygons:
  find the minimmum and maximum X value for each line... This is easy in
  the case of ellipses. You just mirror one side of it... Code:

            Procedure FEllipse(X,Y,RH,RV:Integer;Col:Byte;Where:Word);
            Var Px1,Px2,Py:Integer;
                Delta:Integer;
                Deg:Word;
            Begin
                 For Deg:=0 to (TableElements Div 2) Do
                 Begin
                      Delta:=Trunc(RH*Sines^[Deg]);
                      Px1:=Delta+X;
                      Px2:=X-Delta;
                      Py:=Trunc(RV*Cosines^[Deg]+Y);
                      Line(Px1,Py,Px2,Py,Col,Where);
                 End;
            End;

    Well, and this is the end of the polygons' article. See you the next issue,
  where I'll teach you how to optimize and transform this and all other stuff
  I gave in previous issues in assembly code. 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-


  9. Hints and Tips

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

    - Not tottally random numbers (**)

      Did you knew that Pascal's Random function doesn't give out random
      values ?
      As the matter of fact, there's no way a computer can give out random
      numbers. That would only be possible if the computer thinked by itself,
      and as we all know, he can't... So, how are "random" numbers formed ?
      Well... They are made by using a formula (a very complex formula,
      sometimes, a simpler one other times) which uses a base number, known as
      seed to generate the numbers.
      So, a neat trick to create the same sequence of random numbers everytime
      you run the program is to use the same seed. To do that in Pascal, you
      use the system variable RandSeed...  For example, if you put this in
      your program:
                       RandSeed:=1000;

      the seed for your numbers will be 1000... So, the sequence will be same
      everytime you run the program. Check out the article about sorting and
      see that we've used this to create the same sequence of numbers for all
      sorting procedures. This can be very usefull for games... Especially
      games were are various things that are made randomly...
      For example, if you want to create an entire galaxy... Well, a galaxy in
      Pascal could only have 65536 positions, each of these positions only
      ocupying one byte, signifying something... Using the trick of the pseudo-
      -random numbers, that number could be the seed for a section of the
      galaxy, increasing even further the size ! You could do this recursively,
      until you have the size you want !!!! Cool, isn't it ?...

    - Typecasting (*)

      Pascal has an annoying thing... I think C has the same problem. It goes
      like this... If you have a statement like this:

                          A:=X*200;

      If you define X as an integer, the result of this operation will be an
      integer, even if you define A as a Longint. So, what's the problem ?
      The problem is if X is equal to (for example) 200. The result in A (in
      case A is a Longint) should be 40000, but instead, you will get the
      value -25536... Because the compiler stores the result in A as an
      integer, and not as a Longint. As the maximmum number allowed for an
      integer is 32768, the results go wrong when a calculation with an
      integer goes beyond this... A way to go around this is to do the
      following:
                          A:=LongInt(X)*200;

      This tells the compiler to do the calculations as it would do if X was
      a LongInt. You can do typecasting (this is the name for this) with any
      kind of variable. For example, you can do:

                          A:=Real(X)*200;

      Altough this would return an error, because A is a LongInt, and this
      would return a Real value.


    - With clause (*)

      Don't you, sometimes, get fed up of typing:
      thisvar.thisfield.thatfield.anotherfield:=10; ?
      That's why the With clause was invented... The With clause enables you
      to replace:
                 Var1.Field1.Field11:=1;
                 Var1.Field1.Field12:=2;
                 Var1.Field2.Field21:=3;
                 Var1.Field2.Field22:=4;
      by:
                With Var1 Do
                Begin
                     Field1.Field11:=1;
                     Field1.Field12:=2;
                     Field2.Field21:=3;
                     Field2.Field22:=4;
                End;

      Simple, isn't 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. Points of View

    Today's topic: three-dimensional graphics. Nowadays, a great controversy is
  installed. In one side, the purists, who are sick and tired of seeing 3d games
  and demos over and over again... On the other side, the 3d addicts, that love
  and can not live without 3d.
    I'm of a mild opinion... I like 3d games and I can enjoy a 3d sequence in a
  demo... What I can't stand is ALL games in 3d and ALL sequences in a demo
  in 3d.
    So, what's up with 3d ?
    The ideia is like this... Humans normally see the world in 3d. So, it's
  natural that a game in 3d is more realistic... People get into the game more
  easily. It's more natural...
    To keep this short, the bottom line is:  Don't exagerate !! Don't do DOOM
  clones over and over again... Do something more original... Like "Dark
  Forces", by LucasArts... And in demos, don't do 3d demos... Do demos with 3d,
  instead. Again, try something original... Don't keep a torus rotating two
  hours, just because it is texture-mapped and phong-shaded, with ten millions
  sides !!! Try to put a nice phong-shaded torus, with a 21bit plasma
  background, with a nice music pumping, and keep it there for 10 seconds
  only... Then move on to shadebobs or something completely different !!!
    So, what's in the next issue of 'The Mag' ?
  Well, we'll continue our series on Text Adventures (this time, monsters and
  special rooms). I'll continue the 3d tutorial (poligons and poligon sorting,
  and maybe light-sourcing), the sprites tutorial (rotation and anything I can
  think off) and the graphics tutorial (I'll delve into assembler). I'll do an
  article on optimization and cross-fading. I think they'll be another article
  by Scorpio (this is, if I can convince him to do one).
    Well, this was quite a large issue... More than 150 Kb in size !! That's
  big !!! That represents more than 150000 characters! 'The Mag' is growing... :)
    I don't know if you noticed, but it was more or less than a year ago that
  I made the first issue... So 'The Mag' has now one year of existence...
    It would be nice if someone writes an article on how 'The Mag' has evolved
  in the course of this year... (HINT, HINT)... :)
    Well, let's move on to:


-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. The adventures of Spellcaster, the rebel programmer of year 2018.

  Episode 10 - Artificial Inteligence ?

  - His heart stopped... - I softly said to Kristie. - ...but his brain is
    still alive.
    Kristie tried hard to hold off the tears.
  - So what ?! - she shouted, angry.
  - I can do something... - I said, looking at the Karl's pale face.
  - What ? His heart is dead ! We can't take him to an hospital... - a bright
    tear rolled down her face.
  - I can save part of him... But it would cost him dearly...
  - What are you talking about ?!
  - He would have to loose his humanity... With the equipment we stole, I think
    I can transfer the contents of his mind to one of the new HexaMind
    computers. He would be as he ever was... But caged inside a machine.
  - I... I... don't know what to say... - she said, bowing her head.
  - Say yes... It's the only way to save him...
  - Are you sure you can do that ? - she asked, looking me directly in the
    eyes.
  - No... - I said, with a sad look on my face.
    Kristie sitted down on the floor... After a couple of minutes, she rised,
  approached Karl and looked down at his cold face. Then, she looked at the
  encefalogram and she hold Karl's hand.
  - Do it, Spell...
    An hour later, Karl's brain was hooked into the HexaMind computer, that was
  ready to read data. The HexaMind computers were an analog computer, of a new
  technologie, similar in design to our brain.
    I pressed a switch and suddently, Karl's body was shaking because of the
  huge amount of electricity it was being forced to hold.
    Minutes later, the operation was finished. The encefalogram showed zero
  activity. Or Karl was dead, or he was in the computer.
  - Karl ?... Can you ear me ? - Kristie said, approaching the large computer.
  - Kristie ?... - a synthesized voice asked. - Where am I ? I can't see you ?
  - I'll hook up a camera... - I said, reaching for a slot, plugging in the
    DeltaMatrox chip that controlled a video camera attached to it.
  - I can see again... - the voice said again, seeming lost.
    After half an hour, the explanations were made. Karl seemed calm and happy
  to be alive. Kristie was happy too... I was not. I had the burden in my soul.
  The burden of the guilt of playing God...
    I went outside, looking at the red sky above. It was near sunset. Just a
  couple of thoughts went throught my mind.
  - Is it right to destroy his humanity ? Can I play God like this ?

                                               See you in the next issue
                                              Diogo "SpellCaster" Andrade