• 计算机图形学 实验一:二维图形绘制


    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);
    }
    
    • 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

    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);
    	}
    }
    
    • 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

    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);
    }
    
    • 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

    6.程序运行结果

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

  • 相关阅读:
    Java项目:SSM台球室计费管理系统
    【分享】集简云新功能:集简云浏览器机器人,将任意网站页面转换为API连接器
    【QT】TCP简易聊天框
    【目标检测】42、RepVGG | 使用结构重参数化来实现精度和速度平衡的主干网络
    【pen200-lab】10.11.1.72
    Java进阶篇--乐观锁 & 悲观锁
    Django--28Django官版UI细节调整及使用simpleui换肤
    陆拾捌- 如何通过数据影响决策(三)
    Golang 获取本地 IP 地址方法
    图像阈值分割(一)
  • 原文地址:https://blog.csdn.net/qq_46640863/article/details/125425797