An OpenGL Flight Simulator

Writing a flight simulator is a nice way to learn about the mathematics of space, the concepts of pitch, yaw, and roll, and making fractal landscapes.

flight.png

flight-wire.png

Geometric Primitives in C++

Let's start with the basics: points, vectors, rays, and planes. These are basic classes that have nothing to do with OpenGL.

geometry.h
// Classes and utility functions for three-dimensional Points, Vectors,
// Planes, and Rays.

#ifndef GEOMETRY_H_
#define GEOMETRY_H_

#include <cmath>

class Point;
class Vector;
class Plane;
class Ray;

// Comparing doubles for equality is useless; allow clients to supply a
// tolerance.
inline bool equal(double x, double y, double epsilon = 0.000001) {
  return fabs(x - y) <= epsilon;
}

// A class for 3-D Vectors.
//
//   v.i, v.j, v.k              Components of vector v
//   Vector(i, j, k)            Construct from components
//   Vector(p)                  Construct from a point
//   u + v, u += v              Vector addition
//   u - v, u -= v              Vector subtraction
//   -v                         <0, 0, 0> - v
//   u.dot(v)                   Dot product of u and v
//   u.cross(v)                 Cross product of u and v
//   v * c, c * v, v *= c       Multiplication of a vector and a scalar
//   v / c, v /= c              Division of a vector by a scalar
//   v.magnitude()              The length of v
//   unit(v)                    The vector of length 1 in the direction of v
//   normalize(v)               Changes v to unit(v)
//   cosine(u, v)               The cosine of the angle from u to v
//   u.isPerpendicularTo(v)     Whether u is almost perpendicular to v
//   u.isParallelTo(v)          Whether u is almost parallel to v
//   u.projectionOnto(v)        The projection of u onto v
//   u.reflectionAbout(v)       The mirror image of u over v

class Vector {
public:
  double i, j, k;
  Vector(double i = 0, double j = 0, double k = 0): i(i), j(j), k(k) {}
  Vector(Point p);
  Vector operator +(Vector v) {return Vector(i + v.i, j + v.j, k + v.k);}
  Vector& operator +=(Vector v) {i += v.i; j += v.j; k += v.k; return *this;}
  Vector operator -(Vector v) {return Vector(i - v.i, j - v.j, k - v.k);}
  Vector& operator -=(Vector v) {i -= v.i; j -= v.j; k -= v.k; return *this;}
  Vector operator -() {return Vector(-i, -j, -k);}
  double dot(Vector v) {return i * v.i + j * v.j + k * v.k;}
  Vector cross(Vector);
  Vector operator *(double c) {return Vector(i * c, j * c, k * c);}
  friend Vector operator *(double c, Vector v) {return v * c;}
  Vector& operator *=(Vector v) {i *= v.i; j *= v.j; k *= v.k; return *this;}
  Vector operator /(double c) {return Vector(i / c, j / c, k / c);}
  Vector& operator /=(double c) {i /= c; j /= c; k /= c; return *this;}
  double magnitude() {return sqrt(this->dot(*this));}
  friend Vector unit(Vector v) {return v / v.magnitude();}
  friend void normalize(Vector& v) {v /= v.magnitude();}
  friend double cosine(Vector u, Vector v) {return unit(u).dot(unit(v));}
  bool isPerpendicularTo(Vector v) {return equal(this->dot(v), 0);}
  bool isParallelTo(Vector v) {return equal(cosine(*this, v), 1.0);}
  Vector projectionOnto(Vector v) {return this->dot(unit(v)) * unit(v);}
  Vector reflectionAbout(Vector v) {return 2 * projectionOnto(v) - *this;}
};

// A class for 3-D Points.
//
//   p.x, p.y, p.z              Components (coordinates) of point p
//   p + v, p += v              Add a point to a vector
//   p - q                      The vector from q to p
//   p.distanceTo(q)            The distance between p and q
//   p.distanceTo(P)            The distance between p and the plane P

