OSG interaction: select the scene model and highlight it

1. Purpose

You can select the specified model entity in the osg view and highlight it. There are two types, one is mouse click selection and the other is frame selection.

2. Mouse click

2.1 Function Description

  1. Generate two groups of objects, one group of cow objects can be selected, and the other group of robots cannot be selected;
  2. Click the cow object to be selected and highlighted, click the robot to be selected and not highlighted;
  3. Click on the blank space, and “select nothing!” will pop up to prompt that no entity has been selected;

Remarks:
There is a bug: when a cow is clicked, another cow generated through the osg::PositionAttitudeTransform matrix transformation node is also highlighted. We will take the time to study the cause and solve it later.

2.2 Effect

The effect is as follows:

2.3 Source Code

#include <osg/Geode>
#include <osg/Geometry>
#include <osg/LineWidth>
#include <osgViewer/Viewer>
#include <osgViewer/CompositeViewer>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgViewer/ViewerEventHandlers>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/LineWidth>
#include <osgViewer/Viewer>
#include <osg/Node>
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/CompositeViewer>
#include <osg/PositionAttitudeTransform>
#include <osg/MatrixTransform>
#include <osgFX/Scribe>
#include <osgParticle/PrecipitationEffect>
#include <osg/NodeCallback>
#include <osg/DrawPixels>
#include <osg/ShapeDrawable>
#include <osg/ComputeBoundsVisitor>
#include <osgGA/TrackballManipulator>
#include <osgGA/StateSetManipulator>
#include <osg/GraphicsContext>
#include <osgViewer/GraphicsWindow>
#include <iostream>
#include <osgFX/Scribe>
#include <osgFX/Outline>
#include <osgViewer/ViewerEventHandlers>

#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")
#pragma comment(lib, "osgSimd.lib")
#pragma comment(lib, "osgFXd.lib")

class nodePick :public osgGA::GUIEventHandler
{<!-- -->
virtual bool handle(const osgGA::GUIEventAdapter & amp; ea, osgGA::GUIActionAdapter & amp; aa)
{<!-- -->
osgViewer::Viewer* viewer = dynamic_cast<osgViewer::Viewer*> ( & amp;aa);
switch (ea.getEventType())
{<!-- -->
case osgGA::GUIEventAdapter::PUSH:
{<!-- -->
osgUtil::LineSegmentIntersector::Intersections intersections;
osg::ref_ptr<osg::Node> node = new osg::Node();
osg::ref_ptr<osg::Group> parent = new osg::Group();
osg::ref_ptr<osg::Group> group0 = dynamic_cast<osg::Group*>(viewer->getSceneData()->asGroup()->getChild(0));
if (viewer->computeIntersections(ea.getX(), ea.getY(), intersections))
{<!-- -->
//Get the selected node
osgUtil::LineSegmentIntersector::Intersection intersection = *intersections.begin();
osg::NodePath & amp; nodePath = intersection.nodePath;
node = nodePath.back();

// Determine the group it belongs to
osg::ref_ptr<osg::Group> group = dynamic_cast<osg::Group*>(nodePath[2]);
if (group->getName() == "groupCow")
{<!-- -->
//Click on the node to switch highlighting
parent = dynamic_cast<osg::Group*> (nodePath[nodePath.size() - 2]);//The parent node of the currently selected node
osgFX::Outline* ot = dynamic_cast<osgFX::Outline*>(parent.get());
//If ot does not exist (not highlighted) (node->parent)=>(node->outline->parent)
if (!ot)
{<!-- -->
osg::ref_ptr<osgFX::Outline> outline = new osgFX::Outline();
outline->setColor(osg::Vec4(1, 1, 0, 1));
outline->setWidth(5);
outline->addChild(node);
parent->replaceChild(node, outline);
}
//If ot exists (highlight) find the parent node of the current outline (node->outline->*itr) => (node->*itr)
else
{<!-- -->
osg::Node::ParentList parentList = ot->getParents();
osg::Node::ParentList::iterator itr = parentList.begin();
(*itr)->replaceChild(ot, node);
}
}
}
else
{<!-- -->
std::cout << "select nothing!" << std::endl;
}
}
default:
return false;
}
}
};

