Table of Contents
Overview
This tutorial shows how to create an OpenSceneGraph application. This particular example demonstrates how to create geometry from raw data such as vertex coordinates and colors, then articulate the geometry using transformations.
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
The example creates three separate pieces of geometry for display; a flat polygon representing a ground-plane, a cube and a pyramid. When run, the cube should sit on top of the ground plane with the pyramid sitting on top of the cube. Both the cube and the pyramid revolve in a circle around the center of the scene, and the pyramid rotates around its own axis, spinning on top of the cube.
Of particularly interest in this tutorial is that the three pieces of geometry are all created in the same space, at the center of the sceen. The polygon, the cube and the pyramid all have their local origins at the same location. Even though the three objects are 'modeled' in the same space, they are separated and positioned in the scene using osg::PositionAttitudeTransform nodes. The animation of the geometry is achieved by inserting osg::NodeCallback objects into some of the transform nodes. With each rendered frame the osg::NodeCallback updates the transformation that it is attached to.
The first thing todo is to create the geometry. To make the code neatly organized, three functions are defined to create the separate pieces of geometry. Each function builds the geometry around the origin and returns an osg::Geode pointer to the root of the respective geometry.
osg::Geode* makeCube()
osg::Geode* makePyramid()
osg::Geode* makePolygon()
Each of the three functions are structured the same. First they create a small scene-graph branch with an osg::Geometry object (derived from osg::Drawable) at the leaf.
osg::Geode* geode = new osg::Geode();
osg::Geometry* geom = new osg::Geometry();
geode->addDrawable(geom);
Next the vertices are added to the osg::Geometry.
osg::Vec3Array* verts = new osg::Vec3Array;
verts->push_back( osg::Vec3(-0.5f, -0.5f, -0.5f) );
verts->push_back( osg::Vec3( 0.5f, -0.5f, -0.5f) );
verts->push_back( osg::Vec3( 0.5f, 0.5f, -0.5f) );
verts->push_back( osg::Vec3(-0.5f, 0.5f, -0.5f) );
verts->push_back( osg::Vec3(-0.5f, -0.5f, 0.5f) );
verts->push_back( osg::Vec3( 0.5f, -0.5f, 0.5f) );
verts->push_back( osg::Vec3( 0.5f, 0.5f, 0.5f) );
verts->push_back( osg::Vec3(-0.5f, 0.5f, 0.5f) );
geom->setVertexArray(verts);
The actual faces of the geometry are defined by the order of vertices.
osg::DrawElementsUInt* face =
new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(3);
face->push_back(2);
face->push_back(1);
face->push_back(0);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(4);
face->push_back(5);
face->push_back(6);
face->push_back(7);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(3);
face->push_back(0);
face->push_back(4);
face->push_back(7);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(1);
face->push_back(2);
face->push_back(6);
face->push_back(5);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(0);
face->push_back(1);
face->push_back(5);
face->push_back(4);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(2);
face->push_back(3);
face->push_back(7);
face->push_back(6);
geom->addPrimitiveSet(face);
Last the colors for each face is defined.
osg::Vec4Array* colors = new osg::Vec4Array;
colors->push_back( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) );
colors->push_back( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) );
colors->push_back( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) );
colors->push_back( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) );
colors->push_back( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) );
colors->push_back( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) );
geom->setColorArray(colors);
geom->setColorBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
Along with creating the geometry a custom osg::NodeCallback is defined to update a transform node by rotating it about the 'Z' axis. osg::NodeCallbacks are functor objects that get inserted into nodes. During rendering updates of the scene-graph the osg::NodeCallback is called and given a chance to update or change the structure of the scene-graph.
class RotationCallback : public osg::NodeCallback
{
public:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
#ifndef _WIN32
msleep((unsigned long)((1.0 / 36.0) * 1000.0));
#endif
osg::PositionAttitudeTransform* pat =
dynamic_cast<osg::PositionAttitudeTransform*>(node);
if ( pat ) {
pat->setAttitude( osg::Quat( osg::DegreesToRadians(angle), osg::Vec3(0.0f, 0.0f, 1.0f) ) );
angle += 1.0f;
}
traverse(node, nv);
}
private:
};
In the main function a root for the entire scene-graph is created.
osg::Group* root = new osg::Group();
Then the structure of the scene-graph is assembled by creating osg::PositionAttitudeTransform nodes and attaching them in parent-child relation ships. All the nodes are ultimately made decendants of the root node.
osg::PositionAttitudeTransform* moveDown =
new osg::PositionAttitudeTransform();
moveDown->setPosition( osg::Vec3( 0.0f, 0.0f, -0.5f) );
osg::PositionAttitudeTransform* moveToSide =
new osg::PositionAttitudeTransform();
moveToSide->setPosition( osg::Vec3(-4.0f, 0.0f, 0.0f) );
osg::PositionAttitudeTransform* moveUp =
new osg::PositionAttitudeTransform();
moveUp->setPosition( osg::Vec3( 0.0f, 0.0f, 1.0f) );
osg::PositionAttitudeTransform* revolution =
new osg::PositionAttitudeTransform();
revolution->setUpdateCallback( new RotationCallback() );
moveDown->addChild( makePolygon() );
moveUp->addChild( makePyramid() );
moveUp->setUpdateCallback( new RotationCallback() );
moveToSide->addChild( makeCube() );
moveToSide->addChild( moveUp );
revolution->addChild( moveToSide );
root->addChild( moveDown );
root->addChild( revolution );
Unlike OpenGL where the order of the transformation calls determin how the geometry is transformed, in OpenSceneGraph it is the hierarchy structure that determins the transformations. That is, making a translation node a child of a rotation node is analogous to calling glRotatef then glTranslatef. With respect to transformations, the order of the parent child relationships matters in OpenSceneGraph, whereas the order of function calls is important in OpenGL.
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/exampleOsgTransforms.exe (on Windows) or ./bin/exampleOsgTransforms (on Linux). For more information about running examples, please see Running Applications and Examples.
Learn More
Example Source Files
exampleOsgTransforms.cxx
#ifndef _WIN32
#include <cerrno>
#include <time.h>
#endif
#include <osg/Group>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/Node>
#include <osg/NodeCallback>
#include <osg/PositionAttitudeTransform>
#include <osgUtil/SmoothingVisitor>
#include <osgViewer/Viewer>
osg::Geode* makeCube()
{
osg::Geode* geode = new osg::Geode();
osg::Geometry* geom = new osg::Geometry();
geode->addDrawable(geom);
osg::Vec3Array* verts = new osg::Vec3Array;
verts->push_back( osg::Vec3(-0.5f, -0.5f, -0.5f) );
verts->push_back( osg::Vec3( 0.5f, -0.5f, -0.5f) );
verts->push_back( osg::Vec3( 0.5f, 0.5f, -0.5f) );
verts->push_back( osg::Vec3(-0.5f, 0.5f, -0.5f) );
verts->push_back( osg::Vec3(-0.5f, -0.5f, 0.5f) );
verts->push_back( osg::Vec3( 0.5f, -0.5f, 0.5f) );
verts->push_back( osg::Vec3( 0.5f, 0.5f, 0.5f) );
verts->push_back( osg::Vec3(-0.5f, 0.5f, 0.5f) );
geom->setVertexArray(verts);
osg::DrawElementsUInt* face =
new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(3);
face->push_back(2);
face->push_back(1);
face->push_back(0);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(4);
face->push_back(5);
face->push_back(6);
face->push_back(7);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(3);
face->push_back(0);
face->push_back(4);
face->push_back(7);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(1);
face->push_back(2);
face->push_back(6);
face->push_back(5);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(0);
face->push_back(1);
face->push_back(5);
face->push_back(4);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(2);
face->push_back(3);
face->push_back(7);
face->push_back(6);
geom->addPrimitiveSet(face);
osg::Vec4Array* colors = new osg::Vec4Array;
colors->push_back( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) );
colors->push_back( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) );
colors->push_back( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) );
colors->push_back( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) );
colors->push_back( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) );
colors->push_back( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) );
geom->setColorArray(colors);
geom->setColorBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
return geode;
}
osg::Geode* makePyramid()
{
osg::Geode* geode = new osg::Geode();
osg::Geometry* geom = new osg::Geometry();
geode->addDrawable(geom);
osg::Vec3Array* verts = new osg::Vec3Array;
verts->push_back( osg::Vec3(-0.5f, -0.5f, -0.5f) );
verts->push_back( osg::Vec3( 0.5f, -0.5f, -0.5f) );
verts->push_back( osg::Vec3( 0.5f, 0.5f, -0.5f) );
verts->push_back( osg::Vec3(-0.5f, 0.5f, -0.5f) );
verts->push_back( osg::Vec3( 0.0f, 0.0f, 0.5f) );
geom->setVertexArray(verts);
osg::DrawElementsUInt* face =
new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(3);
face->push_back(2);
face->push_back(1);
face->push_back(0);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
face->push_back(3);
face->push_back(0);
face->push_back(4);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
face->push_back(1);
face->push_back(2);
face->push_back(4);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
face->push_back(0);
face->push_back(1);
face->push_back(4);
geom->addPrimitiveSet(face);
face = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
face->push_back(2);
face->push_back(3);
face->push_back(4);
geom->addPrimitiveSet(face);
osg::Vec4Array* colors = new osg::Vec4Array;
colors->push_back( osg::Vec4(1.0f, 1.0f, 0.0f, 1.0f) );
colors->push_back( osg::Vec4(0.0f, 1.0f, 1.0f, 1.0f) );
colors->push_back( osg::Vec4(0.0f, 1.0f, 1.0f, 1.0f) );
colors->push_back( osg::Vec4(1.0f, 0.0f, 1.0f, 1.0f) );
colors->push_back( osg::Vec4(1.0f, 0.0f, 1.0f, 1.0f) );
geom->setColorArray(colors);
geom->setColorBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
return geode;
}
osg::Geode* makePolygon()
{
osg::Geode* geode = new osg::Geode();
osg::Geometry* geom = new osg::Geometry();
geode->addDrawable(geom);
osg::Vec3Array* verts = new osg::Vec3Array;
verts->push_back( osg::Vec3(-5.0f, -5.0f, 0.0f) );
verts->push_back( osg::Vec3( 5.0f, -5.0f, 0.0f) );
verts->push_back( osg::Vec3( 5.0f, 5.0f, 0.0f) );
verts->push_back( osg::Vec3(-5.0f, 5.0f, 0.0f) );
geom->setVertexArray(verts);
osg::DrawElementsUInt* face =
new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
face->push_back(0);
face->push_back(1);
face->push_back(2);
face->push_back(3);
geom->addPrimitiveSet(face);
osg::Vec4Array* colors = new osg::Vec4Array;
colors->push_back( osg::Vec4(0.0f, 0.5f, 0.0f, 1.0f) );
geom->setColorArray(colors);
geom->setColorBinding(osg::Geometry::BIND_OVERALL);
return geode;
}
#ifndef _WIN32
void msleep(unsigned long msec)
{
struct timespec req = {0};
time_t sec = (
int)(msec / 1000.0);
msec = msec - sec * 1000;
req.tv_sec = sec;
req.tv_nsec = msec * 1000000L;
while (nanosleep(&req, &req) == -1 && errno == EINTR) {}
}
#endif
class RotationCallback : public osg::NodeCallback
{
public:
RotationCallback()
, angle(0.0f)
{}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
#ifndef _WIN32
msleep((unsigned long)((1.0 / 36.0) * 1000.0));
#endif
osg::PositionAttitudeTransform* pat =
dynamic_cast<osg::PositionAttitudeTransform*>(node);
if ( pat ) {
pat->setAttitude( osg::Quat( osg::DegreesToRadians(angle), osg::Vec3(0.0f, 0.0f, 1.0f) ) );
angle += 1.0f;
}
traverse(node, nv);
}
private:
};
int main()
{
osg::Group* root = new osg::Group();
osg::PositionAttitudeTransform* moveDown =
new osg::PositionAttitudeTransform();
moveDown->setPosition( osg::Vec3( 0.0f, 0.0f, -0.5f) );
osg::PositionAttitudeTransform* moveToSide =
new osg::PositionAttitudeTransform();
moveToSide->setPosition( osg::Vec3(-4.0f, 0.0f, 0.0f) );
osg::PositionAttitudeTransform* moveUp =
new osg::PositionAttitudeTransform();
moveUp->setPosition( osg::Vec3( 0.0f, 0.0f, 1.0f) );
osg::PositionAttitudeTransform* revolution =
new osg::PositionAttitudeTransform();
revolution->setUpdateCallback( new RotationCallback() );
moveDown->addChild( makePolygon() );
moveUp->addChild( makePyramid() );
moveUp->setUpdateCallback( new RotationCallback() );
moveToSide->addChild( makeCube() );
moveToSide->addChild( moveUp );
revolution->addChild( moveToSide );
root->addChild( moveDown );
root->addChild( revolution );
root->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
osgViewer::Viewer viewer;
viewer.setSceneData(root);
viewer.setUpViewInWindow(100, 100, 640, 480);
viewer.run();
return 0;
}