iT邦幫忙

2024 iThome 鐵人賽

DAY 10
0
Modern Web

p5.js 的環形藝術系列 第 10

[Day 10] p5.js 基礎教學(四) –– push 與 pop 函數

  • 分享至 

  • xImage
  •  

根據上一篇教學 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 * PI1/4 * PI2/4 * PI3/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();
    }
}

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


上一篇
[Day 09] p5.js 實戰演練(三) –– 疊合公轉軸(三)
下一篇
[Day 11] p5.js 基礎教學(五) –– p5.js 的顏色系統
系列文
p5.js 的環形藝術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言