Introduction

General description of the VirtualAwesome API.

When you are using VirtualAwesome you are building a scene graph which is rendered with OpenGL. The concept of a scene graph can be very deep yet in the end it just means the following. You are building rendering instructions in a tree-like data structure as opposed to directly sending the instructions to the graphics card. This allows the scene graph library to do smart things with your geometry and simplify many common graphics programming tasks. Examples are mouse interaction with 3D objects, transparency depth sorting, and shadows.

The backbone of VirtualAwesome is OpenSceneGraph. You can take full advantage of this powerful core or keep things simple and use va exclusively. Just keep in mind osg is there when you need to do crazy stuff.

The App Class

App is not essential but useful for structuring the application. All the examples are written around it. They have a MyApp which subclasses App and overwrites functions where appropriate. The functions overwritten are typically the core event handlers like keyPress and touchMove. In a way MyApp is the Grand Central of the application. All core events can be handled there and custom events can be added just as easily. For a comprehensive list of core events see EventHandlers.

Essential Classes

The VirtualAwesome API has a few sets of classes that are important to know. Like all classes and functions they are in the va namespace. If you are not familiar with C++ namespaces you don't have to worry. When you see the va:: in the examples simply treat it as a prefix to the name. It tells you where the class is coming from.

Scene is essential to any app. Typically you have an instance named scene in MyApp. It's the entry point to the scene, interfaces with the windowing system, contains the camera, and brings along many convenience functions.

Nodes are the principal building blocks of the scene graph. They can be added to the scene and nested into other nodes. The Node base class by itself can be used for grouping and transforming its children. For drawing and interactivity you would use the Shape and Widget node classes.

Shapes are nodes for drawing stuff. While there are many Shape classes for drawing various primitives, freeform polygons, text, and images you will probably want to define your own ones pretty soon.

Widgets are similar to the Shape ones with the addition of user interaction. They are clickable/touchable nodes. The scene dispatches pointer events to them based on precise ray-polygon intersection tests.

Drawing

There are basically three ways to draw shapes in the scene. They all involve adding a shape node to the scene graph. You can use the primitive shape nodes (eg. CircleShape, RectangleShape, ImageShape, TextShape) to build up your geometry, define your own shape node classes or use the versatile VertexShape class. Primitives and VertexShape are very useful when all you want is to get the shapes on the screen. On the other hand, when you want to attach some behavior or geometrical properties to the shape it usually makes sense to define your own shape classes.

A few words about the coordinate system. The default camera has the origin (0,0,0) at the bottom-left window corner. OpenGL-typical, the X-axis runs from left to right. The Y-axis runs from bottom to top, and the Z-axis into the screen. Also for anything that is drawn on the z=0 plane, one screen pixel equals one drawing unit. This means if you have your app running at 800x600 pixels and you draw a rectangle at 0,0,0 with width=800 and height=600 it will cover the entire scene space.

Relevant examples code: exampleShapes.

Using Shape Primitives

The following example adds a circle to the scene of radius 50. it also sets the position and the color. The setPosition function is inherited from Node along with a rich set of transformation functions. One word about the constructor. Most va primitives let you assign the major properties of the shape when creating it. It is also perfectly fine to use the default constructor and set the radius through the setRadius function.

va::CircleShape* circle = new va::CircleShape(50);
circle->setPosition(300, 630, 0);
circle->setColor(1.0, 0.19, 0.14, 1.0);
scene->addChild(circle);

Using the VertexShape Class

The VertexShape class is conceptually very close to how you would draw directly with OpenGL--create a list of vertices and define how they should be drawn. The drawing modes are lines, triangles, triangle strips, etc. The following code shows how to create a VertexShape that draws a simple polygon.

va::VertexShape* polygon = new va::VertexShape();
polygon->setMode(GL_TRIANGLES);
polygon->addVertex(100,100,0);
polygon->addVertex(300,100,0);
polygon->addVertex(320,320,0);
polygon->addVertex(130,330,0);
scene->addChild(polygon);

Defining your own Shape Nodes

Let's take a look at the MonsterShape class. It comes with va and defines a fairly arbitrary shape. You can create your own shape nodes along the same lines. Simply create a new class and subclass it from Shape. Then add one or more VertexShapes or any other shape classes as its members. In OOP-parlance this is called "aggregation" or "has-a" relationships.

class MonsterShape : public Shape {
 
    public:
 
        MonsterShape();        
        
        void setColor( float r, float g, float b, float a );
        void setColor( const osg::Vec4& color );
    
    protected:
        osg::ref_ptr<VertexShape> _body;
        osg::ref_ptr<VertexShape> _mouth;
        osg::ref_ptr<VertexShape> _leye;
        osg::ref_ptr<VertexShape> _reye;
};

One thing might strike you as odd. There is no destructor and the type of the various member shapes involves templates. While this osg::ref_ptr<> may look unfamiliar to you it means pretty much the same as VertexShape*. The difference with ref_ptr is that you don't have to delete the object after its use. The underlying osg library makes heavy use of this kind of referencing and addresses a common problem with it. Any node may occur multiple times in the graph and therefore should only be deleted when the last reference disappears. With the ref_ptr idiom the node simply deletes itself when it is no longer referenced. It's easy to use but maybe a bit harder to wrap your head around. For details on using ref_ptr check out this tutorial and smart pointer guide.

