所谓L-System就是通过定义字符串的意义,然后给出不同字符串,就能画出不同的图形。对于一个 L-System 来讲,首先要有一个母串(英文是 axiom,意为公理,表示那些不可被推导的东西,因此也就是最原始的东西,“母串”的叫法是同学都这样翻译),比如对于科赫曲线我们就取 axiom = F-F++F-F。如果对这个字符串中 F、+、-赋予特别的意义,比如F表示向前走一个单位长度,+表示向右转 60 度,-表示向左转 60 度,我们就会发现将这个字符串当作指令一样从左至右依次执行,就可以得到下面这个最原始的科赫曲线。
下面我会讲我所做的L-System的方法。我先讲一下思路。第一,通过给的母串axiom、子串production、递归深度得到最终的字符串。第二,通过最终的字符串得到所有的点,通过这些点的坐标和画布大小来决定图形的位置和画线的长度,使之在画布中央,画出图形。
所以,就两个困难。
第一,通过母串子串和递归深度得到最终的字符串。这里会用到可变长字符串StringBuffer ,和其方法append(String str)将指定字符串添加到此序列。
/**
* 分形类:通过母串、子串和递归层数得到分形后的字符串
* @param count 递归层数
* @return 字符串
*/
public String generate(int count){
if(count==0){
return axiom;
}
String sequence=axiom;
for(int i=0;i<count;i++){
StringBuffer buf=new StringBuffer();//可变长度的字符串
for(int j=0;j<sequence.length();j++){
if(sequence.charAt(j)=='F'){//遇到F则用子串代替并加到可变长度字符串
buf.append(production);
}else{//否则把原字符加到可变长度字符串
buf.append(sequence.charAt(j));
}
}
sequence=buf.toString();//可变长度字符串转化为字符串
}
return sequence;
}
第二,通过最终字符串,+或-也就是旋转方法,还有角度来得到所有点的坐标,不过所用的方法是向量来计算点坐标,并且第一步算出所有点来得到最值以确定确定点的初始位置和画线长度。最后算出所有点坐标画线。
/**
* 根据递归后的字符串得到各个点的坐标
* @param sequence:按要求递归后字符串
* @param angle:旋转角度
*/
public void createSpot(String sequence,double angle){
double x=0,y=0,length=10;//初始点和线长度
double x0=1,y0=0,max_x,min_x,max_y,min_y;//向量x0,y0及最值
max_x=min_x=x;
max_y=min_y=y;
//得到所有点的x、y的最大最小值
for(int i=0;i<sequence.length();i++){
if(sequence.charAt(i)=='F' ){
//通过向量计算点的坐标
double x1=x+length*x0;
double y1=y+length*y0;
x=x1;
y=y1;
//得到最值
if(x1<min_x){
min_x=x1;
}
if(x1>max_x){
max_x=x1;
}
if(y1<min_y){
min_y=y1;
}
if(y1>max_y){
max_y=y1;
}
}
//判断+或者-来确定接下来的向量
if(sequence.charAt(i)=='+' ){
double _x0=x0*Math.cos(angle)-y0*Math.sin(angle);
double _y0=x0*Math.sin(angle)+y0*Math.cos(angle);
x0=_x0;
y0=_y0;
}
if(sequence.charAt(i)=='-' ){
double _x0=x0*Math.cos(-angle)-y0*Math.sin(-angle);
double _y0=x0*Math.sin(-angle)+y0*Math.cos(-angle);
x0=_x0;
y0=_y0;
}
}
//根据x、y的最大、最小值确定x、y的初始位置和画线长度
double r=Math.min(center.getWidth()/(max_x-min_x), center.getHeight()/(max_y-min_y));
length*=r;
double mid_x=(max_x+min_x)/2,mid_y=(max_y+min_y)/2;
x=center.getWidth()/2-mid_x*r;
y=center.getHeight()/2-mid_y*r;
x0=1.0;
y0=0;
//得到真实的点坐标
for(int j=0;j<sequence.length();j++){
if(sequence.charAt(j)=='F' ){
double x1=x+length*x0;
double y1=y+length*y0;
Spot l=new Spot(x1,y1);
DrawBoard.list.add(l);
x=x1;
y=y1;
}
//根据+或-得到新的向量
if(sequence.charAt(j)=='+' ){
double _x0=x0*Math.cos(angle)-y0*Math.sin(angle);
double _y0=x0*Math.sin(angle)+y0*Math.cos(angle);
x0=_x0;
y0=_y0;
}
if(sequence.charAt(j)=='-' ){
double _x0=x0*Math.cos(-angle)-y0*Math.sin(-angle);
double _y0=x0*Math.sin(-angle)+y0*Math.cos(-angle);
x0=_x0;
y0=_y0;
}
}
}
/**
* 画线的方法:根据队列的点坐标画线
* @param center:画图区
*/
public void draw(JPanel center){
for(int i=0;i<DrawBoard.list.size()-1;i++){
Spot s1=DrawBoard.list.get(i);
Spot s2=DrawBoard.list.get(i+1);
Graphics g=center.getGraphics();
g.drawLine((int)s1.x, (int)s1.y, (int)s2.x, (int)s2.y);
}
}
效果如图。