diff --git a/examples/particles.c b/examples/particles.c new file mode 100644 index 00000000..403a9997 --- /dev/null +++ b/examples/particles.c @@ -0,0 +1,1152 @@ +//======================================================================== +// This is a simple, but cool particle engine (buzz-word meaning many +// small objects that are treated as points and drawn as textures +// projected on simple geometry). +// +// This demonstration generates a colorful fountain-like animation. It +// uses several advanced OpenGL teqhniques: +// +// 1) Lighting (per vertex) +// 2) Alpha blending +// 3) Fog +// 4) Texturing +// 5) Display lists (for drawing the static environment geometry) +// 6) Vertex arrays (for drawing the particles) +// 7) GL_EXT_separate_specular_color is used (if available) +// +// Even more so, this program uses multi threading. The program is +// essentialy divided into a main rendering thread and a particle physics +// calculation thread. My benchmarks under Windows 2000 on a single +// processor system show that running this program as two threads instead +// of a single thread means no difference (there may be a very marginal +// advantage for the multi threaded case). On dual processor systems I +// have had reports of 5-25% of speed increase when running this program +// as two threads instead of one thread. +// +// The default behaviour of this program is to use two threads. To force +// a single thread to be used, use the command line switch -s. +// +// To run a fixed length benchmark (60 s), use the command line switch -b. +// +// Benchmark results (640x480x16, best of three tests): +// +// CPU GFX 1 thread 2 threads +// Athlon XP 2700+ GeForce Ti4200 (oc) 757 FPS 759 FPS +// P4 2.8 GHz (SMT) GeForce FX5600 548 FPS 550 FPS +// +// One more thing: Press 'w' during the demo to toggle wireframe mode. +//======================================================================== + +#include +#include +#include +#include +#include + +// Define tokens for GL_EXT_separate_specular_color if not already defined +#ifndef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif // GL_EXT_separate_specular_color + +// Some 's do not define M_PI +#ifndef M_PI +#define M_PI 3.141592654 +#endif + +// Desired fullscreen resolution +#define WIDTH 640 +#define HEIGHT 480 + + +//======================================================================== +// Type definitions +//======================================================================== + +typedef struct { float x,y,z; } VEC; + +// This structure is used for interleaved vertex arrays (see the +// DrawParticles function) - Note: This structure SHOULD be packed on most +// systems. It uses 32-bit fields on 32-bit boundaries, and is a multiple +// of 64 bits in total (6x32=3x64). If it does not work, try using pragmas +// or whatever to force the structure to be packed. +typedef struct { + GLfloat s, t; // Texture coordinates + GLuint rgba; // Color (four ubytes packed into an uint) + GLfloat x, y, z; // Vertex coordinates +} VERTEX; + + +//======================================================================== +// Program control global variables +//======================================================================== + +// "Running" flag (true if program shall continue to run) +int running; + +// Window dimensions +int width, height; + +// "wireframe" flag (true if we use wireframe view) +int wireframe; + +// "multithreading" flag (true if we use multithreading) +int multithreading; + +// Thread synchronization +struct { + double t; // Time (s) + float dt; // Time since last frame (s) + int p_frame; // Particle physics frame number + int d_frame; // Particle draw frame number + GLFWcond p_done; // Condition: particle physics done + GLFWcond d_done; // Condition: particle draw done + GLFWmutex particles_lock; // Particles data sharing mutex +} thread_sync; + + +//======================================================================== +// Texture declarations (we hard-code them into the source code, since +// they are so simple) +//======================================================================== + +#define P_TEX_WIDTH 8 // Particle texture dimensions +#define P_TEX_HEIGHT 8 +#define F_TEX_WIDTH 16 // Floor texture dimensions +#define F_TEX_HEIGHT 16 + +// Texture object IDs +GLuint particle_tex_id, floor_tex_id; + +// Particle texture (a simple spot) +const unsigned char particle_texture[ P_TEX_WIDTH * P_TEX_HEIGHT ] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x22, 0x22, 0x11, 0x00, 0x00, + 0x00, 0x11, 0x33, 0x88, 0x77, 0x33, 0x11, 0x00, + 0x00, 0x22, 0x88, 0xff, 0xee, 0x77, 0x22, 0x00, + 0x00, 0x22, 0x77, 0xee, 0xff, 0x88, 0x22, 0x00, + 0x00, 0x11, 0x33, 0x77, 0x88, 0x33, 0x11, 0x00, + 0x00, 0x00, 0x11, 0x33, 0x22, 0x11, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +// Floor texture (your basic checkered floor) +const unsigned char floor_texture[ F_TEX_WIDTH * F_TEX_HEIGHT ] = { + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0xff, 0xf0, 0xcc, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0xf0, 0xcc, 0xee, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0x66, 0x30, 0x30, 0x30, 0x20, 0x30, 0x30, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xee, 0xf0, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0xf0, 0xf0, 0xf0, 0xf0, 0xcc, 0xf0, 0xf0, 0xf0, 0x30, 0x30, 0x55, 0x30, 0x30, 0x44, 0x30, 0x30, + 0xf0, 0xdd, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x60, 0x30, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x33, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x20, 0x30, 0x30, 0xf0, 0xff, 0xf0, 0xf0, 0xdd, 0xf0, 0xf0, 0xff, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x55, 0x33, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0xf0, + 0x30, 0x44, 0x66, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xaa, 0xf0, 0xf0, 0xcc, 0xf0, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xff, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0xdd, 0xf0, + 0x30, 0x30, 0x30, 0x77, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, +}; + + +//======================================================================== +// These are fixed constants that control the particle engine. In a +// modular world, these values should be variables... +//======================================================================== + +// Maximum number of particles +#define MAX_PARTICLES 3000 + +// Life span of a particle (in seconds) +#define LIFE_SPAN 8.0f + +// A new particle is born every [BIRTH_INTERVAL] second +#define BIRTH_INTERVAL (LIFE_SPAN/(float)MAX_PARTICLES) + +// Particle size (meters) +#define PARTICLE_SIZE 0.7f + +// Gravitational constant (m/s^2) +#define GRAVITY 9.8f + +// Base initial velocity (m/s) +#define VELOCITY 8.0f + +// Bounce friction (1.0 = no friction, 0.0 = maximum friction) +#define FRICTION 0.75f + +// "Fountain" height (m) +#define FOUNTAIN_HEIGHT 3.0f + +// Fountain radius (m) +#define FOUNTAIN_RADIUS 1.6f + +// Minimum delta-time for particle phisics (s) +#define MIN_DELTA_T (BIRTH_INTERVAL * 0.5f) + + +//======================================================================== +// Particle system global variables +//======================================================================== + +// This structure holds all state for a single particle +typedef struct { + float x,y,z; // Position in space + float vx,vy,vz; // Velocity vector + float r,g,b; // Color of particle + float life; // Life of particle (1.0 = newborn, < 0.0 = dead) + int active; // Tells if this particle is active +} PARTICLE; + +// Global vectors holding all particles. We use two vectors for double +// buffering. +static PARTICLE particles[ MAX_PARTICLES ]; + +// Global variable holding the age of the youngest particle +static float min_age; + +// Color of latest born particle (used for fountain lighting) +static float glow_color[4]; + +// Position of latest born particle (used for fountain lighting) +static float glow_pos[4]; + + +//======================================================================== +// Object material and fog configuration constants +//======================================================================== + +const GLfloat fountain_diffuse[4] = {0.7f,1.0f,1.0f,1.0f}; +const GLfloat fountain_specular[4] = {1.0f,1.0f,1.0f,1.0f}; +const GLfloat fountain_shininess = 12.0f; +const GLfloat floor_diffuse[4] = {1.0f,0.6f,0.6f,1.0f}; +const GLfloat floor_specular[4] = {0.6f,0.6f,0.6f,1.0f}; +const GLfloat floor_shininess = 18.0f; +const GLfloat fog_color[4] = {0.1f, 0.1f, 0.1f, 1.0f}; + + +//======================================================================== +// InitParticle() - Initialize a new particle +//======================================================================== + +void InitParticle( PARTICLE *p, double t ) +{ + float xy_angle, velocity; + + // Start position of particle is at the fountain blow-out + p->x = 0.0f; + p->y = 0.0f; + p->z = FOUNTAIN_HEIGHT; + + // Start velocity is up (Z)... + p->vz = 0.7f + (0.3f/4096.f) * (float) (rand() & 4095); + + // ...and a randomly chosen X/Y direction + xy_angle = (2.f * (float)M_PI / 4096.f) * (float) (rand() & 4095); + p->vx = 0.4f * (float) cos( xy_angle ); + p->vy = 0.4f * (float) sin( xy_angle ); + + // Scale velocity vector according to a time-varying velocity + velocity = VELOCITY*(0.8f + 0.1f*(float)(sin( 0.5*t )+sin( 1.31*t ))); + p->vx *= velocity; + p->vy *= velocity; + p->vz *= velocity; + + // Color is time-varying + p->r = 0.7f + 0.3f * (float) sin( 0.34*t + 0.1 ); + p->g = 0.6f + 0.4f * (float) sin( 0.63*t + 1.1 ); + p->b = 0.6f + 0.4f * (float) sin( 0.91*t + 2.1 ); + + // Store settings for fountain glow lighting + glow_pos[0] = 0.4f * (float) sin( 1.34*t ); + glow_pos[1] = 0.4f * (float) sin( 3.11*t ); + glow_pos[2] = FOUNTAIN_HEIGHT + 1.0f; + glow_pos[3] = 1.0f; + glow_color[0] = p->r; + glow_color[1] = p->g; + glow_color[2] = p->b; + glow_color[3] = 1.0f; + + // The particle is new-born and active + p->life = 1.0f; + p->active = 1; +} + + +//======================================================================== +// UpdateParticle() - Update a particle +//======================================================================== + +#define FOUNTAIN_R2 (FOUNTAIN_RADIUS+PARTICLE_SIZE/2)*(FOUNTAIN_RADIUS+PARTICLE_SIZE/2) + +void UpdateParticle( PARTICLE *p, float dt ) +{ + // If the particle is not active, we need not do anything + if( !p->active ) + { + return; + } + + // The particle is getting older... + p->life = p->life - dt * (1.0f / LIFE_SPAN); + + // Did the particle die? + if( p->life <= 0.0f ) + { + p->active = 0; + return; + } + + // Update particle velocity (apply gravity) + p->vz = p->vz - GRAVITY * dt; + + // Update particle position + p->x = p->x + p->vx * dt; + p->y = p->y + p->vy * dt; + p->z = p->z + p->vz * dt; + + // Simple collision detection + response + if( p->vz < 0.0f ) + { + // Particles should bounce on the fountain (with friction) + if( (p->x*p->x + p->y*p->y) < FOUNTAIN_R2 && + p->z < (FOUNTAIN_HEIGHT + PARTICLE_SIZE/2) ) + { + p->vz = -FRICTION * p->vz; + p->z = FOUNTAIN_HEIGHT + PARTICLE_SIZE/2 + + FRICTION * (FOUNTAIN_HEIGHT + + PARTICLE_SIZE/2 - p->z); + } + + // Particles should bounce on the floor (with friction) + else if( p->z < PARTICLE_SIZE/2 ) + { + p->vz = -FRICTION * p->vz; + p->z = PARTICLE_SIZE/2 + + FRICTION * (PARTICLE_SIZE/2 - p->z); + } + + } +} + + +//======================================================================== +// ParticleEngine() - The main frame for the particle engine. Called once +// per frame. +//======================================================================== + +void ParticleEngine( double t, float dt ) +{ + int i; + float dt2; + + // Update particles (iterated several times per frame if dt is too + // large) + while( dt > 0.0f ) + { + // Calculate delta time for this iteration + dt2 = dt < MIN_DELTA_T ? dt : MIN_DELTA_T; + + // Update particles + for( i = 0; i < MAX_PARTICLES; i ++ ) + { + UpdateParticle( &particles[ i ], dt2 ); + } + + // Increase minimum age + min_age += dt2; + + // Should we create any new particle(s)? + while( min_age >= BIRTH_INTERVAL ) + { + min_age -= BIRTH_INTERVAL; + + // Find a dead particle to replace with a new one + for( i = 0; i < MAX_PARTICLES; i ++ ) + { + if( !particles[ i ].active ) + { + InitParticle( &particles[ i ], t + min_age ); + UpdateParticle( &particles[ i ], min_age ); + break; + } + } + } + + // Decrease frame delta time + dt -= dt2; + } +} + + +//======================================================================== +// DrawParticles() - Draw all active particles. We use OpenGL 1.1 vertex +// arrays for this in order to accelerate the drawing. +//======================================================================== + +#define BATCH_PARTICLES 70 // Number of particles to draw in each batch + // (70 corresponds to 7.5 KB = will not blow + // the L1 data cache on most CPUs) +#define PARTICLE_VERTS 4 // Number of vertices per particle + +void DrawParticles( double t, float dt ) +{ + int i, particle_count; + VERTEX vertex_array[ BATCH_PARTICLES * PARTICLE_VERTS ], *vptr; + float alpha; + GLuint rgba; + VEC quad_lower_left, quad_lower_right; + GLfloat mat[ 16 ]; + PARTICLE *pptr; + + // Here comes the real trick with flat single primitive objects (s.c. + // "billboards"): We must rotate the textured primitive so that it + // always faces the viewer (is coplanar with the view-plane). + // We: + // 1) Create the primitive around origo (0,0,0) + // 2) Rotate it so that it is coplanar with the view plane + // 3) Translate it according to the particle position + // Note that 1) and 2) is the same for all particles (done only once). + + // Get modelview matrix. We will only use the upper left 3x3 part of + // the matrix, which represents the rotation. + glGetFloatv( GL_MODELVIEW_MATRIX, mat ); + + // 1) & 2) We do it in one swift step: + // Although not obvious, the following six lines represent two matrix/ + // vector multiplications. The matrix is the inverse 3x3 rotation + // matrix (i.e. the transpose of the same matrix), and the two vectors + // represent the lower left corner of the quad, PARTICLE_SIZE/2 * + // (-1,-1,0), and the lower right corner, PARTICLE_SIZE/2 * (1,-1,0). + // The upper left/right corners of the quad is always the negative of + // the opposite corners (regardless of rotation). + quad_lower_left.x = (-PARTICLE_SIZE/2) * (mat[0] + mat[1]); + quad_lower_left.y = (-PARTICLE_SIZE/2) * (mat[4] + mat[5]); + quad_lower_left.z = (-PARTICLE_SIZE/2) * (mat[8] + mat[9]); + quad_lower_right.x = (PARTICLE_SIZE/2) * (mat[0] - mat[1]); + quad_lower_right.y = (PARTICLE_SIZE/2) * (mat[4] - mat[5]); + quad_lower_right.z = (PARTICLE_SIZE/2) * (mat[8] - mat[9]); + + // Don't update z-buffer, since all particles are transparent! + glDepthMask( GL_FALSE ); + + // Enable blending + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + + // Select particle texture + if( !wireframe ) + { + glEnable( GL_TEXTURE_2D ); + glBindTexture( GL_TEXTURE_2D, particle_tex_id ); + } + + // Set up vertex arrays. We use interleaved arrays, which is easier to + // handle (in most situations) and it gives a linear memeory access + // access pattern (which may give better performance in some + // situations). GL_T2F_C4UB_V3F means: 2 floats for texture coords, + // 4 ubytes for color and 3 floats for vertex coord (in that order). + // Most OpenGL cards / drivers are optimized for this format. + glInterleavedArrays( GL_T2F_C4UB_V3F, 0, vertex_array ); + + // Is particle physics carried out in a separate thread? + if( multithreading ) + { + // Wait for particle physics thread to be done + glfwLockMutex( thread_sync.particles_lock ); + while( running && thread_sync.p_frame <= thread_sync.d_frame ) + { + glfwWaitCond( thread_sync.p_done, thread_sync.particles_lock, + 0.1 ); + } + + // Store the frame time and delta time for the physics thread + thread_sync.t = t; + thread_sync.dt = dt; + + // Update frame counter + thread_sync.d_frame ++; + } + else + { + // Perform particle physics in this thread + ParticleEngine( t, dt ); + } + + // Loop through all particles and build vertex arrays. + particle_count = 0; + vptr = vertex_array; + pptr = particles; + for( i = 0; i < MAX_PARTICLES; i ++ ) + { + if( pptr->active ) + { + // Calculate particle intensity (we set it to max during 75% + // of its life, then it fades out) + alpha = 4.0f * pptr->life; + if( alpha > 1.0f ) + { + alpha = 1.0f; + } + + // Convert color from float to 8-bit (store it in a 32-bit + // integer using endian independent type casting) + ((GLubyte *)&rgba)[0] = (GLubyte)(pptr->r * 255.0f); + ((GLubyte *)&rgba)[1] = (GLubyte)(pptr->g * 255.0f); + ((GLubyte *)&rgba)[2] = (GLubyte)(pptr->b * 255.0f); + ((GLubyte *)&rgba)[3] = (GLubyte)(alpha * 255.0f); + + // 3) Translate the quad to the correct position in modelview + // space and store its parameters in vertex arrays (we also + // store texture coord and color information for each vertex). + + // Lower left corner + vptr->s = 0.0f; + vptr->t = 0.0f; + vptr->rgba = rgba; + vptr->x = pptr->x + quad_lower_left.x; + vptr->y = pptr->y + quad_lower_left.y; + vptr->z = pptr->z + quad_lower_left.z; + vptr ++; + + // Lower right corner + vptr->s = 1.0f; + vptr->t = 0.0f; + vptr->rgba = rgba; + vptr->x = pptr->x + quad_lower_right.x; + vptr->y = pptr->y + quad_lower_right.y; + vptr->z = pptr->z + quad_lower_right.z; + vptr ++; + + // Upper right corner + vptr->s = 1.0f; + vptr->t = 1.0f; + vptr->rgba = rgba; + vptr->x = pptr->x - quad_lower_left.x; + vptr->y = pptr->y - quad_lower_left.y; + vptr->z = pptr->z - quad_lower_left.z; + vptr ++; + + // Upper left corner + vptr->s = 0.0f; + vptr->t = 1.0f; + vptr->rgba = rgba; + vptr->x = pptr->x - quad_lower_right.x; + vptr->y = pptr->y - quad_lower_right.y; + vptr->z = pptr->z - quad_lower_right.z; + vptr ++; + + // Increase count of drawable particles + particle_count ++; + } + + // If we have filled up one batch of particles, draw it as a set + // of quads using glDrawArrays. + if( particle_count >= BATCH_PARTICLES ) + { + // The first argument tells which primitive type we use (QUAD) + // The second argument tells the index of the first vertex (0) + // The last argument is the vertex count + glDrawArrays( GL_QUADS, 0, PARTICLE_VERTS * particle_count ); + particle_count = 0; + vptr = vertex_array; + } + + // Next particle + pptr ++; + } + + // We are done with the particle data: Unlock mutex and signal physics + // thread + if( multithreading ) + { + glfwUnlockMutex( thread_sync.particles_lock ); + glfwSignalCond( thread_sync.d_done ); + } + + // Draw final batch of particles (if any) + glDrawArrays( GL_QUADS, 0, PARTICLE_VERTS * particle_count ); + + // Disable vertex arrays (Note: glInterleavedArrays implicitly called + // glEnableClientState for vertex, texture coord and color arrays) + glDisableClientState( GL_VERTEX_ARRAY ); + glDisableClientState( GL_TEXTURE_COORD_ARRAY ); + glDisableClientState( GL_COLOR_ARRAY ); + + // Disable texturing and blending + glDisable( GL_TEXTURE_2D ); + glDisable( GL_BLEND ); + + // Allow Z-buffer updates again + glDepthMask( GL_TRUE ); +} + + +//======================================================================== +// Fountain geometry specification +//======================================================================== + +#define FOUNTAIN_SIDE_POINTS 14 +#define FOUNTAIN_SWEEP_STEPS 32 + +static const float fountain_side[ FOUNTAIN_SIDE_POINTS*2 ] = { + 1.2f, 0.0f, 1.0f, 0.2f, 0.41f, 0.3f, 0.4f, 0.35f, + 0.4f, 1.95f, 0.41f, 2.0f, 0.8f, 2.2f, 1.2f, 2.4f, + 1.5f, 2.7f, 1.55f,2.95f, 1.6f, 3.0f, 1.0f, 3.0f, + 0.5f, 3.0f, 0.0f, 3.0f +}; + +static const float fountain_normal[ FOUNTAIN_SIDE_POINTS*2 ] = { + 1.0000f, 0.0000f, 0.6428f, 0.7660f, 0.3420f, 0.9397f, 1.0000f, 0.0000f, + 1.0000f, 0.0000f, 0.3420f,-0.9397f, 0.4226f,-0.9063f, 0.5000f,-0.8660f, + 0.7660f,-0.6428f, 0.9063f,-0.4226f, 0.0000f,1.00000f, 0.0000f,1.00000f, + 0.0000f,1.00000f, 0.0000f,1.00000f +}; + + +//======================================================================== +// DrawFountain() - Draw a fountain +//======================================================================== + +void DrawFountain( void ) +{ + static GLuint fountain_list = 0; + double angle; + float x, y; + int m, n; + + // The first time, we build the fountain display list + if( !fountain_list ) + { + // Start recording of a new display list + fountain_list = glGenLists( 1 ); + glNewList( fountain_list, GL_COMPILE_AND_EXECUTE ); + + // Set fountain material + glMaterialfv( GL_FRONT, GL_DIFFUSE, fountain_diffuse ); + glMaterialfv( GL_FRONT, GL_SPECULAR, fountain_specular ); + glMaterialf( GL_FRONT, GL_SHININESS, fountain_shininess ); + + // Build fountain using triangle strips + for( n = 0; n < FOUNTAIN_SIDE_POINTS-1; n ++ ) + { + glBegin( GL_TRIANGLE_STRIP ); + for( m = 0; m <= FOUNTAIN_SWEEP_STEPS; m ++ ) + { + angle = (double) m * (2.0*M_PI/(double)FOUNTAIN_SWEEP_STEPS); + x = (float) cos( angle ); + y = (float) sin( angle ); + + // Draw triangle strip + glNormal3f( x * fountain_normal[ n*2+2 ], + y * fountain_normal[ n*2+2 ], + fountain_normal[ n*2+3 ] ); + glVertex3f( x * fountain_side[ n*2+2 ], + y * fountain_side[ n*2+2 ], + fountain_side[ n*2+3 ] ); + glNormal3f( x * fountain_normal[ n*2 ], + y * fountain_normal[ n*2 ], + fountain_normal[ n*2+1 ] ); + glVertex3f( x * fountain_side[ n*2 ], + y * fountain_side[ n*2 ], + fountain_side[ n*2+1 ] ); + } + glEnd(); + } + + // End recording of display list + glEndList(); + } + else + { + // Playback display list + glCallList( fountain_list ); + } +} + + +//======================================================================== +// TesselateFloor() - Recursive function for building variable tesselated +// floor +//======================================================================== + +void TesselateFloor( float x1, float y1, float x2, float y2, + int recursion ) +{ + float delta, x, y; + + // Last recursion? + if( recursion >= 5 ) + { + delta = 999999.0f; + } + else + { + x = (float) (fabs(x1) < fabs(x2) ? fabs(x1) : fabs(x2)); + y = (float) (fabs(y1) < fabs(y2) ? fabs(y1) : fabs(y2)); + delta = x*x + y*y; + } + + // Recurse further? + if( delta < 0.1f ) + { + x = (x1+x2) * 0.5f; + y = (y1+y2) * 0.5f; + TesselateFloor( x1,y1, x, y, recursion + 1 ); + TesselateFloor( x,y1, x2, y, recursion + 1 ); + TesselateFloor( x1, y, x,y2, recursion + 1 ); + TesselateFloor( x, y, x2,y2, recursion + 1 ); + } + else + { + glTexCoord2f( x1*30.0f, y1*30.0f ); + glVertex3f( x1*80.0f, y1*80.0f , 0.0f ); + glTexCoord2f( x2*30.0f, y1*30.0f ); + glVertex3f( x2*80.0f, y1*80.0f , 0.0f ); + glTexCoord2f( x2*30.0f, y2*30.0f ); + glVertex3f( x2*80.0f, y2*80.0f , 0.0f ); + glTexCoord2f( x1*30.0f, y2*30.0f ); + glVertex3f( x1*80.0f, y2*80.0f , 0.0f ); + } +} + + +//======================================================================== +// DrawFloor() - Draw floor. We builde the floor recursively, and let the +// tesselation in the centre (near x,y=0,0) be high, while the selleation +// around the edges be low. +//======================================================================== + +void DrawFloor( void ) +{ + static GLuint floor_list = 0; + + // Select floor texture + if( !wireframe ) + { + glEnable( GL_TEXTURE_2D ); + glBindTexture( GL_TEXTURE_2D, floor_tex_id ); + } + + // The first time, we build the floor display list + if( !floor_list ) + { + // Start recording of a new display list + floor_list = glGenLists( 1 ); + glNewList( floor_list, GL_COMPILE_AND_EXECUTE ); + + // Set floor material + glMaterialfv( GL_FRONT, GL_DIFFUSE, floor_diffuse ); + glMaterialfv( GL_FRONT, GL_SPECULAR, floor_specular ); + glMaterialf( GL_FRONT, GL_SHININESS, floor_shininess ); + + // Draw floor as a bunch of triangle strips (high tesselation + // improves lighting) + glNormal3f( 0.0f, 0.0f, 1.0f ); + glBegin( GL_QUADS ); + TesselateFloor( -1.0f,-1.0f, 0.0f,0.0f, 0 ); + TesselateFloor( 0.0f,-1.0f, 1.0f,0.0f, 0 ); + TesselateFloor( 0.0f, 0.0f, 1.0f,1.0f, 0 ); + TesselateFloor( -1.0f, 0.0f, 0.0f,1.0f, 0 ); + glEnd(); + + // End recording of display list + glEndList(); + } + else + { + // Playback display list + glCallList( floor_list ); + } + + glDisable( GL_TEXTURE_2D ); + +} + + +//======================================================================== +// SetupLights() - Position and configure light sources +//======================================================================== + +void SetupLights( void ) +{ + float l1pos[4], l1amb[4], l1dif[4], l1spec[4]; + float l2pos[4], l2amb[4], l2dif[4], l2spec[4]; + + // Set light source 1 parameters + l1pos[0] = 0.0f; l1pos[1] = -9.0f; l1pos[2] = 8.0f; l1pos[3] = 1.0f; + l1amb[0] = 0.2f; l1amb[1] = 0.2f; l1amb[2] = 0.2f; l1amb[3] = 1.0f; + l1dif[0] = 0.8f; l1dif[1] = 0.4f; l1dif[2] = 0.2f; l1dif[3] = 1.0f; + l1spec[0] = 1.0f; l1spec[1] = 0.6f; l1spec[2] = 0.2f; l1spec[3] = 0.0f; + + // Set light source 2 parameters + l2pos[0] = -15.0f; l2pos[1] = 12.0f; l2pos[2] = 1.5f; l2pos[3] = 1.0f; + l2amb[0] = 0.0f; l2amb[1] = 0.0f; l2amb[2] = 0.0f; l2amb[3] = 1.0f; + l2dif[0] = 0.2f; l2dif[1] = 0.4f; l2dif[2] = 0.8f; l2dif[3] = 1.0f; + l2spec[0] = 0.2f; l2spec[1] = 0.6f; l2spec[2] = 1.0f; l2spec[3] = 0.0f; + + // Configure light sources in OpenGL + glLightfv( GL_LIGHT1, GL_POSITION, l1pos ); + glLightfv( GL_LIGHT1, GL_AMBIENT, l1amb ); + glLightfv( GL_LIGHT1, GL_DIFFUSE, l1dif ); + glLightfv( GL_LIGHT1, GL_SPECULAR, l1spec ); + glLightfv( GL_LIGHT2, GL_POSITION, l2pos ); + glLightfv( GL_LIGHT2, GL_AMBIENT, l2amb ); + glLightfv( GL_LIGHT2, GL_DIFFUSE, l2dif ); + glLightfv( GL_LIGHT2, GL_SPECULAR, l2spec ); + glLightfv( GL_LIGHT3, GL_POSITION, glow_pos ); + glLightfv( GL_LIGHT3, GL_DIFFUSE, glow_color ); + glLightfv( GL_LIGHT3, GL_SPECULAR, glow_color ); + + // Enable light sources + glEnable( GL_LIGHT1 ); + glEnable( GL_LIGHT2 ); + glEnable( GL_LIGHT3 ); +} + + +//======================================================================== +// Draw() - Main rendering function +//======================================================================== + +void Draw( double t ) +{ + double xpos, ypos, zpos, angle_x, angle_y, angle_z; + static double t_old = 0.0; + float dt; + + // Calculate frame-to-frame delta time + dt = (float)(t-t_old); + t_old = t; + + // Setup viewport + glViewport( 0, 0, width, height ); + + // Clear color and Z-buffer + glClearColor( 0.1f, 0.1f, 0.1f, 1.0f ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + + // Setup projection + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + gluPerspective( 65.0, (double)width/(double)height, 1.0, 60.0 ); + + // Setup camera + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + + // Rotate camera + angle_x = 90.0 - 10.0; + angle_y = 10.0 * sin( 0.3 * t ); + angle_z = 10.0 * t; + glRotated( -angle_x, 1.0, 0.0, 0.0 ); + glRotated( -angle_y, 0.0, 1.0, 0.0 ); + glRotated( -angle_z, 0.0, 0.0, 1.0 ); + + // Translate camera + xpos = 15.0 * sin( (M_PI/180.0) * angle_z ) + + 2.0 * sin( (M_PI/180.0) * 3.1 * t ); + ypos = -15.0 * cos( (M_PI/180.0) * angle_z ) + + 2.0 * cos( (M_PI/180.0) * 2.9 * t ); + zpos = 4.0 + 2.0 * cos( (M_PI/180.0) * 4.9 * t ); + glTranslated( -xpos, -ypos, -zpos ); + + // Enable face culling + glFrontFace( GL_CCW ); + glCullFace( GL_BACK ); + glEnable( GL_CULL_FACE ); + + // Enable lighting + SetupLights(); + glEnable( GL_LIGHTING ); + + // Enable fog (dim details far away) + glEnable( GL_FOG ); + glFogi( GL_FOG_MODE, GL_EXP ); + glFogf( GL_FOG_DENSITY, 0.05f ); + glFogfv( GL_FOG_COLOR, fog_color ); + + // Draw floor + DrawFloor(); + + // Enable Z-buffering + glEnable( GL_DEPTH_TEST ); + glDepthFunc( GL_LEQUAL ); + glDepthMask( GL_TRUE ); + + // Draw fountain + DrawFountain(); + + // Disable fog & lighting + glDisable( GL_LIGHTING ); + glDisable( GL_FOG ); + + // Draw all particles (must be drawn after all solid objects have been + // drawn!) + DrawParticles( t, dt ); + + // Z-buffer not needed anymore + glDisable( GL_DEPTH_TEST ); +} + + +//======================================================================== +// Resize() - GLFW window resize callback function +//======================================================================== + +void GLFWCALL Resize( int x, int y ) +{ + width = x; + height = y > 0 ? y : 1; // Prevent division by zero in aspect calc. +} + + +//======================================================================== +// Input callback functions +//======================================================================== + +void GLFWCALL KeyFun( int key, int action ) +{ + if( action == GLFW_PRESS ) + { + switch( key ) + { + case GLFW_KEY_ESC: + running = 0; + break; + case 'W': + wireframe = !wireframe; + glPolygonMode( GL_FRONT_AND_BACK, + wireframe ? GL_LINE : GL_FILL ); + break; + default: + break; + } + } +} + + +//======================================================================== +// PhysicsThreadFun() - Thread for updating particle physics +//======================================================================== + +void GLFWCALL PhysicsThreadFun( void *arg ) +{ + while( running ) + { + // Lock mutex + glfwLockMutex( thread_sync.particles_lock ); + + // Wait for particle drawing to be done + while( running && thread_sync.p_frame > thread_sync.d_frame ) + { + glfwWaitCond( thread_sync.d_done, thread_sync.particles_lock, + 0.1 ); + } + + // No longer running? + if( !running ) + { + break; + } + + // Update particles + ParticleEngine( thread_sync.t, thread_sync.dt ); + + // Update frame counter + thread_sync.p_frame ++; + + // Unlock mutex and signal drawing thread + glfwUnlockMutex( thread_sync.particles_lock ); + glfwSignalCond( thread_sync.p_done ); + } +} + + +//======================================================================== +// main() +//======================================================================== + +int main( int argc, char **argv ) +{ + int i, frames, benchmark; + double t0, t; + GLFWthread physics_thread = 0; + + // Use multithreading by default, but don't benchmark + multithreading = 1; + benchmark = 0; + + // Check command line arguments + for( i = 1; i < argc; i ++ ) + { + // Use benchmarking? + if( strcmp( argv[i], "-b" ) == 0 ) + { + benchmark = 1; + } + + // Force multithreading off? + else if( strcmp( argv[i], "-s" ) == 0 ) + { + multithreading = 0; + } + + // With a Finder launch on Mac OS X we get a bogus -psn_0_46268417 + // kind of argument (actual numbers vary). Ignore it. + else if( strncmp( argv[i], "-psn_", 5) == 0 ); + + // Usage + else + { + if( strcmp( argv[i], "-?" ) != 0 ) + { + printf( "Unknonwn option %s\n\n", argv[ i ] ); + } + printf( "Usage: %s [options]\n", argv[ 0 ] ); + printf( "\n"); + printf( "Options:\n" ); + printf( " -b Benchmark (run program for 60 s)\n" ); + printf( " -s Run program as single thread (default is to use two threads)\n" ); + printf( " -? Display this text\n" ); + printf( "\n"); + printf( "Program runtime controls:\n" ); + printf( " w Toggle wireframe mode\n" ); + printf( " ESC Exit program\n" ); + exit( 0 ); + } + } + + // Initialize GLFW + if( !glfwInit() ) + { + fprintf( stderr, "Failed to initialize GLFW\n" ); + exit( EXIT_FAILURE ); + } + + // Open OpenGL fullscreen window + if( !glfwOpenWindow( WIDTH, HEIGHT, 0,0,0,0, 16,0, GLFW_FULLSCREEN ) ) + { + fprintf( stderr, "Failed to open GLFW window\n" ); + glfwTerminate(); + exit( EXIT_FAILURE ); + } + + // Set window title + glfwSetWindowTitle( "Particle engine" ); + + // Disable VSync (we want to get as high FPS as possible!) + glfwSwapInterval( 0 ); + + // Window resize callback function + glfwSetWindowSizeCallback( Resize ); + + // Set keyboard input callback function + glfwSetKeyCallback( KeyFun ); + + // Upload particle texture + glGenTextures( 1, &particle_tex_id ); + glBindTexture( GL_TEXTURE_2D, particle_tex_id ); + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE, P_TEX_WIDTH, P_TEX_HEIGHT, + 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, particle_texture ); + + // Upload floor texture + glGenTextures( 1, &floor_tex_id ); + glBindTexture( GL_TEXTURE_2D, floor_tex_id ); + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE, F_TEX_WIDTH, F_TEX_HEIGHT, + 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, floor_texture ); + + // Check if we have GL_EXT_separate_specular_color, and if so use it + if( glfwExtensionSupported( "GL_EXT_separate_specular_color" ) ) + { + glLightModeli( GL_LIGHT_MODEL_COLOR_CONTROL_EXT, + GL_SEPARATE_SPECULAR_COLOR_EXT ); + } + + // Set filled polygon mode as default (not wireframe) + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + wireframe = 0; + + // Clear particle system + for( i = 0; i < MAX_PARTICLES; i ++ ) + { + particles[ i ].active = 0; + } + min_age = 0.0f; + + // Set "running" flag + running = 1; + + // Set initial times + thread_sync.t = 0.0; + thread_sync.dt = 0.001f; + + // Init threading + if( multithreading ) + { + thread_sync.p_frame = 0; + thread_sync.d_frame = 0; + thread_sync.particles_lock = glfwCreateMutex(); + thread_sync.p_done = glfwCreateCond(); + thread_sync.d_done = glfwCreateCond(); + physics_thread = glfwCreateThread( PhysicsThreadFun, NULL ); + } + + // Main loop + t0 = glfwGetTime(); + frames = 0; + while( running ) + { + // Get frame time + t = glfwGetTime() - t0; + + // Draw... + Draw( t ); + + // Swap buffers + glfwSwapBuffers(); + + // Check if window was closed + running = running && glfwGetWindowParam( GLFW_OPENED ); + + // Increase frame count + frames ++; + + // End of benchmark? + if( benchmark && t >= 60.0 ) + { + running = 0; + } + } + t = glfwGetTime() - t0; + + // Wait for particle physics thread to die + if( multithreading ) + { + glfwWaitThread( physics_thread, GLFW_WAIT ); + } + + // Display profiling information + printf( "%d frames in %.2f seconds = %.1f FPS", frames, t, + (double)frames / t ); + printf( " (multithreading %s)\n", multithreading ? "on" : "off" ); + + // Terminate OpenGL + glfwTerminate(); + + exit( EXIT_SUCCESS ); +} +