L-system绘制分形树目录
L-system
Lindenmayer 系统简称L系统,是1968年由匈牙利生物学家林登麦伊尔(Lindenmayer)提出的有关生长发展中的细胞交互作用的数学模型,尤其被广泛应用于植物生长过程的研究。
原理
L-system或被称为Lindenmayer system是一个相似重写系统,是一系列不同形式的正规语法规则,多被用于植物生长过程建模,但是也被用于模拟各种生物体的形态。L-system也能用于生成自相似的分形,例如迭代函数系统。带有参数的L-system包括V:变量符号集合,S:常量符号集合,W:初始状态串,P:产生式规则。
应用于分形树
对于本程序实现的分形树,所用到的变量是“F”,“+”,“-”,“[”,“]”。
其中“F”代表按当前角度向前画线,“+”表示顺时针旋转一个角度θ,“-”表示逆时针旋转一个角度θ,“[”表示当前位置的坐标以及角度入栈保存起来,为下一次回到当前结点绘制树的分支提供起始条件,“]”则表示出栈,当前栈顶元素被取出,用来绘制当前直线。
HTML与JS代码实现
在本次程序设计中,主要是利用canvas画布来绘制分形树。
变量的符号的集合为:“F”,“+”,“-”,“[”,“]”。
其中“F”代表按当前角度向前画线,“+”表示顺时针旋转一个角度θ,“-”表示逆时针旋转一个角度θ,“[”表示当前位置的坐标以及角度入栈保存起来,为下一次回到当前结点绘制树的分支提供起始条件,“]”则表示出栈,当前栈顶元素被取出,用来绘制当前直线。
这里的栈顶元素可以理解成一个结构体元素,这个元素包含当前的分支的坐标以及分支的方向。
已知公理利用规则得出下一个公理
//确定下一个公理
function generate() {
var nextAxiom = "";
r *= γ;
//遍历每一个元公理,进行变换
for (var i = 0; i < Axiom.length; i++) {
var current = Axiom.charAt(i);
var found = false;
for (var j = 0; j < rules.length; j++) {
//每个公理只能与规则匹配一次
if (current == rules[j].a) {
found = true;
nextAxiom += rules[j].b;
break;
}
}
if (!found)
nextAxiom += current;
}
//重置下一个起始点与起始角度
Axiom = nextAxiom;
nowx = window.innerWidth / 3, nowy = -50;
nowangel = Math.PI / 2;
}
分形树的绘制
function drawTree() {
//遍历公理
for (var i = 0; i < Axiom.length; i++) {
current = Axiom.charAt(i);
if (current == "F") {
forward();
}
//right
else if (current == "+") {
nowangel -= angel;
}
//left
else if (current == "-") {
nowangel += angel;
} else if (current == "[") {
var s = new Array;
s["x"] = nowx;
s["y"] = nowy;
s["a"] = nowangel;
lstack.push(s);
} else if (current == "]") {
var s = lstack.pop();
nowx = s.element["x"];
nowy = s.element["y"];
nowangel = s.element["a"];
}
}
}
//向前画线
function forward() {
//镜像翻转至canvas坐标系
context.beginPath();
context.moveTo(nowx, window.innerHeight - 100 - nowy);
//计算下一步的位置
var x = nowx + r * Math.cos(nowangel);
var y = nowy + r * Math.sin(nowangel);
context.lineTo(x, window.innerHeight - 100 - y);
context.stroke();
nowx = x;
nowy = y;
}
GUI界面的实现
var controls = new function() {
this.grow = function() {
context.strokeStyle = this.color
generate();
drawTree();
}
this.clear = function() {
reset();
}
this.color="#FF0000";
}
var gui = new dat.GUI();
gui.domElement.style.position = 'absolute';
gui.domElement.style.width = '400px';
gui.domElement.style.height= '400px';
gui.add(controls, 'grow');
gui.add(controls, 'clear');
gui.addColor(controls, 'color');
二阶分形树的讲解
我们知道一开始的公理为‘F’,它表示的含义就是向前绘制一定的距离。而利用规则产生下一个公理,就是说要把前一个公理中的‘F’替换成‘F F + [ + F – F – F ] - [ - F + F + F ]’,所以我们二阶分形树的公理就变成了‘F F + [ + F – F – F ] - [ - F + F + F ]’,同理,如果我们要绘制三阶分形树,我们就要把上一次的公理中的’F’进行替换。可以写出三阶分形树的公理为‘FF+[+F-F-F]-[-F+F+F]FF+[+F-F-F]-[-F+F+F]+[+FF+[+F-F-F]-[-F+F+F]-FF+[+F-F-F]-[-F+F+F]-FF+[+F-F-F]-[-F+F+F]]-[-FF+[+F-F-F]-[-F+F+F]+FF+[+F-F-F]-[-F+F+F]+FF+[+F-F-F]-[-F+F+F]]’,可以看出树的阶数越高,公理就越复杂,在这里我们暂且讨论二阶分形树。
当前公理:‘F F + [ + F – F – F ] - [ - F + F + F ]’。
利用“其中“F”代表按当前角度向前画线,“+”表示顺时针旋转一个角度θ,“-”表示逆时针旋转一个角度θ,“[”表示当前位置的坐标以及角度入栈保存起来,为下一次回到当前结点绘制树的分支提供起始条件,“]”则表示出栈,当前栈顶元素被取出,用来绘制当前直线。”
这里我们旋转的角度是20°,向前移动的距离是280个距离。这里需要注意的是,为了让树看起来更逼真,当我们的阶数升高时,树枝应该变短
我们绘制的二阶分形树应当如下,二阶分形树可以自己用笔绘制一下,看看是不是这样,但是三阶和三阶以上的分形树就不建议手画了,因为公理非常的长,非常麻烦。
利用GUI界面绘制多阶的分形树
通过制作一个简易的GUI界面,实验多阶分形树的绘制,在此程序中,只需鼠标点击“grow”按键,树将会变成下一阶,而且还可以改变树的颜色等等
三阶
四阶
五阶
六阶
总结
L-system广泛应用与分形的程序设计中,本程序利用了L-system实现分形树,之后将利用L-system绘制koch曲线,比较常规的递归绘制与L-system实现的绘制,显然当绘制的阶数越来越高时,递归的深度也会越来越高,会出现栈溢出的现象,而L-system却不会出现这个问题,L-system只需要存储字符串,也就是我们在本程序中提到的公理。计算机存储字符串还是非常给力的,不可能几个字符串就把内存占满了。