VR-Forces 4.2 Class Documentation
exampleOsgArticulation

Table of Contents

Overview

This example shows how to create an OpenSceneGraph application. The example loads a geometry file in OpenFlight format, locates two degree-of-freedom nodes within the geometry, then applies an update-callback to each node. As the scene is rendered, the update-callbacks articulate the geometry at those nodes. Of particular interest in this example is how to load a geometry file from disk, how the nodes are found within the model after it has been loaded, and how the update-callbacks animate the geometry.

Above the OpenGL layer is OpenSceneGraph, an API that provides scene organization functions as well as data management, LOD management, file loaders and other much more. With OSG we organize 3D geometry into a "scene graph", and provides functions for traversing the graph and making the appropriate calls to OpenGL to render the scene.

Example details

Generally loading a file from disk involves a single call to the osgDB library with the name of the file. If successful the call will return a pointer to the root node of the geometry. If unsuccessful, a NULL pointer is returned. The user should validate the pointer before proceeding.

osg::ref_ptr<osg::Node> root = osgDB::readNodeFile(dataFile);
if (! root.valid())
{
// Failed to load the file,
// so create a bogus root-node to display nothing.
osg::notify(osg::FATAL) << "Cannot load " << dataFile << std::endl;
root = new osg::Group();
}

The desired degree-of-freedom nodes are found using the visitor pattern. When loaded, the geometry is a hierarchy of nodes connected in parent-child fashion. A visitor (in this case an osg::NodeVisitor) is an object that traverses the hierarchy from the point that it is applied to the graph. As it traverses each node in the hierarchy, it calls a known function to 'visit' the node. The node being visited is always passed into the function, and what the function does is user defined. For instance, one of the standard visitors in OpenSceneGraph performs updates on the graph prior to rendering each frame. The osgUtil::UpdateVisitor performs several tasks, one of which checks each node being visited for an osg::NodeCallback functor object. If the node being visited has a pointer to a functor, the visitor will call the function before visiting the next node.

In this tutorial, the application creates a custom visitor to traverse the scene-graph looking for nodes of the type osgSim::DOFTransform. For each osgSim::DOFTransform found, the visitor saves a pointer to the node in a list for later processing. After the visitor is finished, it contains a list of all osgSim::DOFTransform nodes in the model. The application will select two of those osgSim::DOFTransform nodes by name as the 'articulation' nodes and insert a custom osg::NodeCallback to each.

To create a custom visitor, derive a new class from osg::NodeVisitor and over-write the apply() function. The apply() function is the known function that 'visits' each node.

class DOFFinder : public osg::NodeVisitor
{
public:
virtual void apply(osg::Node& node);
protected:
osg::NodeList myNodeList;
};

In this application it simply saves a pointer to the node in a list if that node is of type osgSim::DOFTransform.

void DOFFinder::apply(osg::Node& node)
{
osgSim::DOFTransform* dof(dynamic_cast<osgSim::DOFTransform*>(&node));
if (dof)
{
myNodeList.push_back(dof);
}
traverse(node);

It is important in the apply() function to always call the base class traverse() function, as this kicks the visitor to the next node in the hierarchy.

To use the visitor, simply create one and select a node in the hierarchy to accept the visitor and start its traversal through the graph.

osg::ref_ptr<DOFFinder> df = new DOFFinder();
root->accept(*df);

The last interesting point to this tutorial is how the articulation nodes are actually... well... articulated. This application defines two custom osg::NodeCallback objects, each one specific to the node to which it is applied. These osg::NodeCallback objects cause their nodes to perform transformations on the children of the node.

The steps that update each transform node is as follows: The update osg::NodeVisitor visits the osgSim::DOFTransform node, the visitor checks if the node has an osg::NodeCallback, if so the visitor calls the callback. When the callback is called it checks if the node is a osgSim::DOFTransform, if so the callback calls rotation functions on the node. When all is finished the visitor moves on to visit another node. It is important to note that nodes have several different types of callbacks and this application is only using one, the update callback.

To create a custom callback, derive a new class from osg::NodeCallback and over-write the base class functor operator(). This is the function that is called every rendering frame.

class RotateTurret : public osg::NodeCallback
{
public:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
protected:
double mySimTime;
};

class RotateBarrel : public osg::NodeCallback
{
public:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
protected:
double myMaxAngle;
};

This application has two callbacks, one that rotates an osgSim::DOFTransform in a circle, the other pitches an osgSim::DOFTransform up and down over time.

void RotateTurret::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osgSim::DOFTransform* dof = dynamic_cast<osgSim::DOFTransform*>(node);
if (dof)
{
double simTime = nv->getFrameStamp()->getSimulationTime();
double deltaTime = simTime - mySimTime;
mySimTime = simTime;
osg::Vec3 hpr = dof->getCurrentHPR();
hpr[0] += deltaTime * myRadsPerSecond;
dof->setCurrentHPR(hpr);
}
// Always call base class traverse to handle nested callbacks.
traverse(node, nv);
}

