当前位置: 首页 > news >正文

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

文章目录

  • 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;}elsemodelInfo->is_selected=false;}}QWidget::mousePressEvent(event);
}

模型旋转和移动

// 双击时候选中模型
void TurboOpenGLWidget::mouseDoubleClickEvent(QMouseEvent *event)
{Q_UNUSED(event);if(model_moving_){//再次双击取消移动model_moving_=false;}elseforeach(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());elsefor(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();
}

在这里插入图片描述

相关文章:

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

文章目录QTOpenGL鼠标操作和模型控制鼠标拾取理论有点小复杂从鼠标计算射线第 0 步&#xff1a;2D 视口坐标第 1 步&#xff1a;3d归一化设备坐标第 2 步&#xff1a;4d齐次剪辑坐标第 3 步&#xff1a;4d眼(相机)坐标第 4 步&#xff1a;4d 世界坐标代码展示模型控制多模型加载…...

爱奇艺“资产重定价”:首次全年运营盈利是拐点,底层逻辑大改善

长视频行业历时一年有余的降本增效、去肥增瘦&#xff0c;迎来首个全周期圆满收官的玩家。 北京时间2月22日美股盘前&#xff0c;爱奇艺发布2022年Q4及全年财报&#xff0c;Q4 Non-GAAP净利润明显超越预期&#xff0c;且首次实现全年运营盈利。受业绩提振&#xff0c;爱奇艺盘…...

MySQL —— 库的操作

文章目录1. 创建数据库2. 字符集和校验规则3. 数据库的基本操作3.1 查看数据库3.2 显示创建数据库的语句3.3 修改数据库3.4 删除数据库3.5 备份&#xff0c;还原数据库4. 查看数据库的连接情况1. 创建数据库 基本语法&#xff1a; create database if not exists 数据库名 选项…...

修改shell的命令提示符

以下内容源于C语言中文网的学习与整理&#xff0c;非原创&#xff0c;如有侵权请告知删除。 一、命令提示符格式 从虚拟控制台登陆后&#xff0c;或者从桌面环境的终端进入shell后&#xff0c;就可以看见shell的命令提示符&#xff0c;这意味着可以输入命令了。注意&#xff…...

介绍并比较Apache Hive支持的文件格式

Apache Hive 支持几种熟知的Hadoop使用的文件格式&#xff0c;Hive也能加载并查询其他Hadoop组件创建的不同文件格式&#xff0c;如Pig或MapReduce。本文对比Hive不同文件格式&#xff0c;如&#xff1a;TextFile, SequenceFile, RCFile, AVRO, ORC,Parquet&#xff0c;Clouder…...

C语言之文件操作

目录 一、什么是文件&#xff1f; 二、C语言如何操作文件 1.操作方式 2.文件指针 2.1 定义文件指针 2.2文件的打开与关闭 2.3文件的顺序读写 2.3文件的随机读写 总结 一、什么是文件&#xff1f; 在电脑磁盘的上的文件。在程序设计中&#xff0c;分为两种&#xff1a;程序…...

Linux->父子进程初识和进程状态

目录 前言&#xff1a; 1. 父子进程创建 2. 进程状态 R(running)状态&#xff1a; S(sleep)状态&#xff1a; D(disk sleep)状态&#xff1a; T(stopped)状态&#xff1a; X(dead)和Z(zombie)状态&#xff1a; 孤儿进程&#xff1a; 前言&#xff1a; 本篇主要讲解关…...

【Linux学习笔记】5.Linux 用户和用户组管理

前言 本章介绍Linux的用户和用户组管理。 Linux 用户和用户组管理 Linux系统是一个多用户多任务的分时操作系统&#xff0c;任何一个要使用系统资源的用户&#xff0c;都必须首先向系统管理员申请一个账号&#xff0c;然后以这个账号的身份进入系统。 用户的账号一方面可以…...

茂名市 2021 年高中信息技术学科素养展评

没事干&#xff0c;发一下去年去比赛的题目。 目录 第一题 30分 第二题 30分 第一题 30分 题目&#xff1a; “姐姐&#xff0c;乘除法运算太难了&#xff0c;有什么办法能熟练掌握吗&#xff1f;”今年 读小学四年级的表弟向李红求救。为了提高表弟的运算能力&#xff0c;…...

【软件测试】测试人不躺平,进军高级自动化测试自救,你的不一样结局......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 随着测试从业年龄的…...

win10环境下安装java开发环境安装java

一&#xff1a;环境介绍 安装系统版本&#xff1a;win10 java版本&#xff1a;java SE 17 二&#xff1a;下载Java安装包 官网下载Java安装包&#xff1a;Java Downloads | Oracle 中国 选择需要的Java版本进行下载&#xff0c;如果没有要选择的版本&#xff0c;可以选择最新…...

【华为OD机试模拟题】用 C++ 实现 - 开心消消乐(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…...

opencv图像融合

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…...

没有经验的时候,怎么搞定面试?

在之前的面试技巧&#xff0c;如何写简历上面&#xff0c;我讲了一些方法&#xff0c;希望大家重 视起来。核心其实就一点&#xff1a;他们想要你表现什么能力&#xff0c;以及你在 这个能力之外还有什么。 看清楚这句话的含义&#xff0c;你就可以做到百发百中。具体怎么训练&…...

整数保序的离散化(C/C++)

目录 1. 离散化的概念 1.1 离散化的运用思路 1.2 离散化的方法 1.2.1 排序 1.2.2 确定一个元素离散化后的结果 1.3 案例分析 1.3.1 1.3.2 区间和 &#xff08;来源&#xff1a;Acwing&#xff09; 1. 离散化的概念 离散化&#xff0c;把无限空间中有限的个体映射到有限的…...

python--排序总结

1.快速排序 a.原理 快速排序的基本思想是在待排序的 n 个元素中任取一个元素&#xff08;通常取第一个元素&#xff09;作为基准&#xff0c;把该元素放人最终位置后&#xff0c;整个数据序列被基准分割成两个子序列&#xff0c;所有小于基准的元素放置在前子序列中&#xff0…...

进化的隐藏水印:深度学习提升版权保护的鲁棒性

一、前言 过去几年&#xff0c;以网络视频为代表的泛网络视听领域的崛起&#xff0c;是互联网经济飞速发展最为夺目的大事件之一。泛网络视听领域不仅是21世纪以来互联网领域的重要基础应用、大众文化生活的主要载体&#xff0c;而且在推动中国经济新旧动能转化方面也发挥了重…...

Jenkins配置项目教程

在上一篇[Jenkins的使用教程](https://blog.csdn.net/weixin_43787492/article/details/129028131?spm1001.2014.3001.5501)中我介绍了如何创建一个项目 Jenkins在创建项目中提供了很多功能供我们选择&#xff0c;这里我将对配置项目做一个较完整的介绍Jenkins配置项目0、所有…...

C++多继承,虚继承部分总结与示例

tags: C OOP 写在前面 写一下多继承, 虚继承的一些部分, 包括一些例子. 多继承 简介 多继承是指从多个直接基类中产生派生类的能力. 多继承的派生类继承了所有父类的属性, 所以会带来一些复杂的问题. 示例1: 多继承用法与调用顺序 #include <string> #include <…...

程序员35岁以后就没有出路了吗?听听京东10年测开的分析

国内的互联网行业发展较快&#xff0c;所以造成了技术研发类员工工作强度比较大&#xff0c;同时技术的快速更新又需要员工不断的学习新的技术。因此淘汰率也比较高&#xff0c;超过35岁的基层研发类员工&#xff0c;往往因为家庭原因、身体原因&#xff0c;比较难以跟得上工作…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅

目录 前言 操作系统与驱动程序 是什么&#xff0c;为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中&#xff0c;我们在使用电子设备时&#xff0c;我们所输入执行的每一条指令最终大多都会作用到硬件上&#xff0c;比如下载一款软件最终会下载到硬盘上&am…...

基于鸿蒙(HarmonyOS5)的打车小程序

1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...

前端高频面试题2:浏览器/计算机网络

本专栏相关链接 前端高频面试题1&#xff1a;HTML/CSS 前端高频面试题2&#xff1a;浏览器/计算机网络 前端高频面试题3&#xff1a;JavaScript 1.什么是强缓存、协商缓存&#xff1f; 强缓存&#xff1a; 当浏览器请求资源时&#xff0c;首先检查本地缓存是否命中。如果命…...