也许是不小心又翻到了去年暑假的那个分形PPT,让我想起来还有一个没有完成的任务,就是L-system。
当时刚接触java,还是属于很年轻的,但是经过了那么久的积淀,我觉得我可以解决这个问题。
于是,我开始了探求L-system之旅。
首先我们还是来回顾一下Koch雪花,当时我们是直接用递归来解决的问题:
/**
* 画雪花的方法
*
* @param g图形对象
* @param x1左边点的横坐标
* @param y1左边点的纵坐标
* @param x2右边点的横坐标
* @param y2右边点的纵坐标
* @param count画线的次数
*/
public void drawkoch(Graphics g, double x1, double y1, double x2,
double y2, int count) {
if (count <= 1) {
g.drawLine((int) x1, (int) y1, (int) x2, (int) y2);// 画线
} else {
double x3 = (2 * x1 + x2) / 3;// 第一个三等份点的x坐标
double y3 = (2 * y1 + y2) / 3;// 第一个三等份点的y坐标
double x4 = (x1 + 2 * x2) / 3;// 第二个三等份点的x坐标
double y4 = (y1 + 2 * y2) / 3;// 第二个三等份点的y坐标
double k = (x1 - x2) * (y1 - y2);// 线的斜率
double x5 = x1, y5 = y1;// 第一个三等份点的x坐标
if (y3 == y4)// 直线
{
x5 = (x3 + x4) / 2;
y5 = y3 - (x4 - x3) * Math.sqrt(3) / 2;
} else if (k < 0)// 左斜线
{
x5 = x1;
y5 = y4;
} else if (k > 0)// 右斜线
{
x5 = x2;
y5 = y3;
}
if (x3 == x4) // 如果斜线为竖线
{
x5 = x3;
y5 = y3;
}
// 画尖端的左面那条线
drawkoch(g, x3, y3, x5, y5, count - 1);
// 画尖端的右面那条线
drawkoch(g, x5, y5, x4, y4, count - 1);
// 画左边那条直线
drawkoch(g, x1, y1, x3, y3, count - 1);
// 画右边那条直线
drawkoch(g, x4, y4, x2, y2, count - 1);
}
}
但是这一次,我们使用的并不仅仅是递归,而是用字母表和符号串来表达生成的对象的初始形式,称之为公理(axiom)。
现在我们可以定义如下字符规则:
F:在当前方向前进一个单位,并画线
+:逆时针旋转α角度
-:顺时针旋转α角度
我们再定义一个字符转换规则:F -> F-F++F-F
那么下一步将会转成:F-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F
以此类推,我们取α=60度,便得到我们以前画的Koch雪花。
那么如果我们在每画完一条直线时,稍微附带着偏转一个角度,结果会更加的令人惊讶的!
public static final String strF = "F-F++F-F";// 基础字符串
private double theta = Math.PI * 60 / 180;// 偏转固定的角度
String axiom = "F-F++F-F";// "公理"字符串
double d = 50;// 单位长度
double garma = 3;// 微调时的偏转角度
double x1, y1;// 画线的初始点
/**
* 根据字符串axiom,画出图形
*
* @param axiom
* @param g
*/
public void drawLS(String axiom, Graphics g) {
// 把中点坐标定到屏幕正中心
x1 = getWidth() / 2;
y1 = getHeight() / 2;
double x2 = x1, y2 = y1;// 用于存取下一步的位置
double alpha = 0;// 当前所指向的角度
// 循环遍历字符串,根据字符串所给出的提示,F表示前进一个单位,+表示逆时针旋转α,-表示顺时针旋转α
for (int i = 0; i < axiom.length(); i++) {
switch (axiom.charAt(i)) {
case 'F':// 如果是F就走一部
// 计算下一步的坐标
x2 = x1 + d * Math.cos(alpha);
y2 = y1 + d * Math.sin(alpha);
// 根据坐标画线
g.drawLine((int) x1, (int) y1, (int) x2, (int) y2);
// x1,y1变到新的坐标点
x1 = x2;
y1 = y2;
break;
case '-':// -表示顺时针旋转α
alpha -= theta;
// 在两个strF中间,要进行一个小角度的偏转,使得图形偏转角度趋于混乱
if (i < axiom.length() - 2) {
if (axiom.charAt(i + 1) == 'F'
&& axiom.charAt(i + 2) == '+') {
double a = Math.PI * garma / 180;// 根据γ计算出偏转角度a
alpha -= a;// α再逆时针旋转a
}
}
break;
case '+':// +表示逆时针旋转α
alpha += theta;
break;
}
}
}
/**
* 字符串递归的方法
*
* @param str如果是small就是变小
* ,如果是large就是变大
*/
public void dealAxiom(String str) {
if (str.equals("small")) {
if (!axiom.equals("F")) {
axiom = axiom.replace(strF, "F");// 用"F"代替strF
}
}
if (str.equals("large")) {
axiom = axiom.replace("F", strF);// 用strF代替"F"
}
}
学习永远没有尽头,to be continue...