点の多角形内包チェック


ある点が多角形(ポリゴン)の内部にあるかどうかをチェックするアルゴリズムを実装したくてちょっと調べてみました。凸多角形限定のアルゴリズムは以前実装したことがあったのですが、このページ(http://bal4u.dip.jp/mt/program/archives/2004/11/_ph.html)を見たら限定でなくても結構簡単に実装できるみたいなので、C++的に書き直しながら内部を把握してみました。
ちなみに、高速化を考え、線上は外側とみなすようにしてあります。
(追記)これ、原理的には穴が開いている多角形でも対応可能なのですが、ただ与えられた頂点配列の処理のしかたがそこまで対応できていませんね。しかし、それぞれの閉じた多角形の頂点配列を分けて処理するというか、頂点配列の中に区切りヒントでも埋め込むか、そんな感じで簡単に対応できそうです。

#include "stdafx.h"
#include <stdio.h>
#include <assert.h>

/*!
  2次元ベクトルクラス.
*/
class VEC2{
public:
  float x, y;
public:
  VEC2() : x(0.0f), y(0.0f){;}
  VEC2( const float& cx, const float& cy ) : x(cx), y(cy){;}
  ~VEC2(){;}

  void  set( const float& cx, const float& cy ){
    x = cx;
    y = cy;
  }

  bool  insidePolygon( const int& numV, const VEC2* pV );
};

/*!
  与えた多角形データの内側かどうかを判定します.
  @note 線上は外側とみなします.
  @note 多角形は凸多角形でなくてもよく、内側に切抜きがあっても良い.
  @param  numV  [in]  頂点数(pVの要素数).
  @param  pV    [in]  多角形データの頂点データ配列.
*/
#define DEBUG_INSIDEPOLYGON 1 // 1のとき、逆側のチェック.
bool VEC2::insidePolygon( const int& numV, const VEC2* pV )
{
  if( numV < 3 ){
    return false;
  }

  bool  retInside = false;
#if DEBUG_INSIDEPOLYGON
  bool  retInside_d = false;
#endif
  int   i, j;
  float xx;
  for( i=0; i<numV; i++ ){
    j = i+1;
    if( j>=numV ){
      j -= numV;
    }
    const VEC2& pv0 = pV[i];
    const VEC2& pv1 = pV[j];

    // まず, Y座標のチェック.
    if( (y <= pv0.y && y <= pv1.y) 
      || (y >= pv0.y && y >= pv1.y) ){
      // チェック対象にならないのでスキップ.
      continue;
    }
    // 次に, X座標のチェック.
    xx = ((pv1.x-pv0.x)/(pv1.y-pv0.y)) * (y-pv0.y) + pv0.x;
    if( x < xx ){
      retInside = !retInside; // flip.
    }
#if DEBUG_INSIDEPOLYGON
    if( x > xx ){
      retInside_d = !retInside_d; // flip.
    }
#endif
  }

#if DEBUG_INSIDEPOLYGON
  assert( retInside == retInside_d );
#endif
  return retInside;
}

bool test_insidePolygon()
{
  // テストする矩形.
  int   numPolygon  = 4;
  VEC2  polygon[4];
  polygon[0].set( 1.0f,  1.0f );
  polygon[1].set( 3.0f, -2.0f );
  polygon[2].set( 5.0f,  1.0f );
  polygon[3].set( 2.0f,  3.0f );

  bool  br;
  br  = VEC2(2.0f, 0.0f).insidePolygon( numPolygon, polygon );
  if( br != true ){
    return false;
  }

  br  = VEC2(0.0f, 0.0f).insidePolygon( numPolygon, polygon );
  if( br != false ){
    return false;
  }

  br  = VEC2(2.0f, 3.0f).insidePolygon( numPolygon, polygon );
  if( br != false ){
    return false;
  }

  printf("テスト成功.\n");
  return true;
}

int _tmain(int argc, _TCHAR* argv[])
{
  bool  result  = test_insidePolygon();
  assert( result );

  return 0;
}

なるほどね〜。
(2007-05-05 バグ修正.. forループのインデックスチェックバグ)