文章首发于我的知乎专栏,原地址: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

小练手:用HTML5 Canvas绘制谢尔宾斯基三角形的更多相关文章

  1. Python使用递归绘制谢尔宾斯基三角形

    谢尔宾斯基三角形使用了三路递归算法,从一个大三角形开始,通过连接每一个边的中点,将大三角型分为四个三角形,然后忽略中间的三角形,依次对其余三个三角形执行上述操作. 运行效果: 源代码: 1 impor ...

  2. python 使用turtule绘制递归图形(螺旋、二叉树、谢尔宾斯基三角形)

    插图工具使用Python内置的turtle模块,为什么叫这个turtle乌龟这个名字呢,可以这样理解,创建一个乌龟,乌龟能前进.后退.左转.右转,乌龟的尾巴朝下,它移动时就会画一条线.并且为了增加乌龟 ...

  3. python---使用递归实现谢尔宾斯基三角形及汉诺塔

    渐入佳境. # coding: utf-8 import turtle ''' # =================turtle练手== def draw_spiral(my_turtle, lin ...

  4. 分形之谢尔宾斯基(Sierpinski)三角形

    谢尔宾斯基三角形(英语:Sierpinski triangle)是一种分形,由波兰数学家谢尔宾斯基在1915年提出,它是一种典型的自相似集.也有的资料将其称之为谢尔宾斯基坟垛. 其生成过程为: 取一个 ...

  5. 分形之谢尔宾斯基(Sierpinski)地毯

    前面讲了谢尔宾斯基三角形,和这一节的将把三角形变为正方形,即谢尔宾斯基地毯,它是由瓦茨瓦夫·谢尔宾斯基于1916年提出的一种分形,是自相似集的一种. 谢尔宾斯基地毯的构造与谢尔宾斯基三角形相似,区别仅 ...

  6. 分形之谢尔宾斯基(Sierpinski)四面体

    前面讲了谢尔宾斯基三角形,这一节的将对二维三角形扩展到三维,变成四面体.即将一个正四面体不停地拆分,每个正四面体可以拆分成四个小号的正四面体.由二维转变到三维实现起来麻烦了许多.三维的谢尔宾斯基四面体 ...

  7. 混沌分形之谢尔宾斯基(Sierpinski)

    本文以使用混沌方法生成若干种谢尔宾斯基相关的分形图形. (1)谢尔宾斯基三角形 给三角形的3个顶点,和一个当前点,然后以以下的方式进行迭代处理: a.随机选择三角形的某一个顶点,计算出它与当前点的中点 ...

  8. 【数据结构与算法Python版学习笔记】递归(Recursion)——定义及应用:分形树、谢尔宾斯基三角、汉诺塔、迷宫

    定义 递归是一种解决问题的方法,它把一个问题分解为越来越小的子问题,直到问题的规模小到可以被很简单直接解决. 通常为了达到分解问题的效果,递归过程中要引入一个调用自身的函数. 举例 数列求和 def ...

  9. html5 Canvas绘制图形入门详解

    html5,这个应该就不需要多作介绍了,只要是开发人员应该都不会陌生.html5是「新兴」的网页技术标准,目前,除IE8及其以下版本的IE浏览器之外,几乎所有主流浏览器(FireFox.Chrome. ...

随机推荐

  1. iOS实现截屏 并合适保存

     本文转载至:http://blog.csdn.net/zeng11088/article/details/8664510 分类: UIImageView2013-03-12 16:42 122人阅读 ...

  2. DistroWatch评估XStream桌面153版本

    导读 XStreamOS是一个由Sonicle创建的Solaris的一个版本.XStream桌面将Solaris的强大带给了桌面用户,同时新手用户很可能有兴趣体验一下.DistroWatch对于XSt ...

  3. Android之MessageQueue、Looper、Handler与消息循环

    在android的activity中有各种各样的事件,而这些事件最终是转换为消息来处理的.android中的消息系统涉及到: *  消息发送 *  消息队列 *  消息循环 *  消息分发 *  消息 ...

  4. oracle order by 字段不能为空 为空速度慢 不走索引

    oracle order by 字段不能为空 为空速度慢 不走索引

  5. WCF:并发处理

    当多个线程同时访问相同的资源的时候就会产生并发,WCF缺省情况下会保护并发访问.对并发访问需要恰当处理,控制不好不仅会大大降低WCF服务的吞吐量和性能,而且还有可能会导致WCF服务的死锁.一.WCF并 ...

  6. Android - ViewPager实现Gallery效果

    RelativeLayout viewPagerContainer = (RelativeLayout) headerView.findViewById(R.id.content_pager_layo ...

  7. Location 对象的assign()和replace()有什么区别?

    window.location.assign(url) : 加载 URL 指定的新的 HTML 文档. 就相当于一个链接,跳转到指定的url,当前页面会转为新页面内容,可以点击后退返回上一个页面. w ...

  8. angular中的动画效果

    用angular来形成动画效果的代码如下 <!DOCTYPE html> <html lang="en" ng-app="app"> & ...

  9. Tomcat----->软件密码学基础配置tomcat http连接器 https

    公钥只能私钥解开,私钥只能公钥解开. 类似于别人给你一个盒子,你用他的盒子和自己盒子加密,他手中有他的钥匙和自己的钥匙,可以解开就既能证明是你发的也能相信内容. 每个数据有自己的数据指纹,数据指纹是由 ...

  10. SQLALchemy的其他常用操作

    使用连接池的两种方式 第一种方式: 直接从SessionFactory里获取,此时如果需要开启多个进程,那么创建连接池的代码一定要放在循环里面 不然的话每个进程都是用一个session了 from s ...