summaryrefslogtreecommitdiffstats
path: root/progs/demos
diff options
context:
space:
mode:
authorBrian Paul <[email protected]>2006-07-04 21:43:21 +0000
committerBrian Paul <[email protected]>2006-07-04 21:43:21 +0000
commit1ff8daf21e570fe9525bc5075ecdd52d424891c8 (patch)
tree0aff7cee95d66230c3d26559aef984acbb45d387 /progs/demos
parent1ad914575a89f3fdbfa5bce863e14f8d4679ed3b (diff)
New animated engine demo.
Diffstat (limited to 'progs/demos')
-rw-r--r--progs/demos/Makefile8
-rw-r--r--progs/demos/engine.c1099
2 files changed, 1107 insertions, 0 deletions
diff --git a/progs/demos/Makefile b/progs/demos/Makefile
index aadf33e1ba5..44b79c26f1d 100644
--- a/progs/demos/Makefile
+++ b/progs/demos/Makefile
@@ -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
index 00000000000..8ef73462177
--- /dev/null
+++ b/progs/demos/engine.c
@@ -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;
+}