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


    1. 实验内容

    完成对北极星图案的缩放、平移、旋转、对称等二维变换。
    提示:
    首先要建好图1示的北极星图案的数据模型(顶点表、边表)
    另外,可重复调用“清屏”和“暂停”等函数,使整个变换过程具有动态效果。
    在这里插入图片描述

    2. 实验环境

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

    3. 问题分析

    首先,为了绘制出北极星图形,需要构建顶点表和边表。将顶点按如图2的顺序进行编号,并预处理出所有顶点相对于中心点的坐标值。
    在这里插入图片描述
    在按顺序对顶点编号完成后,即可计算出所有顶点相对于0号顶点的坐标值偏移量。设0号点坐标为窗口中心。

    顶点编号 x x x轴坐标 y y y轴坐标
    000
    11500
    29030
    3120120
    44090
    50240
    6-4090
    7-120120
    8-9030
    9-1500
    10-90-30
    11-120-120
    12-40-90
    130-240
    1440-90
    15120-120
    1690-30

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

    三角形编号颜色顶点三角形编号颜色顶点
    10/1/220/1/16
    30/8/940/9/10
    5绿0/2/36绿0/3/4
    7绿0/10/118绿0/11/12
    90/14/15100/15/16
    110/6/7120/7/8
    130/4/5140/5/6
    150/12/31160/13/14

    在构建完顶点表和边表之后,可以绘制出北极星图案。在对北极星进行放大与缩小时,可以使用如下的矩阵进行变换:
    ( X ′ Y ′ 1 ) = ( X Y 1 ) ( a 0 0 0 d 0 0 0 1 ) (XY1)

    (XY1)
    = (XY1)
    (XY1)
    (a000d0001)
    (XY1)=(XY1)a000d0001
    其中, a a a d d d分别为 x , y x,y x,y方向上的比例变换因子。对于原坐标点 ( x , y ) (x,y) (x,y),其变换后的新坐标点如下:
    { x ′ = a x y ′ = d y {x=axy=dy
    {x=axy=dy

    对图形进行平移时,平移变换的矩阵如下:
    ( X ′ Y ′ 1 ) = ( X Y 1 ) ( 1 0 0 0 1 0 l m 1 ) (XY1)
    = (XY1)
    (100010lm1)
    (XY1)=(XY1)10l01m001

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

    为了实现图形在以图形中心的为原点的缩放而不是以屏幕左上角的计算机坐标系原点进行缩放,需要将图形中心先平移到原点、再执行缩放,最后把图形移回原位置。
    设图形中心坐标点为 ( x p , y p ) (x_p,y_p) (xp,yp),则以图形中心坐标点为原点的缩放表达式如下:
    ( X ′ Y ′ 1 ) = ( X Y 1 ) ( 1 0 0 0 1 0 − x p − y p 1 ) ( a 0 0 0 d 0 0 0 1 ) ( 1 0 0 0 1 0 x p y p 1 ) (XY1)
    = (XY1)
    (100010xpyp1)
    (a000d0001)
    (100010xpyp1)
    (XY1)=(XY1)10xp01yp001a000d000110xp01yp001

    化简,得:
    ( X ′ Y ′ 1 ) = ( X Y 1 ) ( a 0 0 0 d 0 ( 1 − a ) x p ( 1 − d ) y p 1 ) (XY1)
    = (XY1)
    (a000d0(1a)xp(1d)yp1)
    (XY1)=(XY1)a0(1a)xp0d(1d)yp001

    此时新的坐标点为:
    { x ′ = a x + ( 1 − a ) x p y ′ = d y + ( 1 − d ) y p {x=ax+(1a)xpy=dy+(1d)yp
    {x=ax+(1a)xpy=dy+(1d)yp

    在对图形进行旋转时,也需要用到级联变换。若图形直接绕着绘图窗口的原点旋转,则会出现部分坐标点被旋转至窗口之外无法被正常绘制的情况。所以,需要级联变换,使得图形能够绕着自身的中心点旋转。先将图形旋转中心平移到原点,再将图形绕坐标系原点旋转 α \alpha α度,最后将旋转中心平移回到原点位置。
    其变换矩阵如下:
    ( X ′ Y ′ 1 ) = ( X Y 1 ) ( 1 0 0 0 1 0 − x p − y p 1 ) ( cos ⁡ α sin ⁡ α 0 − sin ⁡ α cos ⁡ α 0 0 0 1 ) ( 1 0 0 0 1 0 x p y p 1 ) (XY1)
    = (XY1)
    (100010xpyp1)
    (cosαsinα0sinαcosα0001)
    (100010xpyp1)
    (XY1)=(XY1)10xp01yp001cosαsinα0sinαcosα000110xp01yp001

    化简,得:
    ( X ′ Y ′ 1 ) = ( X Y 1 ) ( cos ⁡ α sin ⁡ α 0 − sin ⁡ α cos ⁡ α 0 ( 1 − cos ⁡ α ) x p + y p sin ⁡ α − x p sin ⁡ α + ( 1 − cos ⁡ α ) y p 1 ) (XY1)
    = (XY1)
    (cosαsinα0sinαcosα0(1cosα)xp+ypsinαxpsinα+(1cosα)yp1)
    (XY1)=(XY1)cosαsinα(1cosα)xp+ypsinαsinαcosαxpsinα+(1cosα)yp001

    此时新的坐标点为:
    { x ′ = x cos ⁡ α − y sin ⁡ α + ( 1 − cos ⁡ α ) x p + y p sin ⁡ α y ′ = x sin ⁡ α + y cos ⁡ α − x p sin ⁡ α + ( 1 − cos ⁡ α ) y p {x=xcosαysinα+(1cosα)xp+ypsinαy=xsinα+ycosαxpsinα+(1cosα)yp
    {x=xcosαysinα+(1cosα)xp+ypsinαy=xsinα+ycosαxpsinα+(1cosα)yp

    对图形进行对称变换时,直线方程为( A x + B y + C = 0 Ax+By+C=0 Ax+By+C=0)。本次实验取 A = − 2 A=-2 A=2 B = 1 B=1 B=1 C = 100 C=100 C=100,则直线方程为 − 2 x + y + 100 = 0 -2x+y+100=0 2x+y+100=0。直线与 x x x轴夹角 α = arctan ⁡ ( − A B ) \alpha=\arctan(-\frac AB) α=arctan(BA)
    对图像的对称变换分为5个步骤:1.将直线平移到原点。2.将直线旋转与 x x x轴重合。3.对图像进行对称。4.第2步的复原操作。5.第1步的复原操作。
    变换矩阵如下:
    T = ( 1 0 0 0 1 0 C A 0 1 ) ( cos ⁡ α − sin ⁡ α 0 sin ⁡ α cos ⁡ α 0 0 0 1 ) ( 1 0 0 0 − 1 0 0 0 1 ) ( cos ⁡ α sin ⁡ α 0 − sin ⁡ α cos ⁡ α 0 0 0 1 ) ( 1 0 0 0 1 0 − C A 0 1 ) T=(100010CA01)
    (cosαsinα0sinαcosα0001)
    (100010001)
    (cosαsinα0sinαcosα0001)
    (100010CA01)
    T=10AC010001cosαsinα0sinαcosα0001100010001cosαsinα0sinαcosα000110AC010001

    化简,可得:
    T = ( cos ⁡ 2 α sin ⁡ 2 α 0 sin ⁡ 2 α − cos ⁡ 2 α 0 ( cos ⁡ 2 α − 1 ) C A sin ⁡ 2 α C A 1 ) T= (cos2αsin2α0sin2αcos2α0(cos2α1)CAsin2αCA1)
    T=cos2αsin2α(cos2α1)ACsin2αcos2αsin2αAC001

    此时新坐标点为:
    { x ′ = x cos ⁡ 2 α + y sin ⁡ 2 α + ( cos ⁡ 2 α − 1 ) C A y ′ = x sin ⁡ 2 α − y cos ⁡ 2 α + sin ⁡ 2 α C A {x=xcos2α+ysin2α+(cos2α1)CAy=xsin2αycos2α+sin2αCA
    {x=xcos2α+ysin2α+(cos2α1)ACy=xsin2αycos2α+sin2αAC

    4. 算法设计

    在本次实验中,实现的功能顺序与流程如下:首先,绘制出北极星图案。之后,对北极星图案依次进行以窗口为原点的放大变换与缩小变换。对北极星进行以北极星中心点为原点的变换。对北极星进行平移变换。对北极星进行旋转变换。对北极星进行对称。
    在对北极星进行放大与缩小变换时,不使用迭代,而是直接用缩放倍数为参数乘上原北极星的点位,以防止误差积累。在对北极星进行旋转变换时,同样不使用迭代。以旋转角度为参数,根据第3章节所涉及的公式进行旋转变换。在对北极星进行对称时,只进行了一次对称操作。
    本实验中使用的算法主要涉及数学处理。数学处理见第三章。程序与流程上的处理较为简单,以图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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143

    6.程序运行结果

    图4 北极星原图
    如图,正确地绘制出了北极星的原图。
    图5 放大北极星
    如图,正确地以屏幕左上角为原点放大了北极星。
    图6 缩小北极星
    如图,正确地以屏幕左上角为原点缩小了北极星。
    图7 缩小北极星
    如图,正确地以北极星中心为原点缩小了北极星。
    图8 放大北极星
    如图,正确地以北极星中心为原点放大了北极星。
    图9 平移北极星
    如图,正确地平移了北极星,向屏幕右下移动。
    图10 旋转北极星
    如图,正确地旋转了北极星。
    图11 对称北极星
    如图,正确地以直线为对称轴对北极星进行了对称变换。

    7.总结

  • 相关阅读:
    Citus 分布式 PostgreSQL 集群 - SQL Reference(查询分布式表 SQL)
    MySQL基础语法快速上手
    kerberos:介绍
    R语言使用hexSticker包将ggplot2包可视化的结果转换为六角图(六角贴、六角形贴纸、ggplot2 plot to hex sticker)
    springboot+java+vue毕业生学习交流平台g1el1
    redis 配置主从复制,哨兵模式案例
    Unity Rain Ai 插件的使用入门
    AES加解密概念
    oracle12c到19c adg搭建(四)dg搭建
    第二课 ceph基础学习-OSD扩容换盘和集群运维
  • 原文地址:https://blog.csdn.net/qq_46640863/article/details/125612275