見出し画像

趣味としてのクリエイティブ・コーディング:105:座標軸を回転(前編)

「趣味としてのクリエイティブ・コーディング 座標編」、前回は translate() を使って座標の原点を移動させてみました。
その中で translate() の基本的な使い方から複数回指定した場合の動き、 draw() の度に原点はリセットされるという注意点などを学びました。

今回からは、座標が難しいと感じる大きな要素「回転」を 2回に分けてやりましょう。
p5.js のコードを例に説明しますが、Processing でも考え方は同様です。

画像1

前回の問題の回答例

その前に前回の問題の回答例を作ってみましたのでご覧ください。

静止画のランダムウォークなら単純にループさせるだけでこの通り。

   translate(width * 0.5, height * 0.5);
   for (i = 0; i < 500; i++) {
	translate(random(-10, 10), random(-8, 8));
	ellipse(0, 0, 10, 10);
   }

画像25


これを draw() ループでアニメーションさせるにはどうしたらいいか?の回答例がこちらです。

let pX = 0;
let pY = 0;
function setup() {
  createCanvas(500, 400);
  background(200);
}
function draw() {
  translate(width * 0.5, height * 0.5);
  pX += random(-10, 10);
  pY += random(-8, 8);
  translate(pX, pY);
  ellipse(0, 0, 10, 10);
}


draw() ループの度に原点はリセットされるので、変数に原点の座標を保存しています。
コードのここは、

translate(pX, pY);
ellipse(0, 0, 10, 10);

こうしても結果は同じです。

ellipse(pX, pY, 10, 10);

でも、原点をランダムウォークさせているか、円の位置をランダムウォークさせているか、その違いは大きいんです。
例えばこんな風に回転が絡むと…


画像2

回転させるなら rotate()

先程のアニメーションは rotate() を使って四角形をちょっとずつ回転させていけば作れます。

こう書くと、角度 r だけ時計回りに回転します。

rotate(r);

長方形をちょっとずつ回転させながら 3つ描画するとこうなります。

画像24


r は通常は度数ではなくラジアンです。

rotate(PI); // 時計回りに 180度回転
rotate(-HALF_PI); // 反時計回りに 90度回転
rotate(TWO_PI); // 時計回りに 360度回転、つまり元と同じ
度数で指定したければ angleMode(DEGREES) で度数指定になります。

注意点として translate() と同じように下記 2点の注意点があります。
1.今の状態から何度回転させるかである。
2.「今の状態」は draw() ループの度にリセットされる。

画像3

回転させてみよう

例えば小さい丸を沢山並べて円を描きたいとき、三角関数で座標を計算してこのように描くことができます。

function setup() {
  createCanvas(640, 640);
  noLoop();
}
function draw() {
  translate(width * 0.5, height * 0.5);
  background(100);
  // 半径 200 の真円
  for (let i = 0; i < 100; i++) {
    let pX = 200 * cos(i * 0.1);
    let pY = 200 * sin(i * 0.1);
    ellipse(pX, pY, 10, 10);
  }
}


画像23

OpenProcessing でぜひ実際に試してみよう!
OpenProcessing の使い方は本シリーズのこちらで紹介しています。
趣味としてのクリエイティブ・コーディング:102:OpenProcessing を使おう


同じものを描くのに rotate() を使うとこうなります。

   // 半径 200 の真円
  for (let i = 0; i < 100; i++) {
    rotate(0.1);
    ellipse(200, 0, 10, 10);
  }

『1.今の状態から何度回転させるかである。』なので、

    rotate(0.1);

で 0.1 ずつどんどん回転していきます。

そして小さい丸は x軸上 200 の位置に描いています。

    ellipse(200, 0, 10, 10);

つまり、毎回同じ位置に描画しているわけですね。
毎回同じ位置に描いているだけなのになぜ円になるのか?

上の図に x軸を足してみるとこうなります。

画像22

x軸上の同じ位置に描いているけど、x軸そのものが rotate() で回転していくので結果的に円になるというわけです。


同じコードで x軸上の位置を変化させると螺旋も簡単に描くことができます。

    ellipse(i * 2, 0, 10, 10);

画像21


楕円で描くと座標が回転していることがよくわかりますね。

    ellipse(i * 2, 0, 50, 10);

画像20


画像4

回転の中心はどこ?

回転の中心は座標の原点です。

原点は translate() で移動させることができました。
この translate() と rotate() を組み合わせるといろいろと面白いことができるのですが、同時にややこしくもあります。

前回やったこの図形、これを例に translate() と rotate() の合わせ技を試してみましょう。

  translate(width * 0.5, height * 0.5);
  // 中央の円
  ellipse(0, 0, 100, 100);
  // 左右の円
  ellipse(-100, 0, 100, 100);
  ellipse( 100, 0, 100, 100);
  // 上下の円
  ellipse(0, -100, 100, 100);
  ellipse(0,  100, 100, 100);

画像19

