Example 10

Example 10

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.

ex_10/doublecam.png

Source Code

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

Files