wiki:Tutorial/OpenSG1/Windows

Previous Chapter: Light

Tutorial Overview

Next Chapter: Traversal


Windows and Viewports

This chapter is all about windows and viewports. What a window is should be self explanatory, as of course, OpenSG needs a window which it will render into. Viewports are rectangular areas within the window. Applications can have an arbitrary number of Windows, each of which can have one or more viewports. All applications you have seen so far, had one window with one viewport, which were provided by the simple scene manager, but that is by no means a limitation of the system.

I will also introduce foregrounds and backgrounds, as these are closely related to windows and viewports. Foregrounds can be used to display logos or they can even be used to capture a screenshot amongst other things, whereas backgrounds do exactly what you expect them to do: clear the background in different ways.

Windows

As I mentioned above, every application needs a window. We always have created a simple GLUTWindow right at the very beginning of the main function.

#!cpp

int main(int argc, char **argv)

{

    osgInit(argc,argv);

        

    int winid = setupGLUT(&argc, argv);

    GLUTWindowPtr gwin= GLUTWindow::create();

    gwin->setId(winid);

    gwin->init();



    scene =createScenegraph();



    // ....

Of course the GLUT Window is not the only one. Here is a list of all available windows

  • GLUTWindow
  • QTWindow
  • XWindow
  • Win32Window
  • PassiveWindow
  • ClusterWindow
  • MultiDisplayWindow
  • SortFirstWindow

The first four windows are providing a connection between OpenSG and the windowing system you are using. GLUT and Qt windows actually do have some advantages compared to the others, because both work on many different platforms, where as X Windows are restricted to UNIX boxes and Win32 windows to the Microsoft platform. Additionally GLUT and Qt windows might be a bit easier to use as there are wrapper classes that may help you out. We have used GLUT windows all the way, so you should be familiar with that. For Qt you can have a look at Qt?

So what does a window actually do? First of all, it is responsible for output of the rendered images. It does no handling of input events or similar things. A window keeps its instances of viewports and manages OpenGL context creation and activation/deactivation. It also handles detection of OpenGL extensions.

The PassiveWindow is a bit different from the others. A passive window does not handle an OpenGL context. It's purpose is to fit seamlessly into other OpenGL programs. Passive windows can be used with any window and GUI system that supports OpenGL. See section Tutorial/OpenSG1/QT? for an example using passive windows.

The last three windows, cluster, multi display and sort first windows are used for clustering. These will be discussed in more detail in chapter Clustering.

At the end of this chapter there will be a tutorial where we will set up an application from scratch, without the simple scene manager. This tutorial may explain the usage of windows and viewports better than words can tell.

Viewports

A Window must at least have one viewport, but can have more. A viewport represents a part of the screen that is being rendered into. One or more viewports can, but don't have to, cover the whole window. Every viewport can only be attached to one window. A typical use of multiple viewports is the perspective/front/left/right view you might know from a modeling package or other applications.

Typical use of four viewports

The size of a viewport is defined by providing values for left, right, top and bottom borders. These can either be between 0 and 1 (inclusively) or greater than 1. The first variant means the size of the viewport is relative to the window size whereas values above 1 specify the size in pixels. You should decide which variant to use depending on whether the viewports should scale together with the window if it is resized or not. Here is an example showing a window with two viewports, one defined relatively and the other absolute.

Resizing window with two different viewports

The yellow viewport is defined relative to the window, whereas the red viewport's values are given absolutly in pixels. When the window is resized the yellow viewports changes it's size too, but the red one doesn't change shape or position at all.

You can also pass a -1 to one or more parameters which means that the most possible space is taken by the viewport. An exception to these sizing rules is the PassiveViewport which simply ignores all given values, as they are taken from the currently active OpenGL context.

In order to use a viewport correctly you not only need to define its size, but some additional things are needed: A node of the scenegraph (usually the root node) where rendering traversal should begin, a camera and a background are needed. Optionally you can provide one or more foreground(s).

As you might guess there are also different types of viewports. The one that came along with the simple scene manager was a standard viewport. Here is a list of all available viewports

  • osg::Viewport
  • osg::PassiveViewport
  • osg::ColorBufferViewport
  • osg::StereoBufferViewport

Well, the default Viewport is what you want in most cases. Passive viewports are needed to integrate OpenSG into existing OpenGL programs. The color buffer viewport allows rendering in specific color channels and finally the stereo buffer viewport is used mostly for active stereo applications. Stereo with shutter glasses for example need four buffers which are provided by osg::StereoBufferViewport. Passive stereo can be achieved with default viewports. Again, more on that in Clustering.

Cameras

A camera is of course needed for every application. The camera defines what is actually being rendered. The same way as you can have multiple viewports, you can also have multiple cameras. However, one camera can be attached to more than one viewport. The most important parameters of a camera are its position and orientation. Cameras in OpenSG behave similar to light sources - their position is defined by the transformation of the beacon they are assigned. In most cases you will have a transformation node right below the root node and use it as a beacon for the camera. However, if you want the user to drive a car, for example, it would make sense to attach the camera to the car geometry, thus every transformation applied to the car geometry will also be applied to the camera as well.

Good news is, that we only need one matrix to describe the position and orientation of the camera, but bad news is that often you are given two points in space, one where the camera is located and one where it is looking at and another vector telling you where "up" is from the camera's point of view. Whenever you want to use these values you would have to calculate the matrix in order to use them with OpenSG. Well, luckily that has been done for you

already: the osg::MatrixLookAt function from OSGMatrixUtility.h does that job!

OpenSG cameras are using the same coordinate system conventions like OpenGL: the camera is looking along the negative z-axis, y points up and x points right.

In addition to position and orientation there are some internal camera parameters which may differ from camera to camera. However, the near- and far clipping planes are needed by every camera.

In OpenGL you have two matrices that are responsible for transforming the world onto the rectangular area which is your screen: GL_MODELVIEW and GL_PROJECTION matrix. OpenSG stores internally three matrices, because the projection matrix is split up into two: one for the real projection and another for a projection transformation. The last one is needed for non-coplanar multi-screen projections like a CAVE. However, we don't need to pay special attention

to that, for now.

Here is a short overview of the different cameras you can use with OpenSG

Perspective Camera

This is the most common camera. Unless you are doing something special you will be fine with this camera. There is only one additional attribute, the vertical field of view defines the opening angle of the camera. The horizontal field of view is automatically adjusted to create a square aspect ratio. If your pixels are not square you can set the pixel aspect ratio in the aspect Field. Like all angles in OpenSG, the value for the fov is given in radians not in degrees!

Here is an example on how to setup a camera

#!cpp

    PerspectiveCameraPtr camera = PerspectiveCamera::create();

    beginEditCP(camera);

        camera->setBeacon( some_node );

        camera->setFov( deg2rad( 60 ) );

        camera->setNear( 0.5 );

        camera->setFar( 8000 );

    endEditCP(camera);

Let us assume that one centimeter equals one unit, then we would have created a camera with an opening angle of 60 degrees, a near clipping plane half a centimeter in front of the camera and the far clipping plane 80 meters away

Clipping Planes and Vertical Field of View

Matrix Camera

Sorry, the matrix camera is not related to the movie trilogy and hence cannot produce that matrix camera effect called 'bullet time'. However if you want to help the development of OpenSG... ;-)

