Plasmas

                    ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸
                    ³         W E L C O M E         ³
                    ³  To the VGA Trainer Program   ³ ³
                    ³              By               ³ ³
                    ³      DENTHOR of ASPHYXIA      ³ ³ ³
                    ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; ³ ³
                      ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³
                        ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ

                            --==[ PART 15 ]==--

 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 þ Introduction

 Hello again. As you can see, this tut is very soon after the last one.
 This is because of two reasons ... 1) The PCGPE ][ will be out soon, so
 I thought I would make sure I have more then just four new trainers for it,
 and 2) I am usually so late between tuts, I thought I would make up for it.

 There is a discussion going on in Usenet, mostly saying that trainers etc.
 should be written a bit more formally and none of this gay banter and
 familiar language should be used. My "quotes" would definately be out ;-)
 But, until I get paid for doing this (and there don't seem to be any takers
 on that score), I will continue to write in this manner. My apologies to those
 who dont like this, but hey, its free, what did you expect?

 This trainer is on plasmas, and the sample program actually became quite large,
 mostly due to the fact that there was some plasma stuff I wanted to try out.

 The concept is very simple, at least for this plasma, so you shouldn't have
 any problems understanding it ... AFTER you have read the text file ...
 jumping straight into the source may be hazardous to your brain.

 Plasmas are a great way to wow your friends by their wierd shapes and forms.
 I was at one stage going to write a game where the bad guy just had two
 circular plasmas instead of eyes... I am sure you will find creative and
 inventive new ways of doing and using plasmas.

 If you would like to contact me, or the team, there are many ways you
 can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail
                   on the ASPHYXIA BBS.
             2) Write to :  Grant Smith
                            P.O.Box 270 Kloof
                            3640
                            Natal
                            South Africa
             3) Call me (Grant Smith) at (031) 73 2129 (leave a message if you
                   call during varsity). Call +27-31-73-2129 if you call
                   from outside South Africa. (It's YOUR phone bill ;-))
             4) Write to denthor@beastie.cs.und.ac.za in E-Mail.
             5) Write to asphyxia@beastie.cs.und.ac.za to get to all of
                us at once.

 NB : If you are a representative of a company or BBS, and want ASPHYXIA
        to do you a demo, leave mail to me; we can discuss it.
 NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling
         quite lonely and want to meet/help out/exchange code with other demo
         groups. What do you have to lose? Leave a message here and we can work
         out how to transfer it. We really want to hear from you!

 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 þ  How do plasmas work?

 I will only cover one type of plasma here ... a realtime plasma of course.
 Other types of plasmas include a static picture with a pallette rotation...

 When you get right down to it, this method of realtime plasmas is merely an
 intersection of four COS waves. We get our color at a particular point by
 saying :
       col := costbl[one]+costbl[two]+costbl[three]+costbl[four];

 The trick is getting the four indexes of that cos table array to create
 something that looks nice. This is how we organise it : Have two of them
 being indexes for vertical movement and two of them being indexes for
 horizontal movement.

 This means that by changing these values we can move along the plasma. To
 draw an individual screen, we pass the values of the four to another four
 so that we do not disturb the origional values. For every pixel across, we
 add values to the first two indexes, then display the next pixel. For
 every row down, we add values to the second two indexes. Sound complex
 enough? Good, because that what we want, a complex shape on the screen.

 By altering the origional four values, we can get all sorts of cool movement
 and cycling of the plasma. The reason we use a cos table is as follows :
 a cos table has a nice curve in the value of the numbers ... when you
 put two or more together, it is possible to get circular pictures ...
 circles are hard to do on a computer, so this makes it a lot easier...

 Okay, now you can have a look at the source file, all I do is put the above
 into practice. I did add one or two things though ...

 Background : This is just a large array, with the values in the array being
 added to the plasma at that pixel.

 Psychadelic : This cycles through about 7000 colors instead of just rotating
 through the base 256.

 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 þ  Clever fading

 You will notice when the sample program fades in and out that the colors
 all reach their destination at the same time ... it other words, they don't
 all increment by one until they hit the right color then stop. When done
 in that way the fading does not look as professional.

 Here is how we do a step-crossfade.

 Each r,g,b value can be between 0 and 64. Have the pallette we want to get
 to in bob and the temporary pallette in bob2. For each step, from 0 to 63
 do the following :
      bob2[loop1].r:=bob[loop1].r*step/64;

 That means if we are halfway through the crossfade (step=32) and the red
 value is meant to get to 16, our equation looks like this :
     r:=16*32/64
      r=8

 Which is half of the way to where it wants to be. This means all colors will
 fade in/out with the same ratios... and look nicer.

 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 þ  Rotating the pallette

 I have done this one before I think .. here it is ...

 move color 0 into temp

 move color 1 into color 0
 move color 2 into color 1
 move color 3 into color 2
 and so on till color 255

 move temp into color 255

 And you pallette is rotating. Easy huh? Recheck tut 2 for more info on
 pallette rotation

 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 þ  In closing

 The text file was a bit short this time, but that is mostly because the
 sample file is self explanitory. The file can however be speeded up, and
 of course you can add certain things which will totally change the look
 of the plasma.

 As always, I am on the lookout for more ideas for future tuts, if you have
 some, mail me!

 No quote today, this lan doesn't encourage creative thinking ;) However,
 there will be quotes in future as I have been told that some people like
 them. Even Pipsy said this while we were playing Ctrl-Alt-Del (two player
 game, one has to hit ctrl and alt as the other hits del, and the person
 hitting the del has to do it quickly so that the computer doesnt reboot.
 If the computer reboots the person who was hitting ctrl and alt has won.
 I thought I was doing really badly against Pipsy until I found out that the
 computer had frozen ;-))

 Byeeee....
   - Denthor
       14:11
         16-9-94

 The following are official ASPHYXIA distribution sites :

 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍ»
 ºBBS Name                  ºTelephone No.   ºOpen º
 ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍ͹
 ºASPHYXIA BBS #1           º+27-31-765-5312 ºALL  º
 ºASPHYXIA BBS #2           º+27-31-765-6293 ºALL  º
 ºC-Spam BBS                º410-531-5886    ºALL  º
 ºPOP!                      º+27-12-661-1257 ºALL  º
 ºSoul Asylum               º+358-0-5055041  ºALL  º
 ºWasted Image              º407-838-4525    ºALL  º
 ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍͼ

 Leave me mail if you want to become an official Asphyxia BBS
 distribution site.
 {$X+}
 USES crt;

 TYPE RGBType = Record
                R, G, B : Byte;
             End;
      PalType = Array[0..255] of RGBType;

 VAR bob,bob2:paltype;  { Two pallettes, current and temporary }
     biiiigpallette : array [1..6656] of RGBType; { A massive pallette for the
                                                    psychadelic effect }
     start:integer;  { Where in the Biiiig pallette are we? }
     Effect,Background:Boolean; { Configuration of effects }

     costbl : Array [0..255] of byte; { cos table lookup }
     mov1,mov2,mov3,mov4 : byte;  { current positions }
     bkg : array [1..50,1..80] of byte; { The pic in the background }

 {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 procedure PAL(Col,R,G,B : Byte); assembler;
    { This sets the Red, Green and Blue values of a certain color }
 asm
    mov    dx,3c8h
    mov    al,[col]
    out    dx,al
    inc    dx
    mov    al,[r]
    out    dx,al
    mov    al,[g]
    out    dx,al
    mov    al,[b]
    out    dx,al
 end;

 {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 Procedure SetAllPal(Var Palette : PalType); Assembler;
   { This dumps the pallette in our variable onto the screen, fast }
 Asm
    push   ds
    lds    si, Palette
    mov    dx, 3c8h
    mov    al, 0
    out    dx, al
    inc    dx
    mov    cx, 768
    rep    outsb
    pop    ds
 End;

 {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 Procedure Makerun (r,g,b:integer);
   { This creates a ramp of colors and puts them into biiiigpallette }
 VAR loop1:integer;
 BEGIN
   for loop1:=start to start+127 do BEGIN
     if r=1 then
       biiiigpallette[loop1].r:=63-(loop1-start) div 4 else
     if r=2 then
       biiiigpallette[loop1].r:=(loop1-start) div 4 else
       biiiigpallette[loop1].r:=0;

     if g=1 then
       biiiigpallette[loop1].g:=63-(loop1-start) div 4 else
     if g=2 then
       biiiigpallette[loop1].g:=(loop1-start) div 4 else
       biiiigpallette[loop1].g:=0;

     if b=1 then
       biiiigpallette[loop1].b:=63-(loop1-start) div 4 else
     if b=2 then
       biiiigpallette[loop1].b:=(loop1-start) div 4 else
       biiiigpallette[loop1].b:=0;
   END;

   for loop1:=start+128 to start+255 do BEGIN
     if r=2 then
       biiiigpallette[loop1].r:=63-(loop1-start) div 4 else
     if r=1 then
       biiiigpallette[loop1].r:=(loop1-start) div 4 else
       biiiigpallette[loop1].r:=0;

     if g=2 then
       biiiigpallette[loop1].g:=63-(loop1-start) div 4 else
     if g=1 then
       biiiigpallette[loop1].g:=(loop1-start) div 4 else
       biiiigpallette[loop1].g:=0;

     if b=2 then
       biiiigpallette[loop1].b:=63-(loop1-start) div 4 else
     if b=1 then
       biiiigpallette[loop1].b:=(loop1-start) div 4 else
       biiiigpallette[loop1].b:=0;
   END;
   start:=start+256;
 END;

 {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 Procedure init;
 VAR loop1,loop2,r,g,b:integer;
     f:text;
     ch:char;

   Function rad (theta : real) : real; { Converts degrees to radians }
   BEGIN
     rad := theta * pi / 180
   END;

 BEGIN
   write ('Do you want the Psychadelic effect? ');
   repeat
     ch:=upcase(readkey);
   until ch in ['Y','N'];
   if ch='Y' then BEGIN
     Writeln ('Yeah!');
     effect:=true;
   END else BEGIN
     Writeln ('Nah');
     effect:=false;
   END;
   writeln;
   while keypressed do readkey;
   write ('Do you want the background? ');
   repeat
     ch:=upcase(readkey);
   until ch in ['Y','N'];
   if ch='Y' then BEGIN
     Writeln ('Yeah!');
     background:=true;
   END else BEGIN
     Writeln ('Nah');
     background:=false;
   END;
   writeln;
   while keypressed do readkey;
   writeln ('Hit any key to continue...');
   readkey;
   while keypressed do readkey;
   asm
     mov     ax,0013h
     int     10h                     { Enter mode 13 }
     cli
     mov     dx,3c4h
     mov     ax,604h                 { Enter unchained mode }
     out     dx,ax
     mov     ax,0F02h                { All planes}
     out     dx,ax

     mov     dx,3D4h
     mov     ax,14h                  { Disable dword mode}
     out     dx,ax
     mov     ax,0E317h               { Enable byte mode.}
     out     dx,ax
     mov     al,9
     out     dx,al
     inc     dx
     in      al,dx
     and     al,0E0h                 { Duplicate each scan 8 times.}
     add     al,7
     out     dx,al
   end;

   fillchar (bob2,sizeof(bob2),0);  { Clear pallette bob2 }
   setallpal (bob2);

   start:=0;
   r:=0;
   g:=0;
   b:=0;
   Repeat
     makerun (r,g,b);
     b:=b+1;
     if b=3 then BEGIN
       b:=0;
       g:=g+1;
     END;
     if g=3 then BEGIN
       g:=0;
       r:=r+1;
     END;
   until (r=2) and (g=2) and (b=2);
     { Set up our major run of colors }

   start:=0;
   if not effect then BEGIN
     for loop1:=0 to 128 do BEGIN
       bob[loop1].r:=63-loop1 div 4;
       bob[loop1].g:=0;
       bob[loop1].b:=loop1 div 4;
     END;
     for loop1:=129 to 255 do BEGIN
       bob[loop1].r:=loop1 div 4;
       bob[loop1].g:=0;
       bob[loop1].b:=63-loop1 div 4;
     END;
   END else
     for loop1:=0 to 255 do bob[loop1]:=biiiigpallette[loop1];

     { Set up a nice looking pallette ... we alter color 0, so the border will
       be altered. }

   For loop1:=0 to 255 do
     costbl[loop1]:=round (cos (rad (loop1/360*255*2))*31)+32;
     { Set up our lookup table...}

   fillchar (bkg,sizeof(bkg),0);
   assign (f,'a:bkg.dat');
   reset (f);
   for loop1:=1 to 50 do BEGIN
     for loop2:=1 to 80 do BEGIN
       read (f,ch);
       if ord (ch)<>48 then
         bkg[loop1,loop2]:=ord (ch)-28;
     END;
     readln (f);
   END;
   close (f);
     { Here we read in our background from the file bkg.dat }
 END;

 {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 Procedure DrawPlasma;
   { This procedure draws the plasma onto the screen }
 VAR loop1,loop2:integer;
     tmov1,tmov2,tmov3,tmov4:byte; { Temporary variables, so we dont destroy
                                     the values of our main variables }
     col:byte;
     where:word;
 BEGIN
   tmov3:=mov3;
   tmov4:=mov4;
   where:=0;
   asm
     mov   ax,0a000h
     mov   es,ax        { In the two loops that follow, ES is not altered so
                          we just set it once, now }
   end;
   For loop1:=1 to 50 do BEGIN   { Fifty rows down }
     tmov1:=mov1;
     tmov2:=mov2;
     for loop2:=1 to 80 do BEGIN { Eighty columns across }
       if background then
         col:=costbl[tmov1]+costbl[tmov2]+costbl[tmov3]+costbl[tmov4]+costbl[loop1]+costbl[loop2]+bkg[loop1,loop2]
       else
         col:=costbl[tmov1]+costbl[tmov2]+costbl[tmov3]+costbl[tmov4]+costbl[loop1]+costbl[loop2];
         { col = Intersection of numerous cos waves }
       asm
         mov    di,where   { di is killed elsewhere, so we need to restore it}
         mov    al,col
         mov    es:[di],al { Place col at ES:DI ... sequential across the screen}
       end;
       where:=where+1;  { Inc the place to put the pixel }
       tmov1:=tmov1+4;
       tmov2:=tmov2+3;  { Arb numbers ... replace to zoom in/out }
     END;
     tmov3:=tmov3+4;
     tmov4:=tmov4+5;    { Arb numbers ... replace to zoom in/out }
   END;
 END;

 {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 Procedure MovePlasma;
   { This procedure moves the plasma left/right/up/down }
 BEGIN
   mov1:=mov1-4;
   mov3:=mov3+4;
   mov1:=mov1+random (1);
   mov2:=mov2-random (2);
   mov3:=mov3+random (1);
   mov4:=mov4-random (2);   { Movement along the plasma + noise}
 END;

 {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 procedure WaitRetrace; assembler;
    {  This waits for a vertical retrace to reduce snow on the screen }
 label
   l1, l2;
 asm
     mov   dx,3DAh
 l1:
     in    al,dx
     test  al,8
     jnz   l1
 l2:
     in    al,dx
     test  al,8
     jz    l2
 end;

 {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 Procedure fadeupone (stage:integer);
   { This procedure fades up the pallette bob2 by one increment and sets the
     onscreen pallette. Colors are increased proportionally, do that all colors
     reach their destonation at the same time }
 VAR loop1:integer;
     temp:rgbtype;
 BEGIN
   if not effect then move (bob[0],temp,3);
   move (bob[1],bob[0],765);
   if effect then move (biiiigpallette[start],bob[255],3) else
     move (temp,bob[255],3);
   start:=start+1;
   if start=6657 then start:=0;
     { Rotate the pallette }

   for loop1:=0 to 255 do BEGIN
     bob2[loop1].r:=integer(bob[loop1].r*stage div 64);
     bob2[loop1].g:=integer(bob[loop1].g*stage div 64);
     bob2[loop1].b:=integer(bob[loop1].b*stage div 64);
   END; { Fade up the pallette }
   setallpal (bob2);
 END;

 {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 Procedure Shiftpallette;
   { This rotates the pallette, and introduces new colors if the psychadelic
     effect has been chosen }
 VAR loop1:integer;
     temp:rgbtype;
 BEGIN
   if not effect then move (bob2[0],temp,3);
   move (bob2[1],bob2[0],765);
   if effect then move (biiiigpallette[start],bob2[255],3) else
     move (temp,bob2[255],3);
   start:=start+1;
   if start=6657 then start:=0;
   setallpal (bob2);
 END;

 {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
 Procedure Play;
 VAR loop1:integer;
 BEGIN
   start:=256;
   for loop1:=1 to 64 do BEGIN
     fadeupone(loop1);
     drawplasma;
     moveplasma;
   END; { Fade up the plasma }
   while keypressed do readkey;
   Repeat
     shiftpallette;
     drawplasma;
     moveplasma;
   Until keypressed; { Do the plasma }
   move (bob2,bob,768);
   for loop1:=1 to 64 do BEGIN
     fadeupone(64-loop1);
     drawplasma;
     moveplasma;
   END; { fade down the plasma }
   while keypressed do readkey;
 END;

 BEGIN
   clrscr;
   writeln ('Hi there ... here is a tut on plasmas! (By popular demand). The');
   writeln ('program will ask you weather you want the Psychadelic effect, in');
   writeln ('which the pallette does strange things (otherwise the pallette');
   writeln ('remains constant), and it will ask weather you want a background');
   writeln ('(a static pic behind the plasma). Try them both!');
   writeln;
   writeln ('The thing about plasmas is that they are very easy to change/modify');
   writeln ('and this one is no exception .. you can even change the background');
   writeln ('with minimum hassle. Try adding and deleting things, you will be');
   writeln ('surprised by the results!');
   writeln;
   writeln ('This is by no means the only way to do plasmas, and there are other');
   writeln ('sample programs out there. Have fun with this one though! ;-)');
   writeln;
   writeln;
   init;
   play;
   asm
     mov  ax,0003h
     int  10h
   end;
   Writeln ('All done. This concludes the fifteenth sample program in the ASPHYXIA');
   Writeln ('Training series. You may reach DENTHOR under the names of GRANT');
   Writeln ('SMITH/DENTHOR/ASPHYXIA on the ASPHYXIA BBS.I also occasinally');
   Writeln ('RSAProg, comp.lang.pascal and comp.sys.ibm.pc.demos. E-mail me at :');
   Writeln ('    denthor@beastie.cs.und.ac.za');
   Writeln ('The numbers are available in the main text. You may also write to me at:');
   Writeln ('             Grant Smith');
   Writeln ('             P.O. Box 270');
   Writeln ('             Kloof');
   Writeln ('             3640');
   Writeln ('             Natal');
   Writeln ('             South Africa');
   Writeln ('I hope to hear from you soon!');
   Writeln; Writeln;
   Write   ('Hit any key to exit ...');
   readkey;
 END.

 [BACK] Back

Discuss this article in the forums


Date this article was posted to GameDev.net: 7/16/1999
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Denthor's Asphyxia Tutorials

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!