《计算机图形学》第二课笔记-----二维变换的推导

前言:为什么这么突兀的把这一节内容放在了第二课,第一是因为我急于求成,第二是因为这一章节太重要了,这几乎是二维三维变换的最核心的东西,理解了这一章节内容,后面的就会像打通了任督二脉一样,so,那让我们开始吧,我们只用初中的知识把这一章说清楚

所谓的二维变换核心的东西其实就是求一个点绕一个点旋转后的位置

让我们对这段话进行一些约束,在一个去掉了Z轴的笛卡尔坐标系内,让我们再大白话点,在一个平面坐标系内,让我们再大白话点,就是小时候上数学课时候老师在黑板上化的那个一横一竖,上面一个箭头,前面一个箭头那种坐标系内
我们终于白话完了,正文开始,在一个坐标系内一个点绕着原点o旋转一定的度数,旋转到新的一个点,求这个点在这个坐标系内的位置,也就是新的点位的x’的长度和y’的长度。
结合下图描述了一个已知的点[x,y],沿原点o旋转了β度到达了新的点[x’,y’],求[x’,y’]的位置
我们需要了解的前提是[x,y]是已知的
那么x’也可以写成r*cos(α+β),因为我们是绕原点旋转,等于在绕原点画圆,so,每一个半径都应该是相等的,记住这个前提,后面要用。

在这里插入图片描述

x ′ = r ∗ c o s ( α + β ) x'=r*cos(α+β) x=rcos(α+β
上面这个式子应该很好理解,如果不理解可以只补一下三角函数的cos 和 sin即可

接下来我们展开这个式子,有人可能会在这里卡住一下,但这其实都是固定推导您只需复制展开前的式子问下一ai,他会说的比我清楚多了,总而言之如果您不想深究,不用理会为什么展开以后是这样子,直接过即可
x ′ = r ∗ c o s α ∗ c o s β − r ∗ s i n α ∗ s i n β x'=r*cosα*cosβ-r*sinα*sinβ x=rcosαcosβrsinαsinβ

当我们观察展开的式子,这个时候神奇的事情发生了,r*cosα,不就是 r x r r\frac{x}{r} rrx,约分以后不就是x么
我们再观察r*sinα,那不就是 r y r r\frac{y}{r} rry,约分以后那不就是y么

然而x,y又是已知的,那不就是用已知的x,y去乘我们旋转的β角的cos和sin么,至此答案已经很清晰了,可以说这篇博客要说的已经说完了,您仅需初中知识就可以理解这一切,后续的剩下的推导也只是上述思考过程的重复,当然我们还是要写完这一切。

x ′ = x ∗ c o s β − y ∗ s i n β x'=x*cosβ-y*sinβ x=xcosβysinβ
y ′ = x ∗ s i n β + y ∗ c o s β y'=x*sinβ+y*cosβ y=xsinβ+ycosβ

最后我们把他写成矩阵的形式就是下面的式子,我们需要注意的是矩阵乘法并不满足交换律,so,我们不能调换位置

[ c o s β − s i n β s i n β c o s β ] [ x y ] \begin{bmatrix} cosβ & -sinβ \\ sinβ & cosβ \\ \end{bmatrix}\begin{bmatrix} x\\ y\\ \end{bmatrix} [cosβsinβsinβcosβ][xy]

当然我们需要注意,当前我们讨论的是逆时针旋转这种情况,顺时针的推到过程在下下张图

那么剩下的顺时针推导我们就不写这么详细了,因为一切已经不言自明了,下一节我们就去到代码层面去实现它,平移我们也就不在此解释了,因为那些比这个容易理解的多。

在这里插入图片描述

原因如上,顺时针的推导就不赘述了

在这里插入图片描述

