!!Attention: this page is currently missing most math formulas and is thus incomplete!!
The affine mode transform is a powerful tool that can be used to generate pseudo 3D displays on the Virtual Boy. If you have ever played Mario Kart on a SNES or a GBA than you have seen affine mode at work. This is a brief tutorial intended to introduce the user to the world of affine.
An affine transform is a way to translate points from one coordinate space to another. There are several properties to affine transforms: 1 to 1 mapping, parallel lines remain parallel, etc., but none of that matters to us. For our purposes, an affine transform is a way to map a point on the display back into a texture buffer, or the BGMap, in such a way that we can perform simple translations on the source bitmap. For example, we can perform rotations, scales, shears, reflections, and, given some fancy trickery, we can even generate pseudo 3D environments.
The following are some general forms of P that we can place into the above equation, along with a simple example of the result produced.
…
The Dx vector is used to set the offset of the object being transformed, without it an object's center of transformation is the upper left hand corner, and it is placed at the upper left hand corner of the display as well.
…
In order to pull off more complicated results, such as a rotation followed by a scale of an image, we can combine the P matrices together via multiplication. Note, however, that the order that you place the transforms in matters. First because a translate followed by a rotation does not produce the same result as a rotation followed by a translation. Second because the image quality will change based on what order you perform the operations. Basically, you want to perform the least destructive transformation first in order to preserve the highest quality image. This concludes our discussion of theory, from here on out it is all hardcore examples.
The Virtual Boy pulls some tricks with the affine equation in order to make things like pseudo 3D environments easier to implement. First off, the VB computes a separate affine transform equation for each line on the display bitmap so that you can easily change the parameters to the P and Dx matrices on a line by line basis; this is stored in the param table on the Virtual Boy. Secondly, the VB combined the P and Dx matrices together in order to save space in memory and to move some of the multiplication out of the display render loop. I have defined the Virtual Boy's modified P matrix
as
…
filling in Dx gives us
…
Notice how the Pvb precomputes the line for each affine transform. This makes sense since we have one affine transform per line and it allows us to move the Dx vector into the P matrix. Otherwise, we would need a separate Dx vector for each line or we would need to modify the Dx vector for each line. This is how the Game Boy Advanced does things.
The Virtual Boy defines a world entry as follows:
Table 1: World entry format
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| LON | RON | BGM | SCX | SCY | OVR | END | 0 | 0 | BGMAP_BASE (0 - 13) |
| GX ( -32,768 - 32,767 ) |
| GP ( -32,768 - 32,767 ) |
| GY ( -32,768 - 32,767 ) |
| MX ( -32,768 - 32,767 ) |
| MP ( -32,768 - 32,767 ) |
| MY ( -32,768 - 32,767 ) |
| W ( 0 - 383 ) |
| H ( 7 - 383 ) |
| PARAM_BASE ( 0×0000 - 0x0EBF ) |
| OVERPLANE_CHARACTER |
| WRITING FORBIDDEN |
| WRITING FORBIDDEN |
| WRITING FORBIDDEN |
| WRITING FORBIDDEN |
| WRITING FORBIDDEN |
There are three entries in the world entry that are specific to the Affine display mode.
First we must set the BGM type to Affine mode.
BGM - Sets the mode of the world
0 Normal BGMap
1 H-bias BGMap
2 Affine BGMap
3 OBJ
Next we need to set a pointer to the parameter table where we will store our Pvb matrices. The parameter table can be located anywhere between 0×00020000 and 0x0003D7FF. Notice however that this space is shared with the BGMaps, so you are probably better off locating the param table after 0x0003C000 if you do not want to limit the number of BGMaps available to you.
PARAM_BASE Parameter Table Base pointer, the last 4 bits must be zero.
The value to be stored can be calculated from the actual address thusly:
((addr - 0×00020000) » 1)
Where addr is the actual address. Note that it is up to you to place your parameter table at a 32 byte boundry (meaning that the last five bits are cleared).
The last setting is the OVR flag. This flag controls the wrapping of a BGMap. Basically, if you try to index off of the end of your BGMap, you can either wrap to the beginning of the BGMap or you can fill in the missing bits with zero. So, if we set the OVR flag, then we disable BGMap wrapping, and, if we clear it, we enable the wrapping.
OVR - Turns off the display wrapping. If you retrieve a pixel from (515,32) on a single BGMap, it would be retrieved from (3,32), if over was not enabled.
Once we have defined the PARAM_BASE, we need to fill in our parameter table. There is one parameter table entry for every line of the image, so there are GY parameter table entries in total. Obviously, the pa, pb_y, pc and pd_y entries correspond to the elements in Pvb = … and parallax is a horizontal shift that is applied to the display to generate a 3D effect just like GP and MP in the world structure. At this time it is not known whether parallax is applied to X before multiplication with Pvb or after. The purpose of the last three entries in the parameter table are not known at this time.
Table 2: Affine Param table entry
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
| pb_y (fixed point 13.3) |
| paralax |
| pd_y (fixed point 13.3) |
| pa (fixed point 7.9) |
| pc (fixed point 7.9) |
| Unknown |
| Unknown |
| Unknown |
When representing fractional numbers, such as 1/3=0.3333… in a computer, you have two options: floating point and fixed point. Floating point (float and double in C) is the standard way to represent a fraction, but floating point math can be quite slow and, in video games, speed can be everything. So, some bright individual came up with the idea of fixed point math. Basically, we take a 16 bit or 32 bit number and declare that some number of the bits represent the fractional portion of the number. To convert from an int or float to a fixed point number, we would multiply the int by 2n where n is the number of bits given over to the fractional part in our fixed point number. For an example lets take an 16 bit number and let 8 bits represent the fractional portion and 8 bits for the integer portion, lets call this new type a FIXED_8_8. Now to convert an integer to a FIXED_8_8 we simply multiply the integer by 28 or 256, and to convert the FIXED_8_8 to a integer we divide by 256. We can speed up these operations by taking advantage of the fact that a multiplication by 2n is the same as a binary shift to the left x bits, and division by 2n is the same as a binary shift to the right x bits. To convert a floating point number to a FIXED_8_8 we perform the same operations only using a floating point constant so that the compiler does not truncate the fractional portion of our number in an effort to be more efficient. When converting from a float to an integer, the least significant bit is always rounded down. We can offset this by adding 0.5 to the floating point number before assigning it to our FIXED_8_8 in order to force the number to round up or down as appropriate.
#define FLOAT_TO_FIXED_8_8 (n) (FIXED_8_8)((n)*256.0f+0.5f)
#define FIXED_8_8 _TO_FLOAT(n) (float) ((n)/256.0f)
#define INT_TO_FIXED_8_8(n) (FIXED_8_8)((n)«8) n*256
#define FIXED_8_8 _TO_INT(n) (int) ((n)»8) n/256
Addition and subtraction between two fixed point numbers operate the same as with integers. Multiplication and division, on the other hand, bring about some small challenges that must be dealt with. Given two numbers A and B that we converted to fixed pint by multiplying with a scale factor S, multiplying the two numbers together results in the following equation: … . As you can see the result is scaled by S2 not by S as we had intended. This can easily be corrected by either dividing A and B by the … before the multiplication step or by dividing the result by S after the multiplication. In the first case, we lose half of our bits in the floating point portion of our number, and in the later case we lose resolution in the real portion of the number. Notice that if you set S to be 1/2 of the bits available in your integer storage type, you potentially could lose all of the real portion of your number with a multiplication. There are many tricks that can work around this. Most involve performing two multiplications: one with the whole number and one with the fractional portion. However, we could just as simply use an integer storage type that has more bits than we are looking for. For example, if your final fixed point number is a FIXED_8_8 we can perform all of our math operations by using a FIXED_16_16, or a 32-bit number and convert the result back to a FIXED_8_8 after all of our multiplications have been performed. Division is the opposite, when we are done dividing A by B we end up canceling out the S factor altogether, and thus removing the fractional component. So, in order to combat this, we scale A by S before the division.
…
Here are some simple examples of how you might combine the P and Dx variables from before into a general Pvb equation on the Virtual Boy.
…
If we do not provide the Dx vector, then the scale and rotate functions use the upper left hand of the BGMap as the center point of the image. You should verify this for yourself by using an empty Dx vector in order to see what is going on.
In order to set things up we must first load up our Char and BGMap tables and define an affine mode world entry.
…
This concludes our introduction to Affine mode transforms on the Virtual boy. At this point you should have no trouble implementing simple scaling and rotation effects.
-
2D Transformations - Introduction to Computer Graphics, Arizona State University, Dianne Hansford, February 2, 2005
Here are some source codes using the Affine mode:
Discussion
we need a math plugin!
Here's an option:
http://xml-maiden.com/21/stress.xml
Even better:
Look for "math" on http://wiki.splitbrain.org/wiki:plugins
i have no way to install any of the needed interpreters for the math plugins on the web server. so the css method is my favorite.