【计算机图形学】实验三:二维图形变换

简介: 【计算机图形学】实验三:二维图形变换

1. 实验内容

完成对北极星图案的缩放、平移、旋转、对称等二维变换。

提示:

首先要建好图1示的北极星图案的数据模型(顶点表、边表)

另外,可重复调用“清屏”和“暂停”等函数,使整个变换过程具有动态效果。

2. 实验环境

Visual Studio 2022、图形学实验程序框架、Windows11系统

3. 问题分析

首先,为了绘制出北极星图形,需要构建顶点表和边表。将顶点按如图2的顺序进行编号,并预处理出所有顶点相对于中心点的坐标值。

在按顺序对顶点编号完成后,即可计算出所有顶点相对于0号顶点的坐标值偏移量。设0号点坐标为窗口中心。

顶点编号 x轴坐标 y轴坐标
0 0 0
1 150 0
2 90 30
3 120 120
4 40 90
5 0 240
6 -40 90
7 -120 120
8 -90 30
9 -150 0
10 -90 -30
11 -120 -120
12 -40 -90
13 0 -240
14 40 -90
15 120 -120
16 90 -30

在构造完顶点表后,对所有顶点构造边表。在绘制图形时,可以将每一个闭合部分视作三角形,并用不同颜色的笔进行绘制。小三角形与颜色对应关系如下:

三角形编号 颜色 顶点 三角形编号 颜色 顶点
1 0/1/2 2 0/1/16
3 0/8/9 4 0/9/10
5 绿 0/2/3 6 绿 0/3/4
7 绿 0/10/11 8 绿 0/11/12
9 0/14/15 10 0/15/16
11 0/6/7 12 0/7/8
13 0/4/5 14 0/5/6
15 0/12/31 16 0/13/14

在构建完顶点表和边表之后,可以绘制出北极星图案。在对北极星进行放大与缩小时,可以使用如下的矩阵进行变换:

image.png

其中,ad分别为x,y方向上的比例变换因子。对于原坐标点(x,y),其变换后的新坐标点如下:

image.png

对图形进行平移时,平移变换的矩阵如下:

image.png

其中,l为图形在x轴上的偏移量,m为图形在y轴上的偏移量。在进行该运算后,得到的新坐标点如下:

image.png

为了实现图形在以图形中心的为原点的缩放而不是以屏幕左上角的计算机坐标系原点进行缩放,需要将图形中心先平移到原点、再执行缩放,最后把图形移回原位置。

设图形中心坐标点为(xp,yp),则以图形中心坐标点为原点的缩放表达式如下:

image.png

在对图形进行旋转时,也需要用到级联变换。若图形直接绕着绘图窗口的原点旋转,则会出现部分坐标点被旋转至窗口之外无法被正常绘制的情况。所以,需要级联变换,使得图形能够绕着自身的中心点旋转。先将图形旋转中心平移到原点,再将图形绕坐标系原点旋转α度,最后将旋转中心平移回到原点位置。

其变换矩阵如下:


image.png

image.png

对图像的对称变换分为5个步骤:1.将直线平移到原点。2.将直线旋转与x轴重合。3.对图像进行对称。4.第2步的复原操作。5.第1步的复原操作。

变换矩阵如下:

image.png

4. 算法设计

在本次实验中,实现的功能顺序与流程如下:首先,绘制出北极星图案。之后,对北极星图案依次进行以窗口为原点的放大变换与缩小变换。对北极星进行以北极星中心点为原点的变换。对北极星进行平移变换。对北极星进行旋转变换。对北极星进行对称。

在对北极星进行放大与缩小变换时,不使用迭代,而是直接用缩放倍数为参数乘上原北极星的点位,以防止误差积累。在对北极星进行旋转变换时,同样不使用迭代。以旋转角度为参数,根据第3章节所涉及的公式进行旋转变换。在对北极星进行对称时,只进行了一次对称操作。

本实验中使用的算法主要涉及数学处理。数学处理见第三章。程序与流程上的处理较为简单,以图3的流程为例。图3的流程为以窗口原点为原点进行的北极星进行放大操作流程。

将北极星的各个点的初始坐标值预处理后,每次执行变换时都是将初始坐标值与参数代入公式绘制新图形,并循环地暂停、清空屏幕、重新绘制新的图像。

5. 源代码

void drawPolaris(CDC* pDC, POINT vertex[17]){
  CPen newPen, * oldPen;
  newPen.CreatePen(PS_SOLID, 2, RGB(255, 0, 255));
  oldPen = pDC->SelectObject(&newPen);
  POINT shape1[3] = { vertex[0],vertex[1],vertex[2] };
  pDC->Polygon(shape1, 3);
  POINT shape2[3] = { vertex[0],vertex[1],vertex[16] };
  pDC->Polygon(shape2, 3);
  POINT shape3[3] = { vertex[0],vertex[9],vertex[8] };
  pDC->Polygon(shape3, 3);
  POINT shape4[3] = { vertex[0],vertex[9],vertex[10] };
  pDC->Polygon(shape4, 3);
  newPen.DeleteObject();
  newPen.CreatePen(PS_SOLID, 2, RGB(0, 255, 0));
  oldPen = pDC->SelectObject(&newPen);
  POINT shape5[3] = { vertex[0],vertex[2],vertex[3] };
  pDC->Polygon(shape5, 3);
  POINT shape6[3] = { vertex[0],vertex[3],vertex[4] };
  pDC->Polygon(shape6, 3);
  POINT shape7[3] = { vertex[0],vertex[11],vertex[10] };
  pDC->Polygon(shape7, 3);
  POINT shape8[3] = { vertex[0],vertex[11],vertex[12] };
  pDC->Polygon(shape8, 3);
  newPen.DeleteObject();
  newPen.CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
  oldPen = pDC->SelectObject(&newPen);
  POINT shape9[3] = { vertex[0],vertex[14],vertex[15] };
  pDC->Polygon(shape9, 3);
  POINT shape10[3] = { vertex[0],vertex[15],vertex[16] };
  pDC->Polygon(shape10, 3);
  POINT shape11[3] = { vertex[0],vertex[6],vertex[7] };
  pDC->Polygon(shape11, 3);
  POINT shape12[3] = { vertex[0],vertex[7],vertex[8] };
  pDC->Polygon(shape12, 3);
  newPen.DeleteObject();
  newPen.CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
  oldPen = pDC->SelectObject(&newPen);
  POINT shape13[3] = { vertex[0],vertex[4],vertex[5] };
  pDC->Polygon(shape13, 3);
  POINT shape14[3] = { vertex[0],vertex[6],vertex[5] };
  pDC->Polygon(shape14, 3);
  POINT shape15[3] = { vertex[0],vertex[12],vertex[13] };
  pDC->Polygon(shape15, 3);
  POINT shape16[3] = { vertex[0],vertex[14],vertex[13] };
  pDC->Polygon(shape16, 3);
}
//北极星
void CDiamondView::Polaris()
{
  const double PI = 3.14159;
  InvalidateRgn(NULL);
  UpdateWindow();
  CDC* pDC = GetDC();
  POINT vertex[17] = {
    {MaxX() / 2,      MaxY() / 2},
    {MaxX() / 2 + 150,MaxY() / 2},
    {MaxX() / 2 + 90, MaxY() / 2 + 30},
    {MaxX() / 2 + 120,MaxY() / 2 + 120},
    {MaxX() / 2 + 40, MaxY() / 2 + 90},
    {MaxX() / 2,      MaxY() / 2 + 240},
    {MaxX() / 2 - 40, MaxY() / 2 + 90},
    {MaxX() / 2 - 120,MaxY() / 2 + 120},
    {MaxX() / 2 - 90, MaxY() / 2 + 30},
    {MaxX() / 2 - 150,MaxY() / 2},
    {MaxX() / 2 - 90, MaxY() / 2 - 30},
    {MaxX() / 2 - 120,MaxY() / 2 - 120},
    {MaxX() / 2 - 40, MaxY() / 2 - 90},
    {MaxX() / 2 ,     MaxY() / 2 - 240},
    {MaxX() / 2 + 40, MaxY() / 2 - 90},
    {MaxX() / 2 + 120,MaxY() / 2 - 120},
    {MaxX() / 2 + 90, MaxY() / 2 - 30}
  };
  POINT initVertex[17]={};
  for (int i = 0; i < 17; i++)
    initVertex[i] = vertex[i];
  drawPolaris(pDC, vertex);
  Sleep(2000);
  InvalidateRect(NULL);
  UpdateWindow();
  for (double k = 1; k <=1.3; k += 0.01) {
    for (int i = 0; i < 17; i++) {
      vertex[i].x = initVertex[i].x * k;
      vertex[i].y = initVertex[i].y * k;
    }
    drawPolaris(pDC, vertex);
    Sleep(50);
    InvalidateRect(NULL);
    UpdateWindow();
  }
  for (double k = 1.3; k >=0.1; k -= 0.01) {
    for (int i = 0; i < 17; i++) {
      vertex[i].x = initVertex[i].x * k;
      vertex[i].y = initVertex[i].y * k;
    }
    drawPolaris(pDC, vertex);
    Sleep(50);
    InvalidateRect(NULL);
    UpdateWindow();
  }
  for (double k = 0.1; k <= 1.5; k += 0.01) {
    int xp = MaxX() / 2;
    int yp = MaxY() / 2;
    for (int i = 0; i < 17; i++) {
      vertex[i].x = k * initVertex[i].x + (1 - k) * xp;
      vertex[i].y = k * initVertex[i].y + (1 - k)* yp;
    }
    drawPolaris(pDC, vertex);
    Sleep(50);
    InvalidateRect(NULL);
    UpdateWindow();
  }
  for (int dx = 0; dx < 100; dx++) {
    for (int i = 0; i < 17; i++) {
      vertex[i].x = initVertex[i].x + dx;
      vertex[i].y = initVertex[i].y + dx;
    }
    drawPolaris(pDC, vertex);
    Sleep(50);
    InvalidateRect(NULL);
    UpdateWindow();
  }
  for (double theta = 0; theta <=2* PI; theta += 0.05) {
    int xp = MaxX() / 2;
    int yp = MaxY() / 2;
    for (int i = 0; i < 17; i++)
    {
      vertex[i].x = initVertex[i].x * cos(theta) - initVertex[i].y * sin(theta) + xp - xp * cos(theta) + yp * sin(theta);
      vertex[i].y = initVertex[i].x * sin(theta) + initVertex[i].y * cos(theta) - xp * sin(theta) + yp - yp * cos(theta);
    }
    drawPolaris(pDC, vertex);
    Sleep(50);
    InvalidateRect(NULL);
    UpdateWindow();
  }
  double A = -2, B = 1, C = 800;
  double alpha = atan(( - 1.0 * A) / (1.0 * B));
  for (int i = 0; i < 17; i++) {
    vertex[i].x = max(initVertex[i].x * cos(2 * alpha) + initVertex[i].y * sin(2 * alpha) + (cos(2 * alpha) - 1) * C / A,0);
    vertex[i].y = max(initVertex[i].x * sin(2 * alpha) + initVertex[i].y * -cos(2 * alpha) + sin(2 * alpha) * C / A,0);
  }
  drawPolaris(pDC, initVertex);
  drawPolaris(pDC, vertex);
}

6.程序运行结果

如图,正确地绘制出了北极星的原图。

如图,正确地以屏幕左上角为原点放大了北极星。

如图,正确地以屏幕左上角为原点缩小了北极星。

如图,正确地以北极星中心为原点缩小了北极星。

如图,正确地以北极星中心为原点放大了北极星。

如图,正确地平移了北极星,向屏幕右下移动。

如图,正确地旋转了北极星。

如图,正确地以直线为对称轴对北极星进行了对称变换。

7.总结


目录
相关文章
|
10月前
|
人工智能 索引 Python
[oeasy]python094_使用python控制音符列表_midi_文件制作
本文介绍了如何使用Python控制音符列表制作MIDI文件。首先回顾了列表下标索引(正数和负数)的用法,接着通过`mido`库实现MIDI文件生成。以《两只老虎》为例,详细解析了代码逻辑:定义音高映射、构建旋律列表、创建MIDI文件框架,并将音符插入音轨。还探讨了音符时值与八度扩展的实现方法。最终生成的MIDI文件可通过不同平台播放或编辑。总结中提到,此技术可用于随机生成符合调性的旋律,同时引发对列表其他实际应用的思考。
373 5
|
算法 图形学
【计算机图形学】实验四 二维图形的缩放、旋转,平移,组合变换
【计算机图形学】实验四 二维图形的缩放、旋转,平移,组合变换
1078 2
|
网络安全
window系统下安装elk
本文介绍了Elasticsearch、Logstash和Kibana(统称ELK栈)8.17.3版本的安装与配置流程。主要内容包括: - **Elasticsearch**:详细描述了从下载到启动服务的步骤,以及`elasticsearch.yml`的关键配置项,并提供了Postman操作示例及常见问题解决方案。 - **Logstash**:涵盖了插件安装、配置文件`logstash.conf`编写及其启动命令。 - **Kibana**:讲解了下载、配置`kibana.yml`和启动过程,确保与Elasticsearch正确连接。
|
自然语言处理 负载均衡 Kubernetes
分布式系统架构2:服务发现
服务发现是分布式系统中服务实例动态注册和发现机制,确保服务间通信。主要由注册中心和服务消费者组成,支持客户端和服务端两种发现模式。注册中心需具备高可用性,常用框架有Eureka、Zookeeper、Consul等。服务注册方式包括主动注册和被动注册,核心流程涵盖服务注册、心跳检测、服务发现、服务调用和注销。
618 13
|
人工智能 云计算 数据中心
加码香港!连续市场第一,启动新计划
加码香港!连续市场第一,启动新计划
512 4
|
网络协议 数据库 网络架构
OSPF 四种设备角色:IR、ABR、BR、ASBR
【4月更文挑战第5天】
4634 2
OSPF 四种设备角色:IR、ABR、BR、ASBR
|
机器学习/深度学习 人工智能 自然语言处理
【AIGC】基于大语言模型构建多语种聊天机器人(基于Bloom大语言模型)
【5月更文挑战第8天】基于大语言模型Bloom构建多语种聊天机器人
427 1
|
存储 安全 编译器
【c++】类和对象(四)深入了解拷贝构造函数
朋友们大家好啊,本篇内容带大家深入了解拷贝构造函数
【c++】类和对象(四)深入了解拷贝构造函数
Python调用谷歌翻译接口
Python调用谷歌翻译接口
|
存储 缓存 算法
双向链表的建立和使用场景
双向链表的建立和使用场景

热门文章

最新文章

下一篇
开通oss服务