写在前面

效果演示移步视频

https://www.bilibili.com/video/BV1ah4y177dL

1. 实验内容

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

2. 实验环境

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

3. 问题分析

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

顶点编号 x x x轴坐标 y y 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

在构建完顶点表和边表之后,可以绘制出北极星图案。在对北极星进行放大与缩小时,可以使用如下的矩阵进行变换:
( X ′ Y ′ 1 ) = ( X Y 1 ) ( a 0 0 0 d 0 0 0 1 ) \begin{pmatrix} X'&Y'&1 \end{pmatrix}= \begin{pmatrix} X&Y&1 \end{pmatrix} \begin{pmatrix} a&0&0\\ 0&d&0\\ 0&0&1 \end{pmatrix} (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 \begin{cases} x'=ax\\y'=dy \end{cases} {x=axy=dy
对图形进行平移时,平移变换的矩阵如下:
( X ′ Y ′ 1 ) = ( X Y 1 ) ( 1 0 0 0 1 0 l m 1 ) \begin{pmatrix} X'&Y'&1 \end{pmatrix}= \begin{pmatrix} X&Y&1 \end{pmatrix} \begin{pmatrix} 1&0&0\\ 0&1&0\\ l&m&1 \end{pmatrix} (XY1)=(XY1) 10l01m001
其中, l l l为图形在 x x x轴上的偏移量, m m m为图形在 y y y轴上的偏移量。在进行该运算后,得到的新坐标点如下:
{ x ′ = x + l y ′ = y + m \begin{cases} x'=x+l\\y'=y+m \end{cases} {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 ) \begin{pmatrix} X'&Y'&1 \end{pmatrix}= \begin{pmatrix} X&Y&1 \end{pmatrix} \begin{pmatrix} 1&0&0\\ 0&1&0\\ -x_p&-y_p&1 \end{pmatrix} \begin{pmatrix} a&0&0\\ 0&d&0\\ 0&0&1 \end{pmatrix} \begin{pmatrix} 1&0&0\\ 0&1&0\\ x_p&y_p&1 \end{pmatrix} (XY1)=(XY1) 10xp01yp001 a000d0001 10xp01yp001
化简,得:
( X ′ Y ′ 1 ) = ( X Y 1 ) ( a 0 0 0 d 0 ( 1 − a ) x p ( 1 − d ) y p 1 ) \begin{pmatrix} X'&Y'&1 \end{pmatrix}= \begin{pmatrix} X&Y&1 \end{pmatrix} \begin{pmatrix} a&0&0\\ 0&d&0\\ (1-a)x_p&(1-d)y_p&1 \end{pmatrix} (XY1)=(XY1) a0(1a)xp0d(1d)yp001
此时新的坐标点为:
{ x ′ = a x + ( 1 − a ) x p y ′ = d y + ( 1 − d ) y p \begin{cases} x'=ax+(1-a)x_p\\y'=dy+(1-d)y_p \end{cases} {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 ) \begin{pmatrix} X'&Y'&1 \end{pmatrix}= \begin{pmatrix} X&Y&1 \end{pmatrix} \begin{pmatrix} 1&0&0\\ 0&1&0\\ -x_p&-y_p&1 \end{pmatrix} \begin{pmatrix} \cos\alpha&\sin\alpha&0\\ -\sin\alpha&\cos\alpha&0\\ 0&0&1 \end{pmatrix} \begin{pmatrix} 1&0&0\\ 0&1&0\\ x_p&y_p&1 \end{pmatrix} (XY1)=(XY1) 10xp01yp001 cosαsinα0sinαcosα0001 10xp01yp001
化简,得:
( 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 ) \begin{pmatrix} X'&Y'&1 \end{pmatrix}= \begin{pmatrix} X&Y&1 \end{pmatrix} \begin{pmatrix} \cos\alpha&\sin\alpha&0\\ -\sin\alpha&\cos\alpha&0\\ (1-\cos\alpha)x_p+y_p\sin\alpha&-x_p\sin\alpha+(1-\cos\alpha)y_p&1 \end{pmatrix} (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 \begin{cases} x'=x\cos\alpha-y\sin\alpha+(1-\cos\alpha)x_p+y_p\sin\alpha\\ y'=x\sin\alpha+y\cos\alpha-x_p\sin\alpha+(1-\cos\alpha)y_p \end{cases} {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=\begin{pmatrix} 1&0&0\\ 0&1&0\\ \frac CA&0&1 \end{pmatrix} \begin{pmatrix} \cos\alpha&-\sin\alpha&0\\ \sin\alpha&\cos\alpha&0\\ 0&0&1 \end{pmatrix} \begin{pmatrix} 1&0&0\\ 0&-1&0\\ 0&0&1 \end{pmatrix} \begin{pmatrix} \cos\alpha&\sin\alpha&0\\ -\sin\alpha&\cos\alpha&0\\ 0&0&1 \end{pmatrix} \begin{pmatrix} 1&0&0\\ 0&1&0\\ -\frac CA&0&1 \end{pmatrix} T= 10AC010001 cosαsinα0sinαcosα0001 100010001 cosαsinα0sinαcosα0001 10AC010001
化简,可得:
T = ( cos ⁡ 2 α sin ⁡ 2 α 0 sin ⁡ 2 α − cos ⁡ 2 α 0 ( cos ⁡ 2 α − 1 ) C A sin ⁡ 2 α C A 1 ) T= \begin{pmatrix} \cos2\alpha&\sin2\alpha&0\\ \sin2\alpha&-\cos2\alpha&0\\ (\cos2\alpha-1)\frac CA&\sin2\alpha\frac CA&1 \end{pmatrix} 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 \begin{cases} x'=x\cos2\alpha+y\sin2\alpha+(\cos2\alpha-1)\frac CA\\ y'=x\sin2\alpha-y\cos2\alpha+\sin2\alpha\frac CA \end{cases} {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);
}

6.程序运行结果

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

7.总结

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