上篇(Python海龟绘图——分形、递归与递归算法(上))介绍了用分形理论和递归函数,用Python的turtle绘制分形树,尽管加了树干、树枝粗细变化,树仍因为分形的自相似性而完全对称,比较呆板。需要给树干、树枝长度加上随机数扰动,树枝夹角也加上随机数扰动,那就更像自然的树。
在讲树干、树枝长度、树枝夹角加随机数扰动前先来画一棵雪松树(圣诞树)。
四、Python绘制向下分叉的分形树——雪松树(圣诞树)
如果二叉树的分叉是120°树枝就向下垂,就像雪松树。最后树干用顶角为2°的等腰三角形绘制。
设三角形的腰长为l,则底边长为d=2lsin(1°)≈2πl/180
Python用turtle绘制雪松树(圣诞树)程序代码如下:
#############################################
# 设计 Zhang Ruilin 创建 2021-12-05 12:55 #
# 用分形理论+递归,turtle绘制雪松树(圣诞树) #
#############################################
import turtle as tl
import random
n=100.0
def tree(d, s): # 递归画树、树枝
if d <=0: return
tl.fd(s) # 画树干
tree(d-1, s*.8) # 在树干上画树枝
tl.rt(120)
tree(d-3,s*.5) # 向右下方在树干上画树枝
tl.rt(120)
tree(d-3,s*.5) # 向左下方在树干上画树枝
tl.rt(120)
tl.bk(s) # 返回上层分叉点
tl.speed('fastest')
tl.screensize(bg='lightyellow')
tl.ht() # 隐藏海龟形状,可加快绘图速度
tl.lt(90) # 方向向上
tl.fd(2.5 * n)
tl.color('orange', 'yellow') # 画笔橘红色、填充黄颜色
tl.begin_fill()
tl.lt(126)
for i in range(5): # 画橘红色边黄颜色五角星
tl.fd(n/5)
tl.rt(144)
tl.fd(n/5)
tl.lt(72)
tl.end_fill()
tl.rt(126) # 方向向上
tl.color('dark green')
tl.bk(n * 4.8)
tree(15,n) # 画树
tl.bk(n/2)
tl.begin_fill() # 画树干(顶角2°的等腰三角形)
tl.fd(n * 5.3)
tl.lt(179)
tl.fd(n * 5.3)
tl.lt(91)
tl.fd(2 * n * 5.3 * 3.141592654 / 180) # 角度较小时的sin近似算法
tl.lt(91)
tl.fd(n * 5.3)
tl.end_fill()
tl.up()
tl.lt(180)
tl.fd(n * 5.3)
tl.rt(91)
tl.fd(4 * n * 5.3 * 3.141592654 / 180)
tl.rt(91)
tl.fd(n * 5.3)
tl.rt(179)
tl.fd(n * 5.3)
a = 200
b = 40
for i in range(200): # 画小礼物
x = a - 2 * a * random.random()
y = b - 2 * b * random.random()
if x*x/a/a+y*y/b/b <= 1: # 2a、2b的sbe椭圆范围内
tl.up()
tl.fd(y)
tl.lt(90)
tl.fd(x)
tl.pd()
if random.randint(0, 1) == 0:
tl.color( 'violet' )
else:
tl.color( 'orange' )
tl.dot(8)
tl.up()
tl.bk(x)
tl.rt(90)
tl.bk(y)
tl.done()
运行结果如图1所示。
图1 雪松树(圣诞树)
五、Python绘制已发芽并开着桃花的桃树
本篇在上篇的基础上,对树枝长度、分叉角增加了随机数扰动,为了避免树枝过长,用树枝长度和树枝粗细共同控制递归深度(结束递归的条件)。
在Python中可以用random模块的random()函数产生[0, 1)的随机数,包含0但不含1。长度每次递归长度缩短0~30%(length=length*(1-0.3*random())),分叉角在0~120%(ang= ang *1.2*random())间变化,分叉角为0即不分叉。randint(a, b)函数产生[a, b]的随机整数,包含a也包含b。
桃树是先开花后长叶,在开花时叶子还只是1、2厘米左右的叶芽。
Python用turtle绘制已发芽并开着桃花的桃树程序代码如下:
##############################################
# 设计 Zhang Ruilin 创建 2021-12-04 20:45 #
# 用分形理论+递归,turtle 绘制开着桃花的桃树 #
##############################################
import turtle as tl
import random
tl.setup(600, 600) # 定义窗体大小
tl.speed(0)
tl.ht() # 隐藏画笔形状
def draw_branches(tree_length, tree_size, tree_angle): # 画桃树的躯干枝叶和花
if tree_length > 3 and tree_size > 1:
if 8 <= tree_length <= 12:
k = random.randint(0, 4)
if k == 0: # 20%画浅色桃花
tl.color('mistyrose') # 薄雾玫瑰色
elif k == 1 or k == 2: # 40%画树叶
tl.color('green') # 绿色
else: # 40%画深色桃花
tl.color('pink') # 粉色(桃红)
tl.pensize(tree_length / 3)
elif tree_length < 8:
if random.randint(0, 1) == 0:
tl.color('mistyrose') # 薄雾玫瑰色
else:
tl.color('pink') # 粉色(桃红)
tl.pensize(tree_length / 2)
else:
tl.color('brown') # 综色
tl.pensize(tree_size)
if tl.color()[0] == 'green': # 画叶子
if tree_length >=5:
for _ in range(int(tree_length+0.5)):
tl.pensize(tree_length/2 -_*(tree_length/2-1)/tree_length)
tl.fd(1)
else:
tl.pensize(3)
tl.fd(int(tree_length/2))
tl.pensize(1)
tl.fd(tree_length-int(tree_length/2))
else: # 画树枝或花朵
tl.fd(tree_length)
a = 1.2 * random.random()
b = random.random()
tl.rt(tree_angle * a)
draw_branches(tree_length * (1-0.3*b), tree_size * 0.85, tree_angle)
tl.lt(2 * tree_angle * a)
draw_branches(tree_length * (1-0.3*b), tree_size * 0.85, tree_angle)
tl.rt(tree_angle * a)
tl.up()
tl.bk(tree_length)
tl.pd()
if __name__ == '__main__':
tl.getscreen().tracer(5, 0) # 设置海龟动画更新及延迟,加快绘图
tl.screensize(bg='honeydew') # 蜜瓜色(白淡绿色)
tl.lt(90) # 方向向上(12点方向)
tl.up()
tl.bk(200) # 移动到树干起始位置
tree_length = 70 # 设置的树干树枝初始长度
tree_size = 6 # 设置的树干树枝初始粗细
tree_angle = 25 # 设置基准树枝分叉角度
tl.pd()
tl.color('brown') # 树干部分为棕色
draw_branches(tree_length, tree_size, tree_angle) # 启动绘图
tl.exitonclick() # 单击鼠标左键退出
tl.done() # 结束时保持图形窗体
运行结果如图2所示。
图2 已发芽并开着桃花的桃树
在程序中为了避免树枝过长,用树枝长度和树枝粗细共同控制递归深度:
tree_length> 3 and tree_size > 1
因此有一部分树枝就没有达到绘制树叶或桃花的条件,故没有绘制树叶或桃花,给人以桃花凋谢的感觉。
另外用randint(0, 4)产生[0, 4]五个整数的随机数,每个数出现的概率是20%,当枝条不是太细时20%画浅色桃花、40%画叶芽和40%画深色桃花,当枝条比较细时用各50%的概率画浅色桃花和深色桃花。由于叶芽很小,就用从粗到细的直线(长三角形)模拟。
那凋谢的花呢? 应该是落地上了。
六、Python绘制有掉落花瓣并开着桃花的桃树
桃树树冠从顶上看大致是个圆形,落花也基本上落在一个圆形区域,侧面看大致是个椭圆形区域。椭圆方程为x2/a2+y2/b2=1,椭圆内的条件是x2/a2+y2/b2<=1。所以x在[-a, a],y在[-b, b]矩形区域内满足:x2/a2+y2/b2<=1,即在椭圆范围之内。
由于绘制落下的花瓣会绘制到树干上(可参考图1),所以需要先画掉落的花瓣,再画桃树,这样可保证在树干上没有落下的花瓣。掉落的花瓣只在椭圆形范围内绘制。
Python用turtle绘制有掉落花瓣并开着桃花的桃树程序代码如下:
#################################################
# 设计 Zhang Ruilin 创建 2021-12-05 07:35 #
# 用分形+递归,turtle绘制洒落花瓣开着桃花的桃树 #
#################################################
import turtle as tl
import random
tl.setup(600, 600) # 定义窗体大小
tl.speed(0)
tl.ht() # 隐藏画笔形状
def draw_branches(tree_length, tree_size, tree_angle): # 画桃树的躯干枝叶和花
if tree_length > 3 and tree_size > 1:
if 8 <= tree_length <= 12:
k = random.randint(0, 4)
if k == 0: # 20%画浅色桃花
tl.color('mistyrose') # 薄雾玫瑰色
elif k == 1 or k == 2: # 40%画树叶
tl.color('green') # 绿色
else: # 40%画深色桃花
tl.color('pink') # 粉色(桃红)
tl.pensize(tree_length / 3)
elif tree_length < 8:
if random.randint(0, 1) == 0:
tl.color('mistyrose') # 薄雾玫瑰色
else:
tl.color('pink') # 粉色(桃红)
tl.pensize(tree_length / 2)
else:
tl.color('brown') # 综色
tl.pensize(tree_size)
if tl.color()[0] == 'green': # 画叶子
if tree_length >=5:
for _ in range(int(tree_length+0.5)):
tl.pensize(tree_length/2 -_*(tree_length/2-1)/tree_length)
tl.fd(1)
else:
tl.pensize(3)
tl.fd(int(tree_length/2))
tl.pensize(1)
tl.fd(tree_length-int(tree_length/2))
else: # 画树枝或花朵
tl.fd(tree_length)
a = 1.2 * random.random()
b = random.random()
tl.rt(tree_angle * a)
draw_branches(tree_length * (1-0.3*b), tree_size * 0.85, tree_angle)
tl.lt(2 * tree_angle * a)
draw_branches(tree_length * (1-0.3*b), tree_size * 0.85, tree_angle)
tl.rt(tree_angle * a)
tl.up()
tl.bk(tree_length)
tl.pd()
def petals(m): # 画掉落的花瓣
a0 = 225
b0 = 20
for i in range(m):
x = a0 - 2 * a0 * random.random()
y = b0 - 2 * b0 * random.random()
if x * x /a0 / a0 + y * y / b0 / b0 <= 1: # 只画在2a、2b的椭圆中
tl.up()
tl.fd(y)
tl.lt(90)
tl.fd(x)
tl.pd()
tl.color('pink') # 粉色(桃红)
tl.dot(3)
tl.up()
tl.bk(x)
tl.rt(90)
tl.bk(y)
if __name__ == '__main__':
tl.getscreen().tracer(5, 0) # 设置海龟动画更新及延迟,加快绘图
tl.screensize(bg='honeydew') # 蜜瓜色(白淡绿色)
tl.lt(90) # 方向向上(12点方向)
tl.up()
tl.bk(200) # 移动到树干起始位置
tl.pd()
petals(400) # 以树干起始为中心画落花
tree_length = 70 # 设置的树干树枝初始长度
tree_size = 6 # 设置的树干树枝初始粗细
tree_angle = 25 # 设置基准树枝分叉角度
tl.pd()
tl.color('brown') # 树干部分为棕色
draw_branches(tree_length, tree_size, tree_angle) # 启动绘图
tl.exitonclick() # 单击鼠标左键退出
tl.done() # 结束时保持图形窗体
运行结果如图3所示。
图3 有掉落花瓣并开着桃花的桃树
由于用了随机数,所以每次运行绘制的桃树都会有差异,请看图4是另一幅不同时间绘制的开着桃花的桃树。有的可能画得不满意,可选择满意的保存。
图4 不同时间运行生成的有掉落花瓣并开着桃花的桃树