WGT Graphics Tutorial #3
		   Topic: Texture Mapped Polygons
			  By Chris Egerter
			  December 8, 1994
	     Contact me at: chris.egerter@homebase.com
		Compuserve: 75242,2411

Introduction
------------
   This series of tutorials describes a method of drawing filled polygons
using 3 rendering techniques: Solid, Gouraud shading, and texture mapping.

The code in this tutorial was written in Turbo C++ but can be ported to
other graphics libraries and operating systems.  I did not use the
WGT functions in this one, so the wgtgfx.c file contains a few routines
which are needed for the demos.  I have decided to explain the method
used for these routines since I had to discover them on my own, and
think you can learn from the code.


1.0 - From Gouraud to Texture
-----------------------------
If you haven't read the previous tutorials, please read them first since
they provide the basic ideas for filling polygons.  In the last issue I
mentioned that texture mapping is only slightly different from shading.
This may not seem very obvious to you, so I'll explain.

When we scan converted the Gouraud shaded edges, we kept track of the
starting and ending color on each horizontal line.  Texture mapping is
the same, only we need to keep track of X and Y coordinates instead of
a color.  In place of a start and end color, we can get a (x,y) coordinate
at each end of the line.  Let's modify our point structure to store
coordinates:

typedef struct
    {
     int x,y;
     int sx, sy;	/* Source coordinates from the texture image */
    } tpoint;

sx and sy is a location on another image, which is associated with a
vertex of the polygon.  They are commonly called u and v coordiantes in
graphics texts.

When we converted the polygon into a list of x coordinates, we stored them
into 4 arrays, startx and endx, startcol and endcol.  With texture mapping
we need to remove the color array, and replace it with startx,
starty, endx and endy.   I've decided to make this into a structure
to keep things a bit cleaner.

struct {
   int startx;          /* The first X coord */
   int endx;            /* The last X coord */
   int x1;              /* First pixel coord out of texture */
   int y1;
   int x2;              /* Last pixel coord out of texture */
   int y2;
   } texturepoint[200];  /* One for each scan line (320x200x256) */

When the list is created, we will have two screen x coordinates,
two x texture coordinates, and two y texture coordinates.

The gpolyline routine becomes the tpolyline routine, which calculates
the texture coordinates at the ends of each horizontal line.  To do this,
we use fixed point math in the same way as the Gouraud routine, only
we step along the x and y at the same time.

void tpolyline (int x1, int y1, int tx1, int ty1, int x2, int y2,
		int tx2, int ty2)
/* Calculates the coordinates of a line given two
   vertices, (x1,y1) with texture coordinates (tx1, ty1),  and
   (x2, y2) with texture coordinates (tx2,ty2).

   We will use fixed point math to speed things up.
   The x coordinate is multiplied by 256 and for each row,
   a constant m is added to x.  This is a simplified version
   of a line algorithm because we only have to store 1 x coordinate
   for every y coordinate.

   The texture coordinates increase by a step value based on the
   number of pixels between the texture coordinates and the distance between
   the screen y coordinates.

*/
{
 int tmp,y;
 long x,m;

 long xcoord, xstep;   /* X texture coordinate, and step value */
 long ycoord, ystep;   /* Y texture coordinate, and step value */

 if (y2 != y1)         /* This isn't a horizontal line */
 {
   if (y2 < y1)        /* Make sure y2 is greater than y1 */
   {
    tmp = y1;          /* Swap the y coordinate */
    y1 = y2;
    y2 = tmp;

    tmp = x1;          /* Swap the corresponding x coordinates */
    x1 = x2;
    x2 = tmp;

    tmp = tx1;        /* Swap the corresponding x values */
    tx1 = tx2;
    tx2 = tmp;

    tmp = ty1;        /* Swap the corresponding y values */
    ty1 = ty2;
    ty2 = tmp;
   }

 x = (long)x1<<8;  /* Multiply by 256 */

 m = ((long)(x2 - x1)<<8) / ((long)(y2 - y1));
 /* m is the fractional amount to add to the x coordinate every row.
    m is equal to (delta x) / (delta y).  In other words, the x coordinate
    has to change by (x2 - x1) columns in (y2 - y1) rows. */

 xcoord = (long)tx1 << 8;   /* Initial x coord in 8.8 fixed point format */
 xstep = ((long)(tx2 - tx1) << 8) / ((long)(y2 - y1));
 /* Calculate the x step value */

 ycoord = (long)ty1 << 8;   /* Initial y coord in 8.8 fixed point format */
 ystep = ((long)(ty2 - ty1) << 8) / ((long)(y2 - y1));
 /* Calculate the y step value */

 x += m; /* We ALWAYS skip the first point in every line. This is done */
 y1++; /* because we do not want to store the point where two lines
	  meet, twice.  This would result in a single point being drawn. */

 for (y = y1; y <= y2; y++) /* Go through each row */
  {
   if ((y >= 0) & (y < 200)) /* If the coordinate is on the screen */
    if (texturepoint[y].startx == -16000) /* Store the first coordinate */
      {
       texturepoint[y].startx = x >> 8;  /* Store the first coordinate */
       texturepoint[y].x1 = xcoord >> 8;
       texturepoint[y].y1 = ycoord >> 8;
      }
    else
      {
       texturepoint[y].endx = x >> 8;  /* Store the last coordinate */
       texturepoint[y].x2 = xcoord >> 8;
       texturepoint[y].y2 = ycoord >> 8;
      }
   x += m;		     /* Add our constant to x */
   xcoord += xstep;	     /* Add our x step value to the texture */
   ycoord += ystep;          /* Add our y step value to the texture */
   }
 }
}


