// sample problem discussed at the lecture 10/21/2004 #define GLUT_API_VERSION 4 #include #ifdef _WIN32 #include #endif #include #include #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #include "cvec2t.h" #include "cvec3t.h" #include "cvec4t.h" #include "hmatrix.h" typedef CVec2T Vec2f; typedef CVec3T Vec3f; typedef CVec4T Vec4f; typedef void (*DisplayFuncType)(void); void Draw00(void); void Draw01(void); void Draw10(void); void Draw11(void); namespace WindowParams { static int WindowWidth = 800; static int WindowHeight = 600; static int MainWindow; static int SubWindow[2][2]; DisplayFuncType DrawFunc[2][2] = {{Draw00,Draw01},{Draw10,Draw11}}; static int SubWindowLL; static int SubWindowUR; }; namespace Params { const float WorldWidth = 10*1.33; const float WorldHeight = 10; const Vec3f BackgroundColor(0,0,0); const float OrthoCameraOffset = 10; const float OrthoCameraWidth = 20*1.33; const float OrthoCameraHeight = 20; const int TimerStep = 16; // millisec }; enum ViewingModeType {NAVIGATOR, TRACKBALL}; ViewingModeType ViewingMode = NAVIGATOR; const Vec3f red(1,0,0); const Vec3f blue(0,0,1); const Vec3f green(0,1,0); const Vec3f white(1,1,1); class Camera { public: Camera(): Position(10,10,10),ViewCenter(0,0,0),Up(0,1,0), FieldOfView(40),AspectRatio(1.33),Near(1),Far(20),moving(false) {} // glLookAt arguments Vec3f Position; Vec3f ViewCenter; Vec3f Up; // gluPerspective arguments float FieldOfView; float AspectRatio; float Near; float Far; bool moving; // drawing the Frustum outline; this assumes some other camera // is used for viewing //if the same camera is used, only // a border around the window is visible void drawFrustum() { GLfloat modelview[16]; // use opengl to compute the modelview matrix corresponding to camera parameters // this matrix positions the world w.r.t. camera, so to position the camera and frustumregarded as // an object for drawing using a different camera we use the inverse transform glPushMatrix(); glLoadIdentity(); gluLookAt( Position.x(), Position.y(),Position.z(), ViewCenter.x(),ViewCenter.y(),ViewCenter.z(), Up.x(),Up.y(),Up.z()); // retrieve the result glGetFloatv(GL_MODELVIEW_MATRIX,modelview); // discard the top of the stack glPopMatrix(); glPushMatrix(); // invert the matrix that we have retrieved HMatrix M(modelview); M=M.inverse(); // use it as the modelview matrix for drawing // it needs to be multiplied by the current matrix which represents the position of // the different camera we use glMultMatrixf(M); // 8 corners of the frustum double fovtan = tan(FieldOfView*M_PI/180.0/2.0); double a = AspectRatio; Vec3f lln(-a*fovtan*Near,-fovtan*Near, -Near); Vec3f rln( a*fovtan*Near,-fovtan*Near, -Near); Vec3f run( a*fovtan*Near, fovtan*Near, -Near); Vec3f lun(-a*fovtan*Near, fovtan*Near, -Near); Vec3f llf(-a*fovtan*Far,-fovtan*Far, -Far); Vec3f rlf( a*fovtan*Far,-fovtan*Far, -Far); Vec3f ruf( a*fovtan*Far, fovtan*Far, -Far); Vec3f luf(-a*fovtan*Far, fovtan*Far, -Far); // rectangle in the near plane glColor3fv(white); glBegin(GL_LINE_LOOP); glVertex3fv(lln); glVertex3fv(rln); glVertex3fv(run); glVertex3fv(lun); glEnd(); // rectangle in the far plane glBegin(GL_LINE_LOOP); glVertex3fv(llf); glVertex3fv(rlf); glVertex3fv(ruf); glVertex3fv(luf); glEnd(); // connecting lines between near and far glColor3fv(0.75*white); glBegin(GL_LINES); glVertex3fv(lln); glVertex3fv(llf); glVertex3fv(rln); glVertex3fv(rlf); glVertex3fv(run); glVertex3fv(ruf); glVertex3fv(lun); glVertex3fv(luf); glEnd(); glPopMatrix(); } }; Camera* perspCamera; Vec3f SphereCenter(0,0,0); float SphereRadius = 5; float ExaminerRotAngle = 0; Vec3f ExaminerRotAxis(0,1,0); HMatrix ExaminerRotation; void Reshape(int width, int height) { WindowParams::WindowWidth = width; WindowParams::WindowHeight = height; // make sure the aspect ratio of the camera in gluPerspective/glFrustum matches the window // aspect ratio perspCamera->AspectRatio = width/float(height); // adjust positions, shapes and viewports of subwindows; the four subwindows // split the main window into four (approx. up to a pixel) equal subwindows for(int i = 0; i < 2; i++) for(int j = 0; j < 2; j++) { glutSetWindow(WindowParams::SubWindow[i][j]); glutPositionWindow(i*width/2,j*height/2); glutReshapeWindow(width/2,height/2); glViewport(0,0,width/2,height/2); } } // post redisplay for all windows void PostRedisplayAll() { for(int i = 0; i < 2; i++) for(int j = 0; j < 2; j++) { glutSetWindow(WindowParams::SubWindow[i][j]); glutPostRedisplay(); } } // red = X axis, green = Y, blue = Z void drawAxes() { glLineWidth(3.0); float d = min(Params::WorldWidth,Params::WorldHeight); glBegin(GL_LINES); glColor3fv(red); glVertex3f(0,0,0); glVertex3f(d,0,0); glColor3fv(green); glVertex3f(0,0,0); glVertex3f(0,d,0); glColor3fv(blue); glVertex3f(0,0,0); glVertex3f(0,0,d); glEnd(); } void DrawPlanes() { glColor3f(0,1,1); glBegin(GL_POLYGON); glNormal3f(1,0,0); glVertex3f(0,0,0); glVertex3f(0,1,0); glVertex3f(0,1,1); glVertex3f(0,0,1); glEnd(); glColor3f(1,0,1); glBegin(GL_POLYGON); glNormal3f(0,1,0); glVertex3f(0,0,0); glVertex3f(0,0,1); glVertex3f(1,0,1); glVertex3f(1,0,0); glEnd(); glColor3f(1,1,0); glBegin(GL_POLYGON); glNormal3f(0,0,1); glVertex3f(0,0,0); glVertex3f(1,0,0); glVertex3f(1,1,0); glVertex3f(0,1,0); glEnd(); } // projection matrix setup for three axis aligned orthographics cameras void setOrthoCameraParams() { glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho( -Params::OrthoCameraWidth/2,Params::OrthoCameraWidth/2, //left, right -Params::OrthoCameraHeight/2,Params::OrthoCameraHeight/2, // bottom, top 1,Params::OrthoCameraOffset*5); // near, far } // all drawing except initializing the buffer and camera setup (projection matrix + lookat) void Draw() { glEnable(GL_DEPTH_TEST); // enable automatic rescaling of normals to unit length glEnable(GL_NORMALIZE); // enable two lights glEnable(GL_LIGHT0); glEnable(GL_LIGHT1); // directional lights (w=0) along z axis glLightfv(GL_LIGHT0,GL_DIFFUSE,Vec4f(1, 1, 1,1)); glLightfv(GL_LIGHT0,GL_POSITION, Vec4f(0, 0, 1,0)); glLightfv(GL_LIGHT1,GL_DIFFUSE,Vec4f(1, 1, 1,1)); glLightfv(GL_LIGHT1,GL_POSITION, Vec4f(0, 0, -1,0)); // flip normals for polygons facing away from the screen // this ensures the back facing polygins are lit glLightModelf(GL_LIGHT_MODEL_TWO_SIDE,1); glEnable(GL_LIGHTING); glPushMatrix(); glMultMatrixf(ExaminerRotation); if( ViewingMode == TRACKBALL ) { glutWireSphere(5,10,10); glutSolidTeapot(1.0); } else if (ViewingMode == NAVIGATOR) { glPushMatrix(); glTranslatef(1,1,1); glutSolidTeapot(1.0); glPopMatrix(); } glDisable(GL_LIGHTING); if( ViewingMode == NAVIGATOR ) { glPushMatrix(); glScalef(20,20,20); DrawPlanes(); glPopMatrix(); } // axes are drawn without lighting drawAxes(); glPopMatrix(); perspCamera->drawFrustum(); // draw camera glColor3fv(white); glPointSize(3.0); glBegin(GL_POINTS); glVertex3fv(perspCamera->Position); glEnd(); glBegin(GL_LINES); glVertex3fv(perspCamera->Position); glVertex3fv(perspCamera->ViewCenter); glEnd(); // glutSwapBuffers(); } // callbacks for 4 subwindows // Drawij differ only in the camera position, and background color void Draw00() { glClearColor( 0.1, 0.1,0.1,0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); setOrthoCameraParams(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( Params::OrthoCameraOffset,0,0, // camera position (on x-axis) 0,0,0, // the point the camera is looking at 0,1,0 // up direction ); Draw(); // drawFrustum(-ar*n*tan(fov_angle/180.0*M_PI),ar*n*tan(fov_angle/180.0*M_PI),-n*tan(fov_angle/180.0*M_PI),n*tan(fov_angle/180.0*M_PI),n,f); glutSwapBuffers(); } void Draw01() { glClearColor( 0.2, 0.2,0.2,0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); setOrthoCameraParams(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( 0,Params::OrthoCameraOffset,0, 0,0,0, 0,0,-1); Draw(); glutSwapBuffers(); } void Draw10() { glClearColor( 0.4, 0.4,0.4,0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); setOrthoCameraParams(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( 0,0,Params::OrthoCameraOffset, 0,0,0, 0,1,0); Draw(); glutSwapBuffers(); } void Draw11() { glClearColor( 0.6, 0.6,0.6,0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); float fov_angle = perspCamera->FieldOfView; float ar = perspCamera->AspectRatio; float n = perspCamera->Near; float f = perspCamera->Far; // this gluPerspective call is equivalent to glFrustum call below /* gluPerspective(fov_angle, // field of view in degrees ar, // aspect ratio n, // near distance f); // far distance */ glFrustum( -ar*n*tan(fov_angle/180.0*M_PI/2),// left ar*n*tan(fov_angle/180.0*M_PI/2), // right -n*tan(fov_angle/180.0*M_PI/2), // bottom n*tan(fov_angle/180.0*M_PI/2), // top n,f ); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( perspCamera->Position.x(), perspCamera->Position.y(),perspCamera->Position.z(), perspCamera->ViewCenter.x(),perspCamera->ViewCenter.y(),perspCamera->ViewCenter.z(), perspCamera->Up.x(),perspCamera->Up.y(),perspCamera->Up.z()); Draw(); glutSwapBuffers(); } // dummy callback for the main window void DrawMain() { } Vec3f ScreenToWorld(int windowid, int x, int y) { glutSetWindow(windowid); GLdouble modelview[16]; GLdouble projection[16]; GLint viewport[4]; double world_x, world_y, world_z; // get current modelview, projection and viewport transforms glGetDoublev(GL_MODELVIEW_MATRIX,modelview); glGetDoublev(GL_PROJECTION_MATRIX,projection); glGetIntegerv(GL_VIEWPORT,viewport); // this function computes inverse of VPM and applies it to (x,y,0) to convert from pixel to world coords // this computes the world coordinates of the point on the near plane of the frustum which corresponds to pixel (x,y) gluUnProject(x,y,0,modelview,projection,viewport, &world_x,&world_y,&world_z); return Vec3f(world_x,world_y,world_z); } bool SpherePoint(const Vec3f& center, float r, const Vec3f& pscreen, Vec3f& psphere) { Vec3f v = (pscreen- perspCamera->Position).dir(); Vec3f d = perspCamera->Position-center; float ddotv = d.dot(v); float D = ddotv*ddotv-d.dot() +r*r; if (D < 0) return false; float t = -ddotv-sqrt(D); psphere = perspCamera->Position+v*t; return true; } // a mouse button is pressed or released Vec3f CurrentPsphere; Vec3f NewPsphere; void MouseClick (int button, int state, int x, int y) { y = WindowParams::WindowHeight/2 - y-1; if(state == GLUT_DOWN) { if(ViewingMode == NAVIGATOR) { perspCamera->ViewCenter = ScreenToWorld(WindowParams::SubWindow[1][1],x,y); } else if (ViewingMode == TRACKBALL) { Vec3f psphere; if(SpherePoint(SphereCenter,SphereRadius,ScreenToWorld(WindowParams::SubWindow[1][1],x,y),psphere)) { CurrentPsphere = psphere; NewPsphere = psphere; } } PostRedisplayAll(); } if(state == GLUT_UP) { if(ViewingMode == TRACKBALL) CurrentPsphere = NewPsphere; } } void MouseMotion(int x, int y) { y = WindowParams::WindowHeight/2 - y-1; if(ViewingMode == TRACKBALL) { Vec3f psphere; if(SpherePoint(SphereCenter,SphereRadius,ScreenToWorld(WindowParams::SubWindow[1][1],x,y),psphere)) { ExaminerRotAxis = cross(CurrentPsphere-SphereCenter, psphere-SphereCenter); ExaminerRotAngle = acos((CurrentPsphere-SphereCenter).dot(psphere-SphereCenter)/SphereRadius/SphereRadius); ExaminerRotation = HMatrix::Rotation(ExaminerRotAngle,ExaminerRotAxis)*ExaminerRotation; CurrentPsphere = psphere; } } else if (ViewingMode == NAVIGATOR) { // perspCamera->ViewCenter = ScreenToWorld(WindowParams::SubWindow[1][1],x,y); } PostRedisplayAll(); } void MousePassiveMotion(int x,int y) { } void Keyboard(unsigned char key, int x, int y) { if(key == ' ') { // this creates camera with default parameters delete perspCamera; perspCamera = new Camera; } glutPostRedisplay(); if(key == 's') perspCamera->moving = !perspCamera->moving; if(key == 't') { perspCamera->moving = false; ViewingMode = TRACKBALL; } if(key == 'n') { perspCamera->moving = false; ViewingMode = NAVIGATOR; } } void Animate(int time) { if(perspCamera->moving) { perspCamera->Position += (perspCamera->ViewCenter-perspCamera->Position).dir()*0.06; perspCamera->ViewCenter += (perspCamera->ViewCenter-perspCamera->Position).dir()*0.06; } glutTimerFunc(Params::TimerStep,Animate,0); PostRedisplayAll(); } int main(int argc, char* argv[]) { // initialize glut and parse command-line aguments that glut understands glutInit(&argc, argv); // initialize dislay mode: 4 color components, double buffer and depth buffer glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH); glutInitWindowSize(WindowParams::WindowWidth,WindowParams::WindowHeight); WindowParams::MainWindow = glutCreateWindow("Cameras"); // create 4 windows and assign draw callbacks for(int i =0; i < 2; i++) for(int j=0; j < 2; j++) { WindowParams::SubWindow[i][j] = glutCreateSubWindow(WindowParams::MainWindow, i*WindowParams::WindowWidth/2,j*WindowParams::WindowHeight/2, WindowParams::WindowWidth/2,WindowParams::WindowHeight/2); glutSetWindow(WindowParams::SubWindow[i][j]); glutDisplayFunc(WindowParams::DrawFunc[i][j]); } glutSetWindow(WindowParams::SubWindow[1][1]); // gets called whenever a mouse button is pressed or released glutMouseFunc(MouseClick); // gets called when a key is pressed glutKeyboardFunc(Keyboard); glutMotionFunc(MouseMotion); glutPassiveMotionFunc(MousePassiveMotion); glutTimerFunc(Params::TimerStep,Animate,0); // register callbacks for main window glutSetWindow(WindowParams::MainWindow); // dummy callback, no part of the main window is ever visible glutDisplayFunc(DrawMain); // only the main window has reshape callback, sizes of all subwindows are adjusted there glutReshapeFunc(Reshape); perspCamera = new Camera; // this is an infinite loop get event - dispatch event which never returns glutMainLoop(); return 0; }