class Point {
public:
  double x, y, z;
  Point(double x = 0, double y = 0, double z = 0): x(x), y(y), z(z) {}
  Point operator +(Vector v) {return Point(x + v.i, y + v.j, z + v.k);}
  Point& operator +=(Vector v) {x += v.i; y += v.j; z += v.k; return *this;}
  Vector operator -(Point p) {return Vector(x - p.x, y - p.y, z - p.z);}
  double distanceTo(Point p) {return (p - *this).magnitude();}
  double distanceTo(Plane);
};

// A class for 3-D planes.
//
//   P.a, P.b, P.c, P.d         The components of plane P (P is the set of
//                              all points (x, y, z) for which P.a * x +
//                              P.b * y + P.c * z + P.d = 0)
//   Plane(a, b, c d)           Construct from components
//   Plane(p1, p2, p3)          Construct by giving three points on the plane
//                              (may fail if the points are collinear): the
//                              plane's normal is obtained by a right hand
//                              rule: curl your right hand ccw around p1 to
//                              p2 to p3 then your thumb orients the normal
//   P.normal()                 The vector <P.a, P.b, P.c>

class Plane {
public:
  double a, b, c, d;
  Plane(double a = 0, double b = 0, double c = 1, double d = 0);
  Plane(Point p1, Point p2, Point p3);
  Vector normal() {return Vector(a, b, c);}
};

// A class for 3-D rays.
//
//   r.origin, r.direction      The components of the ray r
//   Ray(origin, direction)     Construct from components
//   r(u)                       The point on r at distance u * |r.direction|
//                              from r.origin.

class Ray {
public:
  Point origin;
  Vector direction;
  Ray(Point origin, Vector direction): origin(origin), direction(direction) {}
  Point operator()(double u) {return origin + u * direction;}
};

// Bodies of inlined operations.

inline Vector::Vector(Point p): i(p.x), j(p.y), k(p.z) {
}

inline Vector Vector::cross(Vector v) {
  return Vector(j * v.k - k * v.j, k * v.i - i * v.k, i * v.j - j * v.i);
}

inline Plane::Plane(double a, double b, double c, double d):
  a(a), b(b), c(c), d(d)
{
}

inline double Point::distanceTo(Plane P) {
  return fabs(P.a * x + P.b * y + P.c * z + P.d) / P.normal().magnitude();
}

inline Plane::Plane(Point p1, Point p2, Point p3) {
  Vector n = (p2 - p1).cross(p3 - p1);
  a = n.i;
  b = n.j;
  c = n.k;
  d = -(Vector(p1).dot(n));
}

#endif

A Spaceship Class

Here's a C++ spaceship class. The ship is represented with a position, orientation, and current speed. It is also completely independent of OpenGL.

ship.h
// A simplified spaceship that you can use in flybys, flight simulators and
// other 3D programs.  A ship has a position, an orientation and a speed.
// The position is simply a point.  The orientation is given by three
// UNIT vectors: (1) forward, the vector along which the ship is currently
// moving, (2) up, a vector perpendicular to forward that describes which
// direction is "up" to someone sitting in the ship, and (3) right, which
// is really just the cross product of forward and up that we store to
// simplify most of the calculations.  The speed of the ship is simply the
// distance that the ship moves every time fly() is called.
//
// The public members of the Ship class are:
//
// Ship()           initialize the ship so that it is located at the
//                  origin, is travelling in the direction <0, 0, -1>
//                  that is, along the -z axis, has up vector <0, 1, 0>
//                  (and therefore right = <1, 0, 0>) and has initial
//                  speed 0.01.
// getPosition()    return the current position of the ship.
// teleport(p)      move the ship to absolute position p, but preserve
//                  the orientation and speed.
// getDirection()   return the current direction of the ship.
// getVertical()    return the "up" vector of the ship.
// pitch(theta)     reorient the ship so that it is rising theta radians;
//                  technically, rotate forward and up theta radians
//                  around right.
// roll(theta)      rotate up and right theta radians around forward.
// yaw(theta)       rotate forward and right theta radians around up.
// getSpeed()       return the current speed.
// setSpeed(s)      set the current speed to s.

#ifndef SHIP_H_
#define SHIP_H_

#include "geometry.h"

