1 /*
2  *  fireworks.c
3  *  written by Holmes Futrell
4  *  use however you want
5  */
6 
7 #include "SDL.h"
8 #include "SDL_opengles.h"
9 #include "common.h"
10 #include <math.h>
11 #include <time.h>
12 
13 #define ACCEL 0.0001f           /* acceleration due to gravity, units in pixels per millesecond squared */
14 #define WIND_RESISTANCE 0.00005f        /* acceleration per unit velocity due to wind resistance */
15 #define MAX_PARTICLES 2000      /* maximum number of particles displayed at once */
16 
17 static GLuint particleTextureID;        /* OpenGL particle texture id */
18 static SDL_bool pointSizeExtensionSupported;    /* is GL_OES_point_size_array supported ? */
19 static float pointSizeScale;
20 /*
21     used to describe what type of particle a given struct particle is.
22     emitter - this particle flies up, shooting off trail particles, then finally explodes into dust particles.
23     trail   - shoots off, following emitter particle
24     dust    - radiates outwards from emitter explosion
25 */
26 enum particleType
27 {
28     emitter = 0,
29     trail,
30     dust
31 };
32 /*
33     struct particle is used to describe each particle displayed on screen
34 */
35 struct particle
36 {
37     GLfloat x;                  /* x position of particle */
38     GLfloat y;                  /* y position of particle */
39     GLubyte color[4];           /* rgba color of particle */
40     GLfloat size;               /* size of particle in pixels */
41     GLfloat xvel;               /* x velocity of particle in pixels per milesecond */
42     GLfloat yvel;               /* y velocity of particle in pixels per millescond */
43     int isActive;               /* if not active, then particle is overwritten */
44     enum particleType type;     /* see enum particleType */
45 } particles[MAX_PARTICLES];     /* this array holds all our particles */
46 
47 static int num_active_particles;        /* how many members of the particle array are actually being drawn / animated? */
48 static int screen_w, screen_h;
49 
50 /* function declarations */
51 void spawnTrailFromEmitter(struct particle *emitter);
52 void spawnEmitterParticle(GLfloat x, GLfloat y);
53 void explodeEmitter(struct particle *emitter);
54 void initializeParticles(void);
55 void initializeTexture();
56 int nextPowerOfTwo(int x);
57 void drawParticles();
58 void stepParticles(double deltaTime);
59 
60 /*  helper function (used in texture loading)
61     returns next power of two greater than or equal to x
62 */
63 int
nextPowerOfTwo(int x)64 nextPowerOfTwo(int x)
65 {
66     int val = 1;
67     while (val < x) {
68         val *= 2;
69     }
70     return val;
71 }
72 
73 /*
74     steps each active particle by timestep deltaTime
75 */
76 void
stepParticles(double deltaTime)77 stepParticles(double deltaTime)
78 {
79     float deltaMilliseconds = deltaTime * 1000;
80     int i;
81     struct particle *slot = particles;
82     struct particle *curr = particles;
83     for (i = 0; i < num_active_particles; i++) {
84         /* is the particle actually active, or is it marked for deletion? */
85         if (curr->isActive) {
86             /* is the particle off the screen? */
87             if (curr->y > screen_h)
88                 curr->isActive = 0;
89             else if (curr->y < 0)
90                 curr->isActive = 0;
91             if (curr->x > screen_w)
92                 curr->isActive = 0;
93             else if (curr->x < 0)
94                 curr->isActive = 0;
95 
96             /* step velocity, then step position */
97             curr->yvel += ACCEL * deltaMilliseconds;
98             curr->xvel += 0.0f;
99             curr->y += curr->yvel * deltaMilliseconds;
100             curr->x += curr->xvel * deltaMilliseconds;
101 
102             /* particle behavior */
103             if (curr->type == emitter) {
104                 /* if we're an emitter, spawn a trail */
105                 spawnTrailFromEmitter(curr);
106                 /* if we've reached our peak, explode */
107                 if (curr->yvel > 0.0) {
108                     explodeEmitter(curr);
109                 }
110             } else {
111                 float speed =
112                     sqrt(curr->xvel * curr->xvel + curr->yvel * curr->yvel);
113                 /*      if wind resistance is not powerful enough to stop us completely,
114                    then apply winde resistance, otherwise just stop us completely */
115                 if (WIND_RESISTANCE * deltaMilliseconds < speed) {
116                     float normx = curr->xvel / speed;
117                     float normy = curr->yvel / speed;
118                     curr->xvel -=
119                         normx * WIND_RESISTANCE * deltaMilliseconds;
120                     curr->yvel -=
121                         normy * WIND_RESISTANCE * deltaMilliseconds;
122                 } else {
123                     curr->xvel = curr->yvel = 0;        /* stop particle */
124                 }
125 
126                 if (curr->color[3] <= deltaMilliseconds * 0.1275f) {
127                     /* if this next step will cause us to fade out completely
128                        then just mark for deletion */
129                     curr->isActive = 0;
130                 } else {
131                     /* otherwise, let's fade a bit more */
132                     curr->color[3] -= deltaMilliseconds * 0.1275f;
133                 }
134 
135                 /* if we're a dust particle, shrink our size */
136                 if (curr->type == dust)
137                     curr->size -= deltaMilliseconds * 0.010f;
138 
139             }
140 
141             /* if we're still active, pack ourselves in the array next
142                to the last active guy (pack the array tightly) */
143             if (curr->isActive)
144                 *(slot++) = *curr;
145         }                       /* endif (curr->isActive) */
146         curr++;
147     }
148     /* the number of active particles is computed as the difference between
149        old number of active particles, where slot points, and the
150        new size of the array, where particles points */
151     num_active_particles = (int) (slot - particles);
152 }
153 
154 /*
155     This draws all the particles shown on screen
156 */
157 void
drawParticles()158 drawParticles()
159 {
160 
161     /* draw the background */
162     glClear(GL_COLOR_BUFFER_BIT);
163 
164     /* set up the position and color pointers */
165     glVertexPointer(2, GL_FLOAT, sizeof(struct particle), particles);
166     glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(struct particle),
167                    particles[0].color);
168 
169     if (pointSizeExtensionSupported) {
170         /* pass in our array of point sizes */
171         glPointSizePointerOES(GL_FLOAT, sizeof(struct particle),
172                               &(particles[0].size));
173     }
174 
175     /* draw our particles! */
176     glDrawArrays(GL_POINTS, 0, num_active_particles);
177 
178 }
179 
180 /*
181     This causes an emitter to explode in a circular bloom of dust particles
182 */
183 void
explodeEmitter(struct particle * emitter)184 explodeEmitter(struct particle *emitter)
185 {
186     /* first off, we're done with this particle, so turn active off */
187     emitter->isActive = 0;
188     int i;
189     for (i = 0; i < 200; i++) {
190 
191         if (num_active_particles >= MAX_PARTICLES)
192             return;
193 
194         /* come up with a random angle and speed for new particle */
195         float theta = randomFloat(0, 2.0f * 3.141592);
196         float exponent = 3.0f;
197         float speed = randomFloat(0.00, powf(0.17, exponent));
198         speed = powf(speed, 1.0f / exponent);
199 
200         /* select the particle at the end of our array */
201         struct particle *p = &particles[num_active_particles];
202 
203         /* set the particles properties */
204         p->xvel = speed * cos(theta);
205         p->yvel = speed * sin(theta);
206         p->x = emitter->x + emitter->xvel;
207         p->y = emitter->y + emitter->yvel;
208         p->isActive = 1;
209         p->type = dust;
210         p->size = 15 * pointSizeScale;
211         /* inherit emitter's color */
212         p->color[0] = emitter->color[0];
213         p->color[1] = emitter->color[1];
214         p->color[2] = emitter->color[2];
215         p->color[3] = 255;
216         /* our array has expanded at the end */
217         num_active_particles++;
218     }
219 
220 }
221 
222 /*
223     This spawns a trail particle from an emitter
224 */
225 void
spawnTrailFromEmitter(struct particle * emitter)226 spawnTrailFromEmitter(struct particle *emitter)
227 {
228 
229     if (num_active_particles >= MAX_PARTICLES)
230         return;
231 
232     /* select the particle at the slot at the end of our array */
233     struct particle *p = &particles[num_active_particles];
234 
235     /* set position and velocity to roughly that of the emitter */
236     p->x = emitter->x + randomFloat(-3.0, 3.0);
237     p->y = emitter->y + emitter->size / 2.0f;
238     p->xvel = emitter->xvel + randomFloat(-0.005, 0.005);
239     p->yvel = emitter->yvel + 0.1;
240 
241     /* set the color to a random-ish orangy type color */
242     p->color[0] = (0.8f + randomFloat(-0.1, 0.0)) * 255;
243     p->color[1] = (0.4f + randomFloat(-0.1, 0.1)) * 255;
244     p->color[2] = (0.0f + randomFloat(0.0, 0.2)) * 255;
245     p->color[3] = (0.7f) * 255;
246 
247     /* set other attributes */
248     p->size = 10 * pointSizeScale;
249     p->type = trail;
250     p->isActive = 1;
251 
252     /* our array has expanded at the end */
253     num_active_particles++;
254 
255 }
256 
257 /*
258     spawns a new emitter particle at the bottom of the screen
259     destined for the point (x,y).
260 */
261 void
spawnEmitterParticle(GLfloat x,GLfloat y)262 spawnEmitterParticle(GLfloat x, GLfloat y)
263 {
264 
265     if (num_active_particles >= MAX_PARTICLES)
266         return;
267 
268     /* find particle at endpoint of array */
269     struct particle *p = &particles[num_active_particles];
270 
271     /* set the color randomly */
272     switch (rand() % 4) {
273     case 0:
274         p->color[0] = 255;
275         p->color[1] = 100;
276         p->color[2] = 100;
277         break;
278     case 1:
279         p->color[0] = 100;
280         p->color[1] = 255;
281         p->color[2] = 100;
282         break;
283     case 2:
284         p->color[0] = 100;
285         p->color[1] = 100;
286         p->color[2] = 255;
287         break;
288     case 3:
289         p->color[0] = 255;
290         p->color[1] = 150;
291         p->color[2] = 50;
292         break;
293     }
294     p->color[3] = 255;
295     /* set position to (x, screen_h) */
296     p->x = x;
297     p->y = screen_h;
298     /* set velocity so that terminal point is (x,y) */
299     p->xvel = 0;
300     p->yvel = -sqrt(2 * ACCEL * (screen_h - y));
301     /* set other attributes */
302     p->size = 10 * pointSizeScale;
303     p->type = emitter;
304     p->isActive = 1;
305     /* our array has expanded at the end */
306     num_active_particles++;
307 }
308 
309 /* just sets the endpoint of the particle array to element zero */
310 void
initializeParticles(void)311 initializeParticles(void)
312 {
313     num_active_particles = 0;
314 }
315 
316 /*
317     loads the particle texture
318  */
319 void
initializeTexture()320 initializeTexture()
321 {
322 
323     int bpp;                    /* texture bits per pixel */
324     Uint32 Rmask, Gmask, Bmask, Amask;  /* masks for pixel format passed into OpenGL */
325     SDL_Surface *bmp_surface;   /* the bmp is loaded here */
326     SDL_Surface *bmp_surface_rgba8888;  /* this serves as a destination to convert the BMP
327                                            to format passed into OpenGL */
328 
329     bmp_surface = SDL_LoadBMP("stroke.bmp");
330     if (bmp_surface == NULL) {
331         fatalError("could not load stroke.bmp");
332     }
333 
334     /* Grab info about format that will be passed into OpenGL */
335     SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_ABGR8888, &bpp, &Rmask, &Gmask,
336                                &Bmask, &Amask);
337     /* Create surface that will hold pixels passed into OpenGL */
338     bmp_surface_rgba8888 =
339         SDL_CreateRGBSurface(0, bmp_surface->w, bmp_surface->h, bpp, Rmask,
340                              Gmask, Bmask, Amask);
341     /* Blit to this surface, effectively converting the format */
342     SDL_BlitSurface(bmp_surface, NULL, bmp_surface_rgba8888, NULL);
343 
344     glGenTextures(1, &particleTextureID);
345     glBindTexture(GL_TEXTURE_2D, particleTextureID);
346     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
347                  nextPowerOfTwo(bmp_surface->w),
348                  nextPowerOfTwo(bmp_surface->h),
349                  0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
350     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
351     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
352     /* this is where we actually pass in the pixel data */
353     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bmp_surface->w, bmp_surface->h, 0,
354                  GL_RGBA, GL_UNSIGNED_BYTE, bmp_surface_rgba8888->pixels);
355 
356     /* free bmp surface and converted bmp surface */
357     SDL_FreeSurface(bmp_surface);
358     SDL_FreeSurface(bmp_surface_rgba8888);
359 
360 }
361 
362 int
main(int argc,char * argv[])363 main(int argc, char *argv[])
364 {
365     SDL_Window *window;         /* main window */
366     SDL_GLContext context;
367     int drawableW, drawableH;
368     Uint32 startFrame;          /* time frame began to process */
369     Uint32 endFrame;            /* time frame ended processing */
370     Uint32 delay;               /* time to pause waiting to draw next frame */
371     int done;                   /* should we clean up and exit? */
372 
373     /* initialize SDL */
374     if (SDL_Init(SDL_INIT_VIDEO) < 0) {
375         fatalError("Could not initialize SDL");
376     }
377     /* seed the random number generator */
378     srand(time(NULL));
379     /*
380        request some OpenGL parameters
381        that may speed drawing
382      */
383     SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
384     SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
385     SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
386     SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
387     SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
388     SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);
389     SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
390 
391     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
392     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
393 
394     /* create main window and renderer */
395     window = SDL_CreateWindow(NULL, 0, 0, 320, 480,
396                                 SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI);
397     context = SDL_GL_CreateContext(window);
398 
399     /* The window size and drawable size may be different when highdpi is enabled,
400      * due to the increased pixel density of the drawable. */
401     SDL_GetWindowSize(window, &screen_w, &screen_h);
402     SDL_GL_GetDrawableSize(window, &drawableW, &drawableH);
403 
404     /* In OpenGL, point sizes are always in pixels. We don't want them looking
405      * tiny on a retina screen. */
406     pointSizeScale = (float) drawableH / (float) screen_h;
407 
408     /* load the particle texture */
409     initializeTexture();
410 
411     /*      check if GL_POINT_SIZE_ARRAY_OES is supported
412        this is used to give each particle its own size
413      */
414     pointSizeExtensionSupported =
415         SDL_GL_ExtensionSupported("GL_OES_point_size_array");
416 
417     /* set up some OpenGL state */
418     glDisable(GL_DEPTH_TEST);
419     glDisable(GL_CULL_FACE);
420 
421     glMatrixMode(GL_MODELVIEW);
422     glLoadIdentity();
423 
424     glViewport(0, 0, drawableW, drawableH);
425 
426     glMatrixMode(GL_PROJECTION);
427     glLoadIdentity();
428     glOrthof((GLfloat) 0,
429              (GLfloat) screen_w,
430              (GLfloat) screen_h,
431              (GLfloat) 0, 0.0, 1.0);
432 
433     glEnable(GL_TEXTURE_2D);
434     glEnable(GL_BLEND);
435     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
436     glEnableClientState(GL_VERTEX_ARRAY);
437     glEnableClientState(GL_COLOR_ARRAY);
438 
439     glEnable(GL_POINT_SPRITE_OES);
440     glTexEnvi(GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, 1);
441 
442     if (pointSizeExtensionSupported) {
443         /* we use this to set the sizes of all the particles */
444         glEnableClientState(GL_POINT_SIZE_ARRAY_OES);
445     } else {
446         /* if extension not available then all particles have size 10 */
447         glPointSize(10 * pointSizeScale);
448     }
449 
450     done = 0;
451     /* enter main loop */
452     while (!done) {
453         SDL_Event event;
454         double deltaTime = updateDeltaTime();
455         while (SDL_PollEvent(&event)) {
456             if (event.type == SDL_QUIT) {
457                 done = 1;
458             }
459             if (event.type == SDL_MOUSEBUTTONDOWN) {
460                 int x, y;
461                 SDL_GetMouseState(&x, &y);
462                 spawnEmitterParticle(x, y);
463             }
464         }
465         stepParticles(deltaTime);
466         drawParticles();
467         SDL_GL_SwapWindow(window);
468         SDL_Delay(1);
469     }
470 
471     /* delete textures */
472     glDeleteTextures(1, &particleTextureID);
473     /* shutdown SDL */
474     SDL_Quit();
475 
476     return 0;
477 }
478