JMU
Sprite Animation in OpenGL


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Review
Using Sprites
Using Sprites (cont.)

Sprite: The Header

openglexamples/animation/Sprite.h
        #ifndef edu_jmu_cs_Sprite_h
#define edu_jmu_cs_Sprite_h

#include "GL/glut.h"

/**
 * A Sprite is a geometric object that "plays" on a Stage
 *
 * @author  Prof. David Bernstein, James Madison University
 */
class Sprite {
 public:
  /**
   * Destructor.
   */
  virtual ~Sprite();

  /**
   * Handle clock tick events
   *
   * A Cube rotates itself in response to clock tick events
   */
  virtual void handleTick() = 0;  // A pure virtual (i.e., abstract) method

  /**
   * Handle "key pressed" events
   *
   * By default, a Sprite does nothing in response to mouse clicks
   *
   * @param key   The key
   * @param x      The x-position of the mouse
   * @param y      The y-position of the mouse
   */
  virtual void keyPressed(unsigned char key, int x, int y);

  /**
   * Handle "mouse clicked" events
   *
   * By default, a Sprite does nothing in response to mouse clicks
   *
   * @param button The button (e.g., GLUT_LEFT_BUTTON)
   * @param state  The state (e.g., GLUT_UP or GLUT_DOWN)
   * @param x      The x-position of the mouse
   * @param y      The y-position of the mouse
   */
  virtual void mouseClicked(int button, int state, int x, int y);

  /**
   * Handle timer events
   */
  void timerTicked();
};

#endif
        
Using Sprites (cont.)

Sprite: The Implementation

openglexamples/animation/Sprite.cpp
        #include "Sprite.h"

Sprite::~Sprite() {
}

void Sprite::keyPressed(unsigned char key, int x, int y) {
  // Do nothing
}

void Sprite::mouseClicked(int button, int state, int x, int y) {
  // Do nothing
}

void Sprite::timerTicked() {
  // Save the current transformation
  glPushMatrix();

  // Handle the tick
  handleTick();

  // Restore the transformation
  glPopMatrix();
}
        
Using Sprites (cont.)

Stage: The Header

openglexamples/animation/Stage.h
        #ifndef edu_jmu_cs_Stage_h
#define edu_jmu_cs_Stage_h

#include "GL/glut.h"
#include "Sprite.h"
#define  MAX_SPRITES 10

/**
 * A Stage is a "window" that contains animated Sprite objects
 *
 * @author  Prof. David Bernstein, James Madison University
 */
class Stage {
 public:
  /**
   * Default Constructor
   */
  Stage();

  /**
   * Destructor.
   */
  ~Stage();

  /**
   * Add a Sprite to this Stage.
   *
   * @param s   The Sprite to add
   */
  void addSprite(Sprite* s);

  /**
   * Forwards "key pressed" events to a particular Sprite.
   *
   * @param key   The key
   * @param x      The x-position of the mouse
   * @param y      The y-position of the mouse
   * @param id     The ID of the sprite to forward to
   */
  void keyPressed(unsigned char key, int x, int y, int id);

  /**
   * Forwards "mouse clicked" events to a particular Sprite object.
   *
   * @param button The button (e.g., GLUT_LEFT_BUTTON)
   * @param state  The state (e.g., GLUT_UP or GLUT_DOWN)
   * @param x      The x-position of the mouse
   * @param y      The y-position of the mouse
   * @param id     The ID of the sprite to forward to
   */
  void mouseClicked(int button, int state, int x, int y, int id);

  /**
   * Forwards timer events to Sprite objects.
   */
  void onTimer();

  /**
   * Forces the Stage to be painted/rendered.
   */
  void paint();

  /**
   * Called to reshape the Stage.
   *
   * @param width   The new width
   * @param height  The new height
   */
  void reshape(int width, int height);

  /**
   * Start the Stage
   */
  void start();

 private:
  int size;
  GLfloat light0Position[4];
  GLfloat colorWhite[4];
  Sprite* sprite[MAX_SPRITES];

  /**
   * Perform OpenGL initializations.
   */
  void init();
};

#endif
        
Using Sprites (cont.)

Stage: The Implementation

openglexamples/animation/Stage.cpp
        #include "Stage.h"

