Chapter 23

制約

miku
miku
2021.11.12に更新

自由にものを動かすのも楽しいが制約を付けることで面白い動きになることがある。

円の中にある円

「7章 - クランプ」で移動の制約について扱ったが、今回は円の制約になる。

最初の作例は、円の中にマウスで操作できる円を入れて、外の円から出られないようにするものだ。

const or = 200;
const ir = 20;

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

function draw() {
  const v = createVector(mouseX - width / 2, mouseY - height / 2);
  v.setMag(min(v.mag(), or - ir));

  clear();

  stroke(240);
  noFill();
  circle(width / 2, height / 2, or * 2);

  noStroke();
  fill(240);
  circle(width / 2 + v.x, height / 2 + v.y, ir * 2);
}

固定の外円の半径 or と、マウスで操作する内円の半径 ir を用意する。

createVector(マウス位置 - 画面中央) で、画面中央からマウス座標までのベクトルを作成する。このベクトルの長さが外円の半径を超えないようにすればいい。

具体的には v.setMag(min(v.mag(), or)) になるのだが、マウス座標にあるのは内円の中央なので、or から ir を引かないと内円が半分はみ出てしまう。なので v.setMag(min(v.mag(), or - ir)) にしよう。

あとは画面中央座標にベクトルを足した座標が内円の描画座標になる。

円の中の円 その2

次は内円ではなく外円を操作できるようにする。内円が外円から出られないのは先程と同じだ。

const or = 200;
const ir = 30;
let cx, cy;

function setup() {
  createCanvas(windowWidth, windowHeight);
  cx = width / 2;
  cy = height / 2;
}

function draw() {
  const d = dist(mouseX, mouseY, cx, cy);
  const v = createVector(cx - mouseX, cy - mouseY);
  v.setMag(min(d, or - ir));

  cx = mouseX + v.x;
  cy = mouseY + v.y;

  clear();

  stroke(240);
  noFill();
  circle(mouseX, mouseY, or * 2);

  noStroke();
  fill(240);
  circle(cx, cy, ir * 2);
}

円の中の円 その3

複数の円をグリッドとして配置する。それぞれの円はマウスに近づこうするとするが、円が移動できる最大距離が決まっている。

const n = 6;
const r = 30;
let circles = [];

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

  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;

      circles.push({ x: tx, y: ty });
    }
  }
}

function draw() {
  clear();

  for (let c of circles) {
    const v = createVector(mouseX - c.x, mouseY - c.y);
    v.setMag(min(v.mag(), r));
    const tx = c.x + v.x;
    const ty = c.y + v.y;

    circle(tx, ty, 10);
  }
}

複数の円を配置すること以外は、最初の作例とほとんど同じコードになる。今回は外円の描画がないので、外円の半径から内円を半径を引く、のような細かい処理は書かなくていい。

回転の制約

大砲のようなものを作るとき、目標を狙い撃つために回転させる場合、一瞬で回転させるより、1フレームに回れる角度を制限すると表現の説得力が増す。見た目の影響だけでなく、たとえばゲームだと難易度にも影響するだろう。

const speed = 0.01;
let circles;

function setup() {
  createCanvas(windowWidth, windowHeight);
  noFill();
  stroke(240);

  circles = [];
  for (let i = 0; i < 10; i++) {
    circles.push({ x: random(width), y: random(height), angle: random(-PI, PI) });
  }
}

function draw() {
  clear();

  circles.forEach((c) => {
    let angle = atan2(mouseY - c.y, mouseX - c.x);
    let ld, rd;
    if (angle < c.angle) {
      ld = c.angle - angle;
      rd = angle + TWO_PI - c.angle;
    } else {
      rd = angle - c.angle;
      ld = c.angle - (angle - TWO_PI);
    }

    if (ld < rd) {
      c.angle -= min(speed, ld);
    } else {
      c.angle += min(speed, rd);
    }

    if (c.angle > PI) {
      c.angle -= TWO_PI;
    }

    translate(c.x, c.y);
    rotate(c.angle);
    line(0, 0, 10000, 0);
    fill("#292a33");
    circle(0, 0, 20);
    resetMatrix();
  });
}

位置の制約

マウス座標に円を描くが、ある倍数単位の座標にしか移動できないようにする。

マウス自体は自由に動かしているのに、特定の座標にしか配置できないことで、ある種ランダムの中に秩序を生むことができる。

const unit = 60;
let px, py;

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

  px = 0;
  py = 0;
}

function draw() {
  const x = floor(mouseX / unit) * unit;
  const y = floor(mouseY / unit) * unit;

  if (px == x && py == y) {
    return;
  }

  fill(random(256));
  circle(x, y, unit);

  px = x;
  py = y;
}

位置の制約 その2

計算して得た位置をすべて四捨五入して整数位置だけの描画を行う作例。

const r = 30;
const n = 40;
let angle, hist;

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

  hist = [];

  angleMode(DEGREES);
  angle = 0;
}

function draw() {
  clear();
  translate(width / 2, height / 2);
  scale(6);

  const x = round(cos(angle) * r);
  const y = round(sin(angle) * r);

  hist.push({ x, y });
  if (hist.length > n) {
    hist.shift();
  }

  hist.forEach((p, i) => {
    fill(map(i, 0, hist.length, 0, 240));
    rect(p.x, p.y, 1, 1);
  });
  fill(240);
  rect(x, y, 1, 1);

  angle += 2;
}