int main()
{<!-- -->
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
osg::ref_ptr<osg::Group> root = new osg::Group;

osg::ref_ptr<osg::Group> groupCow = new osg::Group;
groupCow->setName("groupCow");

osg::ref_ptr<osg::Group> groupRobot = new osg::Group;
groupRobot->setName("groupRobot");

osg::ref_ptr<osg::Node> cowNode = osgDB::readNodeFile("D:\OpenSceneGraph_Data\cow.osg");
osg::ref_ptr<osg::Node> robotNode = osgDB::readNodeFile("D:\OpenSceneGraph_Data\\robot.osg");

osg::ref_ptr<osg::PositionAttitudeTransform> cowpatNode = new osg::PositionAttitudeTransform();
cowpatNode->setPosition(osg::Vec3(0, 0, 0));
cowpatNode->addChild(cowNode.get());
cowpatNode->setName("cow1");

osg::ref_ptr<osg::PositionAttitudeTransform> cowpatNode2 = new osg::PositionAttitudeTransform();
cowpatNode2->setPosition(osg::Vec3(10, 0, 0));
cowpatNode2->addChild(cowNode.get());
cowpatNode2->setName("cow2");

osg::ref_ptr<osg::PositionAttitudeTransform> robotpatNode = new osg::PositionAttitudeTransform();
robotpatNode->setPosition(osg::Vec3(0, 10, 0));
robotpatNode->addChild(robotNode.get());

osg::ref_ptr<osg::PositionAttitudeTransform> robotpatNode2 = new osg::PositionAttitudeTransform();
robotpatNode2->setPosition(osg::Vec3(10, 10, 0));
robotpatNode2->addChild(robotNode.get());

groupCow->addChild(cowpatNode);
groupCow->addChild(cowpatNode2);

groupRobot->addChild(robotpatNode);
groupRobot->addChild(robotpatNode2);

root->addChild(groupCow);
root->addChild(groupRobot);

viewer->setSceneData(root.get());
viewer->addEventHandler(new nodePick);
viewer->addEventHandler(new osgViewer::WindowSizeHandler());//F key controls full/half screen

viewer->run();
}

3. Mouse frame selection

Refer to the blog code of osgChina webmaster Yang Shixing.

3.1 Function Description

  1. Generate 20 sphere objects that can be selected;
  2. Click the mouse + ctrl key to draw a frame selection;
  3. The selected entity turns red;

Remarks:
There are bugs: stuck, pressing the F shortcut key to exit full screen, the drawn frame will deviate from the mouse position and not be dragged with the mouse, the practicality is not strong and needs to be improved.

3.2 Effect

The effect is as follows:

3.3 Source Code

#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osg/Camera>
#include <osgGA/GUIEventHandler>
#include <osg/Geode>
#include <osg/ShapeDrawable>
#include <osg/Geometry>
#include <algorithm>


//If the mask is NODE_NPICK, PICK is not accepted. If the mask is NODE_PICK, PICK is accepted.
#define NODE_NPICK ~0x0F
#define NODE_PICK 0x0F

//Draw some balls for selection
class MySphere : public osg::Geode
{<!-- -->
public:
MySphere(osg::Vec3 center, float radius)
{<!-- -->
_bSelect = false;
_sd = new osg::ShapeDrawable(new osg::Sphere(center, radius));
_sd->setColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
addDrawable(_sd);
setNodeMask(NODE_PICK);
}

//Set whether the ball is selected
void setSelect(bool bSelect)
{<!-- -->
if (_bSelect == bSelect)
{<!-- -->
return;
}

_bSelect = bSelect;
if(_bSelect)
{<!-- -->
_sd->setColor(osg::Vec4(1.0, 0.2, 0.2, 1.0));
setNodeMask(NODE_NPICK);
}
else
{<!-- -->
_sd->setColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
setNodeMask(NODE_PICK);
}

//Redraw
_sd->dirtyDisplayList();
}

osg::ShapeDrawable* _sd;
bool _bSelect;
};

