Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

How to Create an OpenSceneGraph Application

Save for later
  • 660 min read
  • 2011-04-07 00:00:00

article-image

OpenSceneGraph 3.0: Beginner's Guide

Constructing your own projects


To build an executable program from your own source code, a platform-dependent solution or makefile is always required.

At the beginning of this article, we are going to introduce another way to construct platform-independent projects with the CMake system, by which means, we are able to focus on interacting with the code and ignore the painstaking compiling and building process.

Time for action – building applications with CMake


Before constructing your own project with CMake scripts, it could be helpful to keep the headers and source files together in an empty directory first. The second step is to create a CMakeLists.txt file using any text editor, then and start writing some simple CMake build rules.

  1. The following code will implement a project with additional OSG headers and dependency libraries. Please enter them into the newly-created CMakeLists.txt file:

    cmake_minimum_required( VERSION 2.6 )
    project( MyProject )


    find_package( OpenThreads )
    find_package( osg )
    find_package( osgDB )
    find_package( osgUtil )
    find_package( osgViewer )
    
    macro( config_project PROJNAME LIBNAME )
    include_directories( ${${LIBNAME}_INCLUDE_DIR} )
    target_link_libraries( ${PROJNAME} ${${LIBNAME}_LIBRARY} )
    endmacro()
    
    add_executable( MyProject main.cpp )
    config_project( MyProject OPENTHREADS )
    config_project( MyProject OSG )
    config_project( MyProject OSGDB )
    config_project( MyProject OSGUTIL )
    config_project( MyProject OSGVIEWER )

  2. We have only added a main.cpp source file here, which is made up of the "Hello World" example and will be compiled to generate an executable file named MyProject. This small project depends on five major OSG components. All of these configurations can be modified to meet certain requirements and different user applications.
  3. Next, start cmake-gui and drag your CMakeLists.txt into the GUI. You may not be familiar with the CMake scripts to be executed, at present. However, the CMake wiki will be helpful for further understanding: http://www.cmake.org/Wiki/CMake.
  4. Create and build a Visual Studio solution or a makefile.
  5. The only point is that you have to ensure that your CMake software version is equal to or greater than 2.6, and make sure you have the OSG_ROOT environment variable set. Otherwise, the find_package() macro may not be able to find OSG installations correctly. The following image shows the unexpected errors encountered because OSG headers and libraries were not found in the path indicated by OSG_ROOT (or the variable was just missed):

    how-create-openscenegraph-application-img-0

  6. Note that, there is no INSTALL project in the Visual Studio solution, or any make install command to run at this time, because we don't write such CMake scripts for post-build installations. You could just run the executable file in the build directory directly.

What just happened?


CMake provides easy-to-read commands to automatically find dependencies for user projects. It will check preset directories and environment variables to see if there are any headers and libraries for the required package.

The environment variable OSG_ROOT (OSG_DIR is OK, too) will facilitate in looking for OSG under Windows and UNIX, as CMake will first search for valid paths defined in it, and check if there are OSG prebuilt headers and libraries existing in these paths.

Have a go hero – testing with different generators


Just try a series of tests to generate your project, using Visual Studio, MinGW, and the UNIX gcc compiler. You will find that CMake is a convenient tool for building binary files from source code on different platforms. Maybe this is also a good start to learning programming in a multi-platform style.

Using a root node


Now we are going to write some code and build it with a self-created CMake script. We will again make a slight change to the frequently-used "Hello World" example.

Time for action – improving the "Hello World" example


The included headers, <osgDB/ReadFile> and <osgViewer/Viewer>, do not need to be modified. We only add a root variable that provides the runtime access to the Cessna model and assigns it to the setSceneData() method.

  1. In the main entry, record the Cessna model with a variable named root:
    osg::ref_ptr<osg::Node> root = osgDB::readNodeFile("cessna.osg");
    osgViewer::Viewer viewer;
    viewer.setSceneData( root.get() );
    return viewer.run();

  2. Build and run it at once:

    how-create-openscenegraph-application-img-1

  3. You will see no difference between this example and the previous "Hello World". So what actually happened?

What just happened?


In this example, we introduced two new OSG classes: osg::ref_ptr<> and osg::Node. The osg::Node class represents the basic element of a scene graph. The variable root stands for the root node of a Cessna model, which is used as the scene data to be visualized.

Meanwhile, an instance of the osg::ref_ptr<> class template is created to manage the node object. It is a smart pointer, which provides additional features for the purpose of efficient memory management.

Understanding memory management


In a typical programming scenario, the developer should create a pointer to the root node, which directly or indirectly manages all other child nodes of the scene graph. In that case, the application will traverse the scene graph and delete each node and its internal data carefully when they no longer need to be rendered. This process is tiresome and error-prone, debugging dozens of bad trees and wild pointers, because developers can never know how many other objects still keep a pointer to the one being deleted. However without writing the management code, data segments occupied by all scene nodes will never be deleted, which will lead to unexpected memory leaks.

This is why memory management is important in OSG programming. A basic concept of memory management always involves two topics:

  1. Allocation: Providing the memory needed by an object, by allocating the required memory block.
  2. Deallocation: Recycling the allocated memory for reuse, when its data is no longer used.
  3. Unlock access to the largest independent learning library in Tech for FREE!
    Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
    Renews at ₹800/month. Cancel anytime


Some modern languages, such as C#, Java, and Visual Basic, use a garbage collector to free memory blocks that are unreachable from any program variables. That means to store the number of objects reaching a memory block, and deallocate the memory when the number decrements to zero.

