두 선이 서로 교차하는지 여부를 확인하려면 어떻게해야합니까?
답변
벡터 교차 곱을 사용하는이 문제에 대한 좋은 접근 방식이 있습니다. 2 차원 벡터 교차 곱 v × w 를 v x w y − v y w x로 정의 합니다.
두 개의 선분이 p 에서 p + r로 , q 에서 q + s로 실행된다고 가정합니다 . 그러면 첫 번째 행에있는 모든 포인트로서 표현할 수있다 P + t R (스칼라 파라미터에 대한 t )와 같은 두번째 라인상의 임의의 지점 (Q) + U S (스칼라 파라미터에 대한 U ).
두 줄 은 다음과 같이 t 와 u를 찾을 수 있으면 교차 합니다.
p + t r = q + u s
s로 양쪽을 가로 지르십시오.
( p + t r ) × s = ( q + u s ) × s
그리고 이후 의 × S = 0,이 수단
t ( r × s ) = ( q − p ) × s
따라서 t에 대한 해결 :
t = ( q – p ) × s / ( r × s )
같은 방식으로 u를 풀 수 있습니다 .
( p + t r ) × r = ( q + u s ) × r
u ( s × r ) = ( p – q ) × r
u = ( p − q ) × r / ( s × r )
계산 단계 수를 줄이려면 다음과 같이 다시 작성하는 것이 편리합니다 ( s × r = − r × s 기억 ).
u = ( q – p ) × r / ( r × s )
이제 네 가지 경우가 있습니다.
-
경우 R × S = 0 ( Q – P ) × R = 0, 다음 두 라인을 동일 선상.
이 경우 첫 번째 선분 ( p + t r ) 의 방정식으로 두 번째 선분 ( q 및 q + s ) 의 끝점을 표현하십시오 .
t 0 = ( q − p ) · r / ( r · r )
t 1 = ( q + s − p ) · r / ( r · r ) = t 0 + s · r / ( r · r )
t 0 과 t 1 사이의 간격 이 간격 [0, 1]과 교차하면 선분은 동일 선상에 있고 겹칩니다. 그렇지 않으면 공선적이고 분리됩니다.
s 와 r 이 반대 방향을 가리키는 경우 , s · r <0이므로 확인할 간격은 [ t 0 , t 1 ]이 아니라 [ t 1 , t 0 ]입니다.
-
경우 R × S = 0 ( Q – P ) × R ≠ 0, 다음 두 라인이 평행이 아닌 교차한다.
-
만약 R × S ≠ 0, 0 ≤ t ≤ 1, 0 ≤ U ≤ 1, 두 선분이 만나는 지점에서 P + t R = Q + U S .
-
그렇지 않으면 두 선분이 평행하지 않지만 교차하지는 않습니다.
크레딧 :이 방법은 304 페이지 그래픽 젬에 출판 된 Ronald Goldman의 기사 “3 공간에서 2 개의 선의 교차점”에서 3D 선 교차 알고리즘의 2 차원 특수화입니다 . 3 차원에서 일반적인 경우는 다음과 같습니다. 선이 기울어 지거나 (병렬 또는 교차하지 않음),이 경우 방법은 두 선에 가장 가까운 접근 점을 제공합니다.
답변
FWIW에서 다음 함수 (C)는 선 교차를 감지하고 교차점을 결정합니다. Andre LeMothe의 ” Tricks of the Windows Game Programming Gurus ” 알고리즘을 기반으로합니다 . 다른 답변 (예 : Gareth)의 일부 알고리즘과 다르지 않습니다. 그런 다음 LeMothe는 Cramer ‘s Rule (나에게 묻지 않음)을 사용하여 방정식 자체를 해결합니다.
나는 그것이 연약한 소행성 클론에서 작동한다는 것을 증명할 수 있으며 Elemental, Dan 및 Wodzu의 다른 답변에 설명 된 가장자리 사례를 올바르게 처리하는 것으로 보입니다. KingNestor가 게시 한 코드보다 아마 빠를 수도 있습니다. 왜냐하면 그것은 모두 곱셈과 나눗셈이기 때문입니다.
내 경우에는 문제가되지 않았지만 0으로 나눌 가능성이 있다고 생각합니다. 어쨌든 충돌을 피하기 위해 쉽게 수정할 수 있습니다.
// Returns 1 if the lines intersect, otherwise 0. In addition, if the lines
// intersect the intersection point may be stored in the floats i_x and i_y.
char get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y,
float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
float s1_x, s1_y, s2_x, s2_y;
s1_x = p1_x - p0_x; s1_y = p1_y - p0_y;
s2_x = p3_x - p2_x; s2_y = p3_y - p2_y;
float s, t;
s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
{
// Collision detected
if (i_x != NULL)
*i_x = p0_x + (t * s1_x);
if (i_y != NULL)
*i_y = p0_y + (t * s1_y);
return 1;
}
return 0; // No collision
}
BTW, 나는 LeMothe의 책에서 분명히 알고리즘을 올바르게 얻지 만, 구체적인 숫자는 플러그를 잘못된 숫자로 표시하고 계산을 잘못한다고 말합니다. 예를 들면 다음과 같습니다.
(4 * (4-1) + 12 * (7-1)) / (17 * 4 + 12 * 10)
= 844 / 0.88
= 0.44
그것은 몇 시간 동안 나를 혼란스럽게했다 . 🙁
답변
문제는이 질문으로 줄어 듭니다. A에서 B로, C에서 D로 두 줄이 교차합니까? 그런 다음 네 번 요청할 수 있습니다 (직사각형의 선과 네면 사이).
여기에 대한 벡터 수학이 있습니다. A에서 B 로의 선이 문제의 선이고 C에서 D 로의 선이 직사각형 선 중 하나라고 가정합니다. 내 표기법은 Ax
“A의 x 좌표”이고 Cy
“C의 y 좌표”입니다. 그리고 ” *
“는 내적을 의미 A*B = Ax*Bx + Ay*By
합니다.
E = B-A = ( Bx-Ax, By-Ay )
F = D-C = ( Dx-Cx, Dy-Cy )
P = ( -Ey, Ex )
h = ( (A-C) * P ) / ( F * P )
이 h
숫자가 열쇠입니다. 경우 h
사이 0
와 1
, 선이 교차하는, 그렇지 않으면하지 않습니다. 경우는 F*P
물론이 계산을 할 수 없습니다의 제로이지만,이 경우 라인은 평행 따라서 만 교차 명백한 경우입니다.
정확한 교차점은 C + F*h
입니다.
더 재미있는:
경우 h
입니다 정확히 0
또는 1
라인은 엔드 포인트에서 누릅니다. 당신은 이것을 “교차”라고 생각할 수 있습니다.
특히, h
다른 선을 정확하게 터치하기 위해 선의 길이를 얼마나 곱해야 하는가입니다.
따라서이면 h<0
사각형 라인이 주어진 라인의 “뒤에”있고 ( “방향”은 “A에서 B로”), h>1
사각형 라인이 주어진 라인의 “앞에”있다는 것을 의미합니다.
유도:
A와 C는 선의 시작을 가리키는 벡터입니다. E와 F는 선을 형성하는 A와 C의 끝에서 나온 벡터입니다.
평면에서 임의의 두 개의 비 평행선, 정확히 하나의 스칼라 한쌍이 있어야 g
하고 h
,이 방정식은 유지되도록 :
A + E*g = C + F*h
왜? 두 개의 비평 행 선이 교차해야하므로 두 선을 일정량 씩 조정하고 서로 접촉 할 수 있습니다.
( 두 개의 미지수를 가진 하나의 방정식처럼 먼저이 외모에서! 그러나 당신이이 정말로에서 방정식의 한 쌍의 의미 차원 벡터 방정식이라고 생각하면 그렇지 않다 x
와 y
.)
이러한 변수 중 하나를 제거해야합니다. 쉬운 방법은 E
용어를 0 으로 만드는 것 입니다. 그렇게하려면 E로 0으로 점이 찍히는 벡터를 사용하여 방정식의 양변에있는 내적을 구하십시오. P
위에서 언급 한 벡터 는 E의 명백한 변형을 수행했습니다.
당신은 지금 :
A*P = C*P + F*P*h
(A-C)*P = (F*P)*h
( (A-C)*P ) / (F*P) = h
답변
위의 Jason이 우아하게 설명한 알고리즘을 구현하려고했습니다. 불행히도 디버깅의 수학을 통해 작업하는 동안 작동하지 않는 많은 사례를 발견했습니다.
예를 들어 점 A (10,10) B (20,20) C (10,1) D (1,10)은 h = .5를 나타내지 만 이러한 세그먼트가 각각의 근처에 있지 않다는 점을 분명히 알 수 있습니다 다른.
이를 그래프로 나타내면 0 <h <1 기준이 인터셉트 포인트가 CD에있을 경우에만 CD에 있다는 것을 나타내지 만 해당 포인트가 AB에 있는지 여부는 아무 것도 알려주지 않습니다. 교차점이 있는지 확인하려면 변수 g에 대해 대칭 계산을 수행해야하고 가로 채기 요구 사항은 다음과 같습니다. 0 <g <1 AND 0 <h <1
답변
Gavin의 답변이 개선되었습니다. marcp의 솔루션도 비슷하지만 부서를 연기하지는 않습니다.
이것은 실제로 Gareth Rees의 답변을 실제로 적용하는 것으로 밝혀졌습니다. 2D에서 교차 제품의 동등 물은 perp-dot-product이므로이 코드는 3 개를 사용합니다. 3D로 전환하고 교차 곱을 사용하여 끝에 s와 t를 보간하면 3D 선 사이에서 가장 가까운 두 지점이 생깁니다. 어쨌든 2D 솔루션 :
int get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y,
float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
float s02_x, s02_y, s10_x, s10_y, s32_x, s32_y, s_numer, t_numer, denom, t;
s10_x = p1_x - p0_x;
s10_y = p1_y - p0_y;
s32_x = p3_x - p2_x;
s32_y = p3_y - p2_y;
denom = s10_x * s32_y - s32_x * s10_y;
if (denom == 0)
return 0; // Collinear
bool denomPositive = denom > 0;
s02_x = p0_x - p2_x;
s02_y = p0_y - p2_y;
s_numer = s10_x * s02_y - s10_y * s02_x;
if ((s_numer < 0) == denomPositive)
return 0; // No collision
t_numer = s32_x * s02_y - s32_y * s02_x;
if ((t_numer < 0) == denomPositive)
return 0; // No collision
if (((s_numer > denom) == denomPositive) || ((t_numer > denom) == denomPositive))
return 0; // No collision
// Collision detected
t = t_numer / denom;
if (i_x != NULL)
*i_x = p0_x + (t * s10_x);
if (i_y != NULL)
*i_y = p0_y + (t * s10_y);
return 1;
}
기본적으로 마지막 순간까지 나누기를 연기하고 특정 계산이 완료되기 전까지 대부분의 테스트를 이동하여 조기 종료를 추가합니다. 마지막으로, 선이 평행 일 때 발생하는 0으로 나누지 않아도됩니다.
0과 비교하는 것이 아니라 엡실론 테스트를 사용하는 것이 좋습니다. 평행에 매우 가까운 선은 약간 벗어난 결과를 생성 할 수 있습니다. 이것은 버그가 아니며 부동 소수점 연산의 제한 사항입니다.
답변
질문 C : 두 선분이 교차하는지 여부를 어떻게 감지합니까?
나는 같은 주제를 검색했으며 그 대답에 만족하지 않았습니다. 그래서 두 선분 이 많은 이미지와 교차하는지 확인하는 방법을 매우 자세히 설명하는 기사를 작성했습니다 . 완전한 Java 코드가 있습니다.
다음은 가장 중요한 부분으로 잘린 기사입니다.
선분 a가 선분 b와 교차하는지 확인하는 알고리즘은 다음과 같습니다.
경계 상자 란 무엇입니까? 다음은 두 선분으로 된 두 개의 경계 상자입니다.
두 경계 상자에 교차점이 있으면 한 점이 (0 | 0)이되도록 선분 a를 이동합니다. 이제 a로 정의 된 원점을 통과하는 선이 생겼습니다. 이제 선분 b를 같은 방식으로 이동하고 선분 b의 새 점이 선 a의 다른쪽에 있는지 확인합니다. 이 경우 다른 방법으로 확인하십시오. 이 경우에도 선분이 교차합니다. 그렇지 않으면 교차하지 않습니다.
질문 A : 두 선분은 어디에 교차합니까?
두 선분 a와 b가 교차한다는 것을 알고 있습니다. 모르는 경우 “질문 C”에서 제공 한 도구를 사용하여 확인하십시오.
이제 몇 가지 사례를 살펴보고 7 학년 수학으로 솔루션을 얻을 수 있습니다 ( 코드 및 대화식 예제 참조 ).
질문 B : 두 선이 교차하는지 여부를 어떻게 감지합니까?
의이 점 가정 해 봅시다 A = (x1, y1)
, 포인트를 B = (x2, y2)
, C = (x_3, y_3)
, D = (x_4, y_4)
. 첫 번째 줄은 AB (A! = B)로, 두 번째 줄은 CD (C! = D)로 정의합니다.
function doLinesIntersect(AB, CD) {
if (x1 == x2) {
return !(x3 == x4 && x1 != x3);
} else if (x3 == x4) {
return true;
} else {
// Both lines are not parallel to the y-axis
m1 = (y1-y2)/(x1-x2);
m2 = (y3-y4)/(x3-x4);
return m1 != m2;
}
}
질문 D : 두 선은 어디에 교차합니까?
질문 B와 전혀 교차하는지 확인하십시오.
선 a와 b는 각 선에 대해 두 점으로 정의됩니다. 기본적으로 질문 A에서 사용한 것과 동일한 논리를 적용 할 수 있습니다.
답변
한 번 여기에 수락 된 답변은 올바르지 않습니다 (허용되지 않았으므로 hooray!). 교차로가 아닌 모든 부분을 올바르게 제거하지는 않습니다. 사소하게 작동하는 것처럼 보일 수 있지만 특히 0과 1이 h에 유효한 것으로 간주되는 경우 실패 할 수 있습니다.
다음과 같은 경우를 고려하십시오.
(4,1)-(5,1) 및 (0,0)-(0,2)의 줄
이들은 겹치지 않는 수직선입니다.
A = (4,1)
B = (5,1)
C = (0,0)
D = (0,2)
E = (5,1)-(4,1) = (-1,0)
F = (0,2)-(0,0) = (0, -2)
P = (0,1)
h = ((4,1)-(0,0)) 도트 (0,1) / ((0 , -2) 도트 (0,1)) = 0
위의 답변에 따르면,이 두 선분은 끝점 (값 0과 1)에서 만나게됩니다. 엔드 포인트는 다음과 같습니다.
(0,0) + (0, -2) * 0 = (0,0)
따라서, 두 개의 선분은 (0,0)에서 만나지 만, 선 CD에는 있지만 AB에는 없습니다. 무슨 일이야? 대답은 0과 1의 값이 유효하지 않으며 종점 교차점을 정확하게 예측하기 위해 때때로 발생한다는 것입니다. 한 선 (다른 선이 아닌)의 연장이 선 세그먼트를 충족 할 때 알고리즘은 선 세그먼트의 교차점을 예측하지만 올바르지 않습니다. AB vs CD로 테스트 한 다음 CD vs AB로 테스트하면이 문제가 해결 될 것이라고 생각합니다. 둘 다 0과 1 사이에있는 경우에만 교차 할 수 있습니다.
엔드 포인트를 예측해야하는 경우 벡터 교차 곱 방법을 사용하는 것이 좋습니다.
-단