			͸
			
			
			
			
			
			ʹ
			  Part IV   -  POLYGONS  
			

Hi and welcome to the issue number 4 of my weekly (doing better this time)
tutorial on graphics programming for the PC.

This collection of files could have reached you via one of the following
methods :

	FTP from  -->  ftp://ftp.teleport.com/users/~craigl/vgatut

	(please note : I think I got the address wrong in tutorial 3.
	 Sorry Craig. (One for the apology section?  Might make it a weekly
	 thing.)

	or

	The Shetland Information Technology Project (SITP)
		Conferences/Computing/Graphics/Programming

	or

	The Official VGA Tutorial Page 
	(really just links to Craig Lovegren's FTP site)

	   http://www.zetnet.co.uk/users/bmercer/vgatut.htm

Craig Lovegren's Homepage  (http://www.teleport.com/craigl)
also contains links to the VGA tutorials.  Take a look at it sometime.  There
are a number of great programming related links.  I am also informed by Craig
that he is busy converting these tutorials to HTML format so that the can be
viewed on the Web.  Good one! :)

If you recieved this from any other source then I'd like to hear about it
so that I can include it in this list.

An apology (number 2)


You will have noticed (if you were paying attention, that is ;) ) that last 
week's tutorial contained a ridiculous error:

In the code given for the WaitVerticalRetrace() function we had the 
following....

    t1:
      in  al, dx
      and al, 0x08
      jz t1
      ^^

    t2:
      in  al, dx
      and al, 0x08
      jnz t2
      ^^^
The JNZ & the JZ should be swapped, otherwise this function only returns
just as a the screen is being refreshed.  Exactly the opposite of what we
need....  Duh Barny....

This tutorial is about drawing filled polygons (polygie?!)

Before we start (again):


ͻ
DISCLAIMER
Ľ
	The code for this tutorial, the compiled .EXEs and any ascociated text
	are freely distributable on the conditions that the contents of the 
	original .ZIP file are distributed as one and that no changes are made
	to any of the data or information contained within.
	The author expresses no warranty implied or otherwise as to the
	suitabilty of this software for execution on any machine other than
	the system on which it was developed (although there should be no
	problems).
	The author also takes no responsibilty for damage resulting from the
	use of information, code or otherwise, obtained from this tutorial 
	(again, I'd be surprised if such a thing did happen).
	Finally you should _NOT_ have paid anything for this tutorial.  
	If you parted with any money (other than a reasonalbe price for a 
	disk) then complain and get your money back.  Please let me know too. 
	This tutorial is FREE.  Please do not abuse this.


Sorry about all that, but unfortunately we live in a world where such things
are a necessity.

Polygons (a definition)


A polygon is defined as being a closed plane figure bounded by 3 or more line
segments.  That is, we can specify points in 2-Dimensional space and join
them together with straight lines to form a polygon.

We will be dealing with convex polygons in this tutorial.  A convex polygon
is one in which the surface boundy bulges outward and consequently you can 
draw a horizontal or vertical line through, and the line will always cross 
exactly two edges of the polygon.  Figure1.pcx (illustrations too, aren't 
you lucky people! <grin>) shows the differences between convex and concave 
polygons.

The reason for dealing only with filled convex polygons is that, quite simply, 
they are considerably easier to deal with than nox-convex polygons.  You will
see why later.

Representing a polygon


The first thing we need to do is decide on a way to represent our polygon as
data.  I've used the following simple structure to hold polygon data:

	struct PolyPoint{ int x, y; };

And we can use it as an array to define our polygon:

	PolyPoint Triangle[3] = { {10, 10}, {150, 75}, {30, 30} };

And that's it.  We can now refer to each point in the polygon using this
array.  e.g.  (Triangle[1].x, Triangle[1].y)

Wire Frame


If you are reading this section then you are obviously not really entirely
with it are you. ;)  Only joking.  

Wire frame polygons are simple, and involve nothing more demanding than a
simple dot-to-dot with the points stored in your polygon array.  
Like this.....

  DrawLine( Triangle[0].x, Triangle[0].y, Triangle[1].x, Triangle[1].y, Col );
  DrawLine( Triangle[1].x, Triangle[1].y, Triangle[2].x, Triangle[2].y, Col );
  DrawLine( Triangle[2].x, Triangle[2].y, Triangle[0].x, Triangle[0].y, Col );

Ta da!

You could obviously use a loop here, to make things more compact.

Filled Polygons


Ah..  Now this is more interesting.

There are a number of ways that you can do this, but I will focus on just one.
The method that we will be using involves finding the edges of the polygon,
and drawing horizontal lines between the start and end points, one line is
drawn per Y value.  That is, we draw a polygon like this.....

                   - first line
                 - second line
               - thrid line

There are a number of advantages to doing it like this, these include

   We don't need to use a flood fill algorithm (slow)
   Horizontal lines are easy to draw quickly

We could use vertical lines instead, but you will see later why we don't do
this.