class Ship {
  Point position;
  Vector forward, up, right;
  double speed;
public:
  Ship(Point initialPosition = Point(0, 0, 0));
  Point getPosition() {return position;}
  void fly() {position += speed * forward;}
  void teleport(Point newPosition) {position = newPosition;}
  Vector getDirection() {return forward;}
  Vector getVertical() {return up;}
  void pitch(double angle);
  void roll(double angle);
  void yaw(double angle);
  double getSpeed() {return speed;}
  void setSpeed(double newSpeed) {speed = newSpeed;}
};

inline Ship::Ship(Point initialPosition):
  position(initialPosition),
  forward(0, 0, -1),
  up(0, 1, 0),
  right(1, 0, 0),
  speed(0.01)
{
}

inline void Ship::pitch(double angle) {
  forward = unit(forward * cos(angle) + up * sin(angle));
  up = right.cross(forward);
}

inline void Ship::roll(double angle) {
  right = unit(right * cos(angle) + up * sin(angle));
  up = right.cross(forward);
}

inline void Ship::yaw(double angle) {
  right = unit(right * cos(angle) + forward * sin(angle));
  forward = up.cross(right);
}

#endif

A Landscape Class

Here's a fractal landscape class. It is OpenGL-dependent, though it should probably be refactored into a non-OpenGL class and an OpenGL-dependent wrapper.

landscape.h
// A simple landscape class.  A landscape is essentially an elevation grid
// which can be drawn in wireframe or as a solid.  Landscape elevations are
// set using the Random Midpoint Displacement technique.
//
// Landscape(m, n)      (constructor) sets the x and z bounds of the landscape
//                      so that the constructed object will be a set of points
//                      (x, y, z) where x and z are integers and 0 <= x < m
//                      and 0 <= z < n, and y is undefined.
// create(rug)          assigns random elevations based on the ruggedness
//                      coefficient rug (rug == 0 defines the completely
//                      flat grid).  This routine also generates OpenGL
//                      display lists for efficient drawing.
// draw()               draws the landscape using a fixed coloring scheme.
// drawWireFrame()      draws a wireframe representation of the landscape.

#ifndef LANDSCAPE_H_
#define LANDSCAPE_H_

#ifdef __APPLE_CC__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

#include <vector>
#include <cmath>
#include <cstdlib>

class Landscape {
  int rows;
  int columns;
  double highest;                         // the highest point in the mesh
  std::vector< std::vector<double>  > d;  // the grid of elevations
  int solidId;                            // display list id for solid mesh
  int wireFrameId;                        // display list id for wire mesh
  static double unused;
  void generate(int x1, int y1, int x2, int y2, double rug);
  double scale(double x) {return x * (((double)rand()/RAND_MAX) - 0.5);}
  void drawTriangle(int x1, int z1, int x2, int z2, int x3, int z3);
  void vertex(double x, double z);
  void createSolidDisplayList();
  void createWireFrameDisplayList();
public:
  Landscape(int rows, int columns);
  void create(double rug);
  void draw() {glCallList(solidId);}
  void drawWireFrame() {glCallList(wireFrameId);}
};

#endif
landscape.cpp
// Implementation of the Landscape class.

#include "landscape.h"
#include "geometry.h"

GLfloat blue[] = {0.0, 0.0, 1.0, 1.0};
GLfloat green[] = {0.0, 1.0, 0.0, 1.0};
GLfloat darkGreen[] = {0.0, 0.5, 0.0, 1.0};
GLfloat brown[] = {0.5, 0.5, 0.0, 1.0};
GLfloat lightBrown[] = {0.7, 0.7, 0.3, 1.0};
GLfloat gray[] = {0.6, 0.6, 0.6, 1.0};
GLfloat forest[] = {0.4, 0.8, 0.5, 1.0};
GLfloat white[] = {1.0, 1.0, 1.0, 1.0};

// Hack.
double Landscape::unused = -10032.4775;

// Constructor.  Ensure the matrix is loaded with the unused constant in
// every cell.
Landscape::Landscape(int r, int c): rows(r), columns(c) {
  std::vector<double> nullRow(columns, unused);
  std::vector< std::vector<double> > nullMatrix(rows, nullRow);
  d = nullMatrix;
}

