particles.c 35 KB

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