QT+OpenGL鼠标操作和模型控制

news2025/7/26 8:33:27

文章目录

  • QT+OpenGL鼠标操作和模型控制
    • 鼠标拾取
      • 理论有点小复杂
        • 从鼠标计算射线
          • 第 0 步:2D 视口坐标
          • 第 1 步:3d归一化设备坐标
          • 第 2 步:4d齐次剪辑坐标
          • 第 3 步:4d眼(相机)坐标
          • 第 4 步:4d 世界坐标
      • 代码展示
    • 模型控制
      • 多模型加载
      • 选中模型
      • 模型旋转和移动

QT+OpenGL鼠标操作和模型控制

本篇完整工程见gitee:QtOpenGL 对应点的tag,由turbolove提供技术支持,您可以关注博主或者私信博主

鼠标拾取

  • 需要将世界坐标转换为视口坐标
void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *data);

理论有点小复杂

Mouse Picking with Ray Casting - Anton’s OpenGL 4 Tutorials (antongerdelan.net) 参考这篇文章

这部分不懂的话,暂时是没关系的,可以接着往下看代码,然后去看我的项目。

光线追踪法 从鼠标投射 3D 射线, 通过摄像机,进入场景,然后检查该光线是否与某个对象相交。

在这里插入图片描述

从鼠标计算射线

第 0 步:2D 视口坐标

range [0:width, height:0]

我们从鼠标光标坐标开始。这些是 2d,并且在视口坐标系中。首先我们需要获取鼠标 x,y 像素 坐标。

如果是QT的话,可以直接使用QT的mousePressEvent的evnet->pos();

这给了我们一个 x 在 0:width 和 y 从height:0 开始。请记住,0 位于此处的屏幕顶部,因此 y 轴方向与其他坐标系中的方向相反。

第 1 步:3d归一化设备坐标

range [-1:1, -1:1, -1:1]

下一步是将其转换为 3D 规范化设备坐标。 这应该在 x [-1:1] y [-1:1] 和 z [-1:1] 的范围内。我们有一个 x 和 y已经,所以我们缩放它们的范围,并反转y的方向。

float x = (2.0f * mouse_x) / width - 1.0f;
float y = 1.0f - (2.0f * mouse_y) / height;
float z = 1.0f;
vec3 ray_nds = vec3(x, y, z);
第 2 步:4d齐次剪辑坐标

range [-1:1, -1:1, -1:1, -1:1]

我们希望我们的射线的z指向前方 - 这通常是 OpenGL 样式中的负 z 方向。我们可以添加一个 ,这样我们就有一个 4d 向量w, 当然对于QT我们可以用QVector4D替换下面的vec4.

vec4 ray_clip = vec4(ray_nds.xy, -1.0, 1.0);
第 3 步:4d眼(相机)坐标

range [-x:x, -y:y, -z:z, -w:w]

通常,为了从眼睛空间进入剪辑空间,我们将向量乘以 投影矩阵。我们可以通过乘以这个的倒数来倒退 矩阵。

vec4 ray_eye = inverse(projection_matrix) * ray_clip;

现在,我们只需要取消x,y部分的投影,所以让我们手动设置z,w部分的意思是“向前,而不是一个点”。

ray_eye = vec4(ray_eye.xy, -1.0, 0.0);
第 4 步:4d 世界坐标

range [-x:x, -y:y, -z:z, -w:w]

同样,回到转换管道的另一个步骤。记住,我们手动为z分量指定了一个-1,这意味着我们的射线没有归一化。我们应该在使用它之前把它弄清楚。

vec3 ray_wor = (inverse(view_matrix) * ray_eye).xyz;
// don't forget to normalise the vector at some point
ray_wor = normalise(ray_wor);

这将为我们平衡上下、左右和前进组件。所以,假设我们的摄像机直接沿着-Z世界轴看,当鼠标在屏幕中心时,我们应该得到[0,0,-1],而当鼠标在屏幕上移动时,z值就不那么重要了。这将取决于纵横比,以及视图和投影矩阵中定义的视场。我们现在有一条射线,可以和世界空间中的曲面进行比较。