// create() sets the elevations of the four grid corners to 0, generates
// internal elevations remembering the highest point, then generates display
// lists.
void Landscape::create(double rug) {
  int r, c;

  // First put zeros for the elevations around the whole boundary:
  for (r = 0; r < rows; r++) d[r][0] = d[r][columns-1] = 0;
  for (c = 0; c < columns; c++) d[0][c] = d[rows-1][c] = 0;

  // Then put zeros in the corners inset two units and generate
  // a fractal landscape in that rectangle.
  d[2][2] = d[2][columns-3] = d[rows-3][2] = d[rows-3][columns-3] = 0;
  generate(2, 2, rows - 3, columns - 3, rug);

  // Then smooth out the inner fractal so it meets the zeroed out
  // edges.  Make the part just outside the fractal one-third higher
  // so it simulates flatter beaches.
  for (r = 2; r < rows - 2; r++) d[r][1] = d[r][2] / 3.0;
  for (r = 2; r < rows - 2; r++) d[r][c-2] = d[r][c-3] / 3.0;
  for (c = 1; c < columns-1; c++) d[1][c] = d[2][c] / 3.0;
  for (c = 1; c < columns-1; c++) d[r-2][c] = d[r-3][c] / 3.0;

  // Finally it part of the land is underwater make that elevation 0.
  highest = 0.0;
  for (int i = 0; i < rows; i++) {
    for (int j = 0; j < columns; j++) {
      if (d[i][j] < 0) d[i][j] = 0;
      if (d[i][j] > highest) highest = d[i][j];
    }
  }

  // Generate the display lists.
  solidId = glGenLists(2);
  wireFrameId = solidId + 1;
  createSolidDisplayList();
  createWireFrameDisplayList();
}

// Simple math for random midpoint displacement.
void Landscape::generate(int x1, int y1, int x2, int y2, double rug) {
  int x3 = (x1 + x2) / 2;
  int y3 = (y1 + y2) / 2;
  if (y3 < y2) {
    if (d[x1][y3] == unused) {
      d[x1][y3] = (d[x1][y1] + d[x1][y2])/2 + scale(rug*(y2-y1));
    }
    d[x2][y3] = (d[x2][y1] + d[x2][y2])/2 + scale(rug*(y2-y1));
  }
  if (x3 < x2) {
    if (d[x3][y1] == unused) {
      d[x3][y1] = (d[x1][y1] + d[x2][y1])/2 + scale(rug*(x2-x1));
    }
    d[x3][y2] = (d[x1][y2] + d[x2][y2])/2 + scale(rug*(x2-x1));
  }
  if (x3 < x2 && y3 < y2) {
    d[x3][y3] = (d[x1][y1] + d[x2][y1] + d[x1][y2] + d[x2][y2])/4
    + scale(rug * (fabs((double)(x2 - x1)) + fabs((double)(y2 - y1))));
  }
  if (x3 < x2 - 1 || y3 < y2 - 1) {
    generate(x1, y1, x3, y3, rug);
    generate(x1, y3, x3, y2, rug);
    generate(x3, y1, x2, y3, rug);
    generate(x3, y3, x2, y2, rug);
  }
}

// We assign colors to points so that blue is the water, then going up
// you get green (grassland), brown (mountain) then white (snowcaps).
void Landscape::vertex(double x, double z) {
  double y = d[(int)x][(int)z];
  double h = y / highest;
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
      (h < 0.03) ? forest :
      (h < 0.2) ? green :
      (h < 0.32) ? darkGreen :
      (h < 0.45) ? brown :
      (h < 0.7) ? lightBrown :
      (h < 0.8) ? gray : white);
  glVertex3f(x, y, z);
}

