New animated engine demo.
authorBrian Paul <brian.paul@tungstengraphics.com>
Tue, 4 Jul 2006 21:43:21 +0000 (21:43 +0000)
committerBrian Paul <brian.paul@tungstengraphics.com>
Tue, 4 Jul 2006 21:43:21 +0000 (21:43 +0000)
progs/demos/Makefile
progs/demos/engine.c [new file with mode: 0644]

index aadf33e1ba523ca1776f2756d573ecd8cdc687f2..44b79c26f1dbd37fbffbb04d905fcf613bfe4686 100644 (file)
@@ -21,6 +21,7 @@ PROGS = \
        clearspd \
        cubemap \
        drawpix \
+       engine \
        fire \
        fogcoord \
        fplight \
@@ -131,6 +132,13 @@ gloss.o: gloss.c trackball.h
        $(CC) -c -I$(INCDIR) $(CFLAGS) gloss.c
 
 
+engine: engine.o trackball.o readtex.o
+       $(CC) -I$(INCDIR) $(CFLAGS) engine.o trackball.o readtex.o $(APP_LIB_DEPS) -o $@
+
+engine.o: engine.c trackball.h
+       $(CC) -c -I$(INCDIR) $(CFLAGS) engine.c
+
+
 clean:
        -rm -f $(PROGS)
        -rm -f *.o *~