Next we have the actual texture mapping routine that calls the previous
routine and draws the polygon.


void texturedpoly (tpoint *vertexlist, int numvertex)
/* Draws a textured polygon given an array of vertices. */
{
int i;
tpoint *curpt,*nextpt;
  /* Two pointers to a vertex. These are used to connect to vertices
     together in when calling the gpolyline routine. */

 curpt = vertexlist;      /* Set to the first vertex in the array */
 nextpt = vertexlist + 1; /* and to the second vertex */

 for (i = 0; i < 200; i++)
  {
   texturepoint[i].startx = -16000;     /* Set up our impossible values */
   texturepoint[i].endx = -16000;
  }

 for (i = 1; i < numvertex; i++)
  {
   tpolyline (curpt->x,  curpt->y,  curpt->sx, curpt->sy,
	      nextpt->x, nextpt->y, nextpt->sx, nextpt->sy);
   /* Calculate the edge of this line. */

   curpt += 1;  /* Go to the next line */
   nextpt += 1;
  }

  nextpt = vertexlist;  /* Now close the polygon by doing a line between
			   the first and last vertex. */
  tpolyline (curpt->x,  curpt->y,  curpt->sx, curpt->sy,
	     nextpt->x, nextpt->y, nextpt->sx, nextpt->sy);

  for (i = 0; i < 200; i++)   /* Now draw the horizontal line list */
    if (texturepoint[i].startx != -16000)
     /* Indicates there is a line on this row */
    {
     if (texturepoint[i].endx == -16000)
	 texturepoint[i].endx = texturepoint[i].startx;
	 /* In case there was only one point found on this row */
       tmapline (texturepoint[i].startx,
		 texturepoint[i].x1, texturepoint[i].y1,
		 texturepoint[i].endx,
		 texturepoint[i].x2, texturepoint[i].y2,
		 i);
       /* Draw a texture mapped line between the two x coordinates,
	  on the row i. */
    }
}


So far the code is just keeping track of the coordinates for each
horizontal line.  The real texture mapping code lies in the tmapline
routine which draws it on the screen.


2.0 Size Restrictions
---------------------
I will now place a restriction on our textures to enable some optimizations.
The texture MUST have a width of 256, and has a maximum height of 256.
The reason for this is you can store an X or Y texture coordinate in half
of a register.

This restriction isn't necessary. I also have code for texture mapping
any size texture, but I feel the speed increase is well worth it and decided
to present this method.

3.0 - TMAPLINE Pseudo-code
--------------------------
Our basic texture mapped line routine looks like this:
  Calculate the x step value
  Calculate the y step value
  Make a coordinate variable equal to the left endpoint's texture coordinate.
  For x = x1 to x2
    Read a pixel from the texture
    Put pixel on screen
    Add x step value to the texture coordinate
    Add y step value to the texture coordinate
  End for


