397 lines
16 KiB
HTML
397 lines
16 KiB
HTML
<HTML>
|
|
<HEAD>
|
|
<META name=vsisbn content="1576101746">
|
|
<META name=vstitle content="Michael Abrash's Graphics Programming Black Book, Special Edition">
|
|
<META name=vsauthor content="Michael Abrash">
|
|
<META name=vspublisher content="The Coriolis Group">
|
|
<META name=vspubdate content="07/01/97">
|
|
<META name=vscategory content="Web and Software Development: Game Development,Web and Software Development: Graphics and Multimedia Development">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<TITLE>Michael Abrash's Graphics Programming Black Book Special Edition: Those Way-Down Polygon Nomenclature Blues</TITLE>
|
|
|
|
<!-- HEADER -->
|
|
<!-- Empty Reference Subhead -->
|
|
|
|
<!--ISBN=1576101746//-->
|
|
<!--TITLE=Michael Abrash's Graphics Programming Black Book Special Edition//-->
|
|
<!--AUTHOR=Michael Abrash//-->
|
|
<!--PUBLISHER=The Coriolis Group, Inc.//-->
|
|
<!--CHAPTER=41//-->
|
|
<!--PAGES=766-771//-->
|
|
<!--UNASSIGNED1//-->
|
|
<!--UNASSIGNED2//--></HEAD><BODY LINK=#0000FF ALINK=#000099 VLINK=#0000FF BGCOLOR=#FFFFFF>
|
|
|
|
<CENTER>
|
|
<TABLE BORDER>
|
|
<TR>
|
|
<TD><A HREF="41-03.html">Previous</A></TD>
|
|
<TD><A HREF="index.html">Table of Contents</A></TD>
|
|
<TD><A HREF="42-01.html">Next</A></TD>
|
|
</TR>
|
|
</TABLE>
|
|
</CENTER>
|
|
<P><BR></P>
|
|
<P><B>LISTING 41.4 L41-4.C</B></P>
|
|
<!-- CODE //-->
|
|
<PRE>
|
|
/* Color-fills an arbitrarily-shaped polygon described by VertexList.
|
|
If the first and last points in VertexList are not the same, the path
|
|
around the polygon is automatically closed. All vertices are offset
|
|
by (XOffset, YOffset). Returns 1 for success, 0 if memory allocation
|
|
failed. All C code tested with Borland C++.
|
|
|
|
If the polygon shape is known in advance, speedier processing may be
|
|
enabled by specifying the shape as follows: “convex” - a rubber band
|
|
stretched around the polygon would touch every vertex in order;
|
|
”nonconvex” - the polygon is not self-intersecting, but need not be
|
|
convex; “complex” - the polygon may be self-intersecting, or, indeed,
|
|
any sort of polygon at all. Complex will work for all polygons; convex
|
|
is fastest. Undefined results will occur if convex is specified for a
|
|
nonconvex or complex polygon.
|
|
|
|
Define CONVEX_CODE_LINKED if the fast convex polygon filling code from
|
|
the February 1991 column is linked in. Otherwise, convex polygons are
|
|
handled by the complex polygon filling code.
|
|
Nonconvex is handled as complex in this implementation. See text for a
|
|
discussion of faster nonconvex handling. */
|
|
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#ifdef __TURBOC__
|
|
#include <alloc.h>
|
|
#else /* MSC */
|
|
#include <malloc.h>
|
|
#endif
|
|
#include “polygon.h”
|
|
|
|
#define SWAP(a,b) {temp = a; a = b; b = temp;}
|
|
|
|
struct EdgeState {
|
|
struct EdgeState *NextEdge;
|
|
int X;
|
|
int StartY;
|
|
int WholePixelXMove;
|
|
int XDirection;
|
|
int ErrorTerm;
|
|
int ErrorTermAdjUp;
|
|
int ErrorTermAdjDown;
|
|
int Count;
|
|
};
|
|
|
|
extern void DrawHorizontalLineSeg(int, int, int, int);
|
|
extern int FillMonotoneVerticalPolygon(struct PointListHeader *,
|
|
int, int, int);
|
|
extern int PolygonIsMonotoneVertical(struct PointListHeader *);
|
|
static void BuildGET(struct PointListHeader *, struct EdgeState *,
|
|
int, int);
|
|
static void MoveXSortedToAET(int);
|
|
static void ScanOutAET(int, int);
|
|
static void AdvanceAET(void);
|
|
static void XSortAET(void);
|
|
|
|
/* Pointers to global edge table (GET) and active edge table (AET) */
|
|
static struct EdgeState *GETPtr, *AETPtr;
|
|
|
|
int FillPolygon(struct PointListHeader * VertexList, int Color,
|
|
int PolygonShape, int XOffset, int YOffset)
|
|
{
|
|
struct EdgeState *EdgeTableBuffer;
|
|
int CurrentY;
|
|
|
|
#ifdef CONVEX_CODE_LINKED
|
|
/* Pass convex polygons through to fast convex polygon filler */
|
|
if ((PolygonShape == CONVEX) ||
|
|
PolygonIsMonotoneVertical(VertexList))
|
|
return(FillMonotoneVerticalPolygon(VertexList, Color, XOffset,
|
|
YOffset));
|
|
#endif
|
|
|
|
/* It takes a minimum of 3 vertices to cause any pixels to be
|
|
drawn; reject polygons that are guaranteed to be invisible */
|
|
if (VertexList->Length < 3)
|
|
return(1);
|
|
/* Get enough memory to store the entire edge table */
|
|
if ((EdgeTableBuffer =
|
|
(struct EdgeState *) (malloc(sizeof(struct EdgeState) *
|
|
VertexList->Length))) == NULL)
|
|
return(0); /* couldn’t get memory for the edge table */
|
|
/* Build the global edge table */
|
|
BuildGET(VertexList, EdgeTableBuffer, XOffset, YOffset);
|
|
/* Scan down through the polygon edges, one scan line at a time,
|
|
so long as at least one edge remains in either the GET or AET */
|
|
AETPtr = NULL; /* initialize the active edge table to empty */
|
|
CurrentY = GETPtr->StartY; /* start at the top polygon vertex */
|
|
while ((GETPtr != NULL) || (AETPtr != NULL)) {
|
|
MoveXSortedToAET(CurrentY); /* update AET for this scan line */
|
|
ScanOutAET(CurrentY, Color); /* draw this scan line from AET */
|
|
AdvanceAET(); /* advance AET edges 1 scan line */
|
|
XSortAET(); /* resort on X */
|
|
CurrentY++; /* advance to the next scan line */
|
|
}
|
|
/* Release the memory we’ve allocated and we’re done */
|
|
free(EdgeTableBuffer);
|
|
return(1);
|
|
}
|
|
|
|
/* Creates a GET in the buffer pointed to by NextFreeEdgeStruc from
|
|
the vertex list. Edge endpoints are flipped, if necessary, to
|
|
guarantee all edges go top to bottom. The GET is sorted primarily
|
|
by ascending Y start coordinate, and secondarily by ascending X
|
|
start coordinate within edges with common Y coordinates. */
|
|
static void BuildGET(struct PointListHeader * VertexList,
|
|
struct EdgeState * NextFreeEdgeStruc, int XOffset, int YOffset)
|
|
{
|
|
int i, StartX, StartY, EndX, EndY, DeltaY, DeltaX, Width, temp;
|
|
struct EdgeState *NewEdgePtr;
|
|
struct EdgeState *FollowingEdge, **FollowingEdgeLink;
|
|
struct Point *VertexPtr;
|
|
|
|
/* Scan through the vertex list and put all non-0-height edges into
|
|
the GET, sorted by increasing Y start coordinate */
|
|
VertexPtr = VertexList->PointPtr; /* point to the vertex list */
|
|
GETPtr = NULL; /* initialize the global edge table to empty */
|
|
for (i = 0; i < VertexList->Length; i++) {
|
|
/* Calculate the edge height and width */
|
|
StartX = VertexPtr[i].X + XOffset;
|
|
StartY = VertexPtr[i].Y + YOffset;
|
|
/* The edge runs from the current point to the previous one */
|
|
if (i == 0) {
|
|
/* Wrap back around to the end of the list */
|
|
EndX = VertexPtr[VertexList->Length-1].X + XOffset;
|
|
EndY = VertexPtr[VertexList->Length-1].Y + YOffset;
|
|
} else {
|
|
EndX = VertexPtr[i-1].X + XOffset;
|
|
EndY = VertexPtr[i-1].Y + YOffset;
|
|
}
|
|
/* Make sure the edge runs top to bottom */
|
|
if (StartY > EndY) {
|
|
SWAP(StartX, EndX);
|
|
SWAP(StartY, EndY);
|
|
}
|
|
/* Skip if this can’t ever be an active edge (has 0 height) */
|
|
if ((DeltaY = EndY - StartY) != 0) {
|
|
/* Allocate space for this edge’s info, and fill in the
|
|
structure */
|
|
NewEdgePtr = NextFreeEdgeStruc++;
|
|
NewEdgePtr->XDirection = /* direction in which X moves */
|
|
((DeltaX = EndX - StartX) > 0) ? 1 : -1;
|
|
Width = abs(DeltaX);
|
|
NewEdgePtr->X = StartX;
|
|
NewEdgePtr->StartY = StartY;
|
|
NewEdgePtr->Count = DeltaY;
|
|
NewEdgePtr->ErrorTermAdjDown = DeltaY;
|
|
if (DeltaX >= 0) /* initial error term going L->R */
|
|
NewEdgePtr->ErrorTerm = 0;
|
|
else /* initial error term going R->L */
|
|
NewEdgePtr->ErrorTerm = -DeltaY + 1;
|
|
if (DeltaY >= Width) { /* Y-major edge */
|
|
NewEdgePtr->WholePixelXMove = 0;
|
|
NewEdgePtr->ErrorTermAdjUp = Width;
|
|
} else { /* X-major edge */
|
|
NewEdgePtr->WholePixelXMove =
|
|
(Width / DeltaY) * NewEdgePtr->XDirection;
|
|
NewEdgePtr->ErrorTermAdjUp = Width % DeltaY;
|
|
}
|
|
/* Link the new edge into the GET so that the edge list is
|
|
still sorted by Y coordinate, and by X coordinate for all
|
|
edges with the same Y coordinate */
|
|
FollowingEdgeLink = &GETPtr;
|
|
for (;;) {
|
|
FollowingEdge = *FollowingEdgeLink;
|
|
if ((FollowingEdge == NULL) ||
|
|
(FollowingEdge->StartY > StartY) ||
|
|
((FollowingEdge->StartY == StartY) &&
|
|
(FollowingEdge->X >= StartX))) {
|
|
NewEdgePtr->NextEdge = FollowingEdge;
|
|
*FollowingEdgeLink = NewEdgePtr;
|
|
break;
|
|
}
|
|
FollowingEdgeLink = &FollowingEdge->NextEdge;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Sorts all edges currently in the active edge table into ascending
|
|
order of current X coordinates */
|
|
static void XSortAET() {
|
|
struct EdgeState *CurrentEdge, **CurrentEdgePtr, *TempEdge;
|
|
int SwapOccurred;
|
|
|
|
/* Scan through the AET and swap any adjacent edges for which the
|
|
second edge is at a lower current X coord than the first edge.
|
|
Repeat until no further swapping is needed */
|
|
if (AETPtr != NULL) {
|
|
do {
|
|
SwapOccurred = 0;
|
|
CurrentEdgePtr = &AETPtr;
|
|
while ((CurrentEdge = *CurrentEdgePtr)->NextEdge != NULL) {
|
|
if (CurrentEdge->X > CurrentEdge->NextEdge->X) {
|
|
/* The second edge has a lower X than the first;
|
|
swap them in the AET */
|
|
TempEdge = CurrentEdge->NextEdge->NextEdge;
|
|
*CurrentEdgePtr = CurrentEdge->NextEdge;
|
|
CurrentEdge->NextEdge->NextEdge = CurrentEdge;
|
|
CurrentEdge->NextEdge = TempEdge;
|
|
SwapOccurred = 1;
|
|
}
|
|
CurrentEdgePtr = &(*CurrentEdgePtr)->NextEdge;
|
|
}
|
|
} while (SwapOccurred != 0);
|
|
}
|
|
}
|
|
|
|
/* Advances each edge in the AET by one scan line.
|
|
Removes edges that have been fully scanned. */
|
|
static void AdvanceAET() {
|
|
struct EdgeState *CurrentEdge, **CurrentEdgePtr;
|
|
|
|
/* Count down and remove or advance each edge in the AET */
|
|
CurrentEdgePtr = &AETPtr;
|
|
while ((CurrentEdge = *CurrentEdgePtr) != NULL) {
|
|
/* Count off one scan line for this edge */
|
|
if ((--(CurrentEdge->Count)) == 0) {
|
|
/* This edge is finished, so remove it from the AET */
|
|
*CurrentEdgePtr = CurrentEdge->NextEdge;
|
|
} else {
|
|
/* Advance the edge’s X coordinate by minimum move */
|
|
CurrentEdge->X += CurrentEdge->WholePixelXMove;
|
|
/* Determine whether it’s time for X to advance one extra */
|
|
if ((CurrentEdge->ErrorTerm +=
|
|
CurrentEdge->ErrorTermAdjUp) > 0) {
|
|
CurrentEdge->X += CurrentEdge->XDirection;
|
|
CurrentEdge->ErrorTerm -= CurrentEdge->ErrorTermAdjDown;
|
|
}
|
|
CurrentEdgePtr = &CurrentEdge->NextEdge;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Moves all edges that start at the specified Y coordinate from the
|
|
GET to the AET, maintaining the X sorting of the AET. */
|
|
static void MoveXSortedToAET(int YToMove) {
|
|
struct EdgeState *AETEdge, **AETEdgePtr, *TempEdge;
|
|
int CurrentX;
|
|
|
|
/* The GET is Y sorted. Any edges that start at the desired Y
|
|
coordinate will be first in the GET, so we’ll move edges from
|
|
the GET to AET until the first edge left in the GET is no longer
|
|
at the desired Y coordinate. Also, the GET is X sorted within
|
|
each Y coordinate, so each successive edge we add to the AET is
|
|
guaranteed to belong later in the AET than the one just added. */
|
|
AETEdgePtr = &AETPtr;
|
|
while ((GETPtr != NULL) && (GETPtr->StartY == YToMove)) {
|
|
CurrentX = GETPtr->X;
|
|
/* Link the new edge into the AET so that the AET is still
|
|
sorted by X coordinate */
|
|
for (;;) {
|
|
AETEdge = *AETEdgePtr;
|
|
if ((AETEdge == NULL) || (AETEdge->X >= CurrentX)) {
|
|
TempEdge = GETPtr->NextEdge;
|
|
*AETEdgePtr = GETPtr; /* link the edge into the AET */
|
|
GETPtr->NextEdge = AETEdge;
|
|
AETEdgePtr = &GETPtr->NextEdge;
|
|
GETPtr = TempEdge; /* unlink the edge from the GET */
|
|
break;
|
|
} else {
|
|
AETEdgePtr = &AETEdge->NextEdge;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fills the scan line described by the current AET at the specified Y
|
|
coordinate in the specified color, using the odd/even fill rule */
|
|
static void ScanOutAET(int YToScan, int Color) {
|
|
int LeftX;
|
|
struct EdgeState *CurrentEdge;
|
|
|
|
/* Scan through the AET, drawing line segments as each pair of edge
|
|
crossings is encountered. The nearest pixel on or to the right
|
|
of left edges is drawn, and the nearest pixel to the left of but
|
|
not on right edges is drawn */
|
|
CurrentEdge = AETPtr;
|
|
while (CurrentEdge != NULL) {
|
|
LeftX = CurrentEdge->X;
|
|
CurrentEdge = CurrentEdge->NextEdge;
|
|
DrawHorizontalLineSeg(YToScan, LeftX, CurrentEdge->X-1, Color);
|
|
CurrentEdge = CurrentEdge->NextEdge;
|
|
}
|
|
}
|
|
</PRE>
|
|
<!-- END CODE //-->
|
|
<P><B>LISTING 41.5 POLYGON.H</B></P>
|
|
<!-- CODE //-->
|
|
<PRE>
|
|
/* Header file for polygon-filling code */
|
|
|
|
#define CONVEX 0
|
|
#define NONCONVEX 1
|
|
#define COMPLEX 2
|
|
|
|
/* Describes a single point (used for a single vertex) */
|
|
struct Point {
|
|
int X; /* X coordinate */
|
|
int Y; /* Y coordinate */
|
|
};
|
|
|
|
/* Describes series of points (used to store a list of vertices that describe
|
|
a polygon; each vertex is assumed to connect to the two adjacent vertices, and
|
|
last vertex is assumed to connect to the first) */
|
|
struct PointListHeader {
|
|
int Length; /* # of points */
|
|
struct Point * PointPtr; /* pointer to list of points */
|
|
};
|
|
|
|
/* Describes beginning and ending X coordinates of a single horizontal line */
|
|
struct HLine {
|
|
int XStart; /* X coordinate of leftmost pixel in line */
|
|
int XEnd; /* X coordinate of rightmost pixel in line */
|
|
};
|
|
|
|
/* Describes a Length-long series of horizontal lines, all assumed to be on
|
|
contiguous scan lines starting at YStart and proceeding downward (used to
|
|
describe scan-converted polygon to low-level hardware-dependent drawing code) */
|
|
struct HLineList {
|
|
int Length; /* # of horizontal lines */
|
|
int YStart; /* Y coordinate of topmost line */
|
|
struct HLine * HLinePtr; /* pointer to list of horz lines */
|
|
};
|
|
|
|
/* Describes a color as an RGB triple, plus one byte for other info */
|
|
struct RGB { unsigned char Red, Green, Blue, Spare; };
|
|
</PRE>
|
|
<!-- END CODE //-->
|
|
<P>Is monotone-vertical polygon detection worth all this trouble? Under the right circumstances, you bet. In a situation where a great many polygons are being drawn, and the application either doesn’t know whether they’re monotone-vertical or has no way to tell the polygon filler that they are, performance can be increased considerably if most polygons are, in fact, monotone-vertical. This potential performance advantage is helped along by the surprising fact that Jim’s test for monotone-vertical status is simpler and faster than my original, nonfunctional test for convexity.
|
|
</P>
|
|
<P>See what accurate terminology and effective communication can do?</P><P><BR></P>
|
|
<CENTER>
|
|
<TABLE BORDER>
|
|
<TR>
|
|
<TD><A HREF="41-03.html">Previous</A></TD>
|
|
<TD><A HREF="index.html">Table of Contents</A></TD>
|
|
<TD><A HREF="42-01.html">Next</A></TD>
|
|
</TR>
|
|
</TABLE>
|
|
</CENTER>
|
|
|
|
<hr width="90%" size="1" noshade>
|
|
<div align="center">
|
|
<font face="Verdana,sans-serif" size="1">Graphics Programming Black Book © 2001 Michael Abrash</font>
|
|
</div>
|
|
<!-- all of the reference materials (books) have the footer and subfoot reveresed -->
|
|
<!-- reference_subfoot = footer -->
|
|
<!-- reference_footer = subfoot -->
|
|
|
|
<!-- BEGIN SUB FOOTER -->
|
|
</BODY>
|
|
</HTML>
|
|
|
|
<!-- END FOOTER -->
|
|
|
|
|