座標変換は実際には、相当な数学的難問で、高校数学程度の知識(行列や三角関数など)が必要になりますが、p5.jsでは専用の関数が用意されているので、それらを適切に呼び出すだけで実現できます。とは言え、その舞台裏で行われていることを分かっておくことも重要で、本稿では、変換の中で最も単純な移動を見ていきます。
次のコードを実行すると、下図の結果が表示されます(グリッドの作成コードはのぞく)。
// 赤の矩形
fill(255, 0, 0);
rect(20, 20, 20, 40);
// 座標を右に40、下に20移す
translate(40, 20);
// 緑の矩形
fill(0, 255, 0);
rect(20, 20, 20, 40);
赤の矩形も緑も矩形も、同じ(20,20)を左上隅とする幅20、高さ40の矩形なのですが、translate(40, 20)によって、座標が右に40、下に20ずれたことで、緑の矩形は赤の矩形の右下に描画されます。これが移動の座標変換です。
座標変換は行列同士の掛け算で表されます。2行2列同士の行列の掛け算は次の要領で計算します。
「行列の積の計算方法と例題」
点(x, y)を、X軸方向にTx、Y軸方向にTyだけ移動する計算は次の行列の掛け算で行います。
行列の扱いが得意なTensorFlow.jsという機械学習用のライブラリを使って、計算してみます。
// 平行移動行列(translate)
const Tx = 40;
const Ty = 20;
// 平行移動の変換行列
const TM = tf.tensor2d(
[1, 0, Tx,
0, 1, Ty,
0, 0, 1
], [3, 3]);
TM.print();
const x = 20;
const y = 20;
const m = tf.tensor2d([x, y, 1], [3, 1]);
m.print();
TM.matMul(m).print();
コンソールには次の結果が出力されます。
上から1つめは、平行移動の変換を行いたいときに、掛け算の左に置く行列です。変数Txに40、Tyに20が代入されているので、(40, 20)だけ座標が平行移動することになります。2つめの出力は、変換される対象の行列で、(20,20)です。赤と緑の矩形の左上隅となる座標です。そして3つめが変換後の行列です。(60,40)という結果は、緑の矩形が実際に描かれた左上隅の座標と一致します。
目次
なぜ行列を使うのか?
行列なんて忘れてしまった、という方は、p5.jsを使う限り、行列の計算で、何と何を掛けて足すのかを覚える必要はありません。行列は単に、変換の複雑な計算を簡単に見せるために使われているだけです。
位置の移動
現在のマウスカーソルの位置に矩形を描画するサンプルです。
function setup() {
createCanvas(120, 120);
background(204);
strokeWeight(2);
fill(255, 0, 0)
}
function draw() {
// 現在のカーソル位置にキャンバスの原点(0,0)を移動する
translate(mouseX, mouseY);
// 現在のキャンバスの原点を左上隅とする矩形を描く
rect(0, 0, 30, 30)
}
translate()関数は、キャンバスの座標(0,0)を、(mouseX, mouseY)に設定します。draw()関数は繰り返し呼び出され、そのたびにrect()関数は新しい原点(0, 0)に矩形を描画します。
translate()
説明
表示ウィンドウ内でオブジェクトを移動させる量を指定する。xパラメータは左右方向の移動量を、yパラメータは上下方向の移動量を指定する。
変換は累積的に作用する。その後に発生するすべてのものに適用され、関数への以降の呼び出しによって効果が蓄積される。たとえばtranslate(50, 0)を呼び出した後、translate(20, 0)を呼び出すことは、translate(70, 0)と同じ。draw()内でtranslate()を呼び出すと、変換は、次のループが始まるときにリセットされる。この関数は、push()とpop()関数の使用によってさらに制御することができる。
シンタックス
translate(x, y, [z])
translate(vector)
複数のtranslate()
作成された変換は、以降のすべての描画関数に適用されます。1つめの矩形はマウスカーソルで制御し、2つめの矩形は(35,10)だけ移動させようと考えると、次のようなコードが書けそうです。
function draw() {
translate(mouseX, mouseY);
rect(0, 0, 30, 30);
translate(35, 10);
rect(0, 0, 15, 15)
}
しかし、複数のtranslate()関数からの複数の値はいっしょに追加されます。2つめの小さい矩形はじっととしていず、マウスの動きに合わせて、mouseX + 35とmouseY + 10だけ移動します。そして変換は、次のdraw()関数が始まるときに、リセットされます。