Site hosted by Angelfire.com: Build your free website today!
Previous Page
Next Page
Table of Contents


Advanced Graphics Display

Still with me? Now might be a good time for a caffeine boost. There's a lot of math in the following paragraphs.

Chain Objects: one chain to rule all, one chain to bind them

A chain object is one of those "out of the loop" objects that is not displayed and cannot be deleted. Chain objects do not have physical coordinates; rather, they form a sort of hub that regulates a large area of the secondary grid. Secondary grid information always references a chain object, which in turn points to a "master" object (usually the object that generated the chain object).

If a grid square referenced by dispptr[][] equals zero, it means no chain information exists at that square. If the value in dispptr[][] is nonzero, this value points to a chain object. A chain object contains the following values:

char ctype -- 0 if additive, 1 if substitutive
char pal -- palette change for additive chains
char anim -- an additive offset for the sprite or matrix position
int master -- a pointer to the object that generated the chain
int sprite -- base sprite for additive chains, base matrix position for
substitutive chains
int xoff, yoff -- tweak offsets (in x- and y-direction) for entire chain

Additive Chains

If ctype is zero, the square will function as follows:

  1. Take sprite (should be of transparent type)
  2. Add value in dispoff[][]
  3. Add value in anim
  4. Change sprite color to the palette entry in pal
  5. Offset the sprite location by xoff and yoff

Additive chains are simple and fast. Because the flip flags are just defined as bits 14 and 15, either dispoff[][] or sprite can control the flip flags.

Of course, what is displayed is purely visual. Chain object-based display does not carry over into the NIB-OOL collision messaging system. To check for collisions, the contents of dispptr[], dispoff[], and master must be examined and interpreted accordingly.

Substitutive Chains

If ctype is one, the square will function as follows:

  1. The value of sprite will point to a substitution matrix
  2. Read dispoff[][] into separate low-order and high-order bytes
  3. Add the low-order byte to the pointer to the matrix; treat as starting position
  4. Add the value of anim to the pointer to the matrix
  5. Treat the high-order byte as the transformation type (curve type)
  6. Offset the location by xoff and yoff

These steps may look easy at first, but they are not. The only way to explain this properly is by showing how the variables represent the drawn square visually.

The low-order byte of dispoff[][] is one of the following values: 0, 16, 32, 48, or 64. This offset focuses on one portion of the 16x5-sized y-line of the substitution matrix. This offset is then augmented by anim (usually from 0 to 15) to obtain the final starting position. The value of anim generally starts at 16 and decreases gradually to 0. The result? The "window" into the matrix appears to be moving backwards, but the user (seeing the window but not the matrix) thinks the image is moving forwards!

Note that even if the [anim+dispoff (low)] offset pushes the physical boundaries of the rightmost pixels (past 16x5), there will be a wrapping effect to the first blank square.

Transformation (curve types)

Good explanation? Well, not quite. We're not done yet. The "window" into the matrix, if displayed as-is, only shows a left-to-right image. The window must be transformed using one of twenty "curve types." Having no other name for them, I'll call the transforming functions "wheel transforms." A wheel transform is a mathematical function that takes in a coordinate pair and returns a coordinate pair, processing the input coordinate pair as a magnitude and direction (even though it resembles rectangular coordinates in the "window").

The result of a wheel transform is to make an image (e.g. snake) appear as if it is bending and winding its way around corners or into/out of holes. There are twelve 2-D wheel transforms (for winding around corners) and eight 3-D wheel transforms (for emerging from/diving into holes).

Four of the curve types are not used. For programmatic purposes, it's easier to leave these blank.

The number 1 represents an "invalid" or "miscellaneous" direction. If diving, the destination direction is 1. If emerging, the origin direction is 1. Because cartesian directions are one of the four numbers 0, 64, 128, and 192, they cannot be confused with an invalid direction.

What, you may ask, goes on during a transformation? On the surface, it looks like some fancy trigonometry is taking place. This is only half true. Trigonometry was used to generate the transformation tables, but no trigonometric functions actually need to be called when drawing a transformed sprite! For each curve type, there is a 16x14 short integer array defined. It is this array that is used to formulate the basis for the drawn sprite. Instead of individual pixel data, the array is composed of references to pixels in the window of the substitution matrix. A single pixel is drawn according to the following input/output steps:

  1. Window reference = curve type array[Screen coordinate]
  2. Pixel = Matrix[Window reference + Window offset]
  3. If Pixel value is 0, don't display; display otherwise

Lost? Keep up the caffeine flow. One should note that the matrices themselves are not updated for palette changes during drawing. This step does not appear in the above list. So how do you get snakes of so many different colors? The answer is that when a section of the substitution matrix is loaded, the sprite is palette changed during the load process. The pal variable serves no purpose in the chain object when ctype = 1. This results in memory-intensive computation at the time of loading a section into a matrix, but it cuts down the drawing computation significantly.

