自定义一个简单的操控器类

news2024/11/19 18:47:14

目录

1. 前言

2. 操控器需求

3. 功能实现

3.1. 预备知识

3.2. 代码实现

3.3. 代码难点说明


1. 前言

       osg已经自己实现了很多操控器类,这些操控器类存放在osg源码目录下的src\osgGA目录。感兴趣的童鞋,可以自己去研究源码。下面两篇博文是我研究osg的键盘切换操控器和动画路径操控器之后写的,感兴趣的可以参考学习:

  • osg操控器之键盘切换操控器osgGA::KeySwitchMatrixManipulator。
  • osg操控器之动画路径操控器osgGA::AnimationPathManipulator分析。

        虽然说osg实现了很多常用的操控器类,但现实中的业务需求是各种各样的,当osg自己实现的操控器不能满足平时的业务需求时,就需要自己开发操控器,本博文通过实现一个简单的操控器,来说明如何定制开发自己的操控器类。

2. 操控器需求

        

 图1

       如上图,实现一个网格,网格在X、Y轴范围为[-5, 5],单元格的长宽都为0.5,网格颜色为白色,坐标轴位于(0, 0, 0)处,实现一个操控器,操控器功能必须满足如下要求:

  • 按住键盘←键,能逆时针转动整个网格。
  • 按住键盘→键,能顺时针转动整个网格。
  • 起始时视点位于(0, -15, 15)处。
  • 当鼠标滚轮朝向人的方向转动时,此时网格变大。
  • 当鼠标滚轮背向人的方向转动,此时网格变小。
  • 按住空格键,网格回到原始状态。

即操控器要能实现如下效果:

图2 

3. 功能实现

3.1. 预备知识

       三维场景中的操控器都有一个共同特点,即它们都采用实时修正观察矩阵(也就是观察者的观察位置和姿态)的方式实现平滑导航浏览。而此处又有一个重要的结论:相机在世界坐标中的位置姿态矩阵,等于观察者的观察位置和姿态的逆矩阵。假设相机在世界坐标系中的位置姿态矩阵为M,观察者在世界坐标系中的观察位置和姿态矩阵为V,则有:                 

                                    M = V的逆

根据物理学相对性原理很好理解这种互逆关系,相机向前(左)运动等同于观察点向后(右)运动,反之亦然。关于这一结论更详细的描述,请参见:浅谈在操控器类中,为何要通过osgGA::CameraManipulator的逆矩阵改变视点位置博文。 

       osg提供了操控器类的基类CameraManipulator,该基类定义了实现操控器的所有接口。如果要定制自己的操控器类,则必须根据自己的需求,重写CameraManipulator类的Virtual开头的函数。CameraManipulator存放在osg源码目录下的include\osgGA目录。

       关于场景(本例是指网格)的拉近拉远,参见:osg下如何将物体拉近拉远博文。

3.2. 代码实现

           main.cpp文件如下:

#include<osgViewer/Viewer>
#include<osgDB/readFile>
#include<osgDB/writeFile>
#include<osgDB/FileUtils>
#include "mySimpleManipulator.h"

// 创建地板网格
osg::Geode* createFloorGrid()
{
    auto pGeode = new osg::Geode;
    auto pQuardGeomery = new osg::Geometry();
    pGeode->addDrawable(pQuardGeomery);

    auto pCoordArray = new osg::Vec3dArray;

    for (auto yIndex = -5.0; yIndex <= 5.0; yIndex += 0.5)
    {
        pCoordArray->push_back(osg::Vec3d(-5.0, yIndex, 0.0));
        pCoordArray->push_back(osg::Vec3d(5.0, yIndex, 0.0));
    }

    for (auto xIndex = -5.0; xIndex <= 5.0; xIndex += 0.5)
    {
        pCoordArray->push_back(osg::Vec3d(xIndex, -5.0, 0.0));
        pCoordArray->push_back(osg::Vec3d(xIndex, 5.0, 0.0));
    }
  
    pQuardGeomery->setVertexArray(pCoordArray);
    
    // 使格子线颜色为白色
    auto pColorArray = new osg::Vec4Array;
    pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 1.0));
    pQuardGeomery->setColorArray(pColorArray, osg::Array::Binding::BIND_OVERALL);

    auto pNormalArray = new osg::Vec3Array();
    pNormalArray->push_back(osg::Vec3(0, 0, 1));
    pQuardGeomery->setNormalArray(pNormalArray, osg::Array::Binding::BIND_OVERALL);

    auto iVertSize = pCoordArray->size();
    osg::DrawElementsUShort* pElementsUShort{nullptr};
    for (auto i = 0; i < iVertSize; ++i)
    {
        if (0 == (i % 2))
        {
            pElementsUShort = new osg::DrawElementsUShort(GL_LINES);
            pQuardGeomery->addPrimitiveSet(pElementsUShort);
        }

        pElementsUShort->push_back(i);
    }

    return pGeode;
}

