티스토리 뷰

728x90

0. 들어가기 전에

첫 알고리즘 포스트입니다. 이번에 쓸 내용은 CCW입니다.

원래는 기하 알고리즘들을 전반적으로 다루려고 했는데 생각보다 글이 길어져서 CCW만 쓰게 되었습니다.

 

본 글의 내용은 고등학교 과정(2007 개정 교육과정 기준)인 '기하와 벡터' 수준의 지식만 있으면 얼추 알아들을 수 있지만, 혹시 그렇지 않다면 설명을 봐도 이해가 안가실 수 있습니다.

급하게 CCW를 사용해야 한다면 바로 아래의 한 줄 요약과 본문 하단의 코드(여기)만 보고 쓰시면 됩니다.

 

1. CCW

기하 알고리즘을 공부하기 전에 필수로 알아야 할 것이 있는데, 그게 바로 CCW입니다. 거의 사칙연산처럼 쓰게 될 거에요.

 

CCW는 Counter Clockwise의 약자로써, 평면 위에 놓여진 세 점의 방향관계를 구할 수 있는 알고리즘입니다.

먼저 한 줄로 요약하자면, CCW는 점 A,B,C를 순서대로 봤을때 반시계방향으로 놓여 있으면 양수를, 시계방향이면 음수를, 평행하게 놓여있으면 0을 반환하게 됩니다. 

 

그럼 세 점의 방향관계를 어떻게 판별할까요?

우선 우리는 세 점으로부터 벡터 a=AB,  b=AC를 놓을 수 있습니다.

 

 

 

이때 ab의 외적의 방향으로 점 A,B,C의 방향을 판단 할 수 있습니다.

 

2. 외적 - 기초 지식

들어가기에 앞서 잠깐 설명할 것이 있습니다.

 

1) 외적은 기호 × 를 사용하여 표기합니다.

외적의 결과로 외적에 쓰인 두 벡터와 동시에 수직인 벡터를 구할 수 있습니다. 이때 이 수직벡터의 방향으로 세 점의 방향 관계를 판단할 수 있습니다.

 

2) 외적은 교환법칙이 성립하지 않습니다. 따라서 a×bb×a는 다릅니다. (사실 크기는 같습니다.)

3) 모든 성분이 0인 벡터를 0 벡터라 하며, 0 으로 표기합니다.

또한 스칼라 0과 임의의 벡터 v 에 대해, 0v = 0 입니다.

 

4) 길이가 1인 벡터를 단위벡터(unit vector)라 합니다.

또한 각각 x축, y축, z축의 양의 방향으로 향하는 단위벡터를 특별히 ˆi, ˆj, ˆk 로 표기하겠습니다.

 

3. 외적

na,b에 수직인 단위벡터라 하고, θa,b가 이루는 각도라 하면 다음과 같습니다.

a×b=(|a||b|sinθ)n, (0θπ)

 

우선 θ=0 혹은 θ=π이면 |a||b|sinθ=0이므로, 외적의 결과는 0n=0임을 알 수 있습니다.

여기서 θ=0 or θ=π는 두 벡터가 평행한 경우, 즉, 세 점이 일직선 상에 있는 경우를 의미합니다.

 

그 외의 경우를 생각해보죠.

A,B,C가 2차원 평면에 놓여있기 때문에 n=ˆk 또는 n=ˆk 입니다.

왜인지 이해가 안가셔도 괜찮습니다. 밑에서 명확하게 알 수 있습니다.

 

이 방향은 어떻게 정해지느냐, 공대의 만능 법칙인 오른손 법칙을 따릅니다.

 

