在〈實作 L-system〉中談到,L-system 可以用來描述海龜繪圖,對 L-system 本身來說,文法規則越豐富,可表示的生態成長就越豐富,為了在使用 L-system 描述海龜時,行為上可以更豐富些,接下來要實作的 L-system 程式,將支援以下的指令:
F
:前進並畫線f
:前進不畫線+
:左轉-
:右轉|
:轉向(轉 180 度(degree))[
:將目前狀態置入堆疊]
:取出堆疊頂的狀態
先前文件中實作的 Turtle
,已經能支援以上的指令了,不過 p5.js 中旋轉之類的函式,可以支援徑度與角度,端看你的 angleMode
是指定 DEGREES
或 RADIANS
,為了便於實現轉向,這邊直接在 Turtle
實作個 reverse
:
class Turtle {
起始位置 (x, y) 與頭面向的角度
constructor(x = 0, y = 0, angle = 0) {
this.coordinateVector = createVector(x, y);
this.headingVector = createVector(1, 0).rotate(angle);
this.state = [];
}
...略
轉向
reverse() {
this.headingVector.mult(-1);
}
}
採用向量計算的話,就不用理會目前 angleMode
是指定 DEGREES
或 RADIANS
了,直接乘上 -1,向量就是轉向了。
接著,基於 Turtle
以及〈實作 L-system〉的 lsystem
寫個 lsystemTurtle
,將海龜繪圖與 L-system 結合在一起:
// length 是每次前進的長度
// angle 是每次轉的角度
// forwardSymbols 指定了也用來代表 F 的符號
function lsystemTurtle(axiom, rules, length, angle, n, forwardSymbols = '') {
let symbols = Array.from(lsystem(axiom, rules, n));
if(forwardSymbols.length > 0) {
symbols = symbols.map(symbol => forwardSymbols.includes(symbol) ? 'F' : symbol);
}
const t = new Turtle();
const lines = [];
for(let i = 0; i < symbols.length; i++) {
switch(symbols[i]) {
case 'F': // 前進並畫線
lines.push(t.forward(length)); break;
case 'f': // 前進不畫線
t.forward(length); break;
case '+': // 左轉
t.turn(-angle); break;
case '-': // 右轉
t.turn(angle); break;
case '|': // 轉向
t.reverse(); break;
case '[': // 將目前狀態置入堆疊
t.push(); break;
case ']': // 取出堆疊頂的狀態
t.pop(); break;
}
}
return lines;
}
那麼〈實作 L-system〉中談到的樹生長:
- 變數:
X
- 常數:
F[]+-
- 公理:
X
- 規則:
X → F[+X][-X]
就可以畫出來了:
如果是這個 L-system 的話:
- 變數:
X
- 常數:
F[]+-
- 公理:
X
- 規則:
X → F[+X][-X]
、F → FF
可以生成另一種風格的樹:
來點有趣的動畫吧!以下是厥葉曲線,然而以動畫示範了整個繪圖的過程:
還能有哪些有趣的 L-system 可以與海龜繪圖結合呢?lsystems.js 我收集了一些(這邊沒有使用 p5.js-widget,因為它載入太長的程式碼會有問題):
令人驚訝吧!目前海龜可接受的指令並不多,卻已經可以變化出這麼多的圖案,你可以嘗試自訂更多的指令,或者是加入一些隨機性,例如,在某個機率以上,才選擇某個生成規則進行生成,構成某種形式的隨機 L-system(Stochastic L-system),這就讓你自己來嘗試了。