glfw/examples/particles.c

1153 lines
38 KiB
C
Raw Normal View History

2014-01-11 18:37:01 +00:00
//========================================================================
// 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <GL/glfw.h>
// 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 <math.h>'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 );
}