void Landscape::drawTriangle(int x1, int z1, int x2, int z2, int x3, int z3) {
  Point p[] = {Point(x1, d[x1][z1], z1),
               Point(x2, d[x2][z2], z2),
               Point(x3, d[x3][z3], z3)};
  Vector normal = unit(Plane(p[0], p[1], p[2]).normal());
  //if (normal.j < 0.0) normal = -normal;
  double h = 0.005;
  if (d[x1][z1] < h && d[x2][z2] < h && d[x3][z3] < h) {
    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
    glNormal3d(0, 1.0, 0);
    glVertex3dv((GLdouble*)&p[0]);
    glVertex3dv((GLdouble*)&p[1]);
    glVertex3dv((GLdouble*)&p[2]);
  }
  else {
    glNormal3dv((GLdouble*)&normal);
    vertex(x1, z1);
    vertex(x2, z2);
    vertex(x3, z3);
  }
}

// Drawing the landscape as a solid is fairly simple using GL_TRIANGLE_STRIP.
// We generate a display list with a triangle strip on each row.
void Landscape::createSolidDisplayList() {
  glNewList(solidId, GL_COMPILE);
  glEnable(GL_LIGHTING);
  glBegin(GL_QUADS);
  glVertex3f(-200, 0, columns+200);
  glVertex3f(-200, 0, -200);
  glVertex3f(rows+200, 0, -200);
  glVertex3f(rows+200, 0, columns+200);
  glEnd();

  glBegin(GL_TRIANGLES);
  for (int x = 0; x < rows - 1; x++) {
    for (int z = 0; z < columns - 1; z++) {
      drawTriangle(x, z, x+1, z, x, z+1);
      drawTriangle(x+1, z, x+1, z+1, x, z+1);
    }
  }
  glEnd();
  glEndList();
}

// Generating a display list for a wire frame representation of the
// landscape is straightforward though a little tedious.
void Landscape::createWireFrameDisplayList() {
  glNewList(wireFrameId, GL_COMPILE);
  glDisable(GL_LIGHTING);
  glColor3f(1.0, 1.0, 1.0);
  int x, z;
  for (x = 0; x < rows; x++) {
    glBegin(GL_LINE_STRIP);
    glVertex3f(x, d[x][0], 0);
    for (z = 1; z < columns; z++) {
      glVertex3f(x, d[x][z], z);
    }
    glEnd();
  }
  for (z = 0; z < columns; z++) {
    glBegin(GL_LINE_STRIP);
    glVertex3f(0, d[0][z], z);
    for (x = 1; x < rows; x++) {
      glVertex3f(x, d[x][z], z);
    }
    glEnd();
  }
  glEndList();
}

The Application

Here's a GLUT-based app. Fly with the keyboard. Good homework assignments include creating a joystick or spaceball UI, and creating an instrument panel.

fly.cpp
// This program is a trivial little flight simulator.  You control a ship
// with the keyboard.  Use
//
//   J and L keys to roll,
//   I and K keys to pitch,
//   H and ; keys to yaw,
//   8 key to increase speed and the M key to decrease speed.
//   W key toggles wireframe mode
//   R key generates a new landscape
//
// In this little program you fly around a single fractal landscape. It would
// be best to extend the program so that one could plug in any arbitrary
// scene.

#ifdef __APPLE_CC__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include "cockpit.h"
#include "landscape.h"
#include <iostream>

// A landscape to fly around, with some parameters that are manipuated by the
// program.
Landscape landscape(200, 143);

// Wireframe view or solid view?
static bool wireframe = false;

void newLandscape() {
  static double rug = ((double)rand()) / RAND_MAX;
  landscape.create(rug);
}

// A ship and some functions to control it: Later, we need to add a ship
// controller class so even the navigation controls are pluggable.
static Ship theShip(Point(60, 40, 220));
static Cockpit cockpit(theShip);

void keyboard(unsigned char key, int, int) {
  const double deltaSpeed = 0.01;
  const double angle = 0.02;
  switch(key) {
    case '8': theShip.setSpeed(theShip.getSpeed() + deltaSpeed); break;
    case 'm': theShip.setSpeed(theShip.getSpeed() - deltaSpeed); break;
    case 'w': wireframe = !wireframe; break;
    case 'r': newLandscape();
    case 'j': theShip.roll(angle); break;
    case 'l': theShip.roll(-angle); break;
    case 'h': theShip.yaw(angle); break;
    case ';': theShip.yaw(-angle); break;
    case 'i': theShip.pitch(-angle); break;
    case 'k': theShip.pitch(angle);  break;
  }
}

