Chris Pollett >
Old Classes
> |
HW3 Solutions PageOverviewMy solution on this homework was more than I expected of students; however, I am still less than completely satisfied with it. Basically, I created my own XML scripting language which can be run by my program. The fractal/shape grammar is generated by a separate program which outputs a textures parameter file. The main program loads these two files to generate the scene. The auxiliary files are by default assumed to be in same directory and named script.txt and textures.txt. Alternatively, you can specify them in that order as command line arguments. My feeling is my parser could easily be improved, I used stringstream mainly because it was easy. However, the way it was done has the effect that the files are essentially read twice which is slow. Also, if YACC or BISON were used the code would be much smaller. Right now, the texture mapping for sphere shapes is using automatically generated texture coordinates and the GLUT sphere drawing command. This could also be improved. Example script.txt<event> <framesTillAction>0</framesTillAction> <shape> <id>ground</id> <lifespan>0</lifespan> <appearance> <type>rectangularprism</type> <dimensions>50 .1 50</dimensions> <color>0 .4 0</color> </appearance> <physics> <position>0 0 0</position> <up>0 1 0</up> <velocity>0 0 1</velocity> <stationary>1</stationary> <mass>1</mass> </physics> </shape> <shape> <id>building1</id> <lifespan>0</lifespan> <appearance> <type>rectangularprism</type> <dimensions>2 8 2</dimensions> <color>.1 .1 .1</color> </appearance> <physics> <position>0 4 0</position> <up>0 1 0</up> <velocity>0 0 1</velocity> <stationary>1</stationary> <mass>1</mass> </physics> </shape> <shape> <id>building2</id> <lifespan>0</lifespan> <appearance> <type>rectangularprism</type> <dimensions>3 6 3</dimensions> <color>.2 .2 .2</color> </appearance> <physics> <position>5 3 0</position> <up>0 1 0</up> <velocity>0 0 1</velocity> <stationary>1</stationary> <mass>1</mass> </physics> </shape> <shape> <id>building3</id> <lifespan>0</lifespan> <appearance> <type>rectangularprism</type> <dimensions>4 9 3</dimensions> <color>.2 .2 .2</color> </appearance> <physics> <position>6 4.5 -5</position> <up>0 1 0</up> <velocity>0 0 1</velocity> <stationary>1</stationary> <mass>1</mass> </physics> </shape> <shape> <id>building4</id> <lifespan>0</lifespan> <appearance> <type>rectangularprism</type> <dimensions>2 4 3</dimensions> <color>.1 .2 .1</color> </appearance> <physics> <position>-1 2 -5</position> <up>0 1 0</up> <velocity>0 0 1</velocity> <stationary>1</stationary> <mass>1</mass> </physics> </shape> <shape> <id>building5</id> <lifespan>0</lifespan> <appearance> <type>rectangularprism</type> <dimensions>4 5 3</dimensions> <color>.3 .2 .2</color> </appearance> <physics> <position>0 2.5 10</position> <up>0 1 0</up> <velocity>0 0 1</velocity> <stationary>1</stationary> <mass>1</mass> </physics> </shape> <shape> <id>building6</id> <lifespan>0</lifespan> <appearance> <type>rectangularprism</type> <dimensions>2 3 2</dimensions> <color>.3 .2 .2</color> </appearance> <physics> <position>7 2.5 11</position> <up>0 1 0</up> <velocity>0 0 1</velocity> <stationary>1</stationary> <mass>1</mass> </physics> </shape> <shape> <id>building7</id> <lifespan>0</lifespan> <appearance> <type>rectangularprism</type> <dimensions>3 4 4</dimensions> <texture>fractal</texture> </appearance> <physics> <position>12 2 10</position> <up>0 1 0</up> <velocity>0 0 1</velocity> <stationary>1</stationary> <mass>1</mass> </physics> </shape> <shape> <id>carpetFront</id> <lifespan>0</lifespan> <appearance> <type>rectangularprism</type> <dimensions>3 .1 2</dimensions> <texture>fractal</texture> </appearance> <physics> <position>-5 1 3</position> <up>0 1 0</up> <velocity>.1 .03 0</velocity> <stationary>0</stationary> <mass>1</mass> <force><gravity>0 -.0004 0</gravity></force> </physics> </shape> <shape> <id>carpetBack</id> <lifespan>0</lifespan> <appearance> <type>rectangularprism</type> <dimensions>3 .1 2</dimensions> <texture>fractal</texture> </appearance> <physics> <position> -8.3 .8 3</position> <up>0 1 0</up> <velocity>.0001 0 0</velocity> <stationary>0</stationary> <force> <spring>carpetFront .004 1.5 0 0 -1.5 0 0</spring> </force> <force><gravity>0 -.0004 0</gravity></force> <mass>.3</mass> </physics> </shape> </event> <event> <framesTillAction>50</framesTillAction> <particles> <id>fireworks</id> <number>100</number> <dispersionSpeed>.4</dispersionSpeed> <shape> <id>baseparticle</id> <lifespan>150</lifespan> <appearance> <type>sphere</type> <dimensions>.1 .1 .1</dimensions> <color>1 1 1</color> </appearance> <physics> <position>5 10 0</position> <up>0 0 1</up> <velocity>0 .2 0</velocity> <stationary>0</stationary> <force><gravity>0 -.03 0</gravity></force> <mass>1</mass> </physics> </shape> </particles> </event> <event> <framesTillAction>100</framesTillAction> <particles> <id>fireworks</id> <number>200</number> <dispersionSpeed>.5</dispersionSpeed> <shape> <id>baseparticle</id> <lifespan>150</lifespan> <appearance> <type>rectangularprism</type> <dimensions>.1 .1 .1</dimensions> <color>1 1 0</color> </appearance> <physics> <position>-5 10 -5</position> <up>0 0 1</up> <velocity>0 .7 0</velocity> <stationary>0</stationary> <force><gravity>0 -.05 0</gravity></force> <mass>1</mass> </physics> </shape> </particles> </event> <event> <framesTillAction>50</framesTillAction> <particles> <id>fireworks</id> <number>200</number> <dispersionSpeed>.5</dispersionSpeed> <shape> <id>baseparticle</id> <lifespan>150</lifespan> <appearance> <type>sphere</type> <dimensions>.1 .1 .1</dimensions> <color>0 0 2</color> </appearance> <physics> <position>-5 13 5</position> <up>0 0 1</up> <velocity>0 .7 0</velocity> <stationary>0</stationary> <force><gravity>0 -.05 0</gravity></force> <mass>1</mass> </physics> </shape> </particles> </event> <event> <framesTillAction>200</framesTillAction> <reset></reset> </event> Program to Generate textures.txt/****************************************************** * Project: CS116B Homework #3b * File: fractal.cpp * Purpose: This program is used to generate the fractal texture used by carpet.cpp * * Start date: May 8, 2005 * Programmer: Chris Pollett * * Remarks: Compile this program with a line like: * g++ fractal.cpp -o fractal * * Then run on the command line with: * fractal >> texture.txt * * Afterwards, texture.txt will contain xml data of the following type: * The start and * *******************************************************/ #include <iostream> #include <string> using namespace std; /* CONSTANTS */ //Texture Dimensions const int WIDTH = 128; //must be a power of 2 const int HEIGHT = 128; const string identity = "fractal"; const string COLOR1 = "1.0 0.0 0.0 1.0 "; //red (RGBA value) const string COLOR2 = "0.0 0.0 1.0 1.0 "; //blue (RGBA value) /* GLOBALS */ int data[WIDTH][HEIGHT]; /* PROTOTYPES */ void makeFractalDataPattern1(int lox, int loy, int hix, int hiy); void makeFractalDataPattern2(int lox, int loy, int hix, int hiy); /*-----------------------------------------------*/ void makeFractalDataBlankPattern(int lox, int loy, int hix, int hiy) /* PURPOSE: Generate blank data for non-fractal part of texture RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { for(int i = lox; i < hix; i++) { for(int j = loy; j < hiy; j++) { data[i][j] = 0; } } } /*-----------------------------------------------*/ void makeFractalDataPattern2(int lox, int loy, int hix, int hiy) /* PURPOSE: One of two recursive pattern generators to make this texture pattern. Looks like x o o x RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { int midx = (lox+hix)/2; int midy = (loy+hiy)/2; if(hix <= lox + 1 && hiy <= loy + 1) { data[lox][loy] = 1; return; } makeFractalDataBlankPattern(lox, loy, midx, midy); makeFractalDataPattern1(midx, loy, hix, midy); makeFractalDataBlankPattern(midx, midy, hix, hiy); makeFractalDataPattern1(lox, midy, midx, hiy); } /*-----------------------------------------------*/ void makeFractalDataPattern1(int lox, int loy, int hix, int hiy) /* PURPOSE: One of two recursive pattern generators to make this texture pattern. Looks like o x x o RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { int midx = (lox+hix)/2; int midy = (loy+hiy)/2; if(hix <= lox + 1 && hiy <= loy + 1) { data[lox][loy] = 1; return; } makeFractalDataPattern2(lox, loy, midx, midy); makeFractalDataBlankPattern(midx, loy, hix, midy); makeFractalDataPattern2(midx, midy, hix, hiy); makeFractalDataBlankPattern(lox, midy, midx, hiy); } /*-----------------------------------------------*/ void makeTexture() /* PURPOSE: Outputs to standard output an xml data file containing a fractal texture RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { cout << "<texture>" << endl; cout << "<id>" <<identity << "</id>" <<endl; cout << "<width>"<< WIDTH <<"</width><height>" << HEIGHT << "</height>" <<endl; cout << "<data>" << endl; makeFractalDataPattern1(0, 0, WIDTH, HEIGHT); for(int i = 0; i < WIDTH; i++) { for(int j = 0; j < HEIGHT; j++) { if(data[i][j] == 0) { cout << COLOR1; } else { cout << COLOR2; } } cout << endl; } cout << "</data>" <<endl; cout << "</texture> " << endl; } int main (int argc, char * const argv[]) { makeTexture(); return 0; } Main Program/****************************************************** * Project: CS116B Homework #3 * File: carpet.cpp * Purpose: Code to draw a flying carpet, flying betwixt a city landscape * with fireworks * Actually, this program reads and parses a script file that generates * the scene. The format of the script file is as a xml document, * consisting of a sequence of <event></event> tags. * <event>'s have a <framesTillAction> </framesTillAction> tag which says * how many frames since the last event was performed until this event * is performed. A frame by default is 1/30 of a second. An <event> can * also have a sequence of <shape></shape>, <particles></particles>, * or <reset></reset> objects on it. The first two of these in turn * have sub-tags. Please consult the example provided with the homework * solution to get a rough idea of the language. * * Start date: May 1, 2005 * Programmer: Chris Pollett * * Remarks: In retrospect parsing is done in a way more complicated than it needs to be -- * I should have faked some YACC - like rule interpreter * *******************************************************/ #ifdef WIN32 #include<windows.h> //for windows #include <GL/glut.h> #include <GL/glu.h> #endif #ifdef __APPLE__ //for MAC /* I created my project under xcode. I chose new C++ tool as the kind of project. Then under External frameworks and libraries I added the two frameworks: OpenGL.framework and GLUT.framework. (Frameworks are in /Library/Frameworks) */ #include <OpenGL/gl.h> #include <OpenGL/glu.h> #include <GLUT/glut.h> #endif #ifdef linux // for linux /*My compile line was: g++ -I /usr/X11R6/include -L /usr/X11R6/lib -lglut -lGL \ -lGLU -lX11 -lXmu -lXi -lm carpet.cpp -o carpet */ #include <GL/glut.h> #include <GL/glu.h> #endif #include <cstdlib> #include <cmath> #include <ctime> #include <iostream> #include <fstream> #include <vector> #include <list> #include <string> #include <sstream> using namespace std; /* CONSTANTS */ //Light constants const GLfloat AMBIENT_LIGHT[] = {.3, .3, .3, 1 }; const GLfloat SUN_VECTOR[] = {80., 30., 50 }; //sun's position const GLfloat SUN_AMBIENT_SPECULAR[] = {1, 1, 1, 1 }; const GLfloat SUN_DIFFUSE[] = {1, 1, 1, 1 }; //Camera constants const GLfloat CAMERA_X = 15.0; //camera's position (looks at origin) const GLfloat CAMERA_Y = 8.0; const GLfloat CAMERA_Z = 20.0; const GLfloat UP_X = 0.0; //camera's up vector const GLfloat UP_Y = 1.0; const GLfloat UP_Z = 0.0; //View Volume const GLfloat NEAR_CLIP = 2.0; const GLfloat FAR_CLIP = 70; //Animation Speed clock_t FRAME_TIME = CLOCKS_PER_SEC/30; //CLASS AND FUNCTION PROTOTYPES class Physics; class World; void crossProduct(GLfloat vec1[], GLfloat vec2[], GLfloat vecResult[]); void normalize(GLfloat vec[]); void randomUnit(GLfloat vec[]); /* CLASS DEFINITIONS */ /*-----------------------------------------------*/ class Force /* PURPOSE: encapsulate the notion of a physical force that might be applied to a Shape in our World to effect its motion REMARK: By default the force is a uniform force of gravity in some fixed direction */ { protected: GLfloat _forceVec[3]; public: Force(GLfloat fVec[]); Force(Force *f); virtual ~Force(){}; virtual void exert(Physics *physics, World *world, GLfloat vec[]); }; /*-----------------------------------------------*/ class SpringForce : public Force /* PURPOSE: Used to model a spring force F= k(x-x0) on a Shape For a SpringForce _forceVec is used to say where the force is applied on the shape and _offsetOtherVec says where the force is atatched to the other Shape REMARK: none */ { private: string _id; GLfloat _springConstant; GLfloat _offsetOtherVec[3]; Physics *_otherPhysics; public: SpringForce(string id, GLfloat springConstant, GLfloat fVec[], GLfloat oVec[]); SpringForce(SpringForce *f); void exert(Physics *physics, World *world, GLfloat vec[]); }; /*-----------------------------------------------*/ class ForceFactory /* PURPOSE: Used to create different kinds of forces according to a supplied context REMARK: none */ { public: ForceFactory() {} static Force* create(stringstream *forceStream); static Force* copy(Force *f); }; /*-----------------------------------------------*/ class Physics /* PURPOSE: Used to encapsulate the dynamical state of a Shape in our World. It keeps track of position, velocity, acceleration, and mass, as well as all forces that are to be applied to the Shape. A boolean flag _stationary controls whether or not the given Shape, if it uses this Physics, can move. REMARK: */ { private: GLfloat _position[3]; GLfloat _up[3]; GLfloat _velocity[3]; GLfloat _acceleration[3]; bool _stationary; GLfloat _mass; vector<Force*> *_forces; public: Physics(GLfloat p[], GLfloat u[], GLfloat v[], bool s, GLfloat m, vector<Force*> *f); Physics(Physics *p); ~Physics(); void addForce(Force* force); void update(World *world); void move(int framesElapsed); void increaseVelocity(GLfloat vec[]); GLfloat mass(){return _mass;} GLfloat* position() {return _position;} /*this is somewhat bogus as it defeats the privateness of these arrays */ GLfloat* up() {return _up;} GLfloat* velocity() {return _velocity;} GLfloat* acceleration() {return _acceleration;} void attitude(GLfloat attitudeMatrix[]); vector<Force*>* forces() {return _forces;} bool stationary() {return _stationary;} }; /*-----------------------------------------------*/ class PhysicsFactory /* PURPOSE: Used to create Physics objects according to a supplied stringstream context REMARK: none */ { public: PhysicsFactory() {} static Physics* create(stringstream *physicsStream); static Physics* copy(Physics *physics); }; /*-----------------------------------------------*/ class Appearance /* PURPOSE: Object of this class are used to control the size and surface properties for Shape's REMARK: right now the useColor flag is used to determine if an object has a texture or one fixed color. This is a little awkward */ { private: GLfloat _dimension[3]; //will be size of a Shape GLfloat _color[3]; string _textureID; protected: bool _useColor; public: Appearance(GLfloat dimension[], GLfloat color[], string textureID, bool useColor); Appearance(Appearance *a); virtual ~Appearance(){}; void preDraw(Physics *physics, World *world); virtual void draw(Physics *physics) {}; void postDraw(); }; /*-----------------------------------------------*/ class RectangularPrismAppearance : public Appearance /* PURPOSE: Appearance used to draw any Shape that looks like a RectangularPrism i.e., a box. REMARK: */ { public: RectangularPrismAppearance(GLfloat d[], GLfloat c[], string t, bool u): Appearance(d,c,t, u){} RectangularPrismAppearance(RectangularPrismAppearance *a): Appearance(a){} void draw(Physics *physics); }; /*-----------------------------------------------*/ class SpheroidAppearance : public Appearance /* PURPOSE: Appearance used to draw any Shape that looks like a Sphere or a squished Sphere REMARK: */ { public: SpheroidAppearance(GLfloat d[], GLfloat c[], string t, bool u): Appearance(d,c,t, u){} SpheroidAppearance(SpheroidAppearance *a): Appearance(a){} void draw(Physics *physics); }; /*-----------------------------------------------*/ class AppearanceFactory /* PURPOSE: Used to create Appearance objects according to a supplied stringstream context REMARK: */ { public: AppearanceFactory() {}; static Appearance* create(stringstream *appearanceStream); static Appearance* copy(Appearance* appearance); }; /*-----------------------------------------------*/ class Shape /* PURPOSE: Used to encapsulate information about a Shape that might exist in a World REMARK: */ { private: string _id; Physics *_physics; Appearance *_appearance; int _age; int _lifespan; public: Shape(string id, int l, Physics* p, Appearance* ap); Shape(Shape *s); ~Shape(); bool cullable(); void draw(World *world); void update(int framesElasped, World *world); void move(int framesElapsed); string id() {return _id;} int age() {return _age;} int lifespan() {return _lifespan;} Physics* physics() {return _physics;} Appearance* appearance() {return _appearance;} }; /*-----------------------------------------------*/ class ShapeFactory /* PURPOSE: Used to create Shape objects according to a supplied stringstream context REMARK: */ { public: ShapeFactory() {}; static Shape* create(stringstream *shapeStream); }; /*-----------------------------------------------*/ class Event /* PURPOSE: Used to represent the collection of actions that might occur on a World in a given time frame. It also holds a _framesTillAction variable used to say the amount of time since the last event. REMARK: */ { private: int _framesTillAction; vector<string>* _deleteShapes; // IDs of shapes to be delete from the world by this Event vector<Shape *>* _insertShapes; // Shape's to be added ot the world by this Event bool _reset; // used to control whether this Event reset's the world public: Event(){}; Event(int f, vector<string>* d, vector<Shape *>* iS, bool reset); virtual ~Event(); void addDeleteShapeByID(string shapeID); void addInsertShape(Shape* shape); int framesTillAction() {return _framesTillAction;} virtual void action(World *world); }; /*-----------------------------------------------*/ class EventFactory /* PURPOSE: Used to create different kinds of Event's that might occur in a Script of what happens in a World REMARK: none */ { public: EventFactory() {}; static Event* create(stringstream *eventStream); static void createParticles(stringstream *eventStream, vector<Shape *> *shapes); }; /*-----------------------------------------------*/ class Script /* PURPOSE: a Script is a vector of Events that are played in sequence on a World to tell a story REMARK: none */ { private: int _currentFrame; unsigned int _currentEventIndex; vector<Event*> _events; public: Script(); ~Script(); int step(int framesElapsed, World *world); void addEvent(Event *evt); void reset(); }; /*-----------------------------------------------*/ class ScriptFactory /* PURPOSE: used to generate a Script (a sequence of events) that can be played on a World REMARK: none */ { public: ScriptFactory() {}; static Script* create(stringstream *scriptStream); }; /*-----------------------------------------------*/ class Texture /* PURPOSE: Class to encapsulate a texture map that might be applied to a Shape's in a World REMARK: none */ { private: string _identity; int _width; int _height; GLfloat *_data; public: Texture(string id, int w, int h, GLfloat *d); ~Texture(); string id(){return _identity;} int width(){return _width;} int height(){return _height;} GLfloat* data(){return _data;} }; /*-----------------------------------------------*/ class TexturesFactory /* PURPOSE: used to parse a vector Texture's to be added to a World. These Texture's used for texture maps on Shape's in a World REMARK: none */ { public: TexturesFactory() {}; static vector<Texture* >* create(stringstream *textureStream); static Texture* createTexture(stringstream *textureStream); }; /*-----------------------------------------------*/ class World /* PURPOSE: This class encapsulates the scene to be draw by this program REMARK: none */ { private: list<Shape* > *_shapes; //using list since slightly better for deleting elements vector<Texture* > *_textures; Script *_script; bool _resetting; public: World(); ~World(); list<Shape*>* shapes() {return _shapes;} vector<Texture*>* textures() {return _textures;} void setScript(Script *s); void setTextures(vector<Texture*>* t); Shape *findShapeByID(string id); Texture *findTextureByID(string id); void cull(); void draw(); void move(int framesElapsed); void update(int framesElapsed); void step(int framesElapsed); void setResetting(bool r){_resetting=r; } bool isResetting(){return _resetting; } void reset(); }; /* GLOBALS */ GLsizei winWidth = 500, winHeight = 500; // used for size of window GLsizei initX = 50, initY = 50; // used for initial position of window World world; string scriptName; string textureName; clock_t animationTime = clock(); // keeps time of last frame update /* IMPLEMENTATIONS */ //Appearance Class Implementations /*-----------------------------------------------*/ Appearance::Appearance(GLfloat dimension[], GLfloat color[], string id, bool useColor) /* PURPOSE: initialize an Appearance object. This object controls what is actually rendered for a Shape. RECEIVES: dimension - 3-vector x,y,z size of shape color - if the shape is drawn with a single color, what it is id -- identity of the Texture in World's _textures vector to use. useColor - whether to use a color or texture (in the future this lame flag could be replaced with a surface object) RETURNS: the Appearance object REMARKS: None */ { for(int i = 0; i < 3; i++) { _dimension[i] = dimension[i]; _color[i] = color[i]; } _textureID = id; _useColor = useColor; } /*-----------------------------------------------*/ Appearance::Appearance(Appearance *a) /* PURPOSE: copy constructor RECEIVES: An Appearance to make a copy of RETURNS: the Appearance object REMARKS: None */ { for(int i = 0; i < 3; i++) { _dimension[i] = a->_dimension[i]; _color[i] = a->_color[i]; } _textureID = a->_textureID; _useColor = a->_useColor; } /*-----------------------------------------------*/ void Appearance::preDraw(Physics *physics, World *world) /* PURPOSE: Part of a template pattern used to draw this Appearance of a Shape according to the supplied Physics. This part is fixed and is before the part that actually draws and is used to set up the relevant matrices RECEIVES: physics -- Physics object used to set up modelview matrices RETURNS: REMARKS: */ { GLfloat *pos = physics->position(); glPushMatrix(); glLoadIdentity(); //set origin to Physics object's glTranslatef(pos[0], pos[1], pos[2]); //Now switch into Physics object's coordinate system GLfloat attitudeMatrix[16]; physics->attitude(attitudeMatrix); glMultMatrixf(attitudeMatrix); glScalef(_dimension[0], _dimension[1], _dimension[2]); if(_useColor) { glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, _color); glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, _color); glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, _color); glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, _color); } else { Texture *texture = world->findTextureByID(_textureID); if(texture) { GLfloat color[] = {.8, .8, .8}; glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, color); glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color); glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, color); glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, color); glEnable(GL_TEXTURE_2D); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->width(), texture->height(), 0, GL_RGBA, GL_FLOAT, texture->data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } } } /*-----------------------------------------------*/ void Appearance::postDraw() /* PURPOSE: Part of a template pattern used to draw this Appearance of a Shape according to the supplied Physics. This part is fixed and is after the part that actually draws and is used to restore matrix stack to its setting prior to the draw activity RECEIVES: nothing RETURNS: nothing REMARKS: */ { if(!_useColor) { glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_2D); } glPopMatrix(); } //RectangularPrismAppearance Class Implementations /*-----------------------------------------------*/ void RectangularPrismAppearance::draw(Physics *physics) /* PURPOSE: Part of a template pattern used to draw this Appearance of a Shape according to the supplied Physics. This part is can be changed. In this particular case, it draws a cube. RECEIVES: physics -- a Physics object which might be used in the drawing process (but isn't in this case). RETURNS: REMARKS: */ { if(_useColor) { //lazy glutSolidCube(1.0); } else { // should output with a QUAD_STRIP and give Normals glBegin(GL_POLYGON); glTexCoord2f(0, 0); glVertex3f(-.5, .5, -.5); glTexCoord2f(1, 0); glVertex3f(.5, .5, -.5); glTexCoord2f(1, 1); glVertex3f(.5, .5, .5); glTexCoord2f(0, 1); glVertex3f(-.5, .5, .5); glEnd(); glBegin(GL_POLYGON); glTexCoord2f(0, 0); glVertex3f(-.5, -.5, -.5); glTexCoord2f(1, 0); glVertex3f(.5, -.5, -.5); glTexCoord2f(1, 1); glVertex3f(.5, -.5, .5); glTexCoord2f(0, 1); glVertex3f(-.5, -.5, .5); glEnd(); glBegin(GL_POLYGON); glTexCoord2f(0, 0); glVertex3f( -.5, -.5, -.5); glTexCoord2f(1, 0); glVertex3f( -.5, .5, -.5); glTexCoord2f(1, 1); glVertex3f( -.5, .5, .5); glTexCoord2f(0, 1); glVertex3f( -.5, -.5, .5); glEnd(); glBegin(GL_POLYGON); glTexCoord2f(0, 0); glVertex3f( .5, -.5, -.5); glTexCoord2f(1, 0); glVertex3f( .5, .5, -.5); glTexCoord2f(1, 1); glVertex3f( .5, .5, .5); glTexCoord2f(0, 1); glVertex3f( .5, -.5, .5); glEnd(); glBegin(GL_POLYGON); glTexCoord2f(0, 0); glVertex3f( -.5, -.5, -.5); glTexCoord2f(1, 0); glVertex3f( .5, -.5, -.5); glTexCoord2f(1, 1); glVertex3f( .5, .5, -.5); glTexCoord2f(0, 1); glVertex3f( -.5, .5, -.5); glEnd(); glBegin(GL_POLYGON); glTexCoord2f(0, 0); glVertex3f( -.5, -.5, .5); glTexCoord2f(1, 0); glVertex3f( .5, -.5, .5); glTexCoord2f(1, 1); glVertex3f( .5, .5, .5); glTexCoord2f(0, 1); glVertex3f( -.5, .5, .5); glEnd(); } } //SpheroidAppearance Class Implementations /*-----------------------------------------------*/ void SpheroidAppearance::draw(Physics *physics) /* PURPOSE: Part of a template pattern used to draw this Appearance of a Shape according to the supplied Physics. This part is can be changed. In this particular case, it draws a sphere. RECEIVES: physics -- a Physics object which might be used in the drawing process (but isn't in this case). RETURNS: REMARKS: */ { if(!_useColor) { //Enable automatic generation of texture coordinates glEnable(GL_TEXTURE_GEN_S); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glEnable(GL_TEXTURE_GEN_T); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); } glutSolidSphere(1.0, 20, 20); } //AppearanceFactory Class Implementations /*-----------------------------------------------*/ Appearance* AppearanceFactory::create(stringstream *appearanceStream) /* PURPOSE: Used to create an Appearance for a Shape based on the supplied stringstream context. (Parses this stream) RECEIVES: appearanceStream -- stringstream to parse an Appearance from RETURNS: REMARKS: */ { string token; unsigned int mask = 0; string type; GLfloat dimensions[3]; GLfloat color[3] = {0, 0, 0}; string textureID; bool useColor = false; Appearance *appearance; try { for(int i = 0; i < 3; i++) { *appearanceStream >> token; if(token == "<type>") { *appearanceStream >> type; *appearanceStream >> token; if(token != "</type>") throw string("No close </type> " + token); mask |=1; } else if(token == "<dimensions>") { *appearanceStream >> dimensions[0] >> dimensions[1] >> dimensions[2]; *appearanceStream >> token; if(token != "</dimensions>") throw string("No close </dimensions> " + token); mask |=2; } else if(token == "<color>") { *appearanceStream >> color[0] >> color[1] >> color[2]; *appearanceStream >> token; if(token != "</color>") throw string("No close </color> " + token); useColor = true; mask |=4; } else if(token == "<texture>") { useColor = false; *appearanceStream >> textureID; *appearanceStream >> token; if(token != "</texture>") throw string("No close </texture> " + token); mask |=4; } else { throw string("Unrecognized token " + token); } } if(mask != 7) throw string("Missing Appearance Parameter"); if(type == "sphere") { appearance = new SpheroidAppearance(dimensions, color, textureID, useColor); } else if(type == "rectangularprism") { appearance = new RectangularPrismAppearance(dimensions, color, textureID, useColor); } else { throw string("Unrecognized Appearance Type: "+type); } } catch(string error) { cerr <<"Parse error:" << error; exit(1); } return appearance; } /*-----------------------------------------------*/ Appearance* AppearanceFactory::copy(Appearance *a) /* PURPOSE: Used to copy an Appearance object on a Shape object. RECEIVES: a -- Appearance object RETURNS: a copy of a. REMARKS: */ { Appearance *appearance; if(typeid(RectangularPrismAppearance) == typeid(*a)) { appearance = new RectangularPrismAppearance((RectangularPrismAppearance*)a); } else if(typeid(SpheroidAppearance) == typeid(*a)) { appearance = new SpheroidAppearance((SpheroidAppearance*)a); } else { appearance = new Appearance(a); } return appearance; } //Force Class Implementations /*-----------------------------------------------*/ Force::Force(GLfloat fVec[]) /* PURPOSE: Creates a gravitional force RECEIVES: fVec - direction and magnitude of the force RETURNS: Force object REMARKS: */ { for(int i = 0; i < 3; i++) { _forceVec[i] = fVec[i]; } } //Force Class Implementations /*-----------------------------------------------*/ Force::Force(Force *f) /* PURPOSE: Copies the passed Force object RECEIVES: f - Force object to copy RETURNS: copy of f REMARKS: */ { for(int i = 0; i < 3; i++) { _forceVec[i] = f->_forceVec[i]; } } /*-----------------------------------------------*/ void Force::exert(Physics *physics, World *world, GLfloat vec[]) /* PURPOSE: Returns the force exerted by this Force on the supplied Physics object physics into the vector vec RECEIVES: physics -- Physics object to use in a caluclating the Force world -- ambient World in which this Force lives vec -- vector to copy force into. RETURNS: REMARKS: For gravitional forces do not need to looks at the Physics object */ { for(int i = 0; i < 3; i++) { vec[i] = _forceVec[i]; } } //SpringForce Class Implementations /*-----------------------------------------------*/ SpringForce::SpringForce(string id, GLfloat springConstant, GLfloat fVec[], GLfloat oVec[]) : Force(fVec) /* PURPOSE: Creates a SpringForce which is attached to the Shape object with the given string id which has the given springConstant and offset vector RECEIVES: id - string id of Object this Force is attached to springConstant -- used in Hooke's law fVec -- offset vector for the Shape this Force is applied to (where on shape Force applied) oVec -- offset vector for the other Shape this Force is attached to (where on other Shape Force is attached) RETURNS: a SpringForce object REMARKS: */ { _id = id; _springConstant = springConstant; for(int i = 0; i < 3; i++) { _offsetOtherVec[i] = oVec[i]; } _otherPhysics = NULL; } /*-----------------------------------------------*/ SpringForce::SpringForce(SpringForce *f) : Force(f) /* PURPOSE: Copies the passed SpringForce object RECEIVES: f -- SpringForce object to copy RETURNS: a copy of f REMARKS: */ { _id = f->_id; _springConstant = f->_springConstant; _otherPhysics = f->_otherPhysics; for(int i = 0; i < 3; i++) { _offsetOtherVec[i] = f->_offsetOtherVec[i]; } } /*-----------------------------------------------*/ void SpringForce::exert(Physics *physics, World *world, GLfloat vec[]) /* PURPOSE: Returns a vector of how strong the spring force is currently being applied to the supplied Physics object RECEIVES: physics -- Physics object to apply the Force to world -- used to look up Shape that SpringForce is between vec - Glfloat vector to return the vector of force in RETURNS: REMARKS: (1) At this point, this method could crash if the other Shape the SpringForce is on is deleted (2)Note this force is exerted assymmetrically. That is, the object the other end of the spring is attached to is assumed to have infinite mass so isn't pussed toward this object */ { if(!_otherPhysics) { Shape *otherShape = world->findShapeByID(_id); if(!otherShape) return; _otherPhysics = otherShape->physics(); } int i; GLfloat *positionThis = physics->position(); GLfloat *positionOther = _otherPhysics->position(); GLfloat attitudeThis[16]; GLfloat attitudeOther[16]; physics->attitude(attitudeThis); _otherPhysics->attitude(attitudeOther); physics->attitude(attitudeOther); GLfloat springPositionThis[3]; GLfloat springPositionOther[3]; GLfloat localOffsetThis[3]; GLfloat localOffsetOther[3]; for(i = 0; i < 3; i++) { localOffsetThis[i] = _forceVec[i]*(attitudeThis[i] + attitudeThis[i+3] + attitudeThis[i+6]); localOffsetOther[i] = _offsetOtherVec[i]*(attitudeOther[i] + attitudeOther[i+3] + attitudeOther[i+6]); } for(i = 0; i < 3; i++) { springPositionThis[i] = positionThis[i] + localOffsetThis[i]; springPositionOther[i] = positionOther[i] + _offsetOtherVec[i]; vec[i] = _springConstant*(springPositionOther[i]-springPositionThis[i]); } } //ForceFactory Class Implementations /*-----------------------------------------------*/ Force* ForceFactory::create(stringstream *forceStream) /* PURPOSE: Used to create a Force object that can be applied to a Physics object based on the supplied stringstream object RECEIVES: forceStream -- stringstream object to parse a Force out of RETURNS: a Force object REMARKS: */ { Force *force; string token; string id; GLfloat springConstant; GLfloat forceVec[3]; GLfloat offsetOtherVec[3]; try { *forceStream >> token; if(token == "<gravity>") { *forceStream >> forceVec[0] >> forceVec[1] >> forceVec[2]; *forceStream >> token; if(token != "</gravity>") throw string("No close </gravity> " + token); force = new Force(forceVec); } else if(token == "<spring>") { *forceStream >> id; *forceStream >> springConstant; *forceStream >> forceVec[0] >> forceVec[1] >> forceVec[2]; *forceStream >> offsetOtherVec[0] >> offsetOtherVec[1] >> offsetOtherVec[2]; *forceStream >> token; if(token != "</spring>") throw string("No close </spring> " + token); force = new SpringForce(id, springConstant, forceVec, offsetOtherVec); } else { throw string("Unrecognized token " + token + " before </force>"); } } catch(string error) { cerr <<"Parse error:" << error; exit(1); } return force; } /*-----------------------------------------------*/ Force* ForceFactory::copy(Force *f) /* PURPOSE: Used to copy a Force object on a Physics object. RECEIVES: f - pointer to Force to copy RETURNS: copy of f REMARKS: */ { Force *force; if(typeid(*f) == typeid(SpringForce)) { force = new SpringForce((SpringForce *)f); } else { force = new Force(f); } return force; } //Physics Class Implementations /*-----------------------------------------------*/ Physics::Physics(GLfloat p[], GLfloat u[], GLfloat v[], bool s, GLfloat m, vector<Force*> *f) /* PURPOSE: Creates a Physics object with a given position, up direction, velocity, mass and forces RECEIVES: p - vector for position u - vector for the up direction for this object v - velocity of this object (if object is stationary still set non-zero to get a coordinate system) s - whether or not the object is stationary m - objects mass f - vector of Force pointers that are applied to this object RETURNS: a Physics object REMARKS: */ { for(int i = 0; i< 3; i++) { _position[i] = p[i]; _up[i] = u[i]; _velocity[i] = v[i]; _acceleration[i] = 0; } _stationary = s; _mass = m; _forces = f; } /*-----------------------------------------------*/ Physics::Physics(Physics *p) /* PURPOSE: Used to create a deep copy of a supplied Physics object RECEIVES: p - pointer to Physics object to copy RETURNS: the copy REMARKS: */ { unsigned int i; for(i = 0; i < 3; i++) { _position[i] = p->_position[i]; _up[i] = p->_up[i]; _velocity[i] = p->_velocity[i]; _acceleration[i] = p->_acceleration[i]; } _mass = p->_mass; _stationary = p->_stationary; _forces = new vector<Force*>(); vector<Force *> *pforces = p->_forces; for(i = 0; i < pforces->size(); i++) { _forces->push_back(ForceFactory::copy((*pforces)[i])); } } /*-----------------------------------------------*/ Physics::~Physics() /* PURPOSE: Deletes the dynamically allocated objects of this Physics object. In this case, just the Force objects on it. RECEIVES: nothing RETURNS: nothing REMARKS: */ { for(unsigned int i = 0; i < _forces->size(); i++) { if((*_forces)[i]) delete (*_forces)[i]; } delete _forces; } /*-----------------------------------------------*/ void Physics::attitude(GLfloat attitudeMatrix[]) /* PURPOSE: Computes the attitude matrix (the local coordinates) of this Physics object RECEIVES: attitudeMatrix - array to store attitude matrix into RETURNS:nothing REMARKS: */ { GLfloat tangent[3]; GLfloat normal[3]; GLfloat binormal[3]; GLfloat *up = _up; if(_acceleration[0] || _acceleration[1] || _acceleration[2]) up = _acceleration; int i; for(i = 0; i < 3; i++) { tangent[i] = _velocity[i]; normal[i] = up[i]; attitudeMatrix[4*i + 3] = 0; attitudeMatrix[i + 12] = 0; } attitudeMatrix[15] = 1; if(up==_acceleration) { crossProduct( normal, tangent, binormal); } else { crossProduct( tangent, normal, binormal); } normalize(tangent); normalize(binormal); crossProduct(tangent, binormal, normal); normalize(normal); int m; for(i = 0; i < 3; i++) { m = 4*i; attitudeMatrix[m] = tangent[i]; attitudeMatrix[m+1] = normal[i]; attitudeMatrix[m+2] = binormal[i]; } } /*-----------------------------------------------*/ void Physics::addForce(Force* force) /* PURPOSE: Adds supplied Force to the vector of Force's on this Physic's object RECEIVES: force -- the Force to add RETURNS: nothing REMARKS: */ { _forces->push_back(force); } /*-----------------------------------------------*/ void Physics::update(World *world) /* PURPOSE: updates the acceleration of this Physics object according to current values of the Force's on this object. RECEIVES: world -- ambient World that this Physics belongs to useful for looking up object that focres might be between RETURNS: Nothing REMARKS: */ { unsigned int i, j; GLfloat forceVec[3]; GLfloat tmpVec[3]; for(i = 0; i < 3; i++) { forceVec[i] = 0.0; } for(i = 0; i < _forces->size(); i++) { (*_forces)[i]->exert(this, world, tmpVec); for(j = 0; j < 3; j++) { forceVec[j] += tmpVec[j]; } } for(i = 0; i < 3; i++) { _acceleration[i] = forceVec[i]/_mass; } } /*-----------------------------------------------*/ void Physics::move(int framesElapsed) /* PURPOSE: change the _position and _velocity of this Physics according to the current _acceleration and the number of frames that have passed since the last movement RECEIVES: framesElapsed -- number of frames since the last movement. RETURNS: REMARKS: */ { if(!_stationary) { for(int i = 0; i < 3; i++) { _velocity[i] += _acceleration[i]*framesElapsed; _position[i] += _velocity[i]*framesElapsed; } } } /*-----------------------------------------------*/ void Physics::increaseVelocity(GLfloat vec[]) /* PURPOSE: Adds to the _velocity vector stored in this Physics object the supplied vector vec. RECEIVES: vec -- vector to add RETURNS: nothing REMARKS: */ { for(int i = 0; i < 3; i++) { _velocity[i] += vec[i]; } } //PhysicsFactory Class Implementations /*-----------------------------------------------*/ Physics* PhysicsFactory::create(stringstream *physicsStream) /* PURPOSE: Used to create a Physics for a Shape based on the supplied stringstream context. (Parses this stream). The Physics of a Shape will control how the Shape will move according to Physical principles RECEIVES: physicsStream -- stringstream to parse the Physics object out of RETURNS: nothing REMARKS: */ { string token; GLfloat position[3]; GLfloat velocity[3]; GLfloat up[3]; GLfloat mass; vector<Force *> *forces = new vector<Force *>; bool stationary; unsigned int mask = 0; bool exitFlag = false; try { while(*physicsStream && !exitFlag) { *physicsStream >> token; if(token == "<position>") { if((mask & 1) == 1) throw string("Can only define a position once within a <physics> tag"); *physicsStream >> position[0] >> position[1] >> position[2]; *physicsStream >> token; if(token != "</position>") throw string("No close </position> " + token); mask |= 1; } else if(token == "<up>") { if((mask & 2) == 2) throw string("Can only define an <up> vector once within a <physics> tag"); *physicsStream >> up[0] >> up[1] >> up[2]; *physicsStream >> token; if(token != "</up>") throw string("No close </up> " + token); mask |= 2; } else if(token == "<velocity>") { if((mask & 4) == 4) throw string("Can only define an <velocity> vector once within a <physics> tag"); *physicsStream >> velocity[0] >> velocity[1] >> velocity[2]; *physicsStream >> token; if(token != "</velocity>") throw string("No close </velocity> " + token); mask |= 4; } else if(token == "<stationary>") { if((mask & 8) == 8) throw string("Can only define an <stationary> flag once within a <physics> tag"); *physicsStream >> stationary; *physicsStream >> token; if(token != "</stationary>") throw string("No close </stationary> " + token); mask |= 8; } else if(token == "<mass>") { if((mask & 16) == 16) throw string("Can only define an <mass> flag once within a <physics> tag"); *physicsStream >> mass; *physicsStream >> token; if(token != "</mass>") throw string("No close </mass> " + token); mask |= 26; } else if(token == "<force>") { forces->push_back(ForceFactory::create(physicsStream)); *physicsStream >> token; if(token != "</force>") throw string("No close </force> " + token); } else if(token == "</physics>") { exitFlag = true; } else { throw string("Unrecognized token " + token + " before </physics>"); } } if( mask < 31) throw string("Parameters missing from <physics> description."); } catch(string error) { cerr <<"Parse error:" << error; exit(1); } return new Physics(position, up, velocity, stationary, mass, forces); } /*-----------------------------------------------*/ Physics* PhysicsFactory::copy(Physics *p) /* PURPOSE: Used to copy the Physics object of a Shape. RECEIVES: p - Physics object to copy RETURNS: a copy of p REMARKS: */ { Physics *physics = new Physics(p); //could check for type and call the appropriate constructor return physics; } //Shape Class Implementations /*-----------------------------------------------*/ Shape::Shape(string id, int l, Physics* p, Appearance* ap) /* PURPOSE: Constructs a Shape object. Shape's represent one object in our World They have an id, lifespan, physics and appearance. RECEIVES: id -- string name for this Shape l -- lifespan in frames that this Shape will live in the World p -- Physics used to control this Shape's motion ap -- Appearance shape will be drawn with RETURNS: Shape object REMARKS: */ { _id = id; _lifespan = l; _physics = p; _appearance = ap; _age = 0; } /*-----------------------------------------------*/ Shape::Shape(Shape *s) /* PURPOSE: Copy constructs the Shape pointed to by s. RECEIVES: s -- Shape to copy RETURNS: a copy of s REMARKS: */ { _id = s->_id; _lifespan = s->_lifespan; _physics = PhysicsFactory::copy(s->_physics); _appearance = AppearanceFactory::copy(s->_appearance); _age = s->_age; } /*-----------------------------------------------*/ Shape::~Shape() /* PURPOSE: Used to delete the dynamically created objects of this Shape. i.e., the _physics and _appearance. RECEIVES: nothing RETURNS: nothing REMARKS: */ { if(_physics) delete _physics; if(_appearance) delete _appearance; } /*-----------------------------------------------*/ bool Shape::cullable() /* PURPOSE: Used to determine if this Shape is ready to be deleted RECEIVES: nothing RETURNS: Whether this Shape has lived more that _lifespan many frames REMARKS: If the _lifespan is set to a value <= 0 the Shape is immortal */ { if(_lifespan > 0) { return (_lifespan < _age); } else { return false; } } /*-----------------------------------------------*/ void Shape::draw(World *world) /* PURPOSE: Use to draw this Shape in by its World according to its Appearance RECEIVES: nothing RETURNS: nothing REMARKS: */ { _appearance->preDraw(_physics, world); _appearance->draw(_physics); _appearance->postDraw(); } /*-----------------------------------------------*/ void Shape::update(int framesElapsed, World *world) /* PURPOSE: Updates the acceleration of this Shape stored in its Physic's object according to the Force's acting on the Shape RECEIVES: framesElapsed -- number of frames since last update world -- World this shape belongs to, useful for looking up other shapes which may be connected to this shape RETURNS: nothing REMARKS: */ { _age += framesElapsed; _physics->update(world); } /*-----------------------------------------------*/ void Shape::move(int framesElapsed) /* PURPOSE: Used to move this Shape by changing its position and velocity stroed in its Physic's object RECEIVES: framesElapsed -- used to figure out how far to move based on the number of frames since last movement RETURNS: REMARKS: */ { _physics->move(framesElapsed); } //ShapeFactory Implementations /*-----------------------------------------------*/ Shape* ShapeFactory::create(stringstream *shapeStream) /* PURPOSE: Used to create a Shape for a World based on the supplied stringstream context. (Parses this stream) RECEIVES: shapeStream -- stringstream object to parse Shape out of RETURNS: Shape object REMARKS: */ { string token; string id; int lifespan; int mask = 0; Appearance *appearance; Physics *physics; try { for(int i = 0; i < 4; i++) { *shapeStream >> token; if(token == "<id>") { *shapeStream >> id; *shapeStream >> token; if(token != "</id>") throw string("No close </id> " + token); mask |=1; } else if(token == "<lifespan>") { *shapeStream >> lifespan; *shapeStream >> token; if(token != "</lifespan>") throw string("No close </lifespan> " + token); mask |=2; } else if(token == "<appearance>") { appearance = AppearanceFactory::create(shapeStream); *shapeStream >> token; if(token != "</appearance>") throw string("No close </appearance> " + token); mask |=4; } else if(token == "<physics>") { physics = PhysicsFactory::create(shapeStream); mask |=8; } else { throw string("Unrecognized token " + token); } } if(mask != 15) throw string("Missing Shape Parameter"); } catch(string error) { cerr <<"Parse error:" << error; exit(1); } return new Shape(id, lifespan, physics, appearance); } //Event Implementations /*-----------------------------------------------*/ Event::Event(int f, vector<string>* d, vector<Shape *>* iS, bool reset=false) /* PURPOSE: Used to consrtuct an Event that can be added to a Script that can be played on a world RECEIVES: f - number of frames after the preceding Event in the Script completes before carrying out the actions of this Event d - string ID's of Shape objects that will be deleted when this Event is carried out iS - Shape's to be inserted into the World when this Event is carried out RETURNS: REMARKS: */ { _framesTillAction = f; _deleteShapes = d; _insertShapes = iS; _reset = reset; } /*-----------------------------------------------*/ Event::~Event() /* PURPOSE: Destructor for an Event. Deletes dynamically created vectors used to store the actions of an Event RECEIVES: nothing RETURNS: REMARKS: */ { if(_insertShapes) { for(unsigned int i = 0; i < _insertShapes->size(); i++) { if((*_insertShapes)[i]) delete (*_insertShapes)[i]; } delete _insertShapes; } if(_deleteShapes) delete _deleteShapes; } /*-----------------------------------------------*/ void Event::addInsertShape(Shape *shape) /* PURPOSE: Add to this Event's vector of shapes to insert into a world RECEIVES: shape -- Shape object to be inserted into the world by this Event RETURNS: nothing REMARKS: */ { _insertShapes->push_back(shape); } /*-----------------------------------------------*/ void Event::addDeleteShapeByID(string shapeID) /* PURPOSE: Add to the vector of Shapes that will be deleted by this Event RECEIVES: shapeId -- string containing name of a Shape to delete RETURNS: nothing REMARKS: */ { _deleteShapes->push_back(shapeID); } /*-----------------------------------------------*/ void Event::action(World *world) /* PURPOSE: Carries out the actions of a this Event on the supplied world, this could be to delete some shapes, add some new ones, or to reset the World RECEIVES: world - World that this event s to be carried out on RETURNS: REMARKS: */ { unsigned int i; list<Shape *>* shapes = world->shapes(); for( i = 0 ; i < _deleteShapes->size(); i++) { for(list<Shape* >::iterator iter = shapes->begin(); iter != shapes->end(); iter++) { if( (*iter)->id() == (*_deleteShapes)[i]) { if(*iter) delete (*iter); iter = shapes->erase(iter); } } } for(unsigned i = 0 ; i < _insertShapes->size(); i++) { shapes->push_back(new Shape((*_insertShapes)[i])); } world->setResetting(_reset); } //EventFactory Implementations /*-----------------------------------------------*/ Event* EventFactory::create(stringstream *eventStream) /* PURPOSE: To create an Event for a Script for a World based on parsing the supplied stringstream RECEIVES: eventStream -- pointer to a stringstream parsed for an Event RETURNS: the Event parsed from the stringstream REMARKS: */ { bool haveFramesTillAction = false; bool exitFlag = false; bool reset = false; int framesTillAction = 0; Shape* shape; string deleteID; vector<Shape *> *insertShapes = new vector<Shape *>; vector<string> *deleteShapes = new vector<string>; string token; try { while(*eventStream && !exitFlag) { *eventStream >> token; if(token == "<framesTillAction>") { *eventStream >> framesTillAction; *eventStream >> token; if(token != "</framesTillAction>") throw string("No close </framesTillAction> " + token); haveFramesTillAction = true; } else if(token == "<shape>") { shape = ShapeFactory::create(eventStream); insertShapes->push_back(shape); *eventStream >> token; if(token != "</shape>") throw string("No close </shape> " + token); } else if(token == "<delete>") { *eventStream >> deleteID; *eventStream >> token; deleteShapes->push_back(deleteID); if(token != "</delete>") throw string("No close </delete> " + token); } else if(token == "<particles>") { EventFactory::createParticles(eventStream, insertShapes); *eventStream >> token; if(token != "</particles>") throw string("No close </particle> " + token); } else if(token == "<reset>") { reset = true; *eventStream >> token; if(token != "</reset>") throw string("No close </reset> " + token); } else if(token == "</event>") { exitFlag = true; } else { throw string("Unrecognized token " + token + " before </event>"); } } if(!haveFramesTillAction && exitFlag) throw string("Missing framesTillAction in event"); } catch(string error) { cerr <<"Parse error:" << error; exit(1); } return new Event(framesTillAction, deleteShapes, insertShapes, reset); } /*-----------------------------------------------*/ void EventFactory::createParticles(stringstream *eventStream, vector<Shape *> *shapes) /* PURPOSE: Parses information about a particle system from the supplied event stream so that the Shape's of this particle system can be created RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { string token; string id; int number; GLfloat dispersionSpeed; Shape *shape; int mask = 0; try { for(int i = 0; i < 4; i++) { *eventStream >> token; if(token == "<id>") { *eventStream >> id; *eventStream >> token; if(token != "</id>") throw string("No close </id> " + token); mask |=1; } else if(token == "<number>") { *eventStream >> number; *eventStream >> token; if(token != "</number>") throw string("No close </number> " + token); mask |=2; } else if(token == "<dispersionSpeed>") { *eventStream >> dispersionSpeed; *eventStream >> token; if(token != "</dispersionSpeed>") throw string("No close </dispersionSpeed> " + token); mask |=4; } else if(token == "<shape>") { shape = ShapeFactory::create(eventStream); *eventStream >> token; if(token != "</shape>") throw string("No close </shape> " + token); mask |=8; } else { throw string("Unrecognized token " + token); } } if(mask != 15) throw string("Missing particles Parameter"); } catch(string error) { cerr <<"Parse error:" << error; exit(1); } Physics* physics; Appearance* appearance; int lifespan = shape->lifespan(); Shape* tmpShape; GLfloat randVec[3]; for(int i = 0; i < number; i++) { randomUnit(randVec); for(int j = 0; j < 3; j++) { randVec[j] *= dispersionSpeed; } appearance = AppearanceFactory::copy(shape->appearance()); physics = PhysicsFactory::copy(shape->physics()); physics->increaseVelocity(randVec); tmpShape = new Shape(id, lifespan, physics, appearance); shapes->push_back(tmpShape); } delete shape; } //Script Class Implementations /*-----------------------------------------------*/ Script::Script() /* PURPOSE: Used to create a Script with no Events and whose frame parameters are set to zero RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { _currentEventIndex = 0; _currentFrame = 0; } /*-----------------------------------------------*/ Script::~Script() /* PURPOSE: Deletes all of the dynamically created Event's in the Script RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { for(unsigned int i = 0; i < _events.size(); i++) { if(_events[i]) delete _events[i]; } } /*-----------------------------------------------*/ int Script::step(int framesElapsed, World *world) /* PURPOSE: Processes framesElapsed many frames of the current Script on the supplied World RECEIVES: framesElapsed -- number of frames to process world -- World that the Script will be carried out on RETURNS: Nothing REMARKS: */ { if(_currentEventIndex < _events.size()) { _currentFrame += framesElapsed; int framesTillAction = _events[_currentEventIndex]->framesTillAction(); if(framesTillAction <= _currentFrame) { _currentFrame -= framesTillAction; _events[_currentEventIndex]->action(world); _currentEventIndex++; } } return framesElapsed; } /*-----------------------------------------------*/ void Script::addEvent(Event *evt) /* PURPOSE: Adds an Event to the current Script RECEIVES: evt - an Event to add to the end of this Script RETURNS: Nothing REMARKS: */ { _events.push_back(evt); } /*-----------------------------------------------*/ void Script::reset() /* PURPOSE: Returns a Script back to its start state so it can be played again RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { _currentFrame = 0; _currentEventIndex = 0; } //ScriptFactory Class Implementations /*-----------------------------------------------*/ Script* ScriptFactory::create(stringstream *scriptStream) /* PURPOSE: Used to create a Script for a World based on the supplied stringstream context. (Parses this stream) RECEIVES: scriptStream - stream to parse into a Script for world RETURNS: a Script playable on a World REMARKS: */ { string token; Event *event; Script *script = new Script(); scriptStream->setf(ios::skipws); //skip whitespace try { *scriptStream >> token; while(*scriptStream) { if(token == "<event>") { event = EventFactory::create(scriptStream); script->addEvent(event); } else { throw string("Unrecognized token " + token + " before </event>"); } *scriptStream >> token; } } catch(string error) { cerr <<"Parse error:" << error; exit(1); } return script; } //Texture Class Implementations /*-----------------------------------------------*/ Texture::Texture(string id, int w, int h, GLfloat *d) /* PURPOSE: Creates a Texture with the given id, of the given width and height, and with the given texture data. Such a Texture might be applied to a Shape to give it a more realistic appearance. RECEIVES: id - string name of the Texture w - width of the Texture h - height of the Texture d - RGBA color data for this Texture RETURNS: REMARKS: */ { _identity = id; _width = w; _height = h; _data = d; } /*-----------------------------------------------*/ Texture::~Texture() /* PURPOSE: Dectructor. Gets rid of _data whose contents is dynamically allocated RECEIVES: nothing RETURNS: nothing REMARKS: */ { if(_data) delete _data; } //TexturesFactory Class Implementations /*-----------------------------------------------*/ vector<Texture *>* TexturesFactory::create(stringstream *textureStream) /* PURPOSE: Used to create a vector of Texture's for a World based on the supplied stringstream context. (Parses this stream) These Texture's can be texture mapped to Shape's in the world as needed. RECEIVES: textureStream - stream to parse Texture's from RETURNS: a pointer to a vector of Texture pointers. REMARKS: */ { string token; Texture *texture; vector<Texture *> *textureRepository = new vector<Texture *>(); textureStream->setf(ios::skipws); //skip whitespace try { *textureStream >> token; while(*textureStream) { if(token == "<texture>") { texture = TexturesFactory::createTexture(textureStream); textureRepository->push_back(texture); *textureStream >> token; if(token != "</texture>") throw string("Expecting token </texture> rather than" +token); } else { throw string("Unrecognized token " + token + " before </texture>"); } *textureStream >> token; } } catch(string error) { cerr <<"Parse error:" << error; exit(1); } return textureRepository; } /*-----------------------------------------------*/ Texture* TexturesFactory::createTexture(stringstream *textureStream) /* PURPOSE: Used to create a single Texture based on the supplied stringstream context. (Parses this stream) These Texture's can be texture mapped to Shape's in the world as needed. RECEIVES: textureStream - stream to parse Texture from RETURNS: a pointer to a Texture. REMARKS: */ { string token; string id; int width; int height; GLfloat *data; int mask = 0; try { for(int i = 0; i < 4; i++) { *textureStream >> token; if(token == "<id>") { *textureStream >> id; *textureStream >> token; if(token != "</id>") throw string("No close </id> " + token); mask |=1; } else if(token == "<width>") { *textureStream >> width; *textureStream >> token; if(token != "</width>") throw string("No close </width> " + token); mask |=2; } else if(token == "<height>") { *textureStream >> height; *textureStream >> token; if(token != "</height>") throw string("No close </height> " + token); mask |=4; } else if(token == "<data>") { if((mask & 6) != 6) throw string("<width> and <height> info must come before <data>."); int textureLength = 4*width*height; data = new GLfloat[textureLength]; for(int j = 0; j < textureLength; j++) { *textureStream >> data[j]; } *textureStream >> token; if(token != "</data>") throw string("No close </data> " + token); mask |=8; } else { throw string("Unrecognized token " + token); } } if(mask != 15) throw string("Missing texture Parameter"); } catch(string error) { cerr <<"Parse error:" << error; exit(1); } return new Texture(id, width, height, data); } //World Class Implementations /*-----------------------------------------------*/ World::World() /* PURPOSE: Creates and initializes a Word with a blank Script and no Shape's. RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { _shapes = new list<Shape *>; _script = new Script(); } /*-----------------------------------------------*/ World::~World() /* PURPOSE: Deletes are the dynamically created objects on this World. Mainly, Shape's. RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { reset(); if(_shapes) delete _shapes; if(_script ) delete _script; if(_textures) { for(int i = 0 ; i < _textures->size(); i++) { delete (*_textures)[i]; } delete _textures; } } /*-----------------------------------------------*/ void World::setScript(Script *s) /* PURPOSE: Sets what Script is to be played out on this world. RECEIVES: s - pointer to Script to use RETURNS: Nothing REMARKS: Called at start up after script file read. Also, called after the space key is hit and the script file is re-read (useful for debugging) */ { if(_script) delete _script; _script = s; } /*-----------------------------------------------*/ void World::setTextures(vector<Texture*> *t) /* PURPOSE: Sets what Texture's are available on this world. RECEIVES: t - pointer to a vector of Texture pointer than can be used RETURNS: Nothing REMARKS: Called at start up after Texture file read. Also, called after the space key is hit and the script file is re-read (useful for debugging) */ { if(_textures) { for(int i = 0 ; i <_textures->size(); i++) { delete (*_textures)[i]; } delete _textures; } _textures = t; } /*-----------------------------------------------*/ Shape* World::findShapeByID(string id) /* PURPOSE: Finds and returns a pointer to a Shape of this World by string id RECEIVES: s - string ID to use RETURNS: Desired Shape* if it exists; NULL otherwise REMARKS: */ { for(list<Shape* >::iterator iter = _shapes->begin(); iter != _shapes->end(); iter++) { if(id == (*iter)->id()) return *iter; } return NULL; } /*-----------------------------------------------*/ Texture* World::findTextureByID(string id) /* PURPOSE: Finds and returns a pointer to a Texture of this World by string id RECEIVES: s - string ID to use RETURNS: Desired TExture* if it exists; NULL otherwise REMARKS: */ { for(int i=0; _textures->size(); i++) { if(id == (*_textures)[i]->id()) return (*_textures)[i]; } return NULL; } /*-----------------------------------------------*/ void World::cull() /* PURPOSE: Removes all shapes from the World that had been previously marked as deletable RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { for(list<Shape* >::iterator iter = _shapes->begin(); iter != _shapes->end(); iter++) { if((*iter)->cullable()) { delete (*iter); iter = _shapes->erase(iter); } } } /*-----------------------------------------------*/ void World::draw() /* PURPOSE: Draws all the Shape's in this World RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { for(list<Shape* >::iterator iter = _shapes->begin(); iter != _shapes->end(); iter++) { (*iter)->draw(this); } } /*-----------------------------------------------*/ void World::move(int framesElapsed) /* PURPOSE: Calculates new positions and velocities for all objects in our World RECEIVES: framesElapsed- numbers of frames that have passed since the last time this operation was applied RETURNS: Nothing REMARKS: */ { for(list<Shape* >::iterator iter = _shapes->begin(); iter != _shapes->end(); iter++) { (*iter)->move(framesElapsed); } } /*-----------------------------------------------*/ void World::update(int framesElapsed) /* PURPOSE: Calculates new accelerations based on current forces for all objects in our World It also determines which objects have lived beyond their lifespan and marks them RECEIVES: framesElapsed- numbers of frames that have passed since the last time this operation was applied RETURNS: Nothing REMARKS: */ { for(list<Shape* >::iterator iter = _shapes->begin(); iter != _shapes->end(); iter++) { (*iter)->update(framesElapsed, this); } } /*-----------------------------------------------*/ void World::step(int framesElapsed) /* PURPOSE: does one complete update/move/redraw of all the objects in the world Dead Shapes are culled RECEIVES: framesElapsed- numbers of frames that have passed since the last time this operation was applied RETURNS: Nothing REMARKS: */ { framesElapsed = _script->step(framesElapsed, this); //if framesElapsed was greater then the next script action the framesElasped is reduced update(framesElapsed); cull(); move(framesElapsed); if(_resetting) reset(); } /*-----------------------------------------------*/ void World::reset() /* PURPOSE: Returns the world to the same state as when the program was first run RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { _resetting = false; for(list<Shape* >::iterator iter = _shapes->begin(); iter != _shapes->end(); iter++) { delete (*iter); } _shapes->clear(); _script->reset(); } /*-----------------------------------------------*/ void readScriptFile() /* PURPOSE: Reads a file containing a script of what events will happen on this program's World RECEIVES: filename - name of file with script for world. RETURNS: nothing REMARKS: */ { Script *script; ifstream fin; stringstream scriptStream(stringstream::in | stringstream::out); char c; try { fin.open(scriptName.c_str()); if(fin.fail()) throw string("Failed to open World Script"); while (!fin.eof()) { fin.get(c); if(c=='<') scriptStream << ' '; //this is a hack scriptStream << c; if(c=='>') scriptStream << ' '; } script = ScriptFactory::create(&scriptStream); fin.close(); } catch(string error) { cerr << error << endl; cerr << "No world script" << endl; exit(1); } world.reset(); world.setScript(script); cout << "Done reading script!!\n"; } /*-----------------------------------------------*/ void readTexturesFile() /* PURPOSE: Reads a file containing Texture maps for the World and add those Texture's RECEIVES: filename - name of file with textures for world. RETURNS: nothing REMARKS: */ { vector<Texture*> *textures; ifstream fin; stringstream textureStream(stringstream::in | stringstream::out); char c; try { fin.open(textureName.c_str()); if(fin.fail()) throw string("Failed to open World Script"); while (!fin.eof()) { fin.get(c); if(c=='<') textureStream << ' '; //this is a hack textureStream << c; if(c=='>') textureStream << ' '; } textures = TexturesFactory::create(&textureStream); fin.close(); } catch(string error) { cerr << error << endl; cerr << "No world texture" << endl; exit(1); } world.setTextures(textures); cout << "Done reading Texture File!!\n"; } //CLASSLESS FUNCTIONS /*-----------------------------------------------*/ void normalize(GLfloat vec[]) /* PURPOSE: change supplied vector to be unit vector in same direction RECEIVES: vec - an array of three elements RETURNS: Nothing REMARKS: */ { int i; GLfloat sum = 0; for(i = 0; i < 3; i++) { sum += vec[i]*vec[i]; } GLfloat length = sqrt(sum); for(i = 0; i < 3; i++) { vec[i] /=length; } } /*-----------------------------------------------*/ void crossProduct(GLfloat vec1[], GLfloat vec2[], GLfloat resultVec[]) /* PURPOSE: computes the cross product of vec1 and vec2 storing the result in resultVec RECEIVES: vec1 - first vector in crossProduct vec2 - second vector in crossProduct resultVec - result of the crossProduct RETURNS: Nothing REMARKS: */ { resultVec[0] = vec1[1]*vec2[2] - vec2[1]*vec1[2]; resultVec[1] = vec1[2]*vec2[0] - vec2[2]*vec1[0]; resultVec[2] = vec1[0]*vec2[1] - vec2[0]*vec1[1]; } /*-----------------------------------------------*/ void randomUnit(GLfloat vec[]) /* PURPOSE: generate a random vector of length 1 RECEIVES: vec - an array of three elements RETURNS: Nothing REMARKS: Vectors along coordinate axes are impossible because of my hack to avoid the zero vector */ { //generate random point within unit sphere int i=0; while( i < 3) { vec[i] = GLfloat(rand())/(RAND_MAX+1.0) - .5; if(vec[i] == 0) i--; //hack to avoid the zero vector i++; } normalize(vec); //push out to unit sphere } /*-----------------------------------------------*/ void init() /* PURPOSE: sets the background color to black RECEIVES: RETURNS: Nothing REMARKS: */ { glClearColor(0.0, 0.0, 0.0, 0.0); glShadeModel(GL_SMOOTH); readScriptFile(); readTexturesFile(); } /*-----------------------------------------------*/ void makeLighting() /* PURPOSE: sets the evening lighting for our city landscape RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { glEnable(GL_LIGHTING); glLightfv(GL_LIGHT0, GL_POSITION, SUN_VECTOR); glLightfv(GL_LIGHT0, GL_AMBIENT, SUN_AMBIENT_SPECULAR); glLightfv(GL_LIGHT0, GL_DIFFUSE, SUN_DIFFUSE); glLightfv(GL_LIGHT0, GL_SPECULAR, SUN_AMBIENT_SPECULAR); glEnable(GL_LIGHT0); glLightModelfv(GL_LIGHT_MODEL_AMBIENT, AMBIENT_LIGHT); } /*-----------------------------------------------*/ void drawFn() /* PURPOSE: Used to craw the complete scene saturn + probe and to swap scene buffers RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { //Compute Depths glEnable(GL_DEPTH_TEST); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); makeLighting(); world.draw(); glutSwapBuffers(); } /*-----------------------------------------------*/ void keyFn(unsigned char key, int xPos, int yPos) /* PURPOSE: Handle nonspecial key events (mainly space key and 1 key) RECEIVES: key - key being pressed xPos - x position of mouse yPos - y position of mouse RETURNS: Nothing REMARKS: */ { switch(key) { case ' ': init(); break; } glutPostRedisplay(); // cause scene to be redrawn } /*-----------------------------------------------*/ void animateFn() /* PURPOSE: Uses system clock to update the angle of the cassini space probe RECEIVES: Nothing RETURNS: Nothing REMARKS: */ { clock_t newTime = clock(); int framesElapsed = (newTime - animationTime)/FRAME_TIME; if(framesElapsed >= 1) { animationTime = newTime; world.step(framesElapsed); glutPostRedisplay(); // cause scene to be redrawn } } /*-----------------------------------------------*/ void reshapeFn(int newWidth, int newHeight) /* PURPOSE: To redraw scene when window gets resized. RECEIVES: newWidth -- new window x size newHeight -- new window y size RETURNS: Nothing REMARKS: */ { glViewport(0, 0, newWidth, newHeight); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(-1.0, 1.0, -1.0, 1.0, NEAR_CLIP, FAR_CLIP); gluLookAt(CAMERA_X, CAMERA_Y, CAMERA_Z, 0.0, 0.0, 0.0, UP_X, UP_Y, UP_Z); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } int main (int argc, char **argv) { if(argc >= 2) { scriptName = argv[1]; } else { scriptName = "script.txt"; } if(argc >= 3) { textureName = argv[2]; } else { textureName = "textures.txt"; } glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); /*Notice added GLUT_DEPTH to do depth detection I am using double buffering for the animation */ glutInitWindowPosition(initX, initY); glutInitWindowSize(winWidth, winHeight); glutCreateWindow("Flying Carpet!!"); init(); glutDisplayFunc(drawFn); glutReshapeFunc(reshapeFn); glutIdleFunc(animateFn); glutKeyboardFunc(keyFn); glutMainLoop(); return 0; } |