とりあえずの点 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は共にプロパティにx
とy
をもったオブジェクトであれば何でも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())