// Display and Animation: To draw we just clear the window and draw the scene.
// Because our main window is double buffered we have to swap the buffers to
// make the drawing visible.  Animation is achieved by successively moving
// the ship and drawing.
void display() {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  wireframe ? landscape.drawWireFrame() : landscape.draw();
  cockpit.draw();
  glFlush();
  glutSwapBuffers();
}

// Move the ship one step, recompute the view, and ask to redisplay.
void timer(int v) {
  theShip.fly();
  Point eye(theShip.getPosition());
  Point at(theShip.getPosition() + theShip.getDirection());
  Vector up(theShip.getVertical());
  glLoadIdentity();
  gluLookAt(eye.x, eye.y, eye.z, at.x, at.y, at.z, up.i, up.j, up.k);
  glutPostRedisplay();
  glutTimerFunc(1000/60, timer, v);
}

// Reshape callback: Make the viewport take up the whole window, recompute the
// camera settings to match the new window shape, and go back to modelview
// matrix mode.
void reshape(int w, int h) {
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 0.05, 300.0);
  glMatrixMode(GL_MODELVIEW);
}

// init(): Initialize GLUT and enter the GLUT event loop.
void init() {
  srand(9903);
  glEnable(GL_DEPTH_TEST);
  newLandscape();
  cockpit.create();
  GLfloat black[] = { 0.0, 0.0, 0.0, 1.0 };
  GLfloat dark[] = { 0.2, 0.15, 0.2, 1.0 };
  GLfloat white[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat direction[] = { 0.2, 1.0, 0.5, 0.0 };

  glMaterialfv(GL_FRONT, GL_SPECULAR, white);
  glMaterialf(GL_FRONT, GL_SHININESS, 30);

  glLightfv(GL_LIGHT0, GL_AMBIENT, dark);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, white);
  glLightfv(GL_LIGHT0, GL_SPECULAR, white);
  glLightfv(GL_LIGHT0, GL_POSITION, direction);

  glEnable(GL_LIGHTING);                // so the renderer considers light
  glEnable(GL_LIGHT0);                  // turn LIGHT0 on
}

// Writes some trivial help text to the console.
void writeHelpToConsole() {
  std::cout << "j/l = roll left / right\n";
  std::cout << "i/k - pitch down / up\n";
  std::cout << "h/; - yaw left / right\n";
  std::cout << "8/m - speed up / slow down\n";
  std::cout << "w - toggle wireframe mode\n";
  std::cout << "r - generate a new landscape\n";
}

// main(): Initialize GLUT and enter the GLUT event loop.
int main(int argc, char** argv) {
  writeHelpToConsole();
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowPosition(80, 80);
  glutInitWindowSize(780, 500);
  glutCreateWindow("Simple Flight");
  glutReshapeFunc(reshape);
  glutTimerFunc(100, timer, 0);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  init();
  glutMainLoop();
}

Note that this simulator comes with a lame cockpit module. It's just blank for now, but you will have a homework assignment to put instruments on it.

cockpit.h
// An OpenGL display of a ship's cockpit.

#ifndef COCKPIT_H_
#define COCKPIT_H_

#ifdef __APPLE_CC__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include "ship.h"

class Cockpit {
  Ship ship;
  int cockpitId;
public:
  Cockpit(Ship ship): ship(ship) {}
  void create();
  void draw();
};

void Cockpit::create() {
  cockpitId = glGenLists(1);
  glNewList(cockpitId, GL_COMPILE);
  glDisable(GL_LIGHTING);
  glColor3f(0.8, 0.8, 0.7);
  glBegin(GL_TRIANGLE_FAN);
  glVertex3f(0, -1, 0);
  glVertex3f(1, -1, 0);
  for (double x = 1.0; x >= -1.05; x -= 0.05) {
    glVertex3f(x, 20*cos(x / 10.0) - 20.6, 0);
  }
  glVertex3f(-1, -1, 0);
  glEnd();
  glEnable(GL_LIGHTING);

  glEndList();
}

inline void Cockpit::draw() {
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glCallList(cockpitId);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
}

#endif