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