Copy Link
Add to Bookmark
Report

DemoScene Starter Kit 3 04

eZine's profile picture
Published in 
DemoScene Starter Kit
 · 5 years ago

  

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ TUTORIAL ON GRAPHICS PROGRAMMING IN ASSEMBLER/C++/PASCAL ³
³ PART 4 OF THE DEMOSCENE STARTER KIT V.3.0 by Zippy of Utopia. ³
³ DO NOT DISTRIBUTE SEPERATELY FROM THE STARTER KIT. ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ


After I released version 1.1 of the DemoScene Starter Kit I got some
feedback about the tutorial beeing a bit too difficult for the very
beginners, so in this version I'll try to explain a bit better.
In this version I have included C/C++ (C) source code some places in addition
to the assembler-source (ASM) to make it even easier for those who don't
quite handle assembler. But since people thought it was so difficult I've
not added too much, either. I've just added how to code lines.
Since v.2.0 I was originally going to add a section written by Jace/TBL
called Advanced Coding Tutorial, but we've not been able to communicate
via e-mail for some reason. I choose to believe that this is the fault
of some grumpy mailer-daemon, and that Jace hasn't backed out.
Due to this I have (with permission) included some of
Snowman's translations of Denthor's democoding-tutorials.
The layout of them have been changed a bit, but nothing else.
You will find them at the end of the normal (old) tutorial.

I am assuming that you allready know a little assembly language or C++
and that you are interested in learning the fundamental parts of
demo-programming. I also assume that you understand the basics concerning
the CPU-registers, memory, and such.
There are a lot of advanced tutorials in this area,
and I found it difficult to find something that would explain all the
really easy stuff. And before you guys start screaming "LAMER!",
this stuff here is all the easy stuff that "everybody" knows.
Why am I explaining it then? I'll tell you: Everybody doesn't know.
The wannabe-coders don't know. The future of the scene doesn't know.
However no-one dares ask these simple things as they are
afraid to be marked a lamer, and never gain any respect, so here you are
wannabe; Everything you wanted to know, but never dared to ask!
However, I'll not talk to much about the routines themselves, because
I do believe so long as you know the basics, it's quite educational, and not
to difficult to figure the rest out for yourself.
So I'll start with the very fundemental building-block of
graphics-programming. How to enter, and put a pixel in MCGA-mode.
Entering it is EASY! But you have to remember that you also have to get OUT
of MCGA-mode and back in to text-mode afterwoods. The best thing i find,
is to make a MACRO. This is faster than a PROC, and you can use parameters.
This simple macro should look like this:

(ASM)
ScrMode MACRO Mode
mov AX,Mode ; Put mode number in AX
int 10H ; Call VIDEO.
ENDM

(C)
void ScrMode(int mode)
{
_AX=mode;
geninterrupt(0x10);
}

This tells your assembler to create a MACRO that can be used with a parameter
called Mode. MCGA-mode is mode 13H. The C-function does the same. Example:

(ASM)
ScrMode 13H
(C)
ScrMode(0x13);

In effect at runtime this gets converted to:

(ASM)
mov AX,13H
int 10H

(C)
_AX=0x13;
geninterrupt(0x10);


Text-mode is mode 3:

(ASM)
ScrMode 3
(C)
ScrMode(3);

The next step now is to put a pixel on the screen. This is also a simple
task. I often use a macro for it, but if you plan on making a 4K intro
I'd recommend you use a PROC because it takes less disk-space.
The macro EATS bytes. Here is the code for a putpixel-macro with comments:

; Caller must pass:
; In X: 0-based X-coordinate of pixel
; In Y: 0-based Y-coordinate of pixel
; In colour: Colour of pixel

pixel MACRO Y,X,colour
pusha ; We need to use all registers, so save all on the stack
mov AX,0A000H ; VGA-memory is at 0A000H
mov ES,AX ; Cannot load directly to segment register
mov AX,Y ; Put Y-coordinate in AX
mov BX,X ; Put X-coordinate in BX
mov CX,AX ; Duplicate Y-coordinate in CX..
shl AX,8 ; ..so we can..
shl CX,6 ; multiply by..
add AX,CX ; ...320 to get proper offset
add AX,BX ; Add to X-coordinate..
mov DI,AX ; ... and put finished offset in DI
mov [ES:DI],BYTE PTR colour ; Put colour-byte in correct offset
popa ; Retrieve registers from the stack
ENDM

That should also be pretty simple to understand. There are 320 vertical lines,
on the screen therefore to get a Y-coordinate we simply multiply the Y-value
by 320. This is done fast by using SHL's instead of IMUL's.
Due to the use of PUSHA, POPA, and the parameter-use of SHL, this routine
demands a computer with a 386-processor.
The BYTE PTR override is used so we only write one pixel, not two.
MCGA-mode uses 8 bits per pixel. This means we get 256 colors to chose from
as our color-byte. It also means we need to use BYTE PTR, or else it would
write two pixels with the same colour next to each other.
With this info you could also code a pixel-reading routine.
It's just as easy. You do the same computations,
but instead of writing to [ES:DI] we read from it into a variable.
Work it out.

In C++ this is much simpler, and much slower:

void pixel(int x, int y, unsigned char col)
{
memset(vga+x+(y*320),col,1);
}

The memset-command gives a byte to a certain point in memory.
The vga-memory'sadress is stored in a pre-defined constant called vga.
Memset's first parameter (vga+x+(y*320)) says where in memory to write.
The second parameter (col) says what to write there.
The last parameter (1) says how many times to do it.
Doesn't get much simpler, so I hope you get this.

Easy, eh? Got another easy one coming: How to fill the screen with one color.

(ASM)
; Caller must pass:
; In VidAddress: The adress of the video refresh buffer, or if in MCGA-mode
; the VGA-buffer. (In DWORD-format!)
; In ClearAtom: The character/attribute pair to fill the buffer with,
; if in text-mode. The high byte contains the attribute
; and the low byte contains the character.
; If in MCGA-mode this value is the colour to fill the
; screen with.
; In BufLength: The number of characters(words, not bytes)in the visible
; display buffer.This is typically 2000 for a 25 line
; screen or 4000 for a 50 line screen. 32000 words
; for an MCGA-screen.

Clear MACRO VidAddress,ClearAtom,BufLength
les DI,DWORD PTR VidAddress
mov AX,ClearAtom
mov CX,BufLength
rep stosw
ENDM

This one is so easy som I'm gonna let you figure it out for yourself.
Remember the buffer-adress is in DWORD-format. The LES-command is short for
"Load pointer to ES". With these parameters it's like saying
"give the complete memory-pos to ES:DI." Geddit?
What STOSW does is take a word from AX and put into memory at [ES:DI].
ES:DI is here the VGA-buffer, and the value in AX is the colour you
want to use. The REP prefix before STOSW means that this is repeated
over and over until CX=0. CX gets decremented each time. DI gets incremented
Here CX is the amount of times to stuff the value into the buffer.
Try making CX smaller, and see what happens.
Here it comes in C++:

(C)
void clearscr(int col)
{
memset(vga, col, 64000);
}

320 pixels * 200 pixels = 64000 pixels = 64000 bytes.
I suppose I fooled you at the pixel-routine. It does come simpler...
If you don't get this you're stupid! :P
Wouldn't I be a good school-teacher.. :) Seriously, this is simple.
"write 'col' to VGA 64000 times after each other". Geddit?

For our next trick, another easy little bit of code, waiting for a key!

(ASM)
mov AH,8 ; Select DOS-service 8
mov AL,0
in 21H ; Call DOS.

(C)
getch();

HA! Never thought it was that easy! Don't wanna wait for just any old key?
Do this:

(ASM)
nope:
mov AH,8 ; Select DOS-service 8
mov AL,0
in 21H ; Call DOS.
cmp AL,'y' ; Was the 'y'-key pressed?
jne nope ; If it was continue, otherwise go back, and wait.

(C)
char somekey;
nope: somekey=getch();
if(somekey!='y') goto nope;

DOS-function 8 returns the value of the key pressed in AL. You can either
use this value or ignore it completely.
The CMP command tells the CPU to compare to bytes, and the JNE says:
Jump if Not Equal to what you just compared it with. It just checks the flags.
The C-version says if the key pressed is not equal (!=) to 'y' check again
until it is.

ON WITH THE NEXT!!

How do you make the image change smoothly and quickly making more than just
static images. Wait a split-second, for the vertical-retrace-beam to
sweep the screen before you write your next pixel. this is done by
simply checking the state of a certain byte in one of the VGA-registers.
You've come this far so now I reckon you R good enough not to need to
many comments. This doesn't always have to be too fast, so I use a PROC:

(ASM)
wait PROC
push DX
push AX
mov dx,03DAh
loop1:
in al,dx
test al,8
jnz loop1
loop2:
in al,dx
test al,8
jz loop2
pop AX
pop DX
ret

wait ENDP

Don't use C for this. If you'r using C, please use inline-assembly
Like this in Borland Turbo C++:

void wait()
{
_DX = 0x03DA;
loop1: asm {
in al,dx;
and al,0x08;
jnz loop1;
}
loop2: asm {
in al,dx;
and al,0x08;
jz loop2;
}
}

Like this in Watcom C++:
void wait();
#pragma aux wait = \
" mov DX,0x3DA" \
" loop1: " \
" in AL,DX " \
" test AL,8 " \
" jne loop1 " \
" loop2: " \
" in AL,DX " \
" test AL,8" \
" je loop2" \
modify [EAX EDX];

Borland has a pretty straightforward inline-assembler, but if you don't
quite understand the Watcom, I understand you.
pragma aux is the directive you use to make an inline-assembler-function.
Aux stands for auxillary function. Directives are meant to be on
just one line, so after each line we put '\' that means
"treat thnext line as a part of this one". The assembler-instructions must
be between quotation-marks. The modify-command at the end states which
registers are modified by your function. It eliminates the need to save stuff
on the stack. If you want a more in-depth explanation of Watcom's
inline-assembler I suggest you download SyNc/Utopia's Watcom-tutorials
from his homepage(listed in the resources-section of DSSK2).

It's just so easy! We push DX, and AX on the stack, because we are using them
in the macro, then we read a bytes worth of info from VGA-register 03DAH.
In programming terms we allmost always start counting from zero.
Bit no. 3 (pos. 1000 binary, 8 decimal)tells us the state of the retrace-beam.
If its not zero, we loop over and over until it is,
then we continue to loop2. This time we want the state of the retrace-beam
to be zero, so we do the same thing just looping until it is zero.
We then pop our registers off the stack in the right order.
If speed *is* essential, use a Macro, and only do loop1. This pushing,
and popping isn't really neccesary, so long as you remember which
registers you are using in your various MACROs and PROCs.
I suggest that beginners keep the pushing, and popping in the
macros to start with, as this minimizes the possibility of bugs to pop up in
your program un-noticed.

Now let's do some simple file-handling..
I'll give you the code to a complete program that displays raw
picture-data on the screen read from a file.

(ASM)
Our data-segment should look like this:
-----------------------------------------------------------------------------
MyData SEGMENT

filehandle DW ?
buff DB 64000 DUP (0)
FileName DB 'WHATEVER.FIL',0

MyData ENDS
----------------------------------------------------------------------------

And this is how our code-segment should look:

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

MyCode SEGMENT PUBLIC
assume CS:MyCode,DS:MyData
Main PROC

Start:

mov AX,MyData
mod DS,AX

mov AX,13
int 10H

call DOPIC

Main ENDP

END Start

DOPIC PROC

lea DX,FileName ; Load the Effective Adress of the FileName string
; to DX
mov AH,3DH ; DOS-function 3DH - Open File
mov AL,00H ; Read Only
int 21H ; Call DOS
jc nothingread ; If the carry-flag is set then there was an error
; opening the file, jump to "nothingread".


; This first part of the program needs the adress of the filename in DX
; Then it opens the file for reading.
; The carry-flag gets set by DOS if it can't open the file
; The filename string must be terminated by a 0-byte.



mov filehandle,AX ; Save the filehandle in the 'filehandle-word'
mov BX,AX ; Move the filehandle to BX
lea DX,buff ; Load the Effective Adress of the buffer in DX
mov CX,64000 ; We are showing a fullscreen-picture in MCGA
; 320x200=64000 pixels=load 64000 bytes in buffer
mov AH,3FH ; DOS-function 3FH - read file to buffer
int 21H ; Call DOS
jc nothingread ; I'm not sure if this is necesarry but the same
; goes here as with the part above.

; Self-explanatory

mov AX,0A000H ; VGA memory is at A000H in flat memory model.
mov ES,AX ; This is an 8086-function so we can't load
; directly to a segment-register
mov cx,64000 ; Number of bytes to move
mov si,dx ; [ds:si] = This is where the buffer with the
; picture data is located.
mov di,0 ; [es:di] = startposition on screen.
; This is where we want to move the data.
rep movsb ; Move 64000 bytes from our buffer to
; video-memory.. Pretty quickly I might add.


; Oh, how we love the beatiful and quick MOVS-family.
; REP MOVSB moves a byte from DS:SI to ES:DI as many times as indicated by CX
; decrementing SI and DI each time. This is a very quick way to fill the
; screen with an image.

nothingread:
mov AH,3EH ; DOS-function 3EH - Close File
mov BX,filehandle ; Needs filehandle in BX
int 21H ; Call DOS
ret ; Return from function


DOPIC ENDP

MyCode ENDS

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


That's so easy you could laugh! HA!
If you use this code directly you will display the picture on the screen
and the program will terminate in MCGA-mode with the picture there,
so add a delay and change back to mode 3.

Here's the DOPIC in C++ (It requires this to appear before it gets called:
FILE *filp; That defines a filepointer called filp.):

(C)
void dopic(char *fil)
{
filp=fopen(fil,"rb"); //Open specified file and give
// adress to filp
fread(vga,64000,1,filp); // Read 64000 bytes from file directly
// to VGA.
fclose(filp); // Close the file
}

So easy compared to the ASM-PROC it's allmost not fun.

Lets mess around with the palette. If you want to change the colours of your
picture without writing any pixels you could save the alternative colours
in a palette file. The pallete is in MCGA 768 bytes. That is 256 * 3.
256 collections of RGB-values. Let's say that we've loaded these values
into a buffer called buff2. At port 03C8H we find the VGA's register
made especially for this purpose. At 03C9H we find the VGA's data register.
Check out this code-snippet and see if you get it.

lea BP,buff2 ; Load Effective Adress of buff2 into BP
mov DX,03C8H ; VGA register 3C8 - Write Register
mov AL,0 ; Zero is the first colour-byte were changing
out DX,AL ; Tell the VGA what we're gonna do
mov CX,768 ; We're gonna write 768 values
mov DX,03C9H ; VGA register 3C9 - Data Register
Give: mov AL,BYTE PTR [BP] ; Put the value into AL
out DX,AL ; Give the VGA the value in AL
inc BP ; Now we point to the next value
loop Give ; Do it till we've written all the values

