Advanced user interaction with a Viewer

VVE allows the user to inherit its Viewer class, extending the Qt widget as needed.

The first step to extend the Viewer is to create your own viewer class inheriting the VVE Viewer:

class MyViewer : public Viewer
{
public:
  MyViewer(QWidget* parent)
    : Viewer(parent)
  { }
};

and to tell VVE you want to use this viewer class:

DEFINE_VIEWER(MyViewer);

The viewer can be extended using all the regular Qt QWidget extensions and the QGLViewer ones. You will find the documentation:

Here I will describe some common extensions.

Creating a context menu

Creating a context menu relies on the Qt QContextMenuEvent, QMenu and QAction classes. First, we need to include them:

#include <QContextMenuEvent>
#include <QAction>
#include <QMenu>

Then, we define our class, and add the Q_OBJECT macro as we will need to specify Qt slots:

#include <iostream>

using namespace std;

class MyViewer : public Viewer
{
  Q_OBJECT

The menu items should be defined as actions (or action groups if required):

  QAction *firstAct, *secondAct, *thirdAct;

And the actions should be created in the constructor and connected to the slots:

public:
  MyViewer(QWidget* parent)
    : Viewer(parent)
    , firstAct(new QAction("First action", this))
    , secondAct(new QAction("Second action", this))
    , thirdAct(new QAction("Third action", this))
  {
    connect(firstAct, SIGNAL(activated()), SLOT(firstActivated()));
    connect(secondAct, SIGNAL(activated()), SLOT(secondActivated()));
    connect(thirdAct, SIGNAL(activated()), SLOT(thirdActivated()));
  }

Now, the slots have to be defined:

public slots:
  void firstActivated()
  {
    cout << "First action selected" << endl;
  }

  void secondActivated()
  {
    cout << "Second action selected" << endl;
  }

  void thirdActivated()
  {
    cout << "Third action selected" << endl;
  }

Then, the contextMenuEvent method has to be overridden to create and show the menu:

protected:
  void contextMenuEvent(QContextMenuEvent* event)
  {
    QMenu *popup = new QMenu(this);
    popup->addAction(firstAct);
    popup->addAction(secondAct);
    popup->addAction(thirdAct);
    popup->popup(event->globalPos());
  }
};

At last, as we defined slots, we will need Qt to generate the moc file and we will need to include it. Also, we need to tell VVE we want to use this class as viewer:

#include "model.moc"

DEFINE_VIEWER(MyViewer);

The whole can be found in context_menu.cpp

Just don't forget to add the moc file as dependency in your project!

Selecting a 3D object

The 3D object selection uses both OpenGL naming system and the QGLViewer selection helpers. The example will draw a grid of 10 by 10 squares using a graph and color the square in gray. When a square is selected (i.e. by pressing Alt and clicking on the square) it is drawn in red. Reselecting the square draw it in gray again. You will find the full code in the file select.cpp

The first step is to define the data we need for our vertex. In our case, we need the position of the graph (in 3D) and its status (i.e. selected or not). The position will be a util::Vector, so we need to include it.

#include <vve.h>
#include <util/vector.h>

typedef util::Vector<3, double> Point3d;

struct VertexContent
{
  VertexContent()
    : selected(false)
    {}

  Point3d pos;
  bool selected;
};

Note:
The default constructor specifies the vertex is, by default, not selected. The util::Vector class specifies already a default constructor setting the coordinates to 0, so we don't need to respecify it.

Next, we define our graph and vertex types:

typedef graph::VVGraph<VertexContent> vvgraph;
typedef vvgraph::vertex_t vertex;

Then, the model class, its constructor (filling in the graph) and an empty step method (remember the step method is mandatory):

class SelectModel : public Model
{
public:
  vvgraph S;

  SelectModel(QObject *parent)
    : Model(parent)
  {
    for(double i = 0 ; i < 10 ; i++)
    {
      for(double j = 0 ; j < 10 ; j++)
      {
        vertex v; // Create a vertex
        v->pos.x() = i;
        v->pos.y() = j;
        S.insert(v);
      }
    }
  }

  void step() { }

Now, just for presentation purpose, we want to setup the size of our scene in the viewer, so that the camera will be nicely placed. We also want a black background.

  void preDraw( Viewer* viewer )
  {
    glClearColor(0,0,0,0);
    viewer->setSceneBoundingBox(qglviewer::Vec(0,0,0), qglviewer::Vec(10, 10, 0));
  }

The next function just draw a square at the position of the vertex. It is extracted as we will need need both to actually draw the square and to define the shape of the cells for the selection.

  void drawCell(const vertex& v)
  {
    Point3d c1 = v->pos;
    Point3d c2 = v->pos + Point3d(1, 0, 0);
    Point3d c3 = v->pos + Point3d(1, 1, 0);
    Point3d c4 = v->pos + Point3d(0, 1, 0);
    glBegin(GL_QUADS);
    glVertex3dv(c1.c_data());
    glVertex3dv(c2.c_data());
    glVertex3dv(c3.c_data());
    glVertex3dv(c4.c_data());
    glEnd();
  }

Now, the draw method draw the squares, setting the color to red or gray depending on the state:

  void draw()
  {
    forall(const vertex& v, S)
    {
      if(v->selected)
      {
        glColor3f(1, 0, 0);
      }
      else
      {
        glColor3f(0.5, 0.5, 0.5);
      }
      drawCell(v);
    }
  }

And now, the most important method for the selection: Model::drawWithNames. This method should draw all the selectable items, setting the name with glPushName and glPopName. To name the vertexes, the method graph::Vertex::label is very convenient, as you are guarantied a unique name for each vertex of the same type (be careful though that vertexes of different type may have the same label).

  void drawWithNames()
  {
    forall(const vertex& v, S)
    {
      glPushName((int)v.label());
      drawCell(v);
      glPopName();
    }
  }
};

The last step is to define the viewer and what to do with the selection. This is done by overriding the Viewer::postSelection method. We will also use a way to obtain a weak pointer on a vertex. If you hold a valid label of a vertex (and know its type), you can then construct a vertex using this label. Be careful though, as there is no checking of the validity of the label. Also, the reference you will hold is weak, so you must not store it!

class SelectViewer : public Viewer
{
public:
  SelectViewer(QWidget *parent)
    : Viewer(parent)
  { }

protected:
  void postSelection(const QPoint& p)
  {
    int s = selectedName();
    if(s != -1)
    {
      // Obtain a weak pointer on the vertex
      // Be careful, if s is not the label of a vertex, this could crash
      vertex v((vertex::identity_t)s);
      v->selected ^= true;
    }
  }
};

And don't forget to declare the model and viewer:

DEFINE_MODEL(SelectModel);
DEFINE_VIEWER(SelectViewer);

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines
Generated on Fri May 31 15:37:54 2013 for VVE by  doxygen 1.6.3