The matrix camera is a very simple one as it keeps the modelview and perspective matrices directly and ignores all other attributes it may have. Under certain circumstances this may be very useful, especially if you are integrating OpenSG into an existing OpenGL program by using a passive window as well as passive viewports. With this camera you are very close to standard OpenGL behaviour.

Camera Decorators

A decorator can be used instead of the original camera object, where the decorator enhances the original in some way. Here is an example: For a stereo application you need two cameras and if you are moving the camera around you normally would have to do things twice. With camera decorators you can use two osg::ShearedStereoCameraDecorator which will point both to the same camera. The decorators will be assigned to the viewports instead of the camera. If you now move the camera both virtual cameras represented by the decorators will move simultanously. And again, more on stereo in chapter Clustering.

A big advantage of decorators in general is, that you can easily change the object which is decorated by some other and maintain the enhancements the decorators provide. In our example you could exchange a perspective camera with a matrix camera and the stereo setup won't be destroyed!

Here is an overview of the decorators currently available

Stereo Decorators

There are actually two different decorators for stereo solutions. Both have some attributes in common. The eye separation defines the distance between the eyes and is provided in model units. You should be very careful with this value, as too small or big values will destroy the stereo effect (and may make people sick...). Finally you need to tell the decorator if it is the left or the right one. This is handled by a simple boolean variable where true indicates the left eye.

