From 53245d754e350fc96370e6d28b7678c439a857c2 Mon Sep 17 00:00:00 2001
From: Camilla Berglund
Date: Tue, 28 Aug 2012 20:16:43 +0200
Subject: [PATCH 01/12] Added detection of joystick disconnect on X11.
---
src/x11_joystick.c | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/src/x11_joystick.c b/src/x11_joystick.c
index 8492850b..7f4fdbc1 100644
--- a/src/x11_joystick.c
+++ b/src/x11_joystick.c
@@ -36,6 +36,7 @@
#include
#include
#include
+#include
#include
#include
@@ -109,6 +110,7 @@ static void pollJoystickEvents(void)
{
#ifdef _GLFW_USE_LINUX_JOYSTICKS
int i;
+ ssize_t result;
struct js_event e;
for (i = 0; i <= GLFW_JOYSTICK_LAST; i++)
@@ -117,8 +119,17 @@ static void pollJoystickEvents(void)
continue;
// Read all queued events (non-blocking)
- while (read(_glfwLibrary.X11.joystick[i].fd, &e, sizeof(e)) > 0)
+ for (;;)
{
+ errno = 0;
+ result = read(_glfwLibrary.X11.joystick[i].fd, &e, sizeof(e));
+
+ if (errno == ENODEV)
+ _glfwLibrary.X11.joystick[i].present = GL_FALSE;
+
+ if (result <= 0)
+ break;
+
// We don't care if it's an init event or not
e.type &= ~JS_EVENT_INIT;
@@ -221,6 +232,8 @@ void _glfwTerminateJoysticks(void)
int _glfwPlatformGetJoystickParam(int joy, int param)
{
+ pollJoystickEvents();
+
if (!_glfwLibrary.X11.joystick[joy].present)
return 0;
@@ -251,11 +264,11 @@ int _glfwPlatformGetJoystickPos(int joy, float* pos, int numAxes)
{
int i;
+ pollJoystickEvents();
+
if (!_glfwLibrary.X11.joystick[joy].present)
return 0;
- pollJoystickEvents();
-
if (_glfwLibrary.X11.joystick[joy].numAxes < numAxes)
numAxes = _glfwLibrary.X11.joystick[joy].numAxes;
@@ -275,11 +288,11 @@ int _glfwPlatformGetJoystickButtons(int joy, unsigned char* buttons,
{
int i;
+ pollJoystickEvents();
+
if (!_glfwLibrary.X11.joystick[joy].present)
return 0;
- pollJoystickEvents();
-
if (_glfwLibrary.X11.joystick[joy].numButtons < numButtons)
numButtons = _glfwLibrary.X11.joystick[joy].numButtons;
From 54fceaaf64a7d5205953a472e774d1ac748ee31a Mon Sep 17 00:00:00 2001
From: Camilla Berglund
Date: Tue, 28 Aug 2012 20:24:37 +0200
Subject: [PATCH 02/12] Clarified result comparison.
---
src/x11_joystick.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/x11_joystick.c b/src/x11_joystick.c
index 7f4fdbc1..09885373 100644
--- a/src/x11_joystick.c
+++ b/src/x11_joystick.c
@@ -127,7 +127,7 @@ static void pollJoystickEvents(void)
if (errno == ENODEV)
_glfwLibrary.X11.joystick[i].present = GL_FALSE;
- if (result <= 0)
+ if (result < sizeof(e))
break;
// We don't care if it's an init event or not
From f50d38f14815979b3a9f21febbf4a96433778b45 Mon Sep 17 00:00:00 2001
From: Camilla Berglund
Date: Tue, 28 Aug 2012 22:56:35 +0200
Subject: [PATCH 03/12] Moved glfwinfo version check to before glfwInit.
---
tests/glfwinfo.c | 42 ++++++++++++++++++++++--------------------
1 file changed, 22 insertions(+), 20 deletions(-)
diff --git a/tests/glfwinfo.c b/tests/glfwinfo.c
index 90fd329f..11a84612 100644
--- a/tests/glfwinfo.c
+++ b/tests/glfwinfo.c
@@ -181,6 +181,28 @@ int main(int argc, char** argv)
argc -= optind;
argv += optind;
+ // Report GLFW version
+
+ glfwGetVersion(&major, &minor, &revision);
+
+ printf("GLFW header version: %u.%u.%u\n",
+ GLFW_VERSION_MAJOR,
+ GLFW_VERSION_MINOR,
+ GLFW_VERSION_REVISION);
+
+ printf("GLFW library version: %u.%u.%u\n", major, minor, revision);
+
+ if (major != GLFW_VERSION_MAJOR ||
+ minor != GLFW_VERSION_MINOR ||
+ revision != GLFW_VERSION_REVISION)
+ {
+ printf("*** WARNING: GLFW version mismatch! ***\n");
+ }
+
+ printf("GLFW library version string: \"%s\"\n", glfwGetVersionString());
+
+ // Initialize GLFW and create window
+
glfwSetErrorCallback(error_callback);
if (!glfwInit())
@@ -216,26 +238,6 @@ int main(int argc, char** argv)
glfwMakeContextCurrent(window);
- // Report GLFW version
-
- glfwGetVersion(&major, &minor, &revision);
-
- printf("GLFW header version: %u.%u.%u\n",
- GLFW_VERSION_MAJOR,
- GLFW_VERSION_MINOR,
- GLFW_VERSION_REVISION);
-
- printf("GLFW library version: %u.%u.%u\n", major, minor, revision);
-
- if (major != GLFW_VERSION_MAJOR ||
- minor != GLFW_VERSION_MINOR ||
- revision != GLFW_VERSION_REVISION)
- {
- printf("*** WARNING: GLFW version mismatch! ***\n");
- }
-
- printf("GLFW library version string: \"%s\"\n", glfwGetVersionString());
-
// Report OpenGL version
printf("OpenGL context version string: \"%s\"\n", glGetString(GL_VERSION));
From 3ef7c554585ce80621d5710c7ce052739d82fad4 Mon Sep 17 00:00:00 2001
From: Camilla Berglund
Date: Wed, 29 Aug 2012 01:24:53 +0200
Subject: [PATCH 04/12] Added credit.
---
readme.html | 2 ++
1 file changed, 2 insertions(+)
diff --git a/readme.html b/readme.html
index 39d0739b..23c9113f 100644
--- a/readme.html
+++ b/readme.html
@@ -950,6 +950,8 @@ their skills. Special thanks go out to:
Samuli Tuomola, for support, bug reports and testing
+ Torsten Walluhn, for fixing various compilation issues on OS X
+
Frank Wille, for helping with the AmigaOS port and making GLFW
compile under IRIX 5.3
From 54f1a57f8d33faaa2f569a74cebdff557b5a851b Mon Sep 17 00:00:00 2001
From: Camilla Berglund
Date: Wed, 29 Aug 2012 16:00:54 +0200
Subject: [PATCH 05/12] Added channel bit depth hint defaults.
---
src/window.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/window.c b/src/window.c
index acd48195..e59a1d25 100644
--- a/src/window.c
+++ b/src/window.c
@@ -84,6 +84,12 @@ void _glfwSetDefaultWindowHints(void)
// The default is to allow window resizing
_glfwLibrary.hints.resizable = GL_TRUE;
+
+ // The default is 24 bits of depth, 8 bits of color
+ _glfwLibrary.hints.depthBits = 24;
+ _glfwLibrary.hints.redBits = 8;
+ _glfwLibrary.hints.greenBits = 8;
+ _glfwLibrary.hints.blueBits = 8;
}
From 74f5cd6fa7f00cd1a8b838371dc15458620c9e0a Mon Sep 17 00:00:00 2001
From: Camilla Berglund
Date: Wed, 29 Aug 2012 16:12:44 +0200
Subject: [PATCH 06/12] Removed unused example files.
---
examples/particles.c | 1152 -----------------------------------
examples/pong3d.c | 854 --------------------------
examples/pong3d_field.tga | Bin 17816 -> 0 bytes
examples/pong3d_instr.tga | Bin 21279 -> 0 bytes
examples/pong3d_menu.tga | Bin 1835 -> 0 bytes
examples/pong3d_title.tga | Bin 106516 -> 0 bytes
examples/pong3d_winner1.tga | Bin 861 -> 0 bytes
examples/pong3d_winner2.tga | Bin 891 -> 0 bytes
8 files changed, 2006 deletions(-)
delete mode 100644 examples/particles.c
delete mode 100644 examples/pong3d.c
delete mode 100644 examples/pong3d_field.tga
delete mode 100644 examples/pong3d_instr.tga
delete mode 100644 examples/pong3d_menu.tga
delete mode 100644 examples/pong3d_title.tga
delete mode 100644 examples/pong3d_winner1.tga
delete mode 100644 examples/pong3d_winner2.tga
diff --git a/examples/particles.c b/examples/particles.c
deleted file mode 100644
index c7904e11..00000000
--- a/examples/particles.c
+++ /dev/null
@@ -1,1152 +0,0 @@
-//========================================================================
-// 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( !glfwCreateWindow( 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 );
-}
-
diff --git a/examples/pong3d.c b/examples/pong3d.c
deleted file mode 100644
index 58c9ee20..00000000
--- a/examples/pong3d.c
+++ /dev/null
@@ -1,854 +0,0 @@
-//========================================================================
-// This is a small test application for GLFW.
-// This is an OpenGL port of the famous "PONG" game (the first computer
-// game ever?). It is very simple, and could be improved alot. It was
-// created in order to show off the gaming capabilities of GLFW.
-//========================================================================
-
-#include
-#include
-#include
-#include
-
-
-//========================================================================
-// Constants
-//========================================================================
-
-// Screen resolution
-#define WIDTH 640
-#define HEIGHT 480
-
-// Player size (units)
-#define PLAYER_XSIZE 0.05f
-#define PLAYER_YSIZE 0.15f
-
-// Ball size (units)
-#define BALL_SIZE 0.02f
-
-// Maximum player movement speed (units / second)
-#define MAX_SPEED 1.5f
-
-// Player movement acceleration (units / seconds^2)
-#define ACCELERATION 4.0f
-
-// Player movement deceleration (units / seconds^2)
-#define DECELERATION 2.0f
-
-// Ball movement speed (units / second)
-#define BALL_SPEED 0.4f
-
-// Menu options
-#define MENU_NONE 0
-#define MENU_PLAY 1
-#define MENU_QUIT 2
-
-// Game events
-#define NOBODY_WINS 0
-#define PLAYER1_WINS 1
-#define PLAYER2_WINS 2
-
-// Winner ID
-#define NOBODY 0
-#define PLAYER1 1
-#define PLAYER2 2
-
-// Camera positions
-#define CAMERA_CLASSIC 0
-#define CAMERA_ABOVE 1
-#define CAMERA_SPECTATOR 2
-#define CAMERA_DEFAULT CAMERA_CLASSIC
-
-
-//========================================================================
-// Textures
-//========================================================================
-
-#define TEX_TITLE 0
-#define TEX_MENU 1
-#define TEX_INSTR 2
-#define TEX_WINNER1 3
-#define TEX_WINNER2 4
-#define TEX_FIELD 5
-#define NUM_TEXTURES 6
-
-// Texture names
-char * tex_name[ NUM_TEXTURES ] = {
- "pong3d_title.tga",
- "pong3d_menu.tga",
- "pong3d_instr.tga",
- "pong3d_winner1.tga",
- "pong3d_winner2.tga",
- "pong3d_field.tga"
-};
-
-// OpenGL texture object IDs
-GLuint tex_id[ NUM_TEXTURES ];
-
-
-//========================================================================
-// Global variables
-//========================================================================
-
-// Display information
-int width, height;
-
-// Frame information
-double thistime, oldtime, dt, starttime;
-
-// Camera information
-int camerapos;
-
-// Player information
-struct {
- double ypos; // -1.0 to +1.0
- double yspeed; // -MAX_SPEED to +MAX_SPEED
-} player1, player2;
-
-// Ball information
-struct {
- double xpos, ypos;
- double xspeed, yspeed;
-} ball;
-
-// And the winner is...
-int winner;
-
-// Lighting configuration
-const GLfloat env_ambient[4] = {1.0f,1.0f,1.0f,1.0f};
-const GLfloat light1_position[4] = {-3.0f,3.0f,2.0f,1.0f};
-const GLfloat light1_diffuse[4] = {1.0f,1.0f,1.0f,0.0f};
-const GLfloat light1_ambient[4] = {0.0f,0.0f,0.0f,0.0f};
-
-// Object material properties
-const GLfloat player1_diffuse[4] = {1.0f,0.3f,0.3f,1.0f};
-const GLfloat player1_ambient[4] = {0.3f,0.1f,0.0f,1.0f};
-const GLfloat player2_diffuse[4] = {0.3f,1.0f,0.3f,1.0f};
-const GLfloat player2_ambient[4] = {0.1f,0.3f,0.1f,1.0f};
-const GLfloat ball_diffuse[4] = {1.0f,1.0f,0.5f,1.0f};
-const GLfloat ball_ambient[4] = {0.3f,0.3f,0.1f,1.0f};
-const GLfloat border_diffuse[4] = {0.3f,0.3f,1.0f,1.0f};
-const GLfloat border_ambient[4] = {0.1f,0.1f,0.3f,1.0f};
-const GLfloat floor_diffuse[4] = {1.0f,1.0f,1.0f,1.0f};
-const GLfloat floor_ambient[4] = {0.3f,0.3f,0.3f,1.0f};
-
-
-//========================================================================
-// LoadTextures() - Load textures from disk and upload to OpenGL card
-//========================================================================
-
-GLboolean LoadTextures( void )
-{
- int i;
-
- // Generate texture objects
- glGenTextures( NUM_TEXTURES, tex_id );
-
- // Load textures
- for( i = 0; i < NUM_TEXTURES; i ++ )
- {
- // Select texture object
- glBindTexture( GL_TEXTURE_2D, tex_id[ i ] );
-
- // Set texture parameters
- 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 );
-
- // Upload texture from file to texture memory
- if( !glfwLoadTexture2D( tex_name[ i ], 0 ) )
- {
- fprintf( stderr, "Failed to load texture %s\n", tex_name[ i ] );
- return GL_FALSE;
- }
- }
-
- return GL_TRUE;
-}
-
-
-//========================================================================
-// DrawImage() - Draw a 2D image as a texture
-//========================================================================
-
-void DrawImage( int texnum, float x1, float x2, float y1, float y2 )
-{
- glEnable( GL_TEXTURE_2D );
- glBindTexture( GL_TEXTURE_2D, tex_id[ texnum ] );
- glBegin( GL_QUADS );
- glTexCoord2f( 0.0f, 1.0f );
- glVertex2f( x1, y1 );
- glTexCoord2f( 1.0f, 1.0f );
- glVertex2f( x2, y1 );
- glTexCoord2f( 1.0f, 0.0f );
- glVertex2f( x2, y2 );
- glTexCoord2f( 0.0f, 0.0f );
- glVertex2f( x1, y2 );
- glEnd();
- glDisable( GL_TEXTURE_2D );
-}
-
-
-//========================================================================
-// GameMenu() - Game menu (returns menu option)
-//========================================================================
-
-int GameMenu( void )
-{
- int option;
-
- // Enable sticky keys
- glfwEnable( GLFW_STICKY_KEYS );
-
- // Wait for a game menu key to be pressed
- do
- {
- // Get window size
- glfwGetWindowSize( &width, &height );
-
- // Set viewport
- glViewport( 0, 0, width, height );
-
- // Clear display
- glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
- glClear( GL_COLOR_BUFFER_BIT );
-
- // Setup projection matrix
- glMatrixMode( GL_PROJECTION );
- glLoadIdentity();
- glOrtho( 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f );
-
- // Setup modelview matrix
- glMatrixMode( GL_MODELVIEW );
- glLoadIdentity();
-
- // Display title
- glColor3f( 1.0f, 1.0f, 1.0f );
- DrawImage( TEX_TITLE, 0.1f, 0.9f, 0.0f, 0.3f );
-
- // Display menu
- glColor3f( 1.0f, 1.0f, 0.0f );
- DrawImage( TEX_MENU, 0.38f, 0.62f, 0.35f, 0.5f );
-
- // Display instructions
- glColor3f( 0.0f, 1.0f, 1.0f );
- DrawImage( TEX_INSTR, 0.32f, 0.68f, 0.65f, 0.85f );
-
- // Swap buffers
- glfwSwapBuffers();
-
- // Check for keys
- if( glfwGetKey( 'Q' ) || !glfwGetWindowParam( GLFW_OPENED ) )
- {
- option = MENU_QUIT;
- }
- else if( glfwGetKey( GLFW_KEY_F1 ) )
- {
- option = MENU_PLAY;
- }
- else
- {
- option = MENU_NONE;
- }
-
- // To avoid horrible busy waiting, sleep for at least 20 ms
- glfwSleep( 0.02 );
- }
- while( option == MENU_NONE );
-
- // Disable sticky keys
- glfwDisable( GLFW_STICKY_KEYS );
-
- return option;
-}
-
-
-//========================================================================
-// NewGame() - Initialize a new game
-//========================================================================
-
-void NewGame( void )
-{
- // Frame information
- starttime = thistime = glfwGetTime();
-
- // Camera information
- camerapos = CAMERA_DEFAULT;
-
- // Player 1 information
- player1.ypos = 0.0;
- player1.yspeed = 0.0;
-
- // Player 2 information
- player2.ypos = 0.0;
- player2.yspeed = 0.0;
-
- // Ball information
- ball.xpos = -1.0 + PLAYER_XSIZE;
- ball.ypos = player1.ypos;
- ball.xspeed = 1.0;
- ball.yspeed = 1.0;
-}
-
-
-//========================================================================
-// PlayerControl() - Player control
-//========================================================================
-
-void PlayerControl( void )
-{
- float joy1pos[ 2 ], joy2pos[ 2 ];
-
- // Get joystick X & Y axis positions
- glfwGetJoystickPos( GLFW_JOYSTICK_1, joy1pos, 2 );
- glfwGetJoystickPos( GLFW_JOYSTICK_2, joy2pos, 2 );
-
- // Player 1 control
- if( glfwGetKey( 'A' ) || joy1pos[ 1 ] > 0.2f )
- {
- player1.yspeed += dt * ACCELERATION;
- if( player1.yspeed > MAX_SPEED )
- {
- player1.yspeed = MAX_SPEED;
- }
- }
- else if( glfwGetKey( 'Z' ) || joy1pos[ 1 ] < -0.2f )
- {
- player1.yspeed -= dt * ACCELERATION;
- if( player1.yspeed < -MAX_SPEED )
- {
- player1.yspeed = -MAX_SPEED;
- }
- }
- else
- {
- player1.yspeed /= exp( DECELERATION * dt );
- }
-
- // Player 2 control
- if( glfwGetKey( 'K' ) || joy2pos[ 1 ] > 0.2f )
- {
- player2.yspeed += dt * ACCELERATION;
- if( player2.yspeed > MAX_SPEED )
- {
- player2.yspeed = MAX_SPEED;
- }
- }
- else if( glfwGetKey( 'M' ) || joy2pos[ 1 ] < -0.2f )
- {
- player2.yspeed -= dt * ACCELERATION;
- if( player2.yspeed < -MAX_SPEED )
- {
- player2.yspeed = -MAX_SPEED;
- }
- }
- else
- {
- player2.yspeed /= exp( DECELERATION * dt );
- }
-
- // Update player 1 position
- player1.ypos += dt * player1.yspeed;
- if( player1.ypos > 1.0 - PLAYER_YSIZE )
- {
- player1.ypos = 1.0 - PLAYER_YSIZE;
- player1.yspeed = 0.0;
- }
- else if( player1.ypos < -1.0 + PLAYER_YSIZE )
- {
- player1.ypos = -1.0 + PLAYER_YSIZE;
- player1.yspeed = 0.0;
- }
-
- // Update player 2 position
- player2.ypos += dt * player2.yspeed;
- if( player2.ypos > 1.0 - PLAYER_YSIZE )
- {
- player2.ypos = 1.0 - PLAYER_YSIZE;
- player2.yspeed = 0.0;
- }
- else if( player2.ypos < -1.0 + PLAYER_YSIZE )
- {
- player2.ypos = -1.0 + PLAYER_YSIZE;
- player2.yspeed = 0.0;
- }
-}
-
-
-//========================================================================
-// BallControl() - Ball control
-//========================================================================
-
-int BallControl( void )
-{
- int event;
- double ballspeed;
-
- // Calculate new ball speed
- ballspeed = BALL_SPEED * (1.0 + 0.02*(thistime-starttime));
- ball.xspeed = ball.xspeed > 0 ? ballspeed : -ballspeed;
- ball.yspeed = ball.yspeed > 0 ? ballspeed : -ballspeed;
- ball.yspeed *= 0.74321;
-
- // Update ball position
- ball.xpos += dt * ball.xspeed;
- ball.ypos += dt * ball.yspeed;
-
- // Did the ball hit a top/bottom wall?
- if( ball.ypos >= 1.0 )
- {
- ball.ypos = 2.0 - ball.ypos;
- ball.yspeed = -ball.yspeed;
- }
- else if( ball.ypos <= -1.0 )
- {
- ball.ypos = -2.0 - ball.ypos;
- ball.yspeed = -ball.yspeed;
- }
-
- // Did the ball hit/miss a player?
- event = NOBODY_WINS;
-
- // Is the ball entering the player 1 goal?
- if( ball.xpos < -1.0 + PLAYER_XSIZE )
- {
- // Did player 1 catch the ball?
- if( ball.ypos > (player1.ypos-PLAYER_YSIZE) &&
- ball.ypos < (player1.ypos+PLAYER_YSIZE) )
- {
- ball.xpos = -2.0 + 2.0*PLAYER_XSIZE - ball.xpos;
- ball.xspeed = -ball.xspeed;
- }
- else
- {
- event = PLAYER2_WINS;
- }
- }
-
- // Is the ball entering the player 2 goal?
- if( ball.xpos > 1.0 - PLAYER_XSIZE )
- {
- // Did player 2 catch the ball?
- if( ball.ypos > (player2.ypos-PLAYER_YSIZE) &&
- ball.ypos < (player2.ypos+PLAYER_YSIZE) )
- {
- ball.xpos = 2.0 - 2.0*PLAYER_XSIZE - ball.xpos;
- ball.xspeed = -ball.xspeed;
- }
- else
- {
- event = PLAYER1_WINS;
- }
- }
-
- return event;
-}
-
-
-//========================================================================
-// DrawBox() - Draw a 3D box
-//========================================================================
-
-#define TEX_SCALE 4.0f
-
-
-void DrawBox( float x1, float y1, float z1, float x2, float y2, float z2 )
-{
- // Draw six sides of a cube
- glBegin( GL_QUADS );
- // Side 1 (down)
- glNormal3f( 0.0f, 0.0f, -1.0f );
- glTexCoord2f( 0.0f, 0.0f );
- glVertex3f( x1,y2,z1 );
- glTexCoord2f( TEX_SCALE, 0.0f );
- glVertex3f( x2,y2,z1 );
- glTexCoord2f( TEX_SCALE, TEX_SCALE );
- glVertex3f( x2,y1,z1 );
- glTexCoord2f( 0.0f, TEX_SCALE );
- glVertex3f( x1,y1,z1 );
- // Side 2 (up)
- glNormal3f( 0.0f, 0.0f, 1.0f );
- glTexCoord2f( 0.0f, 0.0f );
- glVertex3f( x1,y1,z2 );
- glTexCoord2f( TEX_SCALE, 0.0f );
- glVertex3f( x2,y1,z2 );
- glTexCoord2f( TEX_SCALE, TEX_SCALE );
- glVertex3f( x2,y2,z2 );
- glTexCoord2f( 0.0f, TEX_SCALE );
- glVertex3f( x1,y2,z2 );
- // Side 3 (backward)
- glNormal3f( 0.0f, -1.0f, 0.0f );
- glTexCoord2f( 0.0f, 0.0f );
- glVertex3f( x1,y1,z1 );
- glTexCoord2f( TEX_SCALE, 0.0f );
- glVertex3f( x2,y1,z1 );
- glTexCoord2f( TEX_SCALE, TEX_SCALE );
- glVertex3f( x2,y1,z2 );
- glTexCoord2f( 0.0f, TEX_SCALE );
- glVertex3f( x1,y1,z2 );
- // Side 4 (forward)
- glNormal3f( 0.0f, 1.0f, 0.0f );
- glTexCoord2f( 0.0f, 0.0f );
- glVertex3f( x1,y2,z2 );
- glTexCoord2f( TEX_SCALE, 0.0f );
- glVertex3f( x2,y2,z2 );
- glTexCoord2f( TEX_SCALE, TEX_SCALE );
- glVertex3f( x2,y2,z1 );
- glTexCoord2f( 0.0f, TEX_SCALE );
- glVertex3f( x1,y2,z1 );
- // Side 5 (left)
- glNormal3f( -1.0f, 0.0f, 0.0f );
- glTexCoord2f( 0.0f, 0.0f );
- glVertex3f( x1,y1,z2 );
- glTexCoord2f( TEX_SCALE, 0.0f );
- glVertex3f( x1,y2,z2 );
- glTexCoord2f( TEX_SCALE, TEX_SCALE );
- glVertex3f( x1,y2,z1 );
- glTexCoord2f( 0.0f, TEX_SCALE );
- glVertex3f( x1,y1,z1 );
- // Side 6 (right)
- glNormal3f( 1.0f, 0.0f, 0.0f );
- glTexCoord2f( 0.0f, 0.0f );
- glVertex3f( x2,y1,z1 );
- glTexCoord2f( TEX_SCALE, 0.0f );
- glVertex3f( x2,y2,z1 );
- glTexCoord2f( TEX_SCALE, TEX_SCALE );
- glVertex3f( x2,y2,z2 );
- glTexCoord2f( 0.0f, TEX_SCALE );
- glVertex3f( x2,y1,z2 );
- glEnd();
-}
-
-
-//========================================================================
-// UpdateDisplay() - Draw graphics (all game related OpenGL stuff goes
-// here)
-//========================================================================
-
-void UpdateDisplay( void )
-{
- // Get window size
- glfwGetWindowSize( &width, &height );
-
- // Set viewport
- glViewport( 0, 0, width, height );
-
- // Clear display
- glClearColor( 0.02f, 0.02f, 0.02f, 0.0f );
- glClearDepth( 1.0f );
- glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
-
- // Setup projection matrix
- glMatrixMode( GL_PROJECTION );
- glLoadIdentity();
- gluPerspective(
- 55.0f, // Angle of view
- (GLfloat)width/(GLfloat)height, // Aspect
- 1.0f, // Near Z
- 100.0f // Far Z
- );
-
- // Setup modelview matrix
- glMatrixMode( GL_MODELVIEW );
- glLoadIdentity();
- switch( camerapos )
- {
- default:
- case CAMERA_CLASSIC:
- gluLookAt(
- 0.0f, 0.0f, 2.5f,
- 0.0f, 0.0f, 0.0f,
- 0.0f, 1.0f, 0.0f
- );
- break;
- case CAMERA_ABOVE:
- gluLookAt(
- 0.0f, 0.0f, 2.5f,
- (float)ball.xpos, (float)ball.ypos, 0.0f,
- 0.0f, 1.0f, 0.0f
- );
- break;
- case CAMERA_SPECTATOR:
- gluLookAt(
- 0.0f, -2.0, 1.2f,
- (float)ball.xpos, (float)ball.ypos, 0.0f,
- 0.0f, 0.0f, 1.0f
- );
- break;
- }
-
- // Enable depth testing
- glEnable( GL_DEPTH_TEST );
- glDepthFunc( GL_LEQUAL );
-
- // Enable lighting
- glEnable( GL_LIGHTING );
- glLightModelfv( GL_LIGHT_MODEL_AMBIENT, env_ambient );
- glLightModeli( GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE );
- glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE );
- glLightfv( GL_LIGHT1, GL_POSITION, light1_position );
- glLightfv( GL_LIGHT1, GL_DIFFUSE, light1_diffuse );
- glLightfv( GL_LIGHT1, GL_AMBIENT, light1_ambient );
- glEnable( GL_LIGHT1 );
-
- // Front face is counter-clock-wise
- glFrontFace( GL_CCW );
-
- // Enable face culling (not necessary, but speeds up rendering)
- glCullFace( GL_BACK );
- glEnable( GL_CULL_FACE );
-
- // Draw Player 1
- glMaterialfv( GL_FRONT, GL_DIFFUSE, player1_diffuse );
- glMaterialfv( GL_FRONT, GL_AMBIENT, player1_ambient );
- DrawBox( -1.f, (GLfloat)player1.ypos-PLAYER_YSIZE, 0.f,
- -1.f+PLAYER_XSIZE, (GLfloat)player1.ypos+PLAYER_YSIZE, 0.1f );
-
- // Draw Player 2
- glMaterialfv( GL_FRONT, GL_DIFFUSE, player2_diffuse );
- glMaterialfv( GL_FRONT, GL_AMBIENT, player2_ambient );
- DrawBox( 1.f-PLAYER_XSIZE, (GLfloat)player2.ypos-PLAYER_YSIZE, 0.f,
- 1.f, (GLfloat)player2.ypos+PLAYER_YSIZE, 0.1f );
-
- // Draw Ball
- glMaterialfv( GL_FRONT, GL_DIFFUSE, ball_diffuse );
- glMaterialfv( GL_FRONT, GL_AMBIENT, ball_ambient );
- DrawBox( (GLfloat)ball.xpos-BALL_SIZE, (GLfloat)ball.ypos-BALL_SIZE, 0.f,
- (GLfloat)ball.xpos+BALL_SIZE, (GLfloat)ball.ypos+BALL_SIZE, BALL_SIZE*2 );
-
- // Top game field border
- glMaterialfv( GL_FRONT, GL_DIFFUSE, border_diffuse );
- glMaterialfv( GL_FRONT, GL_AMBIENT, border_ambient );
- DrawBox( -1.1f, 1.0f, 0.0f, 1.1f, 1.1f, 0.1f );
- // Bottom game field border
- glColor3f( 0.0f, 0.0f, 0.7f );
- DrawBox( -1.1f, -1.1f, 0.0f, 1.1f, -1.0f, 0.1f );
- // Left game field border
- DrawBox( -1.1f, -1.0f, 0.0f, -1.0f, 1.0f, 0.1f );
- // Left game field border
- DrawBox( 1.0f, -1.0f, 0.0f, 1.1f, 1.0f, 0.1f );
-
- // Enable texturing
- glEnable( GL_TEXTURE_2D );
- glBindTexture( GL_TEXTURE_2D, tex_id[ TEX_FIELD ] );
-
- // Game field floor
- glMaterialfv( GL_FRONT, GL_DIFFUSE, floor_diffuse );
- glMaterialfv( GL_FRONT, GL_AMBIENT, floor_ambient );
- DrawBox( -1.01f, -1.01f, -0.01f, 1.01f, 1.01f, 0.0f );
-
- // Disable texturing
- glDisable( GL_TEXTURE_2D );
-
- // Disable face culling
- glDisable( GL_CULL_FACE );
-
- // Disable lighting
- glDisable( GL_LIGHTING );
-
- // Disable depth testing
- glDisable( GL_DEPTH_TEST );
-}
-
-
-//========================================================================
-// GameOver()
-//========================================================================
-
-void GameOver( void )
-{
- // Enable sticky keys
- glfwEnable( GLFW_STICKY_KEYS );
-
- // Until the user presses ESC or SPACE
- while( !glfwGetKey( GLFW_KEY_ESC ) && !glfwGetKey( ' ' ) &&
- glfwGetWindowParam( GLFW_OPENED ) )
- {
- // Draw display
- UpdateDisplay();
-
- // Setup projection matrix
- glMatrixMode( GL_PROJECTION );
- glLoadIdentity();
- glOrtho( 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f );
-
- // Setup modelview matrix
- glMatrixMode( GL_MODELVIEW );
- glLoadIdentity();
-
- // Enable blending
- glEnable( GL_BLEND );
-
- // Dim background
- glBlendFunc( GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA );
- glColor4f( 0.3f, 0.3f, 0.3f, 0.3f );
- glBegin( GL_QUADS );
- glVertex2f( 0.0f, 0.0f );
- glVertex2f( 1.0f, 0.0f );
- glVertex2f( 1.0f, 1.0f );
- glVertex2f( 0.0f, 1.0f );
- glEnd();
-
- // Display winner text
- glBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_COLOR );
- if( winner == PLAYER1 )
- {
- glColor4f( 1.0f, 0.5f, 0.5f, 1.0f );
- DrawImage( TEX_WINNER1, 0.35f, 0.65f, 0.46f, 0.54f );
- }
- else if( winner == PLAYER2 )
- {
- glColor4f( 0.5f, 1.0f, 0.5f, 1.0f );
- DrawImage( TEX_WINNER2, 0.35f, 0.65f, 0.46f, 0.54f );
- }
-
- // Disable blending
- glDisable( GL_BLEND );
-
- // Swap buffers
- glfwSwapBuffers();
- }
-
- // Disable sticky keys
- glfwDisable( GLFW_STICKY_KEYS );
-}
-
-
-//========================================================================
-// GameLoop() - Game loop
-//========================================================================
-
-void GameLoop( void )
-{
- int playing, event;
-
- // Initialize a new game
- NewGame();
-
- // Enable sticky keys
- glfwEnable( GLFW_STICKY_KEYS );
-
- // Loop until the game ends
- playing = GL_TRUE;
- while( playing && glfwGetWindowParam( GLFW_OPENED ) )
- {
- // Frame timer
- oldtime = thistime;
- thistime = glfwGetTime();
- dt = thistime - oldtime;
-
- // Get user input and update player positions
- PlayerControl();
-
- // Move the ball, and check if a player hits/misses the ball
- event = BallControl();
-
- // Did we have a winner?
- switch( event )
- {
- case PLAYER1_WINS:
- winner = PLAYER1;
- playing = GL_FALSE;
- break;
- case PLAYER2_WINS:
- winner = PLAYER2;
- playing = GL_FALSE;
- break;
- default:
- break;
- }
-
- // Did the user press ESC?
- if( glfwGetKey( GLFW_KEY_ESC ) )
- {
- playing = GL_FALSE;
- }
-
- // Did the user change camera view?
- if( glfwGetKey( '1' ) )
- {
- camerapos = CAMERA_CLASSIC;
- }
- else if( glfwGetKey( '2' ) )
- {
- camerapos = CAMERA_ABOVE;
- }
- else if( glfwGetKey( '3' ) )
- {
- camerapos = CAMERA_SPECTATOR;
- }
-
- // Draw display
- UpdateDisplay();
-
- // Swap buffers
- glfwSwapBuffers();
- }
-
- // Disable sticky keys
- glfwDisable( GLFW_STICKY_KEYS );
-
- // Show winner
- GameOver();
-}
-
-
-//========================================================================
-// main() - Program entry point
-//========================================================================
-
-int main( void )
-{
- int menuoption;
-
- // Initialize GLFW
- if( !glfwInit() )
- {
- fprintf( stderr, "Failed to initialize GLFW\n" );
- exit( EXIT_FAILURE );
- }
-
- // Open OpenGL window
- if( !glfwCreateWindow( WIDTH, HEIGHT, 0,0,0,0, 16,0, GLFW_FULLSCREEN ) )
- {
- fprintf( stderr, "Failed to open GLFW window\n" );
- glfwTerminate();
- exit( EXIT_FAILURE );
- }
-
- glfwSwapInterval( 1 );
-
- // Load all textures
- if( !LoadTextures() )
- {
- glfwTerminate();
- exit( EXIT_FAILURE );
- }
-
- // Main loop
- do
- {
- // Get menu option
- menuoption = GameMenu();
-
- // If the user wants to play, let him...
- if( menuoption == MENU_PLAY )
- {
- GameLoop();
- }
- }
- while( menuoption != MENU_QUIT );
-
- // Unload all textures
- if( glfwGetWindowParam( GLFW_OPENED ) )
- {
- glDeleteTextures( NUM_TEXTURES, tex_id );
- }
-
- // Terminate GLFW
- glfwTerminate();
-
- exit( EXIT_SUCCESS );
-}
-
diff --git a/examples/pong3d_field.tga b/examples/pong3d_field.tga
deleted file mode 100644
index cc20bbdb82434faa3bbc01d27fd199d5214c8b2f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 17816
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
T6XevNs>&3`i95eA{_g(;MdbFK
diff --git a/examples/pong3d_instr.tga b/examples/pong3d_instr.tga
deleted file mode 100644
index 758eb447a97187cef812cb9100aa94631954c256..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 21279
zcmeHP2Rzl?8-Hw~u1JaYUN7zOrunvumiCagw6_K-?eS|Wl1eG+&3!AgpOA8k(A#T3TA#+S)ogI;BdL($&?~)6*+mx^$T`Wds6&zP^6hvSken3=9no
z%atoRYdx^?T-t5?5%{RRyhG;G+gQKLqG{PBmGnVGq{dE>^7n>1PN7A;%0Y}Kk&>(;H?v}yC_pMSP(+t$LuqFuXo?c2BS(4j-ejvYI7>eRV&
z=fD2?t4o(IUAuPe)~#Fj?%jLz=+U!h&tAQH_3qufPoF-PmX=mlR(<>S?bokg|Ni|4
z3>YwQ;6Q6@>p_DC4IVsr$dDmJhYlS!Y}oMO!$*u5F>>U{QKLqU9zEK|#%9czF}Ak0
zW5&6LI($jMT-_KUc7k8k|j%*E_HNtbaHZXc6MI2
zY}xYV%U7&e;o{=5a^=cZt5&UEy?V`>HEY+dUAJ!C`t|EKY}l}I&OZQHkR-?3wdtE=nIojZ5!+U4fv=I-vkd-rY+505>2_IP@F?%lh0-@bkO
z_wV=e@;Y$fz`=tDy}i8;9XfRQ@L?YxA75YJBS(%LJ$m%-zyCgV?3kaQpHL|L=bwL$
zA3uKL#EFw9Px|}&2LuG1I&~^AF!1#0(`U|{IeYf(xpU`&f`ZPUKY!uEh2Y@eix)3m
zx^(IC<;z#DT)BGn>a}avu3x`?-O#2ckbM|d-rZ=XlPhi*u8uA
z?%%)v;K7534<9~y^yu;9$4{O-dHVF}vuDqqKY#w>#fz6OU%qhGS8$adC0+@$m@>317Z^
zNlZ-q`t@s4Qc`kqa!N`{YHDg)TH3d7-@bqUo}Qkbk&%&^nJE&9#9}cbvJOyUlqmtj
zWv7FEZPk?#@gGX7@$yCYD$P&c*8dOXNr|k~!;zF)J882LGX6tJ)&2KSZnP2}{i}Iu
zNaHZa&5`7xQ%J~BScOp{6JEi@tLPzi`-aKwF=QskevfqqPc7P44{Wvd!HU`kDfK!&^A$k63Q
zWr-*2w`&4Ew(rZJ<$PjR5SiU=#iBfN0%BQ{p4gCbMth!1sPIXW(9jz90CI1_7T>xj?nFUf
z`jO_|K1^$0PIA`SYU-T(%mLG
zwyaF<#sW;jJ)n~&HZB*Bv6_bORey*sB!@Y^%aYW5ZmfNd_Dpp8|A=q_Z_GY}l&dyJ
zs!bH9Xlk@TJTUG)`T^{qZnYG{X2J5W#+pwvsH>9+#3cZ;jzHP9xE;Vcvn}|cHCW*T
z05@Dgy{YF9(S_t_k~I{lQKy|AX_P~7ytJfQa4>c}CHg@0RA$Cu!dR%j=T1h;NPJ68
zu$0#;@3f#9)O&{B`21c{p^L*pPqk_7AG&17(eQ(m+#)D4VHsEkgc17t}=fZ}9CbF+;0!c$D4JrajSgESNTjB6yfGlxQ
z+5X{Pm?#sjnYd+b0@}*FQrj?GJc2njUjbahmN~xW(`&l8Mm}RI7p;Qb
z4!(v3-GmjGf8k;pE%k95t^$MIeAhPuFhHbjf6X;M?zCwxJI#gb!Er6BtT^mBMPoF#
z5~gz;uaTX>ams9N3WB+eRM{N&LmiPz>FGt14iHyPMa;l2<)J;|DbV?dnTQKv3w>FJ
zXcvR&h+BOzzCt>F;P=-z5VdjS_J-qB0>*zJ
z+JD9~q+gAIHi+90CtSj(zL@X=Gb$AVodRJt7U9)AxX|YuqCXRzp2pmkBZyw|bbKO$
zd|725lSovqb_#}hn6;j~jH#1TF|9g?Hkn`Zwix6elfu$6>m%6fB#E#j$m9r>NW3it1XJkBhG${+hlZTWSe{yAJfR@
zhp!8fj$)YOXY@IMm>(@X(V2&sgee^n$IwvL{t>3pX!cJceh;%noEnEoB0NZR1|W82
ze7ZkVYTjWY6vm9f;5<;{UdC&BhuOcC}F3Om)?
z7U;J~m*{9771}XbF*L?0F^w`23S&m&2|J`yyu1yG{$(UK?=%PwmWm)cvRE7~8kDCa
zla|fS74sIR#$smqY>|KM$&HSq(2Y%_QiZr29oSlAbaKUP7loL<_}2uohn5lOWe^
zsR{(rQxF}&k3(wE5J-=qL?
z*CMHqC>0%Ot+b?+wAq-Fj6Dh|ulW#DgrK$UCRM9sV?Aj=KC)40QT`RsLd$)LAf-yc
zMkwUpxF{t=@x&v9!rYg{ii`V^Vm;E`!QwY75e*bVZNzcCfV9uOxE7Soh}Zsd@|Vh5
zZ1CA(2}EbD&uraI{Qhk%$E_Y;8VnbE@1LauWjM|S>^QChsjcUC=D57^t8kpP8OPbp
zqf2@K6S>~7nlrHEIN3#C7TR>3k83}=@`~$y=IU(X)ix^$c0w62aPan;tqZ#3g9#jG
z0#i7L4jd=BF4!1xAM8eK{c#@&smt_%h!vSYiRmX|0WL&`l+%4eFS5`S7W7*G9%hK?
zN~i^*xO4=&@_G#)E2p#Y)a3znM0kY)#JcYBv^UdD!;Grg_G9}HdnDp(
z0vm`pVo_!VN|l1I2xkW3hCsR@I+^5lrROsX6{2$vv9Sf^g{LzF(IGn>cPgIG7)dY$
zYdcrO6v*-TmI{Gjw;sn2FqUAu5oyP4bRZl*nA0^)=*CZzCs1}LnpRvIveSVFK@^sP@^mgz@;Lgt
zrwE|~IEe1irp
zioN$|I*u9Ta?+tTL2)DTo0k9A5}2*9TgJgO|UuWEELhz^6a$}
zErrd{lM1}6F_F5eYOEKd
za|JOOZ%c*(f^GLRomyfV1hdgG5c4gZV9m18A$1cS2w2b~hlE`u$DSR3$eA3C4W69N
zL8lAfTUn2_R)|h01KE|JL3C0v`2$SH$H<{OoN+v^csqjQJnMoyG&A+-3sv`pGi>r?8(z{
z9hoZ~f~EY_!--Nl^B!Vq2Kw*7aEQcowJUg>rWt}YmC8R)k}fj0)>j&A*paCG^B*Z4
z8{w)$e*EF;3x7t3M!>12>jQ*yOSuRgVS{id%n@>{h5PpgjAWvIZ-~zBFDPV@JmS<@
z?M!U?YdRx@s}BlCv51e(GCITQK%?T?0O5RHXW?>KBis!$gxi)2z4n|M!y+-m<_%Zg
zbtqb8Jx3ksCGV5`Na?h%xpY8uT>UVaskVDyH%6yrDYp3dx$k7bI;R`N
zW8z)uxkXm^Pp&B>Xc`|n04-<$r9oFN&QElj;z+j0iJ2uANkJ#0Gf8rOrV;N>bTV~l
zHvc`HMmUx&a$@J@g{4k2m!$Kn2sGH+b;;8vX)+ywK&5oH5P=p&Qfhfk%5H5nfk2Z%
z=<1f@0d)yiE~nZ8focv|?b1rZ%84&=mXttA3H-m8z^*exi@gYn5HrC0b1~{aB_&sc
znA%`*uehtHXfe;y{-L;y(wOXBKF1zG^3VD8>!0weHksp?R_6lSAkrIfVffRXCKXw<
z=6rxYbJElOf*Zv#m++XItLBMW(w!lOqx?p(gKsej|LQT;(+EPQqRIzJ6
zyRlF)6YEH?e;V~Am6F`bF;9$~5fsN{*Eq>E%PHw+zc*=PcvBAFv&WzH>1I)53Rs0-
z7m($S8zi5TkoI{Jql1|D{zex0SGMAq?*7d>bg(S@D`DBg_VUUO3e(3oq@5J4((+@^
zbJe18@Dn0%11%)WMD}!FYDiV_yEnSBXt~>pW2$&<&0U=Q7f%@-0ej644VImZ#@0Yj
zxnG0ZU`FRb=<|#q$$uh{r4yt4LRc1>f4cyfYNsXJv%Ek^bW^Kp6srtRrwn@wPGi~$
zx{2517W>}>73sd{U4TS#$SPZ`WbG6L(;(I*FDmRii5|Q0yN9}jPQ|M=`lgF3L*)!{
zCHg-DUBE2K9-Poct$rqkob{9n6f3CW&zX;cV1ko4KQjl32D$+52$IG&@;#<3f!cwg
zVeVib5?mWf4OxWNdYaje8(5fvMq$kHe{|08Ukt+fEAFs>jq5uE@
diff --git a/examples/pong3d_menu.tga b/examples/pong3d_menu.tga
deleted file mode 100644
index d0d6c5a431687bfd6442e43c3878a9eb905c48b8..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1835
zcmbVMTawc-5FFd7KrLK{i*bQLe#C)-km6&1*q~+M0-Qi+$_YgGjAbh+zSu1}vefFC
z>1kQyjg-Il@=jjMRenoIj~IXChe+&m7gLVsZQ6^Zkej3-2jP$yvZRY#urXlCQ#Jn=lGQ*?msDM%?fom_#3D0WLYd>|
zrM8HEH4zdvDSlgLUu?J06w%uHU$F5BH?%^)6{nIMA=d
zk04{L6gsNXLvPU$y7bcLR=F~R;{Yz~AoE6761IR~EZy-Sc90#?@jjEKjdZ2xgd?t2
zqtTm)Fw3kYxE=nmQqN{37J-&5_=7y4JqUx5<790bksq4`P6Xm?W2J#gsv94$c80Ep
zhB(C(Tcv$%4nwfg>5lD?ncV@L-j6_|Q=WiD2HsmBQ7_k*Bwh}mIAS5_(&Zv)0+JQ*BV=)qzNL$W#l<{{t=av1
zh6fQJ3*Rf(fcY7y8DcVufs`P8r)X65U6x`6n^qTRM5LcL_vtFnz@hK-l+BKK+wK>L@83So!V-2z3xXZm}7`ZB*l$gvAC5tvMEd)H$-L
j=(lM?g-Vm}Fwx!OsnJ{IV_$0Jn){}t&ktU$$5s9UqXSX}
diff --git a/examples/pong3d_title.tga b/examples/pong3d_title.tga
deleted file mode 100644
index d0d8e36d74011c25b908647632b4997dc0cf6456..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 106516
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 |