Stage::Stage() {
  light0Position[0] = 0.0;
  light0Position[1] = 0.0;
  light0Position[2] = 1.0;
  light0Position[3] = 1.0;

  colorWhite[0] = 1.0;
  colorWhite[1] = 1.0;
  colorWhite[2] = 1.0;
  colorWhite[3] = 1.0;

  size = 0;
}

Stage::~Stage() {
  // Someone else is responsible for deleting the Sprite
}

void Stage::addSprite(Sprite* s) {
  if (size < MAX_SPRITES) {
    sprite[size] = s;
    ++size;
  }
}

void Stage::init() {
  glClearColor(1, 1, 1, 1);

  // Shading
  glShadeModel(GL_SMOOTH);

  // Lighting
  GLfloat lightingmodelAmbient[] = { 0.2, 0.2, 0.2, 1.0 };
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lightingmodelAmbient);
  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

  // Lights
  glLightfv(GL_LIGHT0, GL_POSITION, light0Position);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, colorWhite);
  glLightfv(GL_LIGHT0, GL_SPECULAR, colorWhite);

  // Enable
  glEnable(GL_LIGHTING);     // Enable lighting
  glEnable(GL_LIGHT0);       // Enable light number 0
  glEnable(GL_DEPTH_TEST);   // Enable depth buffering
}

void Stage::keyPressed(unsigned char key, int x, int y, int id) {
  if ((id >= 0) && (id < size))
    sprite[id]->keyPressed(key, x, y);
}

void Stage::mouseClicked(int button, int state, int x, int y, int id) {
  if ((id >= 0) && (id < size))
    sprite[id]->mouseClicked(button, state, x, y);
}

void Stage::onTimer() {
  // Clear the screen
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // Tell the sprites
  for (int i = 0; i < size; i++)
    sprite[i]->timerTicked();

  // Force a display callback
  glutPostRedisplay();
}

void Stage::paint() {
  // Force the rendering (off-screen)
  glFlush();

  // Handle the double buffering
  glutSwapBuffers();
}