代码展示

// (传入参数为鼠标点击的坐标)计算世界坐标
QVector4D TurboOpenGLWidget::worldPositionFromMousePosition(const QPoint &pos)
{
    float winZ;
    glReadPixels((int)pos.x(), this->height() - (int)pos.y(),
                 1,1,GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);
    float x = (2.0 * pos.x()) / this->width() - 1.0f;
    float y = 1.0f - (2.0f * pos.y()) / this->height();
    float z = winZ * 2.0 -1.0f;

    float w = (2.0 * near_ * far_) / (far_ + near_ - z *(far_ - near_));
    QVector4D worldPosition(x, y, z, 1);
    worldPosition *= w;
    worldPosition = view.inverted() * projection.inverted() * worldPosition;
    return worldPosition;
}

模型控制

多模型加载

我们需要用之前模型加载那一块的代码,然后修改成可以加载多个模型的代码。

为此我们需要添加一个类型:

struct ModelInfo
{
    Model *model;
    QVector3D world_pos;
    float pitch;
    float roll;
    float yaw;
    bool is_selected;
    QString name;
}
QMap<QString, ModelInfo> models_;

然后需要将loadModel 修改为多模型的

void TurboOpenGLWidget::loadModel(const QString &file)
{
    makeCurrent();
    static int model_i = 0;
    Model *model = new Model(QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_4_5_Core>(),
                       file.toStdString());
    camera_.setPosition(cameraPositionInit(model->max_y_, model->min_y_));
    models_["aa" + QString::number(model_i++)] = ModelInfo{model, QVector3D(0, 0, 0), 0.0, 0.0, 0.0, false, "aa"};
    doneCurrent();
}

之后需要对绘制的地方进行修改,遍历加载的模型然后绘制对应的模型。

选中模型

选中模型就是需要判断鼠标点击的位置和模型所在位置是否重叠,如果在一定范围内是重叠的则认为我们选中了该模型。

// 判断鼠标是否选中模型
void TurboOpenGLWidget::mousePressEvent(QMouseEvent *event)
{
    bool hasSelected=false;
    makeCurrent();
    if(event->buttons() & Qt::LeftButton)
    {
        QVector4D worldPosition;
        worldPosition = worldPositionFromMousePosition(event->pos());
        emit sig_worldPosition(worldPosition);
        for(QMap<QString, ModelInfo>::iterator iter=models_.begin();iter!=models_.end();iter++){
            ModelInfo *modelInfo=&iter.value();
            float r=(modelInfo->model->max_y_-modelInfo->model->min_y_)/2;
            if(modelInfo->world_pos.distanceToPoint(QVector3D(worldPosition))<r
               &&!hasSelected){
                modelInfo->is_selected=true;
                hasSelected=true;
            }
            else
                modelInfo->is_selected=false;
        }
    }
    QWidget::mousePressEvent(event);
}

模型旋转和移动

// 双击时候选中模型
void TurboOpenGLWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
    Q_UNUSED(event);
    if(model_moving_){
        //再次双击取消移动
        model_moving_=false;
    }else
        foreach(auto modelInfo,models_){
        //双击启动移动
        if(modelInfo.is_selected==true)
            model_moving_=true;
    }

    QWidget::mouseDoubleClickEvent(event);
}
// 如果是移动的状态则移动模型

void TurboOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
    makeCurrent();
    static QPoint lastPos(width()/2, height()/2);
    if(model_moving_){
        for(auto iter=models_.begin();iter!=models_.end();iter++){
            ModelInfo *modelInfo=&iter.value();
            if(!modelInfo->is_selected) continue;
            modelInfo->world_pos=
                    QVector3D(worldPositionFromMousePosition(event->pos());
        }
    }
    else if(event->buttons() & Qt::RightButton
       || event->buttons() & Qt::LeftButton
       || event->buttons() & Qt::MiddleButton){

        auto currentPos=event->pos();
        QPoint deltaPos=currentPos-lastPos;
        lastPos=currentPos;
        if(event->buttons() & Qt::RightButton)
            camera_.processMouseMovement(deltaPos.x(),-deltaPos.y());
        else
            for(auto iter=models_.begin();iter!=models_.end();iter++){
                ModelInfo *modelInfo=&iter.value();
                if(!modelInfo->is_selected) continue;
                if(event->buttons() & Qt::MiddleButton){
                    modelInfo->roll+=deltaPos.x();
                }
                else if(event->buttons() & Qt::LeftButton){
                    modelInfo->yaw+=deltaPos.x();
                    modelInfo->pitch+=deltaPos.y();
                }
            }
    }
    doneCurrent();
}