void RotateBarrel::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osgSim::DOFTransform* dof = dynamic_cast<osgSim::DOFTransform*>(node);
if (dof)
{
double simTime = nv->getFrameStamp()->getSimulationTime();
double angle = ((sin(simTime) + 1.0f) * 0.5f) * myMaxAngle;
osg::Vec3 hpr = dof->getCurrentHPR();
hpr[1] = angle;
dof->setCurrentHPR( hpr );
}
// Always call base class traverse to handle nested callbacks.
traverse(node, nv);
}

It is possible to nest osg::NodeCallbacks so that multiple callbacks exist on a single node. Therefore the function should always call the base class traverse() function to kick the visitor to the next nested callback.

To use the callback, simply create one and insert it into the node to be updated.

if (dof->getName() == std::string("TURRET"))
{
dof->setUpdateCallback(new RotateTurret());
}
else if (dof->getName() == std::string("BARREL"))
{
dof->setUpdateCallback(new RotateBarrel());
}

Building the Example

VR-Vantage includes pre-built versions of the example application. To build it yourself, follow the instructions at Building VR-Vantage Examples, Applications, and Plug-ins.

Running the Example

This example is an application. You can run it by running ./bin/exampleOsgArticulation.exe (on Windows) or ./bin/exampleOsgArticulation (on Linux). For more information about running examples, please see Running Applications and Examples.

Learn More

Example Source Files


exampleOsgArticulation.cxx

/******************************************************************************
** Copyright (c) 2012 MAK Technologies, Inc.
** All rights reserved.
******************************************************************************/
// This application loads a geometry file in OpenFlight format, locates two
// degree-of-freedom nodes within the geometry, then applies an update-callback
// to each node. As the scene is rendered, the update-callbacks articulate the
// geometry at those nodes. Of particular interest in this example application
// is how to load a geometry file from disk, how the nodes are found within the
// model after it has been loaded, and how the update-callbacks animate the
// geometry.
#include <string>
#include <osg/Node>
#include <osg/Notify>
#include <osgDB/ReadFile>
#include <osgSim/DOFTransform>
#include <osgViewer/Viewer>
// Relative path and name for the geometry file that will be loaded.
// This path is relative to the solution's 'bin' directory where the
// application should be run. This file should contain two DOFTransform
// nodes named 'TURRET' and 'BARREL' representing parts of a tank. These
// are the nodes that will be manipulated to articulate the geometry.
static const std::string dataPath("../data/Vehicles/Tracked/");
static const std::string M1A2("M1A2/M1A2.medf");
static const std::string M1A2_Abrams("M1A2_Abrams/M1A2_Abrams.medf");
static const std::string M1A2_Desert("M1A2_DESERT_V7.0.flt/DB_M1A2_DESERT_V7.0.medf");
static const std::string dataFile(dataPath + M1A2_Desert);
int main()
{
// The root Node of the scene is the root of the geometry file we load.
osg::notify(osg::ALWAYS) << "Loading " << dataFile << std::endl;
osg::notify(osg::ALWAYS)
<< "This large file could take a minute to load" << std::endl;
osg::ref_ptr<osg::Node> root = osgDB::readNodeFile(dataFile);
if (! root.valid())
{
// Failed to load the file,
// so create a bogus root-node to display nothing.
osg::notify(osg::FATAL) << "Cannot load " << dataFile << std::endl;
root = new osg::Group();
}
// Send a visitor down the graph to locate all the DOFTransform nodes.
osg::notify(osg::ALWAYS) << "Looking for DOF nodes" << std::endl;
osg::ref_ptr<DOFFinder> df = new DOFFinder();
root->accept(*df);
if (! df->dofCount())
{
osg::notify(osg::FATAL) << "Cannot find any DOF nodes" << std::endl;
}
// For the turret and barrel, attach their own unique update-callback.
osg::ref_ptr<osgSim::DOFTransform> dof = df->takeNextDOF();
while (dof.valid())
{
if (dof->getName() == std::string("TURRET"))
{
dof->setUpdateCallback(new RotateTurret());
}
else if (dof->getName() == std::string("BARREL"))
{
dof->setUpdateCallback(new RotateBarrel());
}
dof = df->takeNextDOF();
}
// Create a Viewer to display the scene.
osgViewer::Viewer viewer;
// The final step is to set up and enter a simulation loop.
// Add the root of the scene to the viewer and start rendering.
viewer.setSceneData(root);
viewer.setUpViewInWindow(100, 100, 640, 480);
osg::notify(osg::ALWAYS) << "Start running display" << std::endl;
viewer.run();
return 0;
}