敲黑板``有个非常重要的问题,我们基于数学上的推导角度都是没有符号的,也就是说我们有两种方式实现顺逆的切换

  1. 角度不变,也就是无论顺逆都使用正角度表达
  2. 用算法切换正逆,也就是逆时针用这个矩阵 [ c o s β − s i n β s i n β c o s β ] [ x y ] \begin{bmatrix} cosβ & -sinβ \\ sinβ & cosβ \\ \end{bmatrix}\begin{bmatrix} x\\ y\\ \end{bmatrix} [cosβsinβsinβcosβ][xy]顺时针用这个矩阵 [ c o s β + s i n β − s i n β c o s β ] [ x y ] \begin{bmatrix} cosβ & +sinβ \\ -sinβ & cosβ \\ \end{bmatrix}\begin{bmatrix} x\\ y\\ \end{bmatrix} [cosβsinβ+sinβcosβ][xy]

敲黑板``那如果您不想通过切换算法的方式来实现正拟切换,那么算法用固定的,此处以使用逆时针算法举例,固定使用了逆时针算法,那么逆时针旋转还是使用正角度,如果要使用顺时针算法秩序给角度前面加上负号即可

现在我们来到代码层面,UI框架选择c++ qt5,因为我更熟悉这个框架,而且如果我们仅仅知识为了演示二维变换的推导,qt5足够了,主要原因还是我足够熟悉。不用opengl是因为我们像尽其可能的展示细节,而不是直接调用显卡为我们实现好的接口。

头文件

#ifndef TWOCUBE_1_H // 防止头文件被重复包含
#define TWOCUBE_1_H
#include <QGridLayout>
#include <QPushButton>
#include <QSpacerItem>
#include <QWidget> // 包含QWidget类,用于创建窗口
#include <QTimer>  // 包含QTimer类,用于定时器功能// TwoCube_1类继承自QWidget,用于创建自定义窗口
class TwoCube_1: public QWidget
{Q_OBJECT // 启用Qt的元对象系统,支持信号和槽机制public:// 构造函数,参数为父窗口指针TwoCube_1(QWidget* parent);// 析构函数~TwoCube_1();protected:// 重写paintEvent函数,用于处理窗口的绘制事件void paintEvent(QPaintEvent* event) override;private:QList<QPointF> rectPoints;  // 用于存储矩形的顶点坐标(2D坐标)int angle = 0;              // 旋转角度,初始化为0float rWidth = 300;         // 矩形的宽度float rHeight = 180;        // 矩形的高度QGridLayout grid_main;QPushButton btn_resetAngle;QPushButton btn_turned;int direction=1;
};#endif // TWOCUBE_1_H // 结束头文件定义

cpp文件

