Chapter 13

距離

miku
miku
2021.11.25に更新

前章で複数の円を用意して等間隔に並べる方法を学んだが、ただ並べて表示するだけではあまり面白くない。この章では、距離を利用して並べた円の表示を変化させることを学ぶ。

距離

dist(x1, y1, x2, y2);

まず距離を求める関数の使い方をおさらいしよう。dist()(x1, y1)~(x2, y2) までの二点間の距離を返す。

この距離を利用して円の表示を変化させたい。一番わかりやすいのが距離が遠くなるにつれて円が大きくなったり小さくなったりすることだ。

距離が遠いほど大きくなる

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

  const maxDist = dist(0, 0, width / 2, height / 2);
  const n = 15;
  for (let y = 0; y < n; y++) {
    for (let x = 0; x < n; x++) {
      const tx = (width / (n - 1)) * x;
      const ty = (height / (n - 1)) * y;

      const d = dist(width / 2, height / 2, tx, ty);
      const nd = map(d, 0, maxDist, 0, 30);
      circle(tx, ty, nd);
    }
  }
}

画面中央から離れた円ほどサイズが大きくなる。円を等間隔に配置する方法は前章で説明しているので省略する。

ここで重要なのは距離に応じて円のサイズを変える方法だ。

const d = dist(width / 2, height / 2, tx, ty);

まず円ごとに画面中央からの距離を計算する。この計算した距離をそのまま円のサイズに設定してもいいのだが、大きすぎたりしてサイズが合わないことがあるので、ある程度は調整したいところだ。距離の範囲を、円のサイズの範囲に変換するのだが、これは map() を利用すると便利だ。

画面中央からの距離の範囲は、最小が 0 で、最大が「中央から画面端までの距離」になる。後者はコードで書くと dist(0, 0, width / 2, height / 2) である。この範囲を map() を利用して新しい範囲に変化してやればいい。

const nd = map(d, 0, maxDist, 0, 30);

上記コードだと、画面中央から一番遠い場所である画面端に円を配置したなら、直径30pxの円になる。30 は適当な数字なので、実際には好きな値を入れてもらえればいい。

circle(tx, ty, nd);

あとは計算した直径で円を描画する。

距離が遠いほど小さくなる

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

  const maxDist = dist(0, 0, width / 2, height / 2);
  const n = 15;
  for (let y = 0; y < n; y++) {
    for (let x = 0; x < n; x++) {
      const tx = (width / (n - 1)) * x;
      const ty = (height / (n - 1)) * y;

      const d = dist(width / 2, height / 2, tx, ty);
      const nd = map(d, 0, maxDist, 30, 0);
      circle(tx, ty, nd);
    }
  }
}

画面中央から離れた円ほどサイズが小さくなる作例。

先程のコード例の map() の変換先範囲を 0~30 から 30~0 に変換するだけで完成する。この場合は、画面中央に近づくほど円の直径が 30 に近くなり、逆に画面端に近づくほど円の直径が 0 に近くなる。

今までは基準点が画面中央で固定だったが、次はマウス座標が基準になるようにしてみよう。

マウス座標を基準点にする

let maxDist;

function setup() {
  createCanvas(windowWidth, windowHeight);
  maxDist = dist(0, 0, width, height);
}

function draw() {
  clear();

  const n = 15;
  for (let y = 0; y < n; y++) {
    for (let x = 0; x < n; x++) {
      const tx = (width / (n - 1)) * x;
      const ty = (height / (n - 1)) * y;

      const d = dist(mouseX, mouseY, tx, ty);
      const nd = map(d, 0, maxDist, 30, 0);
      circle(tx, ty, nd);
    }
  }
}

マウス座標から離れた円ほどサイズが小さくなる作例。

マウス座標によって描画を変えたいので、ほとんどの処理を setup() から draw() に移す必要がある。円からマウス座標までの取り得る距離は画面端から対角の画面端まで、つまり dist(0, 0, width, height) なので、このときに円の直径を最大(最小)にすればいい。

距離によって色を変える

fill(赤の量, 緑の量, 青の量);

