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