Rename chapter files
This commit is contained in:
parent
ba650ed5b9
commit
498fdab210
72 changed files with 14 additions and 7 deletions
601
chapter-56.md
Normal file
601
chapter-56.md
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
---
|
||||
title: Michael Abrash's Graphics Programming Black Book, Special Edition
|
||||
author: Michael Abrash
|
||||
date: '1997-07-01'
|
||||
isbn: '1576101746'
|
||||
publisher: The Coriolis Group
|
||||
category: 'Web and Software Development: Game Development,Web and Software Development:
|
||||
Graphics and Multimedia Development'
|
||||
chapter: '56'
|
||||
pages: 1046-1059
|
||||
---
|
||||
|
||||
## Chapter 56\
|
||||
Pooh and the Space Station {#Heading1}
|
||||
|
||||
### Using Fast Texture Mapping to Place Pooh on a Polygon {#Heading2}
|
||||
|
||||
So, here's where Winnie the Pooh lives: in a space station orbiting
|
||||
Saturn. No, really; I have it straight from my daughter, and an
|
||||
eight-year-old wouldn't make up something that important, would she? One
|
||||
day she wondered aloud, "Where is the Hundred Acre Wood, exactly?" and
|
||||
before I could give one of those boring parental responses about how it
|
||||
was imaginary—but A.A. Milne probably imagined it to be somewhere near
|
||||
London—my daughter announced that the Hundred Acre Wood was in a space
|
||||
station orbiting Saturn, and there you have it.
|
||||
|
||||
As it turns out, that's a very good location for the Hundred Acre Wood,
|
||||
leading to many exciting adventures for Pooh and Piglet. Consider the
|
||||
time they went down to the Jupiter gravity level (we're talking
|
||||
centrifugal force here; the station is spinning, of course) and nearly
|
||||
turned into pancakes of the Pooh and Piglet varieties, respectively. Or
|
||||
the time they drifted out into the free-fall area at the core and had to
|
||||
be rescued by humans with wings strapped on (a tip of the hat to Robert
|
||||
Heinlein here). Or the time they were caught up by the current in the
|
||||
river through the Wood and drifted for weeks around the circumference of
|
||||
the station, meeting many cultures and finding many adventures along the
|
||||
way. (Yes, Farmer's Riverworld; no one said the stories you tell your
|
||||
children need to be purely original, just interesting.)
|
||||
|
||||
(If you think Pooh and Piglet in a space station is a tad peculiar, then
|
||||
I won't even mention Karla, the woman who invented agriculture,
|
||||
medicine, sanitation, reading and writing, peace, and just about
|
||||
everything else while travelling the length of the Americas with her
|
||||
mountain lion during the last Ice Age; or the Mars Cats and their trip
|
||||
in suspended animation to the Lesser Magellenic Cloud and beyond; or
|
||||
most assuredly Little Whale, the baby Universe Whale that is naughty
|
||||
enough to eat inhabited universes. But I digress.)
|
||||
|
||||
Anyway, I bring up Pooh and the space station because the time has come
|
||||
to discuss fast texture mapping. *Texture mapping* is the process of
|
||||
mapping an image (in our case, a bitmap) onto the surface of a polygon
|
||||
that's been transformed in the process of 3-D drawing. Up to this point,
|
||||
each polygon we've drawn in X-Sharp has been a single, solid color. Over
|
||||
the last couple of chapters we added the ability to shade polygons
|
||||
according to lighting, but each polygon was still a single color. Thus,
|
||||
in order to produce any sort of intricate design, a great many tiny
|
||||
polygons would have to be drawn. That would be very slow, so we need
|
||||
another approach. One such approach is texture mapping; that is, mapping
|
||||
the bitmap containing the desired image onto the pixels contained within
|
||||
the transformed polygon. Done properly, this should make it possible to
|
||||
change X-Sharp's output from a bland collection of monocolor facets to a
|
||||
lively, detailed, and much more realistic scene.
|
||||
|
||||
"What sort of scene?" you may well ask. This is where Pooh and the space
|
||||
station came in. When I sat down to think of a sample texture-mapping
|
||||
application, it occurred to me that the shaded ball demo we added to
|
||||
X-Sharp recently looked at least a bit like a spinning, spherical space
|
||||
station, and that the single unshaded, yellow polygon looked somewhat
|
||||
like a window in the space station, and it might be a nice example if
|
||||
someone were standing in the window....
|
||||
|
||||
The rest is history.
|
||||
|
||||
### Principles of Quick-and-Dirty Texture Mapping {#Heading3}
|
||||
|
||||
The key to our texture-mapping approach will be to quickly determine
|
||||
what pixel value to draw for each pixel in the transformed destination
|
||||
polygon. These polygon pixel values will be determined by mapping each
|
||||
destination pixel in the transformed polygon back to the image bitmap,
|
||||
via a reverse transformation, and seeing what color resides at the
|
||||
corresponding location in the image bitmap, as shown in Figure 56.1. It
|
||||
might seem more intuitive to map pixels the other way, from the image
|
||||
bitmap to the transformed polygon, but in fact it's crucial that the
|
||||
mapping proceed backward from the destination to avoid gaps in the final
|
||||
image. With the approach of finding the right value for each destination
|
||||
pixel in turn, via a backward mapping, there's no way we can miss any
|
||||
destination pixels. On the other hand, with the forward-mapping method,
|
||||
some destination pixels may be skipped or double-drawn, because this is
|
||||
not necessarily a one-to-one or one-to-many mapping. Although we're not
|
||||
going to take advantage of it now, mapping back to the source makes it
|
||||
possible to average several neighboring image pixels together to
|
||||
calculate the value for each destination pixel; that is, to antialias
|
||||
the image. This can greatly improve texture quality, although it is
|
||||
slower.
|
||||
|
||||

|
||||
|
||||
#### Mapping Textures Made Easy {#Heading4}
|
||||
|
||||
To understand how we're going to map textures, consider Figure 56.2,
|
||||
which maps a bitmapped image directly onto an untransformed polygon.
|
||||
Here, we simply map the origin of the polygon's untransformed coordinate
|
||||
system somewhere within the image, then map the vertices to the
|
||||
corresponding image pixels. (For simplicity, I'll assume in this
|
||||
discussion that the polygon's coordinate system is in units of pixels,
|
||||
but scaling images to polygons is eminently doable. This will become
|
||||
clearer when we look at mapping images onto transformed polygons, next.)
|
||||
Mapping the image to the polygon is then a simple matter of stepping one
|
||||
scan line at a time in both the image and the polygon, each time
|
||||
advancing the X coordinates of the edges according to the slopes of the
|
||||
lines, just as is normally done when filling a polygon. Since the
|
||||
polygon is untransformed, the stepping is identical in both the image
|
||||
and the polygon, and the pixel mapping is one-to-one, so the appropriate
|
||||
part of each scan line of the image can simply be block copied to the
|
||||
destination.
|
||||
|
||||

|
||||
|
||||
Now, matters get more complicated. What if the destination polygon is
|
||||
rotated in two dimensions? We no longer have a neat direct mapping from
|
||||
image scan lines to destination polygon scan lines. We still want to
|
||||
draw across each destination scan line, but the proper source pixels for
|
||||
each destination scan line may now track across the source bitmap at an
|
||||
angle, as shown in Figure 56.3. What can we do?
|
||||
|
||||
The solution is remarkably simple. We'll just map each transformed
|
||||
vertex to the corresponding vertex in the bitmap; this is easy, because
|
||||
the vertices are at the same indices in the original and transformed
|
||||
vertex lists. Each time we select a new edge to scan for the destination
|
||||
polygon, we'll select the corresponding edge in the source bitmap, as
|
||||
well. Then—and this is crucial—each time we step a destination edge one
|
||||
scan line, we'll step the corresponding source image edge an equivalent
|
||||
amount.
|
||||
|
||||
Ah, but what is an "equivalent amount"? Think of it this way. If a
|
||||
destination edge is 100 scan lines high, it will be stepped 100 times.
|
||||
Then, we'll divide the `SourceXWidth` and `SourceYHeight` lengths of
|
||||
the source edge by 100, and add those amounts to the source edge's
|
||||
coordinates each time the destination is stepped one scan line. Put
|
||||
another way, we have, as usual, arranged things so that in the
|
||||
destination polygon we step `DestYHeight` times, where `DestYHeight`
|
||||
is the height of the destination edge. The this approach arranges to
|
||||
step the source image edge `DestYHeight` times also, to match what the
|
||||
destination is doing.
|
||||
|
||||

|
||||
|
||||
Now we're able to track the coordinates of the polygon edges through the
|
||||
source image in tandem with the destination edges. Stepping across each
|
||||
destination scan line uses precisely the same technique, as shown in
|
||||
Figure 56.4. In the destination, we step `DestXWidth` times across
|
||||
each scan line of the polygon, once for each pixel on the scan line.
|
||||
(`DestXWidth` is the horizontal distance between the two edges being
|
||||
scanned on any given scan line.) To match this, we divide
|
||||
`SourceXWidth` and `SourceYHeight` (the lengths of the scan line in
|
||||
the source image, as determined by the source edge points we've been
|
||||
tracking, as just described) by the width of the destination scan line,
|
||||
`DestXWidth`, to produce `SourceXStep` and `SourceYStep`. Then, we
|
||||
just step `DestXWidth` times, adding `SourceXStep` and
|
||||
`SourceYStep` to `SourceX` and `SourceY` each time, and choose the
|
||||
nearest image pixel to (`SourceX`,`SourceY`) to copy to (`DestX`,
|
||||
`DestY`). (Note that the names used above, such as `SourceXWidth`,
|
||||
are used for descriptive purposes, and don't necessarily correspond to
|
||||
the actual variable names used in Listing 56.2.)
|
||||
|
||||
That's a workable approach for 2-D rotated polygons—but what about 3-D
|
||||
rotated polygons, where the visible dimensions of the polygon can vary
|
||||
with 3-D rotation and perspective projection? First, I'd like to make it
|
||||
clear that texture mapping takes place from the source image to the
|
||||
destination polygon after the destination polygon is projected to the
|
||||
screen. That is, the image will be mapped after the destination polygon
|
||||
is in its final, drawable form. Given that, it should be apparent that
|
||||
the above approach automatically compensates for all changes in the
|
||||
dimensions of a polygon. You see, this approach divides source edges and
|
||||
scan lines into however many steps the destination polygon requires. If
|
||||
the destination polygon is much narrower than the source polygon, as a
|
||||
result of 3-D rotation and perspective projection, we just end up taking
|
||||
bigger steps through the source image and skipping a lot of source image
|
||||
pixels, as shown in Figure 56.5. The upshot is that the above approach
|
||||
handles all transformations and projections effortlessly. It could also
|
||||
be used to scale source images up to fit in larger polygons; all that's
|
||||
needed is a list of where the polygon's vertices map into the source
|
||||
image, and everything else happens automatically. In fact, mapping from
|
||||
any polygonal area of a bitmap to any destination polygon will work,
|
||||
given only that the two polygons have the same number of vertices.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### Notes on DDA Texture Mapping {#Heading5}
|
||||
|
||||
That's all there is to quick-and-dirty texture mapping. This technique
|
||||
basically uses a two-stage digital differential analyzer (DDA) approach
|
||||
to step through the appropriate part of the source image in tandem with
|
||||
the normal scan-line stepping through the destination polygon, so I'll
|
||||
call it "DDA texture mapping." It's worth noting that there is no need
|
||||
for any trigonometric functions at all, and only two divides are
|
||||
required per scan line.
|
||||
|
||||
This isn't a perfect approach, of course. For one thing, it isn't
|
||||
anywhere near as fast as drawing solid polygons; the speed is more
|
||||
comparable to drawing each polygon as a series of lines. Also, the DDA
|
||||
approach results in far from perfect image quality, since source pixels
|
||||
may be skipped or selected twice. I trust, however, that you can see how
|
||||
easy it would be to improve image quality by antialiasing with the DDA
|
||||
approach. For example, we could simply average the four surrounding
|
||||
pixels as we did for simple, unweighted antialiasing in Chapters F,
|
||||
G,Chapter K on the companion CD-ROM. Or, we could take a Wu antialiasing
|
||||
approach (see Chapter 57) and average the two bracketing pixels along
|
||||
each axis according to proximity. If we had cycles to waste (which,
|
||||
given that this is real-time animation on a PC, we don't), we could
|
||||
improve image quality by putting the source pixels through a low-pass
|
||||
filter sized in X and Y according to the ratio of the source and
|
||||
destination dimensions (that is, how much the destination is scaled up
|
||||
or down from the source).
|
||||
|
||||
Even more important is that the sort of texture mapping I'll do in
|
||||
X-Sharp doesn't correct for perspective. That doesn't much matter for
|
||||
small polygons or polygons that are nearly parallel to the screen in
|
||||
3-space, but it can produce very noticeable bowing of textures on large
|
||||
polygons at an angle to the screen. Perspective texture mapping is a
|
||||
complex subject that's outside the scope of this book, but you should be
|
||||
aware of its existence, because perspective texture mapping is a key
|
||||
element of many games these days.
|
||||
|
||||
Finally, I'd like to point out that this sort of DDA texture mapping is
|
||||
display-hardware dependent, because the bitmap for each image must be
|
||||
compatible with the number of bits per pixel in the destination. That's
|
||||
actually a fairly serious issue. One of the nice things about X-Sharp's
|
||||
polygon orientation is that, until now, the only display dependent part
|
||||
of X-Sharp has been the transformation from RGB color space to the
|
||||
adapter's color space. Compensation for aspect ratio, resolution, and
|
||||
the like all happens automatically in the course of projection. Still,
|
||||
we need the ability to display detailed surfaces, and it's hard to
|
||||
conceive of a fast way to do so that's totally hardware independent. (If
|
||||
you know of one, let me know care of the publisher.)
|
||||
|
||||
For now, all we need is fast texture mapping of adequate quality, which
|
||||
the straightforward, non-antialiased DDA approach supplies. I'm sure
|
||||
there are many other fast approaches, and, as I've said, there are more
|
||||
accurate approaches, but DDA texture mapping works well, given the
|
||||
constraints of the PC's horsepower. Next, we'll look at code that
|
||||
performs DDA texture mapping. First, though, I'd like to take a moment
|
||||
to thank Jim Kent, author of Autodesk Animator and a frequent
|
||||
correspondent, for getting me started with the DDA approach.
|
||||
|
||||
### Fast Texture Mapping: An Implementation {#Heading6}
|
||||
|
||||
As you might expect, I've implemented DDA texture mapping in X-Sharp,
|
||||
and the changes are reflected in the X-Sharp archive in this chapter's
|
||||
subdirectory on the listings disk. Listing 56.1 shows the new header
|
||||
file entries, and Listing 56.2 shows the actual texture-mapped polygon
|
||||
drawer. The set-pixel routine that Listing 56.2 calls is a slight
|
||||
modification of the Mode X set-pixel routine from Chapter 47. In
|
||||
addition, INITBALL.C has been modified to create three texture-mapped
|
||||
polygons and define the texture bitmaps, and modifications have been
|
||||
made to allow the user to flip the axis of rotation. You will of course
|
||||
need the complete X-Sharp library to see texture mapping in action, but
|
||||
Listings 56.1 and 56.2 are the actual texture mapping code in its
|
||||
entirety.
|
||||
|
||||
> 
|
||||
> Here's a major tip: DDA texture mapping looks best on fast-moving
|
||||
> surfaces, where the eye doesn't have time to pick nits with the shearing
|
||||
> and aliasing that's an inevi table by-product of such a crude approach.
|
||||
> Compile DEMO1 from the X-Sharp archive in this chapter's subdirectory of
|
||||
> the listings disk, and run it. The initial display looks okay, but
|
||||
> certainly not great, because the rotational speed is so slow. Now press
|
||||
> the S key a few times to speed up the rotation and flip between
|
||||
> different rotation axes. I think you'll be amazed at how much better DDA
|
||||
> texture mapping looks at high speed. This technique would be great for
|
||||
> mapping textures onto hurtling asteroids or jets, but would come up
|
||||
> short for slow, finely detailed movements.
|
||||
|
||||
**LISTING 56.1 L56-1.C**
|
||||
|
||||
```c
|
||||
/* New header file entries related to texture-mapped polygons */
|
||||
|
||||
/* Draws the polygon described by the point list PointList with a bitmap
|
||||
texture mapped onto it */
|
||||
#define DRAW_TEXTURED_POLYGON(PointList,NumPoints,TexVerts,TexMap) \
|
||||
Polygon.Length = NumPoints; Polygon.PointPtr = PointList; \
|
||||
DrawTexturedPolygon(&Polygon, TexVerts, TexMap);
|
||||
#define FIXED_TO_INT(FixedVal) ((int) (FixedVal >> 16))
|
||||
#define ROUND_FIXED_TO_INT(FixedVal) \
|
||||
((int) ((FixedVal + DOUBLE_TO_FIXED(0.5)) >> 16))
|
||||
/* Retrieves specified pixel from specified image bitmap of specified width. */
|
||||
#define GET_IMAGE_PIXEL(TexMapBits, TexMapWidth, X, Y) \
|
||||
TexMapBits[(Y * TexMapWidth) + X]
|
||||
/* Masks to mark shading types in Face structure */
|
||||
#define NO_SHADING 0x0000
|
||||
#define AMBIENT_SHADING 0x0001
|
||||
#define DIFFUSE_SHADING 0x0002
|
||||
#define TEXTURE_MAPPED_SHADING 0x0004
|
||||
/* Describes a texture map */
|
||||
typedef struct {
|
||||
int TexMapWidth; /* texture map width in bytes */
|
||||
char *TexMapBits; /* pointer to texture bitmap */
|
||||
} TextureMap;
|
||||
|
||||
/* Structure describing one face of an object (one polygon) */
|
||||
typedef struct {
|
||||
int * VertNums; /* pointer to list of indexes of this polygon's vertices
|
||||
in the object's vertex list. The first two indexes
|
||||
must select end and start points, respectively, of this
|
||||
polygon's unit normal vector. Second point should also
|
||||
be an active polygon vertex */
|
||||
int NumVerts; /* # of verts in face, not including the initial
|
||||
vertex, which must be the end of a unit normal vector
|
||||
that starts at the second index in VertNums */
|
||||
int ColorIndex; /* direct palette index; used only for non-shaded faces */
|
||||
ModelColor FullColor; /* polygon's color */
|
||||
int ShadingType; /* none, ambient, diffuse, texture mapped, etc. */
|
||||
TextureMap * TexMap; /* pointer to bitmap for texture mapping, if any */
|
||||
Point * TexVerts; /* pointer to list of this polygon's vertices, in
|
||||
TextureMap coordinates. Index n must map to index
|
||||
n + 1 in VertNums, (the + 1 is to skip over the unit
|
||||
normal endpoint in VertNums) */
|
||||
} Face;
|
||||
extern void DrawTexturedPolygon(PointListHeader *, Point *, TextureMap *);
|
||||
```
|
||||
|
||||
**LISTING 56.2 L56-2.C**
|
||||
|
||||
```c
|
||||
/* Draws a bitmap, mapped to a convex polygon (draws a texture-mapped polygon).
|
||||
"Convex" means that every horizontal line drawn through the polygon at any
|
||||
point would cross exactly two active edges (neither horizontal lines nor
|
||||
zero-length edges count as active edges; both are acceptable anywhere in
|
||||
the polygon), and that the right & left edges never cross. Nonconvex
|
||||
polygons won't be drawn properly. Can't fail. */
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include "polygon.h"
|
||||
/* Describes the current location and stepping, in both the source and
|
||||
the destination, of an edge */
|
||||
typedef struct {
|
||||
int Direction; /* through edge list; 1 for a right edge (forward
|
||||
through vertex list), -1 for a left edge (backward
|
||||
through vertex list) */
|
||||
int RemainingScans; /* height left to scan out in dest */
|
||||
int CurrentEnd; /* vertex # of end of current edge */
|
||||
Fixedpoint SourceX; /* current X location in source for this edge */
|
||||
Fixedpoint SourceY; /* current Y location in source for this edge */
|
||||
Fixedpoint SourceStepX;/* X step in source for Y step in dest of 1 */
|
||||
Fixedpoint SourceStepY;/* Y step in source for Y step in dest of 1 */
|
||||
/* variables used for all-integer Bresenham's-type
|
||||
X stepping through the dest, needed for precise
|
||||
pixel placement to avoid gaps */
|
||||
int DestX; /* current X location in dest for this edge */
|
||||
int DestXIntStep; /* whole part of dest X step per scan-line Y step */
|
||||
int DestXDirection; /* -1 or 1 to indicate way X steps (left/right) */
|
||||
int DestXErrTerm; /* current error term for dest X stepping */
|
||||
int DestXAdjUp; /* amount to add to error term per scan line move */
|
||||
int DestXAdjDown; /* amount to subtract from error term when the
|
||||
error term turns over */
|
||||
} EdgeScan;
|
||||
int StepEdge(EdgeScan *);
|
||||
int SetUpEdge(EdgeScan *, int);
|
||||
void ScanOutLine(EdgeScan *, EdgeScan *);
|
||||
int GetImagePixel(char *, int, int, int);
|
||||
/* Statics to save time that would otherwise pass them to subroutines. */
|
||||
static int MaxVert, NumVerts, DestY;
|
||||
static Point * VertexPtr;
|
||||
static Point * TexVertsPtr;
|
||||
static char * TexMapBits;
|
||||
static int TexMapWidth;
|
||||
/* Draws a texture-mapped polygon, given a list of destination polygon
|
||||
vertices, a list of corresponding source texture polygon vertices, and a
|
||||
pointer to the source texture's descriptor. */
|
||||
void DrawTexturedPolygon(PointListHeader * Polygon, Point * TexVerts,
|
||||
TextureMap * TexMap)
|
||||
{
|
||||
int MinY, MaxY, MinVert, i;
|
||||
EdgeScan LeftEdge, RightEdge;
|
||||
NumVerts = Polygon->Length;
|
||||
VertexPtr = Polygon->PointPtr;
|
||||
TexVertsPtr = TexVerts;
|
||||
TexMapBits = TexMap->TexMapBits;
|
||||
TexMapWidth = TexMap->TexMapWidth;
|
||||
/* Nothing to draw if less than 3 vertices */
|
||||
if (NumVerts < 3) {
|
||||
return;
|
||||
}
|
||||
/* Scan through the destination polygon vertices and find the top of the
|
||||
left and right edges, taking advantage of our knowledge that vertices run
|
||||
in a clockwise direction (else this polygon wouldn't be visible due to
|
||||
backface removal) */
|
||||
MinY = 32767;
|
||||
MaxY = -32768;
|
||||
for (i=0; i<NumVerts; i++) {
|
||||
if (VertexPtr[i].Y < MinY) {
|
||||
MinY = VertexPtr[i].Y;
|
||||
MinVert = i;
|
||||
}
|
||||
if (VertexPtr[i].Y > MaxY) {
|
||||
MaxY = VertexPtr[i].Y;
|
||||
MaxVert = i;
|
||||
}
|
||||
}
|
||||
/* Reject flat (0-pixel-high) polygons */
|
||||
if (MinY >= MaxY) {
|
||||
return;
|
||||
}
|
||||
/* The destination Y coordinate is not edge specific; it applies to
|
||||
both edges, since we always step Y by 1 */
|
||||
DestY = MinY;
|
||||
/* Set up to scan the initial left and right edges of the source and
|
||||
destination polygons. We always step the destination polygon edges
|
||||
by one in Y, so calculate the corresponding destination X step for
|
||||
each edge, and then the corresponding source image X and Y steps */
|
||||
LeftEdge.Direction = -1; /* set up left edge first */
|
||||
SetUpEdge(&LeftEdge, MinVert);
|
||||
RightEdge.Direction = 1; /* set up right edge */
|
||||
SetUpEdge(&RightEdge, MinVert);
|
||||
/* Step down destination edges one scan line at a time. At each scan
|
||||
line, find the corresponding edge points in the source image. Scan
|
||||
between the edge points in the source, drawing the corresponding
|
||||
pixels across the current scan line in the destination polygon. (We
|
||||
know which way the left and right edges run through the vertex list
|
||||
because visible (non-backface-culled) polygons always have the vertices
|
||||
in clockwise order as seen from the viewpoint) */
|
||||
for (;;) {
|
||||
/* Done if off bottom of clip rectangle */
|
||||
if (DestY >= ClipMaxY) {
|
||||
return;
|
||||
}
|
||||
/* Draw only if inside Y bounds of clip rectangle */
|
||||
if (DestY >= ClipMinY) {
|
||||
/* Draw the scan line between the two current edges */
|
||||
ScanOutLine(&LeftEdge, &RightEdge);
|
||||
}
|
||||
/* Advance the source and destination polygon edges, ending if we've
|
||||
scanned all the way to the bottom of the polygon */
|
||||
if (!StepEdge(&LeftEdge)) {
|
||||
break;
|
||||
}
|
||||
if (!StepEdge(&RightEdge)) {
|
||||
break;
|
||||
}
|
||||
DestY++;
|
||||
}
|
||||
}
|
||||
/* Steps an edge one scan line in the destination, and the corresponding
|
||||
distance in the source. If an edge runs out, starts a new edge if there
|
||||
is one. Returns 1 for success, or 0 if there are no more edges to scan. */
|
||||
int StepEdge(EdgeScan * Edge)
|
||||
{
|
||||
/* Count off the scan line we stepped last time; if this edge is
|
||||
finished, try to start another one */
|
||||
if (--Edge->RemainingScans == 0) {
|
||||
/* Set up the next edge; done if there is no next edge */
|
||||
if (SetUpEdge(Edge, Edge->CurrentEnd) == 0) {
|
||||
return(0); /* no more edges; done drawing polygon */
|
||||
}
|
||||
return(1); /* all set to draw the new edge */
|
||||
}
|
||||
/* Step the current source edge */
|
||||
Edge->SourceX += Edge->SourceStepX;
|
||||
Edge->SourceY += Edge->SourceStepY;
|
||||
/* Step dest X with Bresenham-style variables, to get precise dest pixel
|
||||
placement and avoid gaps */
|
||||
Edge->DestX += Edge->DestXIntStep; /* whole pixel step */
|
||||
/* Do error term stuff for fractional pixel X step handling */
|
||||
if ((Edge->DestXErrTerm += Edge->DestXAdjUp) > 0) {
|
||||
Edge->DestX += Edge->DestXDirection;
|
||||
Edge->DestXErrTerm -= Edge->DestXAdjDown;
|
||||
}
|
||||
return(1);
|
||||
}
|
||||
/* Sets up an edge to be scanned; the edge starts at StartVert and proceeds
|
||||
in direction Edge->Direction through the vertex list. Edge->Direction must
|
||||
be set prior to call; -1 to scan a left edge (backward through the vertex
|
||||
list), 1 to scan a right edge (forward through the vertex list).
|
||||
Automatically skips over 0-height edges. Returns 1 for success, or 0 if
|
||||
there are no more edges to scan. */
|
||||
int SetUpEdge(EdgeScan * Edge, int StartVert)
|
||||
{
|
||||
int NextVert, DestXWidth;
|
||||
Fixedpoint DestYHeight;
|
||||
for (;;) {
|
||||
/* Done if this edge starts at the bottom vertex */
|
||||
if (StartVert == MaxVert) {
|
||||
return(0);
|
||||
}
|
||||
/* Advance to the next vertex, wrapping if we run off the start or end
|
||||
of the vertex list */
|
||||
NextVert = StartVert + Edge->Direction;
|
||||
if (NextVert >= NumVerts) {
|
||||
NextVert = 0;
|
||||
} else if (NextVert < 0) {
|
||||
NextVert = NumVerts - 1;
|
||||
}
|
||||
/* Calculate the variables for this edge and done if this is not a
|
||||
zero-height edge */
|
||||
if ((Edge->RemainingScans =
|
||||
VertexPtr[NextVert].Y - VertexPtr[StartVert].Y) != 0) {
|
||||
DestYHeight = INT_TO_FIXED(Edge->RemainingScans);
|
||||
Edge->CurrentEnd = NextVert;
|
||||
Edge->SourceX = INT_TO_FIXED(TexVertsPtr[StartVert].X);
|
||||
Edge->SourceY = INT_TO_FIXED(TexVertsPtr[StartVert].Y);
|
||||
Edge->SourceStepX = FixedDiv(INT_TO_FIXED(TexVertsPtr[NextVert].X) -
|
||||
Edge->SourceX, DestYHeight);
|
||||
Edge->SourceStepY = FixedDiv(INT_TO_FIXED(TexVertsPtr[NextVert].Y) -
|
||||
Edge->SourceY, DestYHeight);
|
||||
/* Set up Bresenham-style variables for dest X stepping */
|
||||
Edge->DestX = VertexPtr[StartVert].X;
|
||||
if ((DestXWidth =
|
||||
(VertexPtr[NextVert].X - VertexPtr[StartVert].X)) < 0) {
|
||||
/* Set up for drawing right to left */
|
||||
Edge->DestXDirection = -1;
|
||||
DestXWidth = -DestXWidth;
|
||||
Edge->DestXErrTerm = 1 - Edge->RemainingScans;
|
||||
Edge->DestXIntStep = -(DestXWidth / Edge->RemainingScans);
|
||||
} else {
|
||||
/* Set up for drawing left to right */
|
||||
Edge->DestXDirection = 1;
|
||||
Edge->DestXErrTerm = 0;
|
||||
Edge->DestXIntStep = DestXWidth / Edge->RemainingScans;
|
||||
}
|
||||
Edge->DestXAdjUp = DestXWidth % Edge->RemainingScans;
|
||||
Edge->DestXAdjDown = Edge->RemainingScans;
|
||||
return(1); /* success */
|
||||
}
|
||||
StartVert = NextVert; /* keep looking for a non-0-height edge */
|
||||
}
|
||||
}
|
||||
/* Texture-map-draw the scan line between two edges. */
|
||||
void ScanOutLine(EdgeScan * LeftEdge, EdgeScan * RightEdge)
|
||||
{
|
||||
Fixedpoint SourceX = LeftEdge->SourceX;
|
||||
Fixedpoint SourceY = LeftEdge->SourceY;
|
||||
int DestX = LeftEdge->DestX;
|
||||
int DestXMax = RightEdge->DestX;
|
||||
Fixedpoint DestWidth;
|
||||
Fixedpoint SourceXStep, SourceYStep;
|
||||
/* Nothing to do if fully X clipped */
|
||||
if ((DestXMax <= ClipMinX) || (DestX >= ClipMaxX)) {
|
||||
return;
|
||||
}
|
||||
if ((DestXMax - DestX) <= 0) {
|
||||
return; /* nothing to draw */
|
||||
}
|
||||
/* Width of destination scan line, for scaling. Note: because this is an
|
||||
integer-based scaling, it can have a total error of as much as nearly
|
||||
one pixel. For more precise scaling, also maintain a fixed-point DestX
|
||||
in each edge, and use it for scaling. If this is done, it will also
|
||||
be necessary to nudge the source start coordinates to the right by an
|
||||
amount corresponding to the distance from the the real (fixed-point)
|
||||
DestX and the first pixel (at an integer X) to be drawn) */
|
||||
DestWidth = INT_TO_FIXED(DestXMax - DestX);
|
||||
/* Calculate source steps that correspond to each dest X step (across
|
||||
the scan line) */
|
||||
SourceXStep = FixedDiv(RightEdge->SourceX - SourceX, DestWidth);
|
||||
SourceYStep = FixedDiv(RightEdge->SourceY - SourceY, DestWidth);
|
||||
/* Clip right edge if necessary */
|
||||
if (DestXMax > ClipMaxX) {
|
||||
DestXMax = ClipMaxX;
|
||||
}
|
||||
/* Clip left edge if necssary */
|
||||
if (DestX < ClipMinX) {
|
||||
SourceX += SourceXStep * (ClipMinX - DestX);
|
||||
SourceY += SourceYStep * (ClipMinX - DestX);
|
||||
DestX = ClipMinX;
|
||||
}
|
||||
/* Scan across the destination scan line, updating the source image
|
||||
position accordingly */
|
||||
for (; DestX<DestXMax; DestX++) {
|
||||
/* Get currently mapped pixel out of image and draw it to screen */
|
||||
WritePixelX(DestX, DestY,
|
||||
GET_IMAGE_PIXEL(TexMapBits, TexMapWidth,
|
||||
FIXED_TO_INT(SourceX), FIXED_TO_INT(SourceY)) );
|
||||
/* Point to the next source pixel */
|
||||
SourceX += SourceXStep;
|
||||
SourceY += SourceYStep;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
No matter how you slice it, DDA texture mapping beats boring,
|
||||
single-color polygons nine ways to Sunday. The big downside is that it's
|
||||
much slower than a normal polygon fill; move the ball close to the
|
||||
screen in DEMO1, and watch things slow down when one of those big
|
||||
texture maps comes around. Of course, that's partly because the code is
|
||||
all in C; some well-chosen optimizations would work wonders. In the next
|
||||
chapter we'll discuss texture mapping further, crank up the speed of our
|
||||
texture mapper, and attend to some rough spots that remain in the DDA
|
||||
texture mapping implementation, most notably in the area of exactly
|
||||
which texture pixels map to which destination pixels as a polygon
|
||||
rotates.
|
||||
|
||||
And, in case you're curious, yes, there is a bear in DEMO1. I wouldn't
|
||||
say he looks much like a Pooh-type bear, but he's a bear nonetheless. He
|
||||
does tend to look a little startled when you flip the ball around so
|
||||
that he's zipping by on his head, but, heck, you would too in the same
|
||||
situation. And remember, when you buy the next VGA megahit, *Bears in
|
||||
Space*, you saw it here first.
|
||||
Loading…
Reference in a new issue