Another thing that you will see is that we _can_ draw concave polygons
using this routine, but they must be concave along the vertical axis.
Looking back to Figure1.pcx we can see that drawing a horizontal line through
some of the concave polygons illustrated will work.  This however, is not
determinable by our algorithm.

Finding the sides


In order to draw our filled polygon we will need to calculate all the points
along each edge of the polygon.  We will use two arrays to store this
information.

	int LineStart[200];
	int LineFinish[200];

This will allow us to store a starting & and ending X coordinate for each of
the 200 horizontal lines displayable on screen.

We now need to find the edges of the polygon and store them in these arrays.
Take a look at the following code.....

void PolyLine(int x1, int y1, int x2, int y2)
{
    int y;
    long x, grad;

    if (y2 != y1)               // this is not a horizontal line
    {
	if (y2 < y1)            // swap values if necessary so that
	{                       // y2 is higher than y1  (the line remains
	    int temp;           // exactly the same)

	    // swap values
	    temp =   y1;
	    y1   =   y2;
	    y2   = temp;             

	    temp =   x1;        // we must also swap the corresponding x
	    x1   =   x2;        // coordinates
	    x2   = temp;
	}

	x = (long)x1<<8;        // multiply x by 256 (fixed point math)

	// the gradient of the line is represented using fixed point math
	// in this case the lower 8 bits hold the fractional part of the
	// value.  We can add 'grad' to 'x' without needing to deal with
	// floating point values, by trimming the first byte with a '>>'
	// shift right.

	grad = ((long)(x2-x1)<<8)/((long)(y2-y1)); // gradient of line

	x += grad;              // skip first point of line so that
	y1++;                   // single pixel lines are ignored

	for (y=y1; y<=y2; y++)       // step through entire y length of line
	{
	    if ((y>=0) & (y<200))           // if co-ordinate is on screen
		if (LineStart[y] == -32767) // check to see if we already
		    LineFinish[y] = x>>8;   // have a starting point for our line
		else
		    EndX[y] = x>>8;      // so it must be an ending point

	    x+=grad;                     // increase x coordinate for next
	}                                // y coordinate
    }
}

There are a number of things to note here...

	The LineStart & LineFinish arrays have been initialised so that
	every value in the array is -32767.  This was chosen as a value that
	will never occur during sensible use of the line routine.
	We can then check to see if we already have a point in our array
	by checking against this value.

	I am using fixed point math here.  This makes an incredible difference
	to the speed of your calculations.  Fixed point math allows us to
	handle fractions without needing to use floating point numbers.
	The number is stored as type 'long' with the first 3 bytes being used
	to store the integral part of the number and the remaining byte
	storing the fractional part.  Drop me a line if you'd like a deeper
	explanation of what I've done here.  But you're bright people, you
	should be able to fathom it.

	The algorithm for calculating the line is far simpler than the
	line algorithm we looked at in Tutorial 2.  This is because we only
	need to find 1 x value per y value.  Therefore the line :

		
		   
		    
		      

	can be represented more simply by:

		
		  
		    
		      

	since the gaps will be filled when we draw the horizontal line.

We call the PolyLine function once for each line which bounds the polygon.
On the first calling, all points will be stored in the LineStart array, but
subsequent callings will have to check to see if there is already a start
point, and place the value in the LineFinish array instead.

Don't be confused by the names of the arrays.  You may presume from this that
all lines run in the same direction.  They don't.  Consider the following
sample data, occuring at the corner of a polygon:

     LineStart[10] = 100;       LineFinish[10] = 200;   // left to right
     LineStart[11] = 105;       LineFinish[11] = 150;   // left to right
     LineStart[12] = 110;       LineFinish[12] = 100;   // right to left

This means that our horizontal line function must accept that the line
may run backwards across the screen.

Once we have the LineStart & LineFinish arrays filled it is time to draw the
connecting lines.

The HorizLine Function


The two examples provided each use a different line routine.  The first is
the DrawLine function that we developed in Tutorial 2.  This routine is not
well suited to the task in hand for a number of reasons.  Firstly, it is
designed to cope with lines of any slope (or gradient).  We _know_ that our
lines are horizontal (x++ each loop) so we can eliminate a great deal of the
math involved.  Secondly, we know that Y remains constant and X is 
incremented (or decremented in the case of backward lines.  See above) so
we are only performing a few of the simpler aspects of the general line
routine.
Our optimised algorithm looks like this:

     int Y;     // Y coordinate of line (remains constant for entire length)
     
     X = LineStart[Y];  // X now contains the starting point of the line

     // a bit of pseudocode now
     Loop Start
	Place a pixel at current point (X, Y)
	(Increment/Decrement) X
	If it is not (greater/less) than LineFinish[Y]
	   Jump to Loop Start

The bracketed items require us to know in which direction the line is
travelling, but this is no problem.

Another thing that we can do to optimise the routine is to avoid calculating
the video memory offset for each pixel.  We know that by adding 1 to X, we
are also adding 1 to the video offset, so we can do this directly by
incrementing DI (the register that we are holding the offset in).  As Y
remains constant we need only calculate it once.

The final line routine looks something like this (+free comments <g>):

