小练手:用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

 

posted @ 2017-04-27 15:34  HowardZhang  阅读(1859)  评论(0编辑  收藏  举报