写在前面

效果演示移步视频

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

1. 实验内容

1.绘制金刚石图案

金刚石图案的成图规则是:把一个圆周等分成 n n n份,然后每两点之间连线。当 n n n取奇数时,该图案可一笔连续绘成,即用MoveTo函数确定一个当前点,然后连续用LineTo函数连点成线。
请设计连线规则并编程实现。

2.绘制魔术三角形

绘制下图所示的魔术三角形图案 ,采用三种可明显区分的颜色填充。
在这里插入图片描述

3.绘制递归圆

应用递归的方法绘制如下所示的图案。
在这里插入图片描述

2. 实验环境

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

3. 问题分析

3.1问题1

对于问题1,首先需要计算出图案上各个像素点的坐标,设图案上有 n n n个点,图案中心点坐标 ( x m , y m ) (x_m,y_m) (xm,ym),记图案上每个顶点坐标为 ( x i , y i ) (x_i,y_i) (xi,yi), i = 0 , 1 , 2... n − 1 i=0,1,2...n-1 i=0,1,2...n1,则即每个顶点相对于中心点连线的夹角为 θ = 2 π n \theta=\frac{2\pi}n θ=n2π,则 x i = x m + c o s ( i θ ) x_i=x_m+cos(i\theta) xi=xm+cos(iθ) y i = y m + s i n ( i θ ) y_i=y_m+sin(i\theta) yi=ym+sin(iθ)。第二步就是将点连接起来。当且仅当 n n n为奇数时,可以使用一笔画算法。一笔画算法如下:记录当前点为 p o s pos pos。从0号点出发,大循环 n n n次。每次大循环中,小循环画直线。先隔0个点画一条直线,再隔1个点画一条直线,隔2个点画一条直线…直到小循环的最后是隔 n / 2 − 1 n/2-1 n/21个点画直线。设本次隔了 i i i个点画了直线,则目标点 j = ( p o s + 1 + i ) m o d    n j=(pos+1+i)\mod n j=(pos+1+i)modn。直到大循环结束,整个金刚石图案也绘制完成。

3.2问题2

对于问题2,可以预先处理出12个点位的坐标。点位标号如下:
在这里插入图片描述
初始时,设0点的坐标为 ( 400 , 800 ) (400,800) (400,800),长边长度500,短边长度100.则各个点坐标如下表所示:

点位编号 x x x坐标 y y y坐标所属区域点位编号 x x x坐标 y y y坐标所属区域
0400800红区6750713红区、紫区
1900800红区、黄区7700454红区、黄区
2950713黄区8500627紫区、黄区
3700281黄区、紫区9700627紫区
4600281紫区10650541红区
5350713红区、紫区11600627黄区

创建三个多边形,每个多边形表示魔术三角形的一个同色区域。多边形1按顺序将点0、点1、点7、点10、点6、点5连接。多边形2按顺序将点1、点2、点3、点8、点7、点11连接。多边形3将点3、点4、点5、点6、点9、点8连接。
为了实现变色的效果,每次都需要将每一个多边形重新赋值颜色并重新上色。在选取颜色时,选择使用随机的颜色。

3.3问题3

对于问题3,首先绘制出中心圆。之后定义一个函数circle,参数为递归的深度、小圆的半径、小圆的圆心 x x x坐标、小圆的圆心 y y y坐标、CDC类。中心圆外8个方向需要小圆。取中心圆半径为 r r r为大圆半径的1/4,坐标 ( x m , y m ) (x_m,y_m) (xm,ym),则每个小圆圆心坐标为 x i = 2 r cos ⁡ ( i θ ) x_i=2r\cos(i\theta) xi=2rcos(iθ), y i = 2 r sin ⁡ ( i θ ) y_i=2r\sin(i\theta) yi=2rsin(iθ),其中 i = 0 , 1 , 2 , . . . 7 i=0,1,2,...7 i=0,1,2,...7 θ = 4 5 ∘ ∗ 2 π / 180 \theta=45^{\circ}*2\pi/180 θ=452π/180。对于每个小圆,同样递归地再去画小圆的小圆,直到当前圆的递归深度为0。

