: Documentation : Interaction Tutorials

Introduction

This tutorial demonstrates how to handle mouse input in an osgART application. Here we will use mouse input to allow us to start and stop a 3D object rotating.

Setup

As before we add the event handlers to the OSG Viewer, and set up the Scene node. As with our Keyboard Input tutorial, we will need to define an event handler class, so we also add this to the Viewer’s list of event handlers. As the event handler will act on the scene, we pass our scene in argument.

viewer.addEventHandler(new MouseEventHandler(scene)); // Our handler

Here we will track one marker, and we’ll add the 3D car model to it - we name its Transform node “car” and declare it globally, so that our MouseEventHandler can access it easily. The code has been omitted as it is the same as previous tutorials. We also add an AnimationPathCallback as we did in the Rigid Body Animation tutorial, but initially pause it. Within our mouse input code, we can then start/stop the animation using this pause functionality.

carMT->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(0.0,0.0,0.0),
                                                           osg::Z_AXIS,
                                                           osg::inDegrees(45.0f)));

osg::AnimationPathCallback* cb =
        dynamic_cast<osg::AnimationPathCallback*>(carMT->getUpdateCallback());

cb->setPause(true);

The only other new code is the following line. In OSG, we can “name” any Node object using the setDescription() or setDescriptions() methods. We can use this description string to identify particular nodes in the scene graph - we will use this to simplify things a bit later in the example.

carMT->addDescription("CAR");

MouseEventHandler

To handle both keyboard and mouse events in OSG, we need to define a subclass of osgGA::GUIEventHandler, and override the handle() method with custom code.

Once we have received the mouse event, we can perform some action. Here we perform mouse picking. Mouse picking can be thought of as shooting a “ray” from the mouse position into our scene, and determining which parts of the scene intersect with the ray. Typically we will only be interested in the first - ie. closest to our view - (if any) object that the ray hits. In this example, we are going to interact with our car model - if the mouse has “picked” it, we will start a simple rotation on it.

Note that when our “ray” hits the first object, rather than giving us a single node, we are in fact returned a NodePath - a collection of nodes defining the path from the node we picked, up to the scene graph root. 3D models are often made up of a number of nodes, and loaded into the scene graph that way - for example a car model may have a subgraph of a “body” node and four “wheel” nodes. Thus the NodePath may include some or all of these nodes, depending on the part of the car we “clicked” on. We need to take this into account when defining our mouse picking functionality - which node we want from the NodePath depends on the behaviour we want - here we want to be able to rotate the entire model by clicking anywhere on it, thus the Node we want from the NodePath is the car model’s parent MatrixTransform node.

A simple way to achieve this is to make use of our previous “naming” of this node - we traverse the NodePath until we find the node with description equal to “CAR”. We then obtain its AnimationPathCallback - if this is paused we “un-pause” it, and vice-versa.

Our MouseEventHandler class code is as follows. To recap, it first receives the mouse event, performs picking to determine what object (if any) has been clicked on, and if the car model has been picked, starts/stops the rotation on it.

/**
MOUSE EVENT HANDLER:
To handle keyboard and mouse events in OSG, we need to define a subclass
of osgGA::GUIEventHandler, and override the handle() method with custom code.
**/
class MouseEventHandler : public osgGA::GUIEventHandler {

    protected:

    float _mX, _mY;

    osgART::Scene* _scene;


    public:

    MouseEventHandler(osgART::Scene* scene) : osgGA::GUIEventHandler() {_scene=scene;}


    /** OVERRIDE THE HANDLE METHOD:
        The handle() method should return true if the event has been dealt with
        and we do not wish it to be handled by any other handler we may also have
        defined. Whether you return true or false depends on the behaviour you
        want - here we have no other handlers defined so return true. **/
    virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& aa) {

       //osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);

        switch (ea.getEventType()) {


            /** HANDLE MOUSE EVENTS:
                Mouse events have associated event names, and mouse
                position co-ordinates. **/
            case osgGA::GUIEventAdapter::RELEASE: {


                /** PERFORM SOME ACTION:
                    Once we have received the mouse event, we can
                    perform some action. Here we perform mouse picking.

                    Mouse picking can be thought of as shooting a "ray"
                    from the mouse position into our scene, and determining
                    which parts of the scene intersect with the ray.
                    Typically we will only be interested in the firstfalse
                    (if any) object that the ray hits. We can then perform
                    some action.

                    In this example, we are going to interact with our
                    car model - if the mouse has "picked" it, we will
                    start a simple rotation on it. **/



                /** PERFORM MOUSE PICKING:
                    In OSG, mouse picking can be done in a few slightly
                    different ways, however, the OSG documentation recommends
                    using the following steps: **/


                /** 1. Create either a PolytopeIntersector, or a LineIntersector
                    using the normalized mouse co-ordinates.**/
                osg::ref_ptr<osgUtil::LineSegmentIntersector> lI = new
                    osgUtil::LineSegmentIntersector(osgUtil::Intersector::PROJECTION,
                        ea.getXnormalized(), ea.getYnormalized());

                /** 2. Create an IntersectionVisitor, passing the
                    Intersector as parameter to the constructor. **/
                osgUtil::IntersectionVisitor iV(lI);

                /** 3. Launch the IntersectionVisitor on the root node of
                    the scene graph. In an OSGART application, we can
                    launch it on the osgART::Scene node. **/
                _scene->accept(iV);

                /** 4. If the Intersector contains any intersections
                    obtain the NodePath and search it to find the
                    Node of interest. **/

                if (lI->containsIntersections()) {
                    osg::NodePath nP = lI->getFirstIntersection().nodePath;


                    /** Here we search the NodePath for the node of interest.
                        This is where we can make use of our node naming.

                        If we find the Transform node named "CAR", we obtain its
                        AnimationPathCallback - if it is currently paused
                        we "un-pause" it, and vice-versa. **/
                    for (int i = 0; i <= nP.size(); i++) {
                        if (nP[i]->getName() == "CAR") {
                            osg::AnimationPathCallback* cb =
                                dynamic_cast<osg::AnimationPathCallback*>(nP[i]->getUpdateCallback());

                            if (cb->getPause()==true)
                                cb->setPause(false);
                            else cb->setPause(true);

                            return true;
                        }
                    }
                }
            }
            default: return false;
        }
    }
};

MousePickingGraph.png