VR-Forces 4.6 Class Documentation
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
exampleOsgTransforms

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) ); // 0 left front bottom
verts->push_back( osg::Vec3( 0.5f, -0.5f, -0.5f) ); // 1 right front bottom
verts->push_back( osg::Vec3( 0.5f, 0.5f, -0.5f) ); // 2 right back bottom
verts->push_back( osg::Vec3(-0.5f, 0.5f, -0.5f) ); // 3 left back bottom
verts->push_back( osg::Vec3(-0.5f, -0.5f, 0.5f) ); // 4 left front top
verts->push_back( osg::Vec3( 0.5f, -0.5f, 0.5f) ); // 5 right front top
verts->push_back( osg::Vec3( 0.5f, 0.5f, 0.5f) ); // 6 right back top
verts->push_back( osg::Vec3(-0.5f, 0.5f, 0.5f) ); // 7 left back top
geom->setVertexArray(verts);

The actual faces of the geometry are defined by the order of vertices.

// 0 bottom face
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);
// 1 top 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);
// 2 left 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);
// 3 right 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);
// 4 front 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);
// 5 back 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) ); // 0 red - bottom
colors->push_back( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); // 1 red - top
colors->push_back( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); // 2 green - left
colors->push_back( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); // 3 green - right
colors->push_back( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); // 4 blue - front
colors->push_back( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); // 5 blue - back
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
// On Linux, only update at 36 frames per second.
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;
}
// Always call base class traverse to send the visitor on its way.
traverse(node, nv);
}
private:
float angle;
};

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