4. 算法设计

4.1问题1

正如3.1所分析,算法的流程如下:
1.计算金刚石图案中各个点的坐标。
2.运行一笔画算法。记录当前点为 p o s pos pos。从0号点出发,大循环 n n n次。每次大循环中,小循环画直线。先隔0个点画一条直线,再隔1个点画一条直线,隔2个点画一条直线…直到小循环的最后是隔 n / 2 − 1 n/2-1 n/21个点画直线。小循环内部,设本次隔了 i i i个点画了直线,则目标点 j = ( p o s + i + 1 ) m o d    n j=(pos+i+1)\mod n j=(pos+i+1)modn。从 p o s pos pos j j j连一条直线。最后,将 p o s pos pos赋值为 j j j,完成小循环。完成 n n n次大循环后,算法成功结束。
流程图如下:
在这里插入图片描述

4.2问题2

正如章节3.2分析,算法的流程如下:
1.计算各个点位坐标,确定魔术三角形每个部分用到了哪些模块。
2.每隔一段时间给三个部分的颜色重新绘制上色,颜色为随机颜色。这一过程不断地循环进行。
在这里插入图片描述

4.3问题3

正如3.3所分析,算法的流程如下:
先创建pDC对象和画笔,然后递归地调用函数。在函数体内,先判断递归深度。若递归深度<=0,则退出,返回上一层。若递归深度>0,则找到递归圆圆心的坐标与半径,根据当前圆心与半径画出当前圆。再找到当前圆附属的8个小圆的坐标,递归地画小圆。小圆的半径为大圆的1/4,小圆的递归层数为大圆的递归层数-1。在画完所有圆后,算法结束。
在这里插入图片描述

5. 源代码

5.1金刚石

void CDiamondView::DrawDiamond(int nVertex, int radius,int millisecond){
	InvalidateRect(NULL);//强制清屏
	UpdateWindow();
	struct Point {
		double x, y;
	};
	Point* pnt = new Point[nVertex];
	const double PI = 3.14159265;
	CDC* pDC = GetDC();
	CRect rgn;
	GetClientRect(&rgn);
	CBrush newBrush;
	newBrush.CreateSolidBrush(RGB(255, 255, 255));
	pDC->FillRect(&rgn, &newBrush);
	CPen redPen(PS_SOLID, 1, RGB(0, 0, 255));
	CPen* OldPen = pDC->SelectObject(&redPen);
	double theta = 2 * PI / nVertex;
	for (int i = 0; i < nVertex; i++) {
		pnt[i].x = radius * cos(i * theta) + MaxX() / 2;
		pnt[i].y = radius * sin(i * theta) + MaxY() / 2;
	}
	int pos = 0;
	if (nVertex % 2 == 1) {
		for (int loop = 0; loop < nVertex; loop++) {
			for (int interv = 0; interv <= nVertex / 2 - 1; interv++) {//间隔的点的数量
				int nextpos = (pos + 1 + interv) % nVertex;
				pDC->MoveTo(round(pnt[pos].x), round(pnt[pos].y));
				pDC->LineTo(round(pnt[nextpos].x), round(pnt[nextpos].y));
				pos = nextpos;
				Sleep(millisecond);
			}
		}
	}
	else {
		for (int i = 0; i < nVertex-1; i++) {
			for (int j = i + 1; j < nVertex; j++) {
				pDC->MoveTo(round(pnt[i].x), round(pnt[i].y));
				pDC->LineTo(round(pnt[j].x), round(pnt[j].y));
				Sleep(millisecond);
			}
		}
	}
	pDC->SelectObject(OldPen);
}

5.2魔术三角形

