abrash-black-book/46-03.md
2013-12-30 20:26:41 +11:00

130 lines
7.5 KiB
Markdown

#### Masked Images {#Heading4}
Masked images are rendered by drawing an object's pixels through a mask;
pixels are actually drawn only where the mask specifies that drawing is
allowed. This makes it possible to draw nonrectangular objects that
don't improperly interfere with one another when they overlap. Masked
images also make it possible to have transparent areas (windows) within
objects. Masked images produce far more realistic animation than do
rectangular images, and therefore are more desirable. Unfortunately,
masked images are also considerably slower to draw—however, a good
assembly language implementation can go a long way toward making masked
images draw rapidly enough, as illustrated by this chapter's code.
(Masked images are also known as *sprites*; some video hardware supports
sprites directly, but on the PC it's necessary to handle sprites in
software.)
Masked images make it possible to render scenes so that a given image
convincingly appears to be in front of or behind other images; that is,
so images are displayed in *z-order* (by distance). By consistently
drawing images that are supposed to be farther away before drawing
nearer images, the nearer images will appear in front of the other
images, and because masked images draw only precisely the correct pixels
(as opposed to blank pixels in the bounding rectangle), there's no
interference between overlapping images to destroy the illusion.
In this chapter, I've used the approach of having separate, paired masks
and images. Another, quite different approach to masking is to specify a
transparent color for copying, and copy only those pixels that are not
the transparent color. This has the advantage of not requiring separate
mask data, so it's more compact, and the code to implement this is a
little less complex than the full masking I've implemented. On the other
hand, the transparent color approach is less flexible because it makes
one color undrawable. Also, with a transparent color, it's not possible
to keep the same base image but use different masks, because the mask
information is embedded in the image data.
#### Internal Animation {#Heading5}
I've added another feature essential to producing convincing animation:
*internal animation*, which is the process of changing the appearance of
a given object over time, as distinguished from changing only the
*location* of a given object. Internal animation makes images look
active and alive. I've implemented the simplest possible form of
internal animation in Listing 46.1—alternation between two images—but
even this level of internal animation greatly improves the feel of the
overall animation. You could easily increase the number of images cycled
through, simply by increasing the value of **InternalAnimateMax** for a
given entity. You could also implement more complex image-selection
logic to produce more interesting and less predictable
internal-animation effects, such as jumping, ducking, running, and the
like.
#### Dirty-Rectangle Management {#Heading6 align="center"}
As mentioned above, dirty-rectangle animation makes it possible to
access display memory a minimum number of times. The previous chapter's
code didn't do any of that; instead, it copied all portions of every
dirty rectangle to the screen, regardless of overlap between rectangles.
The code I've presented in this chapter goes to the other extreme,
taking great pains never to draw overlapped portions of rectangles more
than once. This is accomplished by checking for overlap whenever a
rectangle is to be added to the dirty list. When overlap with an
existing rectangle is detected, the new rectangle is reduced to between
zero and four nonoverlapping rectangles. Those rectangles are then again
considered for addition to the dirty list, and may again be reduced, if
additional overlap is detected.
A good deal of code is required to generate a fully nonoverlapped dirty
list. Is it worth it? It certainly can be, but in the case of Listing
46.1, probably not. For one thing, you'd need larger, heavily overlapped
objects for this approach to pay off big. Besides, this program is
mostly in C, and spends a lot of time doing things other than actually
accessing display memory. It also takes a fair amount of time just to
generate the nonoverlapped list; the overhead of all the looping,
intersecting, and calling required to generate the list eats up a lot of
the benefits of accessing display memory less often. Nonetheless, fully
nonoverlapped drawing can be useful under the right circumstances, and
I've implemented it in Listing 46.1 so you'll have something to refer to
should you decide to go this route.
There are a couple of additional techniques you might try if you want to
wring maximum performance out of dirty-rectangle animation. You could
try coalescing rectangles as you generate the dirty-rectangle list. That
is, you could detect pairs of rectangles that can be joined together
into larger rectangles, so that fewer, larger rectangles would have to
be copied. This would boost the efficiency of the low-level copying
code, albeit at the cost of some cycles in the dirty-list management
code.
You might also try taking advantage of the natural coherence of animated
graphics screens. In particular, because the rectangle used to erase an
image at its old location often overlaps the rectangle within which the
image resides at its new location, you could just directly generate the
two or three nonoverlapped rectangles required to copy both the erase
rectangle and the new-image rectangle for any single moving image. The
calculation of these rectangles could be very efficient, given that you
know in advance the direction of motion of your images. Handling this
particular overlap case would eliminate most overlapped drawing, at a
minimal cost. You might then decide to ignore overlapped drawing between
different images, which tends to be both less common and more expensive
to identify and handle.
#### Drawing Order and Visual Quality {#Heading7}
A final note on dirty-rectangle animation concerns the quality of the
displayed screen image. In the last chapter, we simply stuffed dirty
rectangles into a list in the order they became dirty, and then copied
all of the rectangles in that same order. Unfortunately, this caused all
of the erase rectangles to be copied first, followed by all of the
rectangles of the images at their new locations. Consequently, there was
a significant delay between the appearance of the erase rectangle for a
given image and the appearance of the new rectangle. A byproduct was the
fact that a partially complete—part old, part new—image was visible long
enough to be noticed. In short, although the pixels ended up correct,
they were in an intermediate, incorrect state for a sufficient period of
time to make the animation look wrong.
This violated a fundamental rule of animation: *No pixel should ever be
displayed in a perceptibly incorrect state*. To correct the problem,
I've sorted the dirty rectangles first by Y coordinate, and secondly by
X coordinate. This means the screen updates from to draw a given image
should be drawn nearly simultaneously. Run the code from the last
chapter and then this chapter; you'll see quite a difference in
appearance.
Avoid the trap of thinking animation is merely a matter of drawing the
right pixels, one after another. Animation is the art of drawing *the
right pixels at the right times* so that the eye and brain see what you
want them to see. Animation is a lot more challenging than merely
cranking out pixels, and it sure as heck isn't a purely linear process.