小練手:用HTML5 Canvas繪制謝爾賓斯基三角形


文章首發於我的知乎專欄,原地址:https://zhuanlan.zhihu.com/p/26606208

 

以前看到過一個問題:謝爾賓斯基三角形能用編程寫出來么?該怎么寫? - 知乎,在回答里,各方大神用各種語言各種方法實現了一遍,非常精彩。我當時也回答了這個問題,是用H5的Canvas實現的。這在前端技術上沒什么難度,主要是算法比較有可玩性,所以當時就手癢了。

謝爾賓斯基三角形是分形圖形的一種,大概很多人第一次見到它都是在中學教科書上。它長這樣:

 

 

我用了兩種方法構造它:直接繪制三角形和間接用折線逼近。

 

1.直接繪制三角形。具體方法就是先畫一個大三角形,再遞歸繪制一堆倒三角形。這種方法最為直觀也最好想。放碼過來:

 

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Sierpinski Triangle</title>
</head>
<body>
    <canvas id="canvas" 
    width="600" height="600" 
    style="display:block;margin:50px auto">
        你的瀏覽器不支持canvas
    </canvas>
</body>
<script type="text/javascript">
    var context = 
    document.getElementById("canvas")
    .getContext("2d");
    
    //根據三頂點坐標繪制一個三角形
    function triangle(p1,p2,p3){
        context.moveTo(p1.x,p1.y);
        context.lineTo(p2.x,p2.y);
        context.lineTo(p3.x,p3.y);
        context.lineTo(p1.x,p1.y);
    }
    
    /*繪制謝爾賓斯基三角形的方法
      p:正三角形中心點坐標,len:三角形邊長*/
    function SierpinskiTriangle(p,len){
        var r=len/Math.sqrt(3);
        //計算頂點坐標
        var p1={x:p.x, y:p.y-r};
        var p2={x:p.x-len/2, y:p.y+r/2};
        var p3={x:p2.x+len, y:p2.y};
        triangle(p1,p2,p3); //繪制正三角形外框
        //遞歸繪制所有的倒三角形
        middleTriangle(p1,p2,p3); 
        
        function middleTriangle(p1,p2,p3){
            var tp1={x:(p2.x+p3.x)/2, y:(p2.y+p3.y)/2};
            var tp2={x:(p1.x+p3.x)/2, y:(p1.y+p3.y)/2};
            var tp3={x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2};
            triangle(tp1,tp2,tp3);
            //遞歸前判斷最短線條長度是否短於臨界值
            if((tp1.x-tp2.x)*(tp1.x-tp2.x)+
            (tp1.y-tp2.y)*(tp1.y-tp2.y)>20){
                middleTriangle(p1,tp2,tp3);
                middleTriangle(p2,tp1,tp3);
                middleTriangle(p3,tp1,tp2);
            }
        }
    }

    //繪制
    SierpinskiTriangle({x:300,y:360},560);
    context.lineWidth = 0.5;
    context.strokeStyle = "#F5270B";
    context.stroke();
</script>
</html>

 

保存成html文件用瀏覽器打開,效果如下:

 

2.折線逼近法:

 

 

這個方法比較神奇,簡單來說,是從一條水平線開始,每次遞歸都把所有線段替換成有規律的三段折線。無限遞歸下去,整段折線會越來越逼近謝爾賓斯基三角形。

用這個思路實現的SierpinskiTriangle函數如下,多了一個設置遞歸深度的depth參數:

 

/*繪制謝爾賓斯基三角形的方法
  p:正三角形中心點坐標,len:三角形邊長,depth:遞歸深度*/
function SierpinskiTriangle(p,len,depth){
    var r=len/Math.sqrt(3);
    //記錄當前端點,默認為左下角頂點坐標
    var currentPoint={x:p.x-len/2, y:p.y+r/2};
    //記錄當前方向角
    var currentAngle=0;

    //旋轉方法,將下次畫線的方向逆時針旋轉
    function turn(angle){
        currentAngle+=angle;
    }
    //畫線方法,根據當前端點和畫線方向繪制
    function draw_line(length){
        var angle=currentAngle/180*Math.PI;
        currentPoint.x+=length*Math.cos(angle);
        currentPoint.y-=length*Math.sin(angle);
        context.lineTo(currentPoint.x,currentPoint.y);
    }

    //開始畫折線,如果深度是偶數便可直接繪制折線,否則需要以斜60度為初始方向
    context.moveTo(currentPoint.x,currentPoint.y);
    if (depth%2==0){
        curve(depth,len,-60);
    }else{
        turn(60);
        curve(depth,len,-60);
    }

    function curve(order,length,angle)
    {
        if (order==0){
            draw_line(length);
        }else{
            //遞歸畫三段折線
            curve(order-1,length/2,-angle);
            turn(angle);
            curve(order-1,length/2,angle);
            turn(angle);
            curve(order-1,length/2,-angle);
        }
    }
}

 

用這個函數替換上一段HTML中的SierpinskiTriangle函數就行了,調用時需要給depth參數賦值,推薦為9。我將depth從0到9十種情況的曲線放在同一個canvas里輸出了,可供直觀理解:

 

參考來源:
Sierpinski triangle
Sierpiński arrowhead curve

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM