Developer Documentation
Implementing a mesh smoother plugin

The last tutorial dealt with the implementation of a first plugin without any functionality at all. This tutorial will show you how to progam a simple mesh smoother. We are assuming that you have read A simple plugin and will only explain parts that have to be added in order to include new functionality.

Definition of the header

To start off we first have to add two additional header files. The first one is the ToolboxInterface. We need this interface because we want to add a Toolbox to our plugin in order to be able to set parameters. Additionally, we want to use the LoggingInterface so that we can inform the user of our plugin when something went wrong.

In order to work with the Interfaces we have to define that the plugin will implement the additional interfaces. The class definition then looks as follows:

Furthermore we have to insert the Q_INTERFACES macro. This makes the signals and slots from both interfaces available.

Q_INTERFACES(ToolboxInterface)
Q_INTERFACES(LoggingInterface)

Now that we have defined which Interfaces to implement, we can look at which signals and slots we want to implement. Firstly, we need two additional signals from the BaseInterface :

void updateView();
void updatedObject(int _identifier, const UpdateType& _type);

The first signal updateView() is emitted when we have finished computing the smoothed mesh. The signal tells OpenFlipper to redraw its viewer(s). The second signal updatedObjects() is emitted to inform all other plugins that an object has changed and therfore allows each plugin to react on this change.

Since we also want to use the LoggingInterface we have to define the following signals which allow us to send log messages to the OpenFlipper Loggers.

void log(Logtype _type, QString _message);
void log(QString _message);

The last Interface that's left over is the ToolboxInterface from which we are only implementing one signal:

void addToolbox( QString _name, QWidget* _widget );

As we will see later, this function is used to add a ToolBox (i.e. a QWidget ) to the OpenFlipper user interface.

As a last step we have to add to include additional class members to control the parameter for the smoothing and to actually compute the smoothed mesh.

private:
QSpinBox* iterationsSpinbox_;
private slots:
// BaseInterface
void initializePlugin();
void simpleLaplace();

That's it for the header of the plugin. The complete header looks like this:

#ifndef SMOOTHERPLUGIN_HH
#define SMOOTHERPLUGIN_HH
{
Q_OBJECT
Q_INTERFACES(BaseInterface)
Q_INTERFACES(ToolboxInterface)
Q_INTERFACES(LoggingInterface)
signals:
//BaseInterface
void updateView();
void updatedObject(int _identifier, const UpdateType& _type);
//LoggingInterface
void log(Logtype _type, QString _message);
void log(QString _message);
// ToolboxInterface
void addToolbox( QString _name, QWidget* _widget );
public:
// BaseInterface
QString name() { return (QString("Simple Smoother")); };
QString description( ) { return (QString("Smooths the active Mesh")); };
private:
QSpinBox* iterationsSpinbox_;
private slots:
// BaseInterface
void initializePlugin();
void simpleLaplace();
public slots:
QString version() { return QString("1.0"); };
};
#endif //SMOOTHERPLUGIN_HH

Implemention of the GUI

As we have already mentioned in Definition of the header, the tool box is generated inside the pluginsInitialized() function and then added by the addToolbox() signal passing a reference to our toolbox. So we begin by creating the elements of the ToolBox:

QWidget* toolBox = new QWidget();
QPushButton* smoothButton = new QPushButton("&Smooth", toolBox);
iterationsSpinbox_ = new QSpinBox(toolBox);
iterationsSpinbox_->setMinimum(1);
iterationsSpinbox_->setMaximum(1000);
iterationsSpinbox_->setSingleStep(1);
QLabel* label = new QLabel("Iterations:");

Later, the smoothButton is used to start the mesh smoothing and the iterationsSpinbox_ allows the user to control the number of smoothing iterations that should be performed.

The created Toolbox elements are then combined into a Layout

QGridLayout* layout = new QGridLayout(toolBox);

Here, the SpacerItem, which is added last, only helps aligning the elements at the top of the toolbox.

We have to connect the smoothButton to the simpleLaplace() slot of our class, so that after clicking the button the slot is called. Finally, when the tool box has been entirely created, we emit the signal to add the tool box to OpenFlipper:

connect(smoothButton, SIGNAL(clicked()), this, SLOT(simpleLaplace()));

Implemention of the Smoother

Now that the Toolbox is completely setup, we can start implementing the smoother. We begin with searching objects on which we have to compute the smoothing. The algorithm takes all objects marked as target and tries to apply the smoothing.

