MIRA
Tutorial: Creating an advanced 3D Visualization


Introduction

In this tutorial, we will learn how to write the same 3D visualization like in Tutorial: Creating a 3D Visualization by using mira::Visualization3D instead of mira::Visualization3DBasic as base class.

You can use mira::Visualization3D as base class whenever mira::Visualization3DBasic is too restrictive. It allows flexibility when creating a visualization but it requires some more effort.

Creating the Visualization Class

We start by deriving our class from Visualization3D that forms the base of any 3D visualization. The meta information on that class will be used to group and display visualizations in a list view of all available visualizations. We can specify the name, the category, and a description that is displayed in the visualizations dialog.

The full working example (source code, makefile) can be found in gui/examples/tutorials/Point3Visualization.C.

Moreover, we need to implement the setupScene() method from our base class to setup our scene:

using namespace mira;
class Point3Visualization : public Visualization3D
{
MIRA_META_OBJECT(Point3Visualization,
("Name", "Point")
("Category", "Geomtery")
("Description", "Visualizes a point."))
public:
virtual void setupScene(IVisualization3DSite* site)
{
// TODO setup our scene here
}
};

We can compile it with the following makefile

# Include our dependencies
MIRA_REQUIRE_PACKAGE(GUIVisualization)
MIRA_ADD_LIBRARY(Point3Visualization
SHARED
PACKAGE Visualizations
SOURCE
Point3Visualization.C
LINK_LIBS
GUIVisualization
)

This class is already working and can be added via the "+"-Button of the Visualization Control view. However, it is pretty useless at the moment since it will render nothing onto the screen. So lets setup our scene.

Setup a Scene and Geometry

First we need another member and initialize it in the setupScene method:

#include <OGRE/OgreSceneManager.h>
..
Point3Visualization() :
mPoint(NULL) {}
virtual ~Point3Visualization()
{
delete mPoint;
}
...
virtual void setupScene(IVisualization3DSite* site)
{
Ogre::SceneManager* sceneManager = site->getSceneManager();
mPoint = new MeshObject("Sphere.mesh", sceneManager, sceneManager->getRootSceneNode());
}
...
protected:
MeshObject* mPoint;

Now a sphere mesh is rendered at the origin of the scenery. The position can be changed via:

mPoint->setPosition(p);

Add a ChannelProperty

However, we first need to obtain the position from a Point3f channel. Therefore, we add a ChannelProperty member and reflect it in our reflect() method:

template <typename Reflector>
void reflect(Reflector& r)
{
// call base class reflect
MIRA_REFLECT_BASE(r, Visualization3D);
// our channel property for a channel named "Point"
channelProperty(r, "Point", mPointChannel, "The point channel to visualize");
}
protected:
ChannelProperty<Point3f> mPointChannel;

To update the position of the point corresponding to the channel, we need to listen for changes on that channel. This is done by specifying a callback, i.e. by binding a member method using setDataChangedCallback(). This is similar to subscribing to a "normal" channel:

...
Point3Visualization() :
mPoint(NULL)
{
// register our callback function
mPointChannel.setDataChangedCallback(boost::bind(&Point3Visualization::dataChanged, this, _1));
}
...
void dataChanged(ChannelRead<Point3f> read)
{
// set position of our scene point
Ogre::Vector3 p = OgreUtils::toOgreVector(read->value());
mPoint->setPosition(p);
}

That was easy. But what if the coordinates in that point channel are given in a coordinate system defined by the publisher of that channel instead of the world coordinate frame ?

Use the Correct Transformation

To render the sphere/point at its real position corresponding to the current frame of the 3D editor view, we need to take the selected editor frame and the frame of the channel into account.

To be able to do this, we need to create a new scene node where we attach our point node to.

Point3Visualization() :
mPoint(NULL),
mNode(NULL) {}
virtual ~Point3Visualization()
{
delete mPoint;
if(getSite()==NULL)
return;
getSite()->getSceneManager()->destroySceneNode(mNode);
}
...
virtual void setupScene(IVisualization3DSite* site)
{
Ogre::SceneManager* sceneManager = site->getSceneManager();
mNode = sceneManager->getRootSceneNode()->createChildSceneNode();
mPoint = new MeshObject("Sphere.mesh", sceneManager, mNode);
}
...
protected:
Ogre::SceneNode* mNode;

Done. The point node is now a child of the new scene node and will be positioned relative to that node. We can now also update the position of the scene node according to the channel frame. But let's consider that the channel is only updated once in a minute.

Update the Visualization

So far, when the user changes the transform frame of the 3D editor view, the position of our point is not updated until the data in the channel changes. Besides, our data frame could be part of a transform chain where other frames get updated more often than our point channel. To overcome this limitation, we can overwrite another method of our base class - "onUpdate", that is called periodically by the GUI thread. The time span since the last call of update is passed as parameter dt.

void dataChanged(ChannelRead<Point3f> read)
{
...
// store the frame id and timestamp of our data
mFrameID = read->frameID;
mDataTimestamp = read->timestamp;
}
...
protected:
virtual void onUpdate(Duration dt)
{
if (!mPointChannel.isValid())
return;
// get the transformation between
// our data frame and the selected frame of the 3D editor view
RigidTransform3f d = getAuthority().getTransform<RigidTransform3f>(
mFrameID, mDataTimestamp,
getSite()->getCameraFrame(), Time::now(),
getSite()->getFixedFrame());
// set the transform on the node (indirectly move mPoint)
}
...
protected:
std::string mFrameID;
Time mDataTimestamp;

Now our point is positioned relative to a node that gets translated to the selected frame of our editor.

You can see that compared to using the Visualization3DBasic base class, this is much more code and work to visualize a simple point. However, by using the Visualization3D base class, you have more flexibility (like visualizing multiple channels by providing multiple channel properties, creating several scene nodes...)

Add Drag'n'Drop Support

At the moment our visualization can be added via the "Add" button of the visualization control only. To enable drag'n'drop support, allowing us to drag the Point3f channel directly into the 3D view, we need to implement the getDataConnection() method:

// this method is used to determine the data type of the channel
// we can visualize
virtual DataConnection getDataConnection()
{
return DataConnection(mPointChannel);
}

This method tells the visualization view which type of data we can visualize and what is the main data channel for visualization.