iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0
Modern Web

從新開始學習p5.js畫出一片天系列 第 27

D27_Canvas內容的座標與向量操作

  • 分享至 

  • xImage
  •  

Canvas內容的座標與向量操作

今天來整理在p5.js中有關座標的操作
我們先看一下基本的rect(x, y, w, h)的指令
如果想要執行「平移,旋轉,縮放」的操作的話,就要使用transform的操作
translate(tx, ty); 原本的功能是將canvas原點移到這個位置,不過可以記成將元件平移到這個位置
rotate(sd); scale(rx, ry);
這3個指令執移完,再來執行要呈現的元件。

在p5.js的transform的概念中,translate(tx, ty); rotate(sd); scale(rx, ry); 這3個指令像是在轉移座標軸系統的設定,像是將座標軸原點平移,再旋轉座標軸,以及縮放座標軸,然後再以這個座標軸系統去畫圖。
不過一旦使用了這3個指令,後續的畫圖都會受影響,而且指令之間,是會接續作用的,
像是 translate(100, 100); translate(100, 100); 執行2次其結果就等同於 translate(200, 200);
因此,一般會加上 push() 將原本的座標軸系統暫存,pop() 回復原本的座標軸系統
程式碼如下

push();
translate(100, 100);
rotate(radians(45));
scale(1.5);
rect(-50, -50, 100, 100); 
ellipse(0, 0, 6, 6);
pop();

其中rect(x, y, w, h)可以視為是在自身的原點畫圖,因為rect的基準點在左上角,要將基準點移到中心點的話,就要寫成 rect(-w/2, -h/2, w, h); 也就是 rect(-50, -50, 100, 100);,另外 ellipse(0, 0, 6, 6);是用來指出座標軸系統的原點位置

在p5.js提供了一些方便調整基準點的方法

圓形 ellipse

圓形的部份可以使用這個指令 ellipseMode(CENTER),預設值是中心點
可以設定的項目有 CENTER, RADIUS, CORNER, CORNERS
影響的繪圖指令有 ellipse(), circle(), arc()

組合的結果為
ellipseMode(CENTER);
ellipse(x, y, w, h); //-- 基準點在中心點, w, h分別為寬直徑,高直徑

ellipseMode(RADIUS); //-- 基準點在中心點, w, h分別為寬半徑,高半徑
ellipse(x, y, w, h);

ellipseMode(CORNER);
ellipse(x, y, w, h); //-- 基準點在左上角, w, h分別為寬直徑,高直徑

ellipseMode(CORNERS);
ellipse(x1, y1, x2, y2); //-- 基準點在左上角,以左上,右下的座標作為畫圓形大小的參數

方形 rect

方形的部份可以使用這個指令 rectMode(CORNER),預設值是左上角
可以設定的項目有 CENTER, RADIUS, CORNER, CORNERS
操作方式與圓形相同

圖像 image

圖像的部份可以使用這個指令 imageMode(CORNER),預設值是左上角
可以設定的項目有 CENTER, CORNER, CORNERS
操作方式與圓形相同

文字 text

文字的部份, 可以使用這個文字對齊的指令
textAlign(CENTER, CENTER)
水平對齊的項目有(LEFT, CENTER, RIGHT)
垂直對齊的項目有(TOP, BOTTOM, CENTER, BASELINE)
預設的對齊位置在左下角,textAlign(LEFT, BASELINE)

角度單位

在執行旋轉操作時,需要設定角度,可以用這個指令設定角度單位
angleMode(RADIANS),預設值為RADIANS
可以設定的項目有 RADIANS, DEGREES

角度的常數
QUARTER_PI -> 45度
HALF_PI -> 90度
PI -> 180度
TWO_PI -> 360度
TAU -> TWO_PI -> 360度

顏色模式

在設定顏色的時候可以使用這個指令 colorMode() 來設定
設定的項目有 RGB, HSB
colorMode(RGB,255);
Red: 0 ~ 255
Green: 0 ~ 255
Blue: 0 ~ 255
Alpha: 0 ~ 255

colorMode(HSB, 360, 100, 100, 1);
Hue: 0 ~ 360
Saturation: 0 ~ 100
Brightness: 0 ~ 100
Alpha: 0.0 ~ 1.0

自轉公轉的操作練習

let can;
let sd = 0;
let px = 0.0;
let py = 0.0;
let rt = 2.0;
function setup() {
	can = createCanvas(windowWidth, windowHeight);
	can.id("can1");
	background("lightyellow");

	rectMode(CENTER);
	angleMode(DEGREES);
	textAlign(CENTER, CENTER);
	textSize(32);
}