void Stage::reshape(int width, int height) {
  GLfloat aspect;

  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  if (width <= height) {
    aspect = (GLfloat) height / (GLfloat) width;
    glOrtho(-5.0, 5.0, -5.0 * aspect, 5.0 * aspect, -5.0, 5.0);
  } else {
    aspect = (GLfloat) width / (GLfloat) height;
    glOrtho(-5.0 * aspect, 5.0 * aspect, -5.0, 5.0, -5.0, 5.0);
  }

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

void Stage::start() {
  init();

  glutMainLoop();
}

        
An Example - Spinning Cubes

Cube: The Header

openglexamples/animation/Cube.h
        #ifndef edu_jmu_cs_Cube_h
#define edu_jmu_cs_Cube_h

#include "Sprite.h"

/**
 * A Cube is a cube-shaped Sprite that rotates
 *
 * @author  Prof. David Bernstein, James Madison University
 */
class Cube : public Sprite {
 public:
  /**
   * Constructor
   */
  explicit Cube(GLfloat offset);

  /**
   * Destructor.
   */
  ~Cube();

  void handleTick();

  /**
   * Handle "mouse clicked" events
   *
   * A Cube changes its axis of rotation in response to
   * "mouse clicked" events
   *
   * @param button The button (e.g., GLUT_LEFT_BUTTON)
   * @param state  The state (e.g., GLUT_UP or GLUT_DOWN)
   * @param x      The x-position of the mouse
   * @param y      The y-position of the mouse
   */
  void mouseClicked(int button, int state, int x, int y);

 private:
  GLfloat offset;
  GLfloat theta[3];
  GLint axis;
};
#endif
        
An Example - Spinning Cubes

Cube: The Implementation

openglexamples/animation/Cube.cpp
        #include "Cube.h"

Cube::Cube(GLfloat offset) {
  this->offset = offset;

  theta[0] = 0.0;
  theta[1] = 0.0;
  theta[2] = 0.0;

  axis = 2;
}

Cube::~Cube() {
}


void Cube::handleTick() {
  // Asjust the rotation angle
  theta[axis] += 2.0;
  if (theta[axis] > 360.0)
    theta[axis] = 0.0;

  // Reset the transformation matrix
  glLoadIdentity();

  // Offset
  glTranslatef(offset, 0.0, 0.0);

  // Build the rotation matrix
  glRotatef(theta[0], 1.0, 0.0, 0.0);
  glRotatef(theta[1], 0.0, 1.0, 0.0);
  glRotatef(theta[2], 0.0, 0.0, 1.0);

  // Build the cube
  glutSolidCube(1.0);
}

void Cube::mouseClicked(int button, int state, int x, int y) {
  if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    axis = 0;
  else if (button == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN)
    axis = 1;
  else if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
    axis = 2;
}

        
Spinning Cubes (cont.)

The Driver

openglexamples/animation/Driver.cpp
        #include "Stage.h"
#include "Cube.h"

/**
 * \file
 * The driver for a simple animation
 *
 * To avoid having to deal with the complexities of C++ method pointers,
 * this driver is written in C
 *
 * @author  Prof. David Bernstein, James Madison University
 */
static int selectedSprite = 0;
static GLint delay;
static Stage stage;

/**
 * The display callback
 */
void display() {
  stage.paint();
}

/**
 * The keyboard callback
 */
void keyboard(unsigned char key, int x, int y) {
  // Don't forward these events. In this application they are used to
  // select a Sprite.
  selectedSprite = key - 48;
  if ((selectedSprite < 0) || (selectedSprite > 9))
    selectedSprite = 0;
}

/**
 * The mouse callback
 *
 * @param button The button (e.g., GLUT_LEFT_BUTTON)
 * @param state  The state (e.g., GLUT_UP or GLUT_DOWN)
 * @param x      The x-position of the mouse
 * @param y      The y-position of the mouse
 */
void mouse(int button, int state, int x, int y) {
  stage.mouseClicked(button, state, x, y, selectedSprite);
}

/**
 * The reshape callback
 *
 * @param width   The new width
 * @param height  The new height
 */
void reshape(int width, int height) {
  stage.reshape(width, height);
}

/**
 * The timer callback
 *
 * @param value   The ID
 */
void timer(int value) {
  stage.onTimer();

  // Re-start the timer
  glutTimerFunc(delay, timer, 1);
}

/**
 * The entry point of the application.
 *
 * @param argc  The number of command line arguments
 * @param argv  The array of command line arguments
 * @return      A status code
 */
int main(int argc, char **argv) {
  Cube a(-0.75), b(0.75);

  delay = 20;

  stage.addSprite(&a);
  stage.addSprite(&b);

  glutInit(&argc, argv);

  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(500, 500);
  glutCreateWindow("Stage");
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutMouseFunc(mouse);
  glutKeyboardFunc(keyboard);

  glutTimerFunc(delay, timer, 0);

  stage.start();

  return 0;
}
        
An Example - The Solar System

Sun: The Header

openglexamples/animation/Sun.h
        #ifndef edu_jmu_cs_Sun_h
#define edu_jmu_cs_Sun_h

#include "Sprite.h"
#include "Earth.h"

/**
 * A simple encpsulation of the sun.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
class Sun : public Sprite {
 public:
  /**
   * Default Constructor.
   */
  Sun();

  /**
   * Destructor.
   */
  ~Sun();

  /**
   * Handle clock tick events.
   */
  void handleTick();

 private:
  Earth earth;
};
#endif
        
An Example - The Solar System

Sun: The Implementation

openglexamples/animation/Sun.cpp
        #include "Sun.h"

Sun::Sun() {
}

Sun::~Sun() {
}

void Sun::handleTick() {
  GLfloat color[] = { 1.0, 1.0, 0.0 };

  // Make space black
  glClearColor(0, 0, 0, 1);

  // Inform the Earth
  earth.handleTick();

  // Render the Sun
  glMaterialfv(GL_FRONT, GL_AMBIENT, color);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, color);
  glMaterialfv(GL_FRONT, GL_SPECULAR, color);
  glMaterialf(GL_FRONT, GL_SHININESS, 80);

  glutSolidSphere(1.0, 16, 16);
}
        
The Solar System (cont.)

Earth: The Header

openglexamples/animation/Earth.h
        #ifndef edu_jmu_cs_Earth_h
#define edu_jmu_cs_Earth_h

#include "Sprite.h"
#include "Moon.h"

/**
 * A simple encapsulation of the Earth.
 *
 * @author  Prof. David Bernstein, James Madison University
 */
class Earth : public Sprite {
 public:
  /**
   * Default Constructor.
   */
  Earth();

  /**
   * Destructor.
   */
  ~Earth();

