osg_操作器、碰撞检测、上楼梯篇

一.  操作器

OSG经常用到的自带操作器为TrackBall, osgEarth经常用到的自带操作器为EarthManipulator

自己要写操作器应继承于osgGA::CameraManipulator

流程:1. 设置自定义操作器  2. viewer在帧绘制时候取操作器控制的矩阵,(矩阵控制一般重载getMatrix()和getInverseMatrix()函数),然后更新camera    3. 有事件发生则调用handle更新矩阵

二. 碰撞检测

osg_操作器、碰撞检测、上楼梯篇_第1张图片

原理:老点与新点之间的连线是否与模型或任何node相交。

osg中有一个完成该碰撞检测的类:osgUtil::IntersectVisitor

三. 上楼梯

osg_操作器、碰撞检测、上楼梯篇_第2张图片

原理:1. 原始位置在1,假设需要上楼梯到3

         2. 首先计算出2的点坐标,做一条垂直的虚线A,计算该直线与场景的交点;a.没有交点,说明不存在楼梯,无法移动 b.交点与点2距离较大,无法移动;c.存在一个位置点3可以移动

         3. 若c,则做一条平行于1.2连线的线段B,注意是线段。B可以往上移一点,确保点1,3之间就算有障碍依然可以移动。若B线与场景没有交点,则上楼梯成功。

osg里面可以用LineSegmentIntersector取出线段与物体的交点。

csouth.h

//操作器功能:按键盘上W、S、A、D时,视口会前后左右移动
//加入了碰撞检测


#pragma once


#include "osgGA/CameraManipulator"
#include "osgViewer/Viewer"
#include "osgUtil/IntersectVisitor"
#include "osg/LineSegment"

class CSouth : public osgGA::CameraManipulator
{
	

public:
	CSouth();
	~CSouth();

	virtual void setByMatrix(const osg::Matrixd &matrix){};//直接通过矩阵设置视口,这里不提供这个方法
	virtual osg::Matrixd getMatrix() const;
	virtual void setByInverseMatrix(const osg::Matrixd &matrix){};
	virtual osg::Matrixd getInverseMatrix() const;

	virtual bool handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &us);

	bool changePosition(osg::Vec3 delta);

	void setStep(int delta_step);//改变移动步长,使其可以加速移动

	void setMnode(osg::Node* node);

private:
	osg::Vec3 m_vPosition;  //视点当前位置
	osg::Vec3 m_vRotation;  //视点当前朝向
	int m_vStep;  //行进的步长
	float m_vRotateStep;  //代表鼠标拖动将要旋转的尺度

	//因为要在鼠标单击时进行拖动时旋转视口,因此必须纪录滑动的起点,在终点计算时要减去起点
	int m_iLeftX;
	int m_iLeftY;
	bool m_bLeftDown;//纪录左键是否按下

	osg::Node* m_node;//用作碰撞检测并将其设置为碰撞检测的节点
};


csouth.cpp

#include "csouth.h"

CSouth::CSouth()
{
	m_vPosition = osg::Vec3(0, 0, 5);
	m_vRotation = osg::Vec3(osg::PI, 0, 0);//达到站立在地面的效果,平视XY面可以设置成(0,0,0)
	m_vStep = 1.0;
	m_vRotateStep = 0.01;//旋转力度设置成0.01是一个经验值,代表旋转力度

	m_bLeftDown = false;
	m_node = 0;
}

