ScrapExplorer - particles.c

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