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