//绘制魔术三角
void CDiamondView::DrawTriangle(){
	InvalidateRect(NULL);//强制清屏
	UpdateWindow();
	CDC* pDC = GetDC();
	CRect rect;
	GetClientRect(&rect);
	CBrush br(RGB(0, 0, 0));
	pDC->FillRect(&rect, &br);//设置背景为黑
	CBrush newBrush, * oldBrush;
	POINT vertex[12] =
	{ {400,800},
		{900,800},
		{950,713},
		{700,281},
		{600,281},
		{350,713},
		{750,713},
		{700,454},
		{500,627},
		{700,627},
		{650,541},
		{600,627} };
	POINT vertex1[6] = { vertex[0],vertex[1],vertex[7],vertex[10],vertex[6],vertex[5] };
	POINT vertex2[6] = { vertex[1],vertex[2],vertex[3],vertex[8],vertex[11],vertex[7] };
	POINT vertex3[6] = { vertex[3],vertex[4],vertex[5],vertex[6],vertex[9],vertex[8] };
	int t = 600;
	while (t--) {
		//第一个填充
		{
			CDC* pDC = GetDC();
			CBrush newBrush, * oldBrush;
			newBrush.CreateSolidBrush(RGB(rand() % 256, rand() % 256, rand() % 256));
			oldBrush = pDC->SelectObject(&newBrush);
			pDC->SetPolyFillMode(WINDING);
			pDC->Polygon(vertex1, 6);
			pDC->SelectObject(oldBrush);
			newBrush.DeleteObject();
		}
		//第二个填充
		{
			CDC* pDC = GetDC();
			CBrush newBrush, * oldBrush;
			newBrush.CreateSolidBrush(RGB(rand() % 256, rand() % 256, rand() % 256));
			oldBrush = pDC->SelectObject(&newBrush);
			pDC->SetPolyFillMode(WINDING);
			pDC->Polygon(vertex2, 6);
			pDC->SelectObject(oldBrush);
			newBrush.DeleteObject();
		}
		//第三个填充
		{
			CDC* pDC = GetDC();
			CBrush newBrush, * oldBrush;
			newBrush.CreateSolidBrush(RGB(rand() % 256, rand() % 256, rand() % 256));
			oldBrush = pDC->SelectObject(&newBrush);
			pDC->SetPolyFillMode(WINDING);
			pDC->Polygon(vertex3, 6);
			pDC->SelectObject(oldBrush);
			newBrush.DeleteObject();
		}
		Sleep(100);
	}
}

5.3 递归圆

//绘制递归圆
//nDepth:递归深度
void circle(int n, double r, double x0, double y0, CDC* pDC) {
	if (n == 0) 
		return;
	const double PI = 3.14159;
	double theta = 2*PI/8;
	double x, y;
	for (int i = 0; i < 8; i++)
	{
		x = 2 * r * cos(i * theta) + x0;
		y = 2 * r * sin(i * theta) + y0;
		CPen newPen, * oldPen;
		newPen.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
		oldPen = pDC->SelectObject(&newPen);
		CRect rect1(x - 0.25 * r, y - 0.25 * r, x + 0.25 * r, y + 0.25 * r);
		circle(n - 1, 0.25 * r, x, y, pDC);
		pDC->Ellipse(&rect1);
	}
}
//绘制递归圆主函数
//nDepth:递归深度

void CDiamondView::DrawRecursionCircle(int nDepth)
{
	InvalidateRect(NULL);//强制清屏
	UpdateWindow();
	if (nDepth == 0) 
		return;
	int x0 = MaxX()/2, y0 =MaxY()/2;//圆心
	int r = 160;//半径
	const double PI = 3.14159;
	CDC* pDC = GetDC();
	CPen newPen, * oldPen;
	newPen.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
	oldPen = pDC->SelectObject(&newPen);
	CRect rect(x0 - r, y0 - r, x0 + r, y0 + r);
	pDC->Ellipse(&rect);
	circle(nDepth, r, x0, y0, pDC);
}

6.程序运行结果

在这里插入图片描述
正确地绘制出了金刚石图案,且实现了一笔画的效果。在此图中,含有21个点,半径为150,图案中心在屏幕的中心。
在这里插入图片描述
正确地绘制出了魔术三角形。在每一秒,三角形三个部分的颜色都会随机变换一次。
在这里插入图片描述
正确地绘制出了递归深度为3的递归圆。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