在这里插入图片描述

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

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

相关文章

自动执行自动化测试用例

phpunit 接口自动化测试系列 所有自动化测试用例最终的目的都是一样的&#xff0c;实现无人值守的自动化运行。而目前最常用的就是Jenkins来实现这个功能&#xff0c;在前面的WebdriverPython页面自动化的教程中我们已经详细讲解了如何将自动化测试用例接入到Jenkins中。本章我…

RK系列(RK3568) i2s 音频输入 麦克风驱动

平台&#xff1a;Android12SOC&#xff1a;RK3568外围芯片&#xff1a;XS9922i2s简介&#xff1a;从上图看I2s主要的线有&#xff1a;SDO SCLK LRCK MCLK I2S协议只定义三根信号线&#xff1a;串行时钟信号SCLK(BCLK)、数据信号SD和左右声道选择信号WS。&#xff08;1&#xff…

QT入门Containers之QStackedWidget

目录 一、QStackedWidget界面相关 1、布局介绍 2、插入界面 3、插入类界面 二、Demo展示 此文为作者原创&#xff0c;创作不易&#xff0c;转载请标明出处&#xff01; 一、QStackedWidget界面相关 1、布局介绍 QStackedWidget这个控件在界面布局时&#xff0c;使用还…

JVM整体分析篇

这里写目录标题JVM的组成部分1.类装载子系统1.1一个类加载到JVM的过程1.2类加载机制1.3为什么设计双亲委派机制1.4怎么打破双亲委派机制2.运行时数据区2.1线程私有及共享2.2JVM内存区结构2.3JVM参数设置经验3.Java对象的生命周期3.1.对象的创建3.2.对象大小的计算&#xff08;6…

六、程序计数器(PC寄存器)

JVM中的程序计数寄存器&#xff08;Program Counter Register&#xff09;中&#xff0c;Register 的命名源于CPU的寄存器&#xff0c;寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。 这里&#xff0c;并非是广义上所指的物理寄存器&#xff0c;或许将其…

Python中实现将内容进行base64编码与解码

一、需求说明需要使用Python实现将内容转为base64编码&#xff0c;解码&#xff0c;方便后续的数据操作。二、base64简介Base64是一种二进制到文本的编码方式【是一种基于 64 个可打印字符来表示二进制数据的表示方法&#xff08;由于 2^664&#xff0c;所以每 6 个比特为一个单…

PHP/7.2.11 缺少 apache2/logs/httpd.pid 文件

启动服务时&#xff1a;systemctl restart httpd.service&#xff0c;报错&#xff1a;● httpd.service - httpd serviceLoaded: loaded (/etc/systemd/system/httpd.service; enabled; vendor preset: disabled)Active: failed (Result: exit-code) since 五 2023-02-24 16:1…

Matlab进阶绘图第2期—线型热图

线型热图由共享X轴的多条渐变直线组成&#xff0c;其颜色表示某一特征值。 与传统热图相比&#xff0c;线型热图适应于X轴数据远多于Y轴&#xff08;条数&#xff09;的情况&#xff0c;可以很好地对不同组数据间的分布情况进行比较&#xff0c;也因此可以在一些期刊中看到它的…

IM即时通讯架构技术:可靠性、有序性、弱网优化等

消息的可靠性是IM系统的典型技术指标&#xff0c;对于用户来说&#xff0c;消息能不能被可靠送达&#xff08;不丢消息&#xff09;&#xff0c;是使用这套IM的信任前提。 换句话说&#xff0c;如果这套IM系统不能保证不丢消息&#xff0c;那相当于发送的每一条消息都有被丢失的…

