进行人机交互时,经常需要拾取场景中的点、线、面,而osg中提供了LineSegmentIntersector和PolytopeIntersector两种相交检测方法。
LineSegmentIntersector的实现原理是利用射线与场景求交,因此只能检测射线与平面是否相交;
PolytopeIntersector的实现原理是构造一个五个面的视锥体,检测在场景中与视锥体相交的物体。
本文中使用osgEarth::IntersectionPicker实现点、线、面的求交。
下面的代码既可以拾取单个物体,又可以拾取多个物体,还可以取消拾取。
鼠标左键单击:拾取单个物体;
ctrl+鼠标左键单击:拾取多个物体;
鼠标右键单击:取消所有拾取到的物体;
ctrl+鼠标右键单击:取消单个拾取到的物体。
#pragma once
#include <osgGA/GUIEventHandler>
#include <osgText/Text>
#include <osgViewer/Viewer>
#include <vector>
#include<set>
class PickHandler:public osgGA::GUIEventHandler
{
public:
PickHandler(osgText::Text* updateText) :
_updateText(updateText) {
_isFirst = true;
_pointSphere = new osg::Group;
}
~PickHandler() {}
virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa);
//拾取
virtual void pick(osgViewer::View* view, const osgGA::GUIEventAdapter& ea,bool IsSelect=true);
void setLabel(const std::string& name)
{
if (_updateText.get()) _updateText->setText(name);
}
protected:
private:
osg::ref_ptr<osg::Group> _root;//作相交检测的根节点
std::set<osg::ref_ptr<osg::Geometry>> _allGeom;//拾取到的图形
osg::ref_ptr<osg::Group> _pointSphere;//新绘制的球形点
osg::ref_ptr<osgText::Text> _updateText;
osgViewer::Viewer* _viewer;
bool _isFirst;
std::map<osg::Geometry*, std::vector<osg::Geode*>> _mapSphere;
};
#include "CIntersectPLF.h"
#include<osgEarth/IntersectionPicker>
#include <osg/ShapeDrawable>
#include <osg/Material>
#include <osgEarth/GLUtils>
using namespace osgEarth;
bool PickHandler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
{
_viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
_root = _viewer->getSceneData()->asGroup();
if (_isFirst && _root)
{
_root->addChild(_pointSphere);
_isFirst = false;
}
//左键单选
if (ea.getEventType()==ea.PUSH)
{
if (ea.getButtonMask()==ea.LEFT_MOUSE_BUTTON)
{
//继续选择
if (ea.getModKeyMask() == ea.MODKEY_LEFT_CTRL)
{
pick(_viewer, ea);
return false;
}
else//重新选择一个
{
//清空原有的内容
//清除包围球
_pointSphere->removeChildren(0, _pointSphere->getNumChildren());
_mapSphere.clear();
//清除透明面片和物体
for (auto itor=_allGeom.begin();itor!=_allGeom.end();itor++)
{
osg::StateSet* state = (*itor)->getOrCreateStateSet();
state->setMode(GL_BLEND, osg::StateAttribute::ON);
}
_allGeom.clear();
pick(_viewer, ea);
return false;
}
}
else if(ea.getButtonMask()==ea.RIGHT_MOUSE_BUTTON)
{
//单个取消
if (ea.getModKeyMask() == ea.MODKEY_LEFT_CTRL)
{
pick(_viewer, ea, false);
return false;
}
else//全部取消
{
//清空所有内容 移除球、移除透明
_pointSphere->removeChildren(0, _pointSphere->getNumChildren());
_mapSphere.clear();
//清除透明面片和物体
for (auto itor = _allGeom.begin(); itor != _allGeom.end(); itor++)
{
osg::StateSet* state = (*itor)->getOrCreateStateSet();
state->setMode(GL_BLEND, osg::StateAttribute::OFF);
}
_allGeom.clear();
return false;
}
}
}
return false;
}
void PickHandler::pick(osgViewer::View* view, const osgGA::GUIEventAdapter& ea, bool isSelect)
{
std::string gdlist = "";
IntersectionPicker picker(view, _root);
IntersectionPicker::Hits hits;
if (picker.pick(ea.getX(), ea.getY(), hits))
{
for (auto itor = hits.begin(); itor != hits.end(); itor++)
{
std::ostringstream os;
if (!itor->nodePath.empty() && !(itor->nodePath.back()->getName().empty()))
{
os << "Object \"" << itor->nodePath.back()->getName() << "\"" << std::endl;
}
else if (itor->drawable.valid())
{
os << "Object \"" << itor->drawable->className() << "\"" << std::endl;
}
gdlist += os.str();
osg::Node* node = itor->nodePath.back();
if (itor->nodePath.back()->getName() == "point")
{
osg::Drawable* drawable = itor->drawable;
osg::Geometry* geomTemp = itor->drawable->asGeometry();
if (isSelect)
{
_allGeom.insert(drawable->asGeometry());
auto vertex = drawable->asGeometry()->getVertexArray();
unsigned index = vertex->getNumElements();
const GLvoid* pointer = vertex->getDataPointer();
osg::Vec3* p = (osg::Vec3*)pointer;
std::vector<osg::Geode*> tmpGeode;
for (int i = 0; i < index; i++)
{
osg::Sphere* shape = new osg::Sphere(p[i], 0.01);
osg::Geode* geode = new osg::Geode();
osg::ShapeDrawable* _shapeDrawable = new osg::ShapeDrawable(shape);
_shapeDrawable->setDataVariance(osg::Object::DYNAMIC);
geode->addDrawable(_shapeDrawable);
geode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
GLUtils::setLighting(geode->getOrCreateStateSet(), osg::StateAttribute::OFF);
_pointSphere->addChild(geode);
tmpGeode.push_back(geode);
}
_mapSphere.insert(std::pair<osg::Geometry*, std::vector<osg::Geode*>>(geomTemp, tmpGeode));
}
else
{
//恢复成未选择的状态
auto itorGeom = _allGeom.find(geomTemp);
if (itorGeom != _allGeom.end())
{
std::vector<osg::Geode*> DeleteGeode = _mapSphere[geomTemp];
for (int i = 0; i < DeleteGeode.size(); i++)
{
_pointSphere->removeChild(DeleteGeode[i]);
}
_mapSphere.erase(geomTemp);
_allGeom.erase(itorGeom);
}
}
}
else
{
osg::Geometry* geom = itor->drawable->asGeometry();
if (isSelect)
{
osg::StateSet* state = geom->getOrCreateStateSet();
state->setMode(GL_BLEND, osg::StateAttribute::ON);
osg::ref_ptr<osg::Material> mat = new osg::Material;
//漫发射光
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(1.0, 1.0, 1.0, 0.5));
//环境光
mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4(1.0, 1.0, 1.0, 0.5));
//设置材质的混合光颜色
mat->setTransparency(osg::Material::FRONT_AND_BACK, 0.5);
state->setAttributeAndModes(mat, osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
_allGeom.insert(geom);
}
else
{
auto itorGeom = _allGeom.find(geom);
if (itorGeom != _allGeom.end())
{
//先从
osg::StateSet* state = geom->getOrCreateStateSet();
state->setMode(GL_BLEND, osg::StateAttribute::OFF);
_allGeom.erase(geom);
}
}
}
}
}
}