什么是漫游
在OSG中漫游就是以第一人称或者第三人称视角游览你自定义的三维场景。
漫游器
OSG中漫游器分为很多种:
-
轨迹球漫游器(
osgGA::TrackballManipulator类
):该漫游器只考虑如何将观察者(相机)平滑的交换到指定位置和姿态,而不考虑真实世界里我们能否通过步行或交通工具来实现------如从外太空直接飞跃至地面上。 -
交通工具漫游器:在漫游时模拟一些真实的交通工具,如汽车漫游模拟器(
osgGA::DriveManipulator类
),飞机漫游模拟器(osgGA::FlightManipulator类
),UFO漫游模拟器(osgGA::UFOManipulator类
)。 -
沿路径漫游(
osgGA::AnimationPathManipulator类
):用户事先指定一条穿过场景的路线,并沿着这条路线为观察者(相机)导航。 -
基于目标漫游(
osgGA::NodeTrackerManipulator类
):用户的各种姿态和位置都围绕一个指定位置进行。
漫游器的设置就是在对控制相机的位置和姿态的矩阵进行设置。 因此在OSG中使用派生自GUIEventHandler
的基数类CameraManlpulator类
控制和管理漫游器。
上面打星的函数方法共4个虚函数,这四个虚函数在派生类中必须重写,可以不实现,即函数体可以为空。
为已知视景器viewer设置一个新的轨迹球漫游器:
自定义漫游器
要想自定义一个漫游器有以下步骤:
相机位置姿态矩阵
碰撞检测
关于碰撞详细可以参考这篇文章:OpenSceneGraph 事件响应处理与碰撞
自定义漫游器实例
-
功能:正常的漫游,WASD 四个键前进后退左右,UP DOWN LEFT RIGHT 四个键前进后退左进右退的,HOME 向上移, END 向地下移。+加速,-减速。
-
main.cpp
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osg/Node>
#include "South.h"
void main() {
osgViewer::Viewer viewer;
viewer.setSceneData(osgDB::readNodeFile("ceep.ive"));
viewer.setCameraManipulator(new CSouth());
viewer.realize();
viewer.run();
}
- South.h
#pragma once
#include <osgViewer/Viewer>
// #include <osgGA/MatrixManipulator>
#include <osgGA/CameraManipulator> // MatrixManipulator 在新版本中已改为 CameraManipulator
#include <osgUtil/IntersectVisitor>
#include <osg/LineSegment>
class CSouth : public osgGA::CameraManipulator {
public:
CSouth(void);
~CSouth(void);
private:
// 结点值,用来测试碰撞检测的
osg::ref_ptr<osg::Node> m_node;
// 相机操作器
unsigned int m_nID;
//移动速度
float m_fMoveSpeed;
// 位置
osg::Vec3 m_vPosition;
// 旋转角度
osg::Vec3 m_vRotation;
// 左键是否按下
bool m_bLeftButtonDown;
// 左键点下时屏幕坐标
float m_fpushX;
// 碰撞检测开启状态查询
bool m_bPeng;
// 右键点下时屏幕坐标
float m_fpushY;
public:
// 碰撞检测是否开启
void setPeng(bool peng);
// 得到碰撞检测开启状态
bool getPeng();
// 如果碰撞检测开启则关闭,如果关闭则开启
void setFpeng();
// 设置要进行碰撞检测的数据
virtual void setNode(osg::Node *);
// 虚函数
virtual void setByMatrix(const osg::Matrixd &matrix); // 虚函数
virtual void setByInverseMatrix(const osg::Matrixd &matrix);
virtual osg::Matrixd getMatrix(void) const; // 得到逆矩阵
virtual osg::Matrixd getInverseMatrix(void)const; // 主要事件控制器
virtual bool handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &us);
// 屏目角度
float m_fAngle;
// 位置变换函数
void ChangePosition(osg::Vec3 &delta);
// 得到当前速度
float getSpeed();
// 设置当前速度
void setSpeed(float);
// 设置视点位置
void SetPosition(osg::Vec3 &position);
void SetPosition(double *);
// 得到当前视点位置
osg::Vec3 GetPosition();
// 计算家的位置
void computeHomePosition();
};
- South.cpp
#include "South.h"
// 设置一些初始值
CSouth::CSouth(void) :
m_fMoveSpeed(1.5f) // 移动速度设为 1.5
, m_bLeftButtonDown(false) // 左键没有按下
, m_fpushX(0) // 左键点下时初始坐标为 0
, m_fAngle(2.5) // 初始角速度是2.5
, m_bPeng(false) // 开始时碰撞检测关闭
, m_fpushY(0) { // 右键点下时初始坐标也为0
// 出生点为 000
m_vPosition = osg::Vec3(0.0f, 0.0f, 5.0f);
// 初始角度
m_vRotation = osg::Vec3(osg::PI_2, 0.0f, 0.0f);
}
CSouth::~CSouth(void) {}
void CSouth::setByMatrix(const osg::Matrixd &matrix) {}
void CSouth::setByInverseMatrix(const osg::Matrixd &matrix) {}
// 得到矩阵,这是标准接口,用于控制场景
osg::Matrixd CSouth::getMatrix(void) const {
// 得到旋转后的矩阵,其实也就是视口矩阵,用此控制场景
osg::Matrixd mat;
mat.makeRotate(m_vRotation._v[0], osg::Vec3(1.0f, 0.0f, 0.0f),
m_vRotation._v[1], osg::Vec3(0.0f, 1.0f, 0.0f),
m_vRotation._v[2], osg::Vec3(0.0f, 0.0f, 1.0f));
return mat * osg::Matrixd::translate(m_vPosition);
}
// 得到逆矩阵,标准接口,控制场景
osg::Matrixd CSouth::getInverseMatrix(void) const {
osg::Matrixd mat;
mat.makeRotate(m_vRotation._v[0], osg::Vec3(1.0f, 0.0f, 0.0f),
m_vRotation._v[1], osg::Vec3(0.0f, 1.0f, 0.0f),
m_vRotation._v[2], osg::Vec3(0.0f, 0.0f, 1.0f));
return osg::Matrixd::inverse(mat * osg::Matrixd::translate(m_vPosition));
}
// handle
bool CSouth::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &us) {
// 得到x的初始屏幕坐标
float mouseX = ea.getX();
// 得到y的初始屏幕坐标
float mouseY = ea.getY();
// 判断事件类型
switch (ea.getEventType()) {
// 如果是鼠标按下的事件
case osgGA::GUIEventAdapter::KEYDOWN:
// 如果是空格
if (ea.getKey() == 0x20) {
//重绘
us.requestRedraw();
us.requestContinuousUpdate(false);
return true;
}
// 如果是home键,则视点向上移动
if (ea.getKey() == 0xFF50) {
ChangePosition(osg::Vec3(0, 0, m_fMoveSpeed));
return true;
}
// 如果是 end 键,同视点向下移动
if (ea.getKey() == 0xFF57) {
ChangePosition(osg::Vec3(0, 0, -m_fMoveSpeed));
return true;
}
// 如果是加号键则加速
if (ea.getKey() == 0x2B) {
m_fMoveSpeed += 1.0f;
return true;
}
// 如果是减号键则减速
if (ea.getKey() == 0x2D) {
m_fMoveSpeed -= 1.0f;
if (m_fMoveSpeed < 1.0f) {
m_fMoveSpeed = 1.0f;
}
return true;
}
// 向前走,W键,或者UP键
if (ea.getKey() == 0xFF52 || ea.getKey() == 0x57 || ea.getKey() == 0x77) {
ChangePosition(osg::Vec3(0, m_fMoveSpeed * sinf(osg::PI_2 + m_vRotation._v[2]), 0));
ChangePosition(osg::Vec3(m_fMoveSpeed * cosf(osg::PI_2 + m_vRotation._v[2]), 0, 0));
return true;
}
// 向后退,S键,或者DOWN键
if (ea.getKey() == 0xFF54 || ea.getKey() == 0x53 || ea.getKey() == 0x73) {
ChangePosition(osg::Vec3(0, -m_fMoveSpeed * sinf(osg::PI_2 + m_vRotation._v[2]), 0));
ChangePosition(osg::Vec3(-m_fMoveSpeed * cosf(osg::PI_2 + m_vRotation._v[2]), 0, 0));
return true;
}
// A
if (ea.getKey() == 0x41 || ea.getKey() == 0x61) {
ChangePosition(osg::Vec3(0, m_fMoveSpeed * cosf(osg::PI_2 + m_vRotation._v[2]), 0));
ChangePosition(osg::Vec3(-m_fMoveSpeed * sinf(osg::PI_2 + m_vRotation._v[2]), 0, 0));
return true;
}
// D
if (ea.getKey() == 0x44 || ea.getKey() == 0x64) {
ChangePosition(osg::Vec3(0, -m_fMoveSpeed * cosf(osg::PI_2 + m_vRotation._v[2]), 0));
ChangePosition(osg::Vec3(m_fMoveSpeed * sinf(osg::PI_2 + m_vRotation._v[2]), 0, 0));
return true;
}
// Right
if (ea.getKey() == 0xFF53) {
m_vRotation._v[2] -= osg::DegreesToRadians(m_fAngle);
}
// left
if (ea.getKey() == 0xFF51) {
m_vRotation._v[2] += osg::DegreesToRadians(m_fAngle);
}
// F
if (ea.getKey() == 0x46 || ea.getKey() == 0x66) {
computeHomePosition();
m_fAngle -= 0.2;
return true;
}
// G
if (ea.getKey() == 0x47 || ea.getKey() == 0x67) {
m_fAngle += 0.2;
return true;
}
return false;
// 如果是鼠标单击事件
case osgGA::GUIEventAdapter::PUSH:
// 如果是左键,记录下来,因为左键拖动时场景也要转的
if (ea.getButton() == 1) {
m_fpushX = mouseX;
m_fpushY = mouseY;
m_bLeftButtonDown = true;
}
return false;
// 如果是拖动事件
case osgGA::GUIEventAdapter::DRAG:
if (m_bLeftButtonDown) {
m_vRotation._v[2] -= osg::DegreesToRadians(m_fAngle * (mouseX - m_fpushX));
m_vRotation._v[0] += osg::DegreesToRadians(1.1 * (mouseY - m_fpushY)); //防止背过去
if (m_vRotation._v[0] >= 3.14)
m_vRotation._v[0] = 3.14;
if (m_vRotation._v[0] <= 0)
m_vRotation._v[0] = 0;
}
return false;
// 如果按键释放
case (osgGA::GUIEventAdapter::RELEASE) :
if (ea.getButton() == 1) {
m_bLeftButtonDown = false;
}
return false;
default:
return false;
}
}
// 改变位置
void CSouth::ChangePosition(osg::Vec3 &delta) {
if (m_bPeng) {
// 看新值与旧值之间的连线是否与模型有交点!
// 如果要到达的位置与现在的位置有交点的话,如果碰撞检测也开启了,就不移动。
osg::Vec3 newPos = m_vPosition + delta;
osgUtil::IntersectVisitor iv;
// 前后的线段
osg::ref_ptr<osg::LineSegment> line = new osg::LineSegment(newPos, m_vPosition);
// 上下移动的线段,加入两条线段来检测碰撞
osg::ref_ptr<osg::LineSegment> lineZ = new osg::LineSegment(newPos + osg::Vec3(0.0f, 0.0f, m_fMoveSpeed), newPos - osg::Vec3(0.0f, 0.0f, m_fMoveSpeed));
iv.addLineSegment(lineZ.get());
iv.addLineSegment(line.get());
// 接受碰撞的检测node
m_node->accept(iv);
if (!iv.hits()) {
// 如果没有碰撞,则移动旧位置到新的位置上
m_vPosition += delta;
}
} else // 如果碰撞检测根本没开,则直接移过去,
m_vPosition += delta;
}
// 得到移动速度
float CSouth::getSpeed() {
return m_fMoveSpeed;
}
// 设置移动速度
void CSouth::setSpeed(float sp) {
m_fMoveSpeed = sp;
}
// 设置视口所在位置
void CSouth::SetPosition(osg::Vec3 &position) {
m_vPosition = position;
}
void CSouth::SetPosition(double *position) {
m_vPosition._v[0] = position[0];
m_vPosition._v[1] = position[1];
m_vPosition._v[2] = position[2];
}
// 得到视口所在位置
osg::Vec3 CSouth::GetPosition() {
return m_vPosition;
}
// 设置碰撞检测所起作用的物体
void CSouth::setNode(osg::Node *node) {
m_node = node;
}
// 计算家的位置,其实是包围球的球心处
void CSouth::computeHomePosition() {
//如果有模型,则计算包围球的球心
if (m_node.get()) {
const osg::BoundingSphere &boundingSphere = m_node->getBound();
osg::Vec3 bp = boundingSphere._center;
SetPosition(bp);
}
}
// 设置碰撞检测为开启或者关闭
void CSouth::setPeng(bool peng) {
m_bPeng = peng;
}
// 得到碰撞检测的状态
bool CSouth::getPeng() {
return m_bPeng;
}
// 如果碰撞测试在开启,则关闭它,如果在关闭,则开启它
void CSouth::setFpeng() {
m_bPeng = !m_bPeng;
}