151 lines
7.3 KiB
Markdown
151 lines
7.3 KiB
Markdown
Listing 39.2 shows a C implementation of integer edge tracing. Vertical
|
|
and diagonal lines, which are trivial to trace, are special-cased. Other
|
|
lines are broken into two categories: Y-major (closer to vertical) and
|
|
X-major (closer to horizontal). The handlers for the Y-major and X-major
|
|
cases operate on the principle of similar triangles: The number of X
|
|
pixels advanced per scan line is the same as the ratio of the X delta of
|
|
the edge to the Y delta. Listing 39.2 is more complex than the original
|
|
floating point implementation, but not painfully so. In return for that
|
|
complexity, Listing 39.2 is more than 80 times faster at scanning
|
|
edges—and, as just mentioned, it's actually more accurate than the
|
|
floating point code.
|
|
|
|
Ya gotta love that integer arithmetic.
|
|
|
|
**LISTING 39.2 L39-2.C**
|
|
|
|
/* Scan converts an edge from (X1,Y1) to (X2,Y2), not including the
|
|
point at (X2,Y2). If SkipFirst == 1, the point at (X1,Y1) isn't
|
|
drawn; if SkipFirst == 0, it is. For each scan line, the pixel
|
|
closest to the scanned edge without being to the left of the
|
|
scanned edge is chosen. Uses an all-integer approach for speed and
|
|
precision. */
|
|
|
|
#include <math.h>
|
|
#include "polygon.h"
|
|
|
|
void ScanEdge(int X1, int Y1, int X2, int Y2, int SetXStart,
|
|
int SkipFirst, struct HLine **EdgePointPtr)
|
|
{
|
|
int Y, DeltaX, Height, Width, AdvanceAmt, ErrorTerm, i;
|
|
int ErrorTermAdvance, XMajorAdvanceAmt;
|
|
struct HLine *WorkingEdgePointPtr;
|
|
|
|
WorkingEdgePointPtr = *EdgePointPtr; /* avoid double dereference */
|
|
AdvanceAmt = ((DeltaX = X2 - X1) > 0) ? 1 : -1;
|
|
/* direction in which X moves (Y2 is
|
|
always > Y1, so Y always counts up) */
|
|
|
|
if ((Height = Y2 - Y1) <= 0) /* Y length of the edge */
|
|
return; /* guard against 0-length and horizontal edges */
|
|
|
|
/* Figure out whether the edge is vertical, diagonal, X-major
|
|
(mostly horizontal), or Y-major (mostly vertical) and handle
|
|
appropriately */
|
|
if ((Width = abs(DeltaX)) == 0) {
|
|
/* The edge is vertical; special-case by just storing the same
|
|
X coordinate for every scan line */
|
|
/* Scan the edge for each scan line in turn */
|
|
for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) {
|
|
/* Store the X coordinate in the appropriate edge list */
|
|
if (SetXStart == 1)
|
|
WorkingEdgePointPtr->XStart = X1;
|
|
else
|
|
WorkingEdgePointPtr->XEnd = X1;
|
|
}
|
|
} else if (Width == Height) {
|
|
/* The edge is diagonal; special-case by advancing the X
|
|
coordinate 1 pixel for each scan line */
|
|
if (SkipFirst) /* skip the first point if so indicated */
|
|
X1 += AdvanceAmt; /* move 1 pixel to the left or right */
|
|
/* Scan the edge for each scan line in turn */
|
|
for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) {
|
|
/* Store the X coordinate in the appropriate edge list */
|
|
if (SetXStart == 1)
|
|
WorkingEdgePointPtr->XStart = X1;
|
|
else
|
|
WorkingEdgePointPtr->XEnd = X1;
|
|
X1 += AdvanceAmt; /* move 1 pixel to the left or right */
|
|
}
|
|
} else if (Height > Width) {
|
|
/* Edge is closer to vertical than horizontal (Y-major) */
|
|
if (DeltaX >= 0)
|
|
ErrorTerm = 0; /* initial error term going left->right */
|
|
else
|
|
ErrorTerm = -Height + 1; /* going right->left */
|
|
if (SkipFirst) { /* skip the first point if so indicated */
|
|
/* Determine whether it's time for the X coord to advance */
|
|
if ((ErrorTerm += Width) > 0) {
|
|
X1 += AdvanceAmt; /* move 1 pixel to the left or right */
|
|
ErrorTerm -= Height; /* advance ErrorTerm to next point */
|
|
}
|
|
}
|
|
/* Scan the edge for each scan line in turn */
|
|
for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) {
|
|
/* Store the X coordinate in the appropriate edge list */
|
|
if (SetXStart == 1)
|
|
WorkingEdgePointPtr->XStart = X1;
|
|
else
|
|
WorkingEdgePointPtr->XEnd = X1;
|
|
/* Determine whether it's time for the X coord to advance */
|
|
if ((ErrorTerm += Width) > 0) {
|
|
X1 += AdvanceAmt; /* move 1 pixel to the left or right */
|
|
ErrorTerm -= Height; /* advance ErrorTerm to correspond */
|
|
}
|
|
}
|
|
} else {
|
|
/* Edge is closer to horizontal than vertical (X-major) */
|
|
/* Minimum distance to advance X each time */
|
|
XMajorAdvanceAmt = (Width / Height) * AdvanceAmt;
|
|
/* Error term advance for deciding when to advance X 1 extra */
|
|
ErrorTermAdvance = Width % Height;
|
|
if (DeltaX >= 0)
|
|
ErrorTerm = 0; /* initial error term going left->right */
|
|
else
|
|
ErrorTerm = -Height + 1; /* going right->left */
|
|
if (SkipFirst) { /* skip the first point if so indicated */
|
|
X1 += XMajorAdvanceAmt; /* move X minimum distance */
|
|
/* Determine whether it's time for X to advance one extra */
|
|
if ((ErrorTerm += ErrorTermAdvance) > 0) {
|
|
X1 += AdvanceAmt; /* move X one more */
|
|
ErrorTerm -= Height; /* advance ErrorTerm to correspond */
|
|
}
|
|
}
|
|
/* Scan the edge for each scan line in turn */
|
|
for (i = Height - SkipFirst; i-- > 0; WorkingEdgePointPtr++) {
|
|
/* Store the X coordinate in the appropriate edge list */
|
|
if (SetXStart == 1)
|
|
WorkingEdgePointPtr->XStart = X1;
|
|
else
|
|
WorkingEdgePointPtr->XEnd = X1;
|
|
X1 += XMajorAdvanceAmt; /* move X minimum distance */
|
|
/* Determine whether it's time for X to advance one extra */
|
|
if ((ErrorTerm += ErrorTermAdvance) > 0) {
|
|
X1 += AdvanceAmt; /* move X one more */
|
|
ErrorTerm -= Height; /* advance ErrorTerm to correspond */
|
|
}
|
|
}
|
|
}
|
|
|
|
*EdgePointPtr = WorkingEdgePointPtr; /* advance caller's ptr */
|
|
}
|
|
|
|
### The Finishing Touch: Assembly Language {#Heading6}
|
|
|
|
The C implementation in Listing 39.2 is now nearly 20 times as fast as
|
|
the original, which is good enough for most purposes. Still, it requires
|
|
that one of the large data models be used (for **memset** ), and it's
|
|
certainly not the fastest possible code. The obvious next step is
|
|
assembly language.
|
|
|
|
Listing 39.3 is an assembly language version of
|
|
**DrawHorizontalLineList** . In actual use, it proved to be about 36
|
|
percent faster than Listing 39.1; better than a poke in the eye with a
|
|
sharp stick, but just barely. There's more to these timing results than
|
|
meets that eye, though. Display memory generally responds much more
|
|
slowly than system memory, especially in 386 and 486 systems. That means
|
|
that much of the time taken by Listing 39.3 is actually spent waiting
|
|
for display memory accesses to complete, with the processor forced to
|
|
idle by wait states. If, instead, Listing 39.3 drew to a local buffer in
|
|
system memory or to a particularly fast VGA, the assembly implementation
|
|
might well display a far more substantial advantage over the C code.
|