【计算机图形学】实验一:二维图形绘制
金刚石图案的成图规则是:把一个圆周等分成nnn份,然后每两点之间连线。当nnn取奇数时,该图案可一笔连续绘成,即用MoveTo函数确定一个当前点,然后连续用LineTo函数连点成线。请设计连线规则并编程实现。绘制下图所示的魔术三角形图案 ,采用三种可明显区分的颜色填充。应用递归的方法绘制如下所示的图案。Visual Studio 2019图形学实验程序框架Windows11系统对于问题1,首先需要
写在前面
效果演示移步视频
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...n−1,则即每个顶点相对于中心点连线的夹角为 θ = 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/2−1个点画直线。设本次隔了 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坐标 | 所属区域 |
---|---|---|---|---|---|---|---|
0 | 400 | 800 | 红区 | 6 | 750 | 713 | 红区、紫区 |
1 | 900 | 800 | 红区、黄区 | 7 | 700 | 454 | 红区、黄区 |
2 | 950 | 713 | 黄区 | 8 | 500 | 627 | 紫区、黄区 |
3 | 700 | 281 | 黄区、紫区 | 9 | 700 | 627 | 紫区 |
4 | 600 | 281 | 紫区 | 10 | 650 | 541 | 红区 |
5 | 350 | 713 | 红区、紫区 | 11 | 600 | 627 | 黄区 |
创建三个多边形,每个多边形表示魔术三角形的一个同色区域。多边形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 θ=45∘∗2π/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/2−1个点画直线。小循环内部,设本次隔了
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的递归圆。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)