趣味としてのクリエイティブ・コーディング:106:座標軸を回転(後編)
趣味としてのクリエイティブ・コーディング 座標編、前回は rotate() で座標軸を回転させられることを学びました。
同時に translate() を使うことで回転の中心位置を変えられることも学びました。
今回はその translate() と rotate() で回転を自在に操る方法を極めましょう!
前回の問題を例に考えてみよう
前回の最後の問題、下記のような図を作るにはどうしたらよいか?
これは、元の縦横に並べた四角形それぞれを 45度回転させればよさそうです。
元の図形とそのコード
function setup() {
createCanvas(640, 640);
colorMode(HSB, 360, 100, 100, 100);
rectMode(CENTER);
background(0, 0, 100, 100);
noLoop();
}
function draw() {
translate(width * 0.5, height * 0.5);
// 中央の四角
fill(0, 0, 80, 100);
rect(0, 0, 100, 100);
// 左右の四角
fill(0, 40, 80, 100);
rect(-100, 0, 100, 100);
fill(90, 40, 80, 100);
rect(100, 0, 100, 100);
// 上下の四角
fill(180, 40, 80, 100);
rect(0, -100, 100, 100);
fill(270, 40, 80, 100);
rect(0, 100, 100, 100);
}
それぞれの四角形を描く直前に rotate() を入れてみましょう。
translate(width * 0.5, height * 0.5);
// 中央の四角
fill(0, 0, 80, 100);
rotate(HALF_PI * 0.5)
rect(0, 0, 100, 100);
// 左右の四角
fill(0, 40, 80, 100);
rotate(HALF_PI * 0.5)
rect(-100, 0, 100, 100);
fill(90, 40, 80, 100);
rotate(HALF_PI * 0.5)
rect(100, 0, 100, 100);
// 上下の四角
fill(180, 40, 80, 100);
rotate(HALF_PI * 0.5)
rect(0, -100, 100, 100);
fill(270, 40, 80, 100);
rotate(HALF_PI * 0.5)
rect(0, 100, 100, 100);
…まあ、そりゃダメですよね。
rotate() はその時点の原点を中心として回転させます。今の原点は図の黒丸の場所なので、それぞれの四角形はここを中心として 45度ずつ回転する座標軸の上に描画されていきます。
四角形を 45度回転させているのではありませんよ。回転しているのは座標軸で、その回転した座標軸の上に四角形が描画されていくのです。
rotate() は『今の状態から何度回転させるか』なので、コードの中で rotate() が出てくる度に 45度ずつ回転していきます。
結果として、中心の灰色の四角は 45度回転、次に描画される赤い四角は 90度回転、次の緑の四角は 135度、水色は 180度、紫は 225度回転した位置に描かれます。
回転の中心をコントロール
各四角形をそれぞれの位置で 45度回転させたければ、rotate() で座標軸を回転させる前に translate() で座標の原点を各四角形の中心に移動させればいいですね。
ひとまず中心の灰色の四角と右側の赤い四角だけでやってみると…
translate(width * 0.5, height * 0.5);
// 中央の四角
fill(0, 0, 80, 100);
rotate(HALF_PI * 0.5)
rect(0, 0, 100, 100);
// 左右の四角
fill(0, 40, 80, 100);
translate(-100, 0);
rotate(HALF_PI * 0.5)
rect(0, 0, 100, 100);
はい、こうじゃありませんね。
何度も言うように1つ目の rotate() は中央の四角を回転させているわけではなく、座標軸を回転させてから中央の四角を描画しています。
その後 translate(-100, 0) でその座標軸上の x = -100, y = 0 に原点が移動し、2つ目の rotate() でさらに座標軸を回転させてから赤い四角を描画して、このようになります。
赤い四角形は本当はこの図の黒丸を原点として回転させたかったんですよね。
回転した座標上での黒丸の位置を割り出すことも可能ですが、それにはちょっと面倒な計算が必要です。
rotate() は『今の状態から何度回転させるか』という注意点、これを逆手に取っていちいち座標軸の回転をもとに戻してやることで面倒な計算をしなくて済むようになります。
translate(width * 0.5, height * 0.5);
// 中央の四角
fill(0, 0, 80, 100);
rotate(HALF_PI * 0.5)
rect(0, 0, 100, 100);
// 回転をもとに戻す
rotate(-HALF_PI * 0.5)
// 左右の四角
fill(0, 40, 80, 100);
translate(-100, 0);
rotate(HALF_PI * 0.5)
rect(0, 0, 100, 100);
// 回転をもとに戻す
rotate(-HALF_PI * 0.5)
やった!うまくいきそうです!
でもこれ面倒じゃない…?
rotate() は『今の状態から何度回転させるか』、translate() は『元の原点からどれだけ原点を移動させるか』である点に注意して全部の四角形の描画をコード化するとこうなります。
translate(width * 0.5, height * 0.5);
// 中央の四角
fill(0, 0, 100, 100);
rotate(HALF_PI * 0.5);
rect(0, 0, 100, 100);
// もとに戻す
rotate(-HALF_PI * 0.5);
// 左の四角
fill(0, 40, 80, 100);
translate(-100, 0);
rotate(HALF_PI * 0.5);
rect(0, 0, 100, 100);
// もとに戻す
rotate(-HALF_PI * 0.5);
translate(100, 0);
// 右の四角
fill(90, 40, 80, 100);
translate(100, 0);
rotate(HALF_PI * 0.5);
rect(0, 0, 100, 100);
// もとに戻す
rotate(-HALF_PI * 0.5);
translate(-100, 0);
// 上の四角
fill(180, 40, 80, 100);
translate(0, -100);
rotate(HALF_PI * 0.5);
rect(0, 0, 100, 100);
// もとに戻す
rotate(-HALF_PI * 0.5);
translate(0, 100);
// 下の四角
fill(270, 40, 80, 100);
translate(0, 100);
rotate(HALF_PI * 0.5);
rect(0, 0, 100, 100);
できた!できましたよ!
…でもこれ相当面倒ですよね。
面倒と思うものには答えがある!
「面倒だな」と思ったものには大体それを解決する方法が用意されています。
今回は rotate() や translate() をいちいち元に戻すのが面倒です。この問題を解決するのが push() と pop() です。
push() で状態を保存し、pop() で保存した状態を元に戻すという使い方をします。
translate() と rotate() を行う前に push() すれば原点の位置と座標軸の回転が保存され、pop() すれば元に戻るというわけです。
例えば先程の中央と赤い四角形だけの例でやってみると、
translate(width * 0.5, height * 0.5);
// 中央の四角
push();
fill(0, 0, 80, 100);
rotate(HALF_PI * 0.5)
rect(0, 0, 100, 100);
pop();
// 左右の四角
push();
fill(0, 40, 80, 100);
translate(-100, 0);
rotate(HALF_PI * 0.5)
rect(0, 0, 100, 100);
pop();
このとおり、原点の位置も座標軸の回転も中央の四角を描画する前の時点、つまり translate(width * 0.5, height * 0.5) の時点に戻っています。
push() と pop() で囲むだけで、どれだけ元に戻さないといけないかを考える必要がなくなるのでとても楽チンだし、間違いも少なくなります。
全体のコードがどうなるかは試してみてくださいね。😉
rotate() を使ってアニメーションしてみる
前回お見せしたこのランダムウォーク、これを作るには四角形を少しずつ回転させていけばいいですね。
rotate() の注意点に『draw() ループの度にリセット』がありますので、下記のコードだと単に 45度傾いただけになります。
let pX = 0;
let pY = 0;
function setup() {
createCanvas(640, 640);
rectMode(CENTER);
background(100);
}
function draw() {
// ひし形ランダムウォーク
translate(width * 0.5, height * 0.5);
pX += random(-10, 10);
pY += random(-10, 10);
translate(pX, pY);
rotate(HALF_PI * 0.5);
rect(0, 0, 20, 20);
}
少しずつ回転していくようにするためには、どんどん回転を大きくしていかないといけません。
例えばこのように。
translate(pX, pY);
rotate(frameCount * 0.01);
rect(0, 0, 20, 20);
このように回転の中心を固定して回転させれば円を描きがちなランダムウォークになります。
translate(width * 0.5, height * 0.5);
rotate(frameCount * 0.02);
pX += random(-5, 5);
pY += random(-5, 5);
rect(pX, pY, 20, 20);
では、このような直立した柱が円を描きながらランダムウォークするには?
先のコードの
rect(pX, pY, 20, 20);
を
translate(pX, pY);
rotate(-frameCount * 0.02);
rect(0, 0, 5, 30);
に変えるだけで OK です。前にやった『元に戻す』というやつです。もうわかりますよね。
まとめ
今回は translate と rotate() を使って回転をあやつる方法を学びました。
push() と pop()、draw() ループ中で rotate() を使う場合の注意点も学びましたので、これで回転はもう自由自在です。
前回 rotate() での回転の説明をする際に三角関数で円を描いてみました。
rotate() を使って回転させるだけでも面白いことが沢山できますが、三角関数を使うとさらにもっと面白いことができます。
例えばこんなのとか。
translate(width * 0.5, height * 0.5);
for (let i = 0; i < 10000; i++) {
let pX = 5 * cos(i * 0.1);
let pY = 5 * sin(i * 0.1);
ellipse(pX, pY, 10, 10);
translate(pX, pY);
rotate(i * 0.1);
}
さて、今回で終了となる「趣味としてのクリエイティブ・コーディング 座標編」、ここまで
・一般的なグラフと y軸の方向が逆
・座標の原点移動
・回転
をやってきましたが、いかがだったでしょう。
p5.js を使って説明してきましたが、Processing でも考え方は同じです。これを読んで座標に対する苦手意識が少しでも減ってくれると嬉しいです。🙂
この記事が面白かったらサポートしていただけませんか? ぜんざい好きな私に、ぜんざいをお腹いっぱい食べさせてほしい。あなたのことを想いながら食べるから、ぜんざいサポートお願いね 💕