4.0 Calculating the step values
-------------------------------
The tmapline routine needs to step along the x and y coordinate as we draw
the line.  To do this, we can calculate a step value similar to the
technique described in the Gouraud shading tutorial.

 length = x2 - x1 + 1;		/* Calculate the length of this line */

 deltax = tmapx2 - tmapx1 + 1;	/* Find the delta values */
 deltay = tmapy2 - tmapy1 + 1;

 xincr = ((long)(deltax) << 8) / (long)length;
 yincr = ((long)(deltay) << 8) / (long)length;

Now we have the values which are added to the texture coordinate
for every pixel drawn.


5.0 Assembly Tricks
-------------------
Here's how I would set up the registers to hold the texture coordinates.
This is the key to optimized texture mapping.

EDX is a 32-bit register that will contain the texture coordinates.
Since the x and y coordinates can go up to 256, they use 8 bits each.
As well, we will use 8 bits for the fractional portion of our fixed point
number.

 Y whole value  Y fraction	 X whole value  X fraction
<--------------.--------------> <--------------.-------------->
1 2 3 4 5 6 7 8 9 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3
		  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2

As you can see, the register will hold two 8.8 fixed point numbers.


ESI is used the same way, only it holds the step values.

 Y whole step   Y frac. step     X whole step   X frac. step
<--------------.--------------> <--------------.-------------->
1 2 3 4 5 6 7 8 9 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3
		  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2

To add the step values to the texture coordinates, we simply add
ESI to EDX.

The next step is to take the x and y whole value out of EDX and
create a single offset into our texture image.  To do this, we have
to remove the fractional portions, multiply the y value by the texture's
width, and add them together.

First copy EDX into another register so we can work with it. I used EBX
because it was the only one left.  We want to access the high word of the
register, so we must shift it right.

mov ebx, edx	 /* Copy EDX into EBX for work */
shr ebx, 16      /* BH now contains the y coordinate */

By shifting it right 16 places, EBX now looks like this:
				 Y whole value  Y fraction
				<-----BH-------.------BL------>
1 2 3 4 5 6 7 8 9 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3
		  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2

Here's the trick.  Since our texture image has a width of 256, and the
y is in BH, it is already multiplied by the width! To create an offset,
all we have left to do is add the x value.  In other words, we have to put
the x whole value into BL.  EBX no longer contains the x value, so we'll
get it from EDX.   The x whole value is stored in DH.

mov bl, dh	/* Store the x value in BL */

BX is now an offset into the texture image, between 0 and 65535.
To get the pixel from the texture image, we use bx as the offset,
and ds as the segment of the texture.  This means the texture MUST be
aligned to a segment.  Having a texture fit perfectly within a segment
enables you to have a repeating texture, where the source texture coordinates
are much large than the texture itself.  The values will wrap around at
256 and cause the texture to be shown more than once.  You will see this
effect in the first demo.

mov al, ds:[bx]	/* Get the color from the texture image */

Finally we need to add the step values to the texture coordinates.

add edx, esi	/* Advance one pixel */

You can then put the pixel on the visual screen, where the pointer to the
screen is stored in es:di.


6.0 Other optimizations
-----------------------
Another optimization I used is drawing two pixels at once.  You can
accomplish this by storing the first pixel in ah, the second in al,
and writing AX to the screen.  This method requires a check to see if an odd
pixel needs to be drawn.


7.0 The code
------------
Whew. That was pretty complicated code.  The same thing could have been done
easily in C, but several key optimizations would not be possible.
Here is the full texture mapping line routine:

void tmapline (int x1, int tmapx1, int tmapy1, int x2, int tmapx2,
	       int tmapy2, int y)