OSG::ShearedStereoCameraDecorators are used for typical passive stereo applications, where two beamers are projecting the image and the audience is wearing polarisation glasses. They add one additional attribute, the distance to the zero parallax plane. The zero parallax plane is a plane where the left and right image perfectly overlap, i.e. the parallax is zero. This value should reflect the distance from the user's point of view to the image plane.

The following code creates a decorator for the right eye and a corresponding viewport

#!cpp

    cameraDecorator = ShearedStereoCameraDecorator::create();

    beginEditCP(cameraDecorator);

        cameraDecorator->setLeftEye(false);

        cameraDecorator->setEyeSeparation(6);

        cameraDecorator->setDecoratee(some_camera);

        cameraDecorator->setZeroParallaxDistance(200);

        rightViewport = OSG::Viewport::create();

        beginEditCP(rightViewport);

            rightViewport->setCamera    ( cameraDecorator );

            rightViewport->setBackground( some_background );

            rightViewport->setRoot      ( root );

            rightViewport->setSize      ( .5,0,1,1 );

        endEditCP(rightViewport);

    endEditCP(cameraDecorator);

I choose 6 for the eye separation and 200 for the distance to the image plane. This would make sense only, if one unit is equal to one centimeter. You might have to adjust these values according to your units.

OSG::ProjectionStereoCameraDecorators are mainly used for head-tracked stereo applications like a CAVE. I will not discuss this decorator here unless there is a public demand for it. ;-)

Tile Camera Decorator

This kind of decorator is used to scale some rectangular area up to full viewport size. It's mainly use is to divide a large rendering area into some small areas which are displayed on a different screen each. The parameters of the camera can be set up as if you were using one large viewport. This is much easier than placing several cameras side by side to archive the same effect.

In Darmstadt, Germany there is a wall of screens, called HEye Wall, with 6 * 4 screens which is capable of stereo rendering. A 48 + 1 node cluster running OpenSG is driving the HEye Wall. Instead of using 48 cameras it is sufficient to use only one single camera and let the decorators do the rest!

The Heye Wall in Darmstadt, Germany

Backgrounds

Well, a background does simply what a background is supposed to do. Any region that was not covered by any object, just rendered, is filled with

a background. Every viewport must have exactly one background, it can not have more. However, it is of course possible to swap backgrounds during runtime.

There are several different backgrounds at your disposal.

Solid Background

OSG::SolidBackground is the most simple and common of all backgrounds, it simply fills the screen with one color. Setting up such a background is very easy. The following example will generate a red background.

#!cpp

SolidBackgroundPtr bg = SolidBackground::create();

beginEditCP(bg, SolidBackground::ColorFieldID);

    bg->setColor(Color3f(1,0,0));

endEditCP(bg, SolidBackground::ColorFieldID);

Gradient Background

OSG::GradientBackground enables you to fill the background with a color gradient. To specify the gradient you can use any number of color keys. Gradients in OpenSG are always vertical. Every color key needs a value between 0 (bottom) and 1 (top). If no color key is given the background will be black, if only one key is given the background will be identical to a solid background with the same color. Regions which are not filled will be drawn in black, too.

Here is a little example that will set up a gradient background, which fades from black to white.

#!cpp

GradientBackgroundPtr gb = GradienBackground::create();

beginEditCP(gb);

    gb->addLine(Color3f(0,0,0),0.0);

    gb->addLine(Color3f(1,1,1),0.0);

endEditCP(gb);

Image Background

You also can use images for the background with osg::ImageBackground. The image can be scaled to fill the whole viewport, else it will be drawn from the lower left corner, leaving unoccupied regions in a user defined color. Please notice that the image is not used like a texture, thus image backgrounds are not as fast to draw than the others. Setting up an image background is also very simple. The following example will create a background with some image, which will not be scaled to fit the viewport and left areas will be drawn black.

#!cpp

	ImageBackgroundPtr imBkg = ImageBackground::create();

	beginEditCP(imBkg,  ImageBackground::ColorFieldMask |

                            ImageBackground::ImageFieldMask |

                            ImageBackground::ScaleFieldMask

			   ); 

		imBkg->setColor(Color3f(0,0,0));

		imBkg->setImage(some_image);

		imBkg->setScale(false);

	endEditCP(imBkg,    ImageBackground::ColorFieldMask |

                            ImageBackground::ImageFieldMask |

                            ImageBackground::ScaleFieldMask

			   );

Please notice that the image that need to be passed to setImage() is not just some jpg file or similar. You need to pass an osg::Image object. The tutorial at the end of this chapter will show you how to use an image background.