//scene for selection
osg::Node* g_selectNode = nullptr;

//Draw many balls
osg::Node* BuildScene()
{<!-- -->
osg::Group* root = new osg::Group;

//These balls can be selected
g_selectNode = root;

//Draw 100 balls
for (int i = 0; i < 20; i + + )
{<!-- -->
osg::Vec3 center(::rand() % 100, ::rand() % 100, ::rand() % 100);
float r = ::rand() % 10 + 1.0;
root->addChild(new MySphere(center, r));
}

return root;
}

//Clear the click status
void ClearSelect()
{<!-- -->
for (int i = 0; i < 20; i + + )
{<!-- -->
((MySphere*)g_selectNode->asGroup()->getChild(i))->setSelect(false);
}
}

//Node mask, display and hide
#define NODE_SHOW ~0x0
#define NODE_HIDE 0x0

//The selection box is used as a global variable, which is convenient to use.
osg::Geometry* g_geomSelectBox = new osg::Geometry;

//
osg::Camera* createHUD(osg::Viewport* vp)
{<!-- -->
osg::Camera* camera = new osg::Camera;

//Set the projection matrix to orthogonal projection
camera->setProjectionMatrix(osg::Matrix::ortho2D(0, vp->width(), 0, vp->height()));

//Set its observation matrix to the identity matrix and do not change it. The camera will always be displayed and there is no need to participate in picking.
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
camera->setViewMatrix(osg::Matrix::identity());

//Only clear the depth cache so that its display content can be based on the main camera as the background
camera->setClearMask(GL_DEPTH_BUFFER_BIT);

//Last rendering, because the content displayed by the main camera needs to be the background
camera->setRenderOrder(osg::Camera::POST_RENDER);

//No need to respond to events
camera->setAllowEventFocus(false);

//Draw selection box
osg::Geode* gnode = new osg::Geode;
camera->addChild(gnode);

gnode->addDrawable(g_geomSelectBox);
//Set transparent
osg::StateSet* ss = gnode->getOrCreateStateSet();
ss->setMode(GL_BLEND, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
ss->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);

//Set vertices
osg::Vec3Array* vertices = new osg::Vec3Array;
float depth = -0.1;
vertices->push_back(osg::Vec3(0, 0, depth));
vertices->push_back(osg::Vec3(100, 0, depth));
vertices->push_back(osg::Vec3(100, 100, depth));
vertices->push_back(osg::Vec3(0, 100, depth));
g_geomSelectBox->setVertexArray(vertices);

//Set color
osg::Vec4Array* color = new osg::Vec4Array;
color->push_back(osg::Vec4(0.8, 0.8, 0.8, 0.2));
g_geomSelectBox->setColorArray(color, osg::Array::BIND_OVERALL);

//Draw the box
g_geomSelectBox->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));

return camera;
}