diff --git a/progs/demos/engine.c b/progs/demos/engine.c
new file mode 100644 (file)
index 0000000..8ef7346
--- /dev/null
@@ -0,0 +1,1099 @@
+/**
+ * Simple engine demo (crankshaft, pistons, connecting rods)
+ *
+ * Brian Paul
+ * June 2006
+ */
+
+#define GL_GLEXT_PROTOTYPES
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <GL/glut.h>
+#include "readtex.h"
+#include "trackball.h"
+
+
+#define DEG_TO_RAD(DEG)  ((DEG) * M_PI / 180.0)
+
+#define TEXTURE_FILE "../images/reflect.rgb"
+
+/* Target engine speed: */
+const int RPM = 100.0;
+
+
+/**
+ * Engine description.
+ */
+typedef struct
+{
+   const char *Name;
+   int Pistons;
+   int Cranks;
+   float V_Angle;
+   float PistonRadius;
+   float PistonHeight;
+   float WristPinRadius;
+   float Throw;
+   float CrankPlateThickness;
+   float CrankPinRadius;
+   float CrankJournalRadius;
+   float CrankJournalLength;
+   float ConnectingRodLength;
+   float ConnectingRodThickness;
+   /* display list IDs */
+   GLuint CrankList;
+   GLuint ConnRodList;
+   GLuint PistonList;
+} Engine;
+
+
+typedef struct
+{
+   float CurQuat[4];
+   float Distance;
+   /* When mouse is moving: */
+   GLboolean Rotating, Translating;
+   GLint StartX, StartY;
+   float StartDistance;
+} ViewInfo;
+
+
+typedef enum
+{
+   LIT,
+   WIREFRAME,
+   TEXTURED
+} RenderMode;
+
+
+typedef struct
+{
+   RenderMode Mode;
+   GLboolean Anim;
+   GLboolean Wireframe;
+   GLboolean Blend;
+   GLboolean Antialias;
+   GLboolean Texture;
+   GLboolean UseLists;
+   GLboolean DrawBox;
+   GLboolean ShowInfo;
+} RenderInfo;
+
+
+static GLUquadric *Q;
+
+static GLfloat Theta = 0.0;
+
+static GLfloat PistonColor[4] = { 1.0, 0.5, 0.5, 1.0 };
+static GLfloat ConnRodColor[4] = { 0.7, 1.0, 0.7, 1.0 };
+static GLfloat CrankshaftColor[4] = { 0.7, 0.7, 1.0, 1.0 };
+
+static GLuint TextureObj;
+static GLint WinWidth = 800, WinHeight = 500;
+
+static ViewInfo View;
+static RenderInfo Render;
+
+#define NUM_ENGINES 3
+static Engine Engines[NUM_ENGINES] =
+{
+   {
+      "V-6",
+      6,    /* Pistons */
+      3,    /* Cranks */
+      90.0, /* V_Angle */
+      0.5,  /* PistonRadius */
+      0.6,  /* PistonHeight */
+      0.1,  /* WristPinRadius */
+      0.5,  /* Throw */
+      0.2,  /* CrankPlateThickness */
+      0.25, /* CrankPinRadius */
+      0.3,  /* CrankJournalRadius */
+      0.4,  /* CrankJournalLength */
+      1.3,  /* ConnectingRodLength */
+      0.1   /* ConnectingRodThickness */
+   },
+   {
+      "Inline-4",
+      4,    /* Pistons */
+      4,    /* Cranks */
+      0.0,  /* V_Angle */
+      0.5,  /* PistonRadius */
+      0.6,  /* PistonHeight */
+      0.1,  /* WristPinRadius */
+      0.5,  /* Throw */
+      0.2,  /* CrankPlateThickness */
+      0.25, /* CrankPinRadius */
+      0.3,  /* CrankJournalRadius */
+      0.4,  /* CrankJournalLength */
+      1.3,  /* ConnectingRodLength */
+      0.1   /* ConnectingRodThickness */
+   },
+   {
+      "Boxer-6",
+      6,    /* Pistons */
+      3,    /* Cranks */
+      180.0,/* V_Angle */
+      0.5,  /* PistonRadius */
+      0.6,  /* PistonHeight */
+      0.1,  /* WristPinRadius */
+      0.5,  /* Throw */
+      0.2,  /* CrankPlateThickness */
+      0.25, /* CrankPinRadius */
+      0.3,  /* CrankJournalRadius */
+      0.4,  /* CrankJournalLength */
+      1.3,  /* ConnectingRodLength */
+      0.1   /* ConnectingRodThickness */
+   }
+};
+
+static int CurEngine = 0;
+
+
+
+static void
+InitViewInfo(ViewInfo *view)
+{
+   view->Rotating = GL_FALSE;
+   view->Translating = GL_FALSE;
+   view->StartX = view->StartY = 0;
+   view->Distance = 12.0;
+   view->StartDistance = 0.0;
+   view->CurQuat[0] = -0.194143;
+   view->CurQuat[1] = 0.507848;
+   view->CurQuat[2] = 0.115245;
+   view->CurQuat[3] = 0.831335;
+}
+
+
+static void
+InitRenderInfo(RenderInfo *render)
+{
+   render->Mode = LIT;
+   render->Anim = GL_TRUE;
+   render->Wireframe = GL_FALSE;
+   render->Blend = GL_FALSE;
+   render->Antialias = GL_FALSE;
+   render->Texture = GL_FALSE;
+   render->DrawBox = GL_FALSE;
+   render->ShowInfo = GL_TRUE;
+   render->UseLists = GL_FALSE;
+}
+
+
+/**
+ * Set GL for given rendering mode.
+ */
+static void
+SetRenderState(RenderMode mode)
+{
+   /* defaults */
+   glDisable(GL_LIGHTING);
+   glDisable(GL_TEXTURE_2D);
+   glDisable(GL_BLEND);
+   glDisable(GL_LINE_SMOOTH);
+   glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+   glDisable(GL_TEXTURE_GEN_S);
+   glDisable(GL_TEXTURE_GEN_T);
+
+   switch (mode) {
+   case LIT:
+      glEnable(GL_LIGHTING);
+      break;
+   case WIREFRAME:
+      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+      glEnable(GL_LINE_SMOOTH);
+      glEnable(GL_BLEND);
+      glLineWidth(1.5);
+      break;
+   case TEXTURED:
+      glEnable(GL_LIGHTING);
+      glEnable(GL_TEXTURE_2D);
+      glEnable(GL_TEXTURE_GEN_S);
+      glEnable(GL_TEXTURE_GEN_T);
+      break;
+   default:
+      ;
+   }
+}
+
+
+/**
+ * Animate the engine parts.
+ */
+static void
+Idle(void)
+{
+   /* convert degrees per millisecond to RPM: */
+   const float m = 360.0 / 1000.0 / 60.0;
+   GLint t = glutGet(GLUT_ELAPSED_TIME);
+   Theta = ((int) (t * RPM * m)) % 360;
+   glutPostRedisplay();
+}
+
+
+/**
+ * Compute piston's position along its stroke.
+ */
+static float
+PistonStrokePosition(float throwDist, float crankAngle, float connRodLength)
+{
+   float x = throwDist * cos(DEG_TO_RAD(crankAngle));
+   float y = throwDist * sin(DEG_TO_RAD(crankAngle));
+   float pos = y + sqrt(connRodLength * connRodLength - x * x);
+   return pos;
+}
+
+
+/**
+ * Compute position of nth piston along the crankshaft.
+ */
+static float
+PistonShaftPosition(const Engine *eng, int piston)
+{
+   const int i = piston / (eng->Pistons / eng->Cranks);
+   float z;
+   assert(piston < eng->Pistons);
+   z = 1.5 * eng->CrankJournalLength + eng->CrankPlateThickness
+      + i * (2.0 * (eng->CrankJournalLength + eng->CrankPlateThickness));
+   if (eng->Pistons > eng->Cranks) {
+      if (piston & 1)
+         z += eng->ConnectingRodThickness;
+      else
+         z -= eng->ConnectingRodThickness;
+   }
+   return z;
+}
+
+
+/**
+ * (x0, y0) = position of big end on crankshaft
+ * (x1, y1) = position of small end on piston
+ */
+static void
+ComputeConnectingRodPosition(float throwDist, float crankAngle,
+                             float connRodLength,
+                             float *x0, float *y0, float *x1, float *y1)
+{
+   *x0 = throwDist * cos(DEG_TO_RAD(crankAngle));
+   *y0 = throwDist * sin(DEG_TO_RAD(crankAngle));
+   *x1 = 0.0;
+   *y1 = PistonStrokePosition(throwDist, crankAngle, connRodLength);
+}
+
+
+/**
+ * Compute total length of the crankshaft.
+ */
+static float
+CrankshaftLength(const Engine *eng)
+{
+   float len = (eng->Cranks * 2 + 1) * eng->CrankJournalLength
+             + 2 * eng->Cranks * eng->CrankPlateThickness;
+   return len;
+}
+
+
+/**
+ * Draw a piston.
+ * Axis of piston = Z axis.  Wrist pin is centered on (0, 0, 0).
+ */
+static void
+DrawPiston(const Engine *eng)
+{
+   const int slices = 30, stacks = 4, loops = 4;
+   const float innerRadius = 0.9 * eng->PistonRadius;
+   const float innerHeight = eng->PistonHeight - 0.15;
+   const float wristPinLength = 1.8 * eng->PistonRadius;
+
+   assert(Q);
+
+   glPushMatrix();
+   glTranslatef(0, 0, -1.1 * eng->WristPinRadius);
+
+   gluQuadricOrientation(Q, GLU_INSIDE);
+
+   /* bottom rim */
+   gluDisk(Q, innerRadius, eng->PistonRadius, slices, 1/*loops*/);
+
+   /* inner cylinder */
+   gluCylinder(Q, innerRadius, innerRadius, innerHeight, slices, stacks);
+
+   /* inside top */
+   glPushMatrix();
+   glTranslatef(0, 0, innerHeight);
+   gluDisk(Q, 0, innerRadius, slices, loops);
+   glPopMatrix();
+
+   gluQuadricOrientation(Q, GLU_OUTSIDE);
+
+   /* outer cylinder */
+   gluCylinder(Q, eng->PistonRadius, eng->PistonRadius, eng->PistonHeight,
+               slices, stacks);
+
+   /* top */
+   glTranslatef(0, 0, eng->PistonHeight);
+   gluDisk(Q, 0, eng->PistonRadius, slices, loops);
+
+   glPopMatrix();
+
+   /* wrist pin */
+   glPushMatrix();
+   glTranslatef(0, 0.5 * wristPinLength, 0.0);
+   glRotatef(90, 1, 0, 0);
+   gluCylinder(Q, eng->WristPinRadius, eng->WristPinRadius, wristPinLength,
+               slices, stacks);
+   glPopMatrix();
+}
+
+
+/**
+ * Draw piston at particular position.
+ */
+static void
+DrawPositionedPiston(const Engine *eng, float crankAngle)
+{
+   const float pos = PistonStrokePosition(eng->Throw, crankAngle,
+                                          eng->ConnectingRodLength);
+   glPushMatrix();
+      glRotatef(-90, 1, 0, 0);
+      glTranslatef(0, 0, pos);
+      DrawPiston(eng);
+   glPopMatrix();
+}
+
+
+/**
+ * Draw connector plate.  Used for crankshaft and connecting rods.
+ */
+static void
+DrawConnector(float length, float thickness,
+              float bigEndRadius, float smallEndRadius)
+{
+   const float bigRadius = 1.2 * bigEndRadius;
+   const float smallRadius = 1.2 * smallEndRadius;
+   const float z0 = -0.5 * thickness, z1 = -z0;
+   GLfloat points[36][2], normals[36][2];
+   int i;
+
+   /* compute vertex locations, normals */
+   for (i = 0; i < 36; i++) {
+      const int angle = i * 10;
+      float x = cos(DEG_TO_RAD(angle));
+      float y = sin(DEG_TO_RAD(angle));
+      normals[i][0] = x;
+      normals[i][1] = y;
+      if (angle >= 0 && angle <= 180) {
+         x *= smallRadius;
+         y = y * smallRadius + length;
+      }
+      else {
+         x *= bigRadius;
+         y *= bigRadius;
+      }
+      points[i][0] = x;
+      points[i][1] = y;
+   }
+
+   /* front face */
+   glNormal3f(0, 0, 1);
+   glBegin(GL_POLYGON);
+   for (i = 0; i < 36; i++) {
+      glVertex3f(points[i][0], points[i][1], z1);
+   }
+   glEnd();
+
+   /* back face */
+   glNormal3f(0, 0, -1);
+   glBegin(GL_POLYGON);
+   for (i = 0; i < 36; i++) {
+      glVertex3f(points[35-i][0], points[35-i][1], z0);
+   }
+   glEnd();
+
+   /* edge */
+   glBegin(GL_QUAD_STRIP);
+   for (i = 0; i <= 36; i++) {
+      const int j = i % 36;
+      glNormal3f(normals[j][0], normals[j][1], 0);
+      glVertex3f(points[j][0], points[j][1], z0);
+      glVertex3f(points[j][0], points[j][1], z1);
+   }
+   glEnd();
+}
+
+
+/**
+ * Draw a crankshaft.  Shaft lies along +Z axis, starting at zero.
+ */
+static void
+DrawCrankshaft(const Engine *eng)
+{
+   const int slices = 20, stacks = 2;
+   const int n = eng->Cranks * 4 + 1;
+   const float phiStep = 360 / eng->Cranks;
+   float phi = -90.0;
+   int i;
+   float z = 0.0;
+
+   for (i = 0; i < n; i++) {
+      glPushMatrix();
+      glTranslatef(0, 0, z);
+      if (i & 1) {
+         /* draw a crank plate */
+         glRotatef(phi, 0, 0, 1);
+         glTranslatef(0, 0, 0.5 * eng->CrankPlateThickness);
+         DrawConnector(eng->Throw, eng->CrankPlateThickness,
+                       eng->CrankJournalRadius, eng->CrankPinRadius);
+         z += 0.2;
+         if (i % 4 == 3)
+            phi += phiStep;
+      }
+      else if (i % 4 == 0) {
+         /* draw crank journal segment */
+         gluCylinder(Q, eng->CrankJournalRadius, eng->CrankJournalRadius,
+                     eng->CrankJournalLength, slices, stacks);
+         z += eng->CrankJournalLength;
+      }
+      else if (i % 4 == 2) {
+         /* draw crank pin segment */
+         glRotatef(phi, 0, 0, 1);
+         glTranslatef(0, eng->Throw, 0);
+         gluCylinder(Q, eng->CrankPinRadius, eng->CrankPinRadius,
+                     eng->CrankJournalLength, slices, stacks);
+         z += eng->CrankJournalLength;
+      }
+      glPopMatrix();
+   }
+}
+
+
+/**
+ * Draw crankshaft at a particular rotation.
+ * \param crankAngle  current crankshaft rotation, in radians
+ */
+static void
+DrawPositionedCrankshaft(const Engine *eng, float crankAngle)
+{
+   glPushMatrix();
+      glRotatef(crankAngle, 0, 0, 1);
+      if (eng->CrankList)
+         glCallList(eng->CrankList);
+      else
+         DrawCrankshaft(eng);
+   glPopMatrix();
+}
+
+
+/**
+ * Draw a connecting rod at particular position.
+ * \param eng  description of connecting rod to draw
+ * \param crankAngle  current crankshaft rotation, in radians
+ */
+static void
+DrawPositionedConnectingRod(const Engine *eng, float crankAngle)
+{
+   float x0, y0, x1, y1;
+   float d, phi;
+
+   ComputeConnectingRodPosition(eng->Throw, crankAngle,
+                                eng->ConnectingRodLength,
+                                &x0, &y0, &x1, &y1);
+   d = sqrt(eng->ConnectingRodLength * eng->ConnectingRodLength - x0 * x0);
+   phi = atan(x0 / d) * 180.0 / M_PI;
+
+   glPushMatrix();
+      glTranslatef(x0, y0, 0);
+      glRotatef(phi, 0, 0, 1);
+      if (eng->ConnRodList)
+         glCallList(eng->ConnRodList);
+      else
+         DrawConnector(eng->ConnectingRodLength, eng->ConnectingRodThickness,
+                       eng->CrankPinRadius, eng->WristPinRadius);
+   glPopMatrix();
+}
+
+
+/**
+ * Generate display lists for engine parts.
+ */
+static void
+GenerateDisplayLists(Engine *eng)
+{
+   eng->CrankList = glGenLists(1);
+   glNewList(eng->CrankList, GL_COMPILE);
+   DrawCrankshaft(eng);
+   glEndList();
+
+   eng->ConnRodList = glGenLists(1);
+   glNewList(eng->ConnRodList, GL_COMPILE);
+   DrawConnector(eng->ConnectingRodLength, eng->ConnectingRodThickness,
+                 eng->CrankPinRadius, eng->WristPinRadius);
+   glEndList();
+
+   eng->PistonList = glGenLists(1);
+   glNewList(eng->PistonList, GL_COMPILE);
+   DrawPiston(eng);
+   glEndList();
+}
+
+
+/**
+ * Free engine display lists (render with immediate mode).
+ */
+static void
+FreeDisplayLists(Engine *eng)
+{
+   glDeleteLists(eng->CrankList, 1);
+   eng->CrankList = 0;
+   glDeleteLists(eng->ConnRodList, 1);
+   eng->ConnRodList = 0;
+   glDeleteLists(eng->PistonList, 1);
+   eng->PistonList = 0;
+}
+
+
+
+/**
+ * Draw complete engine.
+ * \param eng  description of engine to draw
+ * \param crankAngle  current crankshaft angle, in radians
+ */
+static void
+DrawEngine(const Engine *eng, float crankAngle)
+{
+   const float crankDelta = 360.0 / eng->Cranks;
+   const float crankLen = CrankshaftLength(eng);
+   const int pistonsPerCrank = eng->Pistons / eng->Cranks;
+   int i;
+
+   glPushMatrix();
+   glRotatef(eng->V_Angle * 0.5, 0, 0, 1);
+   glTranslatef(0, 0, -0.5 * crankLen);
+
+   /* crankshaft */
+   glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, CrankshaftColor);
+   glColor4fv(CrankshaftColor);
+   DrawPositionedCrankshaft(eng, crankAngle);
+
+   for (i = 0; i < eng->Pistons; i++) {
+      const float z = PistonShaftPosition(eng, i);
+      const int crank = i / pistonsPerCrank;
+      float rot = crankAngle + crank * crankDelta;
+      int k;
+
+      glPushMatrix();
+         glTranslatef(0, 0, z);
+
+         /* additional rotation for kth piston per crank */
+         k = i % pistonsPerCrank;
+         glRotatef(k * -eng->V_Angle, 0, 0, 1);
+         rot += k * eng->V_Angle;
+
+         /* piston */
+         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, PistonColor);
+         glColor4fv(PistonColor);
+         DrawPositionedPiston(eng, rot);
+
+         /* connecting rod */
+         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, ConnRodColor);
+         glColor4fv(ConnRodColor);
+         DrawPositionedConnectingRod(eng, rot);
+
+      glPopMatrix();
+   }
+
+   glPopMatrix();
+}
+
+
+static void
+DrawBox(void)
+{
+   const float xmin = -3.0, xmax = 3.0;
+   const float ymin = -1.0, ymax = 3.0;
+   const float zmin = -4.0, zmax = 4.0;
+   const float step = 0.5;
+   const float d = 0.01;
+   float x, y, z;
+   GLboolean lit = glIsEnabled(GL_LIGHTING);
+   GLboolean tex = glIsEnabled(GL_TEXTURE_2D);
+
+   glDisable(GL_LIGHTING);
+   glDisable(GL_TEXTURE_2D);
+
+   glColor3f(1, 1, 1);
+
+   /* Z min */
+   glBegin(GL_LINES);
+   for (x = xmin; x <= xmax; x += step) {
+      glVertex3f(x, ymin, zmin);
+      glVertex3f(x, ymax, zmin);
+   }
+   glEnd();
+   glBegin(GL_LINES);
+   for (y = ymin; y <= ymax; y += step) {
+      glVertex3f(xmin, y, zmin);
+      glVertex3f(xmax, y, zmin);
+   }
+   glEnd();
+
+   /* Y min */
+   glBegin(GL_LINES);
+   for (x = xmin; x <= xmax; x += step) {
+      glVertex3f(x, ymin, zmin);
+      glVertex3f(x, ymin, zmax);
+   }
+   glEnd();
+   glBegin(GL_LINES);
+   for (z = zmin; z <= zmax; z += step) {
+      glVertex3f(xmin, ymin, z);
+      glVertex3f(xmax, ymin, z);
+   }
+   glEnd();
+
+   /* X min */
+   glBegin(GL_LINES);
+   for (y = ymin; y <= ymax; y += step) {
+      glVertex3f(xmin, y, zmin);
+      glVertex3f(xmin, y, zmax);
+   }
+   glEnd();
+   glBegin(GL_LINES);
+   for (z = zmin; z <= zmax; z += step) {
+      glVertex3f(xmin, ymin, z);
+      glVertex3f(xmin, ymax, z);
+   }
+   glEnd();
+
+   glColor3f(0.4, 0.4, 0.6);
+   glBegin(GL_QUADS);
+   /* xmin */
+   glVertex3f(xmin-d, ymin, zmin);
+   glVertex3f(xmin-d, ymax, zmin);
+   glVertex3f(xmin-d, ymax, zmax);
+   glVertex3f(xmin-d, ymin, zmax);
+   /* ymin */
+   glVertex3f(xmin, ymin-d, zmin);
+   glVertex3f(xmax, ymin-d, zmin);
+   glVertex3f(xmax, ymin-d, zmax);
+   glVertex3f(xmin, ymin-d, zmax);
+   /* zmin */
+   glVertex3f(xmin, ymin, zmin-d);
+   glVertex3f(xmax, ymin, zmin-d);
+   glVertex3f(xmax, ymax, zmin-d);
+   glVertex3f(xmin, ymax, zmin-d);
+   glEnd();
+
+   if (lit)
+      glEnable(GL_LIGHTING);
+   if (tex)
+      glEnable(GL_TEXTURE_2D);
+}
+
+
+static void
+PrintString(const char *s)
+{
+   while (*s) {
+      glutBitmapCharacter(GLUT_BITMAP_8_BY_13, (int) *s);
+      s++;
+   }
+}
+
+
+static int
+ComputeFPS(void)
+{
+   static double t0 = -1.0;
+   static int frames = 0;
+   double t = glutGet(GLUT_ELAPSED_TIME) / 1000.0;
+   static int fps = 0;
+
+   frames++;
+
+   if (t0 < 0.0) {
+      t0 = t;
+      fps = 0;
+   }
+   else if (t - t0 >= 1.0) {
+      fps = (int) (frames / (t - t0) + 0.5);
+      t0 = t;
+      frames = 0;
+   }
+
+   return fps;
+}
+
+
+static void
+Draw(void)
+{
+   int fps;
+   GLfloat rot[4][4];
+
+   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+   glPushMatrix();
+
+      glTranslatef(0.0, 0.0, -View.Distance);
+      build_rotmatrix(rot, View.CurQuat);
+      glMultMatrixf(&rot[0][0]);
+
+      glPushMatrix();
+         glTranslatef(0, -0.75, 0);
+         DrawEngine(Engines + CurEngine, Theta);
+         if (Render.DrawBox)
+            DrawBox();
+      glPopMatrix();
+
+   glPopMatrix();
+
+   fps = ComputeFPS();
+   if (Render.ShowInfo) {
+      GLboolean lit = glIsEnabled(GL_LIGHTING);
+      GLboolean tex = glIsEnabled(GL_TEXTURE_2D);
+      char s[100];
+      sprintf(s, "%s  %d FPS  %s", Engines[CurEngine].Name, fps,
+              Render.UseLists ? "Display Lists" : "Immediate mode");
+      glDisable(GL_LIGHTING);
+      glDisable(GL_TEXTURE_2D);
+      glColor3f(1, 1 , 1);
+      glWindowPos2iARB(10, 10);
+      PrintString(s);
+      if (lit)
+        glEnable(GL_LIGHTING);
+      if (tex)
+        glEnable(GL_TEXTURE_2D);
+   }
+
+   glutSwapBuffers();
+}
+
+
+/**
+ * Handle window resize.
+ */
+static void
+Reshape(int width, int height)
+{
+   float ar = (float) width / height;
+   float s = 0.5;
+   glViewport(0, 0, width, height);
+   glMatrixMode(GL_PROJECTION);
+   glLoadIdentity();
+   glFrustum(-ar * s, ar * s, -s, s, 2.0, 50.0);
+   glMatrixMode(GL_MODELVIEW);
+   glLoadIdentity();
+   WinWidth = width;
+   WinHeight = height;
+}
+
+
+/**
+ * Handle mouse button.
+ */
+static void
+Mouse(int button, int state, int x, int y)
+{
+   if (button == GLUT_LEFT_BUTTON) {
+      if (state == GLUT_DOWN) {
+         View.StartX = x;
+         View.StartY = y;
+         View.Rotating = GL_TRUE;
+      }
+      else if (state == GLUT_UP) {
+         View.Rotating = GL_FALSE;
+      }
+   }
+   else if (button == GLUT_MIDDLE_BUTTON) {
+      if (state == GLUT_DOWN) {
+         View.StartX = x;
+         View.StartY = y;
+         View.StartDistance = View.Distance;
+         View.Translating = GL_TRUE;
+      }
+      else if (state == GLUT_UP) {
+         View.Translating = GL_FALSE;
+      }
+   }
+}
+
+
+/**
+ * Handle mouse motion
+ */
+static void
+Motion(int x, int y)
+{
+   int i;
+   if (View.Rotating) {
+      float x0 = (2.0 * View.StartX - WinWidth) / WinWidth;
+      float y0 = (WinHeight - 2.0 * View.StartY) / WinHeight;
+      float x1 = (2.0 * x - WinWidth) / WinWidth;
+      float y1 = (WinHeight - 2.0 * y) / WinHeight;
+      float q[4];
+
+      trackball(q, x0, y0, x1, y1);
+      View.StartX = x;
+      View.StartY = y;
+      for (i = 0; i < 1; i++)
+         add_quats(q, View.CurQuat, View.CurQuat);
+
+      glutPostRedisplay();
+   }
+   else if (View.Translating) {
+      float dz = 0.01 * (y - View.StartY);
+      View.Distance = View.StartDistance + dz;
+      glutPostRedisplay();
+   }
+}
+
+
+/**
+ ** Menu Callbacks
+ **/
+
+static void
+OptAnimation(void)
+{
+   Render.Anim = !Render.Anim;
+   if (Render.Anim)
+      glutIdleFunc(Idle);
+   else
+      glutIdleFunc(NULL);
+}
+
+static void
+OptChangeEngine(void)
+{
+   CurEngine = (CurEngine + 1) % NUM_ENGINES;
+}
+
+static void
+OptRenderMode(void)
+{
+   Render.Mode++;
+   if (Render.Mode > TEXTURED)
+      Render.Mode = 0;
+   SetRenderState(Render.Mode);
+}
+
+static void
+OptDisplayLists(void)
+{
+   int i;
+   Render.UseLists = !Render.UseLists;
+   if (Render.UseLists) {
+      for (i = 0; i < NUM_ENGINES; i++) {
+         GenerateDisplayLists(Engines + i);
+      }
+   }
+   else {
+      for (i = 0; i < NUM_ENGINES; i++) {
+         FreeDisplayLists(Engines + i);
+      }
+   }
+}
+
+static void
+OptShowInfo(void)
+{
+   Render.ShowInfo = !Render.ShowInfo;
+}
+
+static void
+OptShowBox(void)
+{
+   Render.DrawBox = !Render.DrawBox;
+}
+
+static void
+OptRotate(void)
+{
+   Theta += 5.0;
+}
+
+static void
+OptExit(void)
+{
+   exit(0);
+}
+
+
+/**
+ * Define menu entries (w/ keyboard shortcuts)
+ */
+
+typedef struct
+{
+   const char *Text;
+   const char Key;
+   void (*Function)(void);
+} MenuInfo;
+
+static const MenuInfo MenuItems[] = {
+   { "Animation", 'a', OptAnimation },
+   { "Change Engine", 'e', OptChangeEngine },
+   { "Rendering Style", 'm', OptRenderMode },
+   { "Display Lists", 'd', OptDisplayLists },
+   { "Show Info", 'i', OptShowInfo },
+   { "Show Box", 'b', OptShowBox },
+   { "Exit", 27, OptExit },
+   { NULL, 'r', OptRotate },
+   { NULL, 0, NULL }
+};
+
+
+/**
+ * Handle menu selection.
+ */
+static void
+MenuHandler(int entry)
+{
+   MenuItems[entry].Function();
+   glutPostRedisplay();
+}
+
+
+/**
+ * Make pop-up menu.
+ */
+static void
+MakeMenu(void)
+{
+   int i;
+   glutCreateMenu(MenuHandler);
+   for (i = 0; MenuItems[i].Text; i++) {
+      glutAddMenuEntry(MenuItems[i].Text, i);
+   }
+   glutAttachMenu(GLUT_RIGHT_BUTTON);
+}
+
+
+/**
+ * Handle keyboard event.
+ */
+static void
+Key(unsigned char key, int x, int y)
+{
+   int i;
+   (void) x; (void) y;
+   for (i = 0; MenuItems[i].Key; i++) {
+      if (MenuItems[i].Key == key) {
+         MenuItems[i].Function();
+         glutPostRedisplay();
+         break;
+      }
+   }
+}
+
+
+static
+void LoadTexture(void)
+{
+   GLboolean convolve = GL_FALSE;
+
+   glGenTextures(1, &TextureObj);
+   glBindTexture(GL_TEXTURE_2D, TextureObj);
+   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+   glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+   glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+
+   if (convolve) {
+#define FILTER_SIZE 7
+      /* use convolution to blur the texture to simulate a dull finish
+       * on the object.
+       */
+      GLubyte *img;
+      GLenum format;
+      GLint w, h;
+      GLfloat filter[FILTER_SIZE][FILTER_SIZE][4];
+
+      for (h = 0; h < FILTER_SIZE; h++) {
+         for (w = 0; w < FILTER_SIZE; w++) {
+            const GLfloat k = 1.0 / (FILTER_SIZE * FILTER_SIZE);
+            filter[h][w][0] = k;
+            filter[h][w][1] = k;
+            filter[h][w][2] = k;
+            filter[h][w][3] = k;
+         }
+      }
+
+      glEnable(GL_CONVOLUTION_2D);
+      glConvolutionParameteri(GL_CONVOLUTION_2D,
+                              GL_CONVOLUTION_BORDER_MODE, GL_CONSTANT_BORDER);
+      glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGBA,
+                            FILTER_SIZE, FILTER_SIZE,
+                            GL_RGBA, GL_FLOAT, filter);
+
+      img = LoadRGBImage(TEXTURE_FILE, &w, &h, &format);
+      if (!img) {
+         printf("Error: couldn't load texture image file %s\n", TEXTURE_FILE);
+         exit(1);
+      }
+
+      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0,
+                   format, GL_UNSIGNED_BYTE, img);
+      free(img);
+   }
+   else {
+      if (!LoadRGBMipmaps(TEXTURE_FILE, GL_RGB)) {
+         printf("Error: couldn't load texture image file %s\n", TEXTURE_FILE);
+         exit(1);
+      }
+   }
+}
+
+
+static void
+Init(void)
+{
+   const GLfloat lightColor[4] = { 0.7, 0.7, 0.7, 1.0 };
+   const GLfloat specular[4] = { 0.8, 0.8, 0.8, 1.0 };
+
+   Q = gluNewQuadric();
+   gluQuadricNormals(Q, GLU_SMOOTH);
+
+   LoadTexture();
+
+   glClearColor(0.3, 0.3, 0.3, 0.0);
+   glEnable(GL_DEPTH_TEST);
+   glEnable(GL_LIGHTING);
+   glEnable(GL_LIGHT0);
+   glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor);
+   glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 40);
+   glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
+   glEnable(GL_NORMALIZE);
+
+   glBlendFunc(GL_SRC_ALPHA, GL_ZERO);
+   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+   InitViewInfo(&View);
+   InitRenderInfo(&Render);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+   glutInit(&argc, argv);
+   glutInitWindowSize(WinWidth, WinHeight);
+   glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
+   glutCreateWindow("OpenGL Engine Demo");
+   glutReshapeFunc(Reshape);
+   glutMouseFunc(Mouse);
+   glutMotionFunc(Motion);
+   glutKeyboardFunc(Key);
+   glutDisplayFunc(Draw);
+   MakeMenu();
+   Init();
+   if (Render.Anim)
+      glutIdleFunc(Idle);
+   glutMainLoop();
+   return 0;
+}