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:
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 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!
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; };
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);