Skip to content

Latest commit

 

History

History

05.NodeSelection

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Table of contents

This example is part of OpenSceneGraph cross-platform examples.

In this example we implement node selection.

Note: this example requires 02.TextureImage example knowledge.

OpenSceneGraph provides windowing system for desktop (Linux, macOS, Windows) and iOS platforms. Input is working out of the box there.

However, OpenSceneGraph does not have windowing systems for Android and Web (Emscripten), so we need to handle input events ourselves for these platforms.

5.1.1. Android

Receive events at Java side

First, implement View.OnTouchListener interface (source code):

- - - -
public class MainActivity
- - - -
    implements ... View.OnTouchListener
- - - -

Second, listen to touch events (source code):

- - - -
EGLview renderer = (EGLview)findViewById(R.id.render_surface);
renderer.setOnTouchListener(this);
- - - -

Redirect events to C++ side

Implement onTouch() function to handle events and redirect them to C++ side (source code):

- - - -
@Override
public boolean onTouch(View view, MotionEvent event)
{
    int action = event.getAction() & event.ACTION_MASK;
    float x = event.getX(0);
    float y = event.getY(0);
    switch (action)
    {
        case MotionEvent.ACTION_DOWN:
        {
            library.handleMousePress(true, x, y);
            return true;
        }
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
        {
            library.handleMousePress(false, x, y);
            return true;
        }
- - - -

Handle events at C++ side

First, implement handleMousePress() function in the native library (source code):

- - - -
JNI_FUNC(handleMousePress)(JNI_ARG, jboolean down, jfloat x, jfloat y)
{
    example->app->handleMousePress(down == JNI_TRUE, x, y);
}
- - - -

Second, pass mouse events to OpenSceneGraph's event queue (source code):

- - - -
auto queue = this->viewer->getEventQueue();
float correctedY = (this->windowHeight - y);
if (down)
{
    queue->mouseButtonPress(x, correctedY, 1 /* LMB */);
}
else
{
    queue->mouseButtonRelease(x, correctedY, 1 /* LMB */);
}
- - - -

Notes:

  • we correct Y coordinate taking render window height into account
  • we report all taps as left mouse button for simplicity

5.1.2. Web

Receive SDL events

Receive SDL events (source code):

- - - -
SDL_Event e;
while (SDL_PollEvent(&e))
{
    example->app->handleEvent(e);
}
- - - -

Detect mouse and finger events

SDL makes clear distinction between mouse and finger events, so we need to know which ones to accept (source code):

- - - -
// Detect finger events.
if (
    e.type == SDL_FINGERMOTION ||
    e.type == SDL_FINGERDOWN ||
    e.type == SDL_FINGERUP
) {
    this->fingerEventsDetected = true;
}
// Handle mouse events unless finger events are detected.
if (!this->fingerEventsDetected)
{
    return this->handleMouseEvent(e, queue);
}
// Handle finger events.
return this->handleFingerEvent(e, queue);
- - - -

Handle mouse and finger events

First, handle mouse events (source code):

- - - -
bool handleMouseEvent(const SDL_Event &e, osgGA::EventQueue &queue)
{
    switch (e.type)
    {
        case SDL_MOUSEMOTION: {
            auto correctedY = -(this->windowHeight - e.motion.y);
            queue.mouseMotion(e.motion.x, correctedY);
            return true;
        }
        case SDL_MOUSEBUTTONDOWN: {
            auto correctedY = -(this->windowHeight - e.button.y);
            queue.mouseButtonPress(e.button.x, correctedY, e.button.button);
            return true;
        }
        case SDL_MOUSEBUTTONUP: {
            auto correctedY = -(this->windowHeight - e.button.y);
            queue.mouseButtonRelease(e.button.x, correctedY, e.button.button);
            return true;
        }
- - - -

Second, handle finger events (source code):

- - - -
bool handleFingerEvent(const SDL_Event &e, osgGA::EventQueue &queue)
{
    int absX = this->windowWidth * e.tfinger.x;
    int absY = this->windowHeight * e.tfinger.y;
    auto correctedY = -(this->windowHeight - absY);
    switch (e.type)
    {
        case SDL_FINGERMOTION:
            queue.mouseMotion(absX, correctedY);
            return true;
        case SDL_FINGERDOWN:
            queue.mouseButtonPress(absX, correctedY, e.tfinger.fingerId);
            return true;
        case SDL_FINGERUP:
            queue.mouseButtonRelease(absX, correctedY, e.tfinger.fingerId);
            return true;
- - - -

Let's handle OpenSceneGraph mouse events with the help of Mouse:

  • implements osgGA::GUIEventAdapter::handle() to be able to accept OpenSceneGraph events
  • is registered to osgViewer::Viewer to actually accept events
  • keeps current mouse position and a list of pressed mouse buttons
  • reports changes in mouse position and pressed buttons

To tell selectable scene nodes from non-selectable ones apart, we need to mark necessary ones as selectable. One way to do so is to use node masks.

By default, each scene node has 0xFFFFFFFF mask. Let's exclude specific bit from those scene nodes that we want to mark as selectable. Since our scene only contains a single box node, we mark the whole scene (source code):

- - - -
const unsigned int selectionNodeMask = 0x00000004;
- - - -
// Make box node selectable by excluding specific node mask.
this->scene->setNodeMask(
    this->scene->getNodeMask() & ~this->selectionNodeMask
);
- - - -

To find a node at mouse position, we check what mouse position intersects with from the point of view of the camera (source code):

- - - -
// Find intersections.
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector =
    new osgUtil::LineSegmentIntersector(
        osgUtil::Intersector::WINDOW,
        position.x(),
        position.y()
    );
osgUtil::IntersectionVisitor iv(intersector.get());
camera->accept(iv);
- - - -
// Get closest intersection.
auto intersection = intersector->getFirstIntersection();
for (auto node : intersection.nodePath)
{
    // Make sure node mask is excluded.
    if ((node->getNodeMask() & excludedNodeMask) != excludedNodeMask)
    {
        return node;
    }
}
- - - -

Use LinearInterpolator to rotate the node (source code):

- - - -
// Get current box rotation along X.
auto rot = scene::simpleRotation(this->scene);
auto srcX = rot.x();

// Configure interpolation.
this->interpolator.keyValues = {
    {0, srcX},
    {0.5, srcX + 45}, // Rotate by 45 degrees in 0.5 seconds.
    {2, srcX}, // Rotate back in 2 - 0.5 = 1.5 seconds.
};
- - - -

Screenshot

Here's a web build of the example.