1 //========================================================================
2 // A simple particle engine with threaded physics
3 // Copyright (c) Marcus Geelnard
4 // Copyright (c) Camilla Löwy <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 += 100 * 1000 * 1000;
461         ts.tv_sec += ts.tv_nsec / (1000 * 1000 * 1000);
462         ts.tv_nsec %= 1000 * 1000 * 1000;
463         cnd_timedwait(&thread_sync.p_done, &thread_sync.particles_lock, &ts);
464     }
465 
466     // Store the frame time and delta time for the physics thread
467     thread_sync.t = t;
468     thread_sync.dt = dt;
469 
470     // Update frame counter
471     thread_sync.d_frame++;
472 
473     // Loop through all particles and build vertex arrays.
474     particle_count = 0;
475     vptr = vertex_array;
476     pptr = particles;
477 
478     for (i = 0;  i < MAX_PARTICLES;  i++)
479     {
480         if (pptr->active)
481         {
482             // Calculate particle intensity (we set it to max during 75%
483             // of its life, then it fades out)
484             alpha =  4.f * pptr->life;
485             if (alpha > 1.f)
486                 alpha = 1.f;
487 
488             // Convert color from float to 8-bit (store it in a 32-bit
489             // integer using endian independent type casting)
490             ((GLubyte*) &rgba)[0] = (GLubyte)(pptr->r * 255.f);
491             ((GLubyte*) &rgba)[1] = (GLubyte)(pptr->g * 255.f);
492             ((GLubyte*) &rgba)[2] = (GLubyte)(pptr->b * 255.f);
493             ((GLubyte*) &rgba)[3] = (GLubyte)(alpha * 255.f);
494 
495             // 3) Translate the quad to the correct position in modelview
496             // space and store its parameters in vertex arrays (we also
497             // store texture coord and color information for each vertex).
498 
499             // Lower left corner
500             vptr->s    = 0.f;
501             vptr->t    = 0.f;
502             vptr->rgba = rgba;
503             vptr->x    = pptr->x + quad_lower_left.x;
504             vptr->y    = pptr->y + quad_lower_left.y;
505             vptr->z    = pptr->z + quad_lower_left.z;
506             vptr ++;
507 
508             // Lower right corner
509             vptr->s    = 1.f;
510             vptr->t    = 0.f;
511             vptr->rgba = rgba;
512             vptr->x    = pptr->x + quad_lower_right.x;
513             vptr->y    = pptr->y + quad_lower_right.y;
514             vptr->z    = pptr->z + quad_lower_right.z;
515             vptr ++;
516 
517             // Upper right corner
518             vptr->s    = 1.f;
519             vptr->t    = 1.f;
520             vptr->rgba = rgba;
521             vptr->x    = pptr->x - quad_lower_left.x;
522             vptr->y    = pptr->y - quad_lower_left.y;
523             vptr->z    = pptr->z - quad_lower_left.z;
524             vptr ++;
525 
526             // Upper left corner
527             vptr->s    = 0.f;
528             vptr->t    = 1.f;
529             vptr->rgba = rgba;
530             vptr->x    = pptr->x - quad_lower_right.x;
531             vptr->y    = pptr->y - quad_lower_right.y;
532             vptr->z    = pptr->z - quad_lower_right.z;
533             vptr ++;
534 
535             // Increase count of drawable particles
536             particle_count ++;
537         }
538 
539         // If we have filled up one batch of particles, draw it as a set
540         // of quads using glDrawArrays.
541         if (particle_count >= BATCH_PARTICLES)
542         {
543             // The first argument tells which primitive type we use (QUAD)
544             // The second argument tells the index of the first vertex (0)
545             // The last argument is the vertex count
546             glDrawArrays(GL_QUADS, 0, PARTICLE_VERTS * particle_count);
547             particle_count = 0;
548             vptr = vertex_array;
549         }
550 
551         // Next particle
552         pptr++;
553     }
554 
555     // We are done with the particle data
556     mtx_unlock(&thread_sync.particles_lock);
557     cnd_signal(&thread_sync.d_done);
558 
559     // Draw final batch of particles (if any)
560     glDrawArrays(GL_QUADS, 0, PARTICLE_VERTS * particle_count);
561 
562     // Disable vertex arrays (Note: glInterleavedArrays implicitly called
563     // glEnableClientState for vertex, texture coord and color arrays)
564     glDisableClientState(GL_VERTEX_ARRAY);
565     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
566     glDisableClientState(GL_COLOR_ARRAY);
567 
568     glDisable(GL_TEXTURE_2D);
569     glDisable(GL_BLEND);
570 
571     glDepthMask(GL_TRUE);
572 }
573 
574 
575 //========================================================================
576 // Fountain geometry specification
577 //========================================================================
578 
579 #define FOUNTAIN_SIDE_POINTS 14
580 #define FOUNTAIN_SWEEP_STEPS 32
581 
582 static const float fountain_side[FOUNTAIN_SIDE_POINTS * 2] =
583 {
584     1.2f, 0.f,  1.f, 0.2f,  0.41f, 0.3f, 0.4f, 0.35f,
585     0.4f, 1.95f, 0.41f, 2.f, 0.8f, 2.2f,  1.2f, 2.4f,
586     1.5f, 2.7f,  1.55f,2.95f, 1.6f, 3.f,  1.f, 3.f,
587     0.5f, 3.f,  0.f, 3.f
588 };
589 
590 static const float fountain_normal[FOUNTAIN_SIDE_POINTS * 2] =
591 {
592     1.0000f, 0.0000f,  0.6428f, 0.7660f,  0.3420f, 0.9397f,  1.0000f, 0.0000f,
593     1.0000f, 0.0000f,  0.3420f,-0.9397f,  0.4226f,-0.9063f,  0.5000f,-0.8660f,
594     0.7660f,-0.6428f,  0.9063f,-0.4226f,  0.0000f,1.00000f,  0.0000f,1.00000f,
595     0.0000f,1.00000f,  0.0000f,1.00000f
596 };
597 
598 
599 //========================================================================
600 // Draw a fountain
601 //========================================================================
602 
draw_fountain(void)603 static void draw_fountain(void)
604 {
605     static GLuint fountain_list = 0;
606     double angle;
607     float  x, y;
608     int m, n;
609 
610     // The first time, we build the fountain display list
611     if (!fountain_list)
612     {
613         fountain_list = glGenLists(1);
614         glNewList(fountain_list, GL_COMPILE_AND_EXECUTE);
615 
616         glMaterialfv(GL_FRONT, GL_DIFFUSE, fountain_diffuse);
617         glMaterialfv(GL_FRONT, GL_SPECULAR, fountain_specular);
618         glMaterialf(GL_FRONT, GL_SHININESS, fountain_shininess);
619 
620         // Build fountain using triangle strips
621         for (n = 0;  n < FOUNTAIN_SIDE_POINTS - 1;  n++)
622         {
623             glBegin(GL_TRIANGLE_STRIP);
624             for (m = 0;  m <= FOUNTAIN_SWEEP_STEPS;  m++)
625             {
626                 angle = (double) m * (2.0 * M_PI / (double) FOUNTAIN_SWEEP_STEPS);
627                 x = (float) cos(angle);
628                 y = (float) sin(angle);
629 
630                 // Draw triangle strip
631                 glNormal3f(x * fountain_normal[n * 2 + 2],
632                            y * fountain_normal[n * 2 + 2],
633                            fountain_normal[n * 2 + 3]);
634                 glVertex3f(x * fountain_side[n * 2 + 2],
635                            y * fountain_side[n * 2 + 2],
636                            fountain_side[n * 2 +3 ]);
637                 glNormal3f(x * fountain_normal[n * 2],
638                            y * fountain_normal[n * 2],
639                            fountain_normal[n * 2 + 1]);
640                 glVertex3f(x * fountain_side[n * 2],
641                            y * fountain_side[n * 2],
642                            fountain_side[n * 2 + 1]);
643             }
644 
645             glEnd();
646         }
647 
648         glEndList();
649     }
650     else
651         glCallList(fountain_list);
652 }
653 
654 
655 //========================================================================
656 // Recursive function for building variable tesselated floor
657 //========================================================================
658 
tessellate_floor(float x1,float y1,float x2,float y2,int depth)659 static void tessellate_floor(float x1, float y1, float x2, float y2, int depth)
660 {
661     float delta, x, y;
662 
663     // Last recursion?
664     if (depth >= 5)
665         delta = 999999.f;
666     else
667     {
668         x = (float) (fabs(x1) < fabs(x2) ? fabs(x1) : fabs(x2));
669         y = (float) (fabs(y1) < fabs(y2) ? fabs(y1) : fabs(y2));
670         delta = x*x + y*y;
671     }
672 
673     // Recurse further?
674     if (delta < 0.1f)
675     {
676         x = (x1 + x2) * 0.5f;
677         y = (y1 + y2) * 0.5f;
678         tessellate_floor(x1, y1,  x,  y, depth + 1);
679         tessellate_floor(x, y1, x2,  y, depth + 1);
680         tessellate_floor(x1,  y,  x, y2, depth + 1);
681         tessellate_floor(x,  y, x2, y2, depth + 1);
682     }
683     else
684     {
685         glTexCoord2f(x1 * 30.f, y1 * 30.f);
686         glVertex3f(  x1 * 80.f, y1 * 80.f, 0.f);
687         glTexCoord2f(x2 * 30.f, y1 * 30.f);
688         glVertex3f(  x2 * 80.f, y1 * 80.f, 0.f);
689         glTexCoord2f(x2 * 30.f, y2 * 30.f);
690         glVertex3f(  x2 * 80.f, y2 * 80.f, 0.f);
691         glTexCoord2f(x1 * 30.f, y2 * 30.f);
692         glVertex3f(  x1 * 80.f, y2 * 80.f, 0.f);
693     }
694 }
695 
696 
697 //========================================================================
698 // Draw floor. We build the floor recursively and let the tessellation in the
699 // center (near x,y=0,0) be high, while the tessellation around the edges be
700 // low.
701 //========================================================================
702 
draw_floor(void)703 static void draw_floor(void)
704 {
705     static GLuint floor_list = 0;
706 
707     if (!wireframe)
708     {
709         glEnable(GL_TEXTURE_2D);
710         glBindTexture(GL_TEXTURE_2D, floor_tex_id);
711     }
712 
713     // The first time, we build the floor display list
714     if (!floor_list)
715     {
716         floor_list = glGenLists(1);
717         glNewList(floor_list, GL_COMPILE_AND_EXECUTE);
718 
719         glMaterialfv(GL_FRONT, GL_DIFFUSE, floor_diffuse);
720         glMaterialfv(GL_FRONT, GL_SPECULAR, floor_specular);
721         glMaterialf(GL_FRONT, GL_SHININESS, floor_shininess);
722 
723         // Draw floor as a bunch of triangle strips (high tesselation
724         // improves lighting)
725         glNormal3f(0.f, 0.f, 1.f);
726         glBegin(GL_QUADS);
727         tessellate_floor(-1.f, -1.f, 0.f, 0.f, 0);
728         tessellate_floor( 0.f, -1.f, 1.f, 0.f, 0);
729         tessellate_floor( 0.f,  0.f, 1.f, 1.f, 0);
730         tessellate_floor(-1.f,  0.f, 0.f, 1.f, 0);
731         glEnd();
732 
733         glEndList();
734     }
735     else
736         glCallList(floor_list);
737 
738     glDisable(GL_TEXTURE_2D);
739 
740 }
741 
742 
743 //========================================================================
744 // Position and configure light sources
745 //========================================================================
746 
setup_lights(void)747 static void setup_lights(void)
748 {
749     float l1pos[4], l1amb[4], l1dif[4], l1spec[4];
750     float l2pos[4], l2amb[4], l2dif[4], l2spec[4];
751 
752     // Set light source 1 parameters
753     l1pos[0] =  0.f;  l1pos[1] = -9.f; l1pos[2] =   8.f;  l1pos[3] = 1.f;
754     l1amb[0] = 0.2f;  l1amb[1] = 0.2f;  l1amb[2] = 0.2f;  l1amb[3] = 1.f;
755     l1dif[0] = 0.8f;  l1dif[1] = 0.4f;  l1dif[2] = 0.2f;  l1dif[3] = 1.f;
756     l1spec[0] = 1.f; l1spec[1] = 0.6f; l1spec[2] = 0.2f; l1spec[3] = 0.f;
757 
758     // Set light source 2 parameters
759     l2pos[0] =  -15.f; l2pos[1] =  12.f; l2pos[2] = 1.5f; l2pos[3] =  1.f;
760     l2amb[0] =    0.f; l2amb[1] =   0.f; l2amb[2] =  0.f; l2amb[3] =  1.f;
761     l2dif[0] =   0.2f; l2dif[1] =  0.4f; l2dif[2] = 0.8f; l2dif[3] =  1.f;
762     l2spec[0] =  0.2f; l2spec[1] = 0.6f; l2spec[2] = 1.f; l2spec[3] = 0.f;
763 
764     glLightfv(GL_LIGHT1, GL_POSITION, l1pos);
765     glLightfv(GL_LIGHT1, GL_AMBIENT, l1amb);
766     glLightfv(GL_LIGHT1, GL_DIFFUSE, l1dif);
767     glLightfv(GL_LIGHT1, GL_SPECULAR, l1spec);
768     glLightfv(GL_LIGHT2, GL_POSITION, l2pos);
769     glLightfv(GL_LIGHT2, GL_AMBIENT, l2amb);
770     glLightfv(GL_LIGHT2, GL_DIFFUSE, l2dif);
771     glLightfv(GL_LIGHT2, GL_SPECULAR, l2spec);
772     glLightfv(GL_LIGHT3, GL_POSITION, glow_pos);
773     glLightfv(GL_LIGHT3, GL_DIFFUSE, glow_color);
774     glLightfv(GL_LIGHT3, GL_SPECULAR, glow_color);
775 
776     glEnable(GL_LIGHT1);
777     glEnable(GL_LIGHT2);
778     glEnable(GL_LIGHT3);
779 }
780 
781 
782 //========================================================================
783 // Main rendering function
784 //========================================================================
785 
draw_scene(GLFWwindow * window,double t)786 static void draw_scene(GLFWwindow* window, double t)
787 {
788     double xpos, ypos, zpos, angle_x, angle_y, angle_z;
789     static double t_old = 0.0;
790     float dt;
791     mat4x4 projection;
792 
793     // Calculate frame-to-frame delta time
794     dt = (float) (t - t_old);
795     t_old = t;
796 
797     mat4x4_perspective(projection,
798                        65.f * (float) M_PI / 180.f,
799                        aspect_ratio,
800                        1.0, 60.0);
801 
802     glClearColor(0.1f, 0.1f, 0.1f, 1.f);
803     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
804 
805     glMatrixMode(GL_PROJECTION);
806     glLoadMatrixf((const GLfloat*) projection);
807 
808     // Setup camera
809     glMatrixMode(GL_MODELVIEW);
810     glLoadIdentity();
811 
812     // Rotate camera
813     angle_x = 90.0 - 10.0;
814     angle_y = 10.0 * sin(0.3 * t);
815     angle_z = 10.0 * t;
816     glRotated(-angle_x, 1.0, 0.0, 0.0);
817     glRotated(-angle_y, 0.0, 1.0, 0.0);
818     glRotated(-angle_z, 0.0, 0.0, 1.0);
819 
820     // Translate camera
821     xpos =  15.0 * sin((M_PI / 180.0) * angle_z) +
822              2.0 * sin((M_PI / 180.0) * 3.1 * t);
823     ypos = -15.0 * cos((M_PI / 180.0) * angle_z) +
824              2.0 * cos((M_PI / 180.0) * 2.9 * t);
825     zpos = 4.0 + 2.0 * cos((M_PI / 180.0) * 4.9 * t);
826     glTranslated(-xpos, -ypos, -zpos);
827 
828     glFrontFace(GL_CCW);
829     glCullFace(GL_BACK);
830     glEnable(GL_CULL_FACE);
831 
832     setup_lights();
833     glEnable(GL_LIGHTING);
834 
835     glEnable(GL_FOG);
836     glFogi(GL_FOG_MODE, GL_EXP);
837     glFogf(GL_FOG_DENSITY, 0.05f);
838     glFogfv(GL_FOG_COLOR, fog_color);
839 
840     draw_floor();
841 
842     glEnable(GL_DEPTH_TEST);
843     glDepthFunc(GL_LEQUAL);
844     glDepthMask(GL_TRUE);
845 
846     draw_fountain();
847 
848     glDisable(GL_LIGHTING);
849     glDisable(GL_FOG);
850 
851     // Particles must be drawn after all solid objects have been drawn
852     draw_particles(window, t, dt);
853 
854     // Z-buffer not needed anymore
855     glDisable(GL_DEPTH_TEST);
856 }
857 
858 
859 //========================================================================
860 // Window resize callback function
861 //========================================================================
862 
resize_callback(GLFWwindow * window,int width,int height)863 static void resize_callback(GLFWwindow* window, int width, int height)
864 {
865     glViewport(0, 0, width, height);
866     aspect_ratio = height ? width / (float) height : 1.f;
867 }
868 
869 
870 //========================================================================
871 // Key callback functions
872 //========================================================================
873 
key_callback(GLFWwindow * window,int key,int scancode,int action,int mods)874 static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
875 {
876     if (action == GLFW_PRESS)
877     {
878         switch (key)
879         {
880             case GLFW_KEY_ESCAPE:
881                 glfwSetWindowShouldClose(window, GLFW_TRUE);
882                 break;
883             case GLFW_KEY_W:
884                 wireframe = !wireframe;
885                 glPolygonMode(GL_FRONT_AND_BACK,
886                               wireframe ? GL_LINE : GL_FILL);
887                 break;
888             default:
889                 break;
890         }
891     }
892 }
893 
894 
895 //========================================================================
896 // Thread for updating particle physics
897 //========================================================================
898 
physics_thread_main(void * arg)899 static int physics_thread_main(void* arg)
900 {
901     GLFWwindow* window = arg;
902 
903     for (;;)
904     {
905         mtx_lock(&thread_sync.particles_lock);
906 
907         // Wait for particle drawing to be done
908         while (!glfwWindowShouldClose(window) &&
909                thread_sync.p_frame > thread_sync.d_frame)
910         {
911             struct timespec ts;
912             clock_gettime(CLOCK_REALTIME, &ts);
913             ts.tv_nsec += 100 * 1000 * 1000;
914             ts.tv_sec += ts.tv_nsec / (1000 * 1000 * 1000);
915             ts.tv_nsec %= 1000 * 1000 * 1000;
916             cnd_timedwait(&thread_sync.d_done, &thread_sync.particles_lock, &ts);
917         }
918 
919         if (glfwWindowShouldClose(window))
920             break;
921 
922         // Update particles
923         particle_engine(thread_sync.t, thread_sync.dt);
924 
925         // Update frame counter
926         thread_sync.p_frame++;
927 
928         // Unlock mutex and signal drawing thread
929         mtx_unlock(&thread_sync.particles_lock);
930         cnd_signal(&thread_sync.p_done);
931     }
932 
933     return 0;
934 }
935 
936 
937 //========================================================================
938 // main
939 //========================================================================
940 
main(int argc,char ** argv)941 int main(int argc, char** argv)
942 {
943     int ch, width, height;
944     thrd_t physics_thread = 0;
945     GLFWwindow* window;
946     GLFWmonitor* monitor = NULL;
947 
948     if (!glfwInit())
949     {
950         fprintf(stderr, "Failed to initialize GLFW\n");
951         exit(EXIT_FAILURE);
952     }
953 
954     while ((ch = getopt(argc, argv, "fh")) != -1)
955     {
956         switch (ch)
957         {
958             case 'f':
959                 monitor = glfwGetPrimaryMonitor();
960                 break;
961             case 'h':
962                 usage();
963                 exit(EXIT_SUCCESS);
964         }
965     }
966 
967     if (monitor)
968     {
969         const GLFWvidmode* mode = glfwGetVideoMode(monitor);
970 
971         glfwWindowHint(GLFW_RED_BITS, mode->redBits);
972         glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
973         glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
974         glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
975 
976         width  = mode->width;
977         height = mode->height;
978     }
979     else
980     {
981         width  = 640;
982         height = 480;
983     }
984 
985     window = glfwCreateWindow(width, height, "Particle Engine", monitor, NULL);
986     if (!window)
987     {
988         fprintf(stderr, "Failed to create GLFW window\n");
989         glfwTerminate();
990         exit(EXIT_FAILURE);
991     }
992 
993     if (monitor)
994         glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
995 
996     glfwMakeContextCurrent(window);
997     gladLoadGLLoader((GLADloadproc) glfwGetProcAddress);
998     glfwSwapInterval(1);
999 
1000     glfwSetFramebufferSizeCallback(window, resize_callback);
1001     glfwSetKeyCallback(window, key_callback);
1002 
1003     // Set initial aspect ratio
1004     glfwGetFramebufferSize(window, &width, &height);
1005     resize_callback(window, width, height);
1006 
1007     // Upload particle texture
1008     glGenTextures(1, &particle_tex_id);
1009     glBindTexture(GL_TEXTURE_2D, particle_tex_id);
1010     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
1011     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
1012     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
1013     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1014     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1015     glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, P_TEX_WIDTH, P_TEX_HEIGHT,
1016                  0, GL_LUMINANCE, GL_UNSIGNED_BYTE, particle_texture);
1017 
1018     // Upload floor texture
1019     glGenTextures(1, &floor_tex_id);
1020     glBindTexture(GL_TEXTURE_2D, floor_tex_id);
1021     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
1022     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
1023     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
1024     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1025     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1026     glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, F_TEX_WIDTH, F_TEX_HEIGHT,
1027                  0, GL_LUMINANCE, GL_UNSIGNED_BYTE, floor_texture);
1028 
1029     if (glfwExtensionSupported("GL_EXT_separate_specular_color"))
1030     {
1031         glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL_EXT,
1032                       GL_SEPARATE_SPECULAR_COLOR_EXT);
1033     }
1034 
1035     // Set filled polygon mode as default (not wireframe)
1036     glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
1037     wireframe = 0;
1038 
1039     // Set initial times
1040     thread_sync.t  = 0.0;
1041     thread_sync.dt = 0.001f;
1042     thread_sync.p_frame = 0;
1043     thread_sync.d_frame = 0;
1044 
1045     mtx_init(&thread_sync.particles_lock, mtx_timed);
1046     cnd_init(&thread_sync.p_done);
1047     cnd_init(&thread_sync.d_done);
1048 
1049     if (thrd_create(&physics_thread, physics_thread_main, window) != thrd_success)
1050     {
1051         glfwTerminate();
1052         exit(EXIT_FAILURE);
1053     }
1054 
1055     glfwSetTime(0.0);
1056 
1057     while (!glfwWindowShouldClose(window))
1058     {
1059         draw_scene(window, glfwGetTime());
1060 
1061         glfwSwapBuffers(window);
1062         glfwPollEvents();
1063     }
1064 
1065     thrd_join(physics_thread, NULL);
1066 
1067     glfwDestroyWindow(window);
1068     glfwTerminate();
1069 
1070     exit(EXIT_SUCCESS);
1071 }
1072 
1073