|
Sprite Animation in OpenGL
|
|
Prof. David Bernstein |
| Computer Science Department |
| bernstdh@jmu.edu |
#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
#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();
}
#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
#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();
}
#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
#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;
}
#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;
}
#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
#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);
}
#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
#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();
}
#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
#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();
}
#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;
}