Passive Background

Well, like the other passive components encountered so far, the osg::PassiveBackground does nothing at all, thus there are no fields to set. It's main purpose is to make it possible to have two or more overlapping viewports to generate combined images.

Sky Background

The most exciting background for virtual reality environments is most likely to be the osg::SkyBackground. Inspired by the VRML background node the OpenSG sky background is actually a sky box. By setting some parameters you can quickly create a ground-sky environment, but even more interesting is to use six specially designed textures, which then can simulate an infinite environment that will move if the user looks around, but always be infinitely far away, so you don't have to worry about moving out of the skybox.

All six images of a skybox stichted together

Foregrounds

Foregrounds are painted on top of the rendered image. They can be used to add a logo or a watermark for example, but a foreground can also be used to grab a rendered image into an osg::Image or store it in a file. There can be more than one foreground assigned to a viewport. If that is the case they are evaluted in the same order as they were added.

Image Foreground

The most common foreground is the osg::ImageForeground which is often used to add a logo to a rendered scene. That image can be either RGB or RGBA. The image foreground supports correct alpha blending.

Polygon Foreground

The osg::PolygonForeground draws a single polygon with an optional texture on top of the rendered image. It's main purpose is to achieve the same results as with the default image foreground, but by using a texture rather than an image, which is quite faster to compute as this process is better accelerated by the graphics hardware.

The usage might be a bit strange (at least it seems to me ;-) ) - you need a set of 2D points and an equal number of 3D points to define the polygon and it's position on screen. If values for the 2D points are greater than one, the starting point is the lower left corner and for values smaller then -1 are using the upper right corner as origin. This allows you to define polygons that are independent of the viewport size. The 3D points define the texture coordinates used by the 2D points, which allows you to use 3D textures as a Foreground, or to pass 3 parameters to the Material used to render the polygon.

Grab Foreground

The osg::GrabForeground can be used to grab a rendered image into an osg::Image. The size and position of the image define the area that will be grabbed. If you create the image only and leave it's size to default values (i.e 1D image with 1 pixel) the whole viewport will be grabbed. It might be a bit confusing that there is no such method like grab() or anything else that will actually invoke the grabbing process, but the grab is executed every time the frame is rendered. So if you are implementing something like a screenshot function you should deactivate the foreground after fetching the image to avoid grabbing one image each frame!

The GrabForeground is inactive by default and needs to be activated by setting its boolean "active" flag to true.

The following example shows how you could set up a grab foreground. We are creating an image in RGBA color mode with size 1 to grab the whole viewport.

#!cpp

	GrabForegroundPtr grab = GrabForeground::create();

	beginEditCP(grab);

		grabImage = Image::create();

		beginEditCP(grabImage);

			grabImage->set(GL_RGBA, 1);

		endEditCP(grabImage);

		grab->setImage(grabImage);

                

                grab->setActive(true);

	endEditCP(grab);

	

	//mgr is of type osg::SimpleSceneManager

	//We get a pointer to the foreground multifield from the viewport

	MFForegroundPtr *foregrounds = mgr->getWindow()->getPort(0)->getMFForegrounds();

	

	//now we simple add it to the foreground multifield

	foregrounds->push_back(grab);

Whenever a frame is rendered the osg::Image grabImage will store the rendered frame overwriting a possibily previously stored frame.

File Grab Foreground

osg::FileGrabForeground works pretty much the same way grab foreground does. However as the name implies the result is stored in a file rather than in an OpenSG image object. There is a boolean flag to tell the system whether this foreground is active or not, inherited from the GrabForeground. Furthermore you do not need to create an image instance as this is done automatically for you, but you have to provide a filename for the image to be stored. You are not limited storing single files only, it is also possible to store image sequences. The filename is given in a printf style thus naming an image "image%d" will lead to an image sequence as an internal frame counter will replace the variable in the filename with the counter value. If you use %04d instead you will have leading zeros in your filename which might be very useful for other applications that are capable to create a video from an image sequence source.

Simple Statistics Foreground

This foreground is subject to change, so hang on...

Graphic Statistics Foreground

same as simple statistics foreground

Tutorial - Without Simple Scene Manager

As promised, we are going to build an application which will show a torus only. Again? Well, this time we are doing it from bottom up, without the comfortable support of the simple scene manager! Additionally we are going to use nice backgrounds and a logo in the foreground and add a screenshot feature which will grab the rendered image and save it in a file! To make the whole thing complete we are going to use two viewports thus we are having two different viewing angles on our sceen! So here we go:

Here is an overview of what we are going to do.

Application overview

This time we will start with a completely empty file! So open your editor of choice and create a new file and store it in the progs folder like the other tutorial files. First thing we need are a lot of include files:

#!cpp

#include <OpenSG/OSGGLUT.h>

#include <OpenSG/OSGConfig.h>

#include <OpenSG/OSGSimpleGeometry.h>

#include <OpenSG/OSGGLUTWindow.h>

#include <OpenSG/OSGSolidBackground.h>

#include <OpenSG/OSGDirectionalLight.h>

#include <OpenSG/OSGPerspectiveCamera.h>

#include <OpenSG/OSGTransform.h>

#include <OpenSG/OSGRenderAction.h>

#include <OpenSG/OSGViewport.h>

#include <OpenSG/OSGGradientBackground.h>

#include <OpenSG/OSGImageBackground.h>

#include <OpenSG/OSGImage.h>

#include <OpenSG/OSGImageForeground.h>

#include <OpenSG/OSGFileGrabForeground.h>

All include files should be familiar to you, however you might wonder why we need "OSGTransform.h" where we have used transformations before without ever including this header. Well, the simple scene manager we are going to drop this time, included a lot of needed files, like stuff for background, camera, viewports and some important cores. As we are going to do it without the simple scene manager, we need to include all those files by hand now. For the lazy ones, like me, you can of course simply include "OSGSimpleSceneManager.h" even if you don't want to use the manager...

As long as not stated otherwise all coming blocks of code should be added to the bottom of the file, i.e. below the block you just added.

Alright, now activate the OpenSG namespace, because we do not want to begin with OSG:: every time

#!cpp

OSG_USING_NAMESPACE

We will need some global variables later on, so we are going to define them all now. Don't worry if you don't know what they will be for.

#!cpp

NodePtr scene;



PerspectiveCameraPtr leftCamera;

PerspectiveCameraPtr rightCamera;



ViewportPtr leftViewport;

ViewportPtr rightViewport;



WindowPtr window;



NodePtr leftCamBeacon, rightCamBeacon, lightBeacon, lightNode;



RenderAction *renderAction;

The purpose of most variables should be obvious. The render action is needed for anything to render, but simply take it as it is, actions will be discussed in chapter Traversal.

Okay, let us insert our forward declaration like we ever did:

#!cpp

int setupGLUT( int *argc, char *argv[] );

Very well, our header is ready now. Now we can begin writing down the main function. We will begin with initializing OpenSG and a GLUT windows

#!cpp

int main(int argc, char **argv)

