根據上一篇教學 p5.js 實戰演練(三) –– 疊合公轉軸(三),我們成功的疊合了兩個公轉軸,並讓其中 16 個 circle 照著我們期待的方式轉動。
但是校正座標系統實在是太麻煩了,我們寫了很多邏輯重複但無法直接複製的 code,若我們還需要疊加第三個或是第四個公轉軸,那校正行為不僅不好開發,也很難維護。
幸好 p5.js 提供我們兩個非常好用的函數 push
和 pop
,這兩個函數可以幫我們儲存和還原座標系統的轉換,這樣之後我們要校正先前的轉換,就只要用 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();
}
看看現在的程式多麼簡潔,原本要用四行程式進行校正,而且還要根據原本的轉換進行客製化的校正,現在只需要統一使用 push
和 pop
函數就可以解決了。
然後把這兩個函數套在我們的作品上:
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 的座標轉換
}
動畫呈現就跟我們預期的一樣,而且邏輯也統一了許多,所以我們現在還能把這段程式改成 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();
}
}
現在我們跑了 4 次 for 迴圈,每次分別生成一個小公轉軸,在這個邏輯裡,我們要用 translate
函數將小公轉軸平均分佈在四個方位上,也就是 0/4 * PI
、1/4 * PI
、2/4 * PI
、3/4 * PI
,所以我們才會呼叫 translate(200 * sin(i / 4 * 2 * PI), 200 * cos(i / 4 * 2 * 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();
}
}
現在我們已經可以用一個變數 inner_orbit_num
來決定我們要生成幾個小公轉軸了,這一切都歸功於 push
和 pop
有效的化簡了我們的程式邏輯。
然後我們還能對 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();
}
}
當然這段程式還有好多可再化簡的空間,但這系列文章已沒有足夠篇幅更深入的闡述其中細節,就請有興趣的讀者自己試試看了,下面附上作者的其中一個作品,下面一共疊了四層公轉軸:
作品網址:
https://openprocessing.org/sketch/2312163