円だと回転が分かりづらいので四角形に変えてみます。

  translate(width * 0.5, height * 0.5);
  // 中央の四角
  rect(0, 0, 100, 100);
  // 左右の四角
  rect(-100, 0, 100, 100);
  rect(100, 0, 100, 100);
  // 上下の四角
  rect(0, -100, 100, 100);
  rect(0, 100, 100, 100);


画像18

あれ?なんかズレました。
はい、ellipse をそのまま rect にしてもズレます。

ellipse() は円の中心の座標を指定しますが、通常 rect() は四角形の左上隅の座標を指定します。
これだと具合が悪いので、rectMode(CENTER) で rect() の座標指定を四角形の中心に変更します。ついでにそれぞれの四角形の区別が付くように色も変えておきましょう。

全体のコードはこうなります。

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);
}


画像17

さて、このコードに rotate(HALF_PI * 0.5) を入れてみましょう。 HALF_PI * 0.5 は 45度なので斜めになる感じですね。
例えばここに入れると?

  translate(width * 0.5, height * 0.5);
  rotate(HALF_PI * 0.5);
  // 中央の四角

画像16

全体が時計回りに 45度回転していますね。回転時の原点は translate(width * 0.5, height * 0.5) によってキャンバスの中央になっています。この原点が回転の中心です。

では、 rotate(HALF_PI * 0.5) を中央の四角を描画した後に移動したら?

  // 中央の四角
  fill(0, 0, 80, 100);
  rect(0, 0, 100, 100);

  rotate(HALF_PI * 0.5);

  // 左右の四角
  fill(0, 40, 80, 100);


画像15

結果は中央の四角だけが回転せず真っすぐのままになっています。
同じ理屈で、上下の四角を描画する直前に rotate(HALF_PI * 0.5) を移動するとこうなります。

  rotate(HALF_PI * 0.5);

  // 上下の四角
  fill(180, 40, 80, 100);


画像14



ここまで回転の中心は全てキャンバスの中心でした。
では、下記のように translate() の前に rotate() を移動するとどうなるでしょう?

  rotate(HALF_PI * 0.5);
  translate(width * 0.5, height * 0.5);
  // 中央の四角

どうなると思います?

rotate(HALF_PI * 0.5) は translate() の前にあります。
その時点の原点は?
キャンバスの左上ですよね。

なので無残にもこうなります。

画像13

ではさらに translate() の後に rotate() を追加したら?

  rotate(HALF_PI * 0.5);
  translate(width * 0.5, height * 0.5);
  rotate(HALF_PI * 0.5);
  // 中央の四角

キャンバス左上を中心に回転させた後、キャンバス中央を中心にしてさらに回転だから、結果は

画像12

…ん? …合ってるような、なんかちょっと違うような?

画像5

回転によるキャンバス位置の移動

最初にキャンバス左上の原点を中心に回転させ、次にキャンバス中央に移動した原点を中心にさらに回転させるんだから、こうなるんじゃないでしょうか?
黒丸が原点の位置です。

最後の位置がズレてますよね?

画像11

今回の結果

画像10

さっきの結果

実はこれ「キャンバス中央が原点」は正しいけど、そのキャンバス中央の位置が間違っているんです。
これが translate() と rotate() を組み合わせたときのややこしさ、よく勘違いしてしまうところです。

rotate() することで、キャンバス自体が回転するので、キャンバスの中央も回転して移動しているのです。
下記のコード実行後の原点 x = width * 0.5, y = height * 0.5 の位置は、

  rotate(HALF_PI * 0.5);
  translate(width * 0.5, height * 0.5);

ここではなくて、

画像9

ここなのです。

画像8

なので下記コードの回転は、

  rotate(HALF_PI * 0.5);
  translate(width * 0.5, height * 0.5);
  rotate(HALF_PI * 0.5);

こうなり、さっきので正解なのです。

これはキャンバスの枠を四角形で描画してみるとわかりやすいと思います。
黒丸が原点の位置、赤い四角がキャンバスの中央です。

キャンバス自体が回転するということは座標そのものが回転するということ。
つまり x軸、y軸もこのように回転します。
緑の丸は x = 100, y = 0、青の丸は x = 0, y = -400 です。


画像6

まとめ

今回は rotate() を使って座標軸の回転を試してみました。
rotate() は回転を簡単に実現できて便利なものですが、これを図形の回転と思っていると勘違いを起こしてワケわかんなくなってしまいます。

そこで、途中で出てきた注意点に一つ加えておきましょう。
1.今の状態から何度回転させるかである。
2.「今の状態」は draw() ループの度にリセットされる。
3.回転させるのは図形ではなく座標軸。

今回はここまでにしましょう。
次回は rotate() を使ってこういうものを描いてみます。

画像7

さっき出てきた四角を使った図と似てますけど、できるかな?



この記事が面白かったらサポートしていただけませんか? ぜんざい好きな私に、ぜんざいをお腹いっぱい食べさせてほしい。あなたのことを想いながら食べるから、ぜんざいサポートお願いね 💕