int main(int argc, char *argv[])
{
    osgViewer::Viewer viewer;
    auto pAxis = osgDB::readRefNodeFile(R"(E:\osg\OpenSceneGraph-Data\axes.osgt)");
    if (nullptr == pAxis)
    {
        OSG_WARN << "axes node is nullpr!";
        return 1;
    }

    auto pRoot = new osg::Group();
    pRoot->addChild(pAxis);
    viewer.setSceneData(pRoot);

    auto pFloorGrid = createFloorGrid();
    pRoot->addChild(pFloorGrid);

    // 创建一个自定义的操控器对象
    auto pMySimpleManipulator = new CMySimpleManipulator();
    pMySimpleManipulator->setNode(pRoot);
    viewer.setCameraManipulator(pMySimpleManipulator);

    // 设置相机位置,注意:最开始时相机和观察点是重合的
    pMySimpleManipulator->setHomePosition(osg::Vec3d(0.0, -15.0, 15.0), osg::Vec3d(0.0, 0.0, 0.0), osg::Vec3(0, 0, 1));
    
    viewer.run();
}

 mySimpleManipulator.h文件如下:

#pragma once
#include<osgGA/CameraManipulator>
using namespace osgGA;
class CMySimpleManipulator :
    public CameraManipulator
{
public:
    CMySimpleManipulator();
    ~CMySimpleManipulator();
public:

    /** set the position of the matrix manipulator using a 4x4 Matrix.*/
    virtual void setByMatrix(const osg::Matrixd& matrix) override;

    /** set the position of the matrix manipulator using a 4x4 Matrix.*/
    virtual void setByInverseMatrix(const osg::Matrixd& matrix) override;

    /** get the position of the manipulator as 4x4 Matrix.*/
    virtual osg::Matrixd getMatrix() const override;

    /** get the position of the manipulator as a inverse matrix of the manipulator, typically used as a model view matrix.*/
    virtual osg::Matrixd getInverseMatrix() const override;

    /** Handle events, return true if handled, false otherwise. */
    virtual bool handle(const GUIEventAdapter& ea, GUIActionAdapter& us) override;

    /**
        Move the camera to the default position.
        May be ignored by manipulators if home functionality is not appropriate.
        */
    virtual void home(const GUIEventAdapter&, GUIActionAdapter&) override;

    /** Manually set the home position, and set the automatic compute of home position. */
    virtual void setHomePosition(const osg::Vec3d& eye, 
                                 const osg::Vec3d& center, 
                                 const osg::Vec3d& up, 
                                 bool autoComputeHomePosition = false) override;
    
    
    virtual void setNode(osg::Node*) override;

private:

    void setTransformation(const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up);

    // 更新观察点位置
    void updateEyePos(const GUIEventAdapter& ea, GUIActionAdapter& us);
private:
    osg::ref_ptr<osg::Node> _spNode;
    osg::Vec3d _center;
    double _distance;
    osg::Quat _rotation;
    float _rotateZAngle{0};
    osg::Vec3d _initEye;
    float _sina{0.0};
    float _cosa{ 0.0 };
};

mySimpleManipulator.cpp文件如下:

#include "mySimpleManipulator.h"
#include<osgViewer/Viewer>
#include<iostream>
//#include <math.h>
CMySimpleManipulator::CMySimpleManipulator()
{
	
}
CMySimpleManipulator::~CMySimpleManipulator()
{

}

/** set the position of the matrix manipulator using a 4x4 Matrix.*/
void CMySimpleManipulator::setByMatrix(const osg::Matrixd& matrix)
{
	
}

/** set the position of the matrix manipulator using a 4x4 Matrix.*/
void CMySimpleManipulator::setByInverseMatrix(const osg::Matrixd& matrix)
{

}

/** get the position of the manipulator as 4x4 Matrix.*/
osg::Matrixd CMySimpleManipulator::getMatrix() const
{
    return osg::Matrixd::translate(0., 0., _distance) *
        osg::Matrixd::rotate(_rotation) *
        osg::Matrixd::translate(_center);
}

/** get the position of the manipulator as a inverse matrix of the manipulator, typically used as a model view matrix.*/
osg::Matrixd CMySimpleManipulator::getInverseMatrix() const
{
    return osg::Matrixd::translate(-_center) *
        osg::Matrixd::rotate(_rotation.inverse()) *
        osg::Matrixd::translate(0.0, 0.0, -_distance);
}
 
/** Handle events, return true if handled, false otherwise. */
bool CMySimpleManipulator::handle(const GUIEventAdapter& ea, GUIActionAdapter& us)
{
    auto pViewer = (osgViewer::Viewer*)(&us);
    switch (ea.getEventType())
    {
        case GUIEventAdapter::KEYDOWN:
        {
            int nKeyCode = ea.getKey();
            if (GUIEventAdapter::KEY_Left == nKeyCode)
            {
                _rotateZAngle += 1.0;
                updateEyePos(ea, us);
            }
            else if (GUIEventAdapter::KEY_Right == nKeyCode)
            {
                _rotateZAngle -= 1.0;
                updateEyePos(ea, us);
            }
            else if (GUIEventAdapter::KEY_Space == nKeyCode)
            {
                _homeEye = _initEye;
                _rotateZAngle = 0.0;
                home(ea, us);
            }
        }
        break;
        case GUIEventAdapter::SCROLL:
        {
            auto center = _spNode->getBound().center();
            auto scrollingMotion = ea.getScrollingMotion();
            auto scrollingIsMotion = false;
            if (GUIEventAdapter::SCROLL_DOWN == scrollingMotion) // 滚轮朝向人的方向转动
            {
                if (3 < _distance) // 防止拉得过近
                {
                    scrollingIsMotion = true;
                    _distance -= 0.5;
                }
            }
            else if (GUIEventAdapter::SCROLL_UP == scrollingMotion)// 滚轮背向人的方向转动
            {
                if (_distance < 50) // 防止拉得过远
                {
                    scrollingIsMotion = true;
                    _distance += 0.5;
                }
            }

            if (scrollingIsMotion)
            {
                _homeEye = center + osg::Vec3d(0, 0, 1) * _distance;
                pViewer->getCamera()->setViewMatrixAsLookAt(_homeEye, center, osg::Vec3(0, 0, 1));
            }

            pViewer->requiresRedraw();
            pViewer->requestContinuousUpdate(false);
        }
    } // end switch

	return CameraManipulator::handle(ea, us);
}

// 更新观察点位置
void CMySimpleManipulator::updateEyePos(const GUIEventAdapter& ea, GUIActionAdapter& us)
{
    _homeEye.z() = _distance * _sina;
    auto temp = _distance * _cosa;
    auto radians = osg::DegreesToRadians(_rotateZAngle);
    _homeEye.y() = -temp * std::cos(radians);
    _homeEye.x() = -temp * std::sin(radians);

    home(ea, us);
}

void CMySimpleManipulator::setHomePosition( const osg::Vec3d& eye,
                                            const osg::Vec3d& center,
                                            const osg::Vec3d& up,
                                            bool autoComputeHomePosition /*= false*/)
{
    _homeEye = eye;
    _homeCenter = center;
    _homeUp = up;

    // 记录观察点初始位置,以便等下按空格键能重置会初始位姿
    _initEye = _homeEye;  

    // 计算观察点即眼睛和物体中心即(0, 0, 0)点连线和Y轴负半轴的夹角的正弦值、余弦值
    auto angle = std::atan2(std::fabs(eye.z()) / std::fabs(eye.y()), 1.0);
    _sina = std::sinf(angle);
    _cosa = std::cosf(angle);
}

void CMySimpleManipulator::home(const GUIEventAdapter&ea, GUIActionAdapter&aa)
{
	setTransformation(_homeEye, _homeCenter, _homeUp);

	auto pViewer = (osgViewer::Viewer*)(&aa);
	pViewer->requiresRedraw();
	pViewer->requestContinuousUpdate(false);
}

void CMySimpleManipulator::setNode(osg::Node*pNode)
{
	_spNode = pNode;
}

void CMySimpleManipulator::setTransformation(const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up)
{
    osg::Vec3d lv(center - eye); // 相机指向物体中心(这里指世界坐标系中的原点)的向量

    osg::Vec3d f(lv);
    f.normalize();// 相机指向物体中心方向

    // 计算相机其它两个轴的方向
    osg::Vec3d s(f ^ up);
    s.normalize();

    osg::Vec3d u(s ^ f);
    u.normalize();

    // 计算相机姿态矩阵,即旋转矩阵
    osg::Matrixd rotation_matrix(s[0], u[0], -f[0], 0.0f,
                                 s[1], u[1], -f[1], 0.0f,
                                 s[2], u[2], -f[2], 0.0f,
                                 0.0f, 0.0f, 0.0f, 1.0f);

    _center = center;
    _distance = lv.length(); // // 相机距离物体中心(这里指世界坐标系中的原点)的距离
    _rotation = rotation_matrix.getRotate().inverse(); // 相机的位置姿态矩阵的逆是观察点位置姿态矩阵

}

3.3. 代码难点说明

          调用操控器的home函数后,相机将回到程序设置的相机初始位置,即调用操控器类的setHomePosition函数设置的位置。在home函数中调用setTransformation函数来计算相机的姿态矩阵,即朝向,也即旋转矩阵。因为观察点矩阵和相机矩阵互逆,所以可以根据互逆关系求出观察点矩阵。最开始时观察点和相机处于世界坐标系下的同一位置。观察点矩阵是通过操控器类的如下函数获取的:

/** get the position of the manipulator as 4x4 Matrix.*/
osg::Matrixd CMySimpleManipulator::getMatrix() const
{
    return osg::Matrixd::translate(0., 0., _distance) *
        osg::Matrixd::rotate(_rotation) *
        osg::Matrixd::translate(_center);
}

由线性代数矩阵方面的知识,我们知道:矩阵乘积的逆等于矩阵的逆的相反顺序的程序,即满足如下公式:

图3 

所以将getMatrix函数中矩阵乘积求逆即为如下函数(注意:对于平移来说,直接将平移向量中的各个分量分别取反就取逆;对于矩阵来说就是取逆矩阵):

/** get the position of the manipulator as a inverse matrix of the manipulator, typically used as a model view matrix.*/
osg::Matrixd CMySimpleManipulator::getInverseMatrix() const
{
    return osg::Matrixd::translate(-_center) *
        osg::Matrixd::rotate(_rotation.inverse()) *
        osg::Matrixd::translate(0.0, 0.0, -_distance);
}
 

而getInverseMatrix函数的返回值即为相机矩阵。当按住键盘←、→键或滚动鼠标滚轮,改变观察点位置和姿态时,需要将相机矩阵返回到外层。在viewer.cpp第1212行附近的更新阶段void Viewer::updateTraversal()中,会调用如下:

_cameraManipulator->updateCamera(*_camera);

来更新相机位姿,其中_cameraManipulator为操控器类对象,也即main.cpp中的pMySimpleManipulator对象。上面代码的updateCamera函数在操控器基类CameraManipulator定义如下:

 /** update the camera for the current frame, typically called by the viewer classes.
            Default implementation simply set the camera view matrix. */
        virtual void updateCamera(osg::Camera& camera) { camera.setViewMatrix(getInverseMatrix()); }