Mathematical basis of transformations

How does one generate the 16x14 integer arrays that transform the sprites? Okay, I'll be very blunt. If you failed trigonometry, GO AWAY. Otherwise, read on.

I have spelled out here the exact trigonometric transforms. If you can't figure them out, that's your problem.

Curve typeWheel transform
0tx = x;
ty = y;
1tx = y * sin(x * M_PI_2 / 15);
ty = y * cos(x * M_PI_2 / 15);
2Not used
3tx = 15 - (15 - y) * cos(x * M_PI_2 / 15);
ty = 15 - (15 - y) * sin(x * M_PI_2 / 15);
4tx = 15 - (15 - y) * cos(x * M_PI_2 / 15);
ty = 15 - (15 - y) * sin(x * M_PI_2 / 15);
5tx = y;
ty = 15 - x;
6tx = y * cos(x * M_PI_2 / 15);
ty = 15 - y * sin(x * M_PI_2 / 15);
7Not used
8Not used
9tx = 15 - (15 - y) * sin(x * M_PI_2 / 15);
ty = (15 - y) * cos(x * M_PI_2 / 15);
10tx = 15 - x;
ty = 15 - y;
11tx = 15 - y * sin(x * M_PI_2 / 15);
ty = 15 - y * cos(x * M_PI_2 / 15);
12tx = 15 - y * cos(x * M_PI_2 / 15);
ty = y * sin(x * M_PI_2 / 15);
13Not used
14tx = (15 - y) * cos(x * M_PI_2 / 15);
ty = (15 - y) * sin(x * M_PI_2 / 15);
15tx = 15 - y;
ty = x;
16tx = 15 - 12 * cos(x * M_PI_2 / 15);
ty = 8 + (y - 8) * (15 + x) / (15 * 2);
17tx = 8 + (y - 8) * (15 + x) / (15 * 2);
ty = 12 * cos(x * M_PI_2 / 15);
18tx = 12 * cos(x * M_PI_2 / 15);
ty = 8 - (y - 8) * (15 + x) / (15 * 2);
19tx = 8 - (y - 8) * (15 + x) / (15 * 2);
ty = 16 - 12 * cos(x * M_PI_2 / 15);
20tx = 12 * sin(x * M_PI_2 / 15);
ty = 8 + (y - 8) * (1 - x / (15 * 2));
21tx = 8 + (y - 8) * (1 - x / (15 * 2));
ty = 16 - 12 * sin(x * M_PI_2 / 15);
22tx = 16 - 12 * sin(x * M_PI_2 / 15);
ty = 8 + (y - 8) * (x / (15 * 2) - 1);
23tx = 8 + (y - 8) * (x / (15 * 2) - 1);
ty = 12 * sin(x * M_PI_2 / 15);

What the heck does it all mean? I'll try to explain in lay terms (ha ha). There are two basic patterns: the 2-D wheel and the 3-D wheel. A 2-D wheel treats the x-coordinate of the substitution matrix window as a radian measure and the y-coordinate of the matrix window as a magnitude measure. This is roughly a conversion from rectangular to polar coordinates. Naturally, the proportions are distorted at some points, but not significantly, since a snake is supposed to be flexible.

The general form for a 2-D wheel is:

tx or ty = y * trig[(x / maxvaluex) * (pi / 2)]

The 3-D wheel is harder to picture, but fairly straightforward. Instead of converting both tx and ty from polar representations of x and y, a 3-D wheel uses y as a linear magnitude from the x-axis (assuming the x-axis runs down the center of the sprite). The value of x is a radian measure that is multiplied by a fixed magnitude, which normally is less than the full width of the square. The general form for a 3-D wheel is:

lateral = fixedmag * trig[(x / maxvaluex) * (pi / 2)]
axial = yatxaxis + (y - yatxaxis) * [x / (maxvaluex * 2)]

All this aggravating math is performed in the program MATRXGEN.EXE. This program does a few other cosmetic things to the transforms, too. The y-coordinates need to be converted from square (16x16) to almost-square (16x14). Since leaving out two y-lines mangles the appearance of a few curve types, the program also employs a selective removal process to take out less significant y-lines (i.e. avoid removing lines that show the snake's facial expressions).

If I get a 640x480 version going, MATRXGEN.EXE will, unfortunately, need quite a bit of additional work. Then again, having a 32x32 sprite size eliminates the need for cosmetic changes, as no y-lines are removed.

That's all, folks. Compared to the mathematics that are necessary in ray-tracing applications, this may not seem like much. But the logic is the cream of the successful design in Nibbler: it looks really nice, and the user won't need to learn the quirks of the game to be able to play it. More importantly, substitution logic can be used to make more powerful transformations in 2-D or 3-D environments, clearing the way for even more enhanced designs in future applications.

Previous Page
Next Page
Table of Contents
Back to Title Page