/* Draws one scanline of the textured polygon.
   Where:
     x1 is the first x coordinate
     tmapx1 is the x coordinate on the texture relating to (x1,y)
     tmapy1 is the y coordinate on the texture relating to (x1,y)
     x2 is the second x coordinate
     tmapx2 is the x coordinate on the texture relating to (x2,y)
     tmapy2 is the y coordinate on the texture relating to (x2,y)
     y is the scanline to draw the line on
*/
{
long length;
long deltax, deltay;	    /* Difference between the x and y coordinates */

unsigned char far * dest;   /* Ptr to the screen */
unsigned char far * src;    /* Ptr to texture image */

int xincr; /* X offset into texture, amount to increase every pixel */
int yincr; /* Y offset into texture, amount to increase every pixel */

int xpos, ypos;   /* Stores fractional part after clipping */

int t;   /* Used for swapping */


 if (x1 > x2)		/* Swap all the coordinates so x1 < x2 */
  {
   t = x1; x1 = x2; x2 = t;
   t = tmapx1; tmapx1 = tmapx2; tmapx2 = t;
   t = tmapy1; tmapy1 = tmapy2; tmapy2 = t;
  }

 length = x2 - x1 + 1;		/* Calculate the length of this line */
 if (length > 0)
 {
  deltax = tmapx2 - tmapx1 + 1;	/* Find the delta values */
  deltay = tmapy2 - tmapy1 + 1;

  dest = abuf + y * 320 + x1;  /* Make a pointer to the correct place in
				  video memory */
  src  = textureimage + tmapy1 * 256 + tmapx1;
	/* Make a pointer to the correct place on the texture map
	   Assumes the image is 256 in width. */

  xincr = ((long)(deltax) << 8) / (long)length;
  yincr = ((long)(deltay) << 8) / (long)length;

    /* Calculate the step value for the x and y coordinates.
       For every pixel on the destination, the x coordinate on the texture
       will move xincr pixels. */

 xpos = tmapx1<<8;
 ypos = tmapy1<<8;

 asm {
       .386
       push ds
       cld
       mov cx, word ptr length		/* Set length */
       shr cx, 1
       les di, dest			/* Set destination ptr */
       lds si, src			/* Set source ptr */
       mov dx, word ptr ypos		/* Put the y in the low word */
       shl edx, 16                      /* Move the y to the high word */
       mov dx, word ptr xpos		/* Put the x in the low word */

       mov si, word ptr yincr		/* Set up the increments the  */
       shl esi, 16			/* same way */
       mov si, word ptr xincr
      /* Now to advance one pixel, we can add edx and esi together to
	 advance the x and y at the same time, with the fractional
	 portion automatically carrying at 256. */

      cmp cx, 0
      je onepixel
      }
  tlineloop:
  ;
  asm {
       mov ebx, edx
       shr ebx, 16      /* BH now contains the y coordinate */
       mov bl, dh	/* Store the x value in BL, */
			/* BX is now an offset into the texture image,
			   between 0 and 65535. */
       mov al, ds:[bx]	/* Get the color from the texture image */
       add edx, esi	/* Advance one pixel */

       mov ebx, edx	/* Repeat the above, and get another pixel */
       shr ebx, 16
       mov bl, dh
       mov ah, ds:[bx]
       add edx, esi

       stosw		/* Store a word to the destination */
       dec cx		/* Decrease length */
       jnz tlineloop	/* Repeat for all pixels */
      }
  onepixel:
   asm {
       mov cx, word ptr length
       and cx, 1
       jz tlinedone

       mov ebx, edx
       shr ebx, 16      /* BH now contains the y coordinate */
       mov bl, dh	/* Store the x value in BL, */
			/* BX is now an offset into the texture image,
			   between 0 and 65535. */
       mov al, ds:[bx]	/* Get the color from the texture image */
       mov es:[di], al

    }
   tlinedone:
   asm {
       pop ds
      }
  }
}



8.0 - What About Clipping?
--------------------------
Clipping can be performed similar to the way I used in the Gouraud
shaded line routine.  I will leave this for you to complete if you require
it.


9.0 - Future Tutorials
----------------------
The next tutorial will discuss using dirty rectangles to speed up your
screen updates.  If you have any requests for future topics, feel free to
suggest them to me and I'll see what I can do.



10.0 - Where to get WGT 4.0
---------------------------
You can download my graphics library from two places:

FTP site:
x2ftp.oulu.fi under /pub/msdos/programming/wgt/wgt4.zip

BBS:
 Softnet BBS (519)-472-3661
 Located in London, Ontario, Canada.
 Login name: word up
 Password: graphics

 The files are located in file area #10.





Discuss this article in the forums


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

See Also:
Texture Mapping

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