To find all these Objects we use an ObjectIterator. This iterator can be found in the PluginFunctions. Every communication between OpenFlipper and its Plugins is accomplished through either an interface or the PluginFunctions. So these are the places to look for if you want to add additional functionality and therefore need to communicate with OpenFlipper.

We initialize the PluginFunctions::ObjectIterator with PluginFunctions::TARGET_OBJECTS and thereby make sure that the iteration is restricted to target objects only.

In the next line we test if we have to deal with a triangle mesh. Since computation on triangle meshes is in many cases more efficient than on arbitrary polygonal meshes OpenMesh treats them seperately (see OpenMesh documentation for further information on this topic). See also OpenFlipper/common/Types.hh for data types in OpenFlipper.

First off, we backup the current vertex positions by adding a custom property to our mesh. We assume that you already made yourself familiar with OpenMesh. Otherwise we recommend you to read the OpenMesh documentation first since we use many of OpenMeshes's core functions in this tutorial.

// Get the mesh to work on

The smoothing algorithm itself depends on the number of iterations that one can adjust in the plugin's toolbox.

In a few words the algorithm iterates over each vertex of the mesh, accumulates the positions of it's neighboring vertices, devides the sum by it's valence and updates it's position to the computed value. If the current vertex is a boundary vertex, we skip over to the next one without updating the position.

Now that the smoothing operation is done, we want to get rid of the original vertex positions. Since we changed vertices, we also have to update the normals.

// Remove the property
mesh->remove_property(origPositions);
mesh->update_normals();

We now implement the same procedure for arbitrary polygonal meshes using the template types of OpenMesh. Starting at line (see The complete source code of the example for the complete source code of this example plugin):

The next step is to tell the OpenFlipper core that our plugin has updated an object in the scene. The affected object will then be redrawn.

The smoothing algorithm on arbitrary polygonal meshes in this case works analogously. See The complete source code of the example for the complete source code.

If the object's type is neither triangle nor polygonal mesh, we make use of the implemented LoggingInterface by dumping an error log message to OpenFlipper's log widget.

} else {
emit log(LOGERR, "Data type not supported.");
} // Switch data type

The complete source code of the example

SmootherPlugin.hh

#ifndef SMOOTHERPLUGIN_HH
#define SMOOTHERPLUGIN_HH
{
Q_OBJECT
Q_INTERFACES(BaseInterface)
Q_INTERFACES(ToolboxInterface)
Q_INTERFACES(LoggingInterface)
signals:
//BaseInterface
void updateView();
void updatedObject(int _identifier, const UpdateType& _type);
//LoggingInterface
void log(Logtype _type, QString _message);
void log(QString _message);
// ToolboxInterface
void addToolbox( QString _name, QWidget* _widget );
public:
// BaseInterface
QString name() { return (QString("Simple Smoother")); };
QString description( ) { return (QString("Smooths the active Mesh")); };
private:
QSpinBox* iterationsSpinbox_;
private slots:
// BaseInterface
void initializePlugin();
void simpleLaplace();
public slots:
QString version() { return QString("1.0"); };
};
#endif //SMOOTHERPLUGIN_HH

SmootherPlugin.cc