function draw(){
    background("lightyellow");

	translate(width/2, height/2); //-- 先將原點移至畫面中心點

	push();
	rotate(-sd); //-- 負角度為逆時針方向
	rect(0, 0, 100, 100);
	pop();
    
	let n = 10;
	for(let i=0; i<n; i++){
		push();
		//-- 設定公轉半徑及轉動角度
        //-- sd是轉動角度,(360/n*i) 是 n個方形 各個偏移角度
		px = 200*cos(sd+360/n*i);
		py = 200*sin(sd+360/n*i);
		translate(px, py); //-- 平移中心點的座標
		scale(rt);  //-- 縮放大小
		push();   //-- push()可以是巢狀設定,只有方形自轉
		  rotate(sd*5);  //-- 自轉的轉動角度,正角度為順時針方向
		  fill("lightblue");
		  rect(0, 0, 50, 50);
		pop();

		fill(0);
		text((i+1), 0, 0); //-- 文字不自轉
		pop();
	}

	sd += 1;
	rt = 2.0+1.0*sin(sd);
}

自轉公轉的操作練習

座標系的表示法

座標系有分直角座標系(x, y) 與極座標系(r, s)
x, y 為 x軸,y軸, x: 正值向右,負值向左,y: 正值向下,負值向上
r 為與原點的長度,
s 為與 正x軸 的角度,正角度為順時針方向,負角度為逆時針方向

因此,程式上的座標y方向與數學的座標y方向,剛好是相反的
正角度的旋轉是順時針向下方向,負角度的旋轉是逆時針向上方向

在處理角度的操作時要特別注意
像是(x, y)->(r, s),其中s的範圍是 -180度 ~ 180度
0度 -> (1, 0)
90度 -> (0, 1)
180度 -> (-1, 0)
-90度 -> (0, -1)
-180度 -> (-1, 0)

如果想要將角度由 -180 ~ 180 轉換成 0 ~ 360
要先切成2段來轉換 -180 ~ 0 -> 180 ~ 360
p5.js有提供角度轉換指令

let s = atan2(y, x);

console.log(atan2(10, 10));  //-- 45度
console.log(atan2(10, -10));  //-- 135度
console.log(atan2(-10, -10));  //-- -135度
console.log(atan2(-10, 10));  //-- -45度

根據滑鼠位置取得(r, s)

function draw(){
    background("lightyellow");

    translate(width/2, height/2); //-- 先將原點移至畫面中心點
    fill(0, 100);
    ellipse(0, 0, 10, 10);
    text("原點", 0, -30);

    let mx = mouseX-width/2;  //-- 將滑鼠座標減掉中心點
    let my = mouseY-height/2;

    fill("red");
	line(mx, my, 0, 0);
	ellipse(mx, my, 10, 10);
	let s = atan2(my, mx); //-- 計算角度
	let s1 = (s<0)? map(s, -180, 0, 180, 360) : s; 
    text("角度:"+int(s1), mx, my-30);
    let r = dist(mx, my, 0, 0); //-- 計算長度
    text("長度:"+int(r), mx/2, my/2-30);
}

座標系的表示法
座標系的表示法

向量的操作

在p5.js中提供了一組方便好用的向量指令,
首先要先建立 向量物件
createVector(x, y); 其中 x, y 是以原點為基準

一般來說,除了當作向量x,y數值的處理外,
善用向量指令可以讓程式碼更簡潔

let v1 = createVector(width/2, height/2); //-- 產生向量物件 Vector

向量物件

最常見的向量操作有 座標的轉換,向量的平移,旋轉,縮放,相加,相減

let v1 = createVector(width/2, height/2);
console.log(v1);

let v0 = createVector(width/2, 0);   //-- 此為基準向量
let angle = v0.angleBetween(v1);  //-- v0為基準向量,v1為要被計算的向量
let ds = v0.dist(v1);  //-- v0為基準向量,v1為要被計算的向量

console.log(v1.mag().toFixed(2)); //-- 長度:690.23
console.log(v1.heading().toFixed(2));  //-- 角度:47.76度
console.log(angle.toFixed(2));  //-- 角度:47.76度
console.log(ds.toFixed(2));  //-- 角度:47.76度

v1.mag():取得向量的長度
v1.heading():取得向量的角度
v0.angleBetween(v1):取得2向量的角度
v0.dist(v1):取得2向量的距離

找出與此向量v1垂直的向量v2

