Chapter 39

衝突判定

miku
miku
2021.11.15に更新

点と点

function collisionPoints(a, b) {
  return a.x === b.x && a.y === b.y;
}

点と点が衝突しているかは、点同士の xy が同じ値であれば衝突しているといえる。ただし、JavaScriptには整数の型がないので、誤差によってずれる可能性がある。

function eq(a, b, e = 0.001) {
  return abs(a - b) < e;
}

function collisionPoints(a, b) {
  return eq(a.x, b.x) && eq(a.y, b.y);
}

その場合は 0.001 のような小さい値 e を用意して、軸ごとの値の差の絶対値が e より小さければ実質同じとみなせばいい。これなら多少誤差が出ても同値判定ができる。

円と点

円と点では、円の中心座標から点までの距離が、円の半径以下ならば衝突しているといえる。

const r = 100;
let points;

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

  points = [];
  for (let i = 0; i < 20; i++) {
    points.push({ x: random(width), y: random(height) });
  }
}

function draw() {
  clear();

  points.forEach((p) => {
    noStroke();
    const d = dist(p.x, p.y, mouseX, mouseY);
    if (d <= r) {
      fill("#ff9900");
    } else {
      fill(240);
    }
    circle(p.x, p.y, 10);
  });

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

作例では見やすさの為、点の描画は円にしている。

円と円

円と円がぴったりと接触している場合、円の半径の合計が円同士の距離と同じになっている。なので、円同士の距離が円の半径の合計以下ならば円同士が衝突しているといえる。

let c, r;

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

  c = { x: width / 2, y: height / 2, r: 60 };
  r = 100;
}

function draw() {
  const d = dist(c.x, c.y, mouseX, mouseY);
  const col = d <= r + c.r ? "#ff9900" : "#fff";

  clear();
  stroke(240);
  circle(c.x, c.y, c.r * 2);
  stroke(col);
  circle(mouseX, mouseY, r * 2);
}

矩形と矩形

矩形と矩形の衝突判定だが、まず衝突「していない」場合について考えるとわかりやすい。2つの矩形 A, B があり、x軸だけで衝突していない場合を考えると上記の2つの形になる。

上側のケース - A の右側が B の左側より小さい
下側のケース - A の左側が B の右側より大きい

このどちらかを満たすと衝突していないということになる。コードにすると a.right < b.left || a.left > b.right になり、この否定が衝突しているということになるので a.right >= b.left && a.left <= b.right がx軸における矩形の衝突判定になる。y軸の計算も同様である。

let a, b;

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

  const w = 200;
  const h = 160;

  a = {
    x: (width - w) / 2,
    y: (height - h) / 2,
    width: w,
    height: h,
  };

  b = {
    x: 0,
    y: 0,
    width: 100,
    height: 100,
  };
}

function draw() {
  clear();

  stroke(240);
  rect(a.x, a.y, a.width, a.height);

  b.x = mouseX;
  b.y = mouseY;

  const cx = a.x + a.width - 1 >= b.x && a.x <= b.x + b.width - 1;
  const cy = a.y + a.height - 1 >= b.y && a.y <= b.y + b.height - 1;
  if (cx && cy) {
    stroke(255, 0, 0);
  }
  rect(b.x, b.y, b.width, b.height);
}

衝突判定の利用例

let prevX, prevY, prevR;

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

  prevX = width / 2;
  prevY = height / 2;
  prevR = min(width, height) / 2;
}

function draw() {
  if (mouseX <= 0 || mouseX >= width || mouseY <= 0 || mouseY >= height) {
    return;
  }

  const d = dist(mouseX, mouseY, prevX, prevY);
  if (d > prevR) {
    const r = d - prevR;
    circle(mouseX, mouseY, r * 2);

    prevR = r;
    prevX = mouseX;
    prevY = mouseY;
  }
}

マウス座標に円を描画するが、前回描画した円に被るようだと描画をしない。描画ができる場合、前回描画した円と触れるところまで直径を大きくする。つまり繋がった円を描くことができる。