#include "twocube_1.h"
#include <QPainter>
#include <QDebug>
#include <cmath>// 构造函数,初始化窗口和矩形点集
TwoCube_1::TwoCube_1(QWidget* parent): QWidget(parent)
{// 设置窗口的最小大小为800x800setMinimumSize(800, 800);this->setLayout(&grid_main);grid_main.addItem(new QSpacerItem(2000, 2000),1,1);grid_main.addItem(new QSpacerItem(2000, 2000),1,2);grid_main.addWidget(&btn_resetAngle,8,9);btn_resetAngle.setText("重置角度");connect(&btn_resetAngle,&QPushButton::clicked,this,[=]{angle=0;});grid_main.addWidget(&btn_turned,9,9);btn_turned.setStyleSheet("background:Purple;color:white");btn_turned.setText("逆时针旋转");connect(&btn_turned,&QPushButton::clicked,this,[=]{direction=!direction;if(direction){angle=0;btn_turned.setStyleSheet("background:Purple;color:white");btn_turned.setText("逆时针旋转");}else{angle=0;btn_turned.setStyleSheet("background:green;color:white");btn_turned.setText("顺时针旋转");}});// 初始化矩形的四个顶点坐标rectPoints.append(QPointF(0,0));           // 左上角rectPoints.append(QPointF(0,rHeight));      // 左下角rectPoints.append(QPointF(rWidth,rHeight)); // 右下角rectPoints.append(QPointF(rWidth,0));       // 右上角// 创建定时器用于动画效果QTimer* timer = new QTimer(this);// 连接定时器的timeout信号到lambda表达式,每16ms触发一次connect(timer, &QTimer::timeout, this, [this]() {angle += 1;  // 每次更新角度减少1度,顺时针旋转update();    // 触发重绘事件});timer->start(16); // 定时器每16ms触发一次,约60fps
}// 析构函数
TwoCube_1::~TwoCube_1()
{// 析构函数为空,没有需要释放的资源
}// 重写paintEvent函数,处理窗口的绘制事件
void TwoCube_1::paintEvent(QPaintEvent *event)
{QPainter painter(this);  // 创建QPainter对象,用于绘制// 将绘制原点移动到窗口中心painter.translate(width() / 2, height() / 2);// 反转Y轴painter.scale(1, -1);// 设置画笔颜色为黑色,线宽为1painter.setPen(QPen(Qt::black, 1));// 绘制X轴和Y轴painter.drawLine(-width() / 2, 0, width(), 0);  // X轴painter.drawLine(0, -height() / 2, 0, height()); // Y轴// 用于存储旋转后的矩形顶点QList<QPointF> tempRectPoints;// 对矩形的每个顶点进行旋转变换for (int i = 0; i < rectPoints.length(); i++){float x = rectPoints[i].x();  // 获取当前顶点的X坐标float y = rectPoints[i].y();  // 获取当前顶点的Y坐标float newX;float newY;if(direction){// 计算旋转后的新坐标newX = x * cos(angle * M_PI / 180) - y * sin(angle * M_PI / 180);newY = x * sin(angle * M_PI / 180) + y * cos(angle * M_PI / 180);}else{// 计算旋转后的新坐标newX = x * cos(angle * M_PI / 180) + y * sin(angle * M_PI / 180);newY = -x * sin(angle * M_PI / 180) + y * cos(angle * M_PI / 180);}// 更新旋转后的坐标x = newX;y = newY;// 将旋转后的点添加到临时列表中QPointF tempPoint(x, y);tempRectPoints.append(tempPoint);}// 设置画笔颜色为绿色,线宽为5painter.setPen(QPen(Qt::green, 5));// 绘制旋转后的矩形painter.drawLine(tempRectPoints[0], tempRectPoints[1]);  // 左边painter.drawLine(tempRectPoints[1], tempRectPoints[2]);  // 下边painter.drawLine(tempRectPoints[2], tempRectPoints[3]);  // 右边painter.drawLine(tempRectPoints[3], tempRectPoints[0]);  // 上边//绘制测试坐标系的矩形,如果该矩形在Y轴上边那就是右手系二位坐标系painter.setPen(QPen(Qt::black, 1));painter.drawLine(rectPoints[0], rectPoints[1]);  // 左边painter.drawLine(rectPoints[1], rectPoints[2]);  // 下边painter.drawLine(rectPoints[2], rectPoints[3]);  // 右边painter.drawLine(rectPoints[3], rectPoints[0]);  // 上边
}

最后放上一张效果图
我们演示使用的是顺时针旋转,在一个定时器内每个16ms角度自身减1,重新触发绘制,代码里演示的就是固定角度,也就是无论顺逆都是角度递增,但是切换了算法

角度使用了固定角度

angle += 1;  // 每次更新角度减少1度,顺时针旋转

当用户切换了顺逆后选择不同的算法

// 计算旋转后的新坐标
{// 计算旋转后的新坐标newX = x * cos(angle * M_PI / 180) - y * sin(angle * M_PI / 180);newY = x * sin(angle * M_PI / 180) + y * cos(angle * M_PI / 180);
}
else
{// 计算旋转后的新坐标newX = x * cos(angle * M_PI / 180) + y * sin(angle * M_PI / 180);newY = -x * sin(angle * M_PI / 180) + y * cos(angle * M_PI / 180);
}

