Chapter 04

等速の変化

miku
miku
2021.11.10に更新

前章では円の描き方を扱い、マウスやランダムを利用して円の大きさを変化させる作例を紹介したが、この章では円の大きさを等速に変化させる方法について学ぶ。

直径の増加

let d; // 直径を表す

function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();

  d = 0; // 直径の初期化
}

function draw() {
  d++; // 例として、直径に1という固定の数を加え続ける

  clear();
  circle(width / 2, height / 2, d);
}

円の直径を入れる d という変数を用意して、その d に毎フレーム固定の値を加え続ける。d0 より大きい値だと、円が等速に大きくなり続け、いつか画面を覆うようになり、フェードアウトのような演出ができる。

リセット

円は大きくなり続けるので、一度画面が円に覆われたらずっとそのままの表示になるだろう。演出を見逃してしまう可能性があるので、ここでやり直し、つまりリセットの機能を実装したい。

考えられるリセット方法は2つある。1つ目は画面をクリックして任意のタイミングでリセットさせる方法、2つ目は画面が円に完全に覆われたら自動でリセットするという方法である。

let d;

function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();
  reset();
}

function draw() {
  d += 2;
  // 適当な大きさでリセットする
  if (d > 900) {
    reset();
  }

  clear();
  circle(width / 2, height / 2, d);
}

// この関数が呼ばれると直径が初期サイズに戻り、リセットされる
function reset() {
  d = 0;
}

// 画面をマウスでクリックするとリセットする
function mouseClicked() {
  reset();
}

リセットをする際の処理は共通なので reset() という関数を用意して、リセット内容を書いておく。ここでは直径を初期サイズに戻せばいい。

画面上でクリックすると mouseClicked() が呼ばれるので、その中で reset() を呼び出せば1つ目の方法は実現できる。

あとは画面が円に完全に覆われたらリセットするということだが、これは言い換えると、円の直径が一定以上になれば reset() を呼び出すということになる。

if (d > 900) {
  reset();
}

必要となる正確な直径のサイズはこの後に求めるので、今は適当なサイズでリセットさせる。

このようなリセットの配慮は、閲覧するユーザーのためのものだが、自身がコードを書く際のデバッグやイテレーションの効率を上げることにも有効である。

二点間の距離

適当なサイズでリセットしているところを、的確なサイズに変更したい。

画面の中央にある円が完全に画面を覆うというのは、画面中央から画面内にある一番遠い場所までの距離が円の半径になった場合である。

では、画面中央から画面内にある一番遠い場所はどこになるのか。

たとえば、画面の中央から横にまっすぐ移動した先にある画面端か、同じように縦にまっすぐ移動した先にある画面端で、このどちらかの遠い方ではないかということが考えられる。

実際に遠い方に合わせて円を配置してみると、画面端の角の部分まで届いていないことがわかる。どうやら画面中央から画面内にある一番遠い場所は、その角の部分までのようだ。

なので、画面中央から画面端の一角である (0, 0) までの長さが円の半径になると、円がぴったりと画面を覆うことになる。

では、この長さはどうやって計算をするのか?

ここで、三平方の定理 a^2 + b^2 = c^2 を利用する。

三平方の定理は、直角三角形の2辺 a, b を二乗した和 a^2 + b^2 と、斜辺 c を二乗した c^2 が同じ数になるというものだ。

今求めたい長さである、画面中央から (0, 0) までを斜辺 c だとすると、ab の配置は上記のようになる。

  • a = |(0 - width / 2)|
  • b = |(0 - height / 2)|

なので、三平方の定理 a^2 + b^2 = c^2 から、

  • c^2 = (0 - width / 2)^2 + (0 - height / 2)^2

となり、今求めたいのは c なので、両辺の平方根を取る。

  • c = \sqrt{(0 - width / 2)^2 + (0 - height / 2)^2}

これで斜辺の長さ c が求まったので、今までの計算をコードに落としてみる。

const x1 = 0;
const y1 = 0;
const x2 = width / 2;
const y2 = height / 2;

const a = x1 - x2;
const b = y1 - y2;
const c = sqrt(a * a + b * b); // sqrt()で平方根の計算ができる

この計算した c の値が、画面中央から画面端 (0, 0) までの距離となる。画面端は左上以外にも3箇所あるが、その中から代わりに選んで計算しても結果は一緒である。

頑張ってここまで計算してきたが、p5.jsには dist() という今までの計算をしてくれる関数があるので以降はこちらを利用する。今までの計算も応用が効くアルゴリズムなので覚えていて損はない。

// dist(width / 2, height / 2, 0, 0); でも同じ結果が返る
const c = dist(0, 0, width / 2, height / 2);

dist(x1, y1, x2, y2)(x1, y1)~(x2, y2) までの直線距離を返す。なので、画面中央から画面端 (0, 0) までの最短距離を求めたい場合は上記のように指定すればいい。

円が画面全体を覆ったらリセットする

// 円の半径が、画面中央から画面端までの直線距離より大きくなったらリセット
if (d / 2 > dist(0, 0, width / 2, height / 2) {
  reset();
}

今やりたいことをもう一度確認すると、円が画面全体を覆ったら直径をリセットするということだ。

円は画面中央に置かれているので、円の半径が先程求めた直線距離より大きくなったらリセットすればいい。円の半径は 直径 / 2 で求めることができる。

let d;

function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();
  reset();
}