//=============================================================================
//
// OpenFlipper
// Copyright (C) 2008 by Computer Graphics Group, RWTH Aachen
// www.openflipper.org
//
//-----------------------------------------------------------------------------
//
// License
//
// OpenFlipper is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// OpenFlipper is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with OpenFlipper. If not, see <http://www.gnu.org/licenses/>.
//
//-----------------------------------------------------------------------------
//
//=============================================================================
#include "SmootherPlugin.hh"
SmootherPlugin::SmootherPlugin() :
iterationsSpinbox_(0)
{
}
void SmootherPlugin::initializePlugin()
{
// Create the Toolbox Widget
QWidget* toolBox = new QWidget();
QPushButton* smoothButton = new QPushButton("&Smooth", toolBox);
iterationsSpinbox_ = new QSpinBox(toolBox);
iterationsSpinbox_->setMinimum(1);
iterationsSpinbox_->setMaximum(1000);
iterationsSpinbox_->setSingleStep(1);
QLabel* label = new QLabel("Iterations:");
QGridLayout* layout = new QGridLayout(toolBox);
layout->addWidget(label, 0, 0);
layout->addWidget(smoothButton, 1, 1);
layout->addWidget(iterationsSpinbox_, 0, 1);
layout->addItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Expanding), 2, 0, 1, 2);
connect(smoothButton, SIGNAL(clicked()), this, SLOT(simpleLaplace()));
emit addToolbox(tr("Smoother"), toolBox);
}
{
++o_it) {
if (o_it->dataType(DATA_TRIANGLE_MESH)) {
// Get the mesh to work on
TriMesh* mesh = PluginFunctions::triMesh(*o_it);
// Property for the active mesh to store original point positions
// Add a property to the mesh to store original vertex positions
mesh->add_property(origPositions, "SmootherPlugin_Original_Positions");
for (int i = 0; i < iterationsSpinbox_->value(); ++i) {
// Copy original positions to backup ( in vertex property )
TriMesh::VertexIter v_it, v_end = mesh->vertices_end();
for (v_it = mesh->vertices_begin(); v_it != v_end; ++v_it) {
mesh->property(origPositions, v_it) = mesh->point(v_it);
}
// Do one smoothing step (For each point of the mesh ... )
for (v_it = mesh->vertices_begin(); v_it != v_end; ++v_it) {
TriMesh::Point point = TriMesh::Point(0.0, 0.0, 0.0);
// Flag, to skip boundary vertices
bool skip = false;
// ( .. for each outgoing halfedge .. )
TriMesh::VertexOHalfedgeIter voh_it(*mesh, v_it);
for (; voh_it; ++voh_it) {
// .. add the (original) position of the neighbour ( end of the outgoing halfedge )
point += mesh->property(origPositions, mesh->to_vertex_handle(voh_it));
// Check if the current Halfedge is a boundary halfedge
// If it is, abort and keep the current vertex position
if (mesh->is_boundary(*voh_it)) {
skip = true;
break;
}
}
// Devide by the valence of the current vertex
point /= mesh->valence(v_it);
if (!skip) {
// Set new position for the mesh if its not on the boundary
mesh->point(v_it) = point;
}
}
} // Iterations end
// Remove the property
mesh->remove_property(origPositions);
mesh->update_normals();
emit updatedObject(o_it->id(),UPDATE_GEOMETRY);
} else if (o_it->dataType(DATA_POLY_MESH)) {
// Get the mesh to work on
PolyMesh* mesh = PluginFunctions::polyMesh(*o_it);
// Property for the active mesh to store original point positions
// Add a property to the mesh to store original vertex positions
mesh->add_property(origPositions, "SmootherPlugin_Original_Positions");
for (int i = 0; i < iterationsSpinbox_->value(); ++i) {
// Copy original positions to backup ( in Vertex property )
PolyMesh::VertexIter v_it, v_end = mesh->vertices_end();
for (v_it = mesh->vertices_begin(); v_it != v_end; ++v_it) {
mesh->property(origPositions, v_it) = mesh->point(v_it);
}
// Do one smoothing step (For each point of the mesh ... )
for (v_it = mesh->vertices_begin(); v_it != v_end; ++v_it) {
PolyMesh::Point point = PolyMesh::Point(0.0, 0.0, 0.0);
// Flag, to skip boundary vertices
bool skip = false;
// ( .. for each Outoing halfedge .. )
PolyMesh::VertexOHalfedgeIter voh_it(*mesh, v_it);
for (; voh_it; ++voh_it) {
// .. add the (original) position of the Neighbour ( end of the outgoing halfedge )
point += mesh->property(origPositions, mesh->to_vertex_handle(voh_it));
// Check if the current Halfedge is a boundary halfedge
// If it is, abort and keep the current vertex position
if (mesh->is_boundary(*voh_it)) {
skip = true;
break;
}
}
// Devide by the valence of the current vertex
point /= mesh->valence(v_it);
if (!skip) {
// Set new position for the mesh if its not on the boundary
mesh->point(v_it) = point;
}
}
} // Iterations end
// Remove the property
mesh->remove_property(origPositions);
mesh->update_normals();
emit updatedObject(o_it->id(),UPDATE_GEOMETRY);
} else {
emit log(LOGERR, "Data type not supported.");
} // Switch data type
}
}
Q_EXPORT_PLUGIN2( smootherplugin, SmootherPlugin);

We use the cmake project file presented in How to build the plugin with CMake to build this plugin.