class MyEvent : public osgGA::GUIEventHandler
{<!-- -->
public:
MyEvent() :osgGA::GUIEventHandler(),
_xStart(0),
_yStart(0)
{<!-- -->
}

virtual bool handle(const osgGA::GUIEventAdapter & amp; ea, osgGA::GUIActionAdapter & amp; aa)
{<!-- -->
//Hold down the left ctrl and click with the mouse to enter the drawing state
if (ea.getEventType() == ea.PUSH) //
{<!-- -->
//Determine whether the left CTRL key is pressed
if (ea.getModKeyMask() == ea.MODKEY_LEFT_CTRL)
{<!-- -->
_xStart = ea.getX();
_yStart = ea.getY();
//Clear the previously drawn results, just hide them here
g_geomSelectBox->setNodeMask(NODE_HIDE);

//Clear the clicked content and start over
_pickArea.clear();
ClearSelect();

//Returning true means that the subsequent event handler, including the operator, will no longer process the event, thus realizing that the scene does not move during dragging.
return true;
}
}

//When the left ctrl is clicked, it enters the selection state, starts drawing the selection box, and the operator no longer processes mouse drag events.
if (ea.getEventType() == ea.DRAG) //Mouse dragging, dragging moves the mouse when the mouse button is pressed
{<!-- -->
//Determine whether the left CTRL key is pressed
if (ea.getModKeyMask() == ea.MODKEY_LEFT_CTRL)
{<!-- -->
//Start drawing and adjust the vertex parameters
g_geomSelectBox->setNodeMask(NODE_SHOW);

//Get vertices, update vertices
osg::Vec3Array* vertices = (osg::Vec3Array*)g_geomSelectBox->getVertexArray();
int xEnd = ea.getX();
int yEnd = ea.getY();
float depth = -0.1;
vertices->at(0).set(_xStart, _yStart, depth);
vertices->at(1).set(xEnd, _yStart, depth);
vertices->at(2).set(xEnd, yEnd, depth);
vertices->at(3).set(_xStart, yEnd, depth);

//redraw
g_geomSelectBox->dirtyDisplayList();

int xMin = _xStart > ea.getX() ? ea.getX() : _xStart;
int xMax = _xStart < ea.getX() ? ea.getX() : _xStart;
int yMin = _yStart > ea.getY() ? ea.getY() : _yStart;
int yMax = _yStart < ea.getY() ? ea.getY() : _yStart;

//Push the selected area into _pickArea
for (int i = xMin; i <= xMax; i + = 5)
{<!-- -->
for (int j = yMin; j <= yMax; j + = 5)
{<!-- -->
if (!isPick(i, j))
{<!-- -->
_pickArea.push_back(osg::Vec2i(i, j));
//Carry out pick
osgViewer::View* view = dynamic_cast<osgViewer::View*>( & amp;aa);
osgUtil::LineSegmentIntersector::Intersections intersections;
//Only pick and NODE_PICK match & get true
if (view->computeIntersections(i, j, intersections, NODE_PICK))
{<!-- -->
for (osgUtil::LineSegmentIntersector::Intersections::iterator iter = intersections.begin();
iter != intersections.end(); iter + + )
{<!-- -->
osg::NodePath np = iter->nodePath;

MySphere* ms = dynamic_cast<MySphere*>(np.at(np.size() - 1));
if(ms)
{<!-- -->
ms->setSelect(true);
}
}
}
}

}
}

//Returning true means that the subsequent event handler, including the operator, will no longer process the event, thus realizing that the scene does not move during dragging.
return true;
}
}

return false;
}

//Check whether a point has been picked. If true is returned, it means it has been picked.
bool isPick(int x, int y)
{<!-- -->
for (int i = 0; i < _pickArea.size(); i + + )
{<!-- -->
if ((_pickArea.at(i).x() == x) & amp; & amp; (_pickArea.at(i).y() == y))
{<!-- -->
return true;
}
}

return false;
}

int _xStart, _yStart;

//When the mouse is pressed, drag again to press all the points in the selected area for pick.
std::vector<osg::Vec2i> _pickArea;
};


int main()
{<!-- -->
osgViewer::Viewer viewer;

osg::Group* root = new osg::Group;

root->addChild(BuildScene());

viewer.setSceneData(root);
viewer.realize();

//After realize, the context has been initialized and the viewport size can be obtained
//Get the viewport size here to make the viewport size consistent with the creation when creating the HUD
osg::Viewport* vp = viewer.getCamera()->getViewport();
root->addChild(createHUD(vp));

viewer.addEventHandler(new MyEvent);
viewer.addEventHandler(new osgViewer::WindowSizeHandler());//F key controls full/half screen

return viewer.run();
}