色はもともと座標のように指定ができるので距離の変化と相性が良い。つまり、距離に応じて赤の量を増やす、などのように変化させる。

RGBグラデーション

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);

  const yn = 30;
  const xn = 60;

  for (let y = 0; y < yn; y++) {
    for (let x = 0; x < xn; x++) {
      const tx = (width / (xn - 1)) * x;
      const ty = (height / (yn - 1)) * y;

      const r = map(tx, 0, width, 0, 255);
      const g = map(ty, 0, height, 0, 255);
      fill(r, g, 0);
      circle(tx, ty, 10);
    }
  }
}

画面左上を基準点として、x が大きくなるほど赤色を、y が大きくなるほど緑色の値を増やす作例。

  • x座標がとり得る範囲は 0~width で、それを赤の範囲である 0~255 に変換する。
  • y座標がとり得る範囲は 0~height で、それを緑の範囲である 0~255 に変換する。

変換前・変換後の範囲の最小値が 0 なので、map() を使わず tx / width * 255 のように計算してもいい。

放射状グラデーション

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);

  const yn = 30;
  const xn = 60;

  for (let y = 0; y < yn; y++) {
    for (let x = 0; x < xn; x++) {
      const tx = (width / (xn - 1)) * x;
      const ty = (height / (yn - 1)) * y;

      const dx = abs(tx - width / 2);
      const dy = abs(ty - height / 2);

      const r = map(dx, 0, width / 2, 0, 255);
      const b = map(dy, 0, height / 2, 0, 255);
      fill(r, 0, b);
      circle(tx, ty, 10);
    }
  }
}

画面中央からx軸の距離が離れるほど赤色を増加、y軸の距離が離れるほど青色を増加させる例。x軸/y軸ごとに距離に応じて色が変わるので、左右/上下対称のカラーになる。

円形グラデーション

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);

  const yn = 30;
  const xn = 60;
  const maxDist = dist(0, 0, width / 2, height / 2);

  for (let y = 0; y < yn; y++) {
    for (let x = 0; x < xn; x++) {
      const tx = (width / (xn - 1)) * x;
      const ty = (height / (yn - 1)) * y;

      const d = dist(tx, ty, width / 2, height / 2);
      const v = map(d, 0, maxDist, 0, 255);
      fill(v, 0, 0);
      circle(tx, ty, 10);
    }
  }
}

画面中央からの距離が離れるほど赤色の値を増やす作例。画面中央からの半径によって色が決まるので結果的に円形のグラデーションになる。

RGBでの指定は色の明暗が分かれるので、表現としては見づらいところがある。できるなら明るさを固定にしたまま色を変化したいので、次にHSBを利用する。

HSBグラデーション

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
  colorMode(HSB);

  const yn = 30;
  const xn = 60;
  const maxDist = dist(0, 0, width / 2, height / 2);

  for (let y = 0; y < yn; y++) {
    for (let x = 0; x < xn; x++) {
      const tx = (width / (xn - 1)) * x;
      const ty = (height / (yn - 1)) * y;

      const d = dist(tx, ty, width / 2, height / 2);
      const h = map(d, 0, maxDist, 0, 360);
      fill(h, 255, 255);
      circle(tx, ty, 10);
    }
  }
}

画面中央からの距離が離れるほど色相の値を増やす作例。

内側と外側が赤色になっているのは、色相の色はループするからだ。これをやめたい場合は、外側が青色あたりで止まるように map() の変換先最大値を 360 から 240 に変えればいい。

マウスで円の大きさを変化させる

function setup() {
  createCanvas(windowWidth, windowHeight);
  fill(240);
}

function draw() {
  clear();

  const yn = 30;
  const xn = 30;

  for (let y = 0; y < yn; y++) {
    for (let x = 0; x < xn; x++) {
      const tx = (width / (xn - 1)) * x;
      const ty = (height / (yn - 1)) * y;

      const d = dist(tx, ty, mouseX, mouseY);
      const cd = map(d, 0, dist(0, 0, width, height), 0, 60);
      circle(tx, ty, cd);
    }
  }
}

グリッド状に円を配置して、マウス座標が円に近づくほど、円の大きさが小さくなる作例。