void HorizLine( int Start, int End, int Y, unsigned char Col )
{
    int Diff;   // the distance between ends of the line
    int Dir;    // the direction the line is travelling in

    _asm
    {
	mov [Dir], 1            ; direction defaults : left to right

	mov AX, [End]           ; calculate distance of line
	sub AX, [Start]
	mov [Diff], AX          ; stored in Diff

	cmp [Diff], 0x00        ; we don't even want to bother trying to
	jz  Finish              ; plot a line with no length

	cmp [Diff], 0x00        ; is the Distance negative
	jg  Continue

	mov [Dir], 0xFFFF       ; if so multiply Dir by 0xFFFF (-1) to reverse
				; the direction that the line is drawn in

    Continue:           ; great tag name

	mov ax, 0xA000  ; set up video pointers
	mov es, ax
	xor di, di      ; ES:DI now points to the Top-Left hand pixel

	mov bx, [Start] ; calculate start co-ordinates
	mov dx, [Y]
	add di, bx
	mov bx, dx
	shl dx, 8
	shl bx, 6
	add dx, bx
	add di, dx
	mov al, [Col]   ; AL now holds the colour of the line

	mov cx, [Diff]  ; use CX to see if we are at the end of the line

    Again:
	mov es:[di], al ; put pixel at first point - address in ES:DI, colour in AL
	add di, [Dir]   ; point to next pixel

	sub cx, [Dir]   ; move CX closer to 0
	cmp cx, 0       ; stop when we've reached the end of the line
	jnz Again
    Finish:
    }
}

Simple.  Note that there are ways of making this routine even faster.  Have a
go. You could also have a go at writing the PolyLine function in assembler to
get even more speed out of it.

The sample program included demonstrates the speed difference between the two
rotuines.  I have only timed 10 seconds worth of polygons which is not really
enough to give accurate results, but does demonstrate the difference between
the two.  The program will produce different results each time due to larger
polygons taking longer to draw, and the polygons being chosen randomly.
To get accurate timing results from these routines, I suggest running them
5 times for 2 minutes each & averaging the results.  You should find overall
that optimised routine runs just under twice as fast as the original one.

Summing Up


The keen minded among you will realise why this routine falls over big time
when faced with a concave polygon.  For the not so keen minded among you 
<grin>, here's why...

Looking back to Figure1.pcx, we can see that for *most* of the concave 
polygons we can have more than one point occuring in any horizontal cross 
section. This will result in the LineStart & LineFinish arrays getting 
confused and the results can be unpredictable.  Well, not that unpredictable.  
You will have pieces missing from your polygon, but _which_ pieces are not 
so obvious.  There are ways around this, but a different algorithm is 
required.  Notice that I said *most*.  For any polygons which are convex 
on the Y axis but not necessarily the X axis (See Figure2.PCX) the routine 
will work splendidly.

In a not too distant Tutorial I will be looking at various ways of shading
polygons.  These methods will include Gouraud Shading, Texture Mapping &
Phong Shading. If this is gibberish to you then don't worry, all will be
explained.

Goodbye....


And that, My Dear, is quite frankly that.  I hope you managed to understand
by innane ravings without too much difficulty.
Aplogies for the shoddy .PCXs that I've included.  I may understand coded
graphics, but when it comes to drawing them with a mouse, I just can't do it.

I'm considering doing a Tutorial on virtual screens next week.  Very handy
things to know about.

FEEDBACK!!!! FEEDBACK!!! FEEDBACK!!!


Right!  You've read this, so you have no excuses!  Write to me!  Tell me what
you think.  Tell me what you'd like to see.  This is your tutorial, tell me
what YOU want to see.  The more encouragement I get, the more likely I am
to write faster, draw nicer pictures, write nicer demo progs, etc.  So it's
worth your while dropping me a simple e-mail.

(Standard bit ripped from last tutorial)Ŀ
Ŀ
If there is anything in this tutorial which you would like further details 
on, then please do not hesitate to contact me.                             
                                                                           
If you produce anything nice using this tutorial, then please send it to   
me.  I am always keen to see other people's work.                          
                                                                           
Comments on this tutorial will be gratefully recieved.  Was it any use to  
you? Too informal?  Too complicated?  Not clear enough?  Too simple?       
What subjects would you like to see covered in later editions?             

		ڴFrom 'It's Alright Ma' by Bob Dylanÿ
		                                     
		      'So do not fear,               
		       If you hear,                  
		       A foreign sound,              
		       To your ear,                  
		       It's alright Ma,              
		       I'm only sighing.             
		                                     
		

Barny Mercer    - (original code & text file) 2/9/95 @ 5:45pm (how come I 
							       almost always 
							       finish these 
							       things at ?:45 
							       ??!)

email : barny.mercer@zetnet.co.uk
WWW   : http://www.zetnet.co.uk/users/bmercer/
voice : 01595 692097

Richard Griffiths       - Pascal conversion (perhaps <grin>)
email : richard.griffiths@zetnet.co.uk
WWW   : http://www.zetnet.co.uk/users/rgriff/
	
