一. 操作器
OSG经常用到的自带操作器为TrackBall, osgEarth经常用到的自带操作器为EarthManipulator
自己要写操作器应继承于osgGA::CameraManipulator
流程:1. 设置自定义操作器 2. viewer在帧绘制时候取操作器控制的矩阵,(矩阵控制一般重载getMatrix()和getInverseMatrix()函数),然后更新camera 3. 有事件发生则调用handle更新矩阵
二. 碰撞检测
原理:老点与新点之间的连线是否与模型或任何node相交。
osg中有一个完成该碰撞检测的类:osgUtil::IntersectVisitor
三. 上楼梯
原理: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<osgViewer::Viewer*>(&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<osg::LineSegment> 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<osgUtil::IntersectionVisitor> iv = new osgUtil::IntersectionVisitor;//碰撞检测类
osg::ref_ptr<osgUtil::LineSegmentIntersector> 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<osg::LineSegment> 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<osgViewer::Viewer> 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();
}