とりあえずの点 vs 多角形の当たり判定

所用で点vs多角形の当たり判定が必要になったのでメモしておきます。

基本的な考え方は以下を参考にしています。
http://poltergeist.web.fc2.com/hit_test.html

関数と使い方

/**
 * 点 vs 多角形の当たり判定
 * 
 * @param  {Vector2}    point 対象点
 * @param  {...Vector2} vertices 多角形頂点
 * @return {Boolean} 範囲内かどうか
 */
function hitTestPolygon(point, ...vertices) {
  const peripheryVecs = []; // 多角形外周ベクトル群
  const vertToPointVecs = []; // 各頂点~対象点までのベクトル群
  vertices.forEach((v, i)=> {
    // 外周ベクトル生成
    const dest = (vertices[i+1] != null) ? vertices[i+1] : vertices[0];
    peripheryVecs.push({
      x: dest.x - v.x,
      y: dest.y - v.y,
    })
    // 各頂点から点へのベクトル生成
    vertToPointVecs.push({
      x: point.x - v.x,
      y: point.y - v.y,
    })
  })

  // 上記二種のベクトルの外積Z軸成分を計算
  // 全て正もしくは負になれば範囲内という扱い
  let signFlag;
  const isOutsideRange = peripheryVecs.some((vec, i)=> {
    const pVec = vertToPointVecs[i];
    const crossZ = (vec.x * pVec.y) - (vec.y * pVec.x);
    if (signFlag != null) {
      if (signFlag !== (crossZ > 0)) {
        // 符号が変わった場合、領域外になるのでループ抜ける
        return true;
      }
    } else {
      signFlag = (crossZ > 0);
    }
  })

  return !isOutsideRange;
}

見ての通り第2引数以降はrestパラメータ(可変長引数)になっており、五角形なら頂点オブジェクトを5つ、八角形なら8つ渡すみたいな感じで使います。
(ただし頂点同士のなす角度が180以上だとNG)

point, verticesは共にプロパティにxyをもったオブジェクトであれば何でもOKです。

const mouse = {
  x: 20,
  y: 200
}

if (hitTestPolygon(mouse, 
  {x:20, y: 20},
  {x:20, y: 30},
  {x:40, y: 30})) {
  console.log('hit!')
}

使い方サンプル

まだ最適化しきれてないような気がしますが、とりあえずということで…

おまけ

Mapオブジェクトを渡したいとき。(es2017以降?)

const vertexMap = new Map([
  ["bl", new Vector2()],
  ["br", new Vector2()],
  ["tr", new Vector2()],
  ["tl", new Vector2()],
])

hitTestPolygon(p, ...vertexMap.values())