osgArticulationUtilities.h

/******************************************************************************
** Copyright (c) 2012 MAK Technologies, Inc.
** All rights reserved.
******************************************************************************/
#pragma once
#include <osg/Group>
#include <osg/NodeCallback>
#include <osg/NodeVisitor>
namespace osgSim { class DOFTransform; }
class DOFFinder : public osg::NodeVisitor
{
public:
virtual void apply(osg::Node& node);
osgSim::DOFTransform* takeNextDOF();
int dofCount() const;
protected:
virtual ~DOFFinder();
protected:
osg::NodeList myNodeList;
};
class RotateTurret : public osg::NodeCallback
{
public:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
protected:
double mySimTime;
};
class RotateBarrel : public osg::NodeCallback
{
public:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
protected:
double myMaxAngle;
};

osgArticulationUtilities.cxx

/******************************************************************************
** Copyright (c) 2012 MAK Technologies, Inc.
** All rights reserved.
******************************************************************************/
#include <cmath>
#include <osg/Node>
#include <osg/Notify>
#include <osg/Vec3>
#include <osgSim/DOFTransform>
: osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
, myNodeList()
{}
{
myNodeList.clear();
}
void DOFFinder::apply(osg::Node& node)
{
osgSim::DOFTransform* dof(dynamic_cast<osgSim::DOFTransform*>(&node));
if (dof)
{
myNodeList.push_back(dof);
osg::notify(osg::ALWAYS)
<< "Found DOF '" << dof->getName() << "'" << std::endl;
}
// Keep traversing the rest of the scene graph.
traverse(node);
}
osgSim::DOFTransform* DOFFinder::takeNextDOF()
{
if (dofCount() <= 0)
{
return 0;
}
osgSim::DOFTransform* dof =
dynamic_cast<osgSim::DOFTransform*>(myNodeList.back().get());
myNodeList.pop_back();
// Note: For the purpose of this example we are assuming removal from
// the node-list does not cause ref-count deletion since the node
// should still exist in the scene-graph. Real world users should
// probably validate the ref-count before returning an invalid pointer.
return dof;
}
{
return myNodeList.size();
}
: osg::NodeCallback()
, myRadsPerSecond(osg::DegreesToRadians(15.0f))
, mySimTime(0.0f)
{}
void RotateTurret::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osgSim::DOFTransform* dof = dynamic_cast<osgSim::DOFTransform*>(node);
if (dof)
{
double simTime = nv->getFrameStamp()->getSimulationTime();
double deltaTime = simTime - mySimTime;
mySimTime = simTime;
osg::Vec3 hpr = dof->getCurrentHPR();
hpr[0] += deltaTime * myRadsPerSecond;
dof->setCurrentHPR(hpr);
}
// Always call base class traverse to handle nested callbacks.
traverse(node, nv);
}
: osg::NodeCallback()
, myMaxAngle(osg::DegreesToRadians(60.0f))
{}
void RotateBarrel::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osgSim::DOFTransform* dof = dynamic_cast<osgSim::DOFTransform*>(node);
if (dof)
{
double simTime = nv->getFrameStamp()->getSimulationTime();
double angle = ((sin(simTime) + 1.0f) * 0.5f) * myMaxAngle;
osg::Vec3 hpr = dof->getCurrentHPR();
hpr[1] = angle;
dof->setCurrentHPR( hpr );
}
// Always call base class traverse to handle nested callbacks.
traverse(node, nv);
}

Document ID: Generated on Sun Nov 24 19:49:21 EST 2013 from SVN revision 133924
Copyright © 2005-2013 VT MÄK. All Rights Reserved (www.mak.com)