508 lines
21 KiB
HTML
508 lines
21 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: Sorted Spans in Action</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=67//-->
|
|
<!--PAGES=1230-1238//-->
|
|
<!--UNASSIGNED1//-->
|
|
<!--UNASSIGNED2//--></HEAD><BODY LINK=#0000FF ALINK=#000099 VLINK=#0000FF BGCOLOR=#FFFFFF>
|
|
|
|
<CENTER>
|
|
<TABLE BORDER>
|
|
<TR>
|
|
<TD><A HREF="67-02.html">Previous</A></TD>
|
|
<TD><A HREF="index.html">Table of Contents</A></TD>
|
|
<TD><A HREF="67-04.html">Next</A></TD>
|
|
</TR>
|
|
</TABLE>
|
|
</CENTER>
|
|
<P><BR></P>
|
|
<H4 ALIGN="LEFT"><A NAME="Heading7"></A><FONT COLOR="#000077">Independent Span Sorting</FONT></H4>
|
|
<P>Finally, we come to independent span sorting, the simplest and fastest of the three, and the type the sample code in Listing 67.1 uses. Here, polygons never intersect or touch any other polygons except adjacent polygons with which they form a continuous mesh. This means that when a polygon starts on a scan line, a single 1/z comparison between that polygon and the polygons it overlaps on the screen is guaranteed to produce correct sorting, with no extra calculations or tricky cases to worry about.
|
|
</P>
|
|
<P>Independent span sorting is ideal for scenes with lots of moving objects that never actually touch each other, such as a space battle. Next, we’ll look at an implementation of independent 1/z span sorting.</P>
|
|
<H3><A NAME="Heading8"></A><FONT COLOR="#000077">1/z Span Sorting in Action</FONT></H3>
|
|
<P>Listing 67.1 is a portion of a program that demonstrates independent 1/z span sorting. This program is based on the sample 3-D clipping program from Chapter 65; however, the earlier program did hidden surface removal (HSR) by simply z-sorting whole objects and drawing them back-to-front, while Listing 67.1 draws all polygons by way of a 1/z-sorted edge list. Consequently, where the earlier program worked only so long as object centers correctly described sorting order, Listing 67.1 works properly for all combinations of non-intersecting and non-abutting polygons. In particular, Listing 67.1 correctly handles concave polyhedra; a new L-shaped object (the data for which is not included in Listing 67.1) has been added to the sample program to illustrate this capability. The ability to handle complex shapes makes Listing 67.1 vastly more useful for real-world applications than the 3-D clipping demo from Chapter 65.
|
|
</P>
|
|
<P><B>Listing 67.1 L67_1.C</B></P>
|
|
<!-- CODE //-->
|
|
<PRE>
|
|
// Part of Win32 program to demonstrate z-sorted spans. Whitespace
|
|
// removed for space reasons. Full source code, with whitespace,
|
|
// available from ftp.idsoftware.com/mikeab/ddjzsort.zip.
|
|
|
|
#define MAX_SPANS 10000
|
|
#define MAX_SURFS 1000
|
|
#define MAX_EDGES 5000
|
|
|
|
typedef struct surf_s {
|
|
struct surf_s *pnext, *pprev;
|
|
int color, visxstart, state;
|
|
double zinv00, zinvstepx, zinvstepy;
|
|
} surf_t;
|
|
|
|
typedef struct edge_s {
|
|
int x, xstep, leading;
|
|
surf_t *psurf;
|
|
struct edge_s *pnext, *pprev, *pnextremove;
|
|
} edge_t;
|
|
|
|
// Span, edge, and surface lists
|
|
span_t spans[MAX_SPANS];
|
|
edge_t edges[MAX_EDGES];
|
|
surf_t surfs[MAX_SURFS];
|
|
|
|
// Bucket list of new edges to add on each scan line
|
|
edge_t newedges[MAX_SCREEN_HEIGHT];
|
|
|
|
// Bucket list of edges to remove on each scan line
|
|
edge_t *removeedges[MAX_SCREEN_HEIGHT];
|
|
|
|
// Head and tail for the active edge list
|
|
edge_t edgehead, edgetail;
|
|
|
|
// Edge used as sentinel of new edge lists
|
|
edge_t maxedge = {0×7FFFFFFF};
|
|
|
|
// Head/tail/sentinel/background surface of active surface stack
|
|
surf_t surfstack;
|
|
|
|
// pointers to next available surface and edge
|
|
surf_t *pavailsurf;
|
|
edge_t *pavailedge;
|
|
|
|
|
|
// Returns true if polygon faces the viewpoint, assuming a clockwise
|
|
// winding of vertices as seen from the front.
|
|
int PolyFacesViewer(polygon_t *ppoly, plane_t *pplane)
|
|
{
|
|
int i;
|
|
point_t viewvec;
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
viewvec.v[i] = ppoly->verts[0].v[i] - currentpos.v[i];
|
|
// Use an epsilon here so we don’t get polygons tilted so
|
|
// sharply that the gradients are unusable or invalid
|
|
if (DotProduct (&viewvec, &pplane->normal) < -0.01)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Add the polygon’s edges to the global edge table.
|
|
void AddPolygonEdges (plane_t *plane, polygon2D_t *screenpoly)
|
|
{
|
|
double distinv, deltax, deltay, slope;
|
|
int i, nextvert, numverts, temp, topy, bottomy, height;
|
|
edge_t *pedge;
|
|
|
|
numverts = screenpoly->numverts;
|
|
|
|
// Clamp the polygon’s vertices just in case some very near
|
|
// points have wandered out of range due to floating-point
|
|
// imprecision
|
|
for (i=0 ; i<numverts ; i++) {
|
|
if (screenpoly->verts[i].x < -0.5)
|
|
screenpoly->verts[i].x = -0.5;
|
|
if (screenpoly->verts[i].x > ((double)DIBWidth - 0.5))
|
|
screenpoly->verts[i].x = (double)DIBWidth - 0.5;
|
|
if (screenpoly->verts[i].y < -0.5)
|
|
screenpoly->verts[i].y = -0.5;
|
|
if (screenpoly->verts[i].y > ((double)DIBHeight - 0.5))
|
|
screenpoly->verts[i].y = (double)DIBHeight - 0.5;
|
|
}
|
|
|
|
// Add each edge in turn
|
|
for (i=0 ; i<numverts ; i++) {
|
|
nextvert = i + 1;
|
|
if (nextvert >= numverts)
|
|
nextvert = 0;
|
|
topy = (int)ceil(screenpoly->verts[i].y);
|
|
bottomy = (int)ceil(screenpoly->verts[nextvert].y);
|
|
height = bottomy - topy;
|
|
if (height == 0)
|
|
continue; // doesn’t cross any scan lines
|
|
if (height < 0) {
|
|
// Leading edge
|
|
temp = topy;
|
|
topy = bottomy;
|
|
bottomy = temp;
|
|
pavailedge->leading = 1;
|
|
deltax = screenpoly->verts[i].x -
|
|
screenpoly->verts[nextvert].x;
|
|
deltay = screenpoly->verts[i].y -
|
|
screenpoly->verts[nextvert].y;
|
|
slope = deltax / deltay;
|
|
// Edge coordinates are in 16.16 fixed point
|
|
pavailedge->xstep = (int)(slope * (float)0×10000);
|
|
pavailedge->x = (int)((screenpoly->verts[nextvert].x +
|
|
((float)topy - screenpoly->verts[nextvert].y) *
|
|
slope) * (float)0×10000);
|
|
} else {
|
|
// Trailing edge
|
|
pavailedge->leading = 0;
|
|
deltax = screenpoly->verts[nextvert].x -
|
|
screenpoly->verts[i].x;
|
|
deltay = screenpoly->verts[nextvert].y -
|
|
screenpoly->verts[i].y;
|
|
slope = deltax / deltay;
|
|
// Edge coordinates are in 16.16 fixed point
|
|
pavailedge->xstep = (int)(slope * (float)0×10000);
|
|
pavailedge->x = (int)((screenpoly->verts[i].x +
|
|
((float)topy - screenpoly->verts[i].y) * slope) *
|
|
(float)0×10000);
|
|
}
|
|
|
|
// Put the edge on the list to be added on top scan
|
|
pedge = &newedges[topy];
|
|
while (pedge->pnext->x < pavailedge->x)
|
|
pedge = pedge->pnext;
|
|
pavailedge->pnext = pedge->pnext;
|
|
pedge->pnext = pavailedge;
|
|
|
|
// Put the edge on the list to be removed after final scan
|
|
pavailedge->pnextremove = removeedges[bottomy - 1];
|
|
removeedges[bottomy - 1] = pavailedge;
|
|
|
|
// Associate the edge with the surface we’ll create for
|
|
// this polygon
|
|
pavailedge->psurf = pavailsurf;
|
|
|
|
// Make sure we don’t overflow the edge array
|
|
if (pavailedge < &edges[MAX_EDGES])
|
|
pavailedge++;
|
|
}
|
|
|
|
// Create the surface, so we’ll know how to sort and draw from
|
|
// the edges
|
|
pavailsurf->state = 0;
|
|
pavailsurf->color = currentcolor;
|
|
|
|
// Set up the 1/z gradients from the polygon, calculating the
|
|
// base value at screen coordinate 0,0 so we can use screen
|
|
// coordinates directly when calculating 1/z from the gradients
|
|
distinv = 1.0 / plane->distance;
|
|
pavailsurf->zinvstepx = plane->normal.v[0] * distinv *
|
|
maxscreenscaleinv * (fieldofview / 2.0);
|
|
pavailsurf->zinvstepy = -plane->normal.v[1] * distinv *
|
|
maxscreenscaleinv * (fieldofview / 2.0);
|
|
pavailsurf->zinv00 = plane->normal.v[2] * distinv -
|
|
xcenter * pavailsurf->zinvstepx -
|
|
ycenter * pavailsurf->zinvstepy;
|
|
|
|
// Make sure we don’t overflow the surface array
|
|
if (pavailsurf < &surfs[MAX_SURFS])
|
|
pavailsurf++;
|
|
}
|
|
|
|
|
|
// Scan all the edges in the global edge table into spans.
|
|
void ScanEdges (void)
|
|
{
|
|
int x, y;
|
|
double fx, fy, zinv, zinv2;
|
|
edge_t *pedge, *pedge2, *ptemp;
|
|
span_t *pspan;
|
|
surf_t *psurf, *psurf2;
|
|
|
|
pspan = spans;
|
|
|
|
// Set up the active edge list as initially empty, containing
|
|
// only the sentinels (which are also the background fill). Most
|
|
// of these fields could be set up just once at start-up
|
|
edgehead.pnext = &edgetail;
|
|
edgehead.pprev = NULL;
|
|
edgehead.x = -0×FFFF; // left edge of screen
|
|
edgehead.leading = 1;
|
|
edgehead.psurf = &surfstack;
|
|
edgetail.pnext = NULL; // mark edge of list
|
|
edgetail.pprev = &edgehead;
|
|
edgetail.x = DIBWidth << 16; // right edge of screen
|
|
edgetail.leading = 0;
|
|
edgetail.psurf = &surfstack;
|
|
|
|
// The background surface is the entire stack initially, and
|
|
// is infinitely far away, so everything sorts in front of it.
|
|
// This could be set just once at start-up
|
|
surfstack.pnext = surfstack.pprev = &surfstack;
|
|
surfstack.color = 0;
|
|
surfstack.zinv00 = -999999.0;
|
|
surfstack.zinvstepx = surfstack.zinvstepy = 0.0;
|
|
for (y=0 ; y<DIBHeight ; y++) {
|
|
fy = (double)y;
|
|
// Sort in any edges that start on this scan
|
|
pedge = newedges[y].pnext;
|
|
pedge2 = &edgehead;
|
|
while (pedge != &maxedge) {
|
|
while (pedge->x > pedge2->pnext->x)
|
|
pedge2 = pedge2->pnext;
|
|
ptemp = pedge->pnext;
|
|
pedge->pnext = pedge2->pnext;
|
|
pedge->pprev = pedge2;
|
|
pedge2->pnext->pprev = pedge;
|
|
pedge2->pnext = pedge;
|
|
pedge2 = pedge;
|
|
pedge = ptemp;
|
|
}
|
|
|
|
// Scan out the active edges into spans
|
|
// Start out with the left background edge already inserted,
|
|
// and the surface stack containing only the background
|
|
surfstack.state = 1;
|
|
surfstack.visxstart = 0;
|
|
for (pedge=edgehead.pnext ; pedge ; pedge=pedge->pnext) {
|
|
psurf = pedge->psurf;
|
|
if (pedge->leading) {
|
|
// It’s a leading edge. Figure out where it is
|
|
// relative to the current surfaces and insert in
|
|
// the surface stack; if it’s on top, emit the span
|
|
// for the current top.
|
|
// First, make sure the edges don’t cross
|
|
if (++psurf->state == 1) {
|
|
fx = (double)pedge->x * (1.0 / (double)0×10000);
|
|
// Calculate the surface’s 1/z value at this pixel
|
|
zinv = psurf->zinv00 + psurf->zinvstepx * fx +
|
|
psurf->zinvstepy * fy;
|
|
// See if that makes it a new top surface
|
|
psurf2 = surfstack.pnext;
|
|
zinv2 = psurf2->zinv00 + psurf2->zinvstepx * fx +
|
|
psurf2->zinvstepy * fy;
|
|
if (zinv >= zinv2) {
|
|
// It’s a new top surface
|
|
// emit the span for the current top
|
|
x = (pedge->x + 0×FFFF) >> 16;
|
|
pspan->count = x - psurf2->visxstart;
|
|
if (pspan->count > 0) {
|
|
pspan->y = y;
|
|
pspan->x = psurf2->visxstart;
|
|
pspan->color = psurf2->color;
|
|
// Make sure we don’t overflow
|
|
// the span array
|
|
if (pspan < &spans[MAX_SPANS])
|
|
pspan++;
|
|
}
|
|
psurf->visxstart = x;
|
|
// Add the edge to the stack
|
|
psurf->pnext = psurf2;
|
|
psurf2->pprev = psurf;
|
|
surfstack.pnext = psurf;
|
|
psurf->pprev = &surfstack;
|
|
} else {
|
|
// Not a new top; sort into the surface stack.
|
|
// Guaranteed to terminate due to sentinel
|
|
// background surface
|
|
do {
|
|
psurf2 = psurf2->pnext;
|
|
zinv2 = psurf2->zinv00 +
|
|
psurf2->zinvstepx * fx +
|
|
psurf2->zinvstepy * fy;
|
|
} while (zinv < zinv2);
|
|
// Insert the surface into the stack
|
|
psurf->pnext = psurf2;
|
|
psurf->pprev = psurf2->pprev;
|
|
psurf2->pprev->pnext = psurf;
|
|
psurf2->pprev = psurf;
|
|
}
|
|
}
|
|
} else {
|
|
// It’s a trailing edge; if this was the top surface,
|
|
// emit the span and remove it.
|
|
// First, make sure the edges didn’t cross
|
|
if (—psurf->state == 0) {
|
|
if (surfstack.pnext == psurf) {
|
|
// It’s on top, emit the span
|
|
x = ((pedge->x + 0×FFFF) >> 16);
|
|
pspan->count = x - psurf->visxstart;
|
|
if (pspan->count > 0) {
|
|
pspan->y = y;
|
|
pspan->x = psurf->visxstart;
|
|
pspan->color = psurf->color;
|
|
// Make sure we don’t overflow
|
|
// the span array
|
|
if (pspan < &spans[MAX_SPANS])
|
|
pspan++;
|
|
}
|
|
psurf->pnext->visxstart = x;
|
|
}
|
|
// Remove the surface from the stack
|
|
psurf->pnext->pprev = psurf->pprev;
|
|
psurf->pprev->pnext = psurf->pnext;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove edges that are done
|
|
pedge = removeedges[y];
|
|
while (pedge) {
|
|
pedge->pprev->pnext = pedge->pnext;
|
|
pedge->pnext->pprev = pedge->pprev;
|
|
pedge = pedge->pnextremove;
|
|
}
|
|
|
|
// Step the remaining edges one scan line, and re-sort
|
|
for (pedge=edgehead.pnext ; pedge != &edgetail ; ) {
|
|
ptemp = pedge->pnext;
|
|
// Step the edge
|
|
pedge->x += pedge->xstep;
|
|
// Move the edge back to the proper sorted location,
|
|
// if necessary
|
|
while (pedge->x < pedge->pprev->x) {
|
|
pedge2 = pedge->pprev;
|
|
pedge2->pnext = pedge->pnext;
|
|
pedge->pnext->pprev = pedge2;
|
|
pedge2->pprev->pnext = pedge;
|
|
pedge->pprev = pedge2->pprev;
|
|
pedge->pnext = pedge2;
|
|
pedge2->pprev = pedge;
|
|
}
|
|
pedge = ptemp;
|
|
}
|
|
}
|
|
pspan->x = -1; // mark the end of the list
|
|
}
|
|
|
|
// Draw all the spans that were scanned out.
|
|
void DrawSpans (void)
|
|
{
|
|
span_t *pspan;
|
|
for (pspan=spans ; pspan->x != -1 ; pspan++)
|
|
memset (pDIB + (DIBPitch * pspan->y) + pspan->x,
|
|
pspan->color,
|
|
pspan->count);
|
|
}
|
|
|
|
|
|
// Clear the lists of edges to add and remove on each scan line.
|
|
void ClearEdgeLists(void)
|
|
{
|
|
int i;
|
|
for (i=0 ; i<DIBHeight ; i++) {
|
|
newedges[i].pnext = &maxedge;
|
|
removeedges[i] = NULL;
|
|
}
|
|
}
|
|
|
|
// Render the current state of the world to the screen.
|
|
void UpdateWorld()
|
|
{
|
|
HPALETTE holdpal;
|
|
HDC hdcScreen, hdcDIBSection;
|
|
HBITMAP holdbitmap;
|
|
polygon2D_t screenpoly;
|
|
polygon_t *ppoly, tpoly0, tpoly1, tpoly2;
|
|
convexobject_t *pobject;
|
|
int i, j, k;
|
|
plane_t plane;
|
|
point_t tnormal;
|
|
|
|
UpdateViewPos();
|
|
SetUpFrustum();
|
|
ClearEdgeLists();
|
|
pavailsurf = surfs;
|
|
pavailedge = edges;
|
|
|
|
// Draw all visible faces in all objects
|
|
pobject = objecthead.pnext;
|
|
while (pobject != &objecthead) {
|
|
ppoly = pobject->ppoly;
|
|
for (i=0 ; i<pobject->numpolys ; i++) {
|
|
// Move the polygon relative to the object center
|
|
tpoly0.numverts = ppoly[i].numverts;
|
|
for (j=0 ; j<tpoly0.numverts ; j++) {
|
|
for (k=0 ; k<3 ; k++)
|
|
tpoly0.verts[j].v[k] = ppoly[i].verts[j].v[k] +
|
|
pobject->center.v[k];
|
|
}
|
|
if (PolyFacesViewer(&tpoly0, &ppoly[i].plane)) {
|
|
if (ClipToFrustum(&tpoly0, &tpoly1)) {
|
|
currentcolor = ppoly[i].color;
|
|
TransformPolygon (&tpoly1, &tpoly2);
|
|
ProjectPolygon (&tpoly2, &screenpoly);
|
|
|
|
// Move the polygon’s plane into viewspace
|
|
// First move it into worldspace (object relative)
|
|
tnormal = ppoly[i].plane.normal;
|
|
plane.distance = ppoly[i].plane.distance +
|
|
DotProduct (&pobject->center, &tnormal);
|
|
|
|
// Now transform it into viewspace
|
|
// Determine the distance from the viewpont
|
|
plane.distance -=
|
|
DotProduct (&currentpos, &tnormal);
|
|
|
|
// Rotate the normal into view orientation
|
|
plane.normal.v[0] =
|
|
DotProduct (&tnormal, &vright);
|
|
plane.normal.v[1] =
|
|
DotProduct (&tnormal, &vup);
|
|
plane.normal.v[2] =
|
|
DotProduct (&tnormal, &vpn);
|
|
AddPolygonEdges (&plane, &screenpoly);
|
|
}
|
|
}
|
|
}
|
|
pobject = pobject->pnext;
|
|
}
|
|
ScanEdges ();
|
|
DrawSpans ();
|
|
|
|
// We’ve drawn the frame; copy it to the screen
|
|
hdcScreen = GetDC(hwndOutput);
|
|
holdpal = SelectPalette(hdcScreen, hpalDIB, FALSE);
|
|
RealizePalette(hdcScreen);
|
|
hdcDIBSection = CreateCompatibleDC(hdcScreen);
|
|
holdbitmap = SelectObject(hdcDIBSection, hDIBSection);
|
|
BitBlt(hdcScreen, 0, 0, DIBWidth, DIBHeight, hdcDIBSection,
|
|
0, 0, SRCCOPY);
|
|
SelectPalette(hdcScreen, holdpal, FALSE);
|
|
ReleaseDC(hwndOutput, hdcScreen);
|
|
SelectObject(hdcDIBSection, holdbitmap);
|
|
DeleteDC(hdcDIBSection);
|
|
}
|
|
</PRE>
|
|
<!-- END CODE //-->
|
|
<P><BR></P>
|
|
<CENTER>
|
|
<TABLE BORDER>
|
|
<TR>
|
|
<TD><A HREF="67-02.html">Previous</A></TD>
|
|
<TD><A HREF="index.html">Table of Contents</A></TD>
|
|
<TD><A HREF="67-04.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 -->
|
|
|
|
|