477 lines
20 KiB
Markdown
477 lines
20 KiB
Markdown
------------------------ --------------------------------- --------------------
|
||
[Previous](67-02.html) [Table of Contents](index.html) [Next](67-04.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
#### Independent Span Sorting {#Heading7}
|
||
|
||
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.
|
||
|
||
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.
|
||
|
||
### 1/z Span Sorting in Action {#Heading8}
|
||
|
||
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.
|
||
|
||
**Listing 67.1 L67\_1.C**
|
||
|
||
// 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 = {0x7FFFFFFF};
|
||
|
||
// 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)0x10000);
|
||
pavailedge->x = (int)((screenpoly->verts[nextvert].x +
|
||
((float)topy - screenpoly->verts[nextvert].y) *
|
||
slope) * (float)0x10000);
|
||
} 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)0x10000);
|
||
pavailedge->x = (int)((screenpoly->verts[i].x +
|
||
((float)topy - screenpoly->verts[i].y) * slope) *
|
||
(float)0x10000);
|
||
}
|
||
|
||
// 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 = -0xFFFF; // 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)0x10000);
|
||
// 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 + 0xFFFF) >> 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 + 0xFFFF) >> 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 (¤tpos, &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);
|
||
}
|
||
|
||
------------------------ --------------------------------- --------------------
|
||
[Previous](67-02.html) [Table of Contents](index.html) [Next](67-04.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
* * * * *
|
||
|
||
Graphics Programming Black Book © 2001 Michael Abrash
|