OSG-OpenSceneGraph+VS2022自学笔记之交互

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,显示孩子1
  • case 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);加入事件处理器UseEventHandlerviewer
OSG事件类型与响应

案例一代码中的switch(ea.getEventType())下可以有很多种类型:

表1 OSG中常用的事件类型
代码事件类型
NONE0无事件
PUSH1鼠标有键按下
RELEASE2鼠标某键弹起
DOUBLECLICK4鼠标某键双击
DRAG8鼠标某键拖动
MOVE16鼠标移动
KEYDOWN32键盘上某键按下
KEYUP64键盘上某键弹起
FRAME128图像帧,与时间有关
RESIZE256窗口大小改变时会有的事件
SCROLL512鼠标轮滚动
PEN_PRESSURE1024手写板的某事件
PEN_PROXIMITY_ENTER2048手写板的某事件
PEN_ORIENTATION4096手写板的某事件
PEN_PROXIMITY_LEAVE8192手写板的某事件
CLOSE_WINDOWS16384关闭窗口
QUIT_APPLICATION32768退出程序
USER65536用户定义

一般来说1代表鼠标左键,2代表中键,4代表右键

OSG中支持的键盘码值
![在这里插入图片描述](https://img-blog.csdnimg.cn/8ebd0f79bf13408eb2025d1af40c1cb4.png#pic_center) ![在这里插入图片描述](https://img-blog.csdnimg.cn/e553ee065dcd4c0ba50f0c406724353c.png#pic_center)

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值