This tutorial shows how to make a metaballs demo. It uses SDL for drawing the graphics. The metaballs are kept in a vector.
First we start off by including the needed libraries. Here we need SDL for the windowing and drawing. We include time.h only to randomize C++'s random number generator. Not strictly necessary, but if you want to see a different animation every time you run it, the random number generator needs different seeds. Then we include math.h for some simple mathematical functions and the vectors are used to keep a list of all the balls we have on screen.
#include <SDL/SDL.h> #include <time.h> // For seeding random number generator using time #include <math.h> // Some math functions such as absolute value and square root #include <vector> using namespace std; // vectors are part of std namespace
After the includes, we define a few values we need, such as screen size, the threshold for the metaballs surface and also what color to actually draw. You will notice we only have one threshold. If you know anything about isosurfaces then you would have expected two values, i.e. the range for which you consider the surface to be. However, as you can see in the screenshot this demo will completely color the balls giving a lava effect, instead of the isosurface you might have expected.
// White on 32bit windows SDL surface #define WHITE 0xFFFFFFFF // Equation threshold for isosurface #define THRESHOLD 1.0f #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 240
Next, a Metaball structure is defined. It only holds a position (x,y) and a radius r. For the sake of this demonstration, I have added a x and y velocity to the metaball, such that each can have its own speed while moving around on the screen.
The structure needs only one function. That is the Equation() function, which calculates the effect of this particular metaball on a particular pixel on the screen. Any function can be used really.
To give a recap of isosurfaces: Each metaball will have an influence on the entire screen. This influence decays as the pixel gets further from the center of the ball. Should the sum for a pixel over all metaballs get over the threshold we will color it.
Again, for the sake of "niceness," I have added an Update() function which will move the metaballs around according to their velocity. The Update() function will also flip the velocity should the metaball reach the border of the screen.
struct Metaball { float x, y; float xvel, yvel; float r; // Constructor Metaball( float startx, float starty, float xvelocity, float yvelocity, float radius ) { x = startx; y = starty; xvel = xvelocity; yvel = yvelocity; r = radius; } // Calculate influence of this metaball on coordinate x,y float Equation(float x, float y) { //return r / ( fabs(x-this->x) + fabs(y-this->y) ); // Diamond return r / sqrt( ( x - this->x ) * ( x - this->x ) + ( y - this->y ) * ( y - this->y ) ); // Circle } void Update( float dt ) { if ( x < 0 || x >= SCREEN_WIDTH ) xvel *= -1; if ( y < 0 || y >= SCREEN_HEIGHT ) yvel *= -1; x += xvel * dt; y += yvel * dt; } };
Then, a few helper functions. A random number generator, one to set the pixels of an SDL surface, and a function to actually loop through all the pixels on the surface and calculate if it is part of the isosurface, or not. I.e. loop through all metaballs and sum up their effect on each pixel. Does the sum go over the threshold, then draw it white.
// Function to generate a random number in [0,1] float Random() { return (float) rand()/RAND_MAX; } // get pixel value from 32bit SDL surface Uint32 GetPixel32( SDL_Surface *surface, int x, int y ) { Uint32 *pixels = (Uint32*)surface->pixels; return pixels[ ( y * surface->w ) + x ]; } // Draw pixel on 32bit SDL surface void PutPixel32( SDL_Surface *surface, int x, int y, Uint32 pixel ) { Uint32 *pixels = (Uint32 *)surface->pixels; pixels[ ( y * surface->w ) + x ] = pixel; } // Draw all metaballs on SDL surface void DrawMetaballs( SDL_Surface* surface, vector<Metaball>& metaballs ) { // Value to act as a summation of all Metaballs' fields applied to this particular pixel float sum; // Iterate over every pixel on the screen for ( int y = 0; y < SCREEN_HEIGHT; ++y ) { for ( int x = 0; x < SCREEN_WIDTH; ++x ) { // Reset the summation sum = 0; // Iterate through every Metaball in vector vector<Metaball>::iterator curBall = metaballs.begin(); for ( ; curBall != metaballs.end(); ++curBall ) { sum += curBall->Equation( x, y ); } // Decide whether to draw a pixel if ( sum > THRESHOLD ) PutPixel32( surface, x, y, WHITE ); } } }
Finally, the main program. It starts by initializing the random number generator using the computers current time, then sets up SDL, generates a few metaballs and then enters the main loop.
The main loop contains the SDL event loop, which processes all SDL events and acts accordingly. Following the event loop is the physics update, which just loops through all the metaballs in the vector, and calls Update() on them. This is followed by drawing graphics. Notice that some SDL surfaces have to be locked before drawing pixels on them.
int main( int numArguments, char** arguments ) { // Seed random number generator srand( (unsigned)time( NULL ) ); if ( SDL_Init( SDL_INIT_EVERYTHING ) == -1 ) { return 1; // Failed to initialize SDL } SDL_Surface* screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, 32, SDL_SWSURFACE ); if ( screen == NULL ) { return 2; // Failed to create screen } SDL_WM_SetCaption( "Metaballs demo", NULL ); // Create some metaballs const int numBlobs = 6; vector<Metaball> metaballs; for ( int i = 0; i < numBlobs; ++i ) { // Add new metaball to vector Metaball curBall( Random() * SCREEN_WIDTH, Random() * SCREEN_HEIGHT, Random() * 200 - 100, Random() * 200 - 100, Random() * 8 + 8 ); metaballs.push_back( curBall ); } // Main loop SDL_Event event; bool isRunning = true; Uint32 lastTick = SDL_GetTicks(); while ( isRunning ) { // Event loop (processes all events) while ( SDL_PollEvent( &event ) ) { if ( event.type == SDL_QUIT ) { isRunning = false; } else if ( event.type == SDL_KEYDOWN ) { switch ( event.key.keysym.sym ) { case SDLK_ESCAPE: isRunning = false; break; default: break; } } } // Event loop // Calculate time ellapsed since last frame (in seconds) Uint32 curTick = SDL_GetTicks(); Uint32 diff = curTick - lastTick; lastTick = curTick; float dt = (float)diff / 1000.0f; // Update physics vector<Metaball>::iterator curBall = metaballs.begin(); for ( ; curBall != metaballs.end(); ++curBall ) { curBall->Update( dt ); } // Draw SDL_FillRect( screen, NULL, 0 ); if ( SDL_MUSTLOCK( screen ) ) SDL_LockSurface( screen ); DrawMetaballs( screen, metaballs ); if ( SDL_MUSTLOCK( screen ) ) SDL_UnlockSurface( screen ); SDL_Flip( screen ); } // Main Loop SDL_Quit(); return 0; }
Put all ingredients into Code::Blocks, stir a while, make sure to add mingw32, SDLmain and SDL to the linker (in that order), and enjoy.
| Categories: Graphics Tutorials : SDL Tutorials : Tutorials |