目录
一、专栏简介
二、专栏成果展示
2.1 简单分形树
2.2 一般分形树
2.3 复杂分形树
三、简单分形树的生成原理
3.1 生成步骤
3. 2 涉及到的数学方法
四、简单分形树的代码实现
一、专栏简介
专栏系列文章将由浅入深地介绍分形树的生成原理,揭示蕴含其中的数学方法,并使用Java语言实现绘制这类分形的算法。欢迎对分形、数学及编程感兴趣的伙伴一同探讨研究!!!
二、专栏成果展示
2.1 简单分形树
使用固定不变的参数可以快捷地绘制出如下图所示的分形树。这类分形树的特点是缺乏变化,与自然状态下的树木差别很大,它的意义更多地在于提供了一个得到其他复杂分形树的“初代机”。
2.2 一般分形树
对上面简单分形树的生长参数作随机扰动,以丰富形态变化,模拟出相对自然的状态,可避免显地呆板无趣。绘制效果如下图所示。
2.3 复杂分形树
借助Turtle指令,记录下绘制分形树时Turtle经过每个像素的次数,并以此作为着色的依据。如果再配合着渐变色进行绘制,就可以得到一些非常有趣的图片。绘制效果如下图所示。
三、简单分形树的生成原理
3.1 生成步骤
专栏第一章将具体分析简单分形树,下图演示了此类分形树的生成步骤:按照相同的方式不断地在末梢处长出新的分支(递归),当达到一定次数后,结束生长。
因为生长规律是不变的,所以只需分析任意某段树枝是如何长出分支的,即可套用到其他树枝上。结合下图,容易分析得到单根树枝长出分支的方式步骤:
步骤一:主干向左逆时针旋转α角度,然后按某个系数进行缩放;
步骤二:主干起点平移到自身的终点上,成为左侧分支;
步骤三:生成右侧分支的方式步骤与左侧的类似,二者唯一的区别在于旋转方向相反。
3. 2 涉及到的数学方法
a. 主干与分支之间存在角度关系,用简单的三角函数进行处理即可;
b. 使用向量(Vector)来理解物体的旋转和缩放。如下图所示,向量运算有助于对空间变换问题形成本质上的认识,反之,一开始就着眼于坐标计算则容易使人一头雾水,往往事倍功半。
四、简单分形树的代码实现
了解了关于一般分形树的基本原理后,便可开始定义相关类。
首先,定义一个简单分形树类SimpleTree,并在构造函数中完成初始化操作,其属性包括分支相对于主干的转动角度、画笔和窗口高度等:
// 分支相对于主干的旋转角度及其正/余弦值
// 这三个属性不要求很高的数值精度,数据类型使用float即可
// 这类分形树设定左右分支相对于主干对称分布,因此只需要一个角度属性即可。有需要的话,可以定义左右两个角度值,方法大同小异。
private float angle, cosAngle, sinAngle;
// 用来绘制分形树的画笔。在哪个窗口绘制,就在哪个窗口获取画笔
private Graphics2D g2d;
// 窗口高度,用于坐标转换
private int boardHeight;
public SimpleTree(Graphics g, int boardHeight) {
// 将默认角度值18°转换为弧度制,再进行三角函数计算
angle = (float) (18 * Math.PI / 180);
cosAngle = (float) Math.cos(angle);
sinAngle = (float) Math.sin(angle);
// 初始化画笔,设置画笔颜色和宽度
g2d = (Graphics2D) g;
g.setColor(new Color(255, 120, 0));
g2d.setStroke(new BasicStroke(4.5f));
this.boardHeight = boardHeight;
}
定义绘制分形树的方法:
public void drawTree(float x0, float y0, double vx, double vy) {
// 主干的长度
float branchLen = (float) Math.sqrt(vx * vx + vy * vy);
// 长度小于12,不再生长(递归结束条件,数值可根据需要更改)
if (branchLen < 12) {
return;
}
// P(x1,y1):主干的终点坐标,同时也是它的两个分支的起点坐标
float x1 = (float) (x0 + vx);
float y1 = (float) (y0 + vy);
// 绘制出主干
// Java绘制窗口在高度上以从上往下为正方向,这与我们的观察习惯相反,为方便观察,需要在这个方向上进行一个坐标变换
g2d.drawLine((int) x0, boardHeight - (int) y0, (int) x1, boardHeight - (int) y1);
// 计算左侧分支向量V(leftVx, leftVy)
// 第一步:主干向量向左旋转angle角度
double leftVx = vx * cosAngle - vy * sinAngle;
double leftVy = vx * sinAngle + vy * cosAngle;
// 第二步:旋转之后再进行缩放,得到左侧分支向量。每生长一次,树枝长度减11(数值可根据需要更改)
// 计算缩放系数
float ratio = (branchLen - 11) / branchLen;
leftVx *= ratio;
leftVy *= ratio;
// 递归调用drawTree方法,只不过这次绘制的是左侧分支
drawTree(x1, y1, leftVx, leftVy);
// 计算右侧分支向量V(rightVx, rightVy)
// 注意旋转时以逆时针方向为正,向右旋转为顺时针方向,所以旋转角度应为-angle。cos(-angle)=cos(angle),sin(-angle)=-sin(angle)
double rightVx = vx * cosAngle + vy * sinAngle;
double rightVy = -vx * sinAngle + vy * cosAngle;
rightVx *= ratio;
rightVy *= ratio;
// 递归调用drawTree方法,绘制右侧分支
drawTree(x1, y1, rightVx, rightVy);
}
SimpleTree类定义完成后,需要一个绘图窗口,通过调用SimpleTree对象的drawTree方法,查看绘制效果:
public class Entrance extends JFrame {
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
Entrance ent = new Entrance();
ent.initUI();
}
private void initUI() {
// 窗口的一些必要设置
this.setTitle("FractalTree");
this.getContentPane().setBackground(new Color(245, 251, 255));
this.setSize(1350, 900);
this.setLocationRelativeTo(null);
this.setResizable(false);
this.setVisible(true);
// 画笔
Graphics g = this.getGraphics();
// new一个SimpleTree类对象,传参
SimpleTree tree = new SimpleTree(g, 900);
this.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
// 点击窗口即开始绘制分形树
tree.drawTree(675, 10, 0, 120);
}
});
}
}
绘制效果如下图
如前所述,简单分形树缺乏变化,因此可能需要考虑如何丰富绘制效果,例如,下图通过重复绘制的方法来增加层次感。
参考代码:
this.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
// 用于产生随机颜色
Random rand = new Random();
// 初始方向角105°
float angle = (float) (105 * Math.PI / 180);
for (int i = 0; i < 6; i++) {
// V(vx,vy)
double vx = 120 * Math.cos(angle);
double vy = 120 * Math.sin(angle);
// 需要在Simple类中定义setColor和setStroke方法,较为简单,不再赘述
// 随机分配分形树的颜色,宽度则按照循环次数递减
tree.setColor(new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256)));
tree.setStroke(5.5f - i * 0.8f);
tree.drawTree(675, 10, vx, vy);
// 每画完一棵树,初始方向角减5°,循环画一下棵树
angle -= 5.0 * Math.PI / 180;
}
}
});
丰富视效的方法还有很多,不一一列举了,感兴趣的读者可以发挥自己的脑洞,做出自己想象中的效果。本章内容至此结束,专栏第一篇,非常期待大家的建议与反馈。下一篇将详细介绍一般分形树,干货满满,敬请期待!!!



