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