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);
}