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