The Nature of Code閱讀心得與Python實作:8.4 Trees

更新 發佈閱讀 24 分鐘

碎形樹(fractal tree)是碎形界除了Cantor集、Koch曲線外,另一個無人不知、無人不曉的圖案。比較特別的是,製作Cantor集跟Koch區線時,所使用的方法是確定性的(deterministic),不帶有任何隨機性在裡頭;但在製作碎形樹時,可以加入隨機性,讓畫出來的碎形樹長相,更接近大自然中樹木的真實模樣。

The Deterministic Version

先來看不含隨機性,也就是確定性版的碎形樹要怎麼畫。

確定性版碎形樹的製作規則非常簡單,步驟為:

  1. 畫一條線段。
  2. 在線段的尾端分別向右、向左轉一個角度,然後各畫一條短一點的線段。
  3. 重複步驟2。

搭配下圖來看會更清楚一些:

vocus|新世代的創作平台

製作碎形樹的重點,在於如何找出線段分岔之後,那兩條比較短的線段的方向。這個其實不難,我們可以用向量來描述線段的方向,然後旋轉向量就可以了。假設dirpygame.Vector2物件,裡頭放的是線段的方向。要找出dir向右、向左旋轉angle度之後的方向,程式可以這樣寫:

dir_right = dir.rotate(angle)
dir_left = dir.rotate(-angle)

如果用線段的長度來作為判斷是否符合基底情況的條件,那製作碎形樹的遞迴函數可以這樣寫:

def branch(surface, length, start, direction, angle_deg):
# 基底情況
if length < 2:
return

vec = direction.copy()

# 線段終點位置
vec.scale_to_length(length)
end = start + vec

# 畫出線段
pygame.draw.line(surface, (0, 0, 0), start, end)

# 分支長度與方向
length *= 0.67
direction_right = vec.rotate(angle_deg)
direction_left = vec.rotate(-angle_deg)

branch(surface, length, end, direction_right, angle_deg)
branch(surface, length, end, direction_left, angle_deg)

Exercise 8.6

vocus|新世代的創作平台

在圖中,數字代表處裡的順序。因為branch()函數在最後兩行呼叫自己時,是先處理右邊的分支,然後再處理左邊的,所以在畫完右邊的分支之後,才會畫左邊的分支。

Example 8.6: A Recursive Tree

在這個例子中,我們利用滑鼠來控制碎形樹分岔的角度;移動滑鼠就可以看到碎形樹變換成不同的模樣。

vocus|新世代的創作平台
vocus|新世代的創作平台

第二張圖是分支的方向是旋轉90度時的碎形樹。很令人驚奇的是,除了一開始的那條垂直線段外,整棵碎形樹的長相,就跟Exercise 8.1所畫出來的圖案是一樣的。

主程式如下:

# python version 3.10.9
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 8.6: A Recursive Tree")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 320
screen = pygame.display.set_mode(screen_size)

FPS = 30
frame_rate = pygame.time.Clock()

length = 100
start = pygame.Vector2(WIDTH/2, HEIGHT)
direction = pygame.Vector2(0, -1)

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

x, y = pygame.mouse.get_pos()
angle_deg = (x/WIDTH)*90

branch(screen, length, start, direction, angle_deg)

pygame.display.update()
frame_rate.tick(FPS)

利用遞迴函數來畫碎形樹,程式雖然非常簡短,但卻也限制了應用的範圍。例如,如果想將碎形樹的成長製作成動畫,用遞迴函數的方式來做,將會有相當大的困難度。然而,如果像先前在寫Koch曲線時一樣,也使用物件導向的方式來寫,把碎形樹的每條線段都視為是物件,那將可大大地增廣應用範圍與可能性;而碎形樹的成長動畫,也就只是小菜一碟罷了。

Exercise 8.7

branch()函數中,加入用來控制線段粗細的參數thickness即可。

vocus|新世代的創作平台
def branch(surface, length, thickness, start, direction, angle_deg):
# 基底情況
if length < 2:
return

vec = direction.copy()

# 線段終點位置
vec.scale_to_length(length)
end = start + vec

# 畫出線段
pygame.draw.line(surface, (0, 0, 0), start, end, thickness)

# 分支長度、粗細、方向
length *= 0.67
thickness = int(0.8*thickness)
direction_right = vec.rotate(angle_deg)
direction_left = vec.rotate(-angle_deg)

branch(surface, length, thickness, end, direction_right, angle_deg)
branch(surface, length, thickness, end, direction_left, angle_deg)


# python version 3.10.9
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 8.7")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 320
screen = pygame.display.set_mode(screen_size)