function draw() {
  d += 2;

  // 円の半径が、画面中央から画面端までの直線距離より大きくなったらリセット
  if (d / 2 > dist(0, 0, width / 2, height / 2)) {
    reset();
  }

  clear();
  circle(width / 2, height / 2, d);
}

function reset() {
  d = 0;
}

function mouseClicked() {
  reset();
}

直径を持つか半径を持つか

今までは円の情報として直径を保持していた。これはp5.jsで円を描く関数が直径を指定する必要があるからだ。

ただ、先程の計算のように、直径ではなく半径の情報が必要となるケースがあり、直径を2で割れば半径が出てくるのだが、参照される比率が直径より半径の方が多い場面ならば半径の情報を持つほうがコードがシンプルになるので、今後、場面によっては直径ではなく半径をデータとして持つことにする。

デメリットも存在し、circle() の引数は相変わらず直径の指定なので、半径のデータを持つならば 半径 * 2 を指定しなければならない。間違えて半径を指定したままでも円のサイズが小さくなるだけでエラーはでない。意外と気づきにくいバグとなる。

例として、先程のコードを直径から半径を持つように変更したコードは下記の通り。

let r;

function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();
  reset();
}

function draw() {
  r += 4;
  if (r > dist(0, 0, width / 2, height / 2)) {
    reset();
  }

  clear();
  circle(width / 2, height / 2, r * 2);
}

function reset() {
  r = 0;
}

function mouseClicked() {
  reset();
}

色の変更

円が画面いっぱいに広がりきってリセットする際、円の色を背景の色に適用、そのあと円の色をランダムに変更する。

今までと同じように円は1つしかないが、背景と円の色をうまく変えることで、リセットするたびに新しい円が生まれているように見えるはずだ。

// 直径, 背景色, 円の色
let d, bgColor, circleColor;

function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();

  circleColor = random(255);
  reset();
}

function draw() {
  d += 4;
  if (d / 2 > dist(0, 0, width / 2, height / 2)) {
    reset();
  }

  background(bgColor);
  fill(circleColor);
  circle(width / 2, height / 2, d);
}

// リセットするたびに広がりきった円の色を背景に移し、円の色を変更する
function reset() {
  d = 0;
  bgColor = circleColor;
  circleColor = random(255);
}

background() は指定した色で画面全体を塗りつぶす関数だ。色がついた clear() だと思えばいい。

RGBで色を指定する

今までグレースケールの色を利用したのは引数の指定が楽だからだが、赤や緑のような色味のある色も指定できるようにしたい。ここでRGBという色の指定方法を利用する。

色の指定については次の章で詳細に扱うので、ここでは簡易的な説明になるが、RGBは赤と緑と青の3色で1つの色を表現する。

黄色を作りたければ赤と緑を混ぜればいいし、紫なら赤と青を混ぜればいい。混ぜる混ぜないは色の値の強弱によって表現をする。

// fill(赤, 緑, 青)
fill(255, 255, 255);

// stroke(赤, 緑, 青)
stroke(255, 255, 255);

p5.jsにおいてのRGBの指定も同様で、赤・緑・青の3つの値の強弱で色を表現する。指定できる値の範囲は 0~255 とグレースケールと同じで、たとえば (255, 255, 10) なら、赤と緑を混ぜたものにちょびっと青を加えた色になる。

先程の作例では色をグレースケールのランダム、コードで書くと random(255) にしていたが、これをRGBの範囲でランダムにしたい。赤・緑・青それぞれの値を random(255) にすればいいのだが、持ち運びするには変数が3つ必要になる。表現したい色は1つなのに変数が複数あるのは面倒なので、変数の方も1つにまとめたい。ここで color() という関数を利用する。

// color(赤, 緑, 青)
const c = color(255, 255, 255);
fill(c);
stroke(c);

具体的な使い方については次の章で扱うが、color() に赤・緑・青それぞれの色の強弱を数で指定すると1つのオブジェクトを返す。この色のオブジェクトは fill()stroke() に色として指定ができる。これを利用すれば変数が1つにまとまるのでコードが簡潔になる。

グレースケールの代わりにRGBのランダムな色に変えた作例は下記の通り。

let d, bgColor, circleColor;

function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();

  circleColor = color(random(255), random(255), random(255));
  reset();
}

function draw() {
  d += 4;
  if (d / 2 > dist(0, 0, width / 2, height / 2)) {
    reset();
  }

  background(bgColor);
  fill(circleColor);
  circle(width / 2, height / 2, d);
}

function reset() {
  d = 0;
  bgColor = circleColor;
  circleColor = color(random(255), random(255), random(255));
}

この章のまとめ

  • ある数を等速に変化させるには、固定の数を加え続ければいい。
  • 画面中央から画面内の一番遠い場所は、画面左上のような画面端の角である。
  • 2点間の直線距離を求めるアルゴリズムは三平方の定理、関数は dist(x1, y1, x2, y2) を利用する。
  • RGBの色を表現するには 0~255 の3つの数を用意する。1つの変数にまとめるには color() を利用する。