Not to hard to understand.. Feel free to rip it ruthlessly, however it's
really easy so you shouldn't have any trouble writing your own.
Try to work some more stuff out on your own. If you apply your brains a bit
you could do some real cool stuff like palettte rotation, fade in/out
to black, fade in/out to white. Use your imagination.

Allmost forgot to convert this one to C:
(C)
void pal(int no, byte R, byte G, byte B)
{
outp(0x3C8,no);
outp(0x3C9,R);
outp(0x3C9,G);
outp(0x3C9,B);
}

That edits 1 color in the palette.
The next function requires that we've defined a buffer with all the palette
values like this:
unsigned char palbuf[256][3];
How you fill that buffer afterwards is up to you. I'd recommend loading
the values from a file you've made before-hand.
Then you want to make the pallete mach the values in your buffer.
This you do like so:
(C)
void setpalette()
{
int paloop;
for(paloop=0; paloop<256; paloop++)
pal(paloop,palbuf[paloop][0],palbuf[paloop][1],palbuf[paloop][2]);
}

Now, was that hard?

You could try to improve the picture-reading, you could try to figure
out how to read the palette (VGA read-register is 03C7H), try to cycle
the colours. Also try to make some effects of your own.

Now for some code totally new to DSSK for version 2.
Lines. What I'd like to tell you first of all is to buy
Zen of Graphics Programming by Michael Abrash, it is brilliant,
and as a lot of great info on (among other things) lines.
I studied that book for a while before writin my line-drawing-function,
so my function is pretty similar, except that it probably is worse,
and has worse comments. In fact, if you have that book, use it instead.
Mr. Abrash's routines will definetely be better than mine.
Well.. Actually it's more or less identical to his routine...
I myself use only C++ for this routine, so I haven't got any code
on how to do this in assembler, and I can't be bothered to write
it especially. So this one is just for you C-people out there.
This is the most difficult code so far in the DSSK, so don't despair
if you don't get it, my comments are probably not too good, except
for the ones taken from Abrash's book. Not beeing such a tremendously
good coder, and beeing even worse at pedagocial stuff, I'm not going to
confuse you further by trying to explain the function.
You'll need to know some C++ to understand this one.
Read through it, and see if it makes sence.

(C)

#include <dos.h>

const int scr_width=320;
const int scr_segment=0xA000; // Remember to add a 0 if you're in Pmode.

void HorizontalRun(char far **ScreenPtr,int XAdvance,int RunLength,int Color);
void VerticalRun(char far **ScreenPtr,int XAdvance,int RunLength,int Color);

// Draws a line between the specified endpoints in color Color.