FPS = 30
frame_rate = pygame.time.Clock()

length = 100
start = pygame.Vector2(WIDTH/2, HEIGHT)
direction = pygame.Vector2(0, -1)
thickness = 15

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

x, y = pygame.mouse.get_pos()
angle_deg = (x/WIDTH)*90

branch(screen, length, thickness, start, direction, angle_deg)

pygame.display.update()
frame_rate.tick(FPS)

Exercise 8.8

vocus|新世代的創作平台
vocus|新世代的創作平台
vocus|新世代的創作平台
class Branch:
def __init__(self, start, direction, growth_speed, time_span):
self.start = start.copy()
self.direction = direction.normalize()
self.growth_speed = growth_speed
# 生長時間長度
self.time_span = time_span

self.end = self.start.copy()
self.time_left = self.time_span

self.growing = True
self.ready_to_branch = False

def grow(self):
self.time_left -= 1
self.end += self.growth_speed*self.direction

def update(self):
if self.growing:
self.grow()
# 如果生長時間用罄則停止生長,樹枝可以分岔了
if self.time_left <= 0:
self.growing = False
self.ready_to_branch = True

def show(self, surface):
pygame.draw.line(surface, (0, 0, 0), self.start, self.end)

def branch(self, angle_deg):
self.ready_to_branch = False
branch_direction = self.direction.rotate(angle_deg)
return Branch(self.end, branch_direction, self.growth_speed, 0.67*self.time_span)


class Leaf:
def __init__(self, x, y):
self.x = x
self.y = y

def show(self, surface):
pygame.draw.circle(surface, (0, 255, 0), (self.x, self.y), 3)


# python version 3.10.9
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 8.8")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 320
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

screen.fill(WHITE)

length = 100
start = pygame.Vector2(WIDTH/2, HEIGHT)
direction = pygame.Vector2(0, -1)
growth_speed = 1
time_span = length/growth_speed

tree = [Branch(start, direction, growth_speed, time_span)]
leaves = []

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

new_branches = []
for branch in tree:
branch.update()
branch.show(screen)
if branch.ready_to_branch:
new_branches.append(branch.branch(30))
new_branches.append(branch.branch(-30))

if len(tree) < 128:
tree += new_branches
else:
for branch in new_branches:
leaves.append(Leaf(branch.end.x, branch.end.y))

for leaf in leaves:
leaf.show(screen)

pygame.display.update()
frame_rate.tick(FPS)

The Stochastic Version

確定版的碎形樹最大的問題是太確定了,整棵樹裡裡外外精確到不像是真的。真實的樹木樹枝分岔的角度不會都一樣;分支的數量也不會固定就是2支。那要怎麼做,才能讓碎形樹長得不那麼精確,看起來比較真實一點呢?

要讓碎形樹長得比較真實一些,方法挺簡單的,加點隨機性就可以了。下面這個例子,就是在樹枝分岔的角度以及分支的數量上加入隨機性,讓碎形樹看起來不再那麼死板。

Example 8.7: A Stochastic Tree

vocus|新世代的創作平台
vocus|新世代的創作平台
def branch(surface, length, start, direction):
# 基底情況
if length < 2:
return

vec = direction.copy()

# 線段終點位置
vec.scale_to_length(length)
end = start + vec

# 畫出線段
pygame.draw.line(surface, (0, 0, 0), start, end, 2)

# 分支長度
length *= 0.67

# 分支數量1~3支;角度-90度~90度
n = random.randint(1, 3)
for i in range(n):
angle_deg = random.uniform(-90, 90)
direction_branch = vec.rotate(angle_deg)
branch(surface, length, end, direction_branch)


# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 8.7: A Stochastic Tree")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 320
screen = pygame.display.set_mode(screen_size)

FPS = 1
frame_rate = pygame.time.Clock()

length = 100
start = pygame.Vector2(WIDTH/2, HEIGHT)
direction = pygame.Vector2(0, -1)

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

branch(screen, length, start, direction)

pygame.display.update()
frame_rate.tick(FPS)

加入隨機性的碎形樹雖然看起來不再那麼死板,但離栩栩如生還差得遠了。想要讓碎形樹更真實一些,還需要針對隨機性的參數精雕細琢一番;加入Perlin noise也是個可以考慮的方向。

Exercise 8.9

def branch(surface, length, start, direction, angle_deg, perlin1, perlin2):
# 基底情況
if length < 2:
return

vec = direction.copy()

# 線段終點位置
vec.scale_to_length(length)
end = start + vec