(그림출처 http://furthermathematicst.blogspot.com/2011/07/121-3d-vectors.html)

 

정말 명확하긴 한데... 이건 그림만 보고 판단할 때 가능한 방법입니다.

컴퓨터는 오른손이 없으니 일단 외적을 직교좌표계로 나타내봅니다.

 

어떤 두 벡터 u=(m1, m2, m3), v=(n1, n2, n3)가 있다고 합시다. u×v의 결과는 다음과 같습니다. 중간 행렬식은 생략하고 마지막 결과만 보셔도 됩니다.

u×v=|ˆiˆjˆkm1m2m3n1n2n3|=|m2m3n2n3|ˆi|m1m3n1n3|ˆj+|m1m2n1n2|ˆk

=(m2n3m3n2, m3n1m1n3, m1n2m2n1)

 

이렇게 임의의 두 벡터의 외적을 구했습니다.

우리가 고려하는 벡터는 2차원 평면위에 놓인 벡터입니다. 그러므로 z축 성분인 m3n3이 0이어야 합니다.

이를 고려하여 다음과 같은 결과를 얻을 수 있습니다.

u×v=(0, 0, m1n2m2n1)

 

어떤가요? 이제 외적의 결과가 xy평면에 수직이라는 것이 명확해지지 않았나요?

거기다 m1n2m2n1의 부호에 따라서 점 A, B, C의 방향을 구별할 수 있습니다.

오른손 법칙을 생각하시면 됩니다!

 

값에 따른 방향성은 다음과 같습니다.

D=m1n2m2n1라 합시다.

D>0이면 반시계방향입니다.

D=0인 경우는 처음에 다뤘습니다. 이때는 평행합니다.

D<0이면 시계방향입니다.

 

4. 평면위의 세 점의 방향성 판별하기

이제 방금 예시로 들었던 벡터 u, v가 아닌, 아까 우리가 세 점으로부터 정의한 평면 위의 벡터 a, b에 대해서 생각해봅시다.

우선 점 A, B, C의 좌표를 정의합니다.

A=(x1, y1, 0),B=(x2, y2, 0),C=(x3, y3, 0) 라고 하겠습니다.

a=AB=(x2x1, y2y1, 0), b=AC=(x3x1, y3y1, 0) 입니다.

 

아까 u×v=(0, 0, m1n2m2n1)라고 했습니다.

따라서 a×b의 결과는 다음과 같습니다.

a×b=(0, 0, (x2x1)(y3y1)(x3x1)(y2y1)

 

이 결과를 바탕으로 우리는 세 점의 좌표만 가지고도 방향성을 구할 수 있게 되었습니다.

 

5. 코드 구현

다음은 이를 코드로 구현한 것입니다.

 

struct Point {
int x = 0, y = 0;
};
int ccw(Point A, Point B, Point C) {
return (B.x - A.x)*(C.y - A.y) - (C.x - A.x)*(B.y - A.y);
}
view raw ccw.cpp hosted with ❤ by GitHub

 

또한 struct를 쓰지 않고도 사용할 수 있습니다.

 

위 식을 전개해봅시다.

(x2x1)(y3y1)(x3x1)(y2y1)

=x1y2+x2y3+x3y1(y1x2+y2x3+y3x1)

바로 이 식입니다.

 

복잡해보이지만 의외로 외우기 쉽습니다.

사선공식, 혹은 신발끈 공식이라고 불리는데, 세 점의 좌표가 주어질 때 삼각형의 넓이를 쉽게 구할 수 있는 공식입니다.

 

우선 다음과 같이 좌표들을 배치합니다.

|x1x2x3x1y1y2y3y1|

 

빨간색 빗금을 각각 곱한 뒤 모두 더해주면 됩니다.

x1y2+x2y3+x3y1 입니다.

 

파란색 빗금은 각각 곱한 뒤 모두 빼주면 됩니다.

(y1x2+y2x3+y3x1) 입니다.

 

 

이를 코드로 구현하면 다음과 같습니다.

int ccw(int x1, int y1, int x2, int y2, int x3, int y3) {
int a = x1 * y2 + x2 * y3 + x3 * y1;
int b = y1 * x2 + y2 * x3 + y3 * x1;
return a - b;
}
view raw ccw.cpp hosted with ❤ by GitHub

 

번외) 세 점의 좌표로 삼각형의 넓이 구하기

방금 사선공식이 삼각형의 넓이를 구할때 쓰인다고 했는데요, 이에 대해 알아봅시다.

아까전에(여기) 외적의 값이 다음과 같다고 했습니다.

a×b=(|a||b|sinθ)n, (0θπ)

그렇다면 외적의 크기 |a×b|는 다음과 같습니다.

|a×b|=|a||b|sinθ, (0θπ)

 

네, 삼각형 면적을 구하는 공식 중에 이런 공식이 있었습니다.

=12absinθ

위의 삼각형에서 a, b를 벡터로 본다면, 그 외적의 크기는 ab를 변으로 하는 평행사변형의 넓이입니다. 

따라서 CCW함수에서 반환하는 값에 절댓값을 씌우고, 2로 나눠준다면 세 점이 이루는 삼각형의 넓이를 구할 수 있게 됩니다. 넓이에 관한 이 성질은 나중에 다각형의 면적을 구할 때도 이용하게 됩니다.

 

 


 

 

질문 및 피드백은 항상 환영합니다.

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함