A Half-finished OpenGL Object Model
A. First Edition
I spent almost three days including Thanks-giving Day to try to establish an object-oriented model for those primitives and commonly-used model in OpenGL. However, the result is very disappointing. At some point, I have to stop this futile exploration as deadline of assignment is approaching. Then I realized one of the major reason why this way is not so popular. It is efficiency. When talking about efficiency, there are two aspect regarding it. One is the performance efficiency and the other is the coding efficiency. Students from Engineering department can enumerate various reasons against OOP when performance is highly required. Even students want to argue that OOP helps improving coding efficiency, they have to realize that OpenGL transformation must be done at a proper order and sometimes lack of flexibility in OOP will eliminate all those benefits brought by class model.
The idea of my small project is straight forward and it is different from Dr. Grogono who tries to wrap those basic primitives in OpenGL which is handy and sound. My design concentrate on all higher level object developed by programmer and try to find out all the common property and method of them. Then define a base class which will be inherited by all models and also define most commonly operations of them. i.e. Every model object has a property of coordinate, color, direction which is the axis of the object, occupying dimension which is the enclosing cube created by scaling on three dimensional direction. And all objects have to implement an abstract method display which will be called by a global callback. However, within this function programmer doesn't have to think about rotation, translate, scaling operations because base class already take care of that with the property of derived classes such as coordinate, direction, dimension volume etc. Only the most basic simple standard drawing function need to be defined by programmer. And an object manager class handles all creation, storage, viewing jobs of all objects. This sounds quite nice to me and one immediate benefit to this system is that I can used it in my project design by creating a script system that art user can create their 3D world by script file which will be read by my glut Object manager class. However, some viewing problem puzzled me for quite some time and I stopped there. Just in case I need them again, I store a half-finished version here.
In openGL, those error-prone transformation is tedious and boring. Why should I write those similar codes a thousand times?
I think I already explained very clearly about the purpose of this simple program.
Just in case you are not familiar with OpenGL and want a test-drive, you need to install an OpenGL programming environment in your VC++ by
copying "glut.h" file in your "/include" directory and a compiling library "glut32.lib" must be copied in your "lib" directory. The run-time
"dll", "glut32.dll" must be copied to your "system" or "system32" director in your "windows" directory. Once created with a "win console"
project in VC++, go to the "setting" of "project" and "linking" tab to add "glut32.lib".
(This sounds trivial for programmers, but it is worthwhile for beginners.:) And of course I can use a VC++ key word:
#pragma comment( lib, "glut32") to ask linker to search glut32.lib automatically, still those .h .lib and .dll file needs to be copied by
programmer.
E.Further improvement
The next step?
F.File listing
1. glutObject.h
2. glutObject.cpp
3. glutObjects.h
4. glutObjects.cpp
5. main.cpp
file name: glutObject.h
#ifndef GLUTOBJECT_H #define GLUTOBJECT_H #include <GL/glut.h> //#include "glutobjects.h" #include <cstdlib> //#define NICKSPACE_BEGIN namespace NICKSPACE { //#define NICKSPACE_END } //NICKSPACE_BEGIN const GLfloat PI=3.14159; const GLint MaximumObjectNumber=50; //typedef GLfloat[3] GlutVector; class GlutObjectList; extern GlutObjectList* gObjectListPtr; extern enum AdjustViewState; //extern enum PerspetiveStyle; const GLfloat DefaultPosition[3]={0,0,0}; const GLfloat DefaultDirection[3]={0,0,0}; const GLfloat DefaultColor[3]={1,0,0}; const GLfloat DefaultVolume[3]={1,1,1}; const GLfloat DefaultBackColor[3]={1,1,1}; const bool DefaultDrawingStyle=false;//this means solid instead of mesh const GLfloat DefaultPerspectAngle=70; const GLfloat DefaultViewVolume[3]={800, 600, 800}; const GLfloat DefaultNearPlane=0; const GLfloat DefaultWindowWidth=800; const GLfloat DefaultWindowHeight=600; const GLint DefaultWindowPosition_x=0; const GLint DefaultWindowPosition_y=0; //void display(); class GlutObject { friend class GlutObjectList; friend void arrayCopy(GLfloat tgt[], const GLfloat src[]); public: void setPosition(GLfloat x, GLfloat y, GLfloat z); void setPosition(GLfloat* array); void setDirection(GLfloat x, GLfloat y, GLfloat z); void setDirection(GLfloat* array); void setVolume(GLfloat w, GLfloat h, GLfloat l); void setColor(GLfloat r, GLfloat g, GLfloat b); void doRotation(); GlutObject(); //virtual void keyboardFunction(unsigned char key, GLint x, GLint y); void draw(); virtual void display()=0; void setClipRatio(GLfloat ratio); bool bDrawingMesh; protected: GLfloat position[3]; GLfloat direction[3]; GLfloat color[3]; GLfloat volume[3]; GLdouble clipPlane[4]; GLfloat clipRatio; bool bDisableClip; bool bVisible; void setClipPlane(); void normalizeDirection(); }; class GlutObjectList { friend class GlutObject; friend void displayCallback(); friend void reshapeCallback(int w, int h); friend void keyboardCallback(unsigned char key, int x, int y); friend void arrayCopy(GLfloat tgt[], const GLfloat src[]); friend void run(); friend void init(); public: GlutObjectList(); // void keyboardFunction(unsigned char key, GLint x, GLint y); protected: GLint objectCount; GLfloat perspectAngle; //PerspetiveStyle ePerspective; bool bPerspective; bool bDrawAxis; AdjustViewState eAdjustView; GLfloat viewVolume[3]; GLfloat windowWidth, windowHeight; GLfloat viewPortWidth, viewPortHeight; int windowPos_x, windowPos_y; GLfloat nearPlane; GLfloat backColor[3]; GLfloat foreColor[3]; GlutObject* objectList[MaximumObjectNumber]; void reshapeCallback(int w, int h); void displayCallback(); void keyboardCallback(unsigned char key, int x, int y); void addNewObject(GlutObject* ptr); //void draw3DAxis(GLfloat width, GLfloat height); void setDisplayMode(); void adjustView(); void init(); void run(); void drawAll(); }; //NICKSPACE_END #endif
file name: glutObject.cpp
#include <GL/glut.h> #include <cstdlib> #include <cmath> #include "glutObject.h" //NICKSPACE_BEGIN const GLint AdjustScale=2; GlutObjectList* gObjectListPtr=NULL; //enum PerspetiveStyle{PERSPECTIVE, ORTHOGONAL}; enum AdjustViewState{ADV_KEEPSHAPE=0, ADV_ENLARGE=AdjustScale, ADV_REDUCE=-AdjustScale}; GLdouble STDPlane[3][3]= { {1,0,0}, {0,1,0}, {0,0,1} }; const bool DefaultPerspectiveStyle=true; void GlutObject::setColor(GLfloat r, GLfloat g, GLfloat b) { color[0]=r; color[1]=g; color[2]=b; } void GlutObject::setPosition(GLfloat x, GLfloat y, GLfloat z) { position[0]=x; position[1]=y; position[2]=z; //setClipPlane(); } void GlutObject::setPosition(GLfloat* array) { for(int i=0; i<3; i++) { position[i]=array[i]; } //setClipPlane(); } void GlutObject::setDirection(GLfloat x, GLfloat y, GLfloat z) { direction[0]=x; direction[1]=y; direction[2]=z; } void GlutObject::setDirection(GLfloat* array) { for (int i=0; i<3; i++) { direction[i]=array[i]; } } void GlutObject::setVolume(GLfloat w, GLfloat h, GLfloat l) { volume[0]=w; volume[1]=h; volume[2]=l; } GlutObject::GlutObject() { if (gObjectListPtr==NULL) { gObjectListPtr=new GlutObjectList; } gObjectListPtr->addNewObject(this); arrayCopy(direction, DefaultDirection); arrayCopy(volume, DefaultVolume); arrayCopy(color, DefaultColor); arrayCopy(position, DefaultPosition); bDrawingMesh=DefaultDrawingStyle; clipRatio=0.0; setClipPlane(); bDisableClip=true; bVisible=true; } void arrayCopy(GLfloat tgt[], const GLfloat src[]) { for (int i=0; i<3; i++) { tgt[i]=src[i]; } } void GlutObject::normalizeDirection() { GLfloat temp; temp=sqrt(direction[0]*direction[0]+direction[1]*direction[1]+ direction[2]*direction[2]); direction[0]/=temp; direction[1]/=temp; direction[2]/=temp; } void GlutObject::doRotation() { /* GLfloat anglex, angley; //GLfloat d; //normalizeDirection(); anglex=atan2(direction[1], direction[2])*360/2/PI; //d=sqrt(direction[1]*direction[1]+direction[0]*direction[0]); angley=atan2(direction[0], direction[2])*360/2/PI; glRotatef(anglex, 1,0,0); glRotatef(angley, 0,1,0); */ for (int i=0; i<3; i++) { if (direction[i]!=0) { glRotatef(direction[i], STDPlane[i][0], STDPlane[i][1],STDPlane[i][2]); } } } void GlutObject::setClipRatio(GLfloat ratio) { clipPlane[3]=-ratio/2; } void GlutObject::setClipPlane() { //this is a naive clip plane setting, which is xz plane //at position clipPlane[0]=clipPlane[2]=0; clipPlane[1]=1; clipPlane[3]=0; } //no rotatations!!! void GlutObject::draw() { glPushMatrix(); glLoadIdentity(); glColor3f(color[0], color[1], color[2]); glTranslatef(position[0], position[1], position[2]); glScalef(volume[0], volume[1], volume[2]); //glTranslatef(-position[0], -position[1], -position[2]); doRotation(); //glTranslatef(-position[0], -position[1], -position[2]); if (!bDisableClip) { glClipPlane (GL_CLIP_PLANE0, clipPlane); glEnable (GL_CLIP_PLANE0); display(); glDisable(GL_CLIP_PLANE0); } else { display(); } //glTranslatef(-position[0], -position[1], -position[2]); glPopMatrix(); } //GlutObjectList void reshapeCallback(int w, int h) { gObjectListPtr->reshapeCallback(w, h); } void displayCallback() { gObjectListPtr->displayCallback(); } void keyboardCallback(unsigned char key, int x, int y) { gObjectListPtr->keyboardCallback(key, x, y); } void init() { gObjectListPtr->init(); } void run() { gObjectListPtr->run(); } void GlutObjectList::addNewObject(GlutObject* ptr) { objectList[objectCount++]=ptr; } GlutObjectList::GlutObjectList() { objectCount=0; arrayCopy(backColor, DefaultBackColor); arrayCopy(viewVolume, DefaultViewVolume); nearPlane=DefaultNearPlane; perspectAngle=DefaultPerspectAngle; windowWidth=DefaultWindowWidth; windowHeight=DefaultWindowHeight; windowPos_x=DefaultWindowPosition_x; windowPos_y=DefaultWindowPosition_y; viewPortWidth=windowWidth; viewPortHeight=windowHeight; //ePerspective=DefaultPerspectiveStyle; bPerspective=DefaultPerspectiveStyle; eAdjustView=ADV_KEEPSHAPE; setDisplayMode(); bDrawAxis=true; } void GlutObjectList::init() { } void GlutObjectList::keyboardCallback(unsigned char key, int x, int y) { int i; switch(key) { case 'm': case 'M': for (i=0; i<objectCount; i++) { objectList[i]->bDrawingMesh=!objectList[i]->bDrawingMesh; } break; case 'a': case 'A': bDrawAxis=!bDrawAxis; break; case 'p': case 'P': bPerspective=!bPerspective; //glClear(GL_COLOR_BUFFER_BIT); setDisplayMode(); //glFlush(); break; case 's': eAdjustView=ADV_REDUCE; adjustView(); break; case 'S': eAdjustView=ADV_ENLARGE; adjustView(); break; //THIS is only useful for debugging case 'c': case 'C': for (i=0; i<objectCount; i++) { objectList[i]->clipRatio+=0.1; objectList[i]->setClipRatio(objectList[i]->clipRatio); } break; case 27: exit(0); break; default: break; } glutPostRedisplay(); } void GlutObjectList::displayCallback() { glClear(GL_COLOR_BUFFER_BIT); drawAll(); glutPostRedisplay(); } void GlutObjectList::setDisplayMode() { glMatrixMode(GL_PROJECTION); glLoadIdentity(); if(bPerspective) { gluPerspective(perspectAngle, viewVolume[0]/viewVolume[1], nearPlane, nearPlane+viewVolume[2]); } else { glOrtho(-viewVolume[0]/2, viewVolume[0]/2, -viewVolume[1]/2, viewVolume[1]/2, nearPlane, nearPlane+viewVolume[2]); } // Change the view volume to maintain the aspect ratio wrt to viewport. glMatrixMode(GL_MODELVIEW); } void GlutObjectList::adjustView() { GLfloat ratio; ratio=viewVolume[0]/viewVolume[1]; if (eAdjustView==ADV_KEEPSHAPE) { if (windowWidth/windowHeight>ratio) { //too wider viewPortWidth= viewPortHeight*ratio; } else { //become thinner viewPortHeight=viewPortWidth/ratio; } } else { if (bPerspective) { perspectAngle-=eAdjustView; setDisplayMode(); } else { viewPortHeight+=eAdjustView; viewPortWidth+=eAdjustView*ratio; glViewport((windowWidth-viewPortWidth)/2, (windowHeight-viewPortHeight)/2, (windowWidth+viewPortWidth)/2, (windowHeight+viewPortHeight)/2); //glViewport(0, 0, viewPortWidth, viewPortHeight); } eAdjustView=ADV_KEEPSHAPE; } } void GlutObjectList::reshapeCallback(int w, int h) { windowWidth=w; windowHeight=h; adjustView(); glViewport((windowWidth-viewPortWidth)/2, (windowHeight-viewPortHeight)/2, (windowWidth+viewPortWidth)/2, (windowHeight+viewPortHeight)/2); //glViewport(0, 0, viewPortWidth, viewPortHeight); adjustView(); setDisplayMode(); glMatrixMode(GL_MODELVIEW); glutPostRedisplay(); } void GlutObjectList::drawAll() { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0, 0, -50); /* if (bDrawAxis) { axis.draw(); } */ glTranslatef(0, 0, 50); for (int i=0; i<objectCount; i++) { objectList[i]->draw(); } glFlush(); } void GlutObjectList::run() { glutInitWindowSize(windowWidth, windowHeight); glutInitWindowPosition(windowPos_x, windowPos_y); glutCreateWindow("A primitive model of a bike ! ~RG"); glutDisplayFunc(::displayCallback); glutReshapeFunc(::reshapeCallback); glutKeyboardFunc(::keyboardCallback); glutMainLoop(); } //NICKSPACE_END
file name: glutObjects.h
#ifndef GLUTOBJECTS_H #define GLUTOBJECTS_H #include "glutObject.h" //NICKSPACE_BEGIN const GLfloat DefaultTopDownRatio=0.5; class GlutObjectSphere : public GlutObject { public: virtual void display(); }; class GlutObjectCube: public GlutObject { public: virtual void display(); }; class GlutObjectHalfSphere: public GlutObject { public: GlutObjectHalfSphere(); virtual void display(); protected: //GLdouble clipPlane[4]; }; class GlutObject3DAxis: public GlutObject { public: GlutObject3DAxis(); virtual void display(); }; class GlutObjectCylinder: public GlutObject { public: GlutObjectCylinder(); virtual void display(); void setRatio(GLfloat theRatio){ratio=theRatio;} protected: GLfloat ratio; }; class GlutObjectCone: public GlutObject { public: GlutObjectCone(); virtual void display(); protected: GLfloat ratio; }; //NICKSPACE_END #endif
file name: glutObjects.cpp
#include "glutObjects.h" #include <GL/glut.h> const GLfloat DefaultAxisLength=DefaultWindowWidth/2; const GLfloat ArrowLength=4; GlutObject3DAxis axis; //NICKSPACE_BEGIN GLdouble xy[4] = {0.0, 0.0, 1.0, 0.0}; GLdouble yz[4] = {1.0, 0.0, 0.0, 0.0}; GLdouble zx[4] = {0.0, 1.0, 0.0, 0.0}; void GlutObjectCube::display() { if (bDrawingMesh) { glutWireCube(1); } else { glutSolidCube(1); } } void GlutObjectSphere::display() { if (bDrawingMesh) { glutWireSphere(1, (int)volume[0]/2, (int)volume[1]/2); } else { glutSolidSphere(1, (int)volume[0]/2, (int)volume[1]/2); } } GlutObjectHalfSphere::GlutObjectHalfSphere() { bDisableClip=false; } void GlutObjectHalfSphere::display() { //assume clipplane is xy parallel /* clipPlane[0]=clipPlane[2]=0; clipPlane[1]=1; clipPlane[3]=-position[1]+20.3; */ //glClipPlane (GL_CLIP_PLANE0, clipPlane); //glEnable (GL_CLIP_PLANE0); //glTranslatef(position[0], position[1]-volume[1]/2.0, position[2]); if (bDrawingMesh) { glutWireSphere(1, volume[0]/2, volume[1]/2); } else { glutSolidSphere(1, volume[0]/2, volume[1]/2); } //glDisable(GL_CLIP_PLANE0); } GlutObject3DAxis::GlutObject3DAxis() { position[0]=position[1]=0; position[2]=-50; color[0]=color[1]=0; color[2]=1; } void GlutObject3DAxis::display() { glPushMatrix(); glColor3f(color[0], color[1], color[2]); //glMatrixMode(GL_MODELVIEW); //glLoadIdentity(); //glRotatef(30, 1,0, 0); //glRotatef(-45, 0,1, 0); //glRotatef(60, 0, 0, 1); glBegin(GL_LINES); //X axis glVertex3fv(position); glVertex3f(position[0]+DefaultAxisLength, position[1], position[2]); //Y axis glVertex3fv(position); glVertex3f(position[0], position[1]+DefaultAxisLength, position[2]); //Z axis glVertex3fv(position); glVertex3f(position[0], position[1], position[2]+DefaultAxisLength); glEnd(); //draw three solid cone as arrows of axis //glLoadIdentity(); //save current TM matrix //draw cone for X axis glPushMatrix(); glTranslatef(position[0]+DefaultAxisLength-ArrowLength, position[1], position[2]); glRotatef(90, 0, 1, 0); //glTranslatef(x/2-ArrowLength, 0.0, 0.0); //glTranslatef(100, 0.0, 0.0); glutSolidCone(ArrowLength, ArrowLength*4, 10, 10); //glTranslatef(x/2+ArrowLength, ArrowLength, 0); //glutBitmapCharacter(GLUT_BITMAP_9_BY_15, 'X'); //glLoadIdentity(); glPopMatrix(); glPushMatrix(); //glRotatef(-90, 0, 1, 0); //glTranslatef(-(x/2-ArrowLength), 0.0, 0.0); glTranslatef(position[0], position[1]+DefaultAxisLength-ArrowLength, position[2]); glRotatef(-90, 1, 0, 0); glutSolidCone(ArrowLength, ArrowLength*4, 10, 10); glPopMatrix(); //glLoadIdentity(); glTranslatef(position[0], position[1], position[2]+DefaultAxisLength-ArrowLength); //glRotatef(-90, 1, 0, 0); glutSolidCone(ArrowLength, ArrowLength*4, 10, 10); glPopMatrix(); } GlutObjectCylinder::GlutObjectCylinder() { ratio=DefaultTopDownRatio; } void GlutObjectCylinder::display() { GLUquadricObj* pObj; pObj = gluNewQuadric(); // Creates a new quadrics object and returns a pointer to it. glTranslatef(position[0], position[1], -position[2]/2); if (bDrawingMesh) { gluQuadricDrawStyle(pObj, GLU_LINE); } else { gluQuadricDrawStyle(pObj, GLU_FILL); } //gluCylinder(pObj, volume[0]*ratio, volume[0], volume[2], volume[2]/2, volume[0]/2); //gluCylinder(pObj, (GLdouble)volume[0], (GLdouble)volume[0], (GLdouble)volume[2], // (int)volume[2], (int)volume[0]); gluCylinder(pObj, 0.8, 0.8, 0.5, 20, 20); //gluSphere(pObj, volume[0], volume[0]/2, volume[0]/2); // Draw the cylinder with a radius : fRadius. gluDeleteQuadric(pObj); } GlutObjectCone::GlutObjectCone() { ratio=DefaultTopDownRatio; } void GlutObjectCone::display() { //GLUquadricObj* pObj; //pObj = gluNewQuadric(); // Creates a new quadrics object and returns a pointer to it. //glTranslatef(position[0], position[1], -position[2]/2); if (bDrawingMesh) { //gluQuadricDrawStyle(pObj, GLU_LINE); glutWireCone(volume[0], volume[2], volume[0], volume[2]); } else { //gluQuadricDrawStyle(pObj, GLU_FILL); glutSolidCone(volume[0], volume[2], volume[0], volume[2]); } //gluCylinder(pObj, volume[0]*ratio, volume[0], volume[2], volume[2]/2, volume[0]/2); //gluCylinder(pObj, (GLdouble)volume[0], (GLdouble)volume[0], (GLdouble)volume[2], // (int)volume[2], (int)volume[0]); //gluCylinder(pObj, 0.8, 0.8, 0.5, 20, 20); //gluPartialDisk(pObj, volume[0]*ratio, volume[0], volume[0], volume[1], 0, 90); //gluSphere(pObj, volume[0], volume[0]/2, volume[0]/2); // Draw the cylinder with a radius : fRadius. //gluDeleteQuadric(pObj); } //NICKSPACE_END
file name: main.cpp
#include <GL/glut.h> #include "glutObject.h" #include "glutobjects.h" #include <stdio.h> //#include "glutObject.h" //using namespace NICKSPACE; extern void displayCallback(); int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); GlutObjectSphere sphere; GlutObjectCube cube; sphere.setPosition(100, 0, -240); sphere.setVolume(50, 50, 50); cube.setPosition(100, 0, -280); cube.setColor(0,1,0); cube.setVolume(50, 50, 50); cube.setDirection(45, 45,0); GlutObject3DAxis axis; GlutObjectHalfSphere halfSphere; halfSphere.setPosition(0, 20, -240); halfSphere.setVolume(50, 50, 50); halfSphere.setDirection(0,0,30); halfSphere.setClipRatio(0.7); /* GlutObjectCylinder cylinder; cylinder.setColor(0.5, 0.5, 0.0); //cylinder.setDirection(0, 60, 0); cylinder.setVolume(9, 9, 14); cylinder.setPosition(0, 0, -60); */ GlutObjectCone cone; cone.setColor(0, 1, 0); cone.setPosition(0, 10, -60); cone.setVolume(2, 2, 1); run(); return 0; }