科赫曲线和谢尔宾斯基三角形是常见的分形图形(详细介绍见参考文献1),本文使用turtle库绘制这两类图形。
科赫曲线的详细介绍见参考文献2,其中的绘图思路是“画正三角形,并把每一边三等分,取三等分后的一边中间一段为边向外作正三角形,并把这“中间一段”擦掉,重复上述两步,画出更小的三角形。”考虑到绘制完三角形再处理每条边的话,使用turtle的绘制函数不便于计算坐标,于是调整为绘制每条边时都将其三等分,只处理要显示的前后两个子边,重复该过程至指定的迭代次数(看下面录制的绘图动画比较直观)。最终的绘图代码及绘图效果如下所示。
from turtle import *
def Kohn(length, n):
if n==0:
forward(length)
else:
thirdLen=length/3
Kohn(thirdLen, n-1)
right(60)
Kohn(thirdLen, n-1)
left(120)
Kohn(thirdLen, n-1)
right(60)
Kohn(thirdLen, n-1)
screensize(600,400)
tracer(0, 0)
penup()
goto(-180,-90)
pendown()
Kohn(360,8)
left(120)
Kohn(360,8)
left(120)
Kohn(360,8)
left(120)
hideturtle()
exitonclick()
参考文献3中介绍的谢尔宾斯基三角形的画法有去掉中心法、Chaos Game法和L系统,本文中采用Chaos Game法绘制。最初采用参考文献3中的构造方法绘图,绘图代码及绘出的图形如下所示(判断点是否在三角形内的代码来自参考文献4)。:
def Sierpinski_1(x1,y1,x2,y2,x3,y3):
maxX=max((x1,x2,x3))
minX=min((x1,x2,x3))
maxY=max((y1,y2,y3))
minY=min((y1,y2,y3))
rx=random.randint(minX+1,maxX-1)
ry=random.randint(minY+1,maxY-1)
while(not IsInside(x1,y1,x2,y2,x3,y3,rx,ry)):
rx=random.randint(minX+1,maxX-1)
ry=random.randint(minY+1,maxY-1)
tmp=((x1,y1),(x2,y2),(x3,y3))[random.randint(0,2)]
mx=(tmp[0]+rx)/2
my=(tmp[1]+ry)/2
penup()
goto(mx-1,my)
pendown()
right(90)
circle(1)
left(90)
screensize(600,400)
tracer(0, 0)
x1=-250
y1=-150
x2=150
y2=-150
x3=0
y3=100
for i in range(0,5000):
Sierpinski_1(x1,y1,x2,y2,x3,y3)
hideturtle()
估计应该是绘图思路不对,百度谢尔宾斯基三角形的混沌游戏画法,发现步骤都大致相同,但重复的步骤不一致,最终基于参考文献5-6中的介绍,调整绘图思路为:
1、随机生成三个点A,B,C,组成三角形;
2、随机生成三角形的一点P;
3、绘制点P,同时计算P与A,B,C中任意一点的中点,并将中点坐标赋予P;
4、重复步骤3。
基于上述步骤,重新调整代码,绘图代码及绘图效果如下:
def Sierpinski_2(x1,y1,x2,y2,x3,y3,n):
maxX=max((x1,x2,x3))
minX=min((x1,x2,x3))
maxY=max((y1,y2,y3))
minY=min((y1,y2,y3))
px=random.randint(minX+1,maxX-1)
py=random.randint(minY+1,maxY-1)
while(not IsInside(x1,y1,x2,y2,x3,y3,px,py)):
px=random.randint(minX+1,maxX-1)
py=random.randint(minY+1,maxY-1)
penup()
goto(px-1,py)
pendown()
right(90)
circle(1)
left(90)
points=((x1,y1),(x2,y2),(x3,y3))
for index in range(0,n):
tmp=points[random.randint(0,2)]
px=(tmp[0]+px)/2
py=(tmp[1]+py)/2
penup()
goto(px-1,py)
pendown()
right(90)
circle(1)
left(90)
screensize(600,400)
tracer(0, 0)
x1=-250
y1=-150
x2=150
y2=-150
x3=0
y3=100
Sierpinski_2(x1,y1,x2,y2,x3,y3,5000)
hideturtle()
from turtle import *
import random
def IsTrangleOrArea(x1,y1,x2,y2,x3,y3):
return abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2.0)
def IsInside(x1,y1,x2,y2,x3,y3,x,y):
ABC = IsTrangleOrArea(x1,y1,x2,y2,x3,y3)
PBC = IsTrangleOrArea(x,y,x2,y2,x3,y3)
PAC = IsTrangleOrArea(x1,y1,x,y,x3,y3)
PAB = IsTrangleOrArea(x1,y1,x2,y2,x,y)
return (ABC == PBC + PAC + PAB)
maxX=max((x1,x2,x3))
minX=min((x1,x2,x3))
maxY=max((y1,y2,y3))
minY=min((y1,y2,y3))
px=random.randint(minX+1,maxX-1)
py=random.randint(minY+1,maxY-1)
while(not IsInside(x1,y1,x2,y2,x3,y3,px,py)):
px=random.randint(minX+1,maxX-1)
py=random.randint(minY+1,maxY-1)
penup()
goto(px-1,py)
pendown()
right(90)
circle(1)
left(90)
points=((x1,y1),(x2,y2),(x3,y3))
for index in range(0,n):
tmp=points[random.randint(0,2)]
px=(tmp[0]+px)/2
py=(tmp[1]+py)/2
penup()
goto(px-1,py)
pendown()
right(90)
circle(1)
left(90)
width=600
height=400
screensize(width,400)
tracer(0, 0)
x1=random.randint(1-width/2,width/2-1)
y1=random.randint(1-height/2,height/2-1)
x2=random.randint(1-width/2,width/2-1)
y2=random.randint(1-height/2,height/2-1)
x3=random.randint(1-width/2,width/2-1)
y3=random.randint(1-height/2,height/2-1)
Sierpinski_2(x1,y1,x2,y2,x3,y3,5000)
hideturtle()
exitonclick()
参考文献
[1]https://baike.baidu.com/item/%E5%88%86%E5%BD%A2/85449?fr=aladdin
[2]https://baike.baidu.com/item/%E7%A7%91%E8%B5%AB%E6%9B%B2%E7%BA%BF/7090673?fromModule=lemma_inlink
[3]https://baike.baidu.com/item/%E8%B0%A2%E5%B0%94%E5%AE%BE%E6%96%AF%E5%9F%BA%E4%B8%89%E8%A7%92%E5%BD%A2?fromModule=lemma_inlink
[4]https://blog.csdn.net/mxw322/article/details/56026607
[5]https://zhuanlan.zhihu.com/p/103610498?utm_source=wechat_session&ivk_sa=1024320u
[6]https://wiki.swarma.org/index.php?title=%E8%B0%A2%E5%B0%94%E5%AE%BE%E6%96%AF%E5%9F%BA%E4%B8%89%E8%A7%92%E5%BD%A2