即相机的位姿更新就在该处实现的,即前文说的操控器类将相机矩阵即getInverseMatrix()函数返回值返回到外层了。

       代码中重写了setHomePosition函数,在该函数中算出了观察点和物体中心即(0, 0, 0)点连线与Y轴负半轴夹角的正弦值、余弦值,便于后面按下←、→键时实时更新观察点坐标。在转动网格时,始终保持观察点和物体中心即(0, 0, 0)点连线与网格所在平面夹角不变。       

     updateEyePos函数实时更新观察点坐标,需要说明的是:_rotateZAngle的0°位置和Y轴负半轴重合,由Y轴负半轴非0的某个点指向坐标原点(0, 0, 0),逆时针时,_rotateZAngle增加1°;顺时针时,_rotateZAngle减小1°。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1081650.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设备管理工具

做了一个代理类,抽象出来后在注册表中&#xff0c;查找已经注册的设备 python 中 dict 和 lua 中的 table 一样高效 先初始化找到的设备通信程序,底层接口准备好C,这个设备调试界面就是可以用的,剩下就是MV了 软件升级已经稳定可用了 包括软件的备份和回滚操作登录时为设备页面…

【工作流引擎】Activiti的使用03

流程定义查询 // 获取部署时的信息ProcessEngine processEngine ProcessEngines.getDefaultProcessEngine();RepositoryService repositoryService processEngine.getRepositoryService();ProcessDefinitionQuery processDefinitionQuery repositoryService.createProcessDe…

直观全面解释Transformer模型;上海人工智能实验室推出首个图文混合创作大模型浦语灵笔

&#x1f989; AI新闻 &#x1f680; 上海人工智能实验室推出首个图文混合创作大模型浦语灵笔 摘要&#xff1a;上海人工智能实验室推出了一款名为浦语灵笔的图文混合创作大模型&#xff0c;并宣布其开源。浦语灵笔基于书生・浦语大语言模型&#xff0c;具备强大的多模态性能…

LabVIEW开发卫星测试平台

LabVIEW开发卫星测试平台 已经有不少的大学和研究机构经常使用立方体卫星。它们的广泛使用通常归因于使用廉价零件、无偿学生劳动和简单的设计。科学、技术、工程和数学学生已被证明可以通过参与实际工作宇宙飞船系统的规划、开发和测试而从中受益。通过鼓励来自不同学术领域的…

Kubernetes----基于kubeadm工具在CentOS7.9虚拟机上部署一主两从类型的1.26版本的Kubernetes集群环境

【原文链接】Kubernetes----基于kubeadm工具在CentOS7.9虚拟机上部署一主两从类型的1.26版本的Kubernetes集群环境 文章目录 一、虚拟机环境准备1.1 准备三台CentOS操作系统的虚拟机1.2 修改主机名1.3 确认CentOS的版本符合要求1.4 配置地址解析1.5 配置时间同步1.6 关闭防火墙…

【jmeter的使用】【性能测试1】

jmeter的使用笔记2 线程并发的设置定时器1&#xff09;同步定时器2&#xff09;准确的吞吐量定时器3&#xff09;常数吞吐量定时器 用户自定义变量设置响应断言聚合报告查看聚合报告参数详解&#xff1a; 前言&#xff1a;使用jmeter进行简单性能测试实践&#xff0c;以百度搜索…

Java中的错误和异常有什么区别和联系?

​ 概述 错误 该错误表示大多数情况是由于系统资源不足而发生的。系统崩溃和内存错误就是错误的例子。它主要发生在运行时。 错误是用户执行意外操作时产生的严重情况&#xff1b;错误大多发生在编译时&#xff0c;如语法错误&#xff0c;但它也可能发生在运行时&#xff1…

UE5:如何解决背景图片被拉伸的问题?

1.在图片外围包裹一个Scale Box组件 2.将图片的尺寸修改为原始尺寸就可解决问题

北美”闲鱼”Poshmark,如何销售提高成单率?附防封养号攻略

Poshmark 是一款美国的社交商务应用程序&#xff0c;被称为北美的”咸鱼“。该平台提供女性、男性、儿童时尚和家居装饰品。目前在美国、加拿大、澳洲、印度均可开通&#xff0c;其余地区暂不支持。 在平台上&#xff0c;用户可以自由上传和销售产品。Poshmark提供安全的支付解…

