Version 1 (modified by osg, 7 years ago)

Initial copy of NPS tutorial

Using Two Independent Cameras to View a Scene

Goals

Create two views into a scene. One view will represent the scene from the point of view of a driver in a tank. This view will be rendered to the top part of the screen. The second view will be controlled by the default osgProducer::Viewer class interface (trackball, drive, or fly). This view will be rendered to a square area centered in the bottom half of the screen.

Overview

OpenSceneGraph provides programmers with access to various levels of abstraction. The previous tutorials have all dealt exclusively with the highest level of abstraction - using a Viewer class instance to control viewpoints, the scene, interaction devices and the windowing system. One of the advantages of the open scene graph is that programming with these higher level classes doesn't prevent programmers from accessing lower levels of abstraction. This tutorial will integrate some of the lower levels of abstraction by controlling viewpoints and rendering areas with Producer classes. For more information on Producer classes visit here.

The code

To create two views we will need two individually controllable cameras. To make these cameras easily accessible to a viewer class instance, we'll add them to an instance of CameraConfig. For each of these cameras we'll specify some lens settings and describe the area they will render on. For convenience we'll put this in a single function to set up the cameras:

    Producer::CameraConfig* setupCameras()
    {
    // Declare a camera configuration instance..
       Producer::CameraConfig* myCameraConfig = 
          new Producer::CameraConfig();

    // .. and two camera instances. A camera to follow the tank...
    Producer::Camera* tankFollowerCamera = new Producer::Camera();
    // .. and a traditional view of the scene
       Producer::Camera* mainCamera = new Producer::Camera();

    // Set the parameters for the main camera.
    // Projection rectangle describes the area of the render surface
    // this view will occupy. Arguments are (left, right, bottom, top)
       mainCamera->setProjectionRectangle(0.25f, 0.75f, 0.0f, 0.5f);

    // We don't want this camera to share a lens with other cameras.
       mainCamera->setShareLens(false);

    // Let producer determine the fov based on the projection rectangle
       mainCamera->getLens()->setAutoAspect(true);

    // Similar calls for the tank follower camera
       tankFollowerCamera->setProjectionRectangle(0.1f, 0.9f, 0.55f, 0.95f);
       tankFollowerCamera->setShareLens(false);
       tankFollowerCamera->getLens()->setAutoAspect(true);

    // Add the cameras to our camera configuration, we'll use the name
    // to retrieve an handle to the camera later.
       myCameraConfig->addCamera("tankFollowerCamera",tankFollowerCamera);
       myCameraConfig->addCamera("mainCamera",mainCamera);

    // We want both cameras to use the same render surface. Retrieve a
    // handle to the default render surface...
       Producer::RenderSurface* rsOne = mainCamera->getRenderSurface();

    // .. and assign that to the second camera.
       tankFollowerCamera->setRenderSurface( rsOne );

       return myCameraConfig;
    }

Now that we have a function to set up the cameras, we can set up the rest of our simulation. We'll skip the detail of building the basic scene (a terrain model with a tank circling above it.) This function is available in the source download. To set up the rest of the simulation, we'll need to add an offset transform node to the circling tank. This offset and a transformAccumulator will be used in the simulation loop to position a camera a few meters above and behind the tank model.

    int main( int argc, char **argv )
    {
    // pointers to the root node and the tank model node 
       Producer::ref_ptr<osg::Group> rootNode; 
       Producer::ref_ptr<osg::Group> ownTank;

    // build scene with terrain and two tanks
       if (!setupScene(rootNode,ownTank))
       {
          std::cout<< "problem setting up scene" << std::endl;
          return -1;
       }

    // Declare a camera configuration instance, initialize it using
    // the function we defined earlier. 
       Producer::CameraConfig* myCameraConfig = setupCameras();

    // Use this camera configuration instance to set up the viewer 
       osgProducer::Viewer viewer(myCameraConfig);
       viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
       viewer.setSceneData( rootNode.get() );

    // Declare a transform for a position above and behind the tank
    // aimed down slightly. Add this transform to the tank node. 
       osg::PositionAttitudeTransform * followerOffset =
          new osg::PositionAttitudeTransform();
       followerOffset->setPosition( osg::Vec3(0.0,-25.0,10) );
       followerOffset->setAttitude(
          osg::Quat( osg::DegreesToRadians(-15.0), osg::Vec3(1,0,0) ) );
       ownTank.get()->addChild(followerOffset);

    // Declare a transform accumulator to be used to position a 
    // camera. Attach transform accumulator to the follower transform. 
       transformAccumulator* tankFollowerWorldCoords = 
          new transformAccumulator();
       tankFollowerWorldCoords->attachToGroup(followerOffset);

    // create the windows and run the threads.
       viewer.realize();

Now that the simulation is set up we are ready for the simulation loop. This loop will be slightly different than previous. After we've updated the scene graph nodes and before we start the cull traversal we'll manually position the camera that follows the tank. The other camera will present the default viewer and interface. The matrix that is returned from our transform accumulator is expressed in Y up terms. The camera that is used to position the camera is expecting a matrix in Z up terms so we'll need to rotate the matrix. The code to achieve this follows:

       while( !viewer.done() )
       {
          // wait for all cull and draw threads to complete.
          viewer.sync();

          // update the scene by traversing it with the the update visitor which will
          // call all node update callbacks and animations.
          viewer.update();

          // Get a handle to the follower camera. Use camera class method to set
          // the matrix to the world coordinate position of our tank follower.
          // Rotate the matrix from Y up to Z up. 
          viewer.getCameraConfig()->
             findCamera("tankFollowerCamera")->setViewByMatrix( 
             Producer::Matrix(tankFollowerWorldCoords->getMatrix().ptr() ) *
             Producer::Matrix::rotate( -M_PI/2.0, 1, 0, 0 ) );

          // fire off the cull and draw traversals of the scene.
          viewer.frame();
       }

       // wait for all cull and draw threads to complete before exit.
       viewer.sync();

       return 0;
    }