본문으로 건너뛰기
김신건의 로그

(오목 포함) 다각형 내부 점 판정 (Point in Polygon)

· 수정 · 📖 약 2분 · 824자/단어 #algorithm #geometry #point-in-non-convex-polygon
point in polygon, ray casting, 다각형 내부 점 판정, crossing number, even-odd rule

정의

다각형 내부 점 판정 (Point in Polygon, PIP)단순 다각형 (볼록 또는 오목) 과 하나의 점이 주어졌을 때, 점이 내부/경계/외부 중 어디에 있는지 판별하는 문제. Ray casting (ray crossing) 알고리즘이 가장 일반적으로 사용되며, O(N) 시간에 동작한다. N 은 꼭짓점 개수.

문제 상황과 동기

단순 다각형 (볼록/오목 구분 없음) 과 하나의 점. 오목 다각형에서는 CCW 만으로 일관된 판정이 불가능.

  • Naive: 각 변마다 점과의 위치 관계를 확인하는 CCW 검사는 볼록일 때만 O(N). 오목에선 실패.
  • Ray casting: 점에서 임의의 방향으로 반직선을 그려 다각형 변과의 교차 횟수로 판정. O(N).

핵심 통찰: 다각형의 변은 내부와 외부를 가르는 경계선. 반직선이 변과 교차할 때마다 내부/외부 패리티가 바뀐다. 최종 교차 횟수가 홀수면 내부, 짝수면 외부.

시각화

핵심 아이디어

even-odd rule (홀짝 법칙):
  점 p 에서 오른쪽으로 수평 반직선을 긋는다.
  이 반직선과 다각형의 변이 교차하는 횟수를 센다.
  교차 횟수 % 2 == 1 → IN
  교차 횟수 % 2 == 0 → OUT
조건판정
교차 횟수 = 홀수내부 (IN)
교차 횟수 = 짝수 (0 포함)외부 (OUT)
점이 변 위에 있음경계 (ON)

반직선 방향은 어디든 상관없으나, 오른쪽 수평 이 구현이 가장 간단.

알고리즘

INPUT: polygon P[0..N-1] (CCW or CW), query point q
OUTPUT: IN / ON / OUT

count = 0
for i = 0..N-1:
    j = (i + 1) % N
    if q is on segment(P[i], P[j]): return ON

    // P[i].y < q.y <= P[j].y (upward crossing)
    // 또는 P[j].y < q.y <= P[i].y (downward crossing)
    if (P[i].y <= q.y < P[j].y) or (P[j].y <= q.y < P[i].y):
        // 반직선과 변의 교차 x 좌표 계산
        x_intersect = P[i].x + (q.y - P[i].y)
                        * (P[j].x - P[i].x) / (P[j].y - P[i].y)
        if q.x < x_intersect: count++

return count % 2 == 1 ? IN : OUT

주의: 한쪽 끝점만 inclusive 로 처리하여 꼭짓점 중복 교차 방지.

구현

// Ray casting O(N) 점의 다각형 포함 여부
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct Point { ll x, y; };
int ccw(Point a, Point b, Point c) {
  ll ret = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
  if (ret > 0) return 1;
  if (ret < 0) return -1;
  return 0;
}
bool onSegment(Point a, Point b, Point q) {
  if (ccw(a, b, q) != 0) return false;
  return min(a.x, b.x) <= q.x && q.x <= max(a.x, b.x)
      && min(a.y, b.y) <= q.y && q.y <= max(a.y, b.y);
}
string pip(vector<Point>& P, Point q) {
  int n = P.size(), cnt = 0;
  for (int i = 0; i < n; i++) {
      int j = (i + 1) % n;
      if (onSegment(P[i], P[j], q)) return "ON";
      if ((P[i].y <= q.y && q.y < P[j].y) || (P[j].y <= q.y && q.y < P[i].y)) {
          double x_intersect = P[i].x + (double)(q.y - P[i].y)
                             * (P[j].x - P[i].x) / (P[j].y - P[i].y);
          if (q.x < x_intersect) cnt++;
      }
  }
  return cnt % 2 ? "IN" : "OUT";
}
int main() {
  int n, q; cin >> n >> q;
  vector<Point> P(n);
  for (auto& p : P) cin >> p.x >> p.y;
  while (q--) {
      Point qp; cin >> qp.x >> qp.y;
      cout << pip(P, qp) << "\n";
  }
}
stdin
6 4
0 0
4 0
4 2
2 2
2 4
0 4
1 1
5 1
2 2
0 0
결과
IN
OUT
ON
ON

복잡도

항목
점 1개 판정O(N)
Q 개 점O(N Q)
공간O(N)

변형 / 활용

  • 볼록 다각형: O(log N) 이분 탐색 (볼록 다각형 내부 점 판정).
  • Winding number: crossing number 와 달리 방향 고려, self-intersecting 다각형에서도 올바름. CCW 부호를 누적해 wn != 0 이면 내부.
  • Grid point location: 쿼리가 많으면 plane sweep 으로 O(N log N) 전처리 후 O(log N) 쿼리.
  • GIS / 그래픽스: 구역 판별, hit testing, 영역 필터링.

함정

  1. 꼭짓점 중복 교차: 반직선이 꼭짓점 통과 시 두 번 세는 문제. upward/downward edge 끝점을 한쪽만 inclusive.
  2. 수평 변: 반직선과 평행하면 무시. y 비교 부등호로 자연히 걸러짐.
  3. 부동소수점 오차: 교차점 x 계산에 double. 정수 좌표면 유리수 유지 또는 CCW 만으로 onSegment 검사.
  4. 경계 점: 점이 변 위일 때 ON. onSegment 함수로 먼저 검사.

BOJ 연습 문제

번호제목정답률링크
BOJ 1688지민이의 테러23.417%kokoa-lab
BOJ 2254감옥 건설33.149%kokoa-lab
BOJ 3679단순 다각형26.233%kokoa-lab

참고

이 글의 용어 (4개)
각도 정렬 (Angle Sorting)algorithm
정의 각도 정렬 (Angle Sorting / Polar Sort) 은 2차원 평면 위의 점들을 기준점에 대한 극각 (polar angle) 순서로 정렬하는 기법. atan2 함…
볼록 껍질 (Convex Hull)algorithm
정의 볼록 껍질 (Convex Hull) 은 주어진 점 집합 P 를 모두 포함하는 최소 크기의 볼록 다각형. 즉 P 의 어떤 점도 다각형 외부에 있지 않고, 다각형의 꼭짓점은 P…
볼록 다각형 내부 점 판정 (Point in Convex Polygon)algorithm
정의 볼록 다각형 내부 점 판정 은 볼록 다각형(convex polygon) 과 하나의 점이 주어졌을 때, 점이 다각형의 내부/경계/외부 중 어디에 있는지 O(log N) 에 판…
Geometry Basic: 벡터, CCW, 외적, 내적algorithm
정의 기하 알고리즘의 밑바탕. 대부분의 2D/3D 기하 문제는 벡터 연산, 내적/외적, CCW 판정 세 가지의 조합으로 풀립니다. 벡터 2D 벡터: 두 점 $A = (ax, ay…

이 개념을 다룬 위키 페이지 (1)

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기