分形树详细教程
一、引言
分形是自然界的几何学。---- -曼德勃罗(分形理论创始人)
通过本次学习,学到了如何制作分形树 并且被他深深吸引,最终,也能做出一些如下:
落花的凤凰树,落花的紫荆花树,樱花花瓣飘落的树与落花的蓝花楹树
任务
2.2 参考分形的概念,绘制分形树或者其他分形图形。要求可以对图形进行保存和打开等操作。
http://www.matrix67.com/blog/archives/6231
https://www.zhihu.com/question/271643290/answer/525019532
二、实验环境
Visual stdio 2017
Windows窗体应用
三、实验过程
思路
1.分形概念的提出与分形理论的建立
2.创建窗体
3.生成分形树
4.实现文件保存与打开 及Bitmap 类
5.分形树与其衍生出的其他分形图形 效果展示
1.分形概念的提出与分形理论的建立
分形的英文为 fractal,是由美籍法国数学家曼德勃罗(Benoit Mandelbrot)创造出来的,此词源于拉丁文形容词 fractus,对应的拉丁文动词是 frangere(破碎、产生无规则碎片),此外,此词与英文的 fraction(碎片、分数)及 fragment(碎片)具有相同的词根。在20世纪70年代中期以前,曼德勃罗一直使用英文 fractional一词来表示他的分形思想。因此,取拉丁词之头,撷英文之尾所合成的 fractal,本意是不规则的、破碎的、分数的。曼德勃罗是想用此词来描述自然界中传统欧氏几何学所不能描述的一大类复杂无规则的几何对象。例如,蜿蜒曲折的海岸线、起伏不定的山脉、粗糙不堪的断面、变幻无常的浮云、九曲回肠的河流、纵横交错的血脉、令人眼花缭乱的满天繁星等。它们的共同特点是极不规则或极不光滑。直观而粗略地说,这些对象都是分形。
1975年,曼德勃罗出版了他的法文专著《分形对象∶形、机遇与维数》(Les objects fractals∶ forme,hasardetdimension),标志着分形理论的正式诞生,1977年他出版了该书的英译本。1982年曼德勃罗的另一部历史性著作《大自然的分形几何学》与读者见面。该书虽然是前书的增补本,但在曼德勃罗看来却是分形理论的"宣言书",而在分形迷的眼中,它无疑是一部"圣经"。该书旁征博引、图文并茂,从分形的角度考察了自然界中的诸多现象,引起了学术界的广泛注意,曼德勃罗因此一举成名。 此后,一直持续的分形热引起了全世界众多科学家和学者的注意,他们在各自领域中的研究工作,使分形理论遍地开花。
2.创建窗体
创建一个画布,一些控件与按钮,下拉菜单,滚动条等等
最终结果结果如图所示:
为了让界面更加好看, 我采用一张色卡,用PS取色, 提取其中的墨绿色作为背景, 浅橙色作为树干
墨绿色作为背景
this.picBoxCanvas.BackColor = Color.FromArgb(64, 83, 81);//墨绿色背景色
浅橙色作为树干
treeTrunkPen = new Pen(Color.FromArgb(181, 143, 107));//新建树干画笔
3.生成分形树
画出画面DrawScene()函数:
由于画线函数graphics.DrawLine()只能接受float类型
所以 y0 ,height, width ,xloc0 等变量皆用float类型
初始化树 使得刚生成的树位于画面最中央
float xloc0 = width / 2.0f;
float yloc0 = (y0 + height) - YOffset;
初始化树位置展示:
画出画面完整代码:
private void DrawScene()
{
float y0 = 0.0f;
float height = this.picBoxCanvas.Height;//是画布的高
float width = this.picBoxCanvas.Width;是画布的宽
//使得初始化的树处于最中间
float xloc0 = width / 2.0f;
float yloc0 = (y0 + height) - YOffset;
float xloc1 = xloc0;
float yloc1 = yloc0 - Length;
//画树枝
DrawBranch(settings.Depth, xloc0, yloc0, settings.Length, initBranchAngle, scaledLength, settings.Angle);
}
画树枝
接下来 就要实现画树枝功能:
各个变量定义:
/// <summary>
/// 画树枝
/// </summary>
/// <param name="depth">递归深度</param>
/// <param name="X">X 表示树枝的起点</param>
/// <param name="Y">Y 表示树枝的起点</param>
/// <param name="length">表示树枝的长度</param>
/// <param name="theta">表示树枝的角度</param>
/// <param name="scaledLength">按比例计算连续分支的数量</param>
/// <param name="deltaTheta">连续分支的角度变化</param>
树枝的顶点
因此变幻时
// x1, y1 计算分支接结尾
// sin等于对边比斜边,cos等于邻边比斜边(长度等于斜边)
x1 = X + length * (float)Math.Cos(theta * RadiansPerDegree); // 增加x(让它从左到右变幻)
y1 = Y - length * (float)Math.Sin(theta * RadiansPerDegree); // 减少y(让它从下到上变幻)
//比如y的-减变成+ 弄反了就会倒着长
树干的宽度/粗细
定义树干的宽度
treeTrunkPen.Width = treeTrunkWidths[depth];
此处的数组用到了上面自定义的
递增的数量使得最终的树干会有不同粗细 更好地模拟树的生长
//树干粗
private int[] treeTrunkWidths = { 0, 1, 2, 3, 4, 6, 9, 11, 14, 17 };
由于三叉树时会非常庞大杂乱 因此没有设置很长数组的树宽
画出一条线:在X, Y, x1, y1 间画一条线 这个函数只能接受float类型
graphics.DrawLine(treeTrunkPen, X, Y, x1, y1);//在X, Y, x1, y1 间画一条线
递归实现树的生长
自然界中的树也具有分形结构。一根主干生长出两个侧干,每个侧干又生长出两个侧干,以此类推,便生长出疏密有致的结构。这样的生长结构,用分形的递归算法也能模拟出来,但不会像自然界中的树那么自然。 (因此在本次实验中,我尝试加入随机值 使得树更加自然生态)
- 递归分形树一(1)如上图所示是分叉树的生成元,利用递归算法生成分叉树的过程,实际上就是将这个生成元在每一个层次上不断重画的过程,具体步骤如下∶
① 设A点坐标为(x,y),B点坐标为(xo,y0),C点坐标为(x1,y1),D点坐标为(x2,y2),L为树干的长度,α为枝干与主干的夹角。
② 绘制主干 AB,即(x,y)-(xo,yo)直线。
③ 计算C点坐标,L=scaledLength×L,x=x+L×cosα,y=y0-L×sina。
④ 计算D点坐标,L=scaledLength×L,x1=xo+L×cos(-α),y1=y0-L×sin(-α)。
⑤ 将步骤②中x→x,y0→y,x1→x0,y1→y0,再绘制(x,y)-(x0,y0)直线,即画分枝BC。
⑥ 将步骤②中x→x,y0→y,x2→x0,y2→y0,再绘制(x,y)-(xo,y0)直线,即画分枝BD。
⑦重复执行步骤③~步骤⑥,直至完成递归次数。
注: 初始化时,我将scaledLength调整为四分之三
private const float scaledLength = 0.75f;
再者,在这个实例里 通过让depth–从而在画分形树的过程中也不断使树干变细,形成树的效果
同时,也实现了三叉树的生成,以下是二者的对比:
1.生成二叉树过程:
递归调用----->绘制右边 +连续分支的角度变化
递归调用----->绘制左边 +连续分支的角度变化
2.生成三叉树过程:
递归调用----->绘制右边 +连续分支的角度变化
递归调用----->绘制中边 (不用变幻角度)
递归调用----->绘制左边 +连续分支的角度变化
// 如果不是顶部的分支,画出连接的分支
if (depth > 1)
{
//三叉树只是比二叉树多了一个中间的分支
// 递归调用-绘制右边 +deltaTheta (二叉+三叉)
DrawBranch(depth - 1, x1, y1, (length * scaledLength) + randomnessRightHandSide, (theta + deltaTheta) + randomnessRightHandSide, scaledLength, deltaTheta);
if (settings.RandomThirdBranch)//随机三叉树
{
int drawMiddleBranch = randomMiddleBranch.Next(0, 2);
if (drawMiddleBranch == 0)
{
// 绘制中间
DrawBranch(depth - 1, x1, y1, (length * scaledLength) + randomnessLeftHandSide, theta + randomnessLeftHandSide, scaledLength, deltaTheta);// randomnessRightHandSide随机值
}
}
else if (settings.ThreeBranches)//三叉树
{
// 绘制中间
DrawBranch(depth - 1, x1, y1, (length * scaledLength) + randomnessLeftHandSide, theta + randomnessLeftHandSide, scaledLength, deltaTheta);
}
// 递归调用-绘制左边 -deltaTheta (二叉+三叉)
DrawBranch(depth - 1, x1, y1, (length * scaledLength) + randomnessLeftHandSide, (theta - deltaTheta) + randomnessLeftHandSide, scaledLength, deltaTheta);
}
// 如果是顶点叶子
else if (depth == 1)
{
if (settings.EndLeaves)//可以画叶子或者画
{
// 到目前为止,结束叶子只是一个圆(bitmap也可)
graphics.DrawArc(endLeavesPen, x1 - 2, y1 - 2, 6, 6, 0, 360);
graphics.DrawArc(endLeavesPen, x1 - 2, y1 - 2, 4, 4, 0, 360);
}
}
}
画叶子或者花
点击以下窗体应用的面板
else if (depth == 1)//如果到了叶子
{
if (settings.EndLeaves)//可以画叶子或者画
{
// 到目前为止,结束叶子只是一个圆(bitmap也可)
graphics.DrawArc(endLeavesPen, x1 - 2, y1 - 2, 6, 6, 0, 360);//圈
graphics.DrawArc(endLeavesPen, x1 - 2, y1 - 2, 4, 4, 0, 360);
}
}
展示:
我们自然会想到,怎样增加更多树叶或花瓣(甚至是树上的积雪呢)
所以可以对这个代码进行改进
graphics.DrawArc(endLeavesPen, x1 - 2, y1 - 2, 6, 10, 0, 180);
graphics.DrawArc(endLeavesPen, x1 - 4, y1 - 4, 3, 4, 0, 180);
graphics.DrawArc(endLeavesPen, x1 - 3, y1 - 3, 6, 10, 0, 180);
graphics.DrawArc(endLeavesPen, x1 - 2, y1 - 2, 3, 4, 0, 180);
graphics.DrawArc(endLeavesPen, x1 - 6, y1 - 7, 6, 10, 0, 180);
graphics.DrawArc(endLeavesPen, x1 - 2, y1 - 3, 3, 4, 0, 180);
实现随机
if (settings.RandomThirdBranch)//随机三叉树
{
int drawMiddleBranch = randomMiddleBranch.Next(0, 2);//这里完成随机分枝数
if (drawMiddleBranch == 0)
{
// 绘制中间
DrawBranch(depth - 1, x1, y1, (length * scaledLength) + randomnessLeftHandSide, theta + randomnessLeftHandSide, scaledLength, deltaTheta);// randomnessRightHandSide 实现随机值
}
}
函数代码:
private void DrawBranch(int depth, float X, float Y, float length, float theta, float scaledLength, float deltaTheta)
{
float x1;
float y1;
// x1, y1 计算分支接结尾
// sin等于对边比斜边,cos等于邻边比斜边(长度等于斜边)
x1 = X + length * (float)Math.Cos(theta * RadiansPerDegree); // 增加x(让它从左到右变幻)
y1 = Y - length * (float)Math.Sin(theta * RadiansPerDegree); // 减少y(让它从下到上变幻)
//比如y的-减变成+ 弄反了就会倒着长
treeTrunkPen.Width = treeTrunkWidths[depth];
graphics.DrawLine(treeTrunkPen, X, Y, x1, y1);//在X, Y, x1, y1 间画一条线
// 如果不是顶部的分支,画出连接的分支
if (depth > 1)
{
//三叉树只是比二叉树多了一个中间的分支
// 递归调用-绘制右边 +deltaTheta (二叉+三叉)
DrawBranch(depth - 1, x1, y1, (length * scaledLength) + randomnessRightHandSide, (theta + deltaTheta) + randomnessRightHandSide, scaledLength, deltaTheta);// randomnessRightHandSide随机值
if (settings.RandomThirdBranch)//随机三叉树
{
int drawMiddleBranch = randomMiddleBranch.Next(0, 2);
if (drawMiddleBranch == 0)
{
// 绘制中间
DrawBranch(depth - 1, x1, y1, (length * scaledLength) + randomnessLeftHandSide, theta + randomnessLeftHandSide, scaledLength, deltaTheta);
}
}
else if (settings.ThreeBranches)//三叉树
{
// 绘制中间
DrawBranch(depth - 1, x1, y1, (length * scaledLength) + randomnessLeftHandSide, theta + randomnessLeftHandSide, scaledLength, deltaTheta);
}
// 递归调用-绘制左边 -deltaTheta (二叉+三叉)
DrawBranch(depth - 1, x1, y1, (length * scaledLength) + randomnessLeftHandSide, (theta - deltaTheta) + randomnessLeftHandSide, scaledLength, deltaTheta);
}
// 如果是顶点叶子
else if (depth == 1)
{
if (settings.EndLeaves)//可以画叶子或者画
{
// 到目前为止,结束叶子只是一个圆(bitmap也可)
graphics.DrawArc(endLeavesPen, x1 - 2, y1 - 2, 6, 6, 0, 360);
graphics.DrawArc(endLeavesPen, x1 - 2, y1 - 2, 4, 4, 0, 360);
}
}
}
4.实现文件保存与打开 及Bitmap 类
Bitmap 类
Bitmap 类是 Image 类的派生类,通过它可以载入并显示各种位图图像。图像由一系列
的像素构成,知道每个像素的信息,就知道了整个图像。位图图像就通过存储所有像素的
信息来描述图像的。GDI+支持多种图像文件格式,包括 BMP、GIF、JPEG、PNG、TIFF、
EXIF 等。
bitmap里面要有图片
bitmap = new Bitmap(picBoxCanvas.Width, picBoxCanvas.Height)
Path.GetExtension返回指定路径中的扩展名
如果后缀名是.bmp .png .jpeg .jpg .gif 都可以保存
文件保存与打开:
namespace Fractal
{
class FileManager
{
public void SaveFileAsBitmap(System.Windows.Forms.PictureBox picBoxCanvas, String filename)
{
using (var bitmap = new Bitmap(picBoxCanvas.Width, picBoxCanvas.Height))
{
picBoxCanvas.DrawToBitmap(bitmap, picBoxCanvas.ClientRectangle);
ImageFormat imageFormat = null;
var extension = Path.GetExtension(filename);
switch (extension)
{
case ".bmp":
imageFormat = ImageFormat.Bmp;
break;
case ".png":
imageFormat = ImageFormat.Png;
break;
case ".jpeg":
case ".jpg":
imageFormat = ImageFormat.Jpeg;
break;
case ".gif":
imageFormat = ImageFormat.Gif;
break;
default:
throw new NotSupportedException(String.Format("File extension {0} is not supported", extension));
}
bitmap.Save(filename, imageFormat);
}
}
}
}
保存后:
5分形树与其衍生出的其他分形图形 效果展示
利用随机变量 可以生成一些神奇又优美的树
奇怪的树
加入落花效果的樱花树:
//以下是落花效果
//graphics.DrawArc(endLeavesPen, x1 + 20, y1 + 21, 6, 10, 0, 180);
//graphics.DrawArc(endLeavesPen, x1 + 24, y1 + 23, 6, 10, 0, 180);
//graphics.DrawArc(endLeavesPen, x1 - 27, y1 + 25, 6, 10, 0, 180);
//graphics.DrawArc(endLeavesPen, x1 - 15, y1 - 16, 6, 10, 0, 180);
//graphics.DrawArc(endLeavesPen, x1 + 20, y1 + 21, 6, 10, 0, 180);
//graphics.DrawArc(endLeavesPen, x1 - 37, y1 + 35, 6, 10, 0, 180);
四、总结
分形树采用递归的方法 从简单的重复迈向复杂 而用随机树创建出来的树和自然界中的树如此相似 让人不禁设想,是不是上帝用了分形算法创造出这些树和世间很多东西呢