作为一个一开始学习C++就被递归和分形的理解虐的不要不要的新手小白,我jio的还是有必要把自己的探索过程和大家分享一下。
下面的这段程序是利用装载了easyx数据库的VS画koch分形雪花,当时我想了好久才想出了利用三角函数来绘图(*-*)。
注意哈,这个要装easyx库哈。
主要的想法都写在注释里了,边看代码边理解应该容易点吧。。。
//利用easyx分形画雪花的研究
#include<iostream>
#include<graphics.h>
#include<conio.h>
#include<cmath>
#include<Windows.h>
using namespace std;
//思路:先理解递归和分形的意思,然后由最初的初始化情况开始,一步一步慢慢写,慢慢探究.
/*
/\\
/ \\
_______/ \\_______
首先你需要根据目标雪花图找到这个最小重复单元,即雪花是由这个单元不断重复而得到的。
*/
const double PI = 3.1415926;//定义圆周率(全局变量)。
int i = 1; //利用全局变量i控制初始化图像
void drawTriangle(double x0, double y0, double angle, double length, int depth)//利用初始点,角度,长度和递归深度构成函数。
{ //由初始点找到其余四个点。这样的好处是三角形一定是逆时针画出,且由于应用了任意角的三角函数,不用担心正负。
double x1, x2, x3, x4, y1, y2, y3, y4;//定义另外四个点的坐标参数
if (depth == 0) //定义递归结束的条件。否则会出现栈溢出的现象。(类似死循环)会出现warnning,error报错。
{
i = 1; //退出时重置i,初始化下一次绘图,否自会出现第一条线画不出来。
return;
}
else { //根据数学原理:任意角的三角函数和类似极坐标的原理写出1,2,3,4点的坐标。所有坐标都有初始一个点所决定,这样能提高准确率。
x1 = x0 + length * cos(angle) / 3 * 1, y1 = y0 + length * sin(angle) / 3 * 1;//物理或者数学好的同学理解起来方便
x2 = x0 + length * cos(angle) / 3 * 2, y2 = y0 + length * sin(angle) / 3 * 2;
x3 = x0 + length * cos(angle) / 3 * 3, y3 = y0 + length * sin(angle) / 3 * 3;
x4 = x0 + length / sqrt(3)*cos(angle + PI / 6), y4 = y0 + length / sqrt(3)*sin(angle + PI / 6);//注意三角形顶点的寻找,不同于其他三个点。
}//所有点的寻找都是由特殊到一般,推导出一般公式
/* (x4,y4) /|\\y
/\\ |
2 / \\ 3 |
1 / \\ 4 |
________/ \\________ |----------->x ***角度angle起始边为x轴,逆时针为正。***
(x0,y0) (x1,y1) (x2,y3) (x3,y3)
对于另外的倾斜的情况,只需要将此图旋转即可(以(x0,y0)为支点。
*/
if (i == 1) //绘制初始缺失的两条线
{
line(x0, y0, x1, y1);
line(x2, y2, x3, y3);
i++; //此步骤是为了函数递归调用时不重复画(因为只需要画出初始的两条即可)。
}
//接下来对于每条边进行处理,发现其实只要画三条线即可。两条连线,一条擦除线。
line(x1, y1, x4, y4); //连接两条线,即两个三等分点分别于分出三角形的顶点相连。
line(x4, y4, x2, y2);
setlinestyle(PS_SOLID, 3); //设置擦除线的属性,注意要宽,黑,否则由于像素和int型计算的局限性会有白线产生。(微小的错位现象)
setlinecolor(BLACK);
line(x1, y1, x2, y2); //绘制擦除线
setlinestyle(PS_SOLID, 1); //为下一次绘制设置白线属性。
setlinecolor(WHITE);
drawTriangle(x0, y0, angle, length / 3, depth - 1); //非常重要的四个递归,注意到图形的重复性,基础图形为四条直线连成的折线。
drawTriangle(x1, y1, angle + PI / 3, length / 3, depth - 1);//_/\\_为这个图形,此为基础图形。
drawTriangle(x4, y4, angle - PI / 3, length / 3, depth - 1);//对于基础图形,每条直线都要递归,所以会有四个函数。
drawTriangle(x2, y2, angle, length / 3, depth - 1); //程序后面会有参数设置的详细分析。
}
void drawSnowflake_1(double x0, double y0, double angle, double length, int depth)//以某个端点为定位点、起始点并画、旋转。
{
drawTriangle(x0, y0, angle, length, depth);
drawTriangle(x0 + length * cos(angle), y0 + length * sin(angle), angle - 2 * PI / 3, length, depth);
drawTriangle(x0 + length * cos(angle - PI / 3), y0 + length * sin(angle - PI / 3), angle + 2 * PI / 3, length, depth);
}
void drawSnowflake_2(double x0, double y0, double angle, double length, int depth)//以等边三角形中心为定位点,找出起始点再画、旋转。
//调整思路是x0和y0变为等边三角形中心点,再由其找出原来的x0,y0的位置(在_1时的x0,y0)。即用后来变动的公式分别代换x0,y0。
//自己在草稿纸上画图推导,考验数学能力。注意角度都是弧度制。
//其中,angle为旋转角度,默认角度为drawSnowflake_1中angle=0的样式。旋转意为把图形绕中心点(x0,y0)逆时针旋转。
{
x0 += length / sqrt(3)*cos(5 * PI / 6 + angle);//注意,千万注意,分数别写错!!!别写颠倒!!
y0 += length / sqrt(3)*sin(5 * PI / 6 + angle);
drawTriangle(x0, y0, angle, length, depth);
drawTriangle(x0 + length * cos(angle), y0 + length * sin(angle), angle - 2 * PI / 3, length, depth);
drawTriangle(x0 + length * cos(angle - PI / 3), y0 + length * sin(angle - PI / 3), angle + 2 * PI / 3, length, depth);
}
void drawRotatingSnowflake(double x0, double y0, double length, int speed, int depth)//旋转的雪花
{
double rotationAngle = 0;
for (;1;rotationAngle += PI / 360)//调节转速
{
drawSnowflake_2(800, 400, rotationAngle, 600, depth);
Sleep(speed);//也可调节转速
cleardevice();
//Sleep(10);
}
}
int main()//主函数
{
const int depth = 4;//方便调整深度。
initgraph(1600, 800);
setorigin(0, 800);
setaspectratio(1, -1);
//下面三个函数最好不要同时用。
drawSnowflake_1(500, 600, 0, 600, depth);//定位点端点开始画。
//drawSnowflake_2(800, 400,-PI/12, 600, depth);//定位点中心点开始画。
//drawRotatingSnowflake(800, 400, 600, 100, depth);//旋转的雪花。
_getch();
closegraph();
}
/* (x4,y4) /|\\y
/\\ |
2 / \\ 3 |
1 / \\ 4 |
________/ \\________ |----------->x
(x0,y0) (x1,y1) (x2,y3) (x3,y3)
对于另外的倾斜的情况,只需要将此图旋转即可(以(x0,y0)为支点。
*/
/*到此时一个基础图形已经画完了,接下来要对每条边分别递归。即找四个顶点,分别确定角度长度。
注意到对于1234边初始点,长度,深度都是显而易见的。
初学者容易犯的错误就是将角度与长度用常量值作为实参,错误原因是没有意识到递归可以远远不止一两次。
一次两次利用常量不影响但是三次以及更多次递归时就失去了递归的效果,因为参数一直是常量,并不是变量。
当然有的参数必须要是常量的除外。
所以对于参数必须要好好思考,免得出错(一边写一边调试一边改就好了)。
递归的难点在于角度的变化,由于我们之前写函数是方法选的好,所以可以找到统一而又简洁的规律。
drawTriangle(x0, y0, angle, length / 3, depth - 1); //对于1直线,角度不变,继续分形,所以为angle(千万别写成0!)
drawTriangle(x1, y1, angle + PI / 3, length / 3, depth - 1);//对于2直线,自己在图片上再画对于2直线的两次分形,找到对应的
2直线(建议用将2直线标成以(x1,y1)为起点的向量,这样容易找到之后的规律,发现每次递归加上一个PI/3即可(60度)。
drawTriangle(x4, y4, angle - PI / 3, length / 3, depth - 1);//3直线的方法同2直线。
drawTriangle(x2, y2, angle, length / 3, depth - 1); 4直线的方法同1直线。
//最后深度减一
//至此递归过程就全部写完每次调用就会对于一条直线构图,而构图后由于递归调用本体四次,会继续对构图后的四条直线分别再次构图,
一直到深度为零停止构图。*/
/*int main()//看sin()是弧度制还是角度制
{
cout << sin(PI/6);
system(\"pause\");
return 0;
}*/
/*int main()//看一下颜色
{
const int depth = 1;
initgraph(1600, 800);
setorigin(0, 800);
setaspectratio(1, -1);
//setlinecolor(RED);
line(600, 300, 1000, 300);
//setlinecolor(WHITE);
setlinestyle(PS_SOLID , 2);
setlinecolor(BLACK);
line(600, 400, 1000, 400);
_getch();
closegraph();
}*/
程序实际上也是有不足之处(我想想还有啥改进的地方),比如利用画两条擦一条来做,由于像素点是int型所以会有略微偏移,所以黑线要加粗,就会导致深度到7、8甚至更多图像就没了。。。。
溜了溜了先
有问题欢迎各位大佬来指正,IT萌新路过QAQ。
继续阅读与本文标签相同的文章
上一篇 :
户外LED广告机如何尽显示之力,享智慧之极?
下一篇 :
继8K、AR后投影成医疗工程显示应用新技术
-
个人音视频常用工具介绍
2026-05-18栏目: 教程
-
用自定义监控实现 GPU 异常状况的检查与报警
2026-05-18栏目: 教程
-
与你同行,才能无障碍
2026-05-18栏目: 教程
-
分布式Id - redis方式
2026-05-18栏目: 教程
-
HTML5 容器入门解析:支付宝 Hybrid 方案原理与实战
2026-05-18栏目: 教程
