不知不觉到了毕业季,回顾10多年前在海外的大学生活,感觉即特别又辛酸。这几天翻了一下大学期间完成的一个非常有意思的模拟植物生长的小软件,进行分享,给即将步入大学的众多IT行业的学子加油。
一. L-系统原理介绍
L-系统是匈牙利生物学家Aristid LinderMayer于1968年提出的。L-系统的本质是一个重写系统,通过对植物对象生长过程的经验式概括和抽象,初始状态与描述规则,进行有限次迭代,生成字符发展序列以表现植物的拓扑结构,并对产生的字符串进行几何解释,就能生成非常复杂的分形图形。
具体例子如下:雪花曲线
v:{F,+, - }
w:F
p:F->F-F++F-F
几何解释是:
F:向前画一条线
+:右转67.5度(++即为右转135度)
-:左转45度
具体信息见下图,当迭代次数n=3时就可以得出很好的雪花形状。
二. 案例结果展示
本案例是在2005年圣诞节期间开发,采用Java开发语言,底层代码完全自己编写,未引用任何第三方包。
先展示一下系统的结果:
案例一:
规则:
如左图所示,
|
结果展示:
一次迭代 | 三次迭代 | 十次迭代 |
|
|
|
案例二
规则:
结果展示:
一次迭代 | 三次迭代 | 六次迭代 |
|
|
|
案例三
规则:
结果展示如下:
一次迭代 | 三次迭代 | 七次迭代 |
案例四
规则:
结果展示如下:
一次迭代 | 三次迭代 | 四次迭代 |
|
|
|
案例五
规则:
结果展示如下:
一次迭代 | 三次迭代 | 五次迭代 |
|
|
|
案例6
规则:
结果展示如下:
一次迭代 | 三次迭代 | 七次迭代 |
|
|
|
三. 系统界面
这里把系统界面再介绍一下,截屏如下:
界面左侧是绘图区域.
右侧在上一章节的规则部分有截图,除去已经截图的,其他部分说明如下:
- Map Value – 选择初始公式以及规则
- Times – 迭代次数
- Color – 选择绘制的图案颜色
四. 原始代码:
一共五个类文件:
- Gui.java – 主界面
- DrawPanel.java – 图案绘制
- LRules.java – 定义公式以及规则
- Point.java – 点对象
- Start.java – 启动类
下面贴出LRules.java, DrawPanel.java, 以及Point.java的原始代码,感兴趣的可以拿来使用,并且自己定义自己喜欢的风格的界面,进一步的扩充里面的规则案例.
LRules.java
import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.Line2D; import java.util.Stack;
publicclass LRules {
public String combineLine(intiteration, String axioms, String rule1, String rule2) { String products=""; String product = axioms; for (intn = 0; n < iteration; n++) { products=""; for (inti = 0; i < product.length(); i++) { charcurrent = product.charAt(i); charcheck1 = rule1.charAt(0); charcheck2 = rule2.charAt(0); if (current == check1) { products = products.concat(rule1.substring(2,rule1.length())); } elseif (current == check2){ products = products.concat(rule2.substring(2,rule2.length())); } else { products = products.concat(String.valueOf(current)); } } product = products; } returnproducts; } public Stack<Point> getStack(String products,floatangle) { Stack<Point> st = new Stack<Point>(); inti = 0; floatang = 90; booleanoutbraket[] = newboolean[20]; intcount = 0; floatangleSign[] = newfloat[20]; for (intloop = 0; loop < products.length(); loop++) { charletter = products.charAt(loop); switch (letter) { case'F': Point p = new Point(); if (i==0) { p.setStartX(400); p.setStartY(650); p.setAngle(90); } p.setAngle(ang); st.push(p); break; case'f': Point p1 = new Point(); if (i==0) { p1.setStartX(400); p1.setStartY(300); p1.setAngle(90); } p1.setAngle(ang); st.push(p1); break; case'+': ang = ang - angle; break; case'-': ang = ang + angle; break; case'[': angleSign[count] = ang; count++; break; case']': outbraket[count] = true; count--; ang = angleSign[count]; break; default: break; } i++; } returnst; }
publicvoid drawPic( Graphics g,Color blockColor, floatlineLength, floatangel,String sproducts,Stack<Point> points){ floatxStartPos, yStartPos, xEndPos, yEndPos; intcount = 0; Graphics2D g2d = (Graphics2D) g; Point node[] = new Point[20]; intcount1 = 0; for (intloop = 0; loop < sproducts.length(); loop++) { charletter = sproducts.charAt(loop); switch (letter) { case'F': Point p = (Point) points.get(count); xStartPos = p.getStartX(); yStartPos = p.getStartY();
xEndPos = (float)(p.getStartX()+( lineLength*Math.cos(p.getAngle()*Math.PI/180))); yEndPos = (float) (p.getStartY()-( lineLength*Math.sin(p.getAngle()*Math.PI/180))); g2d.setColor(blockColor); Line2D.Float path= new Line2D.Float(xStartPos,yStartPos,xEndPos,yEndPos); g2d.draw(path); if(count < points.size()-1) { ((Point) points.get(count+1)).setStartX(xEndPos); ((Point) points.get(count+1)).setStartY(yEndPos); } count++; break; case'f': Point p1 = (Point) points.get(count);
xStartPos = p1.getStartX(); yStartPos = p1.getStartY();
xEndPos = (float)(p1.getStartX()+( lineLength*Math.cos(p1.getAngle()*Math.PI/180))); yEndPos = (float) (p1.getStartY()-( lineLength*Math.sin(p1.getAngle()*Math.PI/180))); g2d.setColor(blockColor); Line2D.Float path1= new Line2D.Float(xStartPos,yStartPos,xEndPos,yEndPos); g2d.draw(path1); if(count < points.size()-1) { ((Point) points.get(count+1)).setStartX(xEndPos); ((Point) points.get(count+1)).setStartY(yEndPos); } count++; break; case'+': break; case'-': break; case'[': node[count1] = (Point) points.get(count); count1++; break; case']': if(count<points.size()) { Point p4 = (Point) points.get(count); p4.setStartX(node[count1-1].getStartX()); p4.setStartY(node[count1-1].getStartY()); } count1--; break; default : break; } } } } |
DrawPanel.java
import java.awt.Color; import java.awt.Graphics; import java.awt.Image; import java.awt.Polygon; import java.awt.geom.Point2D; import java.util.Stack; import javax.swing.JPanel;
publicclass DrawPanel extends JPanel { publicfinalintWIDTH = 800; publicfinalintHEIGHT = 800; publicfinalintWIDTH_ADJUST = 60; publicfinalintHEIGHT_ADJUST = 40; publicfinaldoubleSTARTDRAWLEAF = 9999; publicfinaldoubleENDDRAWLEAF = 99999; publicfinalintSTART_LEFT = 0; publicfinalintSTART_MIDDLE = 1; public Point2D srcPoint = new Point2D.Double(0,0);
private Point2D srcPoint1 = new Point2D.Double(0,0); public Point2D destPoint = new Point2D.Double(0,0); publicintwidth; private Polygon leafPoly; privatebooleandrawLeaf = false; privatebooleanleafCoord = false; private Thread mainThread = null; private Color blockColor; privateinttimes; privateintlength; privatefloatangle; privateintspeed; private String axiom; private String rule1; private String rule2; private Image im; public Graphics offscreen; floatlb,rb,ub,db; // record all rectangular blocks publiclongmySeed = System.currentTimeMillis();
publicvoid startDrawing(Color blockColor, inttimes, intlength,floatangle, intspeed, String axioms,String rule1,String rule2){ this.blockColor = blockColor; this.times = times; this.length = length; this.angle = angle; this.speed = speed; this.axiom = axioms; this.rule1 = rule1; this.rule2 = rule2; width = WIDTH; if (mainThread == null){ mainThread = new Thread(new RunnableObject()); mainThread.start(); } } publicvoid stop(){ offscreen = null; repaint(); pause(); } publicvoid pause(){ if(mainThread != null){ mainThread. mainThread = null; } } privateclass RunnableObject implements Runnable{ publicvoid run(){ inti = 0; intj = 0; intk = 0; intl = 0; intsrcX, srcY, destX, destY, srcX1, srcY1; if (mainThread != null){ /* if (leafCoord){ srcX = (int)srcPoint.getX() + width/2;// - WIDTH_ADJUST; srcY = HEIGHT - (int)srcPoint.getY();// - HEIGHT_ADJUST; leafPoly.addPoint(srcX, srcY); } else{ if (drawLeaf){ destX = (int)destPoint.getX() + width/2;// - WIDTH_ADJUST; destY = HEIGHT - (int)srcPoint.getY();// - HEIGHT_ADJUST; leafPoly.addPoint(destX, destY); } */ repaint(); try { mainThread.sleep(speed); } catch (InterruptedException e) { e.printStackTrace(); } //} }
}
}
publicvoid paint(Graphics g) { if (offscreen == null) { im = createImage(WIDTH, HEIGHT); offscreen = im.getGraphics(); offscreen.setColor(Color.white); offscreen.fillRect(0,0,WIDTH,HEIGHT); } else { LRules lr = new LRules(); String axioms = lr.combineLine(times,axiom,rule1,rule2); Stack<Point> st = lr.getStack(axioms,angle); lr.drawPic(offscreen,blockColor, length, angle, axioms,st); } g.drawImage(im,0,0,this); }
publicvoid update(Graphics g) { paint(g); }
publicvoid setSpeed(intspeed) { this.speed = speed; } } |
Point.java
publicclass Point { privatefloatstartX; privatefloatstartY; privatefloatangle; publicfloat getStartX() { returnstartX; } publicfloat getStartY() { returnstartY; } publicfloat getAngle() { returnangle; } publicvoid setStartX(floatstartX) { this.startX = startX; } publicvoid setStartY(floatstartY) { this.startY = startY; } publicvoid setAngle(floatangle) { this.angle = angle; } } |
因为时间较早,编码中有一些东西目前已经不建议使用,例如线程用stop方法强制关闭。同时因为大学期间编码能力有限,加上当时是自己在圣诞节的一周假期额外抽时间来开发的,所以代码注释都未加,敬请见谅。不过多看几遍应该也能够明白其中的逻辑。感兴趣的可以关注微信公众号: 智能化IT系统, 进一步的沟通交流.
最后祝愿IT学子大学生活一帆风顺, 未来前程似锦.