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.
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);
- - - -
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;
}
- - - -
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
Receive SDL events (source code):
- - - -
SDL_Event e;
while (SDL_PollEvent(&e))
{
example->app->handleEvent(e);
}
- - - -
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);
- - - -
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.
};
- - - -
Here's a web build of the example.