{

	osgInit(argc,argv);

	

	int winid = setupGLUT(&argc, argv);

Normally we would create the scenegraph now and create an instance of the simple scene manager. This time we will create a node with a group node, which will be a placeholder for the scenegraph we'll add later on.

#!cpp

	NodePtr scene = Node::create();

	beginEditCP(scene);

		scene->setCore(Group::create());

	endEditCP(scene);

Now we can begin with creating some needed objects the simple scene manager would have taken care of otherwise. As we want two different views on the same scene, we need two viewports and two cameras.

#!cpp

	// okay, that's not very spectacular anymore...

	leftCamera = PerspectiveCamera::create();

	rightCamera = PerspectiveCamera::create();

    

	//set attributes for camera

	beginEditCP(leftCamera);

		leftCamera->setBeacon(leftCamBeacon);

		leftCamera->setFov(deg2rad(90));

		leftCamera->setNear(0.1);

		leftCamera->setFar(100);

	endEditCP(leftCamera);

	

	//same stuff for the other one

	beginEditCP(rightCamera);

		rightCamera->setBeacon(rightCamBeacon);

		rightCamera->setFov(deg2rad(90));

		rightCamera->setNear(0.1);

		rightCamera->setFar(100);

	endEditCP(rightCamera);

So we just created two cameras with a field of view of 90 degrees. The near clipping plane is 0.1 units away from the camera where the far clipping plane is 100 units away.

Good, next we need a background. We decided previously to have one gradient and one image background.

#!cpp

	GradientBackgroundPtr leftBkg = GradientBackground::create();

	beginEditCP(leftBkg);

		leftBkg->addLine(Color3f(0,0,0),0);

		leftBkg->addLine(Color3f(1,1,1),1);

	endEditCP(leftBkg);

The gradient background can have an arbitary number of different colors. We only want to have a gradiant background black (bottom) to white (top). You could simply add another line with another color and position. Fell free to experiment with that when this application is running for the first time (At this point of time you won't see anything).

The image background needs an image beforehand, so we first load one

#!cpp

	//load the image file

	ImagePtr bkgImage = Image::create();

	beginEditCP(bkgImage);

		bkgImage->read("data/front.jpg");

	endEditCP(bkgImage);

Now the background itself

#!cpp

	ImageBackgroundPtr rightBkg = ImageBackground::create();

	beginEditCP(rightBkg);

		rightBkg->setImage(bkgImage);

		rightBkg->setScale(true);

	endEditCP(rightBkg);

By now we have an image background displaying data/front.jpg which will be scaled to whatever size the viewport has. Now we have everything we need to create the viewports (i.e. camera, background and a root node).

#!cpp

	leftViewport = Viewport::create();

	rightViewport = Viewport::create();

	

	beginEditCP(leftViewport);

		leftViewport->setCamera(leftCamera);

		leftViewport->setBackground(leftBkg);

		leftViewport->setRoot(scene);

		leftViewport->setSize(0,0,0.5,1);

	endEditCP(leftViewport);

	

	beginEditCP(rightViewport);

		rightViewport->setCamera(rightCamera);

		rightViewport->setBackground(rightBkg);

		rightViewport->setRoot(scene);

		rightViewport->setSize(0.5,0,1,1);

	endEditCP(rightViewport);

As you can see, both viewports are of course quite similar. They both now have a camera, the corresponding background, the same root node and different sizes. If you recall what we have said about sizes of viewports earlier you can see, that we now have a viewport which covers the left half of the screen whereas the other covers the right half. We have provided the size not in pixel format, so the viewports will scale as the window is being resized.

Finally we complete the main() function by creating the window and the render action that will be needed for actually rendering the windows content

#!cpp

	//and the render action - more on that later

	renderAction = RenderAction::create();



	GLUTWindowPtr gwin= GLUTWindow::create();

	gwin->setId(winid);

	gwin->setSize(300,300);

	window = gwin;

	

	// add the viewports to the new window

	window->addPort(leftViewport);

	window->addPort(rightViewport);

	

	window->init();

	

	glutMainLoop();



	return 0;

}

For successfull compilation, you need the GLUTsetup() function we defined at the beginning.

#!cpp

int setupGLUT(int *argc, char *argv[])

{

    glutInit(argc, argv);

    glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);

    

    int winid = glutCreateWindow("OpenSG First Application");

    

    return winid;

}

Well, compilation will work, but execution results in an error, which tells you that there is no display callback function defined. That is true, so extend your setupGLUT function with these lines just before the return statement.

#!cpp

	glutDisplayFunc(display);

	glutIdleFunc(display);

as well as this block between the main() and setupGLUT() functions

#!cpp

void display(void)

{

	window->render(renderAction);

}

You now can execute the application, although there really is not much to see. You will see an error telling you that the camera has no beacon and you can see the gradient background only... anyway, we are not quite done yet, for a minimal application there is still a light source missing and beacons for the light and camera(s). So this will be our next step, to create a working scenegraph. We also add a torus to have at least some visible geometry. Locate the following lines at the beginning of the main() function

#!cpp

	NodePtr scene = Node::create();

	beginEditCP(scene);

		scene->setCore(Group::create());

	endEditCP(scene);

and replace them with our well know call of

#!cpp

	scene =createScenegraph();

Next add the createScenegraph() function - place the code that belongs to it before the main() function! First we create a simple torus and we also create the nodes that will act as a beacon for the cameras and the light source.

#!cpp

