Version 2 (modified by osg, 7 years ago)

Added menu

A Camera that Orbits (and continually point at) a Node in the Scene

(Wiki editing note: Code needs conversion to osgViewer)
(Wiki editing note: Add link to complete source code at bottom)

Goal

Create a callback that updates a matrix that represents the world coordinates of a position circling and pointing at a node in a scene. Use the inverse of this matrix to position a camera.

The code

This callback is based on the osgFollowMe tutorial. In this case, we'll add a matrix data member to store the world coordinate position we want to use for our camera. Each time an update traversal is initiated, we'll load this matrix with current world coordinates of a position orbiting the node. To create the effect of circling a node, we'll add an 'angle' data member that we will increment each frame. The relative position of the matrix with be based on a transformation by a fixed amount and a rotation based on the updated angle data member. To be useful for positioning a camera, we'll add an accessor method that returns the current world coordinates of our orbiting position. The class definition is as follows:

    class orbit : public osg::NodeCallback 
    {
    public:
       orbit(): heading(M_PI/2.0) {} 

       osg::Matrix getWCMatrix(){return worldCoordMatrix;} 

       virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
       { 
          osg::MatrixTransform *tx = dynamic_cast<osg::MatrixTransform *>(node);
          if( tx != NULL )
          {
             heading += M_PI/180.0;
             osg::Matrixd orbitRotation;
             orbitRotation.makeRotate( 
                osg::DegreesToRadians(-10.0), osg::Vec3(0,1,0), // roll
                osg::DegreesToRadians(-20.0), osg::Vec3(1,0,0) , // pitch
                heading, osg::Vec3(0, 0, 1) ); // heading
             osg::Matrixd orbitTranslation;
             orbitTranslation.makeTranslate( 0,-40, 4 );
             tx->setMatrix ( orbitTranslation * orbitRotation);
             worldCoordMatrix = osg::computeLocalToWorld( nv->getNodePath() );
          }
          traverse(node, nv);
       }
    private:
       osg::Matrix worldCoordMatrix;
       float heading;
    };

To use this callback we need to add a matrix transform to our scene and make its update callback an instance or our 'orbit' class. To use the world coordinates of this matrix to position a camera, we'll rely on code for the osgManualCamera tutorial. We'll also use code from the keyboard interface class to add a function that updates a global variable that allows users to toggle default and 'orbiting' viewpoints.

    int main()
    {
       osg::Node* groundNode = NULL;
       osg::Node* tankNode = NULL;
       osg::Group* root = NULL;
       osgProducer::Viewer viewer;
       osg::PositionAttitudeTransform* tankXform = NULL;

       groundNode = osgDB::readNodeFile("\\Models\\JoeDirt\\JoeDirt.flt");
       tankNode = osgDB::readNodeFile("\\Models\\T72-Tank\\T72-tank_des.flt");

       root = new osg::Group();

    // Create green Irish sky
       osg::ClearNode* backdrop = new osg::ClearNode;
       backdrop->setClearColor(osg::Vec4(0.0f,0.8f,0.0f,1.0f));
       root->addChild(backdrop);

       tankXform = new osg::PositionAttitudeTransform();
       root->addChild(groundNode);
       root->addChild(tankXform);
       tankXform->addChild(tankNode);
       tankXform->setPosition( osg::Vec3(10,10,8) );
       tankXform->setAttitude( 
          osg::Quat(osg::DegreesToRadians(-45.0), osg::Vec3(0,0,1) ) );

       viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
       viewer.setSceneData( root );
       viewer.realize();

    // Create a matrix transform that will circle the tank node 
       osg::MatrixTransform* orbitTankXForm = new osg::MatrixTransform();
    // Create an orbit callback instance 
       orbit* tankOrbitCallback = new orbit();
    // Make this callback the update callback for our transform node 
       orbitTankXForm->setUpdateCallback( tankOrbitCallback );
    // Make this orbit relative to the position of the tank by adding it
    // as a child of the tank's transform node. 
       tankXform->addChild(orbitTankXForm);

       keyboardEventHandler* keh = new keyboardEventHandler();
       keh->addFunction('v',toggleTankOrbiterView);
       viewer.getEventHandlerList().push_front(keh); 

       while( !viewer.done() )
       {
          viewer.sync();
          viewer.update();

          if (useTankOrbiterView)
          { 
             osg::Matrixd m = tankOrbitCallback->getWCMatrix();
             osg::Matrixd i = m.inverse(m);
             viewer.setViewByMatrix( 
                Producer::Matrix( i.ptr() )*
                Producer::Matrix::rotate( osg::DegreesToRadians(-90.0), 1, 0, 0 ) );
          }

          viewer.frame();
       }
       return 0;
    }

Note: Pressing the 'v' key switches between views.