OpenSceneGraph 漫游

什么是漫游

在OSG中漫游就是以第一人称或者第三人称视角游览你自定义的三维场景

漫游器

image.png

OSG中漫游器分为很多种:

  • 轨迹球漫游器(osgGA::TrackballManipulator类):该漫游器只考虑如何将观察者(相机)平滑的交换到指定位置和姿态,而不考虑真实世界里我们能否通过步行或交通工具来实现------如从外太空直接飞跃至地面上。

  • 交通工具漫游器:在漫游时模拟一些真实的交通工具,如汽车漫游模拟器(osgGA::DriveManipulator类),飞机漫游模拟器(osgGA::FlightManipulator类),UFO漫游模拟器(osgGA::UFOManipulator类)。

  • 沿路径漫游(osgGA::AnimationPathManipulator类):用户事先指定一条穿过场景的路线,并沿着这条路线为观察者(相机)导航。

  • 基于目标漫游(osgGA::NodeTrackerManipulator类):用户的各种姿态和位置都围绕一个指定位置进行。

image.png
image.png

漫游器的设置就是在对控制相机的位置和姿态的矩阵进行设置。 因此在OSG中使用派生自GUIEventHandler的基数类CameraManlpulator类控制和管理漫游器。

image.png

上面打星的函数方法共4个虚函数,这四个虚函数在派生类中必须重写,可以不实现,即函数体可以为空。

为已知视景器viewer设置一个新的轨迹球漫游器:
image.png

自定义漫游器

要想自定义一个漫游器有以下步骤:

image.png

相机位置姿态矩阵

image.png

碰撞检测

关于碰撞详细可以参考这篇文章:OpenSceneGraph 事件响应处理与碰撞
image.png

自定义漫游器实例

  • 功能:正常的漫游,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;
}

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

freejackman

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值