let v0, v1, v2, v3, v4;
v0 = createVector(200, 0);
v1 = v0.copy();
v2 = v0.copy().rotate(90);
v3 = v0.rotate(1);
//v4 = p5.Vector.fromAngle(PI/4, 200); 
v4 = p5.Vector.fromAngle(radians(45), 200); //-- 用角度來建立向量

line(v1.x, v1.y, 0, 0);
text("基準向量", v1.x, v1.y-30);
line(v2.x, v2.y, 0, 0);
text("垂直向量", v2.x, v2.y-30);
line(v3.x, v3.y, 0, 0);
text("旋轉向量", v3.x, v3.y-30);
line(v4.x, v4.y, 0, 0);
text("角度向量", v4.x, v4.y-30);

其中 v0.copy() 為複製一個獨立的向量
如果寫成 v2 = v0.rotate(90);
當 執行 v0.rotate(1); 時,v2也會被連動到

v2 = v0.copy().rotate(90); 才會得到固定得垂直向量
垂直向量

建立向量除了用createVector(x, y)之後
也可以用角度來建立向量
p5.Vector.fromAngle(s, r); s:角度,r:長度
其中角度固定要用弳度,不受angleMode()的影響

v4 = p5.Vector.fromAngle(PI/4, 200);
v4 = p5.Vector.fromAngle(radians(45), 200);

建立向量物件後,要設定向量的數值可以用set()

let v0 = createVector(200, 0);
v0.set(200, 200);

//-- 查看向量內容
console.log(v0.toString()); //-- p5.Vector Object : [200, 100, 0]
console.log(v0);  //-- 查看Vector物件內容

向量的縮放操作 mult()
let v0 = createVector(x, y);
v0.mult(n); --> (xn, yn)
v0.mult(a, b); --> (xa, yb)

let v0 = createVector(200, 100);
console.log(v0.toString()); //-- [200, 100, 0]

v0.mult(2);
console.log(v0.toString()); //-- [400, 200, 0]

v0.mult(2, 1.5);
console.log(v0.toString()); //-- [800, 300, 0]

向量的加減操作 add(), sub()
let v1 = createVector(x1, y1);
let v2 = createVector(x2, y2);
let v3 = v1.add(v2); --> (x1+x2, y1+y2)
//-------
let v1 = createVector(x1, y1);
let v2 = createVector(x2, y2);
let v4 = v1.sub(v2); --> (x1-x2, y1-y2)

let v1 = createVector(200, 100);  //-- [200, 100, 0]
let v2 = createVector(300, 200);   //-- [300, 200, 0]
let v3 = v1.add(v2);
console.log(v3.toString());  //-- [500, 300, 0]
let v4 = v1.sub(v2);
console.log(v4.toString());  //-- [200, 100, 0]

最後是自轉公轉的操作練習,以向量方法為例

let can;
let sd = 0;
let rt = 2.0;
let pv;
function setup() {
	can = createCanvas(windowWidth, windowHeight);
	can.id("can1");
	background("lightyellow");

	rectMode(CENTER);
	angleMode(DEGREES);
	textAlign(CENTER, CENTER);
	textSize(32);

	pv = createVector(200, 0); //-- 設定基準向量
}

function draw(){
	background("lightyellow");
	translate(width/2, height/2); //-- 先將原點移至畫面中心點

	push();
	rotate(-sd); //-- 負角度為逆時針方向
	rect(0, 0, 100, 100);
	pop();

	pv.rotate(1); //-- 旋轉1度
	let n = 10;
	for(let i=0; i<n; i++){
        //-- 以pv為基準,用rotate(360/n*i)旋轉每個方形 (有以下2種寫法)
		let pv2 = pv.copy().rotate(360/n*i); 
		//let pv2 = pv.rotate(360/n).copy(); 
        push();
		translate(pv2.x, pv2.y); //-- 平移到向量的端點
		scale(rt);  //-- 縮放大小
		push();
		  rotate(sd*5);  //-- 自轉的轉動角度,正角度為順時針方向
		  fill("lightblue");
		  rect(0, 0, 50, 50);
		pop();
		fill(0);
		text((i+1), 0, 0); //-- 文字不自轉
		pop();

	}

	sd += 1;
	rt = 2.0+1.0*sin(sd);

}

參考資料
translate()
https://p5js.org/reference/#/p5/translate
p5.Vector
https://p5js.org/reference/#/p5.Vector


上一篇
D26_物件點擊拖拉事件操作
下一篇
D28_UI介面元件操作
系列文
從新開始學習p5.js畫出一片天40
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言