CSouth::~CSouth()
{

}
osg::Matrixd CSouth::getMatrix() const
{
	osg::Matrixd mat;
	mat.makeTranslate(m_vPosition);
	return osg::Matrixd::rotate(m_vPosition[0], osg::X_AXIS, m_vRotation[1], osg::Y_AXIS, m_vRotation[2], osg::Z_AXIS)*mat;
}
osg::Matrixd CSouth::getInverseMatrix() const
{
	osg::Matrixd mat;
	mat.makeTranslate(m_vPosition);
	return osg::Matrixd::inverse( osg::Matrixd::rotate(m_vPosition[0], osg::X_AXIS, m_vRotation[1], osg::Y_AXIS, m_vRotation[2], osg::Z_AXIS)*mat);
}
bool CSouth::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &us)
{
	osgViewer::Viewer* view = dynamic_cast(&us);
	switch (ea.getEventType())
	{
	case osgGA::GUIEventAdapter::KEYDOWN:
	{
		if (ea.getKey() == 'w' || ea.getKey() == 'W')
		{
			//原
			//changePosition(osg::Vec3(0, m_vStep, 0));
			/*改成这一句是为了实现无论视口朝向哪里,按w视点向前走,按s视点向后退,按a视点向左,按d视点像右
			因为此时移动视点和视口当前的朝向有关。因为变量m_vRotation中纪录了与各轴的夹角,因此只需要
			将此夹角求出;将要走的步长delta变为x轴与y轴两个方向的分量
			*/
			changePosition(osg::Vec3d(m_vStep*cosf(osg::PI_2 + m_vRotation._v[2]), m_vStep*sinf(osg::PI_2 + m_vRotation._v[2]), 0));
		}
		else if (ea.getKey() == 's' || ea.getKey() == 'S')
		{
			//changePosition(osg::Vec3(0, -m_vStep, 0));
			changePosition(osg::Vec3d(-m_vStep*cosf(osg::PI_2 + m_vRotation._v[2]), -m_vStep*sinf(osg::PI_2 + m_vRotation._v[2]), 0));
		}
		else if (ea.getKey() == 'a' || ea.getKey() == 'A')
		{
			//changePosition(osg::Vec3(-m_vStep, 0,0));
			changePosition(osg::Vec3d(-m_vStep*sinf(osg::PI_2 + m_vRotation._v[2]), m_vStep*cosf(osg::PI_2 + m_vRotation._v[2]), 0));
		}
		else if (ea.getKey() == 'd' || ea.getKey() == 'D')
		{
			//changePosition(osg::Vec3(m_vStep, 0, 0));
			changePosition(osg::Vec3d(m_vStep*sinf(osg::PI_2 + m_vRotation._v[2]), -m_vStep*cosf(osg::PI_2 + m_vRotation._v[2]), 0));
		}
		else if (ea.getKey() == '+')
		{
			setStep(1);
		}
		else if (ea.getKey() == '-')
		{
			setStep(-1);
		}
		else if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Home)
		{
			changePosition(osg::Vec3(0, 0, 1.0));//视口向上升
		}
		else if (ea.getKey() == osgGA::GUIEventAdapter::KEY_End)
		{
			changePosition(osg::Vec3(0, 0, -1.0));//视口向下降
		}
	}
	break;
	case osgGA::GUIEventAdapter::PUSH:
	{
	    //单击了左键则纪录单击的位置放在m_iLeftX/m_iLeftY中,并置m_bLeftDown为真,标识当前左键以及按下
		if (ea.getButton() == osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
		{
			m_iLeftX = ea.getX();
			m_iLeftY = ea.getY();
			m_bLeftDown = true;
		}
		return false;
	}
	break;
	case osgGA::GUIEventAdapter::DRAG:
	{
		if (m_bLeftDown)
		{
			int delX = ea.getX() - m_iLeftX;
			int delY = ea.getY() - m_iLeftY;
			m_vRotation[2] -= osg::DegreesToRadians(m_vRotateStep*delX);//得到左右旋转角度
			m_vRotation[0] += osg::DegreesToRadians(m_vRotateStep*delY);//得到上下旋转角度

			//下面几句保证了向下向上旋转的范围
			if (m_vRotation[0] <= 0)
			{
				m_vRotation[0] = 0;
			}
			if (m_vRotation[0] >= osg::PI)
			{
				m_vRotation[0] = osg::PI;
			}
		}
	}
	break;
	case osgGA::GUIEventAdapter::RELEASE:
	{
		if (ea.getButton() == osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
		{
			m_bLeftDown = false;
		}
	}
	break;
	default:
		break;
	}
	return false;  //代表事件还会向下传递,否则其他事件处理器不会再收到事件
}

void CSouth::setStep(int delta_step)
{
	m_vStep += delta_step;
	if (m_vStep <= 0)
	{
		m_vStep = 0;
	}
}

//没有加上楼梯算法,加上的见下
//bool CSouth::changePosition(osg::Vec3 delta)
//{
//	//如果外部调用了m_node,需要接口做碰撞检测
//	if (m_node)
//	{
//		osg::Vec3 newPos = m_vPosition + delta;
//		osgUtil::IntersectVisitor iv;
//
//		osg::ref_ptr line = new osg::LineSegment(newPos, m_vPosition);
//		iv.addLineSegment(line.get());
//		m_node->accept(iv);
//		//判断是否碰到,如果没有碰到物体则移动,否则直接返回
//		if (!iv.hits())
//		{
//			m_vPosition += delta;
//		}
//		else
//		{
//			return false;
//		}
//	}
//
//
//	//外部不需要做碰撞检测
//	else
//	{
//		m_vPosition += delta;
//	}
//	
//	return true;
//}

//加上算楼梯算法,没加上的见上
bool CSouth::changePosition(osg::Vec3 delta)
{
	//如果外部调用了m_node,需要接口做碰撞检测
	if (m_node)
	{
		osg::Vec3 newPos = m_vPosition + delta;//代表点2

		//代表上楼梯中线A,上取80m,下取1000m
		osg::Vec3 start = osg::Vec3(newPos.x(), newPos.y(), newPos.z() + 80);
		osg::Vec3 end = osg::Vec3(newPos.x(), newPos.y(), newPos.z() -1000);

		osg::ref_ptr iv = new osgUtil::IntersectionVisitor;//碰撞检测类
		osg::ref_ptr ls = new osgUtil::LineSegmentIntersector(start, end);//可以取出结果集的线段
		
		osgUtil::LineSegmentIntersector::Intersections itersections;
		long height = newPos.z();

		iv->setIntersector(ls.get());
		m_node->accept(*(iv.get()));

		if (ls->containsIntersections())
		{
			itersections = ls->getIntersections();
			osgUtil::LineSegmentIntersector::Intersections::iterator iter = itersections.begin();
			height = iter->getLocalIntersectPoint().z() + 5;
		}
		else
		{
			return false; //如果无结果说明前面不存在楼梯,无法移动。因为上到80m下到1000m都无交点
		}

		//下面是看线段B与场景中物体是否有交点
		osg::Vec3 start2 = osg::Vec3(m_vPosition.x(), m_vPosition.y(), height);
		osg::Vec3 end2 = osg::Vec3(newPos.x(), newPos.y(), height);
		osgUtil::IntersectVisitor iv2;
		osg::ref_ptr line = new osg::LineSegment(start2, end2);
		iv2.addLineSegment(line.get());
		m_node->accept(iv2);
		if (!iv2.hits())
		{
			m_vPosition += delta;
			m_vPosition.set(m_vPosition.x(), m_vPosition.y(), height);
		}
		else
		{
			return false;
		}
	}


	//外部不需要做碰撞检测
	else
	{
		m_vPosition += delta;
	}

	return true;
}
void CSouth::setMnode(osg::Node* node)
{
	m_node = node;
}

main.cpp

int main()
{
	osg::ref_ptr viewer = new osgViewer::Viewer;
	osg::Group* root = new osg::Group;
	//root->addChild(createFloor(osg::Vec3(0, 0, 0), 200));
	root->addChild(CreateStair());//在场景放置一个楼梯用于碰撞检测
	root->addChild(createQuad());
	viewer->setSceneData(root);

	CSouth* cs = new CSouth;
	cs->setMnode(root);
	viewer->setCameraManipulator(cs);
	return viewer->run();
}







你可能感兴趣的:(osg)