This OpenGL program displays a teapot in the middle of a plane and provides two cameras that move using the arcball concept. The outer camera (default) moves around the whole scene, while the inner camera moves a virtual camera inside the scene. The view of this inner camera is shown in the top-right. Pressing 'c' switches controls from the outer camera to the inner one (and vice versa). Pressing 's' will display the sphere the inner camera rotates on and also displays a red dot indicating the the location of the camera. To rotate left/right/up/down drag the left mouse button. To zoom, drag the right button.
doublecam.cpp:
// Alexandri Zavodny // CSE 40166: Computer Graphics, Fall 2010 // Example: Arcball Camera around a teapot #include <math.h> #include <stdio.h> #include <stdlib.h> #include <vector> #ifdef __APPLE__ #include <GLUT/glut.h> #else #include <GL/glut.h> #endif #include "point.h" using namespace std; // GLOBAL VARIABLES //////////////////////////////////////////////////////////// static size_t windowWidth = 640; static size_t windowHeight = 480; static float aspectRatio; GLint leftMouseButton, rightMouseButton; //status of the mouse buttons int mouseX = 0, mouseY = 0; //last known X and Y of the mouse bool sphereOn = false; //show the camera radius sphere //note to students reading this code: // yes, I should really be more object-oriented with this code. // a lot of this would be simplified and better encapsulated inside // of a Camera class. don't let your code get this ugly! enum cameraList { CAMERA_INNER = 0, CAMERA_OUTER = 1 }; enum cameraList currentCamera = CAMERA_OUTER; vector<Point> cameraTPRs; //will contain the position of each //camera in spherical coordinates vector<Point> cameraXYZs; //will contain the position of each //camera in cartesian coordinates // recomputeOrientation() ////////////////////////////////////////////////////// // // This function updates the camera's position in cartesian coordinates based // on its position in spherical coordinates. Should be called every time // either camera's spherical coordinates are updated. // //////////////////////////////////////////////////////////////////////////////// void recomputeOrientation(Point &xyz, Point &tpr) { xyz.x = tpr.z * sinf(tpr.x)*sinf(tpr.y); xyz.z = tpr.z * -cosf(tpr.x)*sinf(tpr.y); xyz.y = tpr.z * -cosf(tpr.y); glutPostRedisplay(); } // resizeWindow() ////////////////////////////////////////////////////////////// // // GLUT callback for window resizing. Resets GL_PROJECTION matrix and viewport. // //////////////////////////////////////////////////////////////////////////////// void resizeWindow(int w, int h) { aspectRatio = w / (float)h; windowWidth = w; windowHeight = h; //update the viewport to fill the window glViewport(0, 0, w, h); //update the projection matrix with the new window properties glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0,aspectRatio,0.1,100000); glutPostRedisplay(); } // mouseCallback() ///////////////////////////////////////////////////////////// // // GLUT callback for mouse clicks. We save the state of the mouse button // when this is called so that we can check the status of the mouse // buttons inside the motion callback (whether they are up or down). // //////////////////////////////////////////////////////////////////////////////// void mouseCallback(int button, int state, int thisX, int thisY) { //update the left and right mouse button states, if applicable if(button == GLUT_LEFT_BUTTON) leftMouseButton = state; else if(button == GLUT_RIGHT_BUTTON) rightMouseButton = state; //and update the last seen X and Y coordinates of the mouse mouseX = thisX; mouseY = thisY; } // mouseMotion() /////////////////////////////////////////////////////////////// // // GLUT callback for mouse movement. We update the current camera's spherical // coordinates based on how much the user has moved the mouse in the // X or Y directions (in screen space) and whether they have held down // the left or right mouse buttons. If the user hasn't held down any // buttons, the function just updates the last seen mouse X and Y coords. // //////////////////////////////////////////////////////////////////////////////// void mouseMotion(int x, int y) { if(leftMouseButton == GLUT_DOWN) { Point *curTPR = &cameraTPRs.at(currentCamera); //just for conciseness below curTPR->x += (x - mouseX)*0.005; curTPR->y += (y - mouseY)*0.005; // make sure that phi stays within the range (0, M_PI) if(curTPR->y <= 0) curTPR->y = 0+0.001; if(curTPR->y >= M_PI) curTPR->y = M_PI-0.001; //update camera (x,y,z) based on (radius,theta,phi) recomputeOrientation(cameraXYZs.at(currentCamera), cameraTPRs.at(currentCamera)); } else if(rightMouseButton == GLUT_DOWN) { double totalChangeSq = (x - mouseX) + (y - mouseY); Point *curTPR = &cameraTPRs.at(currentCamera); //just for conciseness below curTPR->z += totalChangeSq*0.01; //limit the camera radius to some reasonable values so the user can't get lost if(curTPR->z < 2.0) curTPR->z = 2.0; if(curTPR->z > 10.0*(currentCamera+1)) curTPR->z = 10.0*(currentCamera+1); //update camera (x,y,z) based on (radius,theta,phi) recomputeOrientation(cameraXYZs.at(currentCamera), cameraTPRs.at(currentCamera)); } mouseX = x; mouseY = y; } // initScene() ///////////////////////////////////////////////////////////////// // // A basic scene initialization function; should be called once after the // OpenGL context has been created. Doesn't need to be called further. // //////////////////////////////////////////////////////////////////////////////// void initScene() { glEnable(GL_DEPTH_TEST); float lightCol[4] = { 1, 1, 1, 1}; float ambientCol[4] = {0.3, 0.3, 0.3, 1.0}; float lPosition[4] = { 10, 10, 10, 1 }; glLightfv(GL_LIGHT0,GL_POSITION,lPosition); glLightfv(GL_LIGHT0,GL_DIFFUSE,lightCol); glLightfv(GL_LIGHT0, GL_AMBIENT, ambientCol); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_POINT_SMOOTH); glShadeModel(GL_SMOOTH); //glShadeModel(GL_FLAT); glutPostRedisplay(); } // drawSceneElements() ///////////////////////////////////////////////////////// // // Because we'll be drawing the scene twice from different viewpoints, // we encapsulate the code to draw the scene here, so that we can just // call this function twice once the projection and modelview matrices // have been set appropriately. // //////////////////////////////////////////////////////////////////////////////// void drawSceneElements(void) { glDisable(GL_LIGHTING); //draw a simple grid under the teapot glColor3f(1,1,1); for(int dir = 0; dir < 2; dir++) { for(int i = -5; i < 6; i++) { glBegin(GL_LINE_STRIP); for(int j = -5; j < 6; j++) glVertex3f(dir<1?i:j, -0.73f, dir<1?j:i); glEnd(); } } //and then draw the teapot itself! glEnable(GL_LIGHTING); //see documentation for glutSolidTeapot; glutSolidTeapot must be called with //a different winding set. there is a known 'bug' that results in the //winding of the teapot to be backwards. glFrontFace(GL_CW); glutSolidTeapot(1.0f); glFrontFace(GL_CCW); } // drawInnerCamera() /////////////////////////////////////////////////////////// // // Draws a representation of the inner camera in space. This should only be // called when rendering the scene from the POV of the outer camera, // so that we can visualize where the inner camera is positioned // and what it is looking at. // //////////////////////////////////////////////////////////////////////////////// void drawInnerCamera() { Point icpos = cameraXYZs.at(CAMERA_INNER); glPushAttrib(GL_LIGHTING_BIT); glDisable(GL_LIGHTING); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glTranslatef(icpos.x, icpos.y, icpos.z); glRotatef(-cameraTPRs.at(CAMERA_INNER).x*180.0/M_PI, 0, 1, 0); glRotatef(cameraTPRs.at(CAMERA_INNER).y*180.0/M_PI, 1, 0, 0); glScalef(1,2,0.75); glColor3f(0,1,0); glutWireCube(1.0f); //draw the reels on top of the camera... for(int currentReel = 0; currentReel < 2; currentReel++) { float radius = 0.25f; int resolution = 32; Point reelCenter = Point(0, -0.25 + (currentReel==0?0:0.5), -0.5); glBegin(GL_LINES); Point s = reelCenter - Point(0,0.25,0); glVertex3f(s.x, s.y, s.z); for(int i = 0; i < resolution; i++) { float ex = -cosf( i / (float)resolution * M_PI); float why = sinf( i / (float)resolution * M_PI); Point p = Point(0, ex*radius, -why*radius*3) + reelCenter; glVertex3f(p.x, p.y, p.z); //end of this line... glVertex3f(p.x, p.y, p.z); //and start of the next } Point f = reelCenter + Point(0,0.25,0); glVertex3f(f.x, f.y, f.z); glEnd(); } //and just draw the lens shield manually because //i don't want to think about shear matrices. //clockwise looking from behind the camera: float lensOff = 0.3f; float lensOut = 0.2f; Point v0 = Point( 0.5, 0.5, -0.5); Point v1 = Point(-0.5, 0.5, -0.5); Point v2 = Point(-0.5, 0.5, 0.5); Point v3 = Point( 0.5, 0.5, 0.5); Point l0 = v0 + Point( lensOut,0,0) + Point(0,lensOut,0) + Point(0,0,-lensOff); Point l1 = v1 + Point(-lensOut,0,0) + Point(0,lensOut,0) + Point(0,0,-lensOff); Point l2 = v2 + Point(-lensOut,0,0) + Point(0,lensOut,0) + Point(0,0,lensOff); Point l3 = v3 + Point( lensOut,0,0) + Point(0,lensOut,0) + Point(0,0,lensOff); glBegin(GL_LINE_STRIP); l0.glVertex(); l1.glVertex(); l2.glVertex(); l3.glVertex(); l0.glVertex(); glEnd(); //and connect the two glBegin(GL_LINES); v0.glVertex(); l0.glVertex(); v1.glVertex(); l1.glVertex(); v2.glVertex(); l2.glVertex(); v3.glVertex(); l3.glVertex(); glEnd(); if(sphereOn) { //draw a point at the center of the camera glColor3f(1,0,0); glPointSize(10); glBegin(GL_POINTS); Point(0,0,0).glVertex(); glEnd(); glPopMatrix(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glColor4f(1,1,1,0.3); glutSolidSphere(cameraTPRs.at(CAMERA_INNER).z, 32, 32); glDisable(GL_CULL_FACE); glDisable(GL_BLEND); } else { glPopMatrix(); } /* //draw a wireframe box around where the camera is located Point center = cameraXYZs.at(CAMERA_INNER); Point up = Point(0,1,0); Point dir = -1.0f * center; dir.normalize(); Point right = cross(dir, up); right.normalize(); Point camUp = cross(dir, -1.0f * right); camUp.normalize(); //so the 8 corners of the box are as follows... //clockwise around the top, starting from front-right: Point v0 = center + dir*0.7 + camUp*0.4 + right*0.4; Point v1 = center - dir*0.7 + camUp*0.4 + right*0.4; Point v2 = center - dir*0.7 + camUp*0.4 - right*0.4; Point v3 = center + dir*0.7 + camUp*0.4 - right*0.4; //aaaand the lower four: Point v4 = center + dir*0.7 - camUp*0.4 + right*0.4; Point v5 = center - dir*0.7 - camUp*0.4 + right*0.4; Point v6 = center - dir*0.7 - camUp*0.4 - right*0.4; Point v7 = center + dir*0.7 - camUp*0.4 - right*0.4; //and the points on the shield-y unit out front.. float fOff = 0.2; //heh heh. float fOut = 0.4; Point f0 = v0 + dir*fOut + camUp*fOff + right*fOff; Point f1 = v4 + dir*fOut - camUp*fOff + right*fOff; Point f2 = v7 + dir*fOut - camUp*fOff - right*fOff; Point f3 = v3 + dir*fOut + camUp*fOff - right*fOff; glColor3f(0,1,0); glBegin(GL_LINE_STRIP); glVertex3f(v0.x, v0.y, v0.z); glVertex3f(v1.x, v1.y, v1.z); glVertex3f(v2.x, v2.y, v2.z); glVertex3f(v3.x, v3.y, v3.z); glVertex3f(v0.x, v0.y, v0.z); glVertex3f(v4.x, v4.y, v4.z); glVertex3f(v5.x, v5.y, v5.z); glVertex3f(v6.x, v6.y, v6.z); glVertex3f(v7.x, v7.y, v7.z); glVertex3f(v4.x, v4.y, v4.z); glEnd(); //just a couple of lines that got missed here... glBegin(GL_LINES); glVertex3f(v1.x, v1.y, v1.z); glVertex3f(v5.x, v5.y, v5.z); glVertex3f(v2.x, v2.y, v2.z); glVertex3f(v6.x, v6.y, v6.z); glVertex3f(v3.x, v3.y, v3.z); glVertex3f(v7.x, v7.y, v7.z); glEnd(); glBegin(GL_LINE_STRIP); glVertex3f(v0.x, v0.y, v0.z); glVertex3f(f0.x, f0.y, f0.z); glVertex3f(f1.x, f1.y, f1.z); glVertex3f(f2.x, f2.y, f2.z); glVertex3f(f3.x, f3.y, f3.z); glVertex3f(v3.x, v3.y, v3.z); glEnd(); //just a couple of lines that got missed here... glBegin(GL_LINES); glVertex3f(f0.x, f0.y, f0.z); glVertex3f(f3.x, f3.y, f3.z); glVertex3f(v4.x, v4.y, v4.z); glVertex3f(f1.x, f1.y, f1.z); glVertex3f(v7.x, v7.y, v7.z); glVertex3f(f2.x, f2.y, f2.z); glEnd(); //and then draw the cutesy camera reels up on top... // // ___ ___ // / \ / \ // _\___/_\___/ /| // | | | // |___________| | // \| // for(int currentReel = 0; currentReel < 2; currentReel++) { float radius = 0.7f; int resolution = 32; Point reelCenter = center + (currentReel==0?-1:1)*dir*0.35 + camUp*0.4; glBegin(GL_LINES); Point s = reelCenter - dir*radius*0.5f; glVertex3f(s.x, s.y, s.z); for(int i = 0; i < resolution; i++) { float ex = -cosf( i / (float)resolution * M_PI); float why = sinf( i / (float)resolution * M_PI); Point p = reelCenter + ex*dir*radius*0.5f + why*camUp*radius*0.8f; glVertex3f(p.x, p.y, p.z); //end of this line... glVertex3f(p.x, p.y, p.z); //and start of the next } Point f = reelCenter + dir*radius*0.5f; glVertex3f(f.x, f.y, f.z); glEnd(); } */ glPopAttrib(); } // renderCallback() //////////////////////////////////////////////////////////// // // GLUT callback for scene rendering. Sets up the modelview matrix, renders // a teapot to the back buffer, and switches the back buffer with the // front buffer (what the user sees). // //////////////////////////////////////////////////////////////////////////////// void renderCallback(void) { Point outerXYZ = cameraXYZs.at(CAMERA_OUTER); Point innerXYZ = cameraXYZs.at(CAMERA_INNER); //clear the render buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); float borderWidth = 3; //start with the code from the outer camera, which covers the whole screen! glViewport(0, 0, windowWidth, windowHeight); glDisable(GL_LIGHTING); glDisable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0,1,0,1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if(currentCamera == CAMERA_OUTER) glColor3f(1,0,0); else glColor3f(1,1,1); glBegin(GL_QUADS); glVertex2f(0,0); glVertex2f(0,1); glVertex2f(1,1); glVertex2f(1,0); glEnd(); glViewport(borderWidth, borderWidth, windowWidth-borderWidth*2, windowHeight-borderWidth*2); glColor3f(0,0,0); glBegin(GL_QUADS); glVertex2f(0,0); glVertex2f(0,1); glVertex2f(1,1); glVertex2f(1,0); glEnd(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glEnable(GL_LIGHTING); glEnable(GL_DEPTH_TEST); //update the modelview matrix based on the camera's position glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(outerXYZ.x, outerXYZ.y, outerXYZ.z, 0, 0, 0, 0, 1, 0); drawSceneElements(); drawInnerCamera(); /// draw border and background for preview box in upper corner ////////////////////// //next, do the code for the inner camera, which only sets in the top-right //corner! glDisable(GL_LIGHTING); glDisable(GL_DEPTH_TEST); //step 1: set the projection matrix using gluOrtho2D -- but save it first! glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0,1,0,1); //step 2: clear the modelview matrix glMatrixMode(GL_MODELVIEW); glLoadIdentity(); //step 3: set the viewport matrix a little larger than needed... glViewport(2*windowWidth/3.0-borderWidth, 2*windowHeight/3.0-borderWidth, windowWidth/3.0+borderWidth, windowHeight/3.0+borderWidth); //step 3a: and fill it with a white rectangle! if(currentCamera == CAMERA_OUTER) glColor3f(1,1,1); else glColor3f(1,0,0); glBegin(GL_QUADS); glVertex2f(0,0); glVertex2f(0,1); glVertex2f(1,1); glVertex2f(1,0); glEnd(); //step 4: trim the viewport window to the size we want it... glViewport(2*windowWidth/3.0, 2*windowHeight/3.0, windowWidth/3.0, windowHeight/3.0); //step 4a: and color it black! the padding we gave it before is now a border. glColor3f(0,0,0); glBegin(GL_QUADS); glVertex2f(0,0); glVertex2f(0,1); glVertex2f(1,1); glVertex2f(1,0); glEnd(); //before rendering the scene in the corner, pop the old projection matrix back //and re-enable lighting! glMatrixMode(GL_PROJECTION); glPopMatrix(); glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); /// begin drawing scene in upper corner ////////////////////////////////////////////// glViewport(2*windowWidth/3.0, 2*windowHeight/3.0, windowWidth/3.0, windowHeight/3.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(innerXYZ.x, innerXYZ.y, innerXYZ.z, //camera is located at (x,y,z) 0, 0, 0, //camera is looking at (0,0,0) 0.0f,1.0f,0.0f); //up vector is (0,1,0) (positive Y) glClear(GL_DEPTH_BUFFER_BIT); //ensure that the overlay is always on top! drawSceneElements(); //push the back buffer to the screen glutSwapBuffers(); } // normalKeys() //////////////////////////////////////////////////////////////// // // GLUT keyboard callback. // //////////////////////////////////////////////////////////////////////////////// void normalKeys(unsigned char key, int x, int y) { if(key == 'q' || key == 'Q') exit(0); if(key == 'c' || key == 'C') { if(currentCamera == CAMERA_INNER) currentCamera = CAMERA_OUTER; else currentCamera = CAMERA_INNER; glutPostRedisplay(); } if(key == 's' || key == 'S') { sphereOn = !sphereOn; glutPostRedisplay(); } } // main() ////////////////////////////////////////////////////////////////////// // // Program entry point. Does not process command line arguments. // //////////////////////////////////////////////////////////////////////////////// int main(int argc, char **argv) { //create a double-buffered GLUT window at (50,50) with predefined windowsize glutInit(&argc, argv); glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA); glutInitWindowPosition(50,50); glutInitWindowSize(windowWidth,windowHeight); glutCreateWindow("double cameras... woahhhhh double cameras"); //give the camera a 'pretty' starting point! cameraTPRs.push_back( Point(2.80, 2.0, 7.0) ); cameraXYZs.push_back( Point(0,0,0) ); recomputeOrientation(cameraXYZs.at(0), cameraTPRs.at(0)); cameraTPRs.push_back( Point(1.50, 2.0, 14.0) ); cameraXYZs.push_back( Point(0,0,0) ); recomputeOrientation(cameraXYZs.at(1), cameraTPRs.at(1)); //register callback functions... glutKeyboardFunc(normalKeys); glutDisplayFunc(renderCallback); glutReshapeFunc(resizeWindow); glutMouseFunc(mouseCallback); glutMotionFunc(mouseMotion); //do some basic OpenGL setup initScene(); //and enter the GLUT loop, never to exit. glutMainLoop(); return(0); }