/* * fireworks.c * written by Holmes Futrell * use however you want */ #include "SDL.h" #include "SDL_opengles.h" #include "common.h" #include #include #define MILLESECONDS_PER_FRAME 16 /* about 60 frames per second */ #define ACCEL 0.0001f /* acceleration due to gravity, units in pixels per millesecond squared */ #define WIND_RESISTANCE 0.00005f /* acceleration per unit velocity due to wind resistance */ #define MAX_PARTICLES 2000 /* maximum number of particles displayed at once */ static GLuint particleTextureID; /* OpenGL particle texture id */ static SDL_bool pointSizeExtensionSupported; /* is GL_OES_point_size_array supported ? */ /* used to describe what type of particle a given struct particle is. emitter - this particle flies up, shooting off trail particles, then finally explodes into dust particles. trail - shoots off, following emitter particle dust - radiates outwards from emitter explosion */ enum particleType { emitter = 0, trail, dust }; /* struct particle is used to describe each particle displayed on screen */ struct particle { GLfloat x; /* x position of particle */ GLfloat y; /* y position of particle */ GLubyte color[4]; /* rgba color of particle */ GLfloat size; /* size of particle in pixels */ GLfloat xvel; /* x velocity of particle in pixels per milesecond */ GLfloat yvel; /* y velocity of particle in pixels per millescond */ int isActive; /* if not active, then particle is overwritten */ enum particleType type; /* see enum particleType */ } particles[MAX_PARTICLES]; /* this array holds all our particles */ static int num_active_particles; /* how many members of the particle array are actually being drawn / animated? */ static int screen_w, screen_h; /* function declarations */ void spawnTrailFromEmitter(struct particle *emitter); void spawnEmitterParticle(GLfloat x, GLfloat y); void explodeEmitter(struct particle *emitter); void initializeParticles(void); void initializeTexture(); int nextPowerOfTwo(int x); void drawParticles(); void stepParticles(void); /* helper function (used in texture loading) returns next power of two greater than or equal to x */ int nextPowerOfTwo(int x) { int val = 1; while (val < x) { val *= 2; } return val; } /* steps each active particle by timestep MILLESECONDS_PER_FRAME */ void stepParticles(void) { int i; struct particle *slot = particles; struct particle *curr = particles; for (i = 0; i < num_active_particles; i++) { /* is the particle actually active, or is it marked for deletion? */ if (curr->isActive) { /* is the particle off the screen? */ if (curr->y > screen_h) curr->isActive = 0; else if (curr->y < 0) curr->isActive = 0; if (curr->x > screen_w) curr->isActive = 0; else if (curr->x < 0) curr->isActive = 0; /* step velocity, then step position */ curr->yvel += ACCEL * MILLESECONDS_PER_FRAME; curr->xvel += 0.0f; curr->y += curr->yvel * MILLESECONDS_PER_FRAME; curr->x += curr->xvel * MILLESECONDS_PER_FRAME; /* particle behavior */ if (curr->type == emitter) { /* if we're an emitter, spawn a trail */ spawnTrailFromEmitter(curr); /* if we've reached our peak, explode */ if (curr->yvel > 0.0) { explodeEmitter(curr); } } else { float speed = sqrt(curr->xvel * curr->xvel + curr->yvel * curr->yvel); /* if wind resistance is not powerful enough to stop us completely, then apply winde resistance, otherwise just stop us completely */ if (WIND_RESISTANCE * MILLESECONDS_PER_FRAME < speed) { float normx = curr->xvel / speed; float normy = curr->yvel / speed; curr->xvel -= normx * WIND_RESISTANCE * MILLESECONDS_PER_FRAME; curr->yvel -= normy * WIND_RESISTANCE * MILLESECONDS_PER_FRAME; } else { curr->xvel = curr->yvel = 0; /* stop particle */ } if (curr->color[3] <= MILLESECONDS_PER_FRAME * 0.1275f) { /* if this next step will cause us to fade out completely then just mark for deletion */ curr->isActive = 0; } else { /* otherwise, let's fade a bit more */ curr->color[3] -= MILLESECONDS_PER_FRAME * 0.1275f; } /* if we're a dust particle, shrink our size */ if (curr->type == dust) curr->size -= MILLESECONDS_PER_FRAME * 0.010f; } /* if we're still active, pack ourselves in the array next to the last active guy (pack the array tightly) */ if (curr->isActive) *(slot++) = *curr; } /* endif (curr->isActive) */ curr++; } /* the number of active particles is computed as the difference between old number of active particles, where slot points, and the new size of the array, where particles points */ num_active_particles = slot - particles; } /* This draws all the particles shown on screen */ void drawParticles() { /* draw the background */ glClear(GL_COLOR_BUFFER_BIT); /* set up the position and color pointers */ glVertexPointer(2, GL_FLOAT, sizeof(struct particle), particles); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(struct particle), particles[0].color); if (pointSizeExtensionSupported) { /* pass in our array of point sizes */ glPointSizePointerOES(GL_FLOAT, sizeof(struct particle), &(particles[0].size)); } /* draw our particles! */ glDrawArrays(GL_POINTS, 0, num_active_particles); } /* This causes an emitter to explode in a circular bloom of dust particles */ void explodeEmitter(struct particle *emitter) { /* first off, we're done with this particle, so turn active off */ emitter->isActive = 0; int i; for (i = 0; i < 200; i++) { if (num_active_particles >= MAX_PARTICLES) return; /* come up with a random angle and speed for new particle */ float theta = randomFloat(0, 2.0f * 3.141592); float exponent = 3.0f; float speed = randomFloat(0.00, powf(0.17, exponent)); speed = powf(speed, 1.0f / exponent); /*select the particle at the end of our array */ struct particle *p = &particles[num_active_particles]; /* set the particles properties */ p->xvel = speed * cos(theta); p->yvel = speed * sin(theta); p->x = emitter->x + emitter->xvel; p->y = emitter->y + emitter->yvel; p->isActive = 1; p->type = dust; p->size = 15; /* inherit emitter's color */ p->color[0] = emitter->color[0]; p->color[1] = emitter->color[1]; p->color[2] = emitter->color[2]; p->color[3] = 255; /* our array has expanded at the end */ num_active_particles++; } } /* This spawns a trail particle from an emitter */ void spawnTrailFromEmitter(struct particle *emitter) { if (num_active_particles >= MAX_PARTICLES) return; /* select the particle at the slot at the end of our array */ struct particle *p = &particles[num_active_particles]; /* set position and velocity to roughly that of the emitter */ p->x = emitter->x + randomFloat(-3.0, 3.0); p->y = emitter->y + emitter->size / 2.0f; p->xvel = emitter->xvel + randomFloat(-0.005, 0.005); p->yvel = emitter->yvel + 0.1; /* set the color to a random-ish orangy type color */ p->color[0] = (0.8f + randomFloat(-0.1, 0.0)) * 255; p->color[1] = (0.4f + randomFloat(-0.1, 0.1)) * 255; p->color[2] = (0.0f + randomFloat(0.0, 0.2)) * 255; p->color[3] = (0.7f) * 255; /* set other attributes */ p->size = 10; p->type = trail; p->isActive = 1; /* our array has expanded at the end */ num_active_particles++; } /* spawns a new emitter particle at the bottom of the screen destined for the point (x,y). */ void spawnEmitterParticle(GLfloat x, GLfloat y) { if (num_active_particles >= MAX_PARTICLES) return; /* find particle at endpoint of array */ struct particle *p = &particles[num_active_particles]; /* set the color randomly */ switch (rand() % 4) { case 0: p->color[0] = 255; p->color[1] = 100; p->color[2] = 100; break; case 1: p->color[0] = 100; p->color[1] = 255; p->color[2] = 100; break; case 2: p->color[0] = 100; p->color[1] = 100; p->color[2] = 255; break; case 3: p->color[0] = 255; p->color[1] = 150; p->color[2] = 50; break; } p->color[3] = 255; /* set position to (x, screen_h) */ p->x = x; p->y = screen_h; /* set velocity so that terminal point is (x,y) */ p->xvel = 0; p->yvel = -sqrt(2 * ACCEL * (screen_h - y)); /* set other attributes */ p->size = 10; p->type = emitter; p->isActive = 1; /* our array has expanded at the end */ num_active_particles++; } /* just sets the endpoint of the particle array to element zero */ void initializeParticles(void) { num_active_particles = 0; } /* loads the particle texture */ void initializeTexture() { int bpp; /* texture bits per pixel */ Uint32 Rmask, Gmask, Bmask, Amask; /* masks for pixel format passed into OpenGL */ SDL_Surface *bmp_surface; /* the bmp is loaded here */ SDL_Surface *bmp_surface_rgba8888; /* this serves as a destination to convert the BMP to format passed into OpenGL */ bmp_surface = SDL_LoadBMP("stroke.bmp"); if (bmp_surface == NULL) { fatalError("could not load stroke.bmp"); } /* Grab info about format that will be passed into OpenGL */ SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_ABGR8888, &bpp, &Rmask, &Gmask, &Bmask, &Amask); /* Create surface that will hold pixels passed into OpenGL */ bmp_surface_rgba8888 = SDL_CreateRGBSurface(0, bmp_surface->w, bmp_surface->h, bpp, Rmask, Gmask, Bmask, Amask); /* Blit to this surface, effectively converting the format */ SDL_BlitSurface(bmp_surface, NULL, bmp_surface_rgba8888, NULL); glGenTextures(1, &particleTextureID); glBindTexture(GL_TEXTURE_2D, particleTextureID); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nextPowerOfTwo(bmp_surface->w), nextPowerOfTwo(bmp_surface->h), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); /* this is where we actually pass in the pixel data */ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bmp_surface->w, bmp_surface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, bmp_surface_rgba8888->pixels); /* free bmp surface and converted bmp surface */ SDL_FreeSurface(bmp_surface); SDL_FreeSurface(bmp_surface_rgba8888); } int main(int argc, char *argv[]) { SDL_Window *window; /* main window */ SDL_GLContext context; int w, h; Uint32 startFrame; /* time frame began to process */ Uint32 endFrame; /* time frame ended processing */ Uint32 delay; /* time to pause waiting to draw next frame */ int done; /* should we clean up and exit? */ /* initialize SDL */ if (SDL_Init(SDL_INIT_VIDEO) < 0) { fatalError("Could not initialize SDL"); } /* seed the random number generator */ srand(time(NULL)); /* request some OpenGL parameters that may speed drawing */ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0); SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); /* create main window and renderer */ window = SDL_CreateWindow(NULL, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS); context = SDL_GL_CreateContext(window); /* load the particle texture */ initializeTexture(); /* check if GL_POINT_SIZE_ARRAY_OES is supported this is used to give each particle its own size */ pointSizeExtensionSupported = SDL_GL_ExtensionSupported("GL_OES_point_size_array"); /* set up some OpenGL state */ glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); SDL_GetWindowSize(window, &screen_w, &screen_h); glViewport(0, 0, screen_w, screen_h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrthof((GLfloat) 0, (GLfloat) screen_w, (GLfloat) screen_h, (GLfloat) 0, 0.0, 1.0); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnable(GL_POINT_SPRITE_OES); glTexEnvi(GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, 1); if (pointSizeExtensionSupported) { /* we use this to set the sizes of all the particles */ glEnableClientState(GL_POINT_SIZE_ARRAY_OES); } else { /* if extension not available then all particles have size 10 */ glPointSize(10); } done = 0; /* enter main loop */ while (!done) { startFrame = SDL_GetTicks(); SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { done = 1; } if (event.type == SDL_MOUSEBUTTONDOWN) { int x, y; SDL_GetMouseState(&x, &y); spawnEmitterParticle(x, y); } } stepParticles(); drawParticles(); SDL_GL_SwapWindow(window); endFrame = SDL_GetTicks(); /* figure out how much time we have left, and then sleep */ delay = MILLESECONDS_PER_FRAME - (endFrame - startFrame); if (delay > MILLESECONDS_PER_FRAME) { delay = MILLESECONDS_PER_FRAME; } if (delay > 0) { SDL_Delay(delay); } } /* delete textures */ glDeleteTextures(1, &particleTextureID); /* shutdown SDL */ SDL_Quit(); return 0; }