Make copy of the previous chapter the same way as previously, resulting in ch03
.
The answer to the topic is, as you might have guessed, pixels.
Nobody who does serious graphics uses putpixel()
; it's simply too slow, especially if it is implemented as a function - the overhead that is caused by a simple function call can be quite severe when you're doing it millions of times per second (and you will - even 640 * 480 * 50Hz
is over 15 million). Additionally, typical putpixel()
routine (like ours) also makes sure that you're not drawing out of bounds, which brings in additional overhead.
Thus, we're defining primitives that consist of more than one pixel a piece. Enter simple sprites.
Copy-paste the following under our putpixel()
function. (We won't be using the putpixel()
anywhere, but it's handy to be around if you wish to debug something).
const unsigned char sprite[] =
{
0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,
0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,
0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,
0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,
0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0
};
void drawsprite(int x, int y, unsigned int color)
{
int i, j, c, yofs;
yofs = y * WINDOW_WIDTH + x;
for (i = 0, c = 0; i < 16; i++)
{
for (j = 0; j < 16; j++, c++)
{
if (sprite[c])
{
gFrameBuffer[yofs + j] = color;
}
}
yofs += WINDOW_WIDTH;
}
}
The sprite[]
array defines 16 * 16 = 256
bytes of data. There's no actual reason why I chose this resolution. As you can see from the data, the ones define a kind of filled circle-ish form.
The drawsprite()
function goes through the data, and, if the data is a non-zero value, it writes a pixel to the screen with the desired color.
Next, edit the i * i + j * j + aTicks
- replace the line with gFrameBuffer[c] = 0xff000000;
. If you compile and run, you should end up with a black screen. (Well, maybe you have a blue square near the top left corner, but that's optional).
Feel free to wipe out the putpixel()
s if you have them, as we won't be needing the blue dot.
After the background fill (which should now fill the screen with plain black color), around where the putpixel()
s are (or were), add the following code:
for (int i = 0; i < 128; i++)
{
drawsprite((int)((WINDOW_WIDTH / 2) + sin((aTicks + i * 10) * 0.003459734) * (WINDOW_WIDTH / 2 - 16)),
(int)((WINDOW_HEIGHT / 2) + sin((aTicks + i * 10) * 0.003345973) * (WINDOW_HEIGHT / 2 - 16)),
((int)(sin((aTicks * 0.2 + i) * 0.234897) * 127 + 128) << 16) |
((int)(sin((aTicks * 0.2 + i) * 0.123489) * 127 + 128) << 8) |
((int)(sin((aTicks * 0.2 + i) * 0.312348) * 127 + 128) << 0) |
0xff000000);
}
Compile and run. Whee!
Okay. What's happening here is that we're calling our new drawsprite()
function with some parameters that depend on time, and that causes the animation to happen.
The x coordinate is based on the sin()
of (aTicks + i * 10)
multiplied by a more or less random value that's under 0.01. The result of the sin()
function is between -1 and 1, inclusive. We multiply the sin()
value by little below half of screen width, so the result is +/- half of screen width. This value is added to half of screen width, so the final result will range from around zero to about screen width.
The value of y coordinate is calculated in similar manner. Note that the range of the coordinates is chosen so that the sprite will always remain inside the screen. Writing outside the screen is a Bad Thing.
The color is calculated in a slightly more complicated manner, but not much. We're calculating a value for R, G and B separately, using a range from 1 to 255 for each component. These are then shifted to their correct positions (<<
16 for blue, 8 for green, 0 for red - shift by zero is, naturally, a "no operation", but it is included here just so that the code looks neat). The resulting values are combined with the bitwise or operator |
.
And as always, we set the alpha to 255.
If shifting, ORring, ANDing etc. feels awkward to you, you may find my boolean / bit masking / bit modification tutorial useful.
Note that there are a lot of "magic numbers" in this tutorial. Typically these should be replaced with constants, but since the whole point is to play with the numbers, it's more convenient this way.
i
variable range in the render function)* 10
multiplier for i
in x
and y
parameters for drawsprite()
)[-1..1] * [-1..1] = [-1..1]
, you can safely calculate sin()*sin()
or even sin()*sin()*sin()
etc. Add several sin()
functions to both x and y coordinate parameters with different multipliers.sin()
function.Next up: 04 - Let it Snow..
Any comments etc. can be emailed to me.