Unity 对接 ML-Agents 初探

一、ML-Agents 是什么 The Unity Machine Learning Agents Toolkit (ML-Agents) is an open-source project that enables games and simulations to serve as environments for training intelligent agents. We provide implementations (based on PyTorch) of state-of-the…

为什么很多人转行IT考虑后端开发Java?

顺应互联网时代发展的选择 在计算机广泛运用于社会的各个角落的今天&#xff0c;选择学习一门计算机语言真的很不错&#xff0c;它会让你的生活从此与众不同。软件渗透到组织的运营和管理的后台之中&#xff0c;形成了组织运营支撑平台。这种形态是传统软件的重要应用场景。在…

matlab simulink Buck三电平dcdc变换器

1、内容简介略653-可以交流、咨询、答疑2、内容说明三电平dc/dc变换器的发展是建立在三电平逆变器的基础上的&#xff0c;由于在三电平dc/dc变换器中每个开关管的电压应力是输入直流电压的一半&#xff0c;具有降低开关管电压应力、减小输入输出滤波器大小、减小储能电感和电容…

【Linux应用】进程间通信消息队列

1.前言 进程间通信简称IPC&#xff08;Inter process communication&#xff09;&#xff0c;进程间通信就是在不同进程之间传播或交换信息。 消息队列是进程通信的一种方式&#xff0c;本质是一个存储消息的链表&#xff0c;这些消息具有特定的格式及特定优先级。消息队列是…

新建一个虚拟环境来管理自己的pytorch环境

python只是一个编程语言&#xff0c;在开发具体软件时&#xff0c;可能还需要使用到各种的包&#xff0c;比如著名的 numpy&#xff0c;torch 等等。如果我们自己来管理则显得非常麻烦&#xff0c;于是便有了Anaconda这样一个类似容器的东西&#xff0c;它封装了python各种各样…

Spring Cloud入门篇 Hello World | Spring Cloud 1

一、专栏说明 Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如:服务发现/注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。 本文主要介绍Spring C…

功能测试三年,是应该改变了

前言 测试行业3年多经验&#xff0c;学历大专自考本科&#xff0c;主要测试方向web&#xff0c;PC端&#xff0c;wap站&#xff0c;小程序公众号都测试过&#xff0c;app也测过一些&#xff0c;C端B端都有&#xff0c;除功能外&#xff0c;接口性能也有涉猎&#xff0c;但是不…

JavaScript 两种方案打开文件对话框

JavaScript 两种方案打开文件对话框 文章目录JavaScript 两种方案打开文件对话框一、文件对话框二、传统方案表单元素&#x1f308;三、文件系统访问API&#x1f4a6;四、更进一步使用六、代码仓库&#x1f310;七、参考资料&#x1f498;七、推荐博文&#x1f357;一、文件对话…

ZYNQ双核处理器独立运行AMP

一、简介多核处理器从多核的结构上是否一致&#xff0c;分为两种基本架构&#xff1a;同构多核架构和异构多核架构。同构多核处理器是指系统中的处理器在结构上是相同的&#xff1b;而异构处理器是指系统中的处理器在结构上是不同的&#xff0c;这些处理器可以是通用处理器&…

MySQL递归查询 三种实现方式

1 建表脚本1.1 建表DROP TABLE IF EXISTS sys_region; CREATE TABLE sys_region (id int(50) NOT NULL AUTO_INCREMENT COMMENT 地区主键编号,name varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 地区名称,short_name varchar(50) CHARA…

WebDAV之π-Disk派盘+Cloud Player

Cloud Player 支持WebDAV方式连接π-Disk派盘。 推荐一款云媒体播放器是存储在常见云平台中的内容的通用播放器。 Cloud Player云媒体播放器是存储在常见云平台中的内容的通用播放器,无需将其下载到设备。支持以下云平台:Google Drive、DropBox、One Drive、WebDav等。此外,…