OSG-OpenSceneGraph+VS2022自学笔记之交互
配置项目环境不再重复,可以参考我上一篇文章 OSG-OpenSceneGraph在WIN10与VS2022下的部署(OSG3.6.5+VS2022+Win10_x64
关于事件类型与响应
案例一:
鼠标右键单击时牛和飞机都隐藏,鼠标左键双击时牛和飞机都显示,按键盘上的left键显示牛,按键盘上的right键显示飞机。
显示与隐藏节点我们使用setNodeMask(bool)
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osg/Node>
class UseEventHandler:public osgGA::GUIEventHandler {
public:
virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) {
osgViewer::Viewer* viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
if (!viewer) { return false; }
switch (ea.getEventType()) { //开始判断事件类型
case osgGA::GUIEventAdapter::KEYDOWN: //键盘按下
{
if (ea.getKey() == 0xFF51) { //left按下
//setNodeMask(bool); 0:隐藏 1:显示
viewer->getSceneData()->asGroup()->getChild(0)->setNodeMask(1); //显示孩子0(牛)
viewer->getSceneData()->asGroup()->getChild(1)->setNodeMask(0); //隐藏孩子1(飞机)
}
if (ea.getKey() == 0xFF53) { //right按下
viewer->getSceneData()->asGroup()->getChild(0)->setNodeMask(0); //隐藏0
viewer->getSceneData()->asGroup()->getChild(1)->setNodeMask(1); //显示1
}
}
break;
case osgGA::GUIEventAdapter::PUSH: //鼠标PUSH
{
if (ea.getButton() == 4) { //右键
viewer->getSceneData()->asGroup()->getChild(0)->setNodeMask(0);
viewer->getSceneData()->asGroup()->getChild(1)->setNodeMask(0);
}
}
break;
case osgGA::GUIEventAdapter::DOUBLECLICK: //鼠标双击
{
if (ea.getButton() == 1) {
viewer->getSceneData()->asGroup()->getChild(0)->setNodeMask(1);
viewer->getSceneData()->asGroup()->getChild(1)->setNodeMask(1);
}
}
break;
default:
break;
}
return false;
}
};
void main() {
osgViewer::Viewer viewer;
osg::ref_ptr<osg::Group> root = new osg::Group();
//加入两个子结点 0:牛 1:飞机
root->addChild(osgDB::readNodeFile("cow.osg"));
root->addChild(osgDB::readNodeFile("glider.osg"));
viewer.setSceneData(root.get());
//加入事件处理器UseEventHandler到viewer
viewer.addEventHandler(new UseEventHandler);
viewer.realize();
viewer.run();
}
class UseEventHandler:public osgGA::GUIEventHandler
声明类UseEventHandler,它是从osgGA::GUIEventHandler
派生而来,拥有处理事件的能力,其中这种能力体现在一个虚函数handle上,所有的处理都在handle中。handle
函数,其中有两个机器重要的参数,第一个是const osgGA::GUIEventAdapter& ea
,需要注意的是const不可以漏写,因为handle
是虚函数,参数必须与父类中虚函数保持一致,该参数是用来识别各种事件类型的。第二个参数是osgGA::GUIActionAdapter& aa
,它是控制显示的参数,最重要的是它是Viewer
的祖父类,由它可以得到viewer
。osgViewer::Viewer* viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
由参数二osgGA::GUIActionAdapter
得到了viewer
switch (ea.getEventType())
开始判断事件类型case osgGA::GUIEventAdapter::KEYDOWN:
如果有键按下if (ea.getKey() == 0xFF51)
如果left键按下,就会显示场景中的孩子0(牛);隐藏场景中的孩子1(飞机)if(ea.getKey() == 0xFF53)
如果right键按下,隐藏孩子0,显示孩子1case osgGA::GUIEventAdapter::PUSH: { if (ea.getButton() == 4) { viewer->getSceneData()->asGroup()->getChild(0)->setNodeMask(0); viewer->getSceneData()->asGroup()->getChild(1)->setNodeMask(0); } } break;
如果是鼠标PUSH了,如果是鼠标右键,隐藏俩孩子case osgGA::GUIEventAdapter::DOUBLECLICK: //鼠标双击 { if (ea.getButton() == 1) { viewer->getSceneData()->asGroup()->getChild(0)->setNodeMask(1); viewer->getSceneData()->asGroup()->getChild(1)->setNodeMask(1); } } break;
如果是鼠标双击,如果是鼠标左键,显示俩孩子osg::ref_ptr<osg::Group> root = new osg::Group();
申请多个结点viewer.addEventHandler(new UseEventHandler);
加入事件处理器UseEventHandler
到viewer
OSG事件类型与响应
案例一代码中的switch(ea.getEventType())
下可以有很多种类型:
代码 | 值 | 事件类型 |
---|---|---|
NONE | 0 | 无事件 |
PUSH | 1 | 鼠标有键按下 |
RELEASE | 2 | 鼠标某键弹起 |
DOUBLECLICK | 4 | 鼠标某键双击 |
DRAG | 8 | 鼠标某键拖动 |
MOVE | 16 | 鼠标移动 |
KEYDOWN | 32 | 键盘上某键按下 |
KEYUP | 64 | 键盘上某键弹起 |
FRAME | 128 | 图像帧,与时间有关 |
RESIZE | 256 | 窗口大小改变时会有的事件 |
SCROLL | 512 | 鼠标轮滚动 |
PEN_PRESSURE | 1024 | 手写板的某事件 |
PEN_PROXIMITY_ENTER | 2048 | 手写板的某事件 |
PEN_ORIENTATION | 4096 | 手写板的某事件 |
PEN_PROXIMITY_LEAVE | 8192 | 手写板的某事件 |
CLOSE_WINDOWS | 16384 | 关闭窗口 |
QUIT_APPLICATION | 32768 | 退出程序 |
USER | 65536 | 用户定义 |
一般来说1代表鼠标左键,2代表中键,4代表右键
pick
pick主要是通过鼠标i的点击来拾取一些物体,或者判断鼠标位置在哪里。
在OSG中,pick首先需要响应鼠标单击事件,可放在case(osgGA::GUIEventAdapter::PUSH):
下
pick(float x, float y)
点击时屏幕的位置
在pick中,由这个屏幕上的点发射射线到场景中与场景中的物体相交,可能与多个物体相交,然后逐一判断与哪些物体相交,可以得到这些物体的结点,然后另以区分。
思路如下:
鼠标单击–>handle–>判断类型为PUSH–>获得屏幕上的单击位置传入pick(x,y)中–>射线与场景中的物体相交–>判断出是哪个物体,也可以得到坐标
两个问题:
-
1.如何判断射线与viewer中的物体相交
-
2.如何判断相交结点为我想要的那个结点
解决:
- 1.发出射线并相交。在OSG中有库函数,
osgViewer::View::computeIntersections
,这个函数有三个参数:屏幕x坐标,屏幕y坐标,第三个是存放被交的结点与相交的坐标结点路径等相关信息。 - 由于有存放相交射线交情景的结果集,那么只需要判断该结果集中有没有要用的结点即可
案例二
场景是一个飞机与一个加了白边的牛。点击牛白边消失。
为了简单起见没有做成单击消失再单击出来。
给物体加白边用的是osgFX::Scribe
类,要加边只要把结点加入到 它下面,然后它再加到原先该结点要加的结点下面就加边了。
注:lib配置,不知道是缺了哪个lib文件一直报错,我就把build/lib里边所有.lib文件都加上了,简单粗暴
OpenThreadsd.lib
osgd.lib
osgAnimationd.lib
osgDBd.lib
osgFXd.lib
osgGAd.lib
osgManipulatord.lib
osgParticled.lib
osgPresentationd.lib
osgShadowd.lib
osgSimd.lib
osgTerraind.lib
osgTextd.lib
osgUId.lib
osgUtild.lib
osgViewerd.lib
osgVolumed.lib
osgWidgetd.lib
-
class CPickHandler :public osgGA::GUIEventHandler
申请类来处理事件 -
CPickHandler(osgViewer::Viewer* viewer) :mViewer(viewer){}
构造函数,有参数osgViewer::Viewer*
这样就可以把viewer手动传入类中 -
virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) { switch (ea.getEventType()) { case osgGA::GUIEventAdapter::PUSH: if (ea.getButton() == 1) { Pick(ea.getX(), ea.getY()); } return true; } return false; }
判断事件为鼠标左键单击,然后进入pick函数,传入的两个参数分别是点击时的XY的屏幕坐标,在屏幕上坐标是从0\0到1\1因此从原理上可以表示无限个点。 -
osgUtil::LineSegmentIntersector::Intersections intersections;
申请一个相交测试的结果集,判断屏幕与场景相交后,得出的结果放入此中。 -
if (mViewer->computeIntersections(x, y, intersections))
利用Viewer的computeIntersections
函数来测试屏幕与场景相交的结果,存入到结果集中 -
for (osgUtil::LineSegmentIntersector::Intersections::iterator hitr = intersections.begin(); hitr != intersections.end(); ++hitr) { if (!hitr->nodePath.empty() && !(hitr->nodePath.back()->getName().empty())) { const osg::NodePath& np = hitr->nodePath; for (int i = np.size() - 1; i > 0; --i) { osgFX::Scribe* sc = dynamic_cast<osgFX::Scribe*>(np[i]); if (sc != NULL) { if (sc->getNodeMask() != 0) { sc->setNodeMask(0); } } } } }
先申请一个结果遍历器hitr
遍历该结果,如果结果不为空,就得到遍历器中的nodepath,以此可以判断path中是否有想要的结点;如果结果集中有所需要的结点,则设置隐藏该节点,其中一个动态转换,如果转换成功则左值不为NULL,否则为NULL。 -
osg::ref_ptr<osgFX::Scribe> sc = new osgFX::Scribe();
添加一个Scribe
结点,该结点下的模型会被加白描线高亮显示。
代码如下:
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osg/Group>
#include <osgFX/Scribe>
#include <osgGA/GUIEventHandler>
#include <osgUtil/LineSegmentIntersector>
//申请类来处理事件
class CPickHandler :public osgGA::GUIEventHandler {
public:
//构造函数,参数为Viewer* Viewer,可以手动传入viewer
CPickHandler(osgViewer::Viewer* viewer) :mViewer(viewer){}
virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) {
switch (ea.getEventType()) //开始判断事件类型
{
case osgGA::GUIEventAdapter::PUSH: //判断为鼠标有键按下
if (ea.getButton() == 1) { //判断按下的是鼠标左键
Pick(ea.getX(), ea.getY()); //将鼠标当前的所在的屏幕坐标传入pick
}
return true;
}
return false;
}
protected:
void Pick(float x, float y) {
//申请一个相交测试的结果集,判断屏幕与场景相交后,将结果放入其中
osgUtil::LineSegmentIntersector::Intersections intersections;
//利用Viewer的computeIntersections函数,测试屏幕与场景相交的结果,并存入结果集中
if (mViewer->computeIntersections(x, y, intersections)) {
//申请一个结果遍历器hitr遍历结果集
for (osgUtil::LineSegmentIntersector::Intersections::iterator hitr = intersections.begin();
hitr != intersections.end();
++hitr) {
//如果结果不为空,就得到遍历器中的nodepath,以此可以判断path中是否有想要的结点
if (!hitr->nodePath.empty() && !(hitr->nodePath.back()->getName().empty())) {
const osg::NodePath& np = hitr->nodePath;
for (int i = np.size() - 1; i > 0; --i) {
osgFX::Scribe* sc = dynamic_cast<osgFX::Scribe*>(np[i]);
if (sc != NULL) { //如果转换成功sc不为NULL
if (sc->getNodeMask() != 0) {
sc->setNodeMask(0); //隐藏结点
}
}
}
}
}
}
}
osgViewer::Viewer* mViewer;
};
int main(int, char*) {
osgViewer::Viewer viewer;
//申请多个结点
osg::ref_ptr<osg::Group> root = new osg::Group();
root->addChild(osgDB::readNodeFile("cessna.osg"));
osg::ref_ptr<osg::Node> cow = osgDB::readNodeFile("cow.osg");
//添加一个Scribe结点,该节点下的模型会被加白描线高亮显示
osg::ref_ptr<osgFX::Scribe> sc = new osgFX::Scribe();
sc->addChild(cow.get());
root->addChild(cow.get());
root->addChild(sc.get());
viewer.setSceneData(root.get());
viewer.addEventHandler(new CPickHandler(&viewer));
viewer.realize();
viewer.run();
return 0;
}