• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //========================================================================
2 // A simple particle engine with threaded physics
3 // Copyright (c) Marcus Geelnard
4 // Copyright (c) Camilla Berglund <elmindreda@glfw.org>
5 //
6 // This software is provided 'as-is', without any express or implied
7 // warranty. In no event will the authors be held liable for any damages
8 // arising from the use of this software.
9 //
10 // Permission is granted to anyone to use this software for any purpose,
11 // including commercial applications, and to alter it and redistribute it
12 // freely, subject to the following restrictions:
13 //
14 // 1. The origin of this software must not be misrepresented; you must not
15 //    claim that you wrote the original software. If you use this software
16 //    in a product, an acknowledgment in the product documentation would
17 //    be appreciated but is not required.
18 //
19 // 2. Altered source versions must be plainly marked as such, and must not
20 //    be misrepresented as being the original software.
21 //
22 // 3. This notice may not be removed or altered from any source
23 //    distribution.
24 //
25 //========================================================================
26 
27 #if defined(_MSC_VER)
28  // Make MS math.h define M_PI
29  #define _USE_MATH_DEFINES
30 #endif
31 
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <math.h>
36 #include <time.h>
37 
38 #include <tinycthread.h>
39 #include <getopt.h>
40 #include <linmath.h>
41 
42 #include <glad/glad.h>
43 #include <GLFW/glfw3.h>
44 
45 // Define tokens for GL_EXT_separate_specular_color if not already defined
46 #ifndef GL_EXT_separate_specular_color
47 #define GL_LIGHT_MODEL_COLOR_CONTROL_EXT  0x81F8
48 #define GL_SINGLE_COLOR_EXT               0x81F9
49 #define GL_SEPARATE_SPECULAR_COLOR_EXT    0x81FA
50 #endif // GL_EXT_separate_specular_color
51 
52 
53 //========================================================================
54 // Type definitions
55 //========================================================================
56 
57 typedef struct
58 {
59     float x, y, z;
60 } Vec3;
61 
62 // This structure is used for interleaved vertex arrays (see the
63 // draw_particles function)
64 //
65 // NOTE: This structure SHOULD be packed on most systems. It uses 32-bit fields
66 // on 32-bit boundaries, and is a multiple of 64 bits in total (6x32=3x64). If
67 // it does not work, try using pragmas or whatever to force the structure to be
68 // packed.
69 typedef struct
70 {
71     GLfloat s, t;         // Texture coordinates
72     GLuint  rgba;         // Color (four ubytes packed into an uint)
73     GLfloat x, y, z;      // Vertex coordinates
74 } Vertex;
75 
76 
77 //========================================================================
78 // Program control global variables
79 //========================================================================
80 
81 // Window dimensions
82 float aspect_ratio;
83 
84 // "wireframe" flag (true if we use wireframe view)
85 int wireframe;
86 
87 // Thread synchronization
88 struct {
89     double    t;         // Time (s)
90     float     dt;        // Time since last frame (s)
91     int       p_frame;   // Particle physics frame number
92     int       d_frame;   // Particle draw frame number
93     cnd_t     p_done;    // Condition: particle physics done
94     cnd_t     d_done;    // Condition: particle draw done
95     mtx_t     particles_lock; // Particles data sharing mutex
96 } thread_sync;
97 
98 
99 //========================================================================
100 // Texture declarations (we hard-code them into the source code, since
101 // they are so simple)
102 //========================================================================
103 
104 #define P_TEX_WIDTH  8    // Particle texture dimensions
105 #define P_TEX_HEIGHT 8
106 #define F_TEX_WIDTH  16   // Floor texture dimensions
107 #define F_TEX_HEIGHT 16
108 
109 // Texture object IDs
110 GLuint particle_tex_id, floor_tex_id;
111 
112 // Particle texture (a simple spot)
113 const unsigned char particle_texture[ P_TEX_WIDTH * P_TEX_HEIGHT ] = {
114     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
115     0x00, 0x00, 0x11, 0x22, 0x22, 0x11, 0x00, 0x00,
116     0x00, 0x11, 0x33, 0x88, 0x77, 0x33, 0x11, 0x00,
117     0x00, 0x22, 0x88, 0xff, 0xee, 0x77, 0x22, 0x00,
118     0x00, 0x22, 0x77, 0xee, 0xff, 0x88, 0x22, 0x00,
119     0x00, 0x11, 0x33, 0x77, 0x88, 0x33, 0x11, 0x00,
120     0x00, 0x00, 0x11, 0x33, 0x22, 0x11, 0x00, 0x00,
121     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
122 };
123 
124 // Floor texture (your basic checkered floor)
125 const unsigned char floor_texture[ F_TEX_WIDTH * F_TEX_HEIGHT ] = {
126     0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
127     0xff, 0xf0, 0xcc, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
128     0xf0, 0xcc, 0xee, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0x66, 0x30, 0x30, 0x30, 0x20, 0x30, 0x30,
129     0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xee, 0xf0, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
130     0xf0, 0xf0, 0xf0, 0xf0, 0xcc, 0xf0, 0xf0, 0xf0, 0x30, 0x30, 0x55, 0x30, 0x30, 0x44, 0x30, 0x30,
131     0xf0, 0xdd, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
132     0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x60, 0x30,
133     0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x33, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
134     0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
135     0x30, 0x30, 0x30, 0x30, 0x30, 0x20, 0x30, 0x30, 0xf0, 0xff, 0xf0, 0xf0, 0xdd, 0xf0, 0xf0, 0xff,
136     0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x55, 0x33, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0xf0,
137     0x30, 0x44, 0x66, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
138     0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xaa, 0xf0, 0xf0, 0xcc, 0xf0,
139     0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xff, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0xdd, 0xf0,
140     0x30, 0x30, 0x30, 0x77, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
141     0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
142 };
143 
144 
145 //========================================================================
146 // These are fixed constants that control the particle engine. In a
147 // modular world, these values should be variables...
148 //========================================================================
149 
150 // Maximum number of particles
151 #define MAX_PARTICLES   3000
152 
153 // Life span of a particle (in seconds)
154 #define LIFE_SPAN       8.f
155 
156 // A new particle is born every [BIRTH_INTERVAL] second
157 #define BIRTH_INTERVAL (LIFE_SPAN/(float)MAX_PARTICLES)
158 
159 // Particle size (meters)
160 #define PARTICLE_SIZE   0.7f
161 
162 // Gravitational constant (m/s^2)
163 #define GRAVITY         9.8f
164 
165 // Base initial velocity (m/s)
166 #define VELOCITY        8.f
167 
168 // Bounce friction (1.0 = no friction, 0.0 = maximum friction)
169 #define FRICTION        0.75f
170 
171 // "Fountain" height (m)
172 #define FOUNTAIN_HEIGHT 3.f
173 
174 // Fountain radius (m)
175 #define FOUNTAIN_RADIUS 1.6f
176 
177 // Minimum delta-time for particle phisics (s)
178 #define MIN_DELTA_T     (BIRTH_INTERVAL * 0.5f)
179 
180 
181 //========================================================================
182 // Particle system global variables
183 //========================================================================
184 
185 // This structure holds all state for a single particle
186 typedef struct {
187     float x,y,z;     // Position in space
188     float vx,vy,vz;  // Velocity vector
189     float r,g,b;     // Color of particle
190     float life;      // Life of particle (1.0 = newborn, < 0.0 = dead)
191     int   active;    // Tells if this particle is active
192 } PARTICLE;
193 
194 // Global vectors holding all particles. We use two vectors for double
195 // buffering.
196 static PARTICLE particles[MAX_PARTICLES];
197 
198 // Global variable holding the age of the youngest particle
199 static float min_age;
200 
201 // Color of latest born particle (used for fountain lighting)
202 static float glow_color[4];
203 
204 // Position of latest born particle (used for fountain lighting)
205 static float glow_pos[4];
206 
207 
208 //========================================================================
209 // Object material and fog configuration constants
210 //========================================================================
211 
212 const GLfloat fountain_diffuse[4]  = { 0.7f, 1.f,  1.f,  1.f };
213 const GLfloat fountain_specular[4] = {  1.f, 1.f,  1.f,  1.f };
214 const GLfloat fountain_shininess   = 12.f;
215 const GLfloat floor_diffuse[4]     = { 1.f,  0.6f, 0.6f, 1.f };
216 const GLfloat floor_specular[4]    = { 0.6f, 0.6f, 0.6f, 1.f };
217 const GLfloat floor_shininess      = 18.f;
218 const GLfloat fog_color[4]         = { 0.1f, 0.1f, 0.1f, 1.f };
219 
220 
221 //========================================================================
222 // Print usage information
223 //========================================================================
224 
usage(void)225 static void usage(void)
226 {
227     printf("Usage: particles [-bfhs]\n");
228     printf("Options:\n");
229     printf(" -f   Run in full screen\n");
230     printf(" -h   Display this help\n");
231     printf(" -s   Run program as single thread (default is to use two threads)\n");
232     printf("\n");
233     printf("Program runtime controls:\n");
234     printf(" W    Toggle wireframe mode\n");
235     printf(" Esc  Exit program\n");
236 }
237 
238 
239 //========================================================================
240 // Initialize a new particle
241 //========================================================================
242 
init_particle(PARTICLE * p,double t)243 static void init_particle(PARTICLE *p, double t)
244 {
245     float xy_angle, velocity;
246 
247     // Start position of particle is at the fountain blow-out
248     p->x = 0.f;
249     p->y = 0.f;
250     p->z = FOUNTAIN_HEIGHT;
251 
252     // Start velocity is up (Z)...
253     p->vz = 0.7f + (0.3f / 4096.f) * (float) (rand() & 4095);
254 
255     // ...and a randomly chosen X/Y direction
256     xy_angle = (2.f * (float) M_PI / 4096.f) * (float) (rand() & 4095);
257     p->vx = 0.4f * (float) cos(xy_angle);
258     p->vy = 0.4f * (float) sin(xy_angle);
259 
260     // Scale velocity vector according to a time-varying velocity
261     velocity = VELOCITY * (0.8f + 0.1f * (float) (sin(0.5 * t) + sin(1.31 * t)));
262     p->vx *= velocity;
263     p->vy *= velocity;
264     p->vz *= velocity;
265 
266     // Color is time-varying
267     p->r = 0.7f + 0.3f * (float) sin(0.34 * t + 0.1);
268     p->g = 0.6f + 0.4f * (float) sin(0.63 * t + 1.1);
269     p->b = 0.6f + 0.4f * (float) sin(0.91 * t + 2.1);
270 
271     // Store settings for fountain glow lighting
272     glow_pos[0] = 0.4f * (float) sin(1.34 * t);
273     glow_pos[1] = 0.4f * (float) sin(3.11 * t);
274     glow_pos[2] = FOUNTAIN_HEIGHT + 1.f;
275     glow_pos[3] = 1.f;
276     glow_color[0] = p->r;
277     glow_color[1] = p->g;
278     glow_color[2] = p->b;
279     glow_color[3] = 1.f;
280 
281     // The particle is new-born and active
282     p->life = 1.f;
283     p->active = 1;
284 }
285 
286 
287 //========================================================================
288 // Update a particle
289 //========================================================================
290 
291 #define FOUNTAIN_R2 (FOUNTAIN_RADIUS+PARTICLE_SIZE/2)*(FOUNTAIN_RADIUS+PARTICLE_SIZE/2)
292 
update_particle(PARTICLE * p,float dt)293 static void update_particle(PARTICLE *p, float dt)
294 {
295     // If the particle is not active, we need not do anything
296     if (!p->active)
297         return;
298 
299     // The particle is getting older...
300     p->life -= dt * (1.f / LIFE_SPAN);
301 
302     // Did the particle die?
303     if (p->life <= 0.f)
304     {
305         p->active = 0;
306         return;
307     }
308 
309     // Apply gravity
310     p->vz = p->vz - GRAVITY * dt;
311 
312     // Update particle position
313     p->x = p->x + p->vx * dt;
314     p->y = p->y + p->vy * dt;
315     p->z = p->z + p->vz * dt;
316 
317     // Simple collision detection + response
318     if (p->vz < 0.f)
319     {
320         // Particles should bounce on the fountain (with friction)
321         if ((p->x * p->x + p->y * p->y) < FOUNTAIN_R2 &&
322             p->z < (FOUNTAIN_HEIGHT + PARTICLE_SIZE / 2))
323         {
324             p->vz = -FRICTION * p->vz;
325             p->z  = FOUNTAIN_HEIGHT + PARTICLE_SIZE / 2 +
326                     FRICTION * (FOUNTAIN_HEIGHT +
327                     PARTICLE_SIZE / 2 - p->z);
328         }
329 
330         // Particles should bounce on the floor (with friction)
331         else if (p->z < PARTICLE_SIZE / 2)
332         {
333             p->vz = -FRICTION * p->vz;
334             p->z  = PARTICLE_SIZE / 2 +
335                     FRICTION * (PARTICLE_SIZE / 2 - p->z);
336         }
337     }
338 }
339 
340 
341 //========================================================================
342 // The main frame for the particle engine. Called once per frame.
343 //========================================================================
344 
particle_engine(double t,float dt)345 static void particle_engine(double t, float dt)
346 {
347     int i;
348     float dt2;
349 
350     // Update particles (iterated several times per frame if dt is too large)
351     while (dt > 0.f)
352     {
353         // Calculate delta time for this iteration
354         dt2 = dt < MIN_DELTA_T ? dt : MIN_DELTA_T;
355 
356         for (i = 0;  i < MAX_PARTICLES;  i++)
357             update_particle(&particles[i], dt2);
358 
359         min_age += dt2;
360 
361         // Should we create any new particle(s)?
362         while (min_age >= BIRTH_INTERVAL)
363         {
364             min_age -= BIRTH_INTERVAL;
365 
366             // Find a dead particle to replace with a new one
367             for (i = 0;  i < MAX_PARTICLES;  i++)
368             {
369                 if (!particles[i].active)
370                 {
371                     init_particle(&particles[i], t + min_age);
372                     update_particle(&particles[i], min_age);
373                     break;
374                 }
375             }
376         }
377 
378         dt -= dt2;
379     }
380 }
381 
382 
383 //========================================================================
384 // Draw all active particles. We use OpenGL 1.1 vertex
385 // arrays for this in order to accelerate the drawing.
386 //========================================================================
387 
388 #define BATCH_PARTICLES 70  // Number of particles to draw in each batch
389                             // (70 corresponds to 7.5 KB = will not blow
390                             // the L1 data cache on most CPUs)
391 #define PARTICLE_VERTS  4   // Number of vertices per particle
392 
draw_particles(GLFWwindow * window,double t,float dt)393 static void draw_particles(GLFWwindow* window, double t, float dt)
394 {
395     int i, particle_count;
396     Vertex vertex_array[BATCH_PARTICLES * PARTICLE_VERTS];
397     Vertex* vptr;
398     float alpha;
399     GLuint rgba;
400     Vec3 quad_lower_left, quad_lower_right;
401     GLfloat mat[16];
402     PARTICLE* pptr;
403 
404     // Here comes the real trick with flat single primitive objects (s.c.
405     // "billboards"): We must rotate the textured primitive so that it
406     // always faces the viewer (is coplanar with the view-plane).
407     // We:
408     //   1) Create the primitive around origo (0,0,0)
409     //   2) Rotate it so that it is coplanar with the view plane
410     //   3) Translate it according to the particle position
411     // Note that 1) and 2) is the same for all particles (done only once).
412 
413     // Get modelview matrix. We will only use the upper left 3x3 part of
414     // the matrix, which represents the rotation.
415     glGetFloatv(GL_MODELVIEW_MATRIX, mat);
416 
417     // 1) & 2) We do it in one swift step:
418     // Although not obvious, the following six lines represent two matrix/
419     // vector multiplications. The matrix is the inverse 3x3 rotation
420     // matrix (i.e. the transpose of the same matrix), and the two vectors
421     // represent the lower left corner of the quad, PARTICLE_SIZE/2 *
422     // (-1,-1,0), and the lower right corner, PARTICLE_SIZE/2 * (1,-1,0).
423     // The upper left/right corners of the quad is always the negative of
424     // the opposite corners (regardless of rotation).
425     quad_lower_left.x = (-PARTICLE_SIZE / 2) * (mat[0] + mat[1]);
426     quad_lower_left.y = (-PARTICLE_SIZE / 2) * (mat[4] + mat[5]);
427     quad_lower_left.z = (-PARTICLE_SIZE / 2) * (mat[8] + mat[9]);
428     quad_lower_right.x = (PARTICLE_SIZE / 2) * (mat[0] - mat[1]);
429     quad_lower_right.y = (PARTICLE_SIZE / 2) * (mat[4] - mat[5]);
430     quad_lower_right.z = (PARTICLE_SIZE / 2) * (mat[8] - mat[9]);
431 
432     // Don't update z-buffer, since all particles are transparent!
433     glDepthMask(GL_FALSE);
434 
435     glEnable(GL_BLEND);
436     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
437 
438     // Select particle texture
439     if (!wireframe)
440     {
441         glEnable(GL_TEXTURE_2D);
442         glBindTexture(GL_TEXTURE_2D, particle_tex_id);
443     }
444 
445     // Set up vertex arrays. We use interleaved arrays, which is easier to
446     // handle (in most situations) and it gives a linear memeory access
447     // access pattern (which may give better performance in some
448     // situations). GL_T2F_C4UB_V3F means: 2 floats for texture coords,
449     // 4 ubytes for color and 3 floats for vertex coord (in that order).
450     // Most OpenGL cards / drivers are optimized for this format.
451     glInterleavedArrays(GL_T2F_C4UB_V3F, 0, vertex_array);
452 
453     // Wait for particle physics thread to be done
454     mtx_lock(&thread_sync.particles_lock);
455     while (!glfwWindowShouldClose(window) &&
456             thread_sync.p_frame <= thread_sync.d_frame)
457     {
458         struct timespec ts;
459         clock_gettime(CLOCK_REALTIME, &ts);
460         ts.tv_nsec += 100000000;
461         cnd_timedwait(&thread_sync.p_done, &thread_sync.particles_lock, &ts);
462     }
463 
464     // Store the frame time and delta time for the physics thread
465     thread_sync.t = t;
466     thread_sync.dt = dt;
467 
468     // Update frame counter
469     thread_sync.d_frame++;
470 
471     // Loop through all particles and build vertex arrays.
472     particle_count = 0;
473     vptr = vertex_array;
474     pptr = particles;
475 
476     for (i = 0;  i < MAX_PARTICLES;  i++)
477     {
478         if (pptr->active)
479         {
480             // Calculate particle intensity (we set it to max during 75%
481             // of its life, then it fades out)
482             alpha =  4.f * pptr->life;
483             if (alpha > 1.f)
484                 alpha = 1.f;
485 
486             // Convert color from float to 8-bit (store it in a 32-bit
487             // integer using endian independent type casting)
488             ((GLubyte*) &rgba)[0] = (GLubyte)(pptr->r * 255.f);
489             ((GLubyte*) &rgba)[1] = (GLubyte)(pptr->g * 255.f);
490             ((GLubyte*) &rgba)[2] = (GLubyte)(pptr->b * 255.f);
491             ((GLubyte*) &rgba)[3] = (GLubyte)(alpha * 255.f);
492 
493             // 3) Translate the quad to the correct position in modelview
494             // space and store its parameters in vertex arrays (we also
495             // store texture coord and color information for each vertex).
496 
497             // Lower left corner
498             vptr->s    = 0.f;
499             vptr->t    = 0.f;
500             vptr->rgba = rgba;
501             vptr->x    = pptr->x + quad_lower_left.x;
502             vptr->y    = pptr->y + quad_lower_left.y;
503             vptr->z    = pptr->z + quad_lower_left.z;
504             vptr ++;
505 
506             // Lower right corner
507             vptr->s    = 1.f;
508             vptr->t    = 0.f;
509             vptr->rgba = rgba;
510             vptr->x    = pptr->x + quad_lower_right.x;
511             vptr->y    = pptr->y + quad_lower_right.y;
512             vptr->z    = pptr->z + quad_lower_right.z;
513             vptr ++;
514 
515             // Upper right corner
516             vptr->s    = 1.f;
517             vptr->t    = 1.f;
518             vptr->rgba = rgba;
519             vptr->x    = pptr->x - quad_lower_left.x;
520             vptr->y    = pptr->y - quad_lower_left.y;
521             vptr->z    = pptr->z - quad_lower_left.z;
522             vptr ++;
523 
524             // Upper left corner
525             vptr->s    = 0.f;
526             vptr->t    = 1.f;
527             vptr->rgba = rgba;
528             vptr->x    = pptr->x - quad_lower_right.x;
529             vptr->y    = pptr->y - quad_lower_right.y;
530             vptr->z    = pptr->z - quad_lower_right.z;
531             vptr ++;
532 
533             // Increase count of drawable particles
534             particle_count ++;
535         }
536 
537         // If we have filled up one batch of particles, draw it as a set
538         // of quads using glDrawArrays.
539         if (particle_count >= BATCH_PARTICLES)
540         {
541             // The first argument tells which primitive type we use (QUAD)
542             // The second argument tells the index of the first vertex (0)
543             // The last argument is the vertex count
544             glDrawArrays(GL_QUADS, 0, PARTICLE_VERTS * particle_count);
545             particle_count = 0;
546             vptr = vertex_array;
547         }
548 
549         // Next particle
550         pptr++;
551     }
552 
553     // We are done with the particle data
554     mtx_unlock(&thread_sync.particles_lock);
555     cnd_signal(&thread_sync.d_done);
556 
557     // Draw final batch of particles (if any)
558     glDrawArrays(GL_QUADS, 0, PARTICLE_VERTS * particle_count);
559 
560     // Disable vertex arrays (Note: glInterleavedArrays implicitly called
561     // glEnableClientState for vertex, texture coord and color arrays)
562     glDisableClientState(GL_VERTEX_ARRAY);
563     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
564     glDisableClientState(GL_COLOR_ARRAY);
565 
566     glDisable(GL_TEXTURE_2D);
567     glDisable(GL_BLEND);
568 
569     glDepthMask(GL_TRUE);
570 }
571 
572 
573 //========================================================================
574 // Fountain geometry specification
575 //========================================================================
576 
577 #define FOUNTAIN_SIDE_POINTS 14
578 #define FOUNTAIN_SWEEP_STEPS 32
579 
580 static const float fountain_side[FOUNTAIN_SIDE_POINTS * 2] =
581 {
582     1.2f, 0.f,  1.f, 0.2f,  0.41f, 0.3f, 0.4f, 0.35f,
583     0.4f, 1.95f, 0.41f, 2.f, 0.8f, 2.2f,  1.2f, 2.4f,
584     1.5f, 2.7f,  1.55f,2.95f, 1.6f, 3.f,  1.f, 3.f,
585     0.5f, 3.f,  0.f, 3.f
586 };
587 
588 static const float fountain_normal[FOUNTAIN_SIDE_POINTS * 2] =
589 {
590     1.0000f, 0.0000f,  0.6428f, 0.7660f,  0.3420f, 0.9397f,  1.0000f, 0.0000f,
591     1.0000f, 0.0000f,  0.3420f,-0.9397f,  0.4226f,-0.9063f,  0.5000f,-0.8660f,
592     0.7660f,-0.6428f,  0.9063f,-0.4226f,  0.0000f,1.00000f,  0.0000f,1.00000f,
593     0.0000f,1.00000f,  0.0000f,1.00000f
594 };
595 
596 
597 //========================================================================
598 // Draw a fountain
599 //========================================================================
600 
draw_fountain(void)601 static void draw_fountain(void)
602 {
603     static GLuint fountain_list = 0;
604     double angle;
605     float  x, y;
606     int m, n;
607 
608     // The first time, we build the fountain display list
609     if (!fountain_list)
610     {
611         fountain_list = glGenLists(1);
612         glNewList(fountain_list, GL_COMPILE_AND_EXECUTE);
613 
614         glMaterialfv(GL_FRONT, GL_DIFFUSE, fountain_diffuse);
615         glMaterialfv(GL_FRONT, GL_SPECULAR, fountain_specular);
616         glMaterialf(GL_FRONT, GL_SHININESS, fountain_shininess);
617 
618         // Build fountain using triangle strips
619         for (n = 0;  n < FOUNTAIN_SIDE_POINTS - 1;  n++)
620         {
621             glBegin(GL_TRIANGLE_STRIP);
622             for (m = 0;  m <= FOUNTAIN_SWEEP_STEPS;  m++)
623             {
624                 angle = (double) m * (2.0 * M_PI / (double) FOUNTAIN_SWEEP_STEPS);
625                 x = (float) cos(angle);
626                 y = (float) sin(angle);
627 
628                 // Draw triangle strip
629                 glNormal3f(x * fountain_normal[n * 2 + 2],
630                            y * fountain_normal[n * 2 + 2],
631                            fountain_normal[n * 2 + 3]);
632                 glVertex3f(x * fountain_side[n * 2 + 2],
633                            y * fountain_side[n * 2 + 2],
634                            fountain_side[n * 2 +3 ]);
635                 glNormal3f(x * fountain_normal[n * 2],
636                            y * fountain_normal[n * 2],
637                            fountain_normal[n * 2 + 1]);
638                 glVertex3f(x * fountain_side[n * 2],
639                            y * fountain_side[n * 2],
640                            fountain_side[n * 2 + 1]);
641             }
642 
643             glEnd();
644         }
645 
646         glEndList();
647     }
648     else
649         glCallList(fountain_list);
650 }
651 
652 
653 //========================================================================
654 // Recursive function for building variable tesselated floor
655 //========================================================================
656 
tessellate_floor(float x1,float y1,float x2,float y2,int depth)657 static void tessellate_floor(float x1, float y1, float x2, float y2, int depth)
658 {
659     float delta, x, y;
660 
661     // Last recursion?
662     if (depth >= 5)
663         delta = 999999.f;
664     else
665     {
666         x = (float) (fabs(x1) < fabs(x2) ? fabs(x1) : fabs(x2));
667         y = (float) (fabs(y1) < fabs(y2) ? fabs(y1) : fabs(y2));
668         delta = x*x + y*y;
669     }
670 
671     // Recurse further?
672     if (delta < 0.1f)
673     {
674         x = (x1 + x2) * 0.5f;
675         y = (y1 + y2) * 0.5f;
676         tessellate_floor(x1, y1,  x,  y, depth + 1);
677         tessellate_floor(x, y1, x2,  y, depth + 1);
678         tessellate_floor(x1,  y,  x, y2, depth + 1);
679         tessellate_floor(x,  y, x2, y2, depth + 1);
680     }
681     else
682     {
683         glTexCoord2f(x1 * 30.f, y1 * 30.f);
684         glVertex3f(  x1 * 80.f, y1 * 80.f, 0.f);
685         glTexCoord2f(x2 * 30.f, y1 * 30.f);
686         glVertex3f(  x2 * 80.f, y1 * 80.f, 0.f);
687         glTexCoord2f(x2 * 30.f, y2 * 30.f);
688         glVertex3f(  x2 * 80.f, y2 * 80.f, 0.f);
689         glTexCoord2f(x1 * 30.f, y2 * 30.f);
690         glVertex3f(  x1 * 80.f, y2 * 80.f, 0.f);
691     }
692 }
693 
694 
695 //========================================================================
696 // Draw floor. We build the floor recursively and let the tessellation in the
697 // center (near x,y=0,0) be high, while the tessellation around the edges be
698 // low.
699 //========================================================================
700 
draw_floor(void)701 static void draw_floor(void)
702 {
703     static GLuint floor_list = 0;
704 
705     if (!wireframe)
706     {
707         glEnable(GL_TEXTURE_2D);
708         glBindTexture(GL_TEXTURE_2D, floor_tex_id);
709     }
710 
711     // The first time, we build the floor display list
712     if (!floor_list)
713     {
714         floor_list = glGenLists(1);
715         glNewList(floor_list, GL_COMPILE_AND_EXECUTE);
716 
717         glMaterialfv(GL_FRONT, GL_DIFFUSE, floor_diffuse);
718         glMaterialfv(GL_FRONT, GL_SPECULAR, floor_specular);
719         glMaterialf(GL_FRONT, GL_SHININESS, floor_shininess);
720 
721         // Draw floor as a bunch of triangle strips (high tesselation
722         // improves lighting)
723         glNormal3f(0.f, 0.f, 1.f);
724         glBegin(GL_QUADS);
725         tessellate_floor(-1.f, -1.f, 0.f, 0.f, 0);
726         tessellate_floor( 0.f, -1.f, 1.f, 0.f, 0);
727         tessellate_floor( 0.f,  0.f, 1.f, 1.f, 0);
728         tessellate_floor(-1.f,  0.f, 0.f, 1.f, 0);
729         glEnd();
730 
731         glEndList();
732     }
733     else
734         glCallList(floor_list);
735 
736     glDisable(GL_TEXTURE_2D);
737 
738 }
739 
740 
741 //========================================================================
742 // Position and configure light sources
743 //========================================================================
744 
setup_lights(void)745 static void setup_lights(void)
746 {
747     float l1pos[4], l1amb[4], l1dif[4], l1spec[4];
748     float l2pos[4], l2amb[4], l2dif[4], l2spec[4];
749 
750     // Set light source 1 parameters
751     l1pos[0] =  0.f;  l1pos[1] = -9.f; l1pos[2] =   8.f;  l1pos[3] = 1.f;
752     l1amb[0] = 0.2f;  l1amb[1] = 0.2f;  l1amb[2] = 0.2f;  l1amb[3] = 1.f;
753     l1dif[0] = 0.8f;  l1dif[1] = 0.4f;  l1dif[2] = 0.2f;  l1dif[3] = 1.f;
754     l1spec[0] = 1.f; l1spec[1] = 0.6f; l1spec[2] = 0.2f; l1spec[3] = 0.f;
755 
756     // Set light source 2 parameters
757     l2pos[0] =  -15.f; l2pos[1] =  12.f; l2pos[2] = 1.5f; l2pos[3] =  1.f;
758     l2amb[0] =    0.f; l2amb[1] =   0.f; l2amb[2] =  0.f; l2amb[3] =  1.f;
759     l2dif[0] =   0.2f; l2dif[1] =  0.4f; l2dif[2] = 0.8f; l2dif[3] =  1.f;
760     l2spec[0] =  0.2f; l2spec[1] = 0.6f; l2spec[2] = 1.f; l2spec[3] = 0.f;
761 
762     glLightfv(GL_LIGHT1, GL_POSITION, l1pos);
763     glLightfv(GL_LIGHT1, GL_AMBIENT, l1amb);
764     glLightfv(GL_LIGHT1, GL_DIFFUSE, l1dif);
765     glLightfv(GL_LIGHT1, GL_SPECULAR, l1spec);
766     glLightfv(GL_LIGHT2, GL_POSITION, l2pos);
767     glLightfv(GL_LIGHT2, GL_AMBIENT, l2amb);
768     glLightfv(GL_LIGHT2, GL_DIFFUSE, l2dif);
769     glLightfv(GL_LIGHT2, GL_SPECULAR, l2spec);
770     glLightfv(GL_LIGHT3, GL_POSITION, glow_pos);
771     glLightfv(GL_LIGHT3, GL_DIFFUSE, glow_color);
772     glLightfv(GL_LIGHT3, GL_SPECULAR, glow_color);
773 
774     glEnable(GL_LIGHT1);
775     glEnable(GL_LIGHT2);
776     glEnable(GL_LIGHT3);
777 }
778 
779 
780 //========================================================================
781 // Main rendering function
782 //========================================================================
783 
draw_scene(GLFWwindow * window,double t)784 static void draw_scene(GLFWwindow* window, double t)
785 {
786     double xpos, ypos, zpos, angle_x, angle_y, angle_z;
787     static double t_old = 0.0;
788     float dt;
789     mat4x4 projection;
790 
791     // Calculate frame-to-frame delta time
792     dt = (float) (t - t_old);
793     t_old = t;
794 
795     mat4x4_perspective(projection,
796                        65.f * (float) M_PI / 180.f,
797                        aspect_ratio,
798                        1.0, 60.0);
799 
800     glClearColor(0.1f, 0.1f, 0.1f, 1.f);
801     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
802 
803     glMatrixMode(GL_PROJECTION);
804     glLoadMatrixf((const GLfloat*) projection);
805 
806     // Setup camera
807     glMatrixMode(GL_MODELVIEW);
808     glLoadIdentity();
809 
810     // Rotate camera
811     angle_x = 90.0 - 10.0;
812     angle_y = 10.0 * sin(0.3 * t);
813     angle_z = 10.0 * t;
814     glRotated(-angle_x, 1.0, 0.0, 0.0);
815     glRotated(-angle_y, 0.0, 1.0, 0.0);
816     glRotated(-angle_z, 0.0, 0.0, 1.0);
817 
818     // Translate camera
819     xpos =  15.0 * sin((M_PI / 180.0) * angle_z) +
820              2.0 * sin((M_PI / 180.0) * 3.1 * t);
821     ypos = -15.0 * cos((M_PI / 180.0) * angle_z) +
822              2.0 * cos((M_PI / 180.0) * 2.9 * t);
823     zpos = 4.0 + 2.0 * cos((M_PI / 180.0) * 4.9 * t);
824     glTranslated(-xpos, -ypos, -zpos);
825 
826     glFrontFace(GL_CCW);
827     glCullFace(GL_BACK);
828     glEnable(GL_CULL_FACE);
829 
830     setup_lights();
831     glEnable(GL_LIGHTING);
832 
833     glEnable(GL_FOG);
834     glFogi(GL_FOG_MODE, GL_EXP);
835     glFogf(GL_FOG_DENSITY, 0.05f);
836     glFogfv(GL_FOG_COLOR, fog_color);
837 
838     draw_floor();
839 
840     glEnable(GL_DEPTH_TEST);
841     glDepthFunc(GL_LEQUAL);
842     glDepthMask(GL_TRUE);
843 
844     draw_fountain();
845 
846     glDisable(GL_LIGHTING);
847     glDisable(GL_FOG);
848 
849     // Particles must be drawn after all solid objects have been drawn
850     draw_particles(window, t, dt);
851 
852     // Z-buffer not needed anymore
853     glDisable(GL_DEPTH_TEST);
854 }
855 
856 
857 //========================================================================
858 // Window resize callback function
859 //========================================================================
860 
resize_callback(GLFWwindow * window,int width,int height)861 static void resize_callback(GLFWwindow* window, int width, int height)
862 {
863     glViewport(0, 0, width, height);
864     aspect_ratio = height ? width / (float) height : 1.f;
865 }
866 
867 
868 //========================================================================
869 // Key callback functions
870 //========================================================================
871 
key_callback(GLFWwindow * window,int key,int scancode,int action,int mods)872 static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
873 {
874     if (action == GLFW_PRESS)
875     {
876         switch (key)
877         {
878             case GLFW_KEY_ESCAPE:
879                 glfwSetWindowShouldClose(window, GLFW_TRUE);
880                 break;
881             case GLFW_KEY_W:
882                 wireframe = !wireframe;
883                 glPolygonMode(GL_FRONT_AND_BACK,
884                               wireframe ? GL_LINE : GL_FILL);
885                 break;
886             default:
887                 break;
888         }
889     }
890 }
891 
892 
893 //========================================================================
894 // Thread for updating particle physics
895 //========================================================================
896 
physics_thread_main(void * arg)897 static int physics_thread_main(void* arg)
898 {
899     GLFWwindow* window = arg;
900 
901     for (;;)
902     {
903         mtx_lock(&thread_sync.particles_lock);
904 
905         // Wait for particle drawing to be done
906         while (!glfwWindowShouldClose(window) &&
907                thread_sync.p_frame > thread_sync.d_frame)
908         {
909             struct timespec ts;
910             clock_gettime(CLOCK_REALTIME, &ts);
911             ts.tv_nsec += 100000000;
912             cnd_timedwait(&thread_sync.d_done, &thread_sync.particles_lock, &ts);
913         }
914 
915         if (glfwWindowShouldClose(window))
916             break;
917 
918         // Update particles
919         particle_engine(thread_sync.t, thread_sync.dt);
920 
921         // Update frame counter
922         thread_sync.p_frame++;
923 
924         // Unlock mutex and signal drawing thread
925         mtx_unlock(&thread_sync.particles_lock);
926         cnd_signal(&thread_sync.p_done);
927     }
928 
929     return 0;
930 }
931 
932 
933 //========================================================================
934 // main
935 //========================================================================
936 
main(int argc,char ** argv)937 int main(int argc, char** argv)
938 {
939     int ch, width, height;
940     thrd_t physics_thread = 0;
941     GLFWwindow* window;
942     GLFWmonitor* monitor = NULL;
943 
944     if (!glfwInit())
945     {
946         fprintf(stderr, "Failed to initialize GLFW\n");
947         exit(EXIT_FAILURE);
948     }
949 
950     while ((ch = getopt(argc, argv, "fh")) != -1)
951     {
952         switch (ch)
953         {
954             case 'f':
955                 monitor = glfwGetPrimaryMonitor();
956                 break;
957             case 'h':
958                 usage();
959                 exit(EXIT_SUCCESS);
960         }
961     }
962 
963     if (monitor)
964     {
965         const GLFWvidmode* mode = glfwGetVideoMode(monitor);
966 
967         glfwWindowHint(GLFW_RED_BITS, mode->redBits);
968         glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
969         glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
970         glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
971 
972         width  = mode->width;
973         height = mode->height;
974     }
975     else
976     {
977         width  = 640;
978         height = 480;
979     }
980 
981     window = glfwCreateWindow(width, height, "Particle Engine", monitor, NULL);
982     if (!window)
983     {
984         fprintf(stderr, "Failed to create GLFW window\n");
985         glfwTerminate();
986         exit(EXIT_FAILURE);
987     }
988 
989     if (monitor)
990         glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
991 
992     glfwMakeContextCurrent(window);
993     gladLoadGLLoader((GLADloadproc) glfwGetProcAddress);
994     glfwSwapInterval(1);
995 
996     glfwSetFramebufferSizeCallback(window, resize_callback);
997     glfwSetKeyCallback(window, key_callback);
998 
999     // Set initial aspect ratio
1000     glfwGetFramebufferSize(window, &width, &height);
1001     resize_callback(window, width, height);
1002 
1003     // Upload particle texture
1004     glGenTextures(1, &particle_tex_id);
1005     glBindTexture(GL_TEXTURE_2D, particle_tex_id);
1006     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
1007     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
1008     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
1009     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1010     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1011     glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, P_TEX_WIDTH, P_TEX_HEIGHT,
1012                  0, GL_LUMINANCE, GL_UNSIGNED_BYTE, particle_texture);
1013 
1014     // Upload floor texture
1015     glGenTextures(1, &floor_tex_id);
1016     glBindTexture(GL_TEXTURE_2D, floor_tex_id);
1017     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
1018     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
1019     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
1020     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1021     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1022     glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, F_TEX_WIDTH, F_TEX_HEIGHT,
1023                  0, GL_LUMINANCE, GL_UNSIGNED_BYTE, floor_texture);
1024 
1025     if (glfwExtensionSupported("GL_EXT_separate_specular_color"))
1026     {
1027         glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL_EXT,
1028                       GL_SEPARATE_SPECULAR_COLOR_EXT);
1029     }
1030 
1031     // Set filled polygon mode as default (not wireframe)
1032     glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
1033     wireframe = 0;
1034 
1035     // Set initial times
1036     thread_sync.t  = 0.0;
1037     thread_sync.dt = 0.001f;
1038     thread_sync.p_frame = 0;
1039     thread_sync.d_frame = 0;
1040 
1041     mtx_init(&thread_sync.particles_lock, mtx_timed);
1042     cnd_init(&thread_sync.p_done);
1043     cnd_init(&thread_sync.d_done);
1044 
1045     if (thrd_create(&physics_thread, physics_thread_main, window) != thrd_success)
1046     {
1047         glfwTerminate();
1048         exit(EXIT_FAILURE);
1049     }
1050 
1051     glfwSetTime(0.0);
1052 
1053     while (!glfwWindowShouldClose(window))
1054     {
1055         draw_scene(window, glfwGetTime());
1056 
1057         glfwSwapBuffers(window);
1058         glfwPollEvents();
1059     }
1060 
1061     thrd_join(physics_thread, NULL);
1062 
1063     glfwDestroyWindow(window);
1064     glfwTerminate();
1065 
1066     exit(EXIT_SUCCESS);
1067 }
1068 
1069