# 畫出線段
pygame.draw.line(surface, (0, 0, 0), start, end)

# 分支長度
length *= 0.67

# 分支方向
perlin1 += 0.011
perlin2 += 0.013
angle_right = angle_deg + 10*noise.pnoise1(perlin1)
angle_left = -angle_deg + 10*noise.pnoise1(perlin2)
direction_right = vec.rotate(angle_right)
direction_left = vec.rotate(angle_left)

branch(surface, length, end, direction_right, angle_right, perlin1, perlin2)
branch(surface, length, end, direction_left, angle_left, perlin1, perlin2)


# python version 3.10.9
import sys

import noise # version 1.2.2
import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 8.9")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 320
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

length = 100
start = pygame.Vector2(WIDTH/2, HEIGHT)
direction = pygame.Vector2(0, -1)
angle_deg = 30

# 取Perlin noise值之引數
perlin1, perlin2 = 0, 100

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

perlin1 += 0.011
perlin2 += 0.013
branch(screen, length, start, direction, angle_deg, perlin1, perlin2)

pygame.display.update()
frame_rate.tick(FPS)

Exercise 8.10


留言
avatar-img
ysf的沙龍
34會員
168內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2025/06/23
Koch曲線(Koch curve),碎形界另一個名氣響噹噹的圖案,是由瑞典數學家Helge von Koch在1904年所提出的。Koch曲線的製作規則非常簡單,就是把一段線段三等分,然後以中間那等分為底部畫出正三角形,接著把底部挖掉。不斷針對新形成的線段重複同樣的動作,就可以產生Koch曲線了。
Thumbnail
2025/06/23
Koch曲線(Koch curve),碎形界另一個名氣響噹噹的圖案,是由瑞典數學家Helge von Koch在1904年所提出的。Koch曲線的製作規則非常簡單,就是把一段線段三等分,然後以中間那等分為底部畫出正三角形,接著把底部挖掉。不斷針對新形成的線段重複同樣的動作,就可以產生Koch曲線了。
Thumbnail
2025/06/16
除了自我相似性之外,碎形的另一個基本組成部分是遞迴(recursion)。遞迴指的是,利用稱為製作規則(production rule)的同一套規則,不斷地進行迭代,而且每次迭代時,都會把上一次迭代的結果,作為這次迭代的起點。
Thumbnail
2025/06/16
除了自我相似性之外,碎形的另一個基本組成部分是遞迴(recursion)。遞迴指的是,利用稱為製作規則(production rule)的同一套規則,不斷地進行迭代,而且每次迭代時,都會把上一次迭代的結果,作為這次迭代的起點。
Thumbnail
2025/06/09
「fractal」這個字,有人翻譯成「碎形」,也有人翻譯成「分形」,是Benoit Mandelbrot在1975年,根據拉丁文中含有「零碎」、「破裂」意思的「fractus」這個字所造出來的。
Thumbnail
2025/06/09
「fractal」這個字,有人翻譯成「碎形」,也有人翻譯成「分形」,是Benoit Mandelbrot在1975年,根據拉丁文中含有「零碎」、「破裂」意思的「fractus」這個字所造出來的。
Thumbnail
看更多
你可能也想看
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
在模擬自然界中的事物時導入隨機性,可以讓結果看起來比較自然,但如果導入的隨機性都是uniform distribution,那未免也太呆板了。這時候,我們需要nonuniform distribution亂數,來讓模擬出來的結果,更像真的一樣。
Thumbnail
在模擬自然界中的事物時導入隨機性,可以讓結果看起來比較自然,但如果導入的隨機性都是uniform distribution,那未免也太呆板了。這時候,我們需要nonuniform distribution亂數,來讓模擬出來的結果,更像真的一樣。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
Thumbnail
本文介紹如何用Python繪製散布圖與迴歸線
Thumbnail
本文介紹如何用Python繪製散布圖與迴歸線
Thumbnail
本文在介紹如何用Python繪製各點大小不同的散布圖及用箭頭標註特殊點
Thumbnail
本文在介紹如何用Python繪製各點大小不同的散布圖及用箭頭標註特殊點
Thumbnail
這篇文章介紹如何使用Python整理資料成百分比資料以及繪製百分比堆疊直條圖。
Thumbnail
這篇文章介紹如何使用Python整理資料成百分比資料以及繪製百分比堆疊直條圖。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
如何用Python繪製堆疊直條圖(以2022年直轄市市長選舉政治獻金為例)
Thumbnail
如何用Python繪製堆疊直條圖(以2022年直轄市市長選舉政治獻金為例)
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News