NodePtr createScenegraph(){

    //create geometry - just a simple torus

    NodePtr torus = makeTorus(1,5,8,16);



    //create transformations & beacons for cameras & light

    leftCamBeacon = Node::create();

    rightCamBeacon = Node::create();

    lightBeacon = Node::create();

now we fill the three nodes we just created with life:

#!cpp

    // the following style is a bit different than from before

    // this is only to remind you that beginEditCP()'s can also

    // be interleaved



    beginEditCP(leftCamBeacon);

    beginEditCP(rightCamBeacon);

    beginEditCP(lightBeacon);



        //create Transformations

        TransformPtr leftCamTrans, rightCamTrans, lightTrans;



        leftCamTrans = Transform::create();

        rightCamTrans = Transform::create();

        lightTrans = Transform::create();



        beginEditCP(leftCamTrans);

        beginEditCP(rightCamTrans);

        beginEditCP(lightTrans);

            Matrix leftM, rightM, lightM;

            leftM.setTransform(Vec3f(-5,6,10));

            rightM.setTransform(Vec3f(5,-6,10));

            lightM.setTransform(Vec3f(1,10,2));



            leftCamTrans->setMatrix(leftM);

            rightCamTrans->setMatrix(rightM);

            lightTrans->setMatrix(lightM);



        endEditCP(leftCamTrans);

        endEditCP(rightCamTrans);

        endEditCP(lightTrans);



        leftCamBeacon->setCore(leftCamTrans);

        rightCamBeacon->setCore(rightCamTrans);

        lightBeacon->setCore(lightTrans);

    endEditCP(leftCamBeacon);

    endEditCP(rightCamBeacon);

    endEditCP(lightBeacon);

    // -- end of camera beacon creation

Note that this can be quite a bit more compact when using CPEdit and makeCoredNode(), if you can use a more current version than 1.2.

All three nodes now contain a transformation core with a different transformation matrix. Relative to the origin, the first camera is located in the upper left corner where as the second is in the lower right, both 10 units away along the z axis. The light will be located 10 units above the origin.

Next, we create the light source for the scene. As we have only very simple geometry in our scene, one directional light should be sufficient.

#!cpp

    //create the light source

    DirectionalLightPtr dLight = DirectionalLight::create();



    beginEditCP(dLight);

        dLight->setDirection(Vec3f(0,1,2));

        

        //color information

        dLight->setDiffuse(Color4f(1,1,1,1));

        dLight->setAmbient(Color4f(0.2,0.2,0.2,1));

        dLight->setSpecular(Color4f(1,1,1,1));

        

        //set the beacon

        dLight->setBeacon(lightBeacon);

    endEditCP  (dLight);

The beacon is set, but we will also need a node which will contain the light as a core to define what should be lit (see also Point Light).

#!cpp

    // create the node that will contain the light source

    

    lightNode = Node::create();

    beginEditCP(lightNode);

        lightNode->setCore(dLight);

        lightNode->addChild(torus);

    endEditCP(lightNode);

We are nearly finished for the first working version, we are just missing the root node:

#!cpp

    // now create the root and add all children

    

    NodePtr root = Node::create();

    beginEditCP(root);

        root->setCore(Group::create());

        root->addChild(lightNode);

        root->addChild(leftCamBeacon);

        root->addChild(rightCamBeacon);

        root->addChild(lightBeacon);

    endEditCP(root);

    

    return root;

}

Now try to compile and execute the application. It should look like this

Two different viewports on the same scene

As you can see, the torus is displayed from two different positions. This is really not a miracle as we have defined different locations for both cameras. This is nice, but if you resize the window, the viewports are not scaled correctly although they should! The reason is, that we have not specified the GLUT reshape callback function. Add this line to the setupGLUT() function

#!cpp

    glutReshapeFunc(reshape);

and add this block of code right below the display() function

#!cpp

void reshape(int w, int h)

{

    window->resize(w, h);

    glutPostRedisplay();

}

If you compare this function with the previous reshape function you will notice that the only thing that changed, is a replacement of the simple scene manager object with the window object.

Now the application can be resized correctly. We are still missing functionality we wanted to implement: the foreground is still missing as well as the snapshot feature. First we create a foreground which will display a png file in the right viewport. In the main() function locate the lines that say

#!cpp

    rightViewport->setRoot(scene);

    rightViewport->setSize(0.5,0,1,1);

    endEditCP(rightViewport);



    //and the render action - more on that later

    renderAction = RenderAction::create();

and add the following new code right before the renderAction is created.

#!cpp

    // add a logo foreground to the right viwport



    //load the logo image file

    ImagePtr frgImage = Image::create();

    beginEditCP(frgImage);

        frgImage->read("data/logo.png");

    endEditCP(frgImage);



    ImageForegroundPtr imgFrg = ImageForeground::create();

    beginEditCP(imgFrg);

        //NOTE: the position values are between 0 and 1

        //and are relative to the viewport!

        imgFrg->addImage(frgImage,Pnt2f(0.1,0));

    endEditCP(imgFrg);

Now we have loaded an image from a file like we did for the image background. After that, we created the image foreground and simply added the image. The position I provided via Pnt2f is the location of the image relative to the lower left corner of that viewport. As these values are smaller than one, these are meant relative the same way when defining sizes of viewports.

The foreground still would not be visible, because we need to add it to the foreground field of a viewport. That is done by the following line of code

#!cpp

    //add the created foreground by appending it to

    //the vieports foreground multifield

    rightViewport->getMFForegrounds()->push_back(imgFrg);

Finally we need to add another foreground that will allow us to capture the screen into files. This is with the osg::FileGrabForeground. The code can be placed right below the code you just added

#!cpp

    //create the foreground for screenshot functionality

    FileGrabForegroundPtr fileGrab = FileGrabForeground::create();

    beginEditCP(fileGrab);

        fileGrab->setActive(false);

        fileGrab->setName("data/screenshot%04d.jpg");

    endEditCP(fileGrab);

    

    //add this one to the right viewport, too

    rightViewport->getMFForegrounds()->push_back(fileGrab);

We added the foreground the same way we did with the image foreground. We only set two attributes for the file grab foreground. We set a file name and also set it's active flag to false, to avoid that the applications starts to shot screenshots right at the beginning. Rather we want to de-/ activate this feature by pressing the 's' key. Maybe you find the filename a bit unusual - if I would have set it to "data/screenshot.jpg" every screenshot taken, would have overwritten that file. Of course that can be exactly what you want, but I decided, we want to have a series of images taken. The %d is replaced by some internal counter variable and %04d means that there are leading zeros until four digits are used. Therefore the images series is screenshot0001.jpg screenshot0002.jpg and so on.

Let us add the keyboard callback to enable the user to switch screen capturing on and off. Add this to the setupGLUT function

#!cpp

    glutKeyboardFunc(keyboard);

add this right below the reshape() function

#!cpp

void keyboard(unsigned char k, int x, int y){

    switch(k)

    {

        case 27:

        {

            osgExit();

            exit(1);

        }

        break;



        case 's':

        {

            // The return value is actually a pointer to on osgPtr class

            // I don't know if that makes much sense at all...

            MFForegroundPtr foregrounds = *(window->getPort(1)->getMFForegrounds());

            FileGrabForegroundPtr fg = FileGrabForegroundPtr::dcast(foregrounds[1]);



            if (!fg->getActive())

            {

                std::cout << "start recording..." << std::endl;

                fg->setActive(true);

            }

            else

            {

                std::cout << "stopped" << std::endl;

                fg->setActive(false);

            }

        }

        break;

    }

}

This one looks a bit more complicated... well, if the 's' key is pressed we enter the corresponding case statement, where we first get an osg::MFForegroundPtr variable, which is actually a multi field that contains all osg::ForegroundPtr of this specific viewport. The viewport we have chosen is the right one with the image background. Remember we added the viewport with the gradient background first, which will become viewport number 0 and the right viewport therefore becoming number 1 which we got above the call of window->getPort(1). The return value of getMFForegrounds() is actually a pointer to the osg::MFForegroundPtr datastructure. We simply dereference the pointer and now have the multi field correctly stored. In the next line we 'extract' the osg::FileGrabForeground which is number 1, because we first added the image foreground which is number 0 - same procedure as with the viewport numbers. However, the multi field stores osg::ForegroundPtr and we need to downcast it to the correct type. As we know that this foreground is a file grab foreground, we invoke a dynamic cast. Finally we have the foreground we are looking for and can now set the active attribut. By the way: I know that it would be easier to store the foreground as a global variable, but I wanted to show you how elements of OpenSG can be retrieved from more complex data structures. With bigger projects it won't be possible to store everything globally

Our application is complete now. One hint for those with fast machines: Do not turn on screen capturing for to long, or you will have lots of jpg files...

One essential functionaliy is indeed still missing! We are not able to navigate in any means. Well, this is up to you now, have a look at the exercises ;-)

Exercises

1) Sky Background

Try to use a sky background with textures. You can find the needed textures in the progs/data folder. The files are called right.jpg, top.jpg,... and so on.

2) Navigator

Navigation is not possible in our tutorial. Implement a mouse driven navigation comparable to the navigation possibilities from the other tutorials.

Important Hint: You can save a great amount of time, if you have a closer look at the osg::Navigator helper class. This class is also used by the simple scene manager.


Previous Chapter: Light

Tutorial Overview

Next Chapter: Traversal

Attachments