最后还有一点需要非常注意,qt的绘制坐标系是Y轴朝下的,我们需要把它翻转过来,才符合我们推导的数学坐标系,就是下面这行代码

// 反转Y轴
painter.scale(1, -1);

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OTP单片机调试工具之—单线数据编码

OTP单片机调试工具在实现过程中离不开单线数据的传输&#xff0c;那么使用哪一种方式的数据编码会比较好呢&#xff1f; 我所了解的主要有以下三种&#xff1a; 1.UART&#xff08;串口&#xff09;&#xff0c;这种方式在单片机和pc之间进行传输都非常常见&#xff0c;效率比较…

背诵--2

DAY01 面向对象回顾、继承、抽象类 学习目标 能够写出类的继承格式public class 子类 extends 父类{}public class Cat extends Animal{} 能够说出继承的特点子类继承父类,就会自动拥有父类非私有的成员 能够说出子类调用父类的成员特点1.子类有使用子类自己的2.子类没有使用…

穷举vs暴搜vs深搜vs回溯vs剪枝刷题 + 总结

文章目录 全排列题解代码 子集题解代码 总结 全排列 题目链接 题解 1. 画一颗决策树 2. 全局变量&#xff1a; int[ ][ ] ret&#xff1a;用于存结果的二维数组 int[ ] path&#xff1a;用于存每次路径的答案 bool[ ] check&#xff1a;判断这个数是否已经用过&#xff0c;…

深度学习中学习率调整策略

学习率衰减策略是深度学习优化过程中的一个关键因素&#xff0c;它决定了训练过程中学习率的调整方式&#xff0c;从而影响模型收敛的速度和效果。不同的衰减策略在不同的任务和模型上可能有不同的表现&#xff0c;下面从我用到过的几个衰减策略进行记录&#xff0c;后续慢慢跟…

《Electron 学习之旅:从入门到实践》

前言 Electron 简介 Electron 是由 GitHub 开发的一个开源框架&#xff0c;基于 Chromium 和 Node.js。 它允许开发者使用 Web 技术&#xff08;HTML、CSS、JavaScript&#xff09;构建跨平台的桌面应用程序。 Electron 的优势 跨平台&#xff1a;支持 Windows、macOS 和 Linux…

UBuntu24.04-JDK7-TOMCAT7安装

jdk7 apt-get 找不到。 tomcat7 也没找到。 以下是安装成功的&#xff0c;供大家参考。 1.JAVA openjdk-7-jdk /usr/lib/jvm/java-7-openjdk-amd641.安装指定版本apt search jdk //查找版本sudo apt install default-jdk //此为默认版本sudo apt install ope…

美畅物联丨WebRTC 技术详解:构建实时通信的数字桥梁

在互联网技术飞速发展的今天&#xff0c;实时通信已成为数字生活的核心需求。WebRTC作为一个开源项目&#xff0c;凭借卓越的技术实力与创新理念&#xff0c;为网页和移动应用带来了颠覆性的实时通信能力。它突破了传统通信方式的限制&#xff0c;实现了音频、视频和数据在用户…

驾驭 DeepSeek 科技之翼,翱翔现代学习新天际

在当今这个信息爆炸的时代&#xff0c;学习的方式和途径正在经历着前所未有的变革。人工智能技术的飞速发展&#xff0c;为我们的学习带来了全新的机遇和挑战。DeepSeek 作为一款强大的大语言模型&#xff0c;凭借其卓越的性能和丰富的功能&#xff0c;为现代学习注入了新的活力…

写时拷贝技术

目录 写时拷贝 核心思想 基本原理 基本过程 一个例子深入理解 补充知识--引用计数 小总结 写时拷贝实现 宏观理解&#xff08;进程、线程角度&#xff09; 资源共享 只读访问 写操作触发拷贝 独立修改 微观理解&#xff08;fork系统调用角度&#xff09; 进程创…

requests库的request和response对象的属性和方法

Python requests库 request 参数信息 response 参数信息

MySQL数据库操作

