163 lines
7.9 KiB
Markdown
163 lines
7.9 KiB
Markdown
------------------------ --------------------------------- --------------------
|
||
[Previous](39-02.html) [Table of Contents](index.html) [Next](39-04.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
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.
|
||
|
||
------------------------ --------------------------------- --------------------
|
||
[Previous](39-02.html) [Table of Contents](index.html) [Next](39-04.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
* * * * *
|
||
|
||
Graphics Programming Black Book © 2001 Michael Abrash
|