  /**
   * Handle clock tick events.
   */
  void handleTick();

 private:
  int day, hour;
  Moon moon;
};
#endif
        
The Solar System (cont.)

Earth: The Implementation

openglexamples/animation/Earth.cpp
        #include "Earth.h"

Earth::Earth() {
  day = 0;
  hour = 0;
}

Earth::~Earth() {
}

void Earth::handleTick() {
  GLfloat color[] = { 0.0, 0.0, 1.0 };

  ++hour;

  if (hour == 24) {
    hour = 0;
    day = (day + 1) % 365;
  }

  // Save the state
  glPushMatrix();

  // Position the Earth in its orbit
  glRotatef(((GLfloat) day) / 365.0f * 360.0f, 0.0, 0.0, 1.0);
  glTranslatef(4.0, 0.0, 0.0);

  // Inform the Moon
  moon.handleTick();

  // Rotate the Earth around its axis
  glRotatef(((GLfloat) hour) / 24.0f * 360.0f, 0.0, 0.0, 1.0);

  // Render the Earth
  glMaterialfv(GL_FRONT, GL_AMBIENT, color);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, color);
  glMaterialfv(GL_FRONT, GL_SPECULAR, color);
  glMaterialf(GL_FRONT, GL_SHININESS, 80);

  glutSolidSphere(0.5, 8, 8);

  // Restore the state
  glPopMatrix();
}
        
The Solar System (cont.)

Moon: The Header

openglexamples/animation/Moon.h
        #ifndef edu_jmu_cs_Moon_h
#define edu_jmu_cs_Moon_h

#include "Sprite.h"

/**
 * A simple encapsulation of the moon.
 *
 * @author  Prof. David Bernstein, James Madison University
 */
class Moon : public Sprite {
 public:
  /**
   * Default Constructor.
   */
  Moon();

  /**
   * Destructor.
   */
  ~Moon();

  /**
   * Handle clock tick events.
   */
  void handleTick();

 private:
  int dom, hour;
};
#endif
        
The Solar System (cont.)

Moon: The Implementation

openglexamples/animation/Moon.cpp
        #include "Moon.h"

Moon::Moon() {
  dom = 0;
  hour = 0;
}

Moon::~Moon() {
}

void Moon::handleTick() {
  GLfloat color[] = { 1.0, 1.0, 1.0 };

  ++hour;

  if (hour == 24) {
    hour = 0;
    dom = (dom + 1) % 28;
  }

  // Save the state
  glPushMatrix();

  // Position the Moon in its orbit
  glRotatef(((GLfloat) dom) / 28.0f * 360.0f, 0.0, 0.0, 1.0);
  glTranslatef(1.0, 0.0, 0.0);

  // Render the Moon
  glMaterialfv(GL_FRONT, GL_AMBIENT, color);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, color);
  glMaterialfv(GL_FRONT, GL_SPECULAR, color);
  glMaterialf(GL_FRONT, GL_SHININESS, 80);

  glutSolidSphere(0.15, 16, 16);

  // Restore the state
  glPopMatrix();
}
        
The Solar System (cont.)

The Driver

openglexamples/animation/SolarSystem.cpp
        #include "Stage.h"
#include "Sun.h"

/**
 * \file
 * A simple "solar system".
 *
 * To avoid having to deal with the complexities of C++ method pointers,
 * this driver is written in C
 *
 * @author  Prof. David Bernstein, James Madison University
 */

static GLint delay;
static Stage stage;
static Sun sun;

/**
 * The display callback
 */
void display() {
  stage.paint();
}

/**
 * The reshape callback
 *
 * @param width   The new width
 * @param height  The new height
 */
void reshape(int width, int height) {
  stage.reshape(width, height);
}

/**
 * The timer callback
 *
 * @param value   The ID
 */
void timer(int value) {
  stage.onTimer();

  // Re-start the timer
  glutTimerFunc(delay, timer, 1);
}

/**
 * The entry point of the application.
 *
 * @param argc  The number of command line arguments
 * @param argv  The array of command line arguments
 * @return      A status code
 */
int main(int argc, char **argv) {
  delay = 20;

  stage.addSprite(&sun);

  glutInit(&argc, argv);

  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
  glutInitWindowSize(500, 500);
  glutCreateWindow("Solar System");
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);

  glutTimerFunc(delay, timer, 0);

  stage.start();

  return 0;
}