/******************************************************************************
** Copyright (c) 2014 MAK Technologies, Inc.
** All rights reserved.
******************************************************************************/
// This particular application demonstrates how to create geometry from raw
// data such as vertex coordinates and colors, then articulate the geometry
// using transformations. The application 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 an circle around the center of the scene, and the pyramid rotates
// around its own axis, spinning on top of the cube.
#ifndef _WIN32
// On Linux we need to regulate the rendering speed of rotation.
#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>
// Create a small scene-graph branch containing a cube centered at the origin.
osg::Geode* makeCube()
{
// The Drawable geometry is held under Geode objects.
osg::Geode* geode = new osg::Geode();
osg::Geometry* geom = new osg::Geometry();
// Associate the Geometry with the Geode.
geode->addDrawable(geom);
// Declare an array of vertices. Each vertex will be represented by a
// triplet. An instance of Vec3Array is be used to store these triplets.
// Using a right-handed coordinate system with 'z' up, and looking up the
// positive 'y' axis with 'x' to the right, array elements zero..seven
// below represent the 8 points required to create a simple cube.
osg::Vec3Array* verts = new osg::Vec3Array;
verts->push_back( osg::Vec3(-0.5f, -0.5f, -0.5f) ); // 0 left front bottom
verts->push_back( osg::Vec3( 0.5f, -0.5f, -0.5f) ); // 1 right front bottom
verts->push_back( osg::Vec3( 0.5f, 0.5f, -0.5f) ); // 2 right back bottom
verts->push_back( osg::Vec3(-0.5f, 0.5f, -0.5f) ); // 3 left back bottom
verts->push_back( osg::Vec3(-0.5f, -0.5f, 0.5f) ); // 4 left front top
verts->push_back( osg::Vec3( 0.5f, -0.5f, 0.5f) ); // 5 right front top
verts->push_back( osg::Vec3( 0.5f, 0.5f, 0.5f) ); // 6 right back top
verts->push_back( osg::Vec3(-0.5f, 0.5f, 0.5f) ); // 7 left back top
// Associate this set of vertices with the Geometry.
geom->setVertexArray(verts);
// Next, create primitive sets and add them to the Geometry. Using
// four points at a time, define the faces with an instance of the
// DrawElementsUint class. To ensure proper backface cullling, vertices
// should be specified in counter-clockwise order. The arguments for
// the constructor are the enumerated type for the primitive (same as
// the OpenGL primitive enumerated types), and the index in the vertex
// array to start from. Since the Geometry will manage the array of
// DrawElementsUInt objects we do not worry about cleanup during shut-
// down.
// 0 bottom face
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);
// 1 top 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);
// 2 left 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);
// 3 right 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);
// 4 front 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);
// 5 back 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);
// Separate colors for the Geometry can be bound to the vertices
// of the object, each primitive of the object, or one color for
// the entire object. We'll declare and load an array of Vec4
// elements to store colors for each primitive (face). Notice that
// the order of the colors added to the array is the same order that
// the faces were added to the geometry as primitive-sets.
osg::Vec4Array* colors = new osg::Vec4Array;
colors->push_back( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); // 0 red - bottom
colors->push_back( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); // 1 red - top
colors->push_back( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); // 2 green - left
colors->push_back( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); // 3 green - right
colors->push_back( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); // 4 blue - front
colors->push_back( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); // 5 blue - back
// The next step is to associate the array of colors with the geometry.
// Assign the color indices created above to the geometry and set the
// binding mode to _PER_PRIMITIVE_SET.
geom->setColorArray(colors);
geom->setColorBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
// return the geode as the root of this geometry.
return geode;
}
// Create a small scene-graph branch containing
// a pyramid centered at the origin.
osg::Geode* makePyramid()
{
// The Drawable geometry is held under Geode objects.
osg::Geode* geode = new osg::Geode();
osg::Geometry* geom = new osg::Geometry();
// Associate the Geometry with the Geode.
geode->addDrawable(geom);
// Declare an array of vertices to create a simple pyramid.
osg::Vec3Array* verts = new osg::Vec3Array;
verts->push_back( osg::Vec3(-0.5f, -0.5f, -0.5f) ); // 0 left front base
verts->push_back( osg::Vec3( 0.5f, -0.5f, -0.5f) ); // 1 right front base
verts->push_back( osg::Vec3( 0.5f, 0.5f, -0.5f) ); // 2 right back base
verts->push_back( osg::Vec3(-0.5f, 0.5f, -0.5f) ); // 3 left back base
verts->push_back( osg::Vec3( 0.0f, 0.0f, 0.5f) ); // 4 peak
// Associate this set of vertices with the Geometry.
geom->setVertexArray(verts);
// Next, create primitive sets and add them to the Geometry.
// Each primitive set represents one face of the pyramid.
// 0 base
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);
// 1 left face
face = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
face->push_back(3);
face->push_back(0);
face->push_back(4);
geom->addPrimitiveSet(face);
// 2 right face
face = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
face->push_back(1);
face->push_back(2);
face->push_back(4);
geom->addPrimitiveSet(face);
// 3 front face
face = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
face->push_back(0);
face->push_back(1);
face->push_back(4);
geom->addPrimitiveSet(face);
// 4 back face
face = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
face->push_back(2);
face->push_back(3);
face->push_back(4);
geom->addPrimitiveSet(face);
// Create a separate color for each face.
osg::Vec4Array* colors = new osg::Vec4Array;
colors->push_back( osg::Vec4(1.0f, 1.0f, 0.0f, 1.0f) ); // yellow - base
colors->push_back( osg::Vec4(0.0f, 1.0f, 1.0f, 1.0f) ); // cyan - left
colors->push_back( osg::Vec4(0.0f, 1.0f, 1.0f, 1.0f) ); // cyan - right
colors->push_back( osg::Vec4(1.0f, 0.0f, 1.0f, 1.0f) ); // magenta - front
colors->push_back( osg::Vec4(1.0f, 0.0f, 1.0f, 1.0f) ); // magenta - back
// The next step is to associate the array of colors with the geometry.
// Assign the color indices created above to the geometry and set the
// binding mode to _PER_PRIMITIVE_SET.
geom->setColorArray(colors);
geom->setColorBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
// return the geode as the root of this geometry.
return geode;
}
// Create a small scene-graph branch containing
// a ground polygon centered at the origin.
osg::Geode* makePolygon()
{
// The Drawable geometry is held under Geode objects.
osg::Geode* geode = new osg::Geode();
osg::Geometry* geom = new osg::Geometry();
// Associate the Geometry with the Geode.
geode->addDrawable(geom);
// Declare an array of vertices to create a simple polygon.
osg::Vec3Array* verts = new osg::Vec3Array;
verts->push_back( osg::Vec3(-5.0f, -5.0f, 0.0f) ); // 0 left front
verts->push_back( osg::Vec3( 5.0f, -5.0f, 0.0f) ); // 1 right front
verts->push_back( osg::Vec3( 5.0f, 5.0f, 0.0f) ); // 2 right back
verts->push_back( osg::Vec3(-5.0f, 5.0f, 0.0f) ); // 3 left back
// Associate this set of vertices with the Geometry.
geom->setVertexArray(verts);
// Next, create a primitive set and add it to the Geometry as a polygon.
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);
// Create a color for the polygon.
osg::Vec4Array* colors = new osg::Vec4Array;
colors->push_back( osg::Vec4(0.0f, 0.5f, 0.0f, 1.0f) ); // dark green
// The next step is to associate the array of colors with the geometry.
// Assign the color indices created above to the geometry and set the
// binding mode to _OVERALL.
geom->setColorArray(colors);
geom->setColorBinding(osg::Geometry::BIND_OVERALL);
// Return the geode as the root of this geometry.
return geode;
}
#ifndef _WIN32
// Helper function used to regulate rendering rates for rotation on Linux.
// This function sleeps the thread for the given amount of milliseconds.
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
// This update callback is used to animate a objects by rotating them over
// time. During each update, this callback increments the angle of a
// which rotates about the 'Z' axis.
class RotationCallback : public osg::NodeCallback
{
public:
// Default constructor.
RotationCallback()
: osg::NodeCallback()
, angle(0.0f)
{}
// This updater function rotates the geometry by incrementing the current
// angle by one degree each rendering update.
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
#ifndef _WIN32
// On Linux, only update at 36 frames per second.
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;
}
// Always call base class traverse to send the visitor on its way.
traverse(node, nv);
}
private:
float angle;
};
// The main function constructs a scene-graph of geometry objects, placing
// the objects with transformation nodes, sets up update animation callbacks
// then starts the rendering loop.
int main()
{
// The root Node of the scene.
// Groups are both Nodes and containers for other Nodes.
osg::Group* root = new osg::Group();
// The geometry objects are oriented and positioned in the scene by
// transformation nodes. Create the geometry for each object, associate
// the geometry with their respective transformation nodes, then associate
// the transformation nodes with the scene root.
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 );
// switch off lighting because we did not define normals on the geometry.
root->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
// 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);
viewer.run();
return 0;
}

Document ID: Generated on Thu Apr 12 03:15:37 EDT 2018 from SVN revision 187986
Copyright © 2005-2018 VT MÄK. All Rights Reserved (www.mak.com)