void LineDraw(int XStart,int YStart,int XEnd,int YEnd,int Color)
{
int Temp,AdjUp,AdjDown,ErrorTerm,XAdvance,XDelta,YDelta;
int WholeStep,InitialPixelCount,FinalPixelCount,i,RunLength;
char far *ScreenPtr;

//Draw top to bottom

if(YStart>YEnd) {
Temp=YStart;
YStart=YEnd;
YEnd=Temp;
Temp=XStart;
XStart=XEnd;
XEnd=Temp;
}

ScreenPtr=MK_FP(scr_segment,YStart*scr_width+XStart);

// This part of the program determines how far to write horisontally
// and if we're wrting towards the left or towards the right.

if((XDelta=XEnd-XStart)<0)
{
XAdvance=-1;
XDelta=-XDelta;
}
else
{
XAdvance=1;
}

// Determine how far it is vertically

YDelta=YEnd-YStart;

// Special-case horizontal, vertical, and diagonal lines, speeds things up

if(XDelta==0)
{
// Vertical
for(i=0;i<=YDelta;i++)
{
*ScreenPtr=Color;
ScreenPtr+=scr_width;
}
return;
}
if(YDelta==0)
{
// Horizontal
for(i=0;i<=XDelta;i++)
{
*ScreenPtr=Color;
ScreenPtr+=XAdvance;
}
return;
}
if(XDelta==YDelta)
{
// Diagonal line
for(i=0;i<=XDelta;i++)
{
*ScreenPtr=Color;
ScreenPtr+=XAdvance + scr_width;
}
return;
}

// Is line X or Y major?

if(XDelta>=YDelta)
{
// X major line

WholeStep=XDelta/YDelta;

// Error term adjust each time Y steps by 1.

AdjUp=(XDelta%YDelta)*2;

// Error term adjust when the error term turns over, used to factor
// out the X step made at that time

AdjDown=YDelta*2;

// Initial error term

ErrorTerm=(XDelta%YDelta)-(YDelta*2);

/* The initial and last runs are partial, as Y only advances by 0.5

InitialPixelCount=(WholeStep/2)+1;
FinalPixelCount=InitialPixelCount;

// If the basic run length is even, and there's no fractional
// advance, we have one pixel that could go to either the initial
// or last partial run, which we'll arbitrarily allocate to the
// last run

if((AdjUp==0)&&((WholeStep&0x01)==0))
{
InitialPixelCount--;
}
// If there are an odd number of pixels per run,
// we have 1 pixel that can't be allocated to either the initial
// *or* last partial run, so we add 0.5 to error term so this
// pixel will be handled by the normal full-run loop

if((WholeStep&0x01)!=0)
{
ErrorTerm+=YDelta;
}
// Draw the first run

HorizontalRun(&ScreenPtr, XAdvance, InitialPixelCount, Color);

// Draw full runs

for(i=0;i<(YDelta-1);i++)
{
RunLength=WholeStep; // Minimum length of run

// Advance the error term and add an extra pixel if needed

if((ErrorTerm+=AdjUp)>0)
{
RunLength++;
ErrorTerm-=AdjDown; // reset the error term
}

// Draw this scan line's run

HorizontalRun(&ScreenPtr,XAdvance,RunLength,Color);
}

// Draw the final run

HorizontalRun(&ScreenPtr,XAdvance,FinalPixelCount,Color);
return;
}
else
{
// Y major

// Minimum number of pixels

WholeStep=YDelta/XDelta;

// Error term adjust each time X steps by 1; used to tell when 1 extra
// pixel should be added to the run

AdjUp=(YDelta%XDelta)*2;

// Same as above, with Y-axis in stead of X-axis.

AdjDown=XDelta*2;

// Initial error term

ErrorTerm=(YDelta%XDelta)-(XDelta*2);

// The initial and last runs are partial, because X advances only 0.5
// for these runs, rather than 1.

InitialPixelCount=(WholeStep/2)+1;
FinalPixelCount=InitialPixelCount;

// If the basic run length is even, and there's no fractional
// advance, we have one pixel that could go to either the initial
// or last partial run, which we'll arbitrarily allocate to the
// last run

if((AdjUp==0)&&((WholeStep&0x01)==0))
{
InitialPixelCount--;
}

// If there are an odd number of pixels per run, we have one pixel
// that can't be allocated to either the initial or last partial
// run, so we'll add 0.5 to the error term so this pixel will be
// handled by the normal full-run loop

if((WholeStep&0x01)!=0)
{
ErrorTerm+=XDelta;
}

// Draw the first run

VerticalRun(&ScreenPtr,XAdvance,InitialPixelCount,Color);

// Draw all full runs
for (i=0;i<(XDelta-1);i++)
{
RunLength=WholeStep; // Minimum length of run

// Advance the error term and add an extra pixel if needed

if ((ErrorTerm+=AdjUp)>0)
{
RunLength++;
ErrorTerm-=AdjDown; // reset error term
}
// Draw run
VerticalRun(&ScreenPtr,XAdvance,RunLength,Color);
}

// Draw the final run of pixels

VerticalRun(&ScreenPtr,XAdvance,FinalPixelCount,Color);
return;
}
}

// Draws a horizontal run of pixels before advancing the bitmap pointer to
// the first pixel of the next run.

void HorizontalRun(char far **ScreenPtr,int XAdvance,int RunLength,int Color)
{
int i;
char far *WorkingScreenPtr=*ScreenPtr;

for(i=0;i<RunLength;i++)
{
*WorkingScreenPtr=Color;
WorkingScreenPtr+=XAdvance;
}

// Advance to the next scan line

WorkingScreenPtr+=scr_width;
*ScreenPtr=WorkingScreenPtr;
}

// Draws a horizontal run of pixels before advancing as with horizontal

void VerticalRun(char far **ScreenPtr,int XAdvance,int RunLength,int Color)
{
int i;
char far *WorkingScreenPtr=*ScreenPtr;

for (i=0;i<RunLength;i++)
{
*WorkingScreenPtr=Color;
WorkingScreenPtr+=scr_width;
}

// Advance to the next column

WorkingScreenPtr+=XAdvance;
*ScreenPtr=WorkingScreenPtr;
}

That was a function for so-called run-sliced lines.
Hope it was understandable.

Here are the Snowman & Denthor tutorials!


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

[Note: things in brackets have been added by Snowman. The original text
has remained mostly unaltered except for the inclusion of C++ material]



--==[ PART 1 : The Basics]==--

þ Introduction

Hi there! This is Denthor of ASPHYXIA, AKA Grant Smith. This training
program is aimed at all those budding young demo coders out there. I am
assuming that the reader is fairly young, has a bit of basic Std. 6 math
under his belt, has done a bit of programming before, probably in BASIC,
and wants to learn how to write a demo all of his/her own.

This I what I am going to do. I am going to describe how certain routines
work, and even give you working source code on how you do it. The source
code will assume that you have a VGA card that can handle the
320x200x256 mode. I will also assume that you have Turbo Pascal 6.0 or
above (this is because some of the code will be in Assembly language,
and Turbo Pascal 6.0 makes this incredibly easy to use). [In addition,
C++ source has been included, so you now have a choice]. By the end of
the first "run" of sections, you will be able to code some cool demo stuff
all by yourself. The info you need, I will provide to you, but it will be
you who decides on the most spectacular way to use it.

Why not download some of our demos and see what I'm trying to head you
towards.

I will be posting one part a week on the Mailbox BBS. I have the first
"run" of sections worked out, but if you want me to also do sections on
other areas of coding, leave a message to Grant Smith in private E-Mail,
or start a conversation here in this conference. I will do a bit of
moderating of a sort, and point out things that have been done wrong.

In this, the first part, I will show you how you are supposed to set up
your Pascal or C++ program, how to get into 320x200x256 graphics mode
without a BGI file, and various methods of putpixels and a clearscreen
utility.

NOTE : I drop source code all through my explanations. You needn't try
to grab all of it from all over the place, at the end of each part I
add a little program that uses all the new routines that we have
learned. If you do not fully understand a section, leave me
private mail telling me what you don't understand or asking how I
got something etc, and I will try to make myself clearer. One
last thing : When you spot a mistake I have made in one of my
parts, leave me mail and I will correct it post-haste. However, I
do not know C++ currently, so if you have trouble with that source,
contact Christopher Mann instead.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Disclaimer

Hi again, sorry that I have to add this, but here goes. All source code
obtained from this series of instruction programs is used at your own
risk. Denthor and the ASPHYXIA demo team hold no responsibility for any
loss or damage suffered by anyone through the use of this code. Look
guys, the code I'm going to give you has been used by us before in
Demos, Applications etc, and we have never had any compliants of machine
damage, but if something does go wrong with your computer, don't blame
us. Sorry, but that's the way it is.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ The MCGA mode and how you get into it in Pascal or C++ without a BGI

Lets face it. BGI's are next to worthless for demo coding. It is
difficult to find something that is slower then the BGI units for doing
graphics. Another thing is, they wern't really meant for 256 color
screens anyhow. You have to obtain a specific external 256VGA BGI to get
into it in Pascal, and it just doesn't make the grade.

So the question remains, how do we get into MCGA 320x200x256 mode in
Pascal or C++ without a BGI? The answer is simple : Assembly language.
Obviously assembly language has loads of functions to handle the VGA
card, and this is just one of them. If you look in Norton Gides to
Assembly Language, it says this ...

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
INT 10h, 00h (0) Set Video Mode

Sets the video mode.

On entry: AH 00h
AL Video mode

Returns: None

Registers destroyed: AX, SP, BP, SI, DI
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

This is all well and good, but what does it mean? It means that if you
plug in the video mode into AL and call interrupt 10h, SHAZAM! you are
in the mode of your choice. Now, the MCGA video mode is mode 13h, and
here is how we do it:

[PASCAL]

Procedure SetMCGA;
BEGIN
asm
mov ax,0013h
int 10h
end;
END;

[C++]

void SetMCGA() {
_AX = 0x0013;
geninterrupt (0x10);
}


There you have it! One call to that procedure/function, and BANG you are in
320x200x256 mode. We can't actually do anything in it yet, so to go back
to text mode, you make the video mode equal to 03h, as seen below :

[PASCAL]

Procedure SetText;
BEGIN
asm
mov ax,0003h
int 10h
end;
END;

[C++]

void SetText() {
_AX = 0x0003;
geninterrupt (0x10);
}


BANG! We are back in text mode! Now, cry all your enquiring minds, what
use is this? We can get into the mode, but how do we actually SHOW
something on the screen? For that, you must move onto the next section
....

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Clearing the screen to a specific color

Now that we are in MCGA mode, how do we clear the screen. The answer is
simple : you must just remember that the base adress of the screen is
a000h. From a000h, the next 64000 bytes are what is actually displayed on
the screen (Note : 320 * 200 = 64000). So to clear the screen, you just use
the fillchar command like so :

[PASCAL]

FillChar (Mem [$a000:0],64000,Col);

[C++]

memset(vga, Col, 0xffff); // "vga" is a pointer to address 0xa000

What the mem command passes the Segment base and the Offset of a part of
memory : in this case the screen base is the Segment, and we are starting
at the top of the screen; Offset 0. The 64000 [0xffff] is the size of the
screen (see above), and Col is a value between 0 and 255, which represents
the color you want to clear the screen to.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Putting a pixel on the screen (two different methoods)

If you look in Norton Guides about putting a pixel onto the screen, you
will see this :

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Writes a pixel dot of a specified color at a specified screen
coordinate.

On entry: AH 0Ch
AL Pixel color
CX Horizontal position of pixel
DX Vertical position of pixel
BH Display page number (graphics modes with more
than 1 page)

Returns: None

Registers destroyed: AX, SP, BP, SI, DI
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

As seen from our SetMCGA example, you would write this by doing the following:

[PASCAL]

Procedure INTPutpixel (X,Y : Integer; Col : Byte);
BEGIN
asm
mov ah,0Ch
mov al,[col]
mov cx,[x]
mov dx,[y]
mov bx,[1]
int 10h
end;
END;

[C++]

void INTPutpixel(int x, int y, unsigned char Col) {
_AH = 0x0C;
_AL = Col;
_CX = x;
_DX = y;
_BX = 0x01;
geninterrupt (0x10);
}


The X would be the X-Coordinate, the Y would be the Y-Coordinate, and the Col
would be the color of the pixel to place. Note that MCGA has 256 colors,
numbered 0 to 255. The startoff pallette is pretty grotty, and I will show
you how to alter it in my next lesson, but for now you will have to hunt for
colors that fit in for what you want to do. Luckily, a byte is 0 to 255
[in C++ syntax, "byte" = "unsigned char"], so that is what we pass to the col
variable. Have a look at the following.

CGA = 4 colours.
4x4 = 16
EGA = 16 colors.
16x16 = 256
VGA = 256 colors.
Therefore an EGA is a CGA squared, and a VGA is an EGA squared ;-)

Anyway, back to reality. Even though the above procedure/function is written
in assembly language, it is slooow. Why? I hear your enquiring minds cry.
The reason is simple : It uses interrupts (It calls INT 10h). Interrupts are
sloooow ... which is okay for getting into MCGA mode, but not for trying
to put down a pixel lickety-split. So, why not try the following ...

[PASCAL]

Procedure MEMPutpixel (X,Y : Integer; Col : Byte);
BEGIN
Mem [VGA:X+(Y*320)]:=Col;
END;

[C++]

void MEMPutpixel (int x, int y, unsigned char Col) {
memset(vga+x+(y*320),Col,1);
}


The Mem/memset command, as we have seen above, allows you to point at a
certain point in memory ... the starting point is a000h, the base of the
VGA's memory, and then we specify how far into this base memory we start.

Think of the monitor this way. It starts in the top left hand corner at
0. As you increase the number, you start to move across the screen to your
right, until you reach 320. At 320, you have gone all the way across the
screen and come back out the left side, one pixel down. This carries on
until you reach 63999, at the bottom right hand side of the screen. This
is how we get the equation X+(Y*320). For every increased Y, we must
increment the number by 320. Once we are at the beginning of the Y line
we want, we add our X by how far out we want to be. This gives us the
exact point in memory that we want to be at, and then we set it equal to
the pixel value we want.

The MEM methood of putpixel is much faster, and it is shown in the sample
program at the end of this lesson. The ASPHYXIA team uses neither putpixel;
we use a DMA-Straight-To-Screen-Kill-Yer-Momma-With-An-Axe type putpixel
which is FAST. We will give it out, but only to those of you who show us
you are serious about coding. If you do do anything, upload it to me,
I will be very interested to see it. Remember : If you do glean anything
from these training sessions, give us a mention in your demos and UPLOAD
YOUR DEMO TO US!

Well, after this is the sample program; have fun with it, UNDERSTAND it,
and next week I will start on fun with the pallette.

See you all later,
- Denthor


--==[ PART 2 ]==--



þ Introduction

Hi there again! This is Grant Smith, AKA Denthor of ASPHYXIA. This is the
second part of my Training Program for new programmers. I have only had a
lukewarm response to my first part of the trainer series ... remember, if
I don't hear from you, I will assume that you are all dead and will stop
writing the series ;-). Also, if you do get in contact with me I will give
you some of our fast assembly routines which will speed up your demos no
end. So go on, leave mail to GRANT SMITH in the main section of the
MailBox BBS, start up a discussion or ask a few questions in this Conference,
leave mail to ASPHYXIA on the ASPHYXIA BBS, leave mail to Denthor on
Connectix, or write to Grant Smith,
P.O.Box 270
Kloof
3640
See, there are many ways you can get in contact with me! Use one of them!

In this part, I will put the Pallette through it's paces. What the hell is
a pallette? How do I find out what it is? How do I set it? How do I stop
the "fuzz" that appears on the screen when I change the pallette? How do
I black out the screen using the pallette? How do I fade in a screen?
How do I fade out a screen? Why are telephone calls so expensive?
Most of these quesions will be answered in this, the second part of my
Trainer Series for Pascal.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ What is the Pallette?

A few weeks ago a friend of mine was playing a computer game. In the game
there was a machine with stripes of blue running across it. When the
machine was activated, while half of the the blue stripes stayed the same,
the other half started to change color and glow. He asked me how two stripes
of the same color suddenly become different like that. The answer is simple:
the program was changing the pallette.

As you know from Part 1, there are 256 colors in MCGA mode, numbered 0 to 255.
What you don't know is that each if those colors is made up of different
intensities of Red, Green and Blue, the primary colors (you should have
learned about the primary colors at school). These intensities are numbers
between 0 and 63. The color of bright red would for example be obtained by
setting red intensity to 63, green intensity to 0, and blue intensity to 0.
This means that two colors can look exactly the same, eg you can set color 10
to bright red and color 78 to color bright red. If you draw a picture using
both of those colors, no-one will be able to tell the difference between the
two.. It is only when you again change the pallette of either of them will
they be able to tell the difference.

Also, by changing the whole pallette, you can obtain the "Fade in" and "Fade
out" effects found in many demos and games. Pallette manipulation can become
quite confusing to some people, because colors that look the same are in fact
totally seperate.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ How do I read in the pallette value of a color?

This is very easy to do. To read in the pallette value, you enter in the
number of the color you want into port $3c7 [0x03C7], then read in the values
of red, green and blue respectively from port $3c9 [0x03C9]. Simple, huh? Here
is a procedure that does it for you :

[Pascal]

Procedure GetPal(ColorNo : Byte; Var R,G,B : Byte);
{ This reads the values of the Red, Green and Blue values of a certain
color and returns them to you. }
Begin
Port[$3c7] := ColorNo;
R := Port[$3c9];
G := Port[$3c9];
B := Port[$3c9];
End;

[C++]

void GetPal(unsigned char ColorNo, unsigned char &R,
unsigned char &G, unsigned char &B) {

outp (0x03C7,ColorNo); // here is the pallette color I want read
R = inp (0x03C9);
G = inp (0x03C9);
B = inp (0x03C9);

}

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ How do I set the pallette value of a color?

This is also as easy as 3.1415926535897932385. What you do is you enter in the
number of the color you want to change into port $3c8 [0x03C8], then enter the
values of red, green and blue respectively into port $3c9 [0x03C9]. Because
you are all so lazy I have written the procedure for you ;-)

[Pascal]

Procedure Pal(ColorNo : Byte; R,G,B : Byte);
{ This sets the Red, Green and Blue values of a certain color }
Begin
Port[$3c8] := ColorNo;
Port[$3c9] := R;
Port[$3c9] := G;
Port[$3c9] := B;
End;

[C++]

void Pal(unsigned char ColorNo, unsigned char R,
unsigned char G, unsigned char B) {

outp (0x03C8,ColorNo); // here is the pallette color I want to set
outp (0x03C9,R);
outp (0x03C9,G);
outp (0x03C9,B);

}

Asphyxia doesn't use the above pallete procedures, we use assembler versions,
which will be given to PEOPLE WHO RESPOND TO THIS TRAINER SERIES (HINT,
HINT)


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ How do I stop the "fuzz" that appears on my screen when I change the
pallette?

If you have used the pallette before, you will have noticed that there is
quite a bit of "fuzz" on the screen when you change it. The way we counter
this is as follows : There is an elctron beam on your monitor that is
constantly updating your screen from top to bottom. As it gets to the
bottom of the screen, it takes a while for it to get back up to the top of
the screen to start updating the screen again.

The period where it moves from the bottom to the top is called the Verticle
Retrace. During the verticle retrace you may change the pallette without
affecting what is on the screen.

What we do is that we wait until a verticle retrace has started by calling a
certain procedure; this means that everything we do now will only be shown
after the verticle retrace, so we can do all sorts of strange and unusual
things to the screen during this retrace and only the results will be shown
when the retrace is finished. This is way cool, as it means that when we
change the pallette, the fuzz doesn't appear on the screen, only the result
(the changed pallette), is seen after the retrace! Neat, huh? ;-) I have put
the purely assembler WaitRetrace routine in the sample code that follows this
message. Use it wisely, my son.

NOTE : WaitRetrace can be a great help to your coding ... code that fits
into one retrace will mean that the demo will run at the same
speed no matter what your computer speed (unless you are doing a lot
during the WaitRetrace and the computer is slooooow). Note that in
the following sample program and in our SilkyDemo, the thing will run
at the same speed whether turbo is on or off.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ How do I black out the screen using the pallette?

This is basic : just set the Red, Green and Blue values of all colors to
zero intensity, like so :

[Pascal]

Procedure Blackout;
{ This procedure blackens the screen by setting the pallette values of
all the colors to zero. }
VAR loop1:integer;
BEGIN
WaitRetrace;
For loop1:=0 to 255 do
Pal (loop1,0,0,0);
END;

[C++]

void Blackout() {

WaitRetrace();
for (int loop1=0;loop1<256;loop1++)
Pal (loop1,0,0,0);

}

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ How do I fade in a screen?

Okay, this can be VERY effective. What you must first do is grab the
pallette into a variable, like so :

[Pascal] VAR Pall := Array [0.255,1..3] of BYTE;
[C++] unsigned char Pall[256][3];

0 to 255 is for the 256 colors in MCGA mode, 1 to 3 is red, green and blue
intensity values;

[Pascal]

Procedure GrabPallette;
VAR loop1:integer;
BEGIN
For loop1:=0 to 255 do
Getpal (loop1,pall[loop1,1],pall[loop1,2],pall[loop1,3]);
END;

[C++]

void GrabPallette() {

for(int loop1=0;loop1<256;loop1++)
GetPal(loop1,Pall2[loop1][0],Pall2[loop1][1],Pall2[loop1][2]);

}

This loads the entire pallette into variable pall. Then you must blackout
the screen (see above), and draw what you want to screen without the
construction being shown. Then what you do is go throgh the pallette. For
each color, you see if the individual intensities are what they should be.
If not, you increase them by one unit until they are. Beacuse intensites
are in a range from 0 to 63, you only need do this a maximum of 64 times.

[Pascal]

Procedure Fadeup;
VAR loop1,loop2:integer;
Tmp : Array [1..3] of byte;
{ This is temporary storage for the values of a color }
BEGIN
For loop1:=1 to 64 do BEGIN
{ A color value for Red, green or blue is 0 to 63, so this loop only
need be executed a maximum of 64 times }
WaitRetrace;
For loop2:=0 to 255 do BEGIN
Getpal (loop2,Tmp[1],Tmp[2],Tmp[3]);
If Tmp[1]<Pall[loop2,1] then inc (Tmp[1]);
If Tmp[2]<Pall[loop2,2] then inc (Tmp[2]);
If Tmp[3]<Pall[loop2,3] then inc (Tmp[3]);
{ If the Red, Green or Blue values of color loop2 are less then they
should be, increase them by one. }
Pal (loop2,Tmp[1],Tmp[2],Tmp[3]);
{ Set the new, altered pallette color. }
END;
END;
END;

[C++]

void Fadeup() {

//This is temporary storage for the values of a color
unsigned char Tmp[3];

// A color value for Red, green or blue is 0 to 63, so this loop only
// need be executed a maximum of 64 times.
for(int loop1=0;loop1<64;loop1++) {

WaitRetrace();

for(int loop2=0; loop2<256; loop2++) {
GetPal(loop2,Tmp[0],Tmp[1],Tmp[2]);

// If the Red, Green or Blue values of color loop2 are less then they
// should be, increase them by one.
if ((Tmp[0] < Pall2[loop2][0]) && (Tmp[0] < 63)) Tmp[0]++;
if ((Tmp[1] < Pall2[loop2][1]) && (Tmp[1] < 63)) Tmp[1]++;
if ((Tmp[2] < Pall2[loop2][2]) && (Tmp[2] < 63)) Tmp[2]++;

// Set the new, altered pallette color.
Pal (loop2,Tmp[0],Tmp[1],Tmp[2]);
}
}
}

Hey-presto! The screen fades up. You can just add in a delay before the
waitretrace if you feel it is too fast. Cool, no?


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ How do I fade out a screen?

This is just like the fade in of a screen, just in the opposite direction.
What you do is you check each color intensity. If it is not yet zero, you
decrease it by one until it is. BAAASIIIC!

[Pascal]

Procedure FadeDown;
VAR loop1,loop2:integer;
Tmp : Array [1..3] of byte;
{ This is temporary storage for the values of a color }
BEGIN
For loop1:=1 to 64 do BEGIN
WaitRetrace;
For loop2:=0 to 255 do BEGIN
Getpal (loop2,Tmp[1],Tmp[2],Tmp[3]);
If Tmp[1]>0 then dec (Tmp[1]);
If Tmp[2]>0 then dec (Tmp[2]);
If Tmp[3]>0 then dec (Tmp[3]);
{ If the Red, Green or Blue values of color loop2 are not yet zero,
then, decrease them by one. }
Pal (loop2,Tmp[1],Tmp[2],Tmp[3]);
{ Set the new, altered pallette color. }
END;
END;
END;

[C++]

void FadeDown() {

// This is temporary storage for the values of a color
unsigned char Tmp[3];

for(int loop1=0; loop1<64; loop1++) {

WaitRetrace();

for(int loop2=0; loop2<256; loop2++) {
GetPal(loop2,Tmp[0],Tmp[1],Tmp[2]);

// If the Red, Green or Blue values of color loop2 are not yet zero,
// then, decrease them by one.
if (Tmp[0] > 0) Tmp[0]--;
if (Tmp[1] > 0) Tmp[1]--;
if (Tmp[2] > 0) Tmp[2]--;

// Set the new, altered pallette color.
Pal(loop2,Tmp[0],Tmp[1],Tmp[2]);
}
}
}

Again, to slow the above down, put in a delay above the WaitRetrace. Fading
out the screen looks SO much more impressive then just clearing the screen;
it can make a world of difference in the impression your demo etc will
leave on the people viewing it. To restore the pallette, just do this :

[Pascal]

Procedure RestorePallette;
VAR loop1:integer;
BEGIN
WaitRetrace;
For loop1:=0 to 255 do
pal (loop1,Pall[loop1,1],Pall[loop1,2],Pall[loop1,3]);
END;

[C++]

void RestorePallette() {

WaitRetrace();
for(int loop1=0; loop1<255; loop1++)
Pal(loop1,Pall2[loop1][0],Pall2[loop1][1],Pall2[loop1][2]);

}

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

Well, there are most of those origional questions answered ;-) The following
sample program is quite big, so it might take you a while to get around it.
Persevere and thou shalt overcome. Pallette manipulation has been a thorn
in many coders sides for quite some time, yet hopefully I have shown you
all how amazingly simple it is once you have grasped the basics.

I need more feedback! In which direction would you like me to head? Is there
any particular section you would like more info on? Also, upload me your
demo's, however trivial they might seem. We really want to get in contact
with/help out new and old coders alike, but you have to leave us that message
telling us about yourself and what you have done or want to do.

IS THERE ANYBODY OUT THERE!?!

P.S. Our new demo should be out soon ... it is going to be GOOOD ... keep
an eye out for it.

[ And so she came across him, slumped over his keyboard
yet again . 'It's three in the morning' she whispered.
'Let's get you to bed'. He stirred, his face bathed in
the dull light of his monitor. He mutters something.
As she leans across him to disconnect the power, she
asks him; 'Was it worth it?'. His answer surprises her.
'No.' he says. In his caffiene-enduced haze, he smiles.
'But it sure is a great way to relax.' ]
- Grant Smith
Tue 13 July, 1993
2:23 am.

See you next week!
- Denthor


--==[ PART 3 ]==--


þ Introduction

Greetings! This is the third part of the VGA Trainer series! Sorry it
took so long to get out, but I had a running battle with the traffic
department for three days to get my car registered, and then the MailBox
went down. Ahh, well, life stinks. Anyway, today will do some things
vital to most programs : Lines and circles.

Watch out for next week's part : Virtual screens. The easy way to
eliminate flicker, "doubled sprites", and subjecting the user to watch
you building your screen. Almost every ASPHYXIA demo has used a virtual
screen (with the exception of the SilkyDemo), so this is one to watch out
for. I will also show you how to put all of these loose procedures into
units.

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 in private mail here on
the Mailbox BBS.
2) Write a message here in the Programming conference here
on the Mailbox (Preferred if you have a general
programming query or problem others would benefit from)
3) Write to ASPHYXIA on the ASPHYXIA BBS.
4) Write to Denthor, Eze or Livewire on Connectix.
5) Write to : Grant Smith
P.O.Box 270 Kloof
3640
6) Call me (Grant Smith) at 73 2129 (leave a message if you
call during varsity)

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!


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Circle Algorithim

You all know what a circle looks like. But how do you draw one on the
computer?

You probably know circles drawn with the degrees at these points :

0
ÜÛ|ÛÜ
ÛÛÛ|ÛÛÛ
270 ----+---- 90
ÛÛÛ|ÛÛÛ
ßÛ|Ûß
180

Sorry about my ASCII ;-) ... anyway, Pascal and C++ don't work that way ...
they work with radians instead of degrees. (You can convert radians to
degrees, but I'm not going to go into that now. Note though that in Pascal
and C++, the circle goes like this :

270
ÜÛ|ÛÜ
ÛÛÛ|ÛÛÛ
180 ----+---- 0
ÛÛÛ|ÛÛÛ
ßÛ|Ûß
90


Even so, we can still use the famous equations to draw our circle ...
(You derive the following by using the theorem of our good friend
Pythagoras)
Sin (deg) = Y/R
Cos (deg) = X/R
(This is standard 8(?) maths ... if you haven't reached that level yet,
take this to your dad, or if you get stuck leave me a message and I'll
do a bit of basic Trig with you. I aim to please ;-))

Where Y = your Y-coord
X = your X-coord
R = your radius (the size of your circle)
deg = the degree

To simplify matters, we rewrite the equation to get our X and Y values :

Y = R*Sin(deg)
X = R*Cos(deg)

This obviousy is perfect for us, because it gives us our X and Y co-ords to
put into our Putpixel routine (see Part 1). Because the Sin and Cos
functions return a Real [long] value, we use a round function to transform
it into an Integer.

[Pascal]

Procedure Circle (oX,oY,rad:integer;Col:Byte);
VAR deg:real;
X,Y:integer;
BEGIN
deg:=0;
repeat
X:=round(rad*COS (deg));
Y:=round(rad*sin (deg));
putpixel (x+ox,y+oy,Col);
deg:=deg+0.005;
until (deg>6.4);
END;

[C++]

void Circle(int X, int Y, int rad, int col) {

float deg = 0;

do {
X = round(rad * cos(deg));
Y = round(rad * sin(deg));
Putpixel (X+160, Y+100, col);
deg += 0.005;
}
while (deg <= 6.4);

}

In the above example, the smaller the amount that deg is increased by,
the closer the pixels in the circle will be, but the slower the procedure.
0.005 seem to be best for the 320x200 screen. NOTE : ASPHYXIA does not use
this particular circle algorithm, ours is in assembly language, but this
one should be fast enough for most. If it isn't, give us the stuff you are
using it for and we'll give you ours.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Line algorithms

There are many ways to draw a line on the computer. I will describe one
and give you two. (The second one you can figure out for yourselves; it
is based on the first one but is faster)

The first thing you need to do is pass what you want the line to look
like to your line procedure. What I have done is said that x1,y1 is the
first point on the screen, and x2,y2 is the second point. We also pass the
color to the procedure. (Remember the screens top left hand corner is (0,0);
see Part 1)

Ie. o (X1,Y1)
ooooooooo
ooooooooo
oooooooo (X2,Y2)

Again, sorry about my drawings ;-)

To find the length of the line, we say the following :

[Pascal]

XLength = ABS (x1-x2)
YLength = ABS (y1-y2)

[C++]

xlength = abs(x1-x2);
ylength = abs(y1-y2);

The ABS function means that whatever the result, it will give you an
absolute, or positive, answer. At this stage I set a variable stating
wheter the difference between the two x's are negative, zero or positive.
(I do the same for the y's) If the difference is zero, I just use a loop
keeping the two with the zero difference positive, then exit.

If neither the x's or y's have a zero difference, I calculate the X and Y
slopes, using the following two equations :

[Pascal]

Xslope = Xlength / Ylength
Yslope = Ylength / Xlength

[C++]

[Note: C++ is significantly different here. I had to add special divide
by zero checking. C++ doesn't like x/0. I also added in type-casting
which might not have been necessary. :)]

if ((xlength != 0) && (ylength != 0)) {
xslope = (float)xlength/(float)ylength;
yslope = (float)ylength/(float)xlength;
}
else {
xslope = 0.0;
yslope = 0.0;
}

As you can see, the slopes are real numbers.
NOTE : XSlope = 1 / YSlope

Now, there are two ways of drawing the lines :

X = XSlope * Y
Y = YSlope * X

The question is, which one to use? if you use the wrong one, your line
will look like this :

o
o
o

Instead of this :

ooo
ooo
ooo

Well, the solution is as follows :

*\``|``/*
***\|/***
----+----
***/|\***
*/``|``\*

If the slope angle is in the area of the stars (*) then use the first
equation, if it is in the other section (`) then use the second one.
What you do is you calculate the variable on the left hand side by
putting the variable on the right hand side in a loop and solving. Below
is our finished line routine :

[Pascal]

Procedure Line (x1,y1,x2,y2:integer;col:byte);
VAR x,y,xlength,ylength,dx,dy:integer;
xslope,yslope:real;
BEGIN
xlength:=abs (x1-x2);
if (x1-x2)<0 then dx:=-1;
if (x1-x2)=0 then dx:=0;
if (x1-x2)>0 then dx:=+1;
ylength:=abs (y1-y2);
if (y1-y2)<0 then dy:=-1;
if (y1-y2)=0 then dy:=0;
if (y1-y2)>0 then dy:=+1;
if (dy=0) then BEGIN
if dx<0 then for x:=x1 to x2 do
putpixel (x,y1,col);
if dx>0 then for x:=x2 to x1 do
putpixel (x,y1,col);
exit;
END;
if (dx=0) then BEGIN
if dy<0 then for y:=y1 to y2 do
putpixel (x1,y,col);
if dy>0 then for y:=y2 to y1 do
putpixel (x1,y,col);
exit;
END;
xslope:=xlength/ylength;
yslope:=ylength/xlength;
if (yslope/xslope<1) and (yslope/xslope>-1) then BEGIN
if dx<0 then for x:=x1 to x2 do BEGIN
y:= round (yslope*x);
putpixel (x,y,col);
END;
if dx>0 then for x:=x2 to x1 do BEGIN
y:= round (yslope*x);
putpixel (x,y,col);
END;
END
ELSE
BEGIN
if dy<0 then for y:=y1 to y2 do BEGIN
x:= round (xslope*y);
putpixel (x,y,col);
END;
if dy>0 then for y:=y2 to y1 do BEGIN
x:= round (xslope*y);
putpixel (x,y,col);
END;
END;
END;

[C++]

void Line2(int x1, int y1, int x2, int y2, int col) {

int x, y, xlength, ylength, dx, dy;
float xslope, yslope;

xlength = abs(x1-x2);
if ((x1-x2) < 0) dx = -1;
if ((x1-x2) == 0) dx = 0;
if ((x1-x2) > 0) dx = +1;

ylength = abs(y1-y2);
if ((y1-y2) < 0) dy = -1;
if ((y1-y2) == 0) dy = 0;
if ((y1-y2) > 0) dy = +1;

if (dy == 0) {
if (dx < 0)
for (x=x1; x<x2+1; x++)
Putpixel (x,y1,col);
if (dx > 0)
for (x=x2; x<x1+1; x++)
Putpixel (x,y1,col);
}

if (dx == 0) {
if (dy < 0)
for (y=y1; y<y2+1; y++)
Putpixel (x1,y,col);
if (dy > 0)
for (y=y2; y<y1+1; y++)
Putpixel (x1,y,col);
}

if ((xlength != 0) && (ylength != 0)) {
xslope = (float)xlength/(float)ylength;
yslope = (float)ylength/(float)xlength;
}
else {
xslope = 0.0;
yslope = 0.0;

  
}

if ((xslope != 0) && (yslope != 0) &&
(yslope/xslope < 1) && (yslope/xslope > -1)) {
if (dx < 0)
for (x=x1; x<x2+1; x++) {
y = round (yslope*x);
Putpixel (x,y,col);
}
if (dx > 0)
for (x=x2; x<x1+1; x++) {
y = round (yslope*x);
Putpixel (x,y,col);
}
}
else {
if (dy < 0)
for (y=x1; y<x2+1; y++) {
x = round (xslope*y);
Putpixel (x,y,col);
}
if (dy > 0)
for (y=x2; y<x1+1; y++) {
x = round (xslope*y);
Putpixel (x,y,col);
}
}

}

Quite big, isn't it? Here is a much shorter way of doing much the same
thing :

[Pascal]

function sgn(a:real):integer;
begin
if a>0 then sgn:=+1;
if a<0 then sgn:=-1;
if a=0 then sgn:=0;
end;

procedure line(a,b,c,d,col:integer);
var u,s,v,d1x,d1y,d2x,d2y,m,n:real;
i:integer;
begin
u:= c - a;
v:= d - b;
d1x:= SGN(u);
d1y:= SGN(v);
d2x:= SGN(u);
d2y:= 0;
m:= ABS(u);
n := ABS(v);
IF NOT (M>N) then
BEGIN
d2x := 0 ;
d2y := SGN(v);
m := ABS(v);
n := ABS(u);
END;
s := INT(m / 2);
FOR i := 0 TO round(m) DO
BEGIN
putpixel(a,b,col);
s := s + n;
IF not (s<m) THEN
BEGIN
s := s - m;
a:= a +round(d1x);
b := b + round(d1y);
END
ELSE
BEGIN
a := a + round(d2x);
b := b + round(d2y);
END;
end;
END;

[C++]

int sgn (long a) {
if (a > 0) return +1;
else if (a < 0) return -1;
else return 0;

}

void Line(int a, int b, int c, int d, int col) {

long u,s,v,d1x,d1y,d2x,d2y,m,n;
int i;

u = c-a;
v = d-b;
d1x = sgn(u);
d1y = sgn(v);
d2x = sgn(u);
d2y = 0;
m = abs(u);
n = abs(v);

if (m<=n) {
d2x = 0;
d2y = sgn(v);
m = abs(v);
n = abs(u);
}

s = (int)(m / 2);

for (i=0;i<round(m);i++) {
Putpixel(a,b,col);
s += n;
if (s >= m) {
s -= m;
a += d1x;
b += d1y;
}
else {
a += d2x;
b += d2y;
}
}

}

This routine is very fast, and should meet almost all of your requirements
(ASPHYXIA used it for quite a while before we made our new one.)
In the end program, both the new line routine and the circle routine are
tested. A few of the procedures of the first parts are also used.

Line and circle routines may seem like fairly trivial things, but they are
a vital component of many programs, and you may like to look up other
methods of drawing them in books in the library (I know that here at the
varsity they have books for doing this kind of stuff all over the place)
A good line routine to look out for is the Bressenhams line routine ...
there is a Bressenhams circle routine too ... I have documentaiton for them
if anybody is interested, they are by far some of the fastest routines
you will use.

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

Varsity has started again, so I am (shock) going to bed before three in
the morning, so my quote this week wasn't written in the same wasted way
my last weeks one was (For last week's one, I had gotten 8 hours sleep in
3 days, and thought up and wrote the quote at 2:23 am before I fell asleep.)

[ "What does it do?" she asks.
"It's a computer," he replies.
"Yes, dear, but what does it do?"
"It ..er.. computes! It's a computer."
"What does it compute?"
"What? Er? Um. Numbers! Yes, numbers!" He smiles
worriedly.
"Why?"
"Why? Well ..um.. why?" He starts to sweat.
"I mean, is it just something to dust around, or does
it actually do something useful?"

"Um...you can call other computers with it!" Hope lights
up his eyes. "So you can get programs from other computers!"
"I see. Tell me, what do these programs do?"
"Do? I don't think I fol..."
"I see. They compute. Numbers. For no particular reason." He
withers under her gaze.
"Yes, but..."
She smiles, and he trails off, defeated. She takes another look
at the thing. "Although," she says, with a strange look in
her eyes. He looks up, an insane look of hope on his
face. "Does it come in pink?" she asks.
]
- Grant Smith
Tue 27 July, 1993
9:35 pm.

See you next time,
- Denthor

--==[ PART 4 ]==--


þ Introduction

Howdy all! Welcome to the fourth part of this trainer series! It's a little
late, but I am sure you will find that the wait was worth it, becase today I
am going to show you how to use a very powerful tool : Virtual Screens.

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 in private mail here on
the Mailbox BBS.
2) Write a message here in the Programming conference here
on the Mailbox (Preferred if you have a general
programming query or problem others would benefit from)
3) Write to ASPHYXIA on the ASPHYXIA BBS.
4) Write to Denthor, Eze or Livewire on Connectix.
5) Write to : Grant Smith
P.O.Box 270 Kloof
3640
6) Call me (Grant Smith) at 73 2129 (leave a message if you
call during varsity)

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!


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ What is a Virtual Screen and why do we need it?

Let us say you are generating a complex screen numerous times on the fly
(for example scrolling up the screen then redrawing all the sprites for
each frame of a game you are writing.) Do you have any idea how awful it
would look if the user could actually see you erasing and redrawing each
sprite for each frame? Can you visualise the flicker effect this would
give off? Do you realise that there would be a "sprite doubling" effect
(where you see two copies of the same sprite next to each other)? In the
sample program I have included a part where I do not use virtual screens
to demonstrate these problems. Virtual screens are not the only way to
solve these problems, but they are definately the easiest to code in.

A virtual screen is this : a section of memory set aside that is exactly
like the VGA screen on which you do all your working, then "flip" it
on to your true screen. In EGA 640x350x16 you automatically have a
virtual page, and it is possible to have up to four on the MCGA using a
particular tweaked mode, but for our puposes we will set one up using base
memory.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Setting up a virtual screen

As you will have seen in the first part of this trainer series, the MCGA
screen is 64000 bytes big (320x200=64000). You may also have noticed that
in TP 6.0 [and C++] you arn't allowed too much space for normal variables.
For example, saying :

[Pascal]

VAR Virtual : Array [1..64000] of byte;

[C++]

unsigned char Virtual[64000];

would be a no-no, as you wouldn't have any space for your other variables.
What is the solution? I hear you enquiring minds cry. The answer : pointers!
Pointers to not use up the base 64k allocated to you by TP 6.0 [and C++], it
gets space from somewhere else in the base 640k memory of your computer. Here
is how you set them up :

[Pascal]

Type Virtual = Array [1..64000] of byte; { The size of our Virtual Screen }
VirtPtr = ^Virtual; { Pointer to the virtual screen }

VAR Virscr : VirtPtr; { Our first Virtual screen }
Vaddr : word; { Segment of our virtual screen }

[C++] [Note: For simplicity's sake, I have decided to handle the memory
allocation differently in the translation to C++ code]

unsigned char *vaddr = NULL; // Pointer to the virtual screen //

If you put this in a program as it stands, and try to acess VirScr [or vaddr],
your machine will probably crash. Why? Because you have to get the memory for
your pointers before you can acess them! You do that as follows :

[Pascal]

Procedure SetUpVirtual;
BEGIN
GetMem (VirScr,64000);
vaddr := seg (virscr^);
END;

[C++]

void SetUpVirtual() {
vaddr = (unsigned char *) calloc(64000,1);
}

This procedure has got the memory for the screen, then set vaddr to the
screens segment. DON'T EVER LEAVE THIS PROCEDURE OUT OF YOUR PROGRAM!
If you leave it out, when you write to your virtual screen you will probably
be writing over DOS or some such thing. Not a good plan ;-).

When you have finished your program, you will want to free the memory
taken up by the virtual screen by doing the following :

[Pascal]

Procedure ShutDown;
BEGIN
FreeMem (VirScr,64000);
END;

[C++]

void ShutDown() {
free(vaddr);
}

If you don't do this your other programs will have less memory to use for
themselves.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Putting a pixel to your virtual screen

This is very similar to putting a pixel to your normal MCGA screen, as
discussed in part one... here is our origonal putpixel :

[Pascal]

Procedure PutPixel (X,Y : Integer; Col : Byte);
BEGIN
Mem [VGA:X+(Y*320)]:=col;
END;

[C++]

void Putpixel (int x, int y, unsigned char Col) {
memset(vga+(x+(y*320)),Col,1);
}

For our virtual screen, we do the following :

[Pascal]

Procedure VirtPutPixel (X,Y : Integer; Col : Byte);
BEGIN
Mem [Vaddr:X+(Y*320)]:=col;
END;

[C++]

void Putpixel (int x, int y, unsigned char Col) {
memset(vaddr+(x+(y*320)),Col,1);
}


It seems quite wasteful to have two procedures doing exactly the same thing,
just to different screens, doesn't it? So why don't we combine the two like
this :

[Pascal]

Procedure PutPixel (X,Y : Integer; Col : Byte; Where : Word);
BEGIN
Mem [Where:X+(Y*320)]:=col;
END;

[C++]

void Putpixel (int x, int y, unsigned char Col, unsigned char *Where) {
memset(Where+(x+(y*320)),Col,1);
}

To use this, you will say something like :

Putpixel (20,20,32,VGA);
PutPixel (30,30,64,Vaddr);

These two statements draw two pixels ... one to the VGA screen and one to
the virtual screen! Doesn't that make you jump with joy! ;-) You will
have noticed that we still can't actually SEE the virtual screen, so on to
the next part ...

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ How to "Flip" your virtual screen on to the true screen

You in fact already have to tools to do this yourselves from information
in the previous parts of this trainer series. We will of course use the
Move [_fmemcpy] command, like so :

[Pascal]

Move (Virscr^,mem [VGA:0],64000);

[C++]

_fmemcpy(vga,vaddr,64000);

simple, eh? You may want to wait for a verticle retrace (Part 2) before you
do that, as it may make the flip much smoother (and, alas, slower).

Note that most of our other procedures may be altered to support the
virtual screen, such as Cls etc. (see Part 1 of this series), using the
methoods described above (I have altered the CLS procedure in the sample
program given at the end of this Part.)

We of ASPHYXIA have used virtual screens in almost all of our demos.
Can you imagine how awful the SoftelDemo would have looked if you had to
watch us redrawing the moving background, text and vectorballs for EACH
FRAME? The flicker, doubling effects etc would have made it awful! So
we used a virtual screen, and are very pleased with the result.
Note, though, that to get the speed we needed to get the demo fast enough,
we wrote our sprites routines, flip routines, pallette routines etc. all
in assembly. The move command is very fast, but not as fast as ASM ;-)

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

I am writing this on the varsity computers in between lectures. I prefer
writing & coding between 6pm and 4am, but it isn't a good plan when
varsity is on ;-), so this is the first part of the trainer series ever
written before 9pm.

I have been asked to do a part on scrolling the screen, so that is
probably what I will do for next week. Also, ASPHYXIA will soon be putting
up a small demo with source on the local boards. It will use routines
that we have discussed in this series, and demonstrate how powerful these
routines can be if used in the correct manner.

Some projects for you to do :
1) Rewrite the flip statement so that you can say :
flip (Vaddr,VGA);
flip (VGA,Vaddr);
( This is how ASPHYXIAS one works )

2) Put most of the routines (putpixel, cls, pal etc.) into a unit,
so that you do not need to duplicate the procedures in each program
you write. If you need help, leave me mail.


See you next week
- Denthor


--==[ PART 5 ]==--


þ Introduction

Hello! This is Denthor here with the 5 part of the ASPHYXIA VGA Trainer
Series : The Scrolling Saga. I have had many requests for information on
scrolling, so I decided to make it this weeks topic. Note that I do make
reference to my recently released program TEXTER5, which should be available
from wherever you get this message. (Note to Sysops : If you put the trainer
series up on your boards, please add WORMIE.ZIP and TEXTER5.ZIP as they
both suppliment this series)

By the way, sorry for the delay in the appearance of this part. Tests,
projects and a few wild days of sin at the Wild Coast all conspired
against the prompt appearance of this part. Also note I need more input as
to what I should do future parts on, so leave me mail.

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 in private mail here on
the Mailbox BBS.
2) Write a message here in the Programming conference here
on the Mailbox (Preferred if you have a general
programming query or problem others would benefit from)
3) Write to ASPHYXIA on the ASPHYXIA BBS.
4) Write to Denthor, Eze or Livewire on Connectix.
5) Write to : Grant Smith
P.O.Box 270 Kloof
3640
Natal
6) Call me (Grant Smith) at 73 2129 (leave a message if you
call during varsity)

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!


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ What is scrolling?

If you have ever seen a demo, you have probably seen some form of scrolling.
Our SILKYDEMO has quite a nice example of scrolling. What it is is a long
row of text moving across your screen, usually from right to left, eg :

H : Step 1
He : Step 2
Hel : Step 3
Hell : Step 4
Hello : Step 5
Hello : Step 6

etc. etc. See the program attatched for an example of scrolling.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ What do we scroll?

Usually, letters. Most groups put greetings and information in their
'scrollies', as they are termed. You can also scroll and entire screen
using the scrolling technique. Scrolling your text is a hell of a lot
less boring then just having it appear on your screen. Unfortunately,
'scrollies' have been used so many times in demos they are wearing a
bit thin, so usually they are accompanied by a cool picture or some nice
routine happening at the same time (In our SILKYDEMO we had a moving
checkerboard and colour bars going at the same time).

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ How do we scroll from side to side?

The theory behind scrolling is quite easy. Let us imagine that we are
scrolling a 16x16 font grabbed by TEXTER (;-)) across the top of the
screen (ie. 320 pixels) As we know, the VGA screen starts at zero at the
top left hand part of the screen, then counts up to the right to 319, then
goes back to the left hand side one pixel down at 320. (See Tut 1) This means
that a 16*320 scroller takes up the space 0 to 5119 on the screen. In ascii
this looks like this :

(0) . . (319)
(320) . . (639)
" " "
(4800) . . (5119)

Simple enough. Now what we do is we put down the first Y-line of the first
character onto the very right hand side of the screen , like so :

[Pascal]

For loop1:=1 to 16 do
Putpixel (319,loop1-1,font['A',1,loop1],vga);

[C++]

for (loop1=0; loop1<16; loop1++)
Putpixel (319,loop1,Font['A'][0][loop1],vga);

This will draw some stuff on the very right hand side. Your screen should now
look like this :

(0) . X. (319)
(320) . X. (639)
"
" "
(4800) . X. (5119)

Next, we move each line one to the left, ie :

[Pascal]

For loop1:=0 to 15 do
Move (mem[VGA:loop1*320+1], mem[VGA:loop1*320], 320);

[C++]

for (loop1=0; loop1<16; loop1++)
memcpy (vga+(loop1*320) , vga+(1+(loop1*320)),320);


This scrolls the screen from right to left, which is the easiest to read.
To scroll the screen from left to right, swap the +1 onto the other side
of the command. Also, to increase the size of the portion scrolled, increase
the 15 to however many lines from the top you wish to scroll-1.

After this move, your screen will look like this :

(0) . X . (319)
(320) . X . (639)
" " "
(4800) . X . (5119)
^
Note this space


What you then do is draw in the next line on the right hand side, move it,
draw the next line, move it etc. etc. Tah-Dah! You have a scrolly! Fairly
simple, isn't it?

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ How do we scroll up or down?

To scroll up or down is also fairly simple. This can be used for 'movie
credit' endings (I once wrote a little game with a desert scrolling down
with you being a little robot on the bottom of the screen). The theory is
this : Draw the top line (or bottom line) then move the entire screen :

[Pascal]

Move (mem[vga:0],mem[vga:320],63680);
{ 64000 - 320 = 63680 }

[C++]

memcpy (vga, vga+320, 63680);

For scrolling down, or :

[Pascal]

Move (mem[vga:320],mem[vga:0],63680);

[C++]

memcpy (vga+320, vga, 63680);

For scrolling up. You then draw the next line and repeat.

Because of the simplicity of coding in a scrolly, most demos have one. It
is usually best to have something extra happening on the screen so that
the viewer doesn't get too bored, even, as I say, if it is only a really nice
picture.

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

The University of Natal, Durban, Science Dept., now has 10 new 486's!
This is a great boon, as now I can program nice routines during frees
(even though I am a Commerce Student (Shhhhh) ;-) ). I can now use those
previously wasted hours that I spent socialising and making friends
coding instead ;-)

I suggest you get a copy of TEXTER, for coding demos with fonts, or in fact
almost any graphics application, it is an amazing help, and we have used it
for *ALL* our demos. (P.S. We have written many demos, but many have been
written for companies and have not been released for the general public)
NOTE : For TEXTER's test program TEST.PAS, add {$X+} {$R-} if you have range
checking on (I code with it off.)

[ "
I'm from the Computer Inspection Agency, sir,
I'm here to check your computer. Here is
my identification."
"
Certainly. Have a look, I'm clean. I don't have
any pirated software."
The C-man pushes past him and sits in front of the
computer. He notes the fact that the computer
is currently off with a look of disdain. He
makes a note on his clipboard. He boots up.
"
What is this?" he asks, pointing at the screen.
"
It's MasterMenu" stutters the man. "I wrote it
myself!"
"
Do you know what the penalty is for using junk
like this on a private machine?" The C-man smiles.
"
This is a two-month sentance in itself!"
"
I'm sorry sir! It won't happen again!"
"
I know. I'll make sure of that." He smiles again.
The C-man runs through the hard drive, checking for
illeagal software, bad programs and anti-government
propaganda. He notes with satisfaction that he has
enough to put this weenie away for ten years, not that
it mattered. He usually could just make something up.
He comes to the last entry on the aphebetised menu tree.
His hands jerk away from the keyboard. Then, tentatively,
he types in the three letters of doom. He looks at the
man, who is backing away with wide eyes and his hands
outstretched in front of him, as if to ward off a blow.
The C-man smiles, his lips a thin, hard line.
"
Windows!"
]
- Grant Smith
1:55pm
16/9/93

Cheers,
- Denthor


--==[ PART 6 ]==--


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

Hi there! I'm back, with the latest part in the series : Pregenerated
arrays. This is a fairly simple concept that can treble the speed of
your code, so have a look.

I still suggest that if you haven't got a copy of TEXTER that you get it.
This is shareware, written by me, that allows you to grab fonts and use
them in your own programs.

I downloaded the Friendly City BBS Demo, an intro for a PE BBS, written
by a new group called DamnRite, with coder Brett Step. The music was
excellent, written by Kon Wilms (If I'm not mistaken, he is an Amiga
weenie ;-)). A very nice first production, and I can't wait to see more
of their work. I will try con a local BBS to allow me to send Brett some
fido-mail.

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 in private mail here on
the Mailbox BBS.
2) Write a message here in the Programming conference here
on the Mailbox (Preferred if you have a general
programming query or problem others would benefit from)
3) Write to ASPHYXIA on the ASPHYXIA BBS.
4) Write to Denthor, Eze or Livewire on Connectix.
5) Write to : Grant Smith
P.O.Box 270 Kloof
3640
Natal
6) Call me (Grant Smith) at (031) 73 2129 (leave a message if you
call during varsity)

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!


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Why do I need a lookup table? What is it?

A lookup table is an imaginary table in memory where you look up the
answers to certain mathematical equations instead of recalculating them
each time. This may speed things up considerably. Please note that a
lookup table is sometimes referred to as a pregenerated array.

One way of looking at a lookup table is as follows : Let us say that for
some obscure reason you need to calculate a lot of multiplications (eg.
5*5 , 7*4 , 9*2 etc.). Instead of actually doing a slow multiply each
time, you can generate a kind of bonds table, as seen below :


ÉÍÑÍËÍÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍ»
ÇÄÅĶ 1 ³ 2 ³ 3 ³ 4 ³ 5 ³ 6 ³ 7 ³ 8 ³ 9 º
ÇÄÁÄ×ÍÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍ͵
º 1 º 1 ³ 2 ³ 3 ³ 4 ³ 5 ³ 6 ³ 7 ³ 8 ³ 9 ³
ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´
º 2 º 2 ³ 4 ³ 6 ³ 8 ³ 10 ³ 12 ³ 14 ³ 16 ³ 18 ³
ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´
º 3 º 3 ³ 6 ³ 9 ³ 12 ³ 15 ³ 18 ³ 21 ³ 24 ³ 27 ³
ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´
º 4 º 4 ³ 8 ³ 12 ³ 16 ³ 20 ³ 24 ³ 28 ³ 32 ³ 36 ³
ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´
º 5 º 5 ³ 10 ³ 15 ³ 20 ³ 25 ³ 30 ³ 35 ³ 40 ³ 45 ³
ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´
º 6 º 6 ³ 12 ³ 18 ³ 24 ³ 30 ³ 36 ³ 42 ³ 48 ³ 54 ³
ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´
º 7 º 7 ³ 14 ³ 21 ³ 28 ³ 35 ³ 42 ³ 49 ³ 56 ³ 63 ³
ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´
º 8 º 8 ³ 16 ³ 24 ³ 32 ³ 40 ³ 48 ³ 56 ³ 64 ³ 72 ³
ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´
º 9 º 9 ³ 18 ³ 27 ³ 36 ³ 45 ³ 54 ³ 63 ³ 72 ³ 81 ³
ÈÍÍÍÊÄÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÙ

This means that instead of calculating 9*4, you just find the 9 on the
top and the 4 on the side, and the resulting number is the answer. This
type of table is very useful when the equations are very long to do.

The example I am going to use for this part is that of circles. Cast
your minds back to Part 3 on lines and circles. The circle section took
quite a while to finish drawing, mainly because I had to calculate the
SIN and COS for EVERY SINGLE POINT. Calculating SIN and COS is obviously
very slow, and that was reflected in the speed of the section.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ How do I generate a lookup table?

[Note: the following paragraph contains a significant error. In the
middle of the paragraph, Denthor says that 360 / 0.4 = 8000. Last I
checked, 360 / 0.4 = 900. As a result, I have changed the C++ code to
reflect this. However, I have left this document fairly unaltered.
So whenever you see a Denthor reference to 8000, you now know why I am
using 900.]

This is very simple. In my example, I am drawing a circle. A circle has
360 degrees, but for greater accuracy, to draw my circle I will start
with zero and increase my degrees by 0.4. This means that in each circle
there need to be 8000 SINs and COSes (360/0.4=8000). Putting these into
the base 64k that Pascal allocates for normal variables is obviously not
a happening thing, so we define them as pointers in the following manner:

[Pascal]

TYPE table = Array [1..8000] of real;

VAR sintbl : ^table;
costbl : ^table;

[C++]

const TABLESIZE = 900;

float *costbl=NULL;
float *sintbl=NULL;

Then in the program we get the memory for these two pointers. Asphyxia
was originally thinking of calling itself Creative Reboot Inc., mainly
because we always forgot to get the necessary memory for our pointers.
(Though a bit of creative assembly coding also contributed to this. We
wound up rating our reboots on a scale of 1 to 10 ;-)). The next obvious
step is to place our necessary answers into our lookup tables. This can
take a bit of time, so in a demo, you would do it in the very beginning
(people just think it's slow disk access or something), or after you
have shown a picture (while the viewer is admiring it, you are
calculating pi to its 37th degree in the background ;-)) Another way of
doing it is, after calculating it once, you save it to a file which you
then load into the variable at the beginning of the program. Anyway,
this is how we will calculate the table for our circle :

[Pascal]

Procedure Setup;
VAR deg:real;
BEGIN
deg:=0;
for loop1:=1 to 8000 do BEGIN
deg:=deg+0.4;
costbl^[loop1]:=cos (rad(deg));
sintbl^[loop1]:=sin (rad(deg));
END;
END;

[C++] [Note: I have included "
Setup" within the LookupCirc() function]

deg = 0.0;
for (loop1=0;loop1<TABLESIZE;loop1++) {
costbl[loop1] = cos(rad(deg));
sintbl[loop1] = sin(rad(deg));
deg += 0.4;
}

This will calculate the needed 16000 reals and place them into our two
variables. The amount of time this takes is dependant on your computer.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ How do I use a lookup table?

This is very easy. In your program, wherever you put

[Pascal] cos (rad(deg))
[C++] cos (rad(deg))

you just replace it with :

[Pascal] costbl^[deg]
[C++] costbl[deg]

Easy, no? Note that the new "
deg" variable is now an integer, always
between 1 and 8000.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Where else do I use lookup tables?

Lookup tables may be used in many different ways. For example, when
working out 3-dimensional objects, sin and cos are needed often, and are
best put in a lookup table. In a game, you may pregen the course an
enemy may take when attacking. Even saving a picture (for example, a
plasma screen) after generating it, then loading it up later is a form
of pregeneration.

When you feel that your program is going much too slow, your problems
may be totally sorted out by using a table. Or, maybe not. ;-)


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

As you have seen above, lookup tables aren't all that exciting, but they
are useful and you need to know how to use them. The attached sample
program will demonstrate just how big a difference they can make.

Keep on coding, and if you finish anything, let me know about it! I
never get any mail, so all mail is greatly appreciated ;-)

Sorry, no quote today, it's hot and I'm tired. Maybe next time ;-)

- Denthor


--==[ PART 7 ]==--

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

Hello! By popular request, this part is all about animation. I will be
going over three methods of doing animation on a PC, and will
concerntrate specifically on one, which will be demonstrated in the
attached sample code.

Although not often used in demo coding, animation is usually used in
games coding, which can be almost as rewarding ;-)

In this part I will also be much more stingy with assembler code :)
Included will be a fairly fast pure assembler putpixel, an asm screen
flip command, an asm icon placer, an asm partial-flip and one or two
others. I will be explaining how these work in detail, so this may also
be used as a bit of an asm-trainer too.

By the way, I apologise for this part taking so long to be released, but
I only finished my exams a few days ago, and they of course took
preference ;-). I have also noticed that the MailBox BBS is no longer
operational, so the trainer will be uploaded regularly to the BBS lists
shown at the end of this tutorial.

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 a message in the Programming conference on the
For Your Eyes Only BBS (of which I am the Moderator )
This is preferred if you have a general programming query
or problem others would benefit from.
4) Write to Denthor, Eze or Livewire on Connectix.
5) Write to : Grant Smith
P.O.Box 270 Kloof
3640
Natal
6) Call me (Grant Smith) at (031) 73 2129 (leave a message if you
call during varsity)
7) Write to mcphail@beastie.cs.und.ac.za on InterNet, and
mention the word Denthor near the top of the letter.

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!



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ The Principals of Animation

I am sure all of you have seen a computer game with animation at one or
other time. There are a few things that an animation sequence must do in
order to give an impression of realism. Firstly, it must move,
preferably using different frames to add to the realism (for example,
with a man walking you should have different frames with the arms an
legs in different positions). Secondly, it must not destroy the
background, but restore it after it has passed over it.

This sounds obvious enough, but can be very difficult to code when you
have no idea of how to go about achieving that.

In this trainer I will discuss various methods of meeting these two
objectives.



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Frames and Object Control

It is quite obvious that for most animation to succeed, you must have
numerous frames of the object in various poses (such as a man with
several frames of him walking). When shown one after the other, these
give the impression of natural movement.

So, how do we store these frames? I hear you cry. Well, the obvious
method is to store them in arrays. After drawing a frame in Autodesk
Animator and saving it as a .CEL, we usually use the following code to
load it in :

TYPE icon = Array [1..50,1..50] of byte;

VAR tree : icon;

Procedure LoadCEL (FileName : string; ScrPtr : pointer);
var
Fil : file;
Buf : array [1..1024] of byte;
BlocksRead, Count : word;
begin
assign (Fil, FileName);
reset (Fil, 1);
BlockRead (Fil, Buf, 800); { Read and ignore the 800 byte header }
Count := 0; BlocksRead := $FFFF;
while (not eof (Fil)) and (BlocksRead <> 0) do begin
BlockRead (Fil, mem [seg (ScrPtr^): ofs (ScrPtr^) + Count], 1024, BlocksRead);
Count := Count + 1024;
end;
close (Fil);
end;

BEGIN
Loadcel ('Tree.CEL',addr (tree));
END.

[Note: This could eaily be converted to C++, but I just finished converting
that huge program and don't feel like doing so. :) If you don't know Pascal,
you'll just have to struggle through this one.]

We now have the 50x50 picture of TREE.CEL in our array tree. We may access
this array in the usual manner (eg. col:=tree [25,30]). If the frame is
large, or if you have many frames, try using pointers (see previous
parts)

Now that we have the picture, how do we control the object? What if we
want multiple trees wandering around doing their own thing? The solution
is to have a record of information for each tree. A typical data
structure may look like the following :

TYPE Treeinfo = Record
x,y:word; { Where the tree is }
speed:byte; { How fast the tree is moving }
Direction:byte; { Where the tree is facing }
frame:byte { Which animation frame the tree is
currently involved in }
active:boolean; { Is the tree actually supposed to be
shown/used? }
END;

VAR Forest : Array [1..20] of Treeinfo;

You now have 20 trees, each with their own information, location etc.
These are accessed using the following means :
Forest [15].x:=100;
This would set the 15th tree's x coordinate to 100.



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Restoring the Overwritten Background

I will discuss three methods of doing this. These are NOT NECESSARILY
THE ONLY OR BEST WAYS TO DO THIS! You must experiment and decide which
is the best for your particular type of program.

METHOD 1 :

Step 1 : Create two virtual pages, Vaddr and Vaddr2.
[Note: the C++ version uses Vaddr1 and Vaddr2]
Step 2 : Draw the background to Vaddr2.
Step 3 : Flip Vaddr2 to Vaddr.
Step 4 : Draw all the foreground objects onto Vaddr.
Step 5 : Flip Vaddr to VGA.
Step 6 : Repeat from 3 continuously.

In ascii, it looks like follows ...

+---------+ +---------+ +---------+
| | | | | |
| VGA | <======= | VADDR | <====== | VADDR2 |
| | | (bckgnd)| | (bckgnd)|
| | |+(icons) | | |
+---------+ +---------+ +---------+

The advantages of this approach is that it is straightforward, continual
reading of the background is not needed, there is no flicker and it is
simple to implement. The disadvantages are that two 64000 byte virtual
screens are needed, and the procedure is not very fast because of the
slow speed of flipping.


METHOD 2 :

Step 1 : Draw background to VGA.
Step 2 : Grab portion of background that icon will be placed on.
Step 3 : Place icon.
Step 4 : Replace portion of background from Step 2 over icon.
Step 5 : Repeat from step 2 continuously.

In terms of ascii ...

+---------+
| +--|------- + Background restored (3)
| * -|------> * Background saved to memory (1)
| ^ |
| +--|------- # Icon placed (2)
+---------+

The advantages of this method is that very little extra memory is
needed. The disadvantages are that writing to VGA is slower then writing
to memory, and there may be large amounts of flicker.


METHOD 3 :

Step 1 : Set up one virtual screen, VADDR.
Step 2 : Draw background to VADDR.
Step 3 : Flip VADDR to VGA.
Step 4 : Draw icon to VGA.
Step 5 : Transfer background portion from VADDR to VGA.
Step 6 : Repeat from step 4 continuously.

In ascii ...

+---------+ +---------+
| | | |
| VGA | | VADDR |
| | | (bckgnd)|
| Icon>* <|-----------|--+ |
+---------+ +---------+

The advantages are that writing from the virtual screen is quicker then
from VGA, and there is less flicker then in Method 2. Disadvantages are
that you are using a 64000 byte virtual screen, and flickering occurs
with large numbers of objects.

In the attached sample program, a mixture of Method 3 and Method 1 is
used. It is faster then Method 1, and has no flicker, unlike Method 3.
What I do is I use VADDR2 for background, but only restore the
background that has been changed to VADDR, before flipping to VGA.

In the sample program, you will see that I restore the entire background
of each of the icons, and then place all the icons. This is because if I
replace the background then place the icon on each object individually,
if two objects are overlapping, one is partially overwritten.

The following sections are explanations of how the various assembler
routines work. This will probably be fairly boring for you if you
already know assembler, but should help beginners and dabblers alike.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ The ASM Putpixel

To begin with, I will explain a few of the ASM variables and functions :

<NOTE THAT THIS IS AN EXTREMELY SIMPLISTIC VIEW OF ASSEMBLY LANGUAGE!
There are numerous books to advance your knowledge, and the Norton
Guides assembler guide may be invaluable for people beginning to code
in assembler. I haven't given you the pretty pictures you are supposed
to have to help you understand it easier, I have merely laid it out like
a programming language with it's own special procedures. >

There are 4 register variables : AX,BX,CX,DX. These are words (double
bytes) with a range from 0 to 65535. You may access the high and low
bytes of these by replacing the X with a "
H" for high or "L" for low.
For example, AL has a range from 0-255.

You also have two pointers : ES:DI and DS:SI. The part on the left is
the segment to which you are pointing (eg $a000), and the right hand
part is the offset, which is how far into the segment you are pointing.
Turbo Pascal places a variable over 16k into the base of a segment, ie.
DI or SI will be zero at the start of the variable.

If you wish to be pointing to pixel number 3000 on the VGA screen (see
previous parts for the layout of the VGA screen), ES would be equal to
$a000 and DI would be equal to 3000. You can quite as easily make ES or
DS be equal to the offset of a virtual screen.

Here are a few functions that you will need to know :

mov destination,source This moves the value in source to
destination. eg mov ax,50
add destination,source This adds source to destination,
the result being stored in destination
mul source This multiplies AX by source. If
source is a byte, the source is
multiplied by AL, the result being
stored in AX. If source is a word,
the source is multiplied by AX, the
result being stored in DX:AX
movsb This moves the byte that DS:SI is
pointing to into ES:DI, and
increments SI and DI.
movsw Same as movsb except it moves a
word instead of a byte.
stosw This moves AX into ES:DI. stosb
moves AL into ES:DI. DI is then
incremented.
push register This saves the value of register by
pushing it onto the stack. The
register may then be altered, but
will be restored to it's original
value when popped.
pop register This restores the value of a pushed
register. NOTE : Pushed values must
be popped in the SAME ORDER but
REVERSED.
rep command This repeats Command by as many
times as the value in CX


SHL Destination,count ;
and SHR Destination,count ;
need a bit more explaining. As you know, computers think in ones and
zeroes. Each number may be represented in this base 2 operation. A byte
consists of 8 ones and zeroes (bits), and have a range from 0 to 255. A
word consists of 16 ones and zeroes (bits), and has a range from 0 to
65535. A double word consists of 32 bits.

The number 53 may be represented as follows : 00110101. Ask someone who
looks clever to explain to you how to convert from binary to decimal and
vice-versa.

What happens if you shift everything to the left? Drop the leftmost
number and add a zero to the right? This is what happens :

00110101 = 53
<-----
01101010 = 106

As you can see, the value has doubled! In the same way, by shifting one
to the right, you halve the value! This is a VERY quick way of
multiplying or dividing by 2. (note that for dividing by shifting, we
get the trunc of the result ... ie. 15 shr 1 = 7)

In assembler the format is SHL destination,count This shifts
destination by as many bits in count (1=*2, 2=*4, 3=*8, 4=*16 etc)
Note that a shift takes only 2 clock cycles, while a mul can take up to 133
clock cycles. Quite a difference, no? Only 286es or above may have count
being greater then one.

This is why to do the following to calculate the screen coordinates for
a putpixel is very slow :

mov ax,[Y]
mov bx,320
mul bx
add ax,[X]
mov di,ax

But alas! I hear you cry. 320 is not a value you may shift by, as you
may only shift by 2,4,8,16,32,64,128,256,512 etc.etc. The solution is
very cunning. Watch.

mov bx, [X] // set BX to X
mov dx, [Y] // set DX to Y
push bx // save BX (our X value)
mov bx, dx // now BX and DX are equal to Y
mov dh, dl // copy DL to DH (multiply Y by 256)
xor dl, dl // zero out DL
shl bx, 6 // shift BX left 6 places (multiply Y by 64).
add dx, bx // add BX to DX (Y*64 + Y*256 = Y*320)
pop bx // restore BX (X coordinate)
add bx, dx // add BX to DX (Y*320 + X). this gives you
// the offset in memory you want
mov di, bx // move the offset to DI

Let us have a look at this a bit closer shall we?
bx=dx=y dx=dx*256 ; bx=bx*64 ( Note, 256+64 = 320 )

dx+bx=Correct y value, just add X!

As you can see, in assembler, the shortest code is often not the
fastest.

The complete putpixel procedure is as follows :

[Pascal]

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
{ This puts a pixel on the screen by writing directly to memory. }
BEGIN
Asm
push ds {; Make sure these two go out the }
push es {; same they went in }
mov ax,[where]
mov es,ax {; Point to segment of screen }
mov bx,[X]
mov dx,[Y]
push bx {; and this again for later}
mov bx, dx {; bx = dx}
mov dh, dl {; dx = dx * 256}
xor dl, dl
shl bx, 1
shl bx, 1
shl bx, 1
shl bx, 1
shl bx, 1
shl bx, 1 {; bx = bx * 64}
add dx, bx {; dx = dx + bx (ie y*320)}
pop bx {; get back our x}
add bx, dx {; finalise location}
mov di, bx {; di = offset }
{; es:di = where to go}
xor al,al
mov ah, [Col]
mov es:[di],ah {; move the value in ah to screen
point es:[di] }
pop es
pop ds
End;
END;

[C++]

/////////////////////////////////////////////////////////////////////////////
// //
// Putpixel() - This puts a pixel on the screen by writing directly to //
// memory. //
// //
/////////////////////////////////////////////////////////////////////////////

void Putpixel (word X, word Y, byte Col, word Where) {
asm {
push ds // save DS
push es // save ES
mov ax, [Where] // move segment of Where to AX
mov es, ax // set ES to segment of Where
mov bx, [X] // set BX to X
mov dx, [Y] // set DX to Y
push bx // save BX (our X value)
mov bx, dx // now BX and DX are equal to Y
mov dh, dl // copy DL to DH (multiply Y by 256)
xor dl, dl // zero out DL
shl bx, 6 // shift BX left 6 places (multiply Y by 64).
add dx, bx // add BX to DX (Y*64 + Y*256 = Y*320)
pop bx // restore BX (X coordinate)
add bx, dx // add BX to DX (Y*320 + X). this gives you
// the offset in memory you want
mov di, bx // move the offset to DI
xor al, al // zero out AL
mov ah, [Col] // move value of Col into AH
mov es:[di], ah // move Col to the offset in memory (DI)
pop es // restore ES
pop ds // restore DS
}
}

Note that with DI and SI, when you use them :
mov di,50 Moves di to position 50
mov [di],50 Moves 50 into the place di is pointing to


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ The Flip Procedure

This is fairly straightforward. We get ES:DI to point to the start of
the destination screen, and DS:SI to point to the start of the source
screen, then do 32000 movsw (64000 bytes).

[Pascal]

procedure flip(source,dest:Word);
{ This copies the entire screen at "
source" to destination }
begin
asm
push ds
mov ax, [Dest]
mov es, ax { ES = Segment of source }
mov ax, [Source]
mov ds, ax { DS = Segment of source }
xor si, si { SI = 0 Faster then mov si,0 }
xor di, di { DI = 0 }
mov cx, 32000
rep movsw { Repeat movsw 32000 times }
pop ds
end;
end;

[C++]

/////////////////////////////////////////////////////////////////////////////
// //
// Flip() - This copies the entire screen at "
source" to destination. //
// //
/////////////////////////////////////////////////////////////////////////////

void Flip(word source, word dest) {
asm {
push ds // save DS
mov ax, [dest] // copy segment of destination to AX
mov es, ax // set ES to point to destination
mov ax, [source] // copy segment of source to AX
mov ds, ax // set DS to point to source
xor si, si // zero out SI
xor di, di // zero out DI
mov cx, 32000 // set our counter to 32000
rep movsw // move source to destination by words. decrement
// CX by 1 each time until CX is 0
pop ds // restore DS
}
}

The cls procedure works in much the same way, only it moves the color
into AX then uses a rep stosw (see program for details)

The PAL command is almost exactly the same as it's Pascal equivalent
(see previous tutorials). Look in the sample code to see how it uses the
out and in commands.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ In Closing

The assembler procedures presented to you in here are not at their best.
Most of these are procedures ASPHYXIA abandoned for better ones after
months of use. But, as you will soon see, they are all MUCH faster then
the original Pascal equivalents I originally gave you. In future, I
hope to give you more and more assembler procedures for your ever
growing collections. But, as you know, I am not always very prompt with
this series (I don't know if even one has been released within one week
of the previous one), so if you want to get any stuff done, try do it
yourself. What do you have to lose, aside from your temper and a few
rather inventive reboots ;-)

What should I do for the next trainer? A simple 3-d tutorial? You may
not like it, because I would go into minute detail of how it works :)
Leave me suggestions for future trainers by any of the means discussed
at the top of this trainer.

After the customary quote, I will place a listing of the BBSes I
currently know that regularly carry this Trainer Series. If your BBS
receives it regularly, no matter where in the country you are, get a
message to me and I'll add it to the list. Let's make it more convenient
for locals to grab a copy without calling long distance ;-)

[ There they sit, the preschooler class encircling their
mentor, the substitute teacher.
"
Now class, today we will talk about what you want to be
when you grow up. Isn't that fun?" The teacher looks
around and spots the child, silent, apart from the others
and deep in thought. "
Jonny, why don't you start?" she
encourages him.
Jonny looks around, confused, his train of thought
disrupted. He collects himself, and stares at the teacher
with a steady eye. "
I want to code demos," he says,
his words becoming stronger and more confidant as he
speaks. "
I want to write something that will change
peoples perception of reality. I want them to walk
away from the computer dazed, unsure of their footing
and eyesight. I want to write something that will
reach out of the screen and grab them, making
heartbeats and breathing slow to almost a halt. I want
to write something that, when it is finished, they
are reluctant to leave, knowing that nothing they
experience that day will be quite as real, as
insightful, as good. I want to write demos."
Silence. The class and the teacher stare at Jonny, stunned. It
is the teachers turn to be confused. Jonny blushes,
feeling that something more is required. "
Either that
or I want to be a fireman."
]
- Grant Smith
14:32
21/11/93

See you next time,
- DENTHOR

--==[ PART 8 ]==--

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

Hello everybody! Christmas is over, the last of the chocolates have been
eaten, so it's time to get on with this, the eighth part of the ASPHYXIA
Demo Trainer Series. This particular part is primarily about 3-D, but
also includes a bit on optimisation.

If you are already a 3-D guru, you may as well skip this text file, have
a quick look at the sample program then go back to sleep, because I am
going to explain in minute detail exactly how the routines work ;)

If you would like to contact me, or the team, there are many ways you
can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail
on the ASPHYXIA BBS.
2) Write a message in the Programming conference on the
For Your Eyes Only BBS (of which I am the Moderator )
This is preferred if you have a general programming query
or problem others would benefit from.
4) Write to Denthor, EzE or Goth on Connectix.
5) Write to : Grant Smith
P.O.Box 270 Kloof
3640
Natal
6) Call me (Grant Smith) at (031) 73 2129 (leave a message if you
call during varsity)
7) Write to mcphail@beastie.cs.und.ac.za on InterNet, and
mention the word Denthor near the top of the letter.

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



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Optimisation

Before I begin with the note on 3-D, I would like to stress that many of
these routines, and probably most of your own, could be sped up quite a
bit with a little optimisation. One must realise, however, that you must
take a look at WHAT to optimise ... converting a routine that is only
called once at startup into a tightly coded assembler routine may show
off your merits as a coder, but does absolutely nothing to speed up your
program. Something that is called often per frame is something that
needs to be as fast as possible. For some, a much used procedure is the
PutPixel procedure. Here is the putpixel procedure I gave you last week:

[Note: Snowman here! I consulted the official Intel documentation and
noticed that Denthor is basing the clock ticks on the 8088 processor and
not the 286, 386, or 486 processors. I have taken the time to include
the information for these other processors.]

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);

BEGIN -clock ticks-
Asm 8088 286 386 486
push ds 14 3 2 3
push es 14 3 2 3
mov ax,[where] 8 5 4 1
mov es,ax

  
2 2 2 3
mov bx,[X] 8 5 4 1
mov dx,[Y] 8 5 4 1
push bx 15 3 2 1
mov bx, dx 2 2 2 1
mov dh, dl 2 2 2 1
xor dl, dl 3 2 2 1
shl bx, 1 2 2 3 3
shl bx, 1 2 2 3 3
shl bx, 1 2 2 3 3
shl bx, 1 2 2 3 3
shl bx, 1 2 2 3 3
shl bx, 1 2 2 3 3
add dx, bx 3 2 2 1
pop bx 12 5 4 4
add bx, dx 3 2 2 1
mov di, bx 2 2 2 3
xor al,al 3 2 2 1
mov ah, [Col] 8 5 4 1
mov es:[di],ah 10 3 2 1
pop es 12 5 7 3
pop ds 12 5 7 3
End; ---------------
END; 153 75 76 52 Total ticks

NOTE : Don't take my clock ticks as gospel, I probably got one or two
wrong.

Right, now for some optimising. Firstly, if you have 286 instructions
turned on, you may replace the 6 shl,1 with shl,6. Secondly, the Pascal
compiler automatically pushes and pops ES, so those two lines may be
removed. DS:[SI] is not altered in this procedure, so we may remove
those too. Also, instead of moving COL into ah, we move it into AL and
call stosb (es:[di]:=al; inc di). Let's have a look at the routine now :


Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
BEGIN -clock ticks-
Asm 8088 286 386 486
mov ax,[where] 8 5 4 1
mov es,ax 2 2 2 3
mov bx,[X] 8 5 4 1
mov dx,[Y] 8 5 4 1
push bx 15 3 2 1
mov bx, dx 2 2 2 1
mov dh, dl 2 2 2 1
xor dl, dl 3 2 2 1
shl bx, 6 8 11 3 2
add dx, bx 3 2 2 1
pop bx 12 5 4 4
add bx, dx 3 2 2 1
mov di, bx 2 2 2 3
mov al, [Col] 8 5 4 1
stosb 11 3 4 5
End; ----------------
END; 95 56 43 27 Total ticks

Now, let us move the value of BX directly into DI, thereby removing a
costly push and pop. The MOV and the XOR of DX can be replaced by it's
equivalent, SHL DX,8

[Note: If you are running on a 286, this last set of optimizations
actually makes the routine slower! On a 286, an "SHL" takes 5 ticks
plus the number you are moving by. For example: an "SHL AX,6" would
take 11 clock ticks (5 ticks for SHL and 6 clock ticks for the "6"
part of the expression.]

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); assembler;

BEGIN -clock ticks-
asm 8088 286 386 486
mov ax,[where] 8 5 4 1
mov es,ax 2 2 2 3
mov bx,[X] 8 5 4 1
mov dx,[Y] 8 5 4 1
mov di,bx 2 2 2 3
mov bx, dx 2 2 2 1
shl dx, 8 8 13 3 2
shl bx, 6 8 11 3 2
add dx, bx 3 2 2 1
add di, dx 3 2 2 1
mov al, [Col] 8 5 4 1
stosb 11 3 4 5
end; ----------------
71 57 36 22 Total ticks

As you can see, we have brought the clock ticks down from 153-52 (8088-486)
ticks to 71-22 (8088-486) ticks ... quite an improvement. (The current
ASPHYXIA putpixel takes 48 clock ticks) . As you can see, by going through
your routines a few times, you can spot and remove unnecessary instructions,
thereby greatly increasing the speed of your program.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Defining a 3-D object

Drawing an object in 3-D is not that easy. Sitting down and plotting a
list of X,Y and Z points can be a time consuming business. So, let us
first look at the three axes you are drawing them on :

Y Z
/|\ /
| /
X<-----|----->
|
\|/

X is the horisontal axis, from left to right. Y is the vertical axis,
from top to bottom. Z is the depth, going straight into the screen.

In this trainer, we are using lines, so we define 2 X,Y and Z
coordinates, one for each end of the line. A line from far away, in the
upper left of the X and Y axes, to close up in the bottom right of the
X and Y axes, would look like this :

{ x1 y1 z1 x2 y2 z2 }
( (-10,10,-10),(10,-10,10) )


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Rotating a point with matrixes

NOTE : I thought that more then one matix are matrisese (sp), but my
spellchecker insists it is matrixes, so I let it have it's way
;-)

Having a 3-D object is useless unless you can rotate it some way. For
demonstration purposes, I will begin by working in two dimensions, X and
Y.

Let us say you have a point, A,B, on a graph.
Y
| /O1 (Cos (a)*A-Sin (a)*B , Sin (a)*A+Cos (a)*B)
|/ (A,B)
X<-----|------O-->
|
|

Now, let us say we rotate this point by 45 degrees anti-clockwise. The
new A,B can be easily be calculated using sin and cos, by an adaption of
our circle algorithm, ie.
A2:=Cos (45)*A - Sin (45)*B
B2:=Sin (45)*A + Cos (45)*B
I recall that in standard 8 and 9, we went rather heavily into this in
maths. If you have troubles, fine a 8/9/10 maths book and have a look;
it will go through the proofs etc.

Anyway, we have now rotated an object in two dimensions, AROUND THE Z
AXIS. In matrix form, the equation looks like this :

[ Cos (a) -Sin (a) 0 0 ] [ x ]
[ Sin (a) Cos (a) 0 0 ] . [ y ]
[ 0 0 1 0 ] [ z ]
[ 0 0 0 1 ] [ 1 ]

I will not go to deeply into matrixes math at this stage, as there are
many books on the subject (it is not part of matric maths, however). To
multiply a matrix, to add the products of the row of the left matrix and
the column of the right matrix, and repeat this for all the columns of the
left matrix. I don't explain it as well as my first year maths lecturer,
but have a look at how I derived A2 and B2 above. Here are the other
matrixes :

Matrix for rotation around the Y axis :
[ Cos (a) 0 -Sin (a) 0 ] [ x ]
[ 0 1 0 0 ] . [ y ]
[ Sin (a) 0 Cos (a) 0 ] [ z ]
[ 0 0 0 1 ] [ 1 ]

Matrix for rotation around the X axis :
[ 1 0 0 ] [ x ]
[ 0 Cos (a) -Sin (a) 0 ] . [ y ]
[ 0 Sin (a) Cos (a) 0 ] [ z ]
[ 0 0 0 1 ] [ 1 ]

By putting all these matrixes together, we can translate out 3D points
around the origin of 0,0,0. See the sample program for how we put them
together.

In the sample program, we have a constant, never changing base object.
This is rotated into a second variable, which is then drawn. I am sure
many of you can thing of cool ways to change the base object, the
effects of which will appear while the object is rotating. One idea is
to "pulsate" a certain point of the object according to the beat of the
music being played in the background. Be creative. If you feel up to it,
you could make your own version of transformers ;)



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Drawing a 3D point to screen

Having a rotated 3D object is useless unless we can draw it to screen.
But how do we show a 3D point on a 2D screen? The answer needs a bit of
explaining. Examine the following diagram :

| ________-------------
____|___------ o Object at X,Y,Z o1 Object at X,Y,Z2
Eye -> O)____|___
| ------________
| -------------- Field of vision
Screen

Let us pretend that the centre of the screen is the horizon of our
little 3D world. If we draw a three dimensional line from object "o" to
the centre of the eye, and place a pixel on the X and Y coordinates
where it passes through the screen, we will notice that when we do the
same with object o1, the pixel is closer to the horizon, even though
their 3D X and Y coords are identical, but "o1"'s Z is larger then
"o"'s. This means that the further away a point is, the closer to the
horizon it is, or the smaller the object will appear. That sounds
right, doesent it? But, I hear you cry, how do we translate this into a
formula? The answer is quite simple. Divide your X and your Y by your Z.
Think about it. The larger the number you divide by, the closer to zero,
or the horizon, is the result! This means, the bigger the Z, the
further away is the object! Here it is in equation form :

[Pascal]

nx := 256*x div (z-Zoff)+Xoff
ny := 256*y div (z-Zoff)+Yoff

[C++]

nx = ((256*x) / (z-Zoff)) + Xoff;
nx = ((256*y) / (z-Zoff)) + Yoff;


NOTE : Zoff is how far away the entire object is, Xoff is the objects X
value, and Yoff is the objects Y value. In the sample program,
Xoff start off at 160 and Yoff starts off at 100, so that the
object is in the middle of the screen.

The 256 that you times by is the perspective with which you are viewing.
Changing this value gives you a "fish eye" effect when viewing the
object. Anyway, there you have it! Draw a pixel at nx,ny, and viola! you
are now doing 3D! Easy, wasn't it?


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Possible improvements

This program is not the most optimised routine you will ever encounter
(;-)) ... it uses 12 muls and 2 divs per point. (Asphyxia currently has
9 muls and 2 divs per point) Real math is used for all the calculations
in the sample program, which is slow, so fixed point math should be
implemented (I will cover fixed point math in a future trainer). The
line routine currently being used is very slow. Chain-4 could be used to
cut down on screen flipping times.

Color values per line should be added, base object morphing could be put
in, polygons could be used instead of lines, handling of more then one
object should be implemented, clipping should be added instead of not
drawing something if any part of it is out of bounds.

In other words, you have a lot of work ahead of you ;)


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

There are a lot of books out there on 3D, and quite a few sample
programs too. Have a look at them, and use the best bits to create your
own, unique 3D engine, with which you can do anything you want. I am
very interested in 3D (though EzE and Goth wrote most of ASPHYXIA'S 3D
routines), and would like to see what you can do with it. Leave me a
message through one of the means described above.

I am delving into the murky world of texture mapping. If anyone out
there has some routines on the subject and are interested in swapping,
give me a buzz!

What to do in future trainers? Help me out on this one! Are there any
effects/areas you would like a bit of info on? Leave me a message!

I unfortunately did not get any messages regarding BBS's that carry this
series, so the list that follows is the same one from last time. Give
me your names, sysops!

Aaaaargh!!! Try as I might, I can't think of a new quote. Next time, I
promise! ;-)

Bye for now,
- Denthor

--==[ PART 9 ]==--

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

Hi there! ASPHYXIA is BACK with our first MegaDemo, Psycho Neurosis! A
paltry 1.3MB download is all it takes to see the group from Durbs first
major production! We are quite proud of it, and think you should see it
;)

Secondly, I released a small little trainer (a trainerette ;-)) on
RsaPROG and Connexctix BBS mail, also on the ASPHYXIA BBS as COPPERS.ZIP
It is a small Pascal program demonstrating how to display copper bars in
text mode. Also includes a check for horizontal retrace (A lot of people
wanted it, that is why I wrote the program) (ASPHYXIA ... first with the
trainer goodies ;-) aargh, sorry, had to be done ))

Thirdly, sorry about the problems with Tut 8! If you had all the
checking on, the tutorial would probably die on the first points. The
reason is this : in the first loop, we have DrawPoints then
RotatePoints. The variables used in DrawPoints are set in RotatePoints,
so if you put RotatePoints before DrawPoints, the program should work
fine. Alternatively, turn off error checking 8-)

Fourthly, I have had a surprisingly large number of people saying that
"I get this, like, strange '286 instructions not enabled' message!
What's wrong with your code, dude?"
To all of you, get into Pascal, hit
Alt-O (for options), hit enter and a 2 (for Enable 286 instructions). Hard
hey? Doesn't anyone EVER set up their version of Pascal?

Now, on to todays tutorial! 3D solids. That is what the people wanted,
that is what the people get! This tutorial is mainly on how to draw the
polygon on screen. For details on how the 3D stuff works, check out tut
8.



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 Denthor, EzE or Goth on Connectix.
3) Write to : Grant Smith
P.O.Box 270 Kloof
3640
Natal
4) Call me (Grant Smith) at (031) 73 2129 (leave a message if you
call during varsity)
5) Write to mcphail@beastie.cs.und.ac.za on InterNet, and
mention the word Denthor near the top of the letter.

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 to draw a polygon

Sounds easy enough, right? WRONG! There are many, many different ways to
go about this, and today I'll only be showing you one. Please don't take
what is written here as anything approaching the best method, it is just
here to get you on your way...

The procedure I will be using here is based on something most of us
learned in standard eight ... I think. I seem to recall doing something
like this in Mrs. Reids maths class all those years ago ;)

Take two points, x1,y1 and x2,y2. Draw them :

+ (x1,y1)
\
\ <-- Point a somewhere along the line
\
+ (x2,y2)

Right, so what we have to do is this : if we know the y-coord of a, what
is it's x-coord? To prove the method we will give the points random
values.

+ (2,10)
\
\ <-- a.y = 12
\
+ (15,30)

Right. Simple enough problem. This is how we do it :
(a.y-y1) = (12 - 10) {to get a.y as though y1 was zero}
*(x2-x1) = *(15 - 2) {the total x-length of the line}
/(y2-y1) = /(30 - 10) {the total y-length of the line}
+x1 = +2 { to get the equation back to real coords}

So our equation is : (a.y-y1)*(x2-x1)/(y2-y1)+x4 or
(12-10)*(15-2)/(30-10)+2
which gives you :
2*13/20+2 = 26/20+2
= 3.3

That means that along the line with y=12, x is equal to 3.3. Since we
are not concerned with the decimal place, we replace the / with a div,
which in Pascal gives us an integer result, and is faster too. All well
and good, I hear you cry, but what does this have to do with life and
how it relates to polygons in general. The answer is simple. For each of
the four sides of the polygon we do the above test for each y line. We
store the smallest and the largest x values into separate variables for
each line, and draw a horizontal line between them. Ta-Dah! We have a
cool polygon!

For example : Two lines going down :

+ +
/ <-x1 x2->| <--For this y line
/ |
+ +

Find x1 and x2 for that y, then draw a line between them. Repeat for all
y values.

Of course, it's not as simple as that. We have to make sure we only
check those y lines that contain the polygon (a simple min y, max y test
for all the points). We also have to check that the line we are
calculating actually extends as far as where our current y is (check
that the point is between both y's). We have to compare each x to see
weather it is smaller then the minimum x value so far, or bigger then
the maximum (the original x min is set as a high number, and the x max
is set as a small number). We must also check that we only draw to the
place that we can see ( 0-319 on the x ; 0-199 on the y (the size of the
MCGA screen))

To see how this looks in practice, have a look at the sample code
provided. (Mrs. Reid would probably kill me for the above explanation,
so when you learn it in school, split it up into thousands of smaller
equations to get the same answer ;))

Okay, that's it! What's that? How do you draw a vertical line? Thats
simple ...

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
þ Drawing a vertical line

Right, this is a lot easier than drawing a normal line (Tut 5 .. I
think), because you stay on the same y value. So, what you do is you set
ES to the screen you want to write to, and get DI to the start of the
y-line (see earlier trainers for a description of how SEGMENT:OFFSET
works.

IN : x1 , x2, y, color, where

asm
mov ax,where
mov es,ax
mov di,y
mov ax,y
shl di,8 { di:=di*256 }
shl ax,6 { ax:=ax*64 }
add di,ax { di := (y*256)+(y*64) := y*320 Faster then a
straight multiplication }

Right, now you add the first x value to get your startoff.
add di,x1
Move the color to store into ah and al
mov al,color
mov ah,al { ah:=al:=color }
then get CX equal to how many pixels across you want to go
mov cx,x2
sub cx,x1 { cx:=x2-x1 }
Okay, as we all know, moving a word is a lot faster then moving a byte,
so we halve CX
shr cx,1 { cx:=cx/2 }
but what happens if CX was an odd number. After a shift, the value of
the last number is placed in the carry flag, so what we do is jump over
a single byte move if the carry flag is zero, or execute it if it is
one.
jnc @Start { If there is no carry, jump to label Start }
stosb { ES:[DI]:=al ; increment DI }
@Start : { Label Start }
rep stosw { ES:[DI]:=ax ; DI:=DI+2; repeat CX times }

Right, the finished product looks like this :

[Pascal]

Procedure Hline (x1,x2,y:word;col:byte;where:word); assembler;
{ This draws a horizontal line from x1 to x2 on line y in color col }
asm
mov ax,where
mov es,ax
mov ax,y
mov di,ax
shl ax,8
shl di,6
add di,ax
add di,x1

mov al,col
mov ah,al
mov cx,x2
sub cx,x1
shr cx,1
jnc @start
stosb
@Start :
rep stosw
end;

[C++]

void Hline (word X1, word X2, word Y, byte Col, word Where) {
asm {
mov ax, [Where] // move segment of Where to AX
mov es, ax // set ES to segment of Where
mov ax, [Y] // set AX to Y
mov di, ax // set DI to Y
shl ax, 8 // shift AX left 8 places (multiply Y by 256)
shl di, 6 // shift DI left 6 places (multiply Y by 64)
add di, ax // add AX to DI (Y*64 + Y*256 = Y*320)
add di, [X1] // add the X1 offset to DI
mov al, [Col] // move Col to AL
mov ah, al // move Col to AH (we want 2 copies for word moving)
mov cx, [X2] // move X2 to CX
sub cx, [X1] // move the change in X to CX
shr cx, 1 // divide change in X by 2 (for word moving)
jnc Start // if we have an even number of moves, go to Start
stosb // otherwise, move one byte more
}
Start: asm {
rep stosw // do it!
}
}

Done!

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

This 3D system is still not perfect. It needs to be faster, and now I
have also dumped the problem of face-sorting on you! Nyahahahaha!

[ My sister and I were driving along the other day when she
asked me, what would I like for my computer.
I thought long and hard about it, and came up with the
following hypothesis. When a girl gets a Barbie doll, she
then wants the extra ballgown for the doll, then the
hairbrush, and the car, and the house, and the friends
etc.
When a guy gets a computer, he wants the extra memory, the
bigger hard drive, the maths co-pro, the better
motherboard, the latest software, and the bigger monitor
etc.
I told my sister all of this, and finished up with : "So as
you can see, computers are Barbie dolls for MEN!"

She called me a chauvinist. And hit me. Hard.
]
- Grant Smith
19:24
26/2/94

See you next time!
- Denthor


That concludes all of the tutorials included in the DSSK3!

Have fun! Experiment! BUT DON'T WRECK YOUR MACHINE BY DOING TOTALLY CRAZY
STUFF YOU DON'T KNOW ANYTHING ABOUT! A little hint: OUT'ing to random
port-adresses is a BAD thing. ;)

PS. DO NOT LISTEN TO THE PEOPLE WHO SAY COMMENTING IS LAME!
It is probably THE most important thing to do. however good a coder
you are it is possible to forget stuff, and then it's handy to read
comments. There is no such thing as commenting to much, only to little.
Comment AT LEAST every 10 lines, preferrably comment at every line.
This goes for ASM, not C/C++.

Remember, as with anything included in the DSSK, I take absolutely no
responsibility for anything at all. That should be pretty straightforward.
If you destroy your machine using any of this stuff, then you typed them
out wrong or you were trying to do them on a toaster.
The standard DSSK license agreement effects this document,
just as much as anything else found in the archive,
or in the directory of DSSK.

[EOF]

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT