這是要介紹的最後一個遞迴繪圖,看起來很複雜,但其實只是程式碼長了一點,實際上並不會比雪花來得複雜,只要弄清楚繪圖順序就可以了。
在這邊只解釋單一樹木的基本繪圖方式,主要分為主幹與支幹的繪製,如下圖所示,其中標號表示頂點的前進順序:
接下來只要在主幹與支幹上不斷的繪製一定比例的相同圖形,就可以完成一顆樹,將這些樹排列起來就成為樹林,詳細的原始碼請看以下的程式,其中註解部份先不 用理會:
- Forest.java
package cc.openhome;
import java.awt.*;
import javax.swing.JApplet;
import static java.lang.Math.*;
public class Forest extends JApplet {
private static final double ANGLE = 86.0, K1 = 1.5, K2 = 1.0, LENG = 2.0;
private static final double K;
static {
K = 1.0 / (K1 + 2 * K2 + 2 * (K1 + K2) * cos(toRadians(ANGLE)));
}
private Turtle t = new Turtle();
public void init() {
setSize(420, 400);
setBackground(Color.black);
}
public void woods(double leng) {
if (leng > LENG) {
t.move(leng);
t.warp(-leng);
// fillForest(leng); // 實心倒影
woods(K * K1 * leng);
t.turn(ANGLE);
woods(K * K1 * leng);
t.turn(-2 * ANGLE);
woods(K * K1 * leng);
t.turn(ANGLE);
woods(K * leng);
t.turn(ANGLE);
woods(K * K2 * leng);
t.turn(-2 * ANGLE);
woods(K * K2 * leng);
t.turn(ANGLE);
woods(K * K2 * leng);
} else {
t.warp(leng);
}
}
public void paint(Graphics g) {
g.setColor(Color.yellow);
t.setGraphics(g);
t.window(0, 0, getWidth(), getHeight());
t.view(0, 0, getWidth(), getHeight());
t.setPoint(10, getHeight() / 2);
t.setAngle(0);
woods(400);
}
// 實心倒影
private void fillForest(double leng) {
Point[] p = new Point[3];
// 畫主幹
t.warp(K * K1 * leng);
p[0] = new Point((int) t.getCurrentX(), (int) t.getCurrentY());
t.turn(ANGLE);
t.warp(K * K1 * leng);
p[1] = new Point((int) t.getCurrentX(), (int) t.getCurrentY());
t.turn(-2 * ANGLE);
t.warp(K * K1 * leng);
p[2] = new Point((int) t.getCurrentX(), (int) t.getCurrentY());
t.polygon(p);
// 畫右支幹
t.turn(ANGLE);
t.warp(K * leng);
p[0] = new Point((int) t.getCurrentX(), (int) t.getCurrentY());
t.turn(ANGLE);
t.warp(K * K2 * leng);
p[1] = new Point((int) t.getCurrentX(), (int) t.getCurrentY());
t.turn(-2 * ANGLE);
t.warp(K * K2 * leng);
p[2] = new Point((int) t.getCurrentX(), (int) t.getCurrentY());
t.polygon(p);
// 支幹退回
t.warp(-K * K2 * leng);
t.turn(2 * ANGLE);
t.warp(-K * K2 * leng);
t.turn(-ANGLE);
t.warp(-K * leng);
t.turn(-ANGLE);
// 主幹退回
t.warp(-K * K1 * leng);
t.turn(2 * ANGLE);
t.warp(-K * K1 * leng);
t.turn(-ANGLE);
t.warp(-K * K1 * leng);
}
}
如果將上例程式的註解符號去除,可以繪製樹林的倒影,繪製方法是大同小異,這邊是使用實心圖形來表示倒影,為了繪製實心圖形,在海龜繪圖法中加入繪製實心多邊形的方法polygon,如下所示:
// Java
public void polygon(Point tri[]) {
int[] xPoints = new int[tri.length];
int[] yPoints = new int[tri.length];
for(int i = 0; i < tri.length; i++) {
xPoints[i] = tri[i].x;
yPoints[i] = tri[i].y;
}
g.fillPolygon(xPoints, yPoints, tri.length);
}
// JavaScript
this.polygon = function(tri) {
this.context.beginPath();
this.context.moveTo(tri[0].x, tri[0].y);
for(var i = 1; i < tri.length; i++) {
this.context.lineTo(tri[i].x, tri[i].y);
}
this.context.closePath();
this.context.fill();
};
public void polygon(Point tri[]) {
int[] xPoints = new int[tri.length];
int[] yPoints = new int[tri.length];
for(int i = 0; i < tri.length; i++) {
xPoints[i] = tri[i].x;
yPoints[i] = tri[i].y;
}
g.fillPolygon(xPoints, yPoints, tri.length);
}
// JavaScript
this.polygon = function(tri) {
this.context.beginPath();
this.context.moveTo(tri[0].x, tri[0].y);
for(var i = 1; i < tri.length; i++) {
this.context.lineTo(tri[i].x, tri[i].y);
}
this.context.closePath();
this.context.fill();
};
以下是使用HTML5 Canvas的方式(如果瀏覽器支援HTML5 Canvas,例如最新版的Firexfox、Chrome、IE9等,可以直接將下面的內容存為HTML或按下檔名連結,直接載入瀏覽器執行觀看結果:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=Big5" http-equiv="content-type">
<script type="text/javascript" src="js/turtle.js"></script>
<script type="text/javascript">
window.onload = function() {
function toRadians(angle) {
return angle * Math.PI / 180;
}
var ANGLE = 86.0, K1 = 1.5, K2 = 1.0, LENG = 2.0;
var K = 1.0 / (K1 + 2 * K2 +
2 * (K1 + K2) * Math.cos(toRadians(ANGLE)));
function woods(leng) {
if (leng > LENG) {
turtle.move(leng);
turtle.warp(-leng);
// 實心倒影
fillForest(leng);
woods(K * K1 * leng);
turtle.turn(ANGLE);
woods(K * K1 * leng);
turtle.turn(-2 * ANGLE);
woods(K * K1 * leng);
turtle.turn(ANGLE);
woods(K * leng);
turtle.turn(ANGLE);
woods(K * K2 * leng);
turtle.turn(-2 * ANGLE);
woods(K * K2 * leng);
turtle.turn(ANGLE);
woods(K * K2 * leng);
} else {
turtle.warp(leng);
}
}
function Point(x, y) {
this.x = x;
this.y = y;
}
// 實心倒影
function fillForest(leng) {
var p = [];
// 畫主幹
turtle.warp(K * K1 * leng);
p[0] = new Point(turtle.currentX, turtle.currentY);
turtle.turn(ANGLE);
turtle.warp(K * K1 * leng);
p[1] = new Point(turtle.currentX, turtle.currentY);
turtle.turn(-2 * ANGLE);
turtle.warp(K * K1 * leng);
p[2] = new Point(turtle.currentX, turtle.currentY);
turtle.polygon(p);
// 畫右支幹
turtle.turn(ANGLE);
turtle.warp(K * leng);
p[0] = new Point(turtle.currentX, turtle.currentY);
turtle.turn(ANGLE);
turtle.warp(K * K2 * leng);
p[1] = new Point(turtle.currentX, turtle.currentY);
turtle.turn(-2 * ANGLE);
turtle.warp(K * K2 * leng);
p[2] = new Point(turtle.currentX, turtle.currentY);
turtle.polygon(p);
// 支幹退回
turtle.warp(-K * K2 * leng);
turtle.turn(2 * ANGLE);
turtle.warp(-K * K2 * leng);
turtle.turn(-ANGLE);
turtle.warp(-K * leng);
turtle.turn(-ANGLE);
// 主幹退回
turtle.warp(-K * K1 * leng);
turtle.turn(2 * ANGLE);
turtle.warp(-K * K1 * leng);
turtle.turn(-ANGLE);
turtle.warp(-K * K1 * leng);
}
var canvas1 = document.getElementById('canvas1');
var context = canvas1.getContext('2d');
var turtle = new Turtle(context);
turtle.window(0, 0, canvas1.width, canvas1.height);
turtle.view(0, 0, canvas1.width, canvas1.height);
turtle.setPoint(10, canvas1.height / 2);
turtle.setAngle(0);
woods(400);
};
</script>
</head>
<body>
<canvas id="canvas1" width="420" height="400"></canvas>
</body>
</html>
下圖為Firefox中的繪圖成果:
到目前為止所介紹的遞迴繪圖都是在2D平面上,實際上遞迴繪圖在3D空間中也有相當的應用,不過複雜性也因維度的增加而複雜許多,如果有興趣,建議您研究一下 JAVA 2D/3D繪圖程式設計實例應用 書中的3D遞迴繪圖原始碼。