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