Also check out the implementation, MonsterShape.cpp. Besides the plain VertexShape calls you will also find a few additional calls that are often useful. setColor(1, 0.19, 0.14, 1) sets the color of the shape. The color values are RGB with values from 0 to 1. disableDepthTest() makes sure geometry is drawn and overlapped in the order it is added to the scene graph. This is usually useful for 2D stuff but not so much for 3D objects. disableLighting() makes sure we get the colors we specify. If lighting is not disabled we would get a color that is calculated from the specified color, the vertex normals, camera and light position.

One more thing, polygon tesselation. Drawing arbitrary polygons is actually not super straight forward in computer graphics. If you were to draw the same shape in OpenGL you would have to split up most concave shapes into convex ones and draw them one after the other. In va you can simply call the tesselate function and this will be done for you. If you do not tesselate, your monster would look something like the following. Pretty rad but probably far from the intented.

Interaction

va interfaces with a wide variety of input devices. Each device triggers a set of low level events in the app; aka app events. There is more though. Besides the keyboard they are mostly pointer-based. For this reason it is of primary interest to be able to correlate pointer events with graphical interface components. This is exactly what Widgets are for. Typical ones are ButtonWidget and SliderWidget.

Pointers interacting with Widget-type nodes trigger Widget events. For example, upon a mouse-click on a Widget, the Widget gets notified with a touchDown event. It is also important to note there are certain traditional assumptions va does not make anymore. For one it supports multiple pointers at the same time and for two, it supports widgets that are fully transformable in 3D. Widgets can be moved, rotated, and scaled without causing any hit-testing difficulties.

App Events

The scene takes care of input processing and generates various events. For convenience all these events can be handled in the MyApp class by overwriting the particular event handlers. There are the following app events:

  • keyPress, keyRelease
  • touchEnter, touchDown, touchMove, touchUp, touchLeave
  • mouseButton, mouseScroll
  • penPressure, penOrientation, penEnter, penLeave

Please note there are no mouseMove or mouseDrag events. As far as va is concerned all pointer devices trigger "touch" events. No matter if you press a mouse button, pen button, or put a finger down on a touch screen you get a touchDown event. The same holds true for touchMove and touchUp. In case you need to distinguish between left, middle, and right mouse buttons va provides the mouseButton event.

To give you an idea how this looks in code, the following changes the background color of the scene upon a mouse click/touch down. For more context check out the exampleWidget, exampleKeymouse and the note about EventHandlers.

// In MyApp.h
void touchDown( va::Touch& touch );
 
// In MyApp.cpp
void MyApp::touchDown( va::Touch& touch ){
	scene->setBackgroundColor(0,0,1);
}

Widget Events

The scene also fires higher level events. Internally it takes pointer-based app events, calculates intersections with widgets, and then notifies the particular widgets. Widgets then get notified about the following events:

  • touchEnter, touchDown, touchMove, touchUp, touchLeave
  • mouseButton, mouseScroll

There are two main ways to handle these Widget events. One is to do it in the widget node itself and the other is to register another class for handling. For former you have to define your own new Widget. For Latter you have to register a handler class with the widget. For sake of simplicity let's focus on the scenario where MyApp is such a handler class. This is what's used throughout the examples code.

Using MyApp to Handle Widget Events

Often the best way to handle Widget events is to delegate them to another class. MyApp is well suited for that. Let's handle ButtonWidget presses. Create a button in MyApp and then register the event handler with the addEventHandler function:

// In MyApp.h
va::ButtonWidget* button1;
 
// In MyApp.cpp
button1 = new va::ButtonWidget(200,40);
button1->setLabel("Fire");
button1->setPosition(150,50);
button1->addEventHandler(this);
scene->addChild(button1);

Also in MyApp add a widget handler for the button:

// In MyApp.h
void widget( va::ButtonWidget& button );
 
// In MyApp.cpp
void MyApp::widget( va::ButtonWidget& button ) {
    if (&button == button1) {
        // do something
    }
}

Using your own Widgets and Event Handling

You can define your own widgets and handle all the events that are relevant to it in your widget code. Simply overwrite the handler functions of interest. This can be used in combination with the previous approach.

Defining your own Widget Nodes

Defining your own Widgets works similar to defining new Shape nodes. All widgets have to directly or indirectly be derived from Widget. The pointer intersection test is done based on the _background; a VertexShape which can have any possible shape. For details, check out the stock Widgets such as:

Addons

The standard way to add functionality to va is with addon. They are just code files structured in a certain way. Addons use their own namespaces and usually compile into their own library. Currently va comes with the following addons preconfigured:

  • vaMice - use multiple mouse devices at the same time
  • vaMultipad - use apple multitouch pads
  • vaTouchkit - use Nortd Lab's TouchKit hardware
  • vaOpencv - integrates the popular computer vision library
  • vaOsc - Open Sound Control communication
  • vaOpenal - integrates OpenAL sound and ogg vorbis playback

As for any topic that is not comprehensively covered in this intro there is the Discussion List ;)