Simple Camera Navigation
Overview
Camera
tgt::Camera objects model the eye of an observer in our 3d scenes. A camera has a position, a focus point and an up vector which define the place where the observer is located, a coordinate the observer looks at, and in which direction it’s upside is. All these coordinates/vectors are in world coordinates. This way, it is very easy to think which parameters to use to get a certain view on the 3d scene.
When calling Camera::look(), the view matrix will be calculated that will translate the world coordinates in according camera coordinates (so that the position of the camera gets the point of origin, the focus point lays on negative z-axis and the up vector is in direction of positive y-axis). This view matrix is then put to model view matrix stack.
Navigation
tgt offers several navigation metaphores which let the application easily navigate the camera through the 3d scene. E.g. the trackball, see next tutorial.
tgt::Navigation
In this tutorial we will have a look at the most simple navigation metaphore, tgt::Navigation, which is the base class of all the other, more complex metaphores.
It offers basic camera movements like moving the camera back and forth, moving or panning it left and right, up and down or rolling the camera to the left or right. It also offers methods for panning around or moving along certain axis.
See doxygen (TODO: link to doxygen) for details.
tgt::Nagivation is derived from tgt::EventListener for easy consturction of user interactive navigation metaphores.
Example
MyPainter
We render a more complex object than just a sphere this time to make camera movements visible. We define some helper methods, MyPainter::obscureObject(), MyPainter::rings(), MyPainter::ring(), which makes our MyPainter::paint() better structured.
Have a look at the screenshot, or alternatively run this sample program, and with the knowloedge of the preceding tutorials the code of MyPainter should become self-explanatory.
class MyPainter : public Painter {
public:
MyPainter(GLCanvas* canvas) : Painter(canvas) {};
virtual void paint();
virtual void init();
virtual void sizeChanged(const ivec2& size);
void obscureObject();
void rings();
void ring(ivec3 axis, float radius);
};
void MyPainter::paint() {
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
// set Camera
getCamera()->look();
obscureObject();
rings();
}
void MyPainter::init() {
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);
glEnable(GL_COLOR_MATERIAL);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
getCamera()->positionCamera(vec3(0.f,0.f,10.f), vec3(0.f,0.f,0.f), vec3(0.f,1.f,0.f));
}
void MyPainter::sizeChanged(const ivec2& size) {
glViewport(0,0,size.x,size.y);
getCamera()->setRatio((float) size.x/size.y);
}
void MyPainter::obscureObject() {
Sphere sp(1., 32, 16);
Quad qu(2., 2., 2.);
Cylinder cy(.5, .2, 5., 16, 3);
glColor3d(.8,.8,.8);
sp.render();
glColor3d(1.,0.,0.);
glPushMatrix();
glRotated(90.,1.,0.,0.);
cy.render();
glPopMatrix();
glColor3d(0.,1.,0.);
glPushMatrix();
glRotated(90.,0.,1.,0.);
cy.render();
glPopMatrix();
glColor3d(0.,0.,1.);
glPushMatrix();
glRotated(90.,0.,0.,1.);
cy.render();
glPopMatrix();
glColor3d(1.,1.,0.);
glPushMatrix();
glTranslated(-1., 1., -1.);
qu.render();
glPopMatrix();
}
void MyPainter::rings() {
ring(ivec3(1, 0, 0), 20.f);
ring(ivec3(0, 1, 0), 20.f);
ring(ivec3(0, 0, 1), 20.f);
}
void MyPainter::ring(ivec3 axis, float radius) {
// find a vector orthogonal to axis
vec3 trans = axis;
if (axis.x != 0) {
trans.x = (axis.y*trans.y+axis.z*trans.z) / axis.x;
if (trans.x == 0.f)
trans.y = 1.f;
} else if (axis.y != 0) {
trans.y = (axis.x*trans.x+axis.z*trans.z) / axis.y;
if (trans.y == 0.f)
trans.z = 1.f;
} else if (axis.z != 0) {
trans.z = (axis.x*trans.x+axis.y*trans.y) / axis.z;
if (trans.z == 0.f)
trans.x = 1.f;
} else return;
trans = radius * normalize(trans);
Sphere sp(.2, 16, 8);
glColor3d(1.,1.,1.);
for (int i = 0; i < 36; ++i) {
glPushMatrix();
glRotatef(i*10.f, axis.x, axis.y, axis.z);
glTranslatef(trans.x, trans.y, trans.z);
sp.render();
glPopMatrix();
}
}
MyNavigation
MyNavigation is derived from tgt::Navigation which itself is derived from tgt::EventListener. We override some event-methods to make our application react on user input. We add the property lastMouseCoord_ to be able to keep track of mouse movements.
We will use the events to call the camera navigation methods of tgt::Navigation:
- Dragging with left mouse button will pan.
- Mouse Wheel will roll by 22.5 degree.
- Arrow keys will move camera in the according direction.
- PgUp, PgDn will move camera forewards and backwards.
class MyNavigation : public Navigation {
private:
ivec2 lastMouseCoord_;
public:
MyNavigation(GLCanvas* canvas) : Navigation(canvas) {};
virtual void mousePressEvent(MouseEvent* e);
virtual void mouseMoveEvent(MouseEvent* e);
virtual void wheelEvent(MouseEvent* e);
virtual void keyEvent(KeyEvent *e);
};
void MyNavigation::mousePressEvent(MouseEvent* e) {
lastMouseCoord_ = e->coord();
}
void MyNavigation::mouseMoveEvent(MouseEvent* e) {
if (e->button() & MouseEvent::MOUSE_BUTTON_LEFT) {
rotateViewHorz(-(e->coord().x-lastMouseCoord_.x)*M_PI/1000);
rotateViewVert((e->coord().y-lastMouseCoord_.y)*M_PI/1000);
glCanvas->update();
}
lastMouseCoord_ = e->coord();
}
void MyNavigation::wheelEvent(MouseEvent* e) {
if (e->button() & MouseEvent::MOUSE_WHEEL_DOWN) {
rollCameraHorz( 2*M_PI/16.);
} else if (e->button() & MouseEvent::MOUSE_WHEEL_UP) {
rollCameraHorz(-2*M_PI/16.);
}
glCanvas->update();
}
void MyNavigation::keyEvent(KeyEvent *e) {
if (e->pressed() == false) {
// only on key release
switch (e->keyCode()) {
case KeyEvent::K_ESCAPE:
tgtApp->quit();
break;
case KeyEvent::K_HOME:
getCanvas()->getPainter()->init();
break;
case KeyEvent::K_LEFT:
moveCameraLeft(.2f);
break;
case KeyEvent::K_RIGHT:
moveCameraRight(.2f);
break;
case KeyEvent::K_UP:
moveCameraUp(.2f);
break;
case KeyEvent::K_DOWN:
moveCameraDown(.2f);
break;
case KeyEvent::K_PAGEUP:
moveCameraForward(.2f);
break;
case KeyEvent::K_PAGEDOWN:
moveCameraBackward(.2f);
break;
default: ;
}
glCanvas->update();
}
}
main-Method
main method accords the ones of preceding tutorials. We must register an instance of MyNavigation at our event handler to take advantage of it’s event listening capabilities.
int main(int argc, char** argv) {
tgtApp = ToolkitFactory::createApplication(argc, argv);
glCanvas = ToolkitFactory::createCanvas("tgt Example: Navigation");
tgtApp->addCanvas(glCanvas);
tgtApp->init();
Camera camera;
glCanvas->setCamera(&camera);
MyPainter painter(glCanvas);
glCanvas->setPainter(&painter);
MyNavigation navigation(glCanvas);
glCanvas->getEventHandler()->addListenerToFront(&navigation);
tgtApp->run();
delete tgtApp;
delete glCanvas;
return 0;
}
Complete source code
samples/navigation.cpp
#include "tgt/navigation/navigation.h"
#include "tgt/quadric.h"
#include "tgt/toolkitfactory.h"
#include "tgt/guiapplication.h"
#include "tgt/painter.h"
using namespace tgt;
GuiApplication* tgtApp;
GLCanvas* glCanvas;
class MyPainter : public Painter {
public:
MyPainter(GLCanvas* canvas) : Painter(canvas) {};
virtual void paint();
virtual void init();
virtual void sizeChanged(const ivec2& size);
void obscureObject();
void rings();
void ring(ivec3 axis, float radius);
};
void MyPainter::paint() {
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
// set Camera
getCamera()->look();
obscureObject();
rings();
}
void MyPainter::init() {
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);
glEnable(GL_COLOR_MATERIAL);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
getCamera()->positionCamera(vec3(0.f,0.f,10.f), vec3(0.f,0.f,0.f), vec3(0.f,1.f,0.f));
}
void MyPainter::sizeChanged(const ivec2& size) {
glViewport(0,0,size.x,size.y);
getCamera()->setRatio((float) size.x/size.y);
}
void MyPainter::obscureObject() {
Sphere sp(1., 32, 16);
Quad qu(2., 2., 2.);
Cylinder cy(.5, .2, 5., 16, 3);
glColor3d(.8,.8,.8);
sp.render();
glColor3d(1.,0.,0.);
glPushMatrix();
glRotated(90.,1.,0.,0.);
cy.render();
glPopMatrix();
glColor3d(0.,1.,0.);
glPushMatrix();
glRotated(90.,0.,1.,0.);
cy.render();
glPopMatrix();
glColor3d(0.,0.,1.);
glPushMatrix();
glRotated(90.,0.,0.,1.);
cy.render();
glPopMatrix();
glColor3d(1.,1.,0.);
glPushMatrix();
glTranslated(-1., 1., -1.);
qu.render();
glPopMatrix();
}
void MyPainter::rings() {
ring(ivec3(1, 0, 0), 20.f);
ring(ivec3(0, 1, 0), 20.f);
ring(ivec3(0, 0, 1), 20.f);
}
void MyPainter::ring(ivec3 axis, float radius) {
// find a vector orthogonal to axis
vec3 trans = axis;
if (axis.x != 0) {
trans.x = (axis.y*trans.y+axis.z*trans.z) / axis.x;
if (trans.x == 0.f)
trans.y = 1.f;
} else if (axis.y != 0) {
trans.y = (axis.x*trans.x+axis.z*trans.z) / axis.y;
if (trans.y == 0.f)
trans.z = 1.f;
} else if (axis.z != 0) {
trans.z = (axis.x*trans.x+axis.y*trans.y) / axis.z;
if (trans.z == 0.f)
trans.x = 1.f;
} else return;
trans = radius * normalize(trans);
Sphere sp(.2, 16, 8);
glColor3d(1.,1.,1.);
for (int i = 0; i < 36; ++i) {
glPushMatrix();
glRotatef(i*10.f, axis.x, axis.y, axis.z);
glTranslatef(trans.x, trans.y, trans.z);
sp.render();
glPopMatrix();
}
}
class MyNavigation : public Navigation {
private:
ivec2 lastMouseCoord_;
public:
MyNavigation(GLCanvas* canvas) : Navigation(canvas) {};
virtual void mousePressEvent(MouseEvent* e);
virtual void mouseMoveEvent(MouseEvent* e);
virtual void wheelEvent(MouseEvent* e);
virtual void keyEvent(KeyEvent *e);
};
void MyNavigation::mousePressEvent(MouseEvent* e) {
lastMouseCoord_ = e->coord();
}
void MyNavigation::mouseMoveEvent(MouseEvent* e) {
if (e->button() & MouseEvent::MOUSE_BUTTON_LEFT) {
rotateViewHorz(-(e->coord().x-lastMouseCoord_.x)*M_PI/1000);
rotateViewVert((e->coord().y-lastMouseCoord_.y)*M_PI/1000);
glCanvas->update();
}
lastMouseCoord_ = e->coord();
}
void MyNavigation::wheelEvent(MouseEvent* e) {
if (e->button() & MouseEvent::MOUSE_WHEEL_DOWN) {
rollCameraHorz( 2*M_PI/16.);
} else if (e->button() & MouseEvent::MOUSE_WHEEL_UP) {
rollCameraHorz(-2*M_PI/16.);
}
glCanvas->update();
}
void MyNavigation::keyEvent(KeyEvent *e) {
if (e->pressed() == false) {
// only on key release
switch (e->keyCode()) {
case KeyEvent::K_ESCAPE:
tgtApp->quit();
break;
case KeyEvent::K_HOME:
getCanvas()->getPainter()->init();
break;
case KeyEvent::K_LEFT:
moveCameraLeft(.2f);
break;
case KeyEvent::K_RIGHT:
moveCameraRight(.2f);
break;
case KeyEvent::K_UP:
moveCameraUp(.2f);
break;
case KeyEvent::K_DOWN:
moveCameraDown(.2f);
break;
case KeyEvent::K_PAGEUP:
moveCameraForward(.2f);
break;
case KeyEvent::K_PAGEDOWN:
moveCameraBackward(.2f);
break;
default: ;
}
glCanvas->update();
}
}
int main(int argc, char** argv) {
std::cout
<< "tgt Sample Program: navigation" << std::endl
<< std::endl
<< "Demonstrates implementation of custom made navigation upon keyboard and mouse events." << std::endl
<< std::endl;
std::cout
<< "Usage:" << std::endl
<< "Left-/Right-/Up-/Down-Arrow : move camera left/right/up/down" << std::endl
<< "Page-Up/Page-Down : move camera forward/backward" << std::endl
<< "drag mouse : look around (turn camera)" << std::endl
<< "mouse wheel : roll camera" << std::endl
<< "Home : reset camera" << std::endl
<< "Esc : exit" << std::endl
<< std::endl;
tgtApp = ToolkitFactory::createApplication(argc, argv);
glCanvas = ToolkitFactory::createCanvas("tgt Example: Navigation");
tgtApp->addCanvas(glCanvas);
tgtApp->init();
Camera camera;
glCanvas->setCamera(&camera);
MyPainter painter(glCanvas);
glCanvas->setPainter(&painter);
MyNavigation navigation(glCanvas);
glCanvas->getEventHandler()->addListenerToFront(&navigation);
tgtApp->run();
delete tgtApp;
delete glCanvas;
return 0;
}


