根據前三篇文章:

p5.js 基礎教學(一) –– 座標與旋轉圖案
p5.js 基礎教學(二) –– rotate 和 translate
p5.js 基礎教學(三) —- 繪製座標格線

我們逐步的探討如何讓一個形狀,對某一個中心點做一個圓周運動,不管是使用 旋轉矩陣 的數學方法,或是使用 rotatetranslate 的 p5js 函數,以及這些方法如何影響 p5js 的座標系統。

現在我們要以這些方法為基底,進行更近一步的創作,對一個目標點 a 繞圈圈,只是讓物體加上一個公轉軸,進行圓周運動。

那如果我讓這個目標點 a,也加上一個公轉軸,對目標點 aa 同時進行圓周運動。

甚至是讓目標點 aa,同時也對一個新的目標點 aaa 進行圓周運動,這樣的公轉軸疊合,會造成什麼樣的結果呢?

就像是月球繞著地球,地球繞著太陽,整個太陽系又繞著銀河系的中心進行公轉,結果可能會非常有趣,讓我們來試試看吧!

往上疊加一個公轉軸(旋轉矩陣版本)

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

function draw() { background(100, 100); translate(final_x, final_y); rotate(frameCount/60/3 2 PI); circle(100, 0, 20); circle(-100, 0, 20); circle(0, 100, 20); circle(0, -100, 20); }

我們稍微修改一下 p5.js 基礎教學(二) —- rotate 和 translate 的作品,現在四個 circle 都改以畫布的中心點繞圈圈,並且每三秒順時針繞一圈。

frameCount 預設增上速度為每秒 60,frameCount/60/3 2 PI 這個算式要求 frameCount 為 180,也就是三秒才會讓結果為 2 * PI,也就是繞行一圈的弧度。

我們希望讓這個畫布的中心點,也能以某個目標點進行圓周運動,我們可以這樣改:

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

function vector_rotate(x, y, angle) { return [cos(angle)x-sin(angle)y,sin(angle)x+cos(angle)y]; }

function draw() { background(100, 100); translate(width/2, height/2); let [final_x, final_y] = vector_rotate(200, 0, -frameCount/60/6 2 PI); // 第二公轉軸 translate(final_x, final_y); rotate(frameCount/60/3 2 PI); // 第一公轉軸 circle(100, 0, 20); circle(-100, 0, 20); circle(0, 100, 20); circle(0, -100, 20); }

現在我們讓小公轉軸維持三秒順時針繞一圈,讓疊加的大公轉軸設為六秒逆時針一圈(-frameCount/60/6 <em> 2 </em> PI),可以得到下面的結果:

Imgur

可以看到我們的大公轉軸是用 旋轉矩陣 來實現的,先用旋轉矩陣來計算小公轉軸心隨時間移動的位置,再用 translate 函數移動到該軸心位置並繪製小公轉軸的四個 circle。

往上疊加一個公轉軸(rotate 函數版本)

若我們想要更簡明一點,都用 rotatetranslate 函數來繪製大公轉軸與小公轉軸也是能辦到的:

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

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

這裡要注意的是當我們在定位小公轉軸的軸心時,用了 rotate(-frameCount/60/6 <em> 2 </em> PI); 對座標系統進行旋轉,但我們最終只是要知道小公轉軸的軸心在大公轉圓周的什麼方位,因此我們要用 rotate(frameCount/60/6 <em> 2 </em> PI);,反向校正座標系統,把他轉回來。

若不這樣做,就會影響我們對小公轉軸繪製細節的角度計算,circle 所在的角度會由步驟一和步驟二的 rotate 函數所疊加,但我們實際上只需要步驟二的 rotate 函數計算出來的角度,因此要校正步驟一 rotate 函數帶來的影響。

觀察移動軌跡

然後有趣的是,我們可以把 background(100, 100); 改成 background(100, 10);,這樣我們可以看到 circle 在移動時留下來的軌跡。

Imgur

原先的例子,大公轉軸(逆時針)和小公轉軸(順時針)的旋轉方向是不一樣的,若我們將旋轉方向統一(順時針),來看看會發生什麼事?

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

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

Imgur

新 pattern 的尖角方向,從原本的外側改到內側,和原本的星星 pattern 呈現完全不一樣的風格,非常有趣!

我們能常理推斷大公轉軸和小公轉軸的旋轉速率必須是一個簡單倍數關係,我們才可以生成一個穩定的運行軌跡(每個移動的 circle,會在很短的時間內回到起點的位置)。

比如說我們原本的範例,小公轉軸的旋轉速度是大公轉軸的 2 倍,所以在大公轉軸繞行一圈的那個瞬間,小公轉軸正好繞行兩圈,circle 確實能夠回到原本的起點。

若將兩者的速率調成一個互質的比例(5:3)會發生什麼事呢?

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

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

Imgur

circle 回到起點的時間點,就會和 3、5 的公倍數相關,也就是在大公轉軸正好繞行 3 圈,小公轉軸正好繞行 5 圈的那個時間點,circle 回到了最原先的起點。

另外我們也可以調整公轉軸的旋轉半徑,來得到不一樣的效果,剩下的就讓大家自己去玩玩看吧!