這是要介紹的最後一個遞迴繪圖,看起來很複雜,但其實只是程式碼長了一點,實際上並不會比雪花來得複雜,只要弄清楚繪圖順序就可以了。
在這邊只解釋單一樹木的基本繪圖方式,主要分為主幹與支幹的繪製,如下圖所示,其中標號表示頂點的前進順序:

接下來只要在主幹與支幹上不斷的繪製一定比例的相同圖形,就可以完成一顆樹,將這些樹排列起來就成為樹林,詳細的原始碼請看以下的程式,其中註解部份先不 用理會:
- 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遞迴繪圖原始碼。