【Zookeeper专题】Zookeeper选举Leader源码解析

目录 前言阅读建议课程内容一、ZK Leader选举流程回顾二、源码流程图三、Leader选举模型图 学习总结 前言 为什么要看源码&#xff1f;说实在博主之前看Spring源码之前没想过这个问题。因为我在看之前就曾听闻大佬们说过【JavaCoder三板斧&#xff1a;Java&#xff0c;Mysql&a…

设计模式_责任链

责任链模式 介绍 设计模式定义案例责任链模式问题 传给 多个可处理人 这多个处理人做成一个链表学生请假条审核 上课老师&#xff08;3天权限&#xff09; 班主任 &#xff08;5天权限&#xff09; 校长 &#xff08;30天权限&#xff09; 问题堆积在哪里解决办法进一步优…

目标检测新思路:DETR

Transformer是一种基于自注意力机制的神经网络架构&#xff0c;它能够从序列中提取重要信息&#xff0c;已被广泛应用于自然语言处理和语音识别等领域。随着Transformer的提出和发展&#xff0c;目标检测领域也开始使用Transformer来提高性能。 DETR是第一篇将Transformer应用于…

点击弹窗页面右侧缩小问题

主页面是由header和下面的tabel&#xff0c;2个组件构成&#xff0c;需要在css里使用flex布局&#xff0c; 就解决了弹窗导致的页面右侧缩小问题。 display: flex; flex-direction: column; <template><div class"main"><div class"header"…

局域网远程控制

被控制端电脑设置 1开启服务 Romate Desktop Services 2设置允许远程控制&#xff0c;并且添加被控制的用户 3检查用户组管理用户名是否一致 我的电脑>管理>本地用户和组>用户 4在控制电脑端WinR 输入 mstsc出现如下界面 输入被控制电脑IP和用户名 然后输入被控制端…

第十届蓝桥杯省赛C++C/研究生组,第十届蓝桥杯省赛JAVAC/研究生组——扫地机器人题解(二分)

题目描述 小明公司的办公区有一条长长的走廊&#xff0c;由 N个方格区域组成&#xff0c;如下图所示。 走廊内部署了 K台扫地机器人&#xff0c;其中第 i台在第 A i A_i Ai​ 个方格区域中。 已知扫地机器人每分钟可以移动到左右相邻的方格中&#xff0c;并将该区域清扫干净…

Android组件通信(二十四)

1. Activity生命周期 1.1 知识点 &#xff08;1&#xff09;掌握Activity的生命周期及操作方法&#xff1b; 1.2 具体内容 范例&#xff1a; 第一个配置文件 <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http…

C语言常见题目(1)交换两个变量的值,数的逆序输出,猜数游戏,两个数比较大小等

我的个人主页&#xff1a;☆光之梦☆的博客_CSDN博客-C语言基础语法&#xff08;超详细&#xff09;领域博主 欢迎各位 &#x1f44d;点赞 ⭐收藏 &#x1f4dd;评论 特别标注&#xff1a;本博主将会长期更新c语言的语法知识&#xff0c;初学c语言的朋友们&#xff0c;可以收藏…

面试经典 150 题 3 —(双指针)— 167. 两数之和 II - 输入有序数组

167. 两数之和 II - 输入有序数组 方法一 class Solution { public:vector<int> twoSum(vector<int>& numbers, int target) {unordered_map<int,int> hashtable;for(int i 1; i < numbers.size(); i){auto item hashtable.find(target - numbers[…

Allegro基本规则设置指导书之Spacing规则设置

进入规则设置界面 1.设置Line 到其他的之间规则: 2.设置Pins 到其他的之间规则: 3.设置Vias 到其他的之间规则:

微软开源 windows-drivers-rs,用 Rust 开发 Windows 驱动程序

Microsoft Azure 首席技术官兼著名 Windows 软件开发人员 Mark Russinovich 在社交平台上宣布&#xff0c;启动了一个名为 windows-drivers-rs 的新开源项目。 该项目可帮助开发人员使用 Rust 开发 Windows 驱动程序&#xff0c;旨在支持 WDM (Windows Driver Model) 和 WDF (…