目录 SQL语句 1、SQL的背景 2、SQL的概念 SQL的分类 SQL的书写规范 MySQL数据库 1、MySQL数据库的编码 &#xff08;1&#xff09;utf8和utf8mb4的区别 &#xff08;2&#xff09;MySQL的字符集 &#xff08;3&#xff09;MySQL默认编码为 latin1 &#xff0c;如何更改…

Blender-MCP服务源码5-BlenderSocket插件安装

Blender-MCP服务源码5-BlenderSocket插件安装 上一篇讲述了Blender是基于Socket进行本地和远程进行通讯&#xff0c;现在尝试将BlenderSocket插件安装到Blender中进行功能调试 1-核心知识点 将开发的BlenderSocket插件安装到Blender中 2-思路整理 1&#xff09;将SocketServe…

Androidstudio实现一个app引导页(超详细)

文章目录 1. 功能需求2. 代码实现过程1. 创建布局文件2. 创建引导页的Adapter3. 实现引导页Activity4. 创建圆点指示器的Drawable5. 创建“立即体验”按钮的圆角背景 2.效果图 1. 功能需求 1、需要和原型图设计稿对应的元素保持一致的样式。 2、引导页需要隐藏导航栏&#xff…

蓝桥杯省赛真题C++B组-小球反弹

一、题目 有一长方形&#xff0c;长为 343720 单位长度&#xff0c;宽为 233333 单位长度。在其内部左上角顶点有一小球(无视其体积)&#xff0c;其初速度如图所示且保持运动速率不变&#xff0c;分解到长宽两个方向上的速率之比为 dx:dy 15:17。小球碰到长方形的边框时会发生…

基于深度学习的多模态人脸情绪识别研究与实现(视频+图像+语音)

这是一个结合图像和音频的情绪识别系统&#xff0c;从架构、数据准备、模型实现、训练等。包括数据收集、预处理、模型训练、融合方法、部署优化等全流程。确定完整系统的组成部分&#xff1a;数据收集与处理、模型设计与训练、多模态融合、系统集成、部署优化、用户界面等。详…

AI 数字人短视频源码开发:开启虚拟世界的创意引擎

在当今数字化浪潮中&#xff0c;AI 数字人正以惊人的速度融入我们的生活&#xff0c;尤其是在短视频领域&#xff0c;AI 数字人凭借其独特的魅力吸引了无数目光。从虚拟偶像的舞台表演到智能客服的贴心服务&#xff0c;AI 数字人已成为推动短视频行业创新发展的重要力量。而这背…

Java 代理模式:从静态代理到动态代理

前言 代理模式是 Java 中常见的设计模式之一&#xff0c;它的核心思想是通过一个代理对象来控制对真实对象的访问。代理模式不仅可以扩展目标对象的功能&#xff0c;而且在不修改原目标对象的情况下&#xff0c;可以增加一些我们自定义的操作。 1. 代理模式简介 代理模式的核心…

PyCharm 2019.1.3使用python3.9创建虚拟环境setuptools-40.8.0报错处理

目录 前置&#xff1a; 一劳永逸方法&#xff08;缺最后一步&#xff0c;没有成行&#xff09; step one: 下载高版本的pip、setuptools、virtualenv的tar.gz包 step two: 进入PyCharm安装目录的 helpers 目录下 step three: 下载并安装grep和sed命令&#xff0c;然后执行 …

word处理控件Aspose.Words教程:使用 Python 删除 Word 中的空白页

Aspose.Words 是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。 Aspose API支持流行文件格式处理&#xff0c;并…

C++数据结构1——栈结构详解

一、栈的基本概念与特性 1. 栈的定义与特点 栈&#xff08;Stack&#xff09;是一种遵循后进先出&#xff08;LIFO, Last In First Out&#xff09;原则的线性数据结构&#xff0c;其核心特征包括&#xff1a; 单端操作&#xff1a;所有操作仅通过栈顶进行 动态存储&#xf…