The standard C++ approach does not work in such a way, but we can mimic it by means of a smart pointer, which is defined as an object that acts like a pointer, but is much smarter in the management of memory. For example, the boost library provides the boost::shared_ptr<> class template to store pointers in order to dynamically allocated related objects.

ref_ptr<> and Referenced classes


Fortunately, OSG also provides a native smart pointer, osg::ref_ptr<>, for the purpose of automatic garbage collection and deallocation. To make it work properly, OSG also provides the osg::Referenced class to manage reference-counted memory blocks, which is used as the base class of any classes that may serve as the template argument.

The osg::ref_ptr<> class template re-implements a number of C++ operators as well as member functions, and thus provides convenient methods to developers. Its main components are as follows:

  • get(): This public method returns the managed pointer, for instance, the osg::Node* pointer if you are using osg::Node as the template argument.
  • operator*(): This is actually a dereference operator, which returns l-value at the pointer address, for instance, the osg::Node& reference variable.
  • operator->() and operator=(): These operators allow a user application to use osg::ref_ptr<> as a normal pointer. The former calls member functions of the managed object, and the latter replaces the current managed pointer with a new one.
  • operator==(), operator!=(), and operator!(): These operators help to compare smart pointers, or check if a certain pointer is invalid. An osg::ref_ptr<> object with NULL value assigned or without any assignment is considered invalid.
  • valid(): This public method returns true if the managed pointer is not NULL. The expression some_ptr.valid() equals to some_ptr!=NULL if some_ptr is defined as a smart pointer.
  • release(): This public method is useful when returning the managed address from a function.


The osg::Referenced class is the pure base class of all elements in a scene graph, such as nodes, geometries, rendering states, and any other allocatable scene objects. The osg::Node class actually inherits from osg::Referenced indirectly. This is the reason why we program as follows:

osg::ref_ptr<osg::Node> root;


The osg::Referenced class contains an integer number to handle the memory block allocated. The reference count is initialized to 0 in the class constructor, and will be increased by 1 if the osg::Referenced object is referred to by an osg::ref_ptr<> smart pointer. On the contrary, the number will be decreased by 1 if the object is removed from a certain smart pointer. The object itself will be automatically destroyed when no longer referenced by any smart pointers.

The osg::Referenced class provides three main member methods:

  • The public method ref() increases the referenced counting number by 1
  • The public method unref() decreases the referenced counting number by 1
  • The public method referenceCount() returns the value of the current referenced counting number, which is useful for code debugging


These methods could also work for classes that are derived from osg::Referenced. Note that it is very rarely necessary to call ref() or unref() directly in user programs, which means that the reference count is managed manually and may conflict with what the osg::ref_ptr<> is going to do. Otherwise, OSG's internal garbage collecting system will get the wrong number of smart pointers in use and even crash when managing memory blocks in an improper way.

Collecting garbage: why and how


Here are some reasons for using smart pointers and the garbage collection system in programming:

  • Fewer bugs: Using smart pointers means the automatic initialization and cleanup of pointers. No dangling pointers will be created because they are always reference-counted.
  • Efficient management: Objects will be reclaimed as soon as they are no longer referenced, which gives more available memory to applications with limited resources.
  • Easy to debug: We can easily obtain the referenced counting number and other information on objects, and then apply other optimizations and experiments.


For instance, a scene graph tree is composed by a root node and multiple levels of child nodes. Assuming that all children are managed with osg::ref_ptr<>, user applications may only keep the pointer to the root node. As is illustrated by the following image, the operation of deleting the root node pointer will cause a cascading effect that will destroy the whole node hierarchy:

how-create-openscenegraph-application-img-2


Each node in the example scene graph is managed by its parent, and will automatically be unreferenced during the deletion of the parent node. This node, if no longer referenced by any other nodes, will be destroyed immediately, and all of its children will be freed up. The entire scene graph will finally be cleaned without worries after the last group node or leaf node is deleted.

The process is really convenient and efficient, isn't it? Please make sure the OSG smart pointer can work for you, and use a class derived from osg::Referenced as the osg::ref_ptr<> template argument, and correctly assign newly-allocated objects to smart pointers.

A smart pointer can be used either as a local variable, a global variable, or a class member variable, and will automatically decrease the referenced counting number when reassigned to another object or moved out of the smart pointer's declaration scope.

It is strongly recommended that user applications always use smart pointers to manage their scenes, but there are still some issues that need special attention:

  • osg::Referenced and its derivatives should be created from the heap only. They cannot be used as local variables because class destructors are declared protected internally for safety. For example:
    osg::ref_ptr<osg::Node> node = new osg::Node; // this is legal
    osg::Node node; // this is illegal!

  • A regular C++ pointer is still workable temporarily. But user applications should remember to assign it to osg::ref_ptr<> or add it to a scene graph element (almost all OSG scene classes use smart pointers to manage child objects) in the end, as it is always the safest approach.
    osg::Node* tmpNode = new osg::Node; // this is OK
    ...
    osg::ref_ptr<osg::Node> node = tmpNode; // Good finish!

  • Don't play with reference cycles, as the garbage collecting mechanism cannot handle it. A reference cycle means that an object refers to itself directly or indirectly, which leads to an incorrect calculation of the referenced counting number.


The scene graph shown in the following image contains two kinds of reference cycles, which are both invalid. The node Child 1.1 directly adds itself as the child node and will form a dead cycle while traversing to its children, because it is the child of itself, too! The node Child 2.2, which also makes a reference cycle indirectly, will cause the same problem while running:

how-create-openscenegraph-application-img-3


Now let's have a better grasp of the basic concepts of memory management, through a very simple example.