Switch branches/tags
Nothing to show
Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
..
Failed to load latest commit information.
android
desktop
ios
web
CMakeLists.txt
README.md
shot.png

README.md

Table of contents

Overview

This example is part of OpenSceneGraph cross-platform examples.

In this example we implement node selection.

Note: this example requires 02.TextureImage example knowledge.

Steps

5.1. Input support across platforms

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):

- - - -
OSGCPE_JNI(handleMousePress)(OSGCPE_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;
- - - -

5.2. Introduce Mouse class

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

5.3. Mark scene node as selectable

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
);
- - - -

5.4. Find a node at mouse position

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;
    }
}
- - - -

5.5. Rotate the node upon selection

Use LinearInterpolator to rotate the node during time interval (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.
};
- - - -

Result

Screenshot

Here's a web build of the example.