根據上一篇教學 p5.js 實戰演練(三) –– 疊合公轉軸(三),我們成功的疊合了兩個公轉軸,並讓其中 16 個 circle 照著我們期待的方式轉動。

但是校正座標系統實在是太麻煩了,我們寫了很多邏輯重複但無法直接複製的 code,若我們還需要疊加第三個或是第四個公轉軸,那校正行為不僅不好開發,也很難維護。

幸好 p5.js 提供我們兩個非常好用的函數 pushpop,這兩個函數可以幫我們儲存和還原座標系統的轉換,這樣之後我們要校正先前的轉換,就只要用 pop,也就是還原他就好。

先來上一個簡單的範例:

function setup() {
	createCanvas(windowWidth, windowHeight);
	background(100);
}

var gridSpacing = 50; // 設定每 50 px 就畫一條格線

function createGrid() { for (var x = -width; x < width; x += gridSpacing) { for (var y = -height; y < height; y += gridSpacing) { stroke(200); // 設定線的顏色為灰色 strokeWeight(1); // 設定線的寬度為1 line(x, -height, x, height); // 畫出垂直線 line(-width, y, width, y); // 畫出水平線 } } }

function draw() { background(100, 100); translate(width/2, height/2); var outer_orbit_speed = -frameCount/60/6 2 PI; var inner_orbit_speed = frameCount/60/3 2 PI; push(); // 步驟 1-1:定位小公轉軸 1 的軸心 rotate(outer_orbit_speed); translate(200, 0); rotate(-outer_orbit_speed); // 校正 rotate 函數對座標系統的轉動 // 步驟 1-2:繪製沿著小公轉軸 1 繞行的 4 個 circle rotate(inner_orbit_speed); circle(100, 0, 20); circle(-100, 0, 20); circle(0, 100, 20); circle(0, -100, 20); pop(); // 繪製格線 createGrid(); }

Imgur

看看現在的程式多麼簡潔,原本要用四行程式進行校正,而且還要根據原本的轉換進行客製化的校正,現在只需要統一使用 pushpop 函數就可以解決了。

然後把這兩個函數套在我們的作品上:

function setup() {
	createCanvas(windowWidth, windowHeight);
	background(100);
}

function draw() { background(100, 100); translate(width/2, height/2); var outer_orbit_speed = -frameCount/60/6 2 PI; var inner_orbit_speed = frameCount/60/3 2 PI; push(); // 儲存小公轉軸 1 的座標轉換 // 步驟 1-1:定位小公轉軸 1 的軸心 rotate(outer_orbit_speed); translate(200, 0); rotate(-outer_orbit_speed); // 校正 rotate 函數對座標系統的轉動 // 步驟 1-2:繪製沿著小公轉軸 1 繞行的 4 個 circle rotate(inner_orbit_speed); circle(100, 0, 20); circle(-100, 0, 20); circle(0, 100, 20); circle(0, -100, 20); pop(); // 回復小公轉軸 1 的座標轉換 push(); // 儲存小公轉軸 2 的座標轉換 // 步驟 2-1:定位小公轉軸 2 的軸心 rotate(outer_orbit_speed); translate(0, 200); rotate(-outer_orbit_speed); // 校正 rotate 函數對座標系統的轉動 // 步驟 2-2:繪製沿著小公轉軸 2 繞行的 4 個 circle rotate(inner_orbit_speed); circle(100, 0, 20); circle(-100, 0, 20); circle(0, 100, 20); circle(0, -100, 20); pop(); // 回復小公轉軸 2 的座標轉換 push(); // 儲存小公轉軸 3 的座標轉換 // 步驟 3-1:定位小公轉軸 3 的軸心 rotate(outer_orbit_speed); translate(-200, 0); rotate(-outer_orbit_speed); // 校正 rotate 函數對座標系統的轉動 // 步驟 3-2:繪製沿著小公轉軸 3 繞行的 4 個 circle rotate(inner_orbit_speed); circle(100, 0, 20); circle(-100, 0, 20); circle(0, 100, 20); circle(0, -100, 20); pop(); // 回復小公轉軸 3 的座標轉換 push(); // 儲存小公轉軸 4 的座標轉換 // 步驟 4-1:定位小公轉軸 4 的軸心 rotate(outer_orbit_speed); translate(0, -200); rotate(-outer_orbit_speed); // 校正 rotate 函數對座標系統的轉動 // 步驟 4-2:繪製沿著小公轉軸 4 繞行的 4 個 circle rotate(inner_orbit_speed); circle(100, 0, 20); circle(-100, 0, 20); circle(0, 100, 20); circle(0, -100, 20); pop(); // 回復小公轉軸 4 的座標轉換 }

Imgur

動畫呈現就跟我們預期的一樣,而且邏輯也統一了許多,所以我們現在還能把這段程式改成 for 迴圈的版本:

function setup() {
    createCanvas(windowWidth, windowHeight);
    background(100);
}

function draw() { background(100, 100); translate(width/2, height/2);

var outer_orbit_speed = -frameCount/60/6 2 PI; var inner_orbit_speed = frameCount/60/3 2 PI;

for (var i = 0; i < 4; i++) { push();

rotate(outer_orbit_speed); translate(200 sin(i / 4 2 PI), 200 cos(i / 4 2 PI)); rotate(-outer_orbit_speed); rotate(inner_orbit_speed); circle(100, 0, 20); circle(-100, 0, 20); circle(0, 100, 20); circle(0, -100, 20); pop(); } }

Imgur

現在我們跑了 4 次 for 迴圈,每次分別生成一個小公轉軸,在這個邏輯裡,我們要用 translate 函數將小公轉軸平均分佈在四個方位上,也就是 0/4 <em> PI1/4 </em> PI2/4 <em> PI3/4 </em> PI,所以我們才會呼叫 translate(200 <em> sin(i / 4 </em> 2 <em> PI), 200 </em> cos(i / 4 <em> 2 </em> PI));

所以如果我們要生成五個小公轉軸,也可以用類似的方式處理:

function setup() {
    createCanvas(windowWidth, windowHeight);
    background(100);
}

function draw() { background(100, 100); translate(width/2, height/2);

var outer_orbit_speed = -frameCount/60/6 2 PI; var inner_orbit_speed = frameCount/60/3 2 PI; var inner_orbit_num = 5;

for (var i = 0; i < inner_orbit_num; i++) { push();

rotate(outer_orbit_speed); translate(200 cos(i / inner_orbit_num 2 PI), 200 sin(i / inner_orbit_num 2 PI)); rotate(-outer_orbit_speed); rotate(inner_orbit_speed); circle(100, 0, 20); circle(-100, 0, 20); circle(0, 100, 20); circle(0, -100, 20); pop(); } }

Imgur

現在我們已經可以用一個變數 inner_orbit_num 來決定我們要生成幾個小公轉軸了,這一切都歸功於 pushpop 有效的化簡了我們的程式邏輯。

然後我們還能對 circle 的生成邏輯寫成 for 迴圈的版本,這樣我們也能指定一個小公轉軸內的 circle 數量了,下面是五個小公轉軸,且每個小公轉軸有 5 個 circle 的版本:

function setup() {
    createCanvas(windowWidth, windowHeight);
    background(100);
}

function draw() { background(100, 100); translate(width/2, height/2);

var outer_orbit_speed = -frameCount/60/6 2 PI; var inner_orbit_speed = frameCount/60/3 2 PI; var inner_orbit_num = 5; var circle_num = 5;

for (var i = 0; i < inner_orbit_num; i++) { push();

rotate(outer_orbit_speed); translate(200 cos(i / inner_orbit_num 2 PI), 200 sin(i / inner_orbit_num 2 PI)); rotate(-outer_orbit_speed); rotate(inner_orbit_speed); for (var j = 0; j < circle_num; j++) { circle(100 cos(j / circle_num 2 PI), 100 sin(j / circle_num 2 PI), 20); } pop(); } }

Imgur

當然這段程式還有好多可再化簡的空間,但這系列文章已沒有足夠篇幅更深入的闡述其中細節,就請有興趣的讀者自己試試看了,下面附上作者的其中一個作品,下面一共疊了四層公轉軸:

作品網址:
https://openprocessing.org/sketch/2312163

Imgur