Waving Flag Animation
21 Dec 2024Introduction
In a previous post we made a simple water droplet demonstration. This is all built on the vga work that we’ve already done.
In today’s post, we’ll use this information again and put a waving flag demo together.
The effect that we’re looking to produce should look something like this:
The Idea
The waving flag effect simulates a piece of fabric rippling in the wind. Here’s the high-level approach:
- Flag Bitmap: Create a bitmap with alternating horizontal stripes of green and white.
- Wave Dynamics: Use trigonometric functions to displace pixels horizontally and vertically, simulating waves.
- Buffering: Use an offscreen buffer to avoid flickering during rendering.
Building the Flag
The flag is a simple bitmap composed of horizontal stripes alternating between green and white. Here’s how it’s generated:
uint8_t* make_flag() {
int width = 200, height = 100;
uint8_t *buffer = (uint8_t*) malloc(width * height);
for (int y = 0; y < height; y++) {
uint8_t col = (y / 10) % 2 == 0 ? 2 : 15; // Green and white stripes
memset(buffer + (y * width), col, width);
}
return buffer;
}
Each stripe spans 10 rows, and the alternating colors give the flag a distinctive look.
Adding the Wave Effect
The waving effect is achieved by modifying the position of each pixel based on sine and cosine functions. Here’s the core logic:
void draw_flag(double theta, int x, int y, int width, int height, uint8_t *flag, uint8_t *buffer) {
double t = 0;
for (int xx = 0; xx < width; xx ++) {
t = theta;
double wave_offset = 5 * sin(theta + xx * 0.1);
for (int yy = 0; yy < height; yy ++) {
uint16_t px = xx + wave_offset;
uint16_t py = yy + (2 * sin((theta + xx) * 0.2));
uint16_t o = x + (px + x) + ((py + y) << 8) + ((py + y) << 6);
uint16_t f = xx + ((yy << 6) + (yy << 5) + (y << 2) << 1);
uint8_t col = 25 + (3 * sin(t)) + (6 * cos(t));
buffer[o] = flag[f];
buffer[o-1] = flag[f];
buffer[o+1] = flag[f];
buffer[o-320] = flag[f];
buffer[o+320] = flag[f];
buffer[o-319] = flag[f];
buffer[o-321] = flag[f];
buffer[o+319] = flag[f];
buffer[o-321] = flag[f];
t += 0.1;
}
}
}
Key Features:
- Wave Dynamics: The
wave_offset
creates a horizontal ripple effect based onsin(theta + xx * 0.1)
. A secondary vertical ripple adds realism. - Boundary Checks: Ensures pixels remain within the screen bounds.
- Direct Pixel Copy: Pixels are copied from the flag bitmap to the appropriate buffer position.
- Redundant Pixel Render: We make sure we render to all surrounding cells so we don’t experience tearing
Main Loop
The main loop ties everything together, handling synchronization, rendering, and input:
int main() {
uint8_t *back_buffer = (uint8_t *)malloc(64000);
uint8_t *flag = make_flag();
double theta = 0;
set_mcga();
clear_buffer(0x00, back_buffer);
while (!kbhit()) {
draw_flag(theta, 20, 20, 200, 100, flag, back_buffer);
wait_vsync();
copy_buffer(vga, back_buffer);
clear_buffer(0x00, back_buffer);
theta += 0.1; // Animate the wave
}
free(flag);
free(back_buffer);
set_text();
return 0;
}
Highlights:
- Synchronization: The
wait_vsync()
call ensures smooth updates. - Animation: The
theta
value incrementally changes, creating continuous movement. - Keyboard Interrupt: The
kbhit()
function allows the user to exit gracefully.
Conclusion
This waving flag effect combines simple algorithms with creative use of VGA mode 13h to create a visually stunning effect. By leveraging trigonometry, palette manipulation, and efficient buffer handling, we replicate the mesmerizing motion of a flag in the wind.
You can find the complete code on GitHub as a gist.
Try it out, tweak the parameters, and share your own effects! There’s a lot of joy in creating beautiful visuals with minimal resources.