在〈海龜也懂碎形〉畫了樹,願意的話,你也可以用海龜來畫〈科赫曲線〉、〈蕨葉曲線〉、〈雪花曲線〉、〈龍形曲線〉、〈希爾伯特曲線〉、〈十字繡曲線〉等碎形曲線。
在實現這類碎形曲線的過程中,你可能會產生一個想法,這些曲線實際上都是以一系列前進、轉彎的指令,命令海龜做出相對應的動作,有沒有辦法將程式通用化,單純地餵一串指令給他們就好?
例如〈海龜也懂碎形〉中的樹,想畫個 Y,就是前進、置入、右轉、[前進]、取出、置入、左轉、[前進]、取出,如果每個分支也想畫個 Y,就是將 [前進] 再生成為前進、置入、右轉、[前進]、取出、置入、左轉、[前進]、取出…如此不斷生成下去,最後海龜只要看這串指令,就可以畫出一棵樹。
這樣的想法是對的,為了便於撰寫與生成指令,可以用符號來表示前進、置入、轉彎、取出等基本動作,先定義如下:
F
:前進並畫線+
:左轉-
:右轉[
:將目前狀態置入堆疊]
:取出堆疊頂的狀態
方才的指令就可以簡化為:
- 初始:
X
- 規則:
X → F[+X][-X]
最先會從 X
開始,X
生成一次就是 F[+X][-X]
,第二次生成 X
就是 F[+F[+X][-X]][-F[+X][-X]]
,第三次生成 X
就是 F[+F[+F[+X][-X]][-F[+X][-X]]][-F[+F[+X][-X]][-F[+X][-X]]]
…就看你要生成幾次,假設只生成三次,若 X
本身也視為前進指令,就相當於 F[+F[+F[+F][-F]][-F[+F][-F]]][-F[+F[+F][-F]][-F[+F][-F]]]
。
這樣的標記方式,其實是 Lindenmaye system,簡稱 L-system,是 1968年由荷蘭生物學和植物學家 Aristid Lindenmayer 提出,有關生長發展中的細胞交互作用的數學模型,方才談及的過程,實際上是在規範一種語言的文法,也就是 G = (V, ω, P)。
V 是符號集合,包含可被生成的變數以及不能被生成的常數;ω 是初始符號集合或稱為公理(axiom),為文法之源,P 是產生規則。
就方才的樹生長來說,依變數、常數、公理、規則,就可以如下表示:
- 變數:
X
- 常數:
F[]+-
- 公理:
X
- 規則:
X → F[+X][-X]
由於生成指令的過程,不過就是一連串規律的符號取代過程,透過程式實現並不困難,如果預計使用以下的方式來產生最後的指令:
const instructions = lsystem(
'X', // 公理
{ // 規則
'X': 'F[+X][-X]'
},
2 // 生成次數
);
若最後結果想產生 'F[+F[+X][-X]][-F[+X][-X]]'
,那麼可以實作出以下的 lsystem
函式:
function lsystem(axiom, rules, n) {
// 若變數符合規則,進行生成,否則直接傳回變數
function produceOne(variable, rules) {
if(rules[variable]) {
return Array.from(rules[variable]);
}
return [variable];
}
// 依指定次數生成
function produceAll(axiom, rules, n) {
let symbols = Array.from(axiom); // 為了利用 Array 的 flatMap 方法
for(let i = 0; i < n; i++) {
symbols = symbols.flatMap(symbol => produceOne(symbol, rules));
}
return symbols;
}
return produceAll(axiom, rules, n).join('');
}
如果你有多個規則的話,指定方式就是:
const instructions = lsystem(
'X', // 公理
{ // 規則
'X': 'F[+X][-X]',
'F': 'FF'
},
3 // 生成次數
);
如上撰寫程式的話,會生成 'FFFF[+FF[+F[+X][-X]][-F[+X][-X]]][-FF[+F[+X][-X]][-F[+X][-X]]]'
。
必須釐清的是,雖然這邊以海龜繪圖來說明,不過 L-system 並不是為了海龜繪圖而生,端看你賦予符號的意義,例如,若是以下的 L-system:
- 變數:
AB
- 常數:無
- 公理:
A
- 規則:
A → ABA
、B → BBB
A
若代表前進畫線、B
代表前進不畫線,第一條公理代表的是一直線:
生成一次的話就是 ABA
,畫出來的話就是:
在這邊並沒有定義直線的長度,ABA
純綷就是表示畫線的規律罷了,生成三次的話就是 ABA
,畫出來的話就是 ABABBBABA
,畫線的規律就是:
記得,這邊並沒有定義直線的長度,ABA
純綷就是表示畫線的規律罷了,這邊只不過特意安排了線的長度,每一次生成的集合稱為康托爾集(Cantor set),如果無限次生成,會是個無限大的集合,然而如果將之畫出,其實什麼都沒有,因為就整個集合畫出的圖來看,每個線段相當於無限小了。
簡單來說,L-system 只是在表示某個規律,怎麼在圖案上呈現是另一回事,例如,若特意安排線的長度,每次生成的康托爾集特意排列,會形成有趣的圖案:
如果不是畫在平面呢?維基的康托爾集(Cantor set)頁面中,就有這麼一張圖:
當然,L-system 可以用來描述海龜繪圖,這是下篇文件要談的內容了…