두 선분이 교차하는 곳을 어떻게 감지합니까? [닫은]


518

두 선이 서로 교차하는지 여부를 확인하려면 어떻게해야합니까?


사각형의 가장자리를 완전한 다각형 대신 별도의 선으로 생각하면 도움이 될 수 있습니다.
Ryan Graham

중재자 참고 사항 :이 게시물이 주제에 있는지 여부에 대한 토론은 Meta Stack Overflow 에 속해 있습니다. 여기에 관한 추가 설명이 삭제됩니다.
Martijn Pieters

답변:


659

벡터 교차 곱을 사용하는이 문제에 대한 좋은 접근 방식이 있습니다. 2 차원 벡터 교차 곱 v  ×  wv x  w y  −  v y  w x로 정의 합니다.

두 개의 선분이 p 에서 p  +  r로 , q 에서 q  +  s로 실행된다고 가정합니다 . 그러면 첫 번째 행에있는 모든 포인트로서 표현할 수있다 P  +  t  R (스칼라 파라미터에 대한  t )와 같은 두번째 라인상의 임의의 지점 (Q)  +  U  S (스칼라 파라미터에 대한  U ).

교차하는 두 선분

두 줄 은 다음과 같이 tu를 찾을 수 있으면 교차 합니다.

p + t  r = q + u  s

교차점에 대한 공식

s로 양쪽을 가로 지르십시오.

( p + t  r ) × s = ( q + u  s ) × s

그리고 이후  ×  S = 0,이 수단

t  ( r × s ) = ( qp ) × s

따라서 t에 대한 해결 :

t = ( q - p ) × s / ( r × s )

같은 방식으로 u를 풀 수 있습니다 .

( p + t  r ) × r = ( q + u  s ) × r

u  ( s × r ) = ( p - q ) × r

u = ( pq ) × r / ( s × r )

계산 단계 수를 줄이려면 다음과 같이 다시 작성하는 것이 편리합니다 ( s  ×  r = −  r  ×  s 기억 ).

u = ( q - p ) × r / ( r × s )

이제 네 가지 경우가 있습니다.

  1. 경우 R  ×  S  = 0 ( Q  -  P ) ×  R  = 0, 다음 두 라인을 동일 선상.

    이 경우 첫 번째 선분 ( p + t r ) 의 방정식으로 두 번째 선분 ( qq  +  s ) 의 끝점을 표현하십시오 .

    t 0 = ( qp ) ·  r / ( r  ·  r )

    t 1 = ( q + sp ) ·  r / ( r  ·  r ) = t 0 + s  ·  r / ( r  ·  r )

    t 0t 1 사이의 간격 이 간격 [0, 1]과 교차하면 선분은 동일 선상에 있고 겹칩니다. 그렇지 않으면 공선적이고 분리됩니다.

    sr 이 반대 방향을 가리키는 경우 , s  ·  r <0이므로 확인할 간격은 [ t 0 , t 1 ]이 아니라 [ t 1 , t 0 ]입니다.

  2. 경우 R  ×  S  = 0 ( Q  -  P ) ×  R  ≠ 0, 다음 두 라인이 평행이 아닌 교차한다.

  3. 만약 R  ×  S  ≠ 0, 0 ≤  t  ≤ 1, 0 ≤  U  ≤ 1, 두 선분이 만나는 지점에서 P + t  R = Q + U  S .

  4. 그렇지 않으면 두 선분이 평행하지 않지만 교차하지는 않습니다.

크레딧 :이 방법은 304 페이지 그래픽 젬에 출판 된 Ronald Goldman의 기사 "3 공간에서 2 개의 선의 교차점"에서 3D 선 교차 알고리즘의 2 차원 특수화입니다 . 3 차원에서 일반적인 경우는 다음과 같습니다. 선이 기울어 지거나 (병렬 또는 교차하지 않음),이 경우 방법은 두 선에 가장 가까운 접근 점을 제공합니다.


5
@myrkos : 아니요. 첫 번째 라인 세그먼트는 "p에서 p + r까지"실행되므로 매개 변수 용어로 "p + tr"로 표시되면 세그먼트는 0 ≤ t ≤ 1에 해당합니다.
Gareth Rees

7
가레스, 뭔가 빠졌어야한다고 생각하지만 어떻게 벡터를 벡터로 나눕니 까? tu에 대한 솔루션은로 끝나지 / (r × s)(r × s)벡터입니까? 벡터 (0, 0, rx * sy - ry * sx). 왼쪽도 마찬가지로 z 축에 평행 한 벡터입니다. 그래서 ... z 구성 요소를 다른 z 구성 요소로 나눕니 까? t의 공식은 실제로 |(q − p) × s| / |(r × s)|입니까?
LarsH

7
@LarsH : 첫 번째 단락을 참조하십시오.
Gareth Rees

35
관심있는 사람들을 위해 간단한 C # 구현으로 PointF 시작 및 끝 좌표를 사용하여 작동합니다. ideone.com/PnPJgb
Matt

24
내가 함께 넣어 자바 스크립트 구현 @ 매트 다음. Tekito가 지적한 오류를 수정했습니다.
pgkelley

230

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

그것은 몇 시간 동안 나를 혼란스럽게했다 . :(


9
getLineIntersection 함수 (p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y) {var 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; var 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);
cortijon

5
if (s> = 0 && s <= 1 && t> = 0 && t <= 1) {// 충돌 감지 var intX = p0_x + (t * s1_x); var intY = p0_y + (t * s1_y); 반환 [intX, intY]; } null을 반환; // 충돌 없음}
cortijon

13
좋은 알고리즘이지만, 결정자가 0 인 경우를 처리하지 않습니다 (위의 -s2_x * s1_y + s1_x * s2_y). 0이거나 0에 가까운 경우 선은 평행 또는 동일 선상에 있습니다. 동일 선상에 있으면 교차점이 다른 선분 일 수 있습니다.
seand

16
두 개의 분할 연산은 속도를 피할 수 있습니다 (구분보다 곱셈 비용이 더 큼). 선이 교차하면 하나의 나눗셈이 필요하고, 교차하지 않으면 0이 필요합니다. 먼저 분모를 계산하고 0 인 경우 일찍 중지해야합니다 (공선 성을 탐지하기위한 코드 추가). 그런 다음 계산 s하고 t직접 계산하는 대신 두 분자와 분모 사이의 관계를 테스트하십시오. 선이 교차하는 것으로 확인 된 경우에만 실제로 값을 계산해야합니다 t(그러나 아님 s).
Qwertie

18
여기에 게시 된 모든 알고리즘에서 성능 테스트를 수행 했으며이 알고리즘은 다른 알고리즘보다 적어도 두 배 빠릅니다. 게시 해 주셔서 감사합니다!
lajos

63

문제는이 질문으로 줄어 듭니다. 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사이 01, 선이 교차하는, 그렇지 않으면하지 않습니다. 경우는 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

왜? 두 개의 비평 행 선이 교차해야하므로 두 선을 일정량 씩 조정하고 서로 접촉 할 수 있습니다.

( 두 개의 미지수를 가진 하나의 방정식처럼 먼저이 외모에서! 그러나 당신이이 정말로에서 방정식의 한 쌍의 의미 차원 벡터 방정식이라고 생각하면 그렇지 않다 xy.)

이러한 변수 중 하나를 제거해야합니다. 쉬운 방법은 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

29
이 알고리즘은 훌륭합니다. 그러나 Dan @ stackoverflow.com/questions/563198 / ... 및 Elemental @ stackoverflow.com/questions/563198/에서 지적한대로 구멍 이 있습니다. 나중에 참조 할 수 있도록 답변을 업데이트하면 좋을 것입니다. 감사.
Chantz

2
이 알고리즘은 수치 적으로 안정적입니까? 나는 비슷한 aproach를 시도했고 float에서 작업 할 때 이상한 결과를내는 것으로 밝혀졌습니다.
milosz

3
이 알고리즘에 다른 문제가있는 것 같습니다. A = {1, 0} B = {2, 0} C = {0, 0} D = {1,0} 점이 공급되면 선 세그먼트가 끝점, F P (및 E 아래 사용자의 수정에 따라 Q)는 모두 0이므로 0으로 나누면 h와 g를 찾습니다. 여전히이 솔루션에 대한 작업을하고 있지만 문제가 지적 할 가치가 있다고 생각했습니다.
candrews

12
이 답변은 간단하지 않습니다. A = {0,0}, B = {0,1}, C = {0,2} D = {2,0}을 시도하십시오
Tim Cooper

6
A + E*g = C + F*h두 라인이 교차하는 경우에만, 그 방정식의 해결책 (이들이 평행하지 않은 가정)을 갖는 gh 0과 1 사이 (만약 종료점 접촉 카운트 여부에 따라 단독 또는 IN-).
다니엘 피셔

46

위의 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


2
수락 된 답변이 왜 효과가 없는지 알아 내려고 노력했습니다. 정말 고마워!
Matt Bridges

1
이 경우 경계 조건이 작동한다는 점도 주목할 만하다 (즉, h = 0 또는 h = 1 또는 g = 0 또는 g = 1 인 경우 라인은 '터치'터치
Elemental

결과를 시각화하는 데 어려움이있는 사람들을 위해 Javascript에서 이것을 구현했습니다 : jsfiddle.net/ferrybig/eokwL9mp
Ferrybig

45

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과 비교하는 것이 아니라 엡실론 테스트를 사용하는 것이 좋습니다. 평행에 매우 가까운 선은 약간 벗어난 결과를 생성 할 수 있습니다. 이것은 버그가 아니며 부동 소수점 연산의 제한 사항입니다.


1
일부 포인트의 값이 0. 인 경우 실패합니다. 올바르게 수행되지 않아야합니까?
hfossli

1
나누기를 연기 할 때 도입 된 버그가 수정되었습니다. 숫자와 음수가 모두 음수 일 때 t는 양수일 수 있습니다.
iMalc

2
p0-p1이 수직이고 p2-p3이 수평이고 두 세그먼트가 교차하는 경우 작동하지 않습니다. (첫 번째 복귀 실행)
파비오 Dalla 리베라에게

Coolinear 케이스에는 두 가지 가능성이 있습니다 : 비 중첩 및 겹치기. 첫 번째 shoul은 두 번째 true를 false로 반환합니다. 코드에서 이것은 테스트되지 않았습니다. 여기에서 대부분의 답변으로 항상 false를 반환합니다. 해결책이 실제로 작동하지 않는 것은 부끄러운 일입니다.
AlexWien

3
당신은 저를 계몽 할 수있는 이유 등 모든 사용 등의 모호한 변수 이름 s32_y대신이 같은 기능에 대해 설명 무엇인가 point2YDifference?
Supuhstar

40

질문 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에서 사용한 것과 동일한 논리를 적용 할 수 있습니다.


15
분명히,이 답변의 질문 B는 실제로 선분이 아닌 두 선이 교차하는 것에 관한 것입니다. 나는 불평 하는게 아니야; 정확하지 않습니다. 누군가가 잘못 인도되기를 원하지 않습니다.
phord

1
"질문 C"는 없습니다. 그리고 질문 D는 질문 A로 다시 되돌아옵니다.
Konrad Viltersten

21

한 번 여기에 수락 된 답변은 올바르지 않습니다 (허용되지 않았으므로 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 사이에있는 경우에만 교차 할 수 있습니다.

엔드 포인트를 예측해야하는 경우 벡터 교차 곱 방법을 사용하는 것이 좋습니다.

-단


4
"허용 된"답변은 변경 될 수 있으므로 다른 것으로 바꿔야합니다. (사실, 나는 당신의 의견 이후에 그것이 바뀌 었다고 생각합니다)
Johannes Hoff

14

iMalc의 Python 버전 답변 :

def find_intersection( p0, p1, p2, p3 ) :

    s10_x = p1[0] - p0[0]
    s10_y = p1[1] - p0[1]
    s32_x = p3[0] - p2[0]
    s32_y = p3[1] - p2[1]

    denom = s10_x * s32_y - s32_x * s10_y

    if denom == 0 : return None # collinear

    denom_is_positive = denom > 0

    s02_x = p0[0] - p2[0]
    s02_y = p0[1] - p2[1]

    s_numer = s10_x * s02_y - s10_y * s02_x

    if (s_numer < 0) == denom_is_positive : return None # no collision

    t_numer = s32_x * s02_y - s32_y * s02_x

    if (t_numer < 0) == denom_is_positive : return None # no collision

    if (s_numer > denom) == denom_is_positive or (t_numer > denom) == denom_is_positive : return None # no collision


    # collision detected

    t = t_numer / denom

    intersection_point = [ p0[0] + (t * s10_x), p0[1] + (t * s10_y) ]


    return intersection_point

숫자를 부동으로 만들거나 8 행을 변경하여 사용해야합니다.denom = float(...)
Jonno_FTW 2

11

두 선분의 올바른 교차점을 찾는 것은 많은 경우가있는 사소한 작업입니다. 다음은 잘 문서화되고 작동하며 테스트 된 Java 솔루션입니다.

본질적으로 두 선분의 교차점을 찾을 때 발생할 수있는 세 가지가 있습니다.

  1. 세그먼트가 교차하지 않습니다

  2. 독특한 교차점이 있습니다

  3. 교차점은 또 다른 세그먼트입니다

참고 : 코드에서 x1 = x2 및 y1 = y2 인 선분 (x1, y1), (x2, y2)가 유효한 선분이라고 가정합니다. 수학적으로 말하면 선 세그먼트는 별개의 점으로 구성되지만이 구현에서 세그먼트가 점이 될 수 있습니다.

코드는 내 github 저장소 에서 가져옵니다.

/**
 * This snippet finds the intersection of two line segments.
 * The intersection may either be empty, a single point or the
 * intersection is a subsegment there's an overlap.
 */

import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.lang.Math.min;

import java.util.ArrayList;
import java.util.List;

public class LineSegmentLineSegmentIntersection {

  // Small epsilon used for double value comparison.
  private static final double EPS = 1e-5;

  // 2D Point class.
  public static class Pt {
    double x, y;
    public Pt(double x, double y) {
      this.x = x; 
      this.y = y;
    }
    public boolean equals(Pt pt) {
      return abs(x - pt.x) < EPS && abs(y - pt.y) < EPS;
    }
  }

  // Finds the orientation of point 'c' relative to the line segment (a, b)
  // Returns  0 if all three points are collinear.
  // Returns -1 if 'c' is clockwise to segment (a, b), i.e right of line formed by the segment.
  // Returns +1 if 'c' is counter clockwise to segment (a, b), i.e left of line
  // formed by the segment.
  public static int orientation(Pt a, Pt b, Pt c) {
    double value = (b.y - a.y) * (c.x - b.x) - 
                   (b.x - a.x) * (c.y - b.y);
    if (abs(value) < EPS) return 0;
    return (value > 0) ? -1 : +1;
  }

  // Tests whether point 'c' is on the line segment (a, b).
  // Ensure first that point c is collinear to segment (a, b) and
  // then check whether c is within the rectangle formed by (a, b)
  public static boolean pointOnLine(Pt a, Pt b, Pt c) {
    return orientation(a, b, c) == 0 && 
           min(a.x, b.x) <= c.x && c.x <= max(a.x, b.x) && 
           min(a.y, b.y) <= c.y && c.y <= max(a.y, b.y);
  }

  // Determines whether two segments intersect.
  public static boolean segmentsIntersect(Pt p1, Pt p2, Pt p3, Pt p4) {

    // Get the orientation of points p3 and p4 in relation
    // to the line segment (p1, p2)
    int o1 = orientation(p1, p2, p3);
    int o2 = orientation(p1, p2, p4);
    int o3 = orientation(p3, p4, p1);
    int o4 = orientation(p3, p4, p2);

    // If the points p1, p2 are on opposite sides of the infinite
    // line formed by (p3, p4) and conversly p3, p4 are on opposite
    // sides of the infinite line formed by (p1, p2) then there is
    // an intersection.
    if (o1 != o2 && o3 != o4) return true;

    // Collinear special cases (perhaps these if checks can be simplified?)
    if (o1 == 0 && pointOnLine(p1, p2, p3)) return true;
    if (o2 == 0 && pointOnLine(p1, p2, p4)) return true;
    if (o3 == 0 && pointOnLine(p3, p4, p1)) return true;
    if (o4 == 0 && pointOnLine(p3, p4, p2)) return true;

    return false;
  }

  public static List<Pt> getCommonEndpoints(Pt p1, Pt p2, Pt p3, Pt p4) {

    List<Pt> points = new ArrayList<>();

    if (p1.equals(p3)) {
      points.add(p1);
      if (p2.equals(p4)) points.add(p2);

    } else if (p1.equals(p4)) {
      points.add(p1);
      if (p2.equals(p3)) points.add(p2);

    } else if (p2.equals(p3)) {
      points.add(p2);
      if (p1.equals(p4)) points.add(p1);

    } else if (p2.equals(p4)) {
      points.add(p2);
      if (p1.equals(p3)) points.add(p1);
    }

    return points;
  }

  // Finds the intersection point(s) of two line segments. Unlike regular line 
  // segments, segments which are points (x1 = x2 and y1 = y2) are allowed.
  public static Pt[] lineSegmentLineSegmentIntersection(Pt p1, Pt p2, Pt p3, Pt p4) {

    // No intersection.
    if (!segmentsIntersect(p1, p2, p3, p4)) return new Pt[]{};

    // Both segments are a single point.
    if (p1.equals(p2) && p2.equals(p3) && p3.equals(p4))
      return new Pt[]{p1};

    List<Pt> endpoints = getCommonEndpoints(p1, p2, p3, p4);
    int n = endpoints.size();

    // One of the line segments is an intersecting single point.
    // NOTE: checking only n == 1 is insufficient to return early
    // because the solution might be a sub segment.
    boolean singleton = p1.equals(p2) || p3.equals(p4);
    if (n == 1 && singleton) return new Pt[]{endpoints.get(0)};

    // Segments are equal.
    if (n == 2) return new Pt[]{endpoints.get(0), endpoints.get(1)};

    boolean collinearSegments = (orientation(p1, p2, p3) == 0) && 
                                (orientation(p1, p2, p4) == 0);

    // The intersection will be a sub-segment of the two
    // segments since they overlap each other.
    if (collinearSegments) {

      // Segment #2 is enclosed in segment #1
      if (pointOnLine(p1, p2, p3) && pointOnLine(p1, p2, p4))
        return new Pt[]{p3, p4};

      // Segment #1 is enclosed in segment #2
      if (pointOnLine(p3, p4, p1) && pointOnLine(p3, p4, p2))
        return new Pt[]{p1, p2};

      // The subsegment is part of segment #1 and part of segment #2.
      // Find the middle points which correspond to this segment.
      Pt midPoint1 = pointOnLine(p1, p2, p3) ? p3 : p4;
      Pt midPoint2 = pointOnLine(p3, p4, p1) ? p1 : p2;

      // There is actually only one middle point!
      if (midPoint1.equals(midPoint2)) return new Pt[]{midPoint1};

      return new Pt[]{midPoint1, midPoint2};
    }

    /* Beyond this point there is a unique intersection point. */

    // Segment #1 is a vertical line.
    if (abs(p1.x - p2.x) < EPS) {
      double m = (p4.y - p3.y) / (p4.x - p3.x);
      double b = p3.y - m * p3.x;
      return new Pt[]{new Pt(p1.x, m * p1.x + b)};
    }

    // Segment #2 is a vertical line.
    if (abs(p3.x - p4.x) < EPS) {
      double m = (p2.y - p1.y) / (p2.x - p1.x);
      double b = p1.y - m * p1.x;
      return new Pt[]{new Pt(p3.x, m * p3.x + b)};
    }

    double m1 = (p2.y - p1.y) / (p2.x - p1.x);
    double m2 = (p4.y - p3.y) / (p4.x - p3.x);
    double b1 = p1.y - m1 * p1.x;
    double b2 = p3.y - m2 * p3.x;
    double x = (b2 - b1) / (m1 - m2);
    double y = (m1 * b2 - m2 * b1) / (m1 - m2);

    return new Pt[]{new Pt(x, y)};
  }

}

간단한 사용법 예는 다음과 같습니다.

  public static void main(String[] args) {

    // Segment #1 is (p1, p2), segment #2 is (p3, p4)
    Pt p1, p2, p3, p4;

    p1 = new Pt(-2, 4); p2 = new Pt(3, 3);
    p3 = new Pt(0, 0);  p4 = new Pt(2, 4);
    Pt[] points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point = points[0];

    // Prints: (1.636, 3.273)
    System.out.printf("(%.3f, %.3f)\n", point.x, point.y);

    p1 = new Pt(-10, 0); p2 = new Pt(+10, 0);
    p3 = new Pt(-5, 0);  p4 = new Pt(+5, 0);
    points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point1 = points[0], point2 = points[1];

    // Prints: (-5.000, 0.000) (5.000, 0.000)
    System.out.printf("(%.3f, %.3f) (%.3f, %.3f)\n", point1.x, point1.y, point2.x, point2.y);
  }

내 지리 좌표 시스템에서 작동했습니다! 감사! 그러나 그것은 무한 선 교차를위한 것이고, 나는 유한 선 교차를 찾고 있습니다.
M. Usman Khan 1

8

Numeric Recipes 시리즈에서 좋은 설명과 명확한 해결책을 찾을 수 있다고 언급하고 싶었습니다. 3 판을 받았는데 답은 1117 페이지, 섹션 21.4에 있습니다. 다른 명칭을 가진 다른 솔루션은 Marina Gavrilova의 신뢰할 수있는 Line Section Intersection Testing에 의해 논문에서 찾을 수 있습니다 . 그녀의 해결책은 제 생각에는 조금 더 간단합니다.

내 구현은 다음과 같습니다.

bool NuGeometry::IsBetween(const double& x0, const double& x, const double& x1){
   return (x >= x0) && (x <= x1);
}

bool NuGeometry::FindIntersection(const double& x0, const double& y0, 
     const double& x1, const double& y1,
     const double& a0, const double& b0, 
     const double& a1, const double& b1, 
     double& xy, double& ab) {
   // four endpoints are x0, y0 & x1,y1 & a0,b0 & a1,b1
   // returned values xy and ab are the fractional distance along xy and ab
   // and are only defined when the result is true

   bool partial = false;
   double denom = (b0 - b1) * (x0 - x1) - (y0 - y1) * (a0 - a1);
   if (denom == 0) {
      xy = -1;
      ab = -1;
   } else {
      xy = (a0 * (y1 - b1) + a1 * (b0 - y1) + x1 * (b1 - b0)) / denom;
      partial = NuGeometry::IsBetween(0, xy, 1);
      if (partial) {
         // no point calculating this unless xy is between 0 & 1
         ab = (y1 * (x0 - a1) + b1 * (x1 - x0) + y0 * (a1 - x1)) / denom; 
      }
   }
   if ( partial && NuGeometry::IsBetween(0, ab, 1)) {
      ab = 1-ab;
      xy = 1-xy;
      return true;
   }  else return false;
}

p1 = (0,0), p2 = (10,0), p3 = (9,0), p4 = (20,0)에서 작동하지 않음
padmalcom

"작동하지 않습니다"라는 정의에 따라 다릅니다. Denom은 0이므로 교차하지 않기 때문에 올바른 것으로 보이는 false를 반환합니다. 공선은 교차와 동일하지 않습니다.
marcp

8

위의 많은 솔루션을 사용할 수 있지만 아래 솔루션은 매우 간단하고 이해하기 쉽다고 생각합니다.

Vector AB와 Vector CD의 두 세그먼트는 다음과 같은 경우에만 교차합니다.

  1. 끝점 a와 b는 세그먼트 CD의 반대쪽에 있습니다.
  2. 종점 c 및 d는 세그먼트 AB의 반대쪽에 있습니다.

보다 구체적으로, 2 개의 트리플 a, c, d 및 b, c, d 중 정확히 하나가 반 시계 방향 인 경우에만 a 및 b는 세그먼트 CD의 반대쪽에있다.

Intersect(a, b, c, d)
 if CCW(a, c, d) == CCW(b, c, d)
    return false;
 else if CCW(a, b, c) == CCW(a, b, d)
    return false;
 else
    return true;

여기서 CCW는 반 시계 방향을 나타내며 점의 방향에 따라 참 / 거짓을 반환합니다.

출처 : http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf 2 페이지


2
좀 더 구체적이어야한다고 생각합니다. CCW테스트 는 어떻게 정의됩니까? 외부 제품의 표시가 있습니까?
ocramz

감사; 이 의사 코드는 스크래치에서 매우 간단한 구현을 가능하게했다. :이 프로젝트를 참조 scratch.mit.edu/projects/129319027
루드 Helderman

8

C와 목표 -C

Gareth Rees의 답변을 바탕으로

const AGKLine AGKLineZero = (AGKLine){(CGPoint){0.0, 0.0}, (CGPoint){0.0, 0.0}};

AGKLine AGKLineMake(CGPoint start, CGPoint end)
{
    return (AGKLine){start, end};
}

double AGKLineLength(AGKLine l)
{
    return CGPointLengthBetween_AGK(l.start, l.end);
}

BOOL AGKLineIntersection(AGKLine l1, AGKLine l2, CGPoint *out_pointOfIntersection)
{
    // http://stackoverflow.com/a/565282/202451

    CGPoint p = l1.start;
    CGPoint q = l2.start;
    CGPoint r = CGPointSubtract_AGK(l1.end, l1.start);
    CGPoint s = CGPointSubtract_AGK(l2.end, l2.start);

    double s_r_crossProduct = CGPointCrossProductZComponent_AGK(r, s);
    double t = CGPointCrossProductZComponent_AGK(CGPointSubtract_AGK(q, p), s) / s_r_crossProduct;
    double u = CGPointCrossProductZComponent_AGK(CGPointSubtract_AGK(q, p), r) / s_r_crossProduct;

    if(t < 0 || t > 1.0 || u < 0 || u > 1.0)
    {
        if(out_pointOfIntersection != NULL)
        {
            *out_pointOfIntersection = CGPointZero;
        }
        return NO;
    }
    else
    {
        if(out_pointOfIntersection != NULL)
        {
            CGPoint i = CGPointAdd_AGK(p, CGPointMultiply_AGK(r, t));
            *out_pointOfIntersection = i;
        }
        return YES;
    }
}

CGFloat CGPointCrossProductZComponent_AGK(CGPoint v1, CGPoint v2)
{
    return v1.x * v2.y - v1.y * v2.x;
}

CGPoint CGPointSubtract_AGK(CGPoint p1, CGPoint p2)
{
    return (CGPoint){p1.x - p2.x, p1.y - p2.y};
}

CGPoint CGPointAdd_AGK(CGPoint p1, CGPoint p2)
{
    return (CGPoint){p1.x + p2.x, p1.y + p2.y};
}

CGFloat CGPointCrossProductZComponent_AGK(CGPoint v1, CGPoint v2)
{
    return v1.x * v2.y - v1.y * v2.x;
}

CGPoint CGPointMultiply_AGK(CGPoint p1, CGFloat factor)
{
    return (CGPoint){p1.x * factor, p1.y * factor};
}

많은 함수와 구조체는 비공개이지만 어떤 일이 일어나고 있는지 쉽게 알 수 있어야합니다. 이것은이 저장소에 공개되어 있습니다 https://github.com/hfossli/AGGeometryKit/


이 코드에서 AGPointZero는 어디에서 제공됩니까?
seanicus

1
@seanicus가 대신 CGPoint를 사용하도록 예제를 업데이트했습니다
hfossli

6

이것은 나를 위해 잘 작동합니다. 여기 에서 찍은 .

 // calculates intersection and checks for parallel lines.  
 // also checks that the intersection point is actually on  
 // the line segment p1-p2  
 Point findIntersection(Point p1,Point p2,  
   Point p3,Point p4) {  
   float xD1,yD1,xD2,yD2,xD3,yD3;  
   float dot,deg,len1,len2;  
   float segmentLen1,segmentLen2;  
   float ua,ub,div;  

   // calculate differences  
   xD1=p2.x-p1.x;  
   xD2=p4.x-p3.x;  
   yD1=p2.y-p1.y;  
   yD2=p4.y-p3.y;  
   xD3=p1.x-p3.x;  
   yD3=p1.y-p3.y;    

   // calculate the lengths of the two lines  
   len1=sqrt(xD1*xD1+yD1*yD1);  
   len2=sqrt(xD2*xD2+yD2*yD2);  

   // calculate angle between the two lines.  
   dot=(xD1*xD2+yD1*yD2); // dot product  
   deg=dot/(len1*len2);  

   // if abs(angle)==1 then the lines are parallell,  
   // so no intersection is possible  
   if(abs(deg)==1) return null;  

   // find intersection Pt between two lines  
   Point pt=new Point(0,0);  
   div=yD2*xD1-xD2*yD1;  
   ua=(xD2*yD3-yD2*xD3)/div;  
   ub=(xD1*yD3-yD1*xD3)/div;  
   pt.x=p1.x+ua*xD1;  
   pt.y=p1.y+ua*yD1;  

   // calculate the combined length of the two segments  
   // between Pt-p1 and Pt-p2  
   xD1=pt.x-p1.x;  
   xD2=pt.x-p2.x;  
   yD1=pt.y-p1.y;  
   yD2=pt.y-p2.y;  
   segmentLen1=sqrt(xD1*xD1+yD1*yD1)+sqrt(xD2*xD2+yD2*yD2);  

   // calculate the combined length of the two segments  
   // between Pt-p3 and Pt-p4  
   xD1=pt.x-p3.x;  
   xD2=pt.x-p4.x;  
   yD1=pt.y-p3.y;  
   yD2=pt.y-p4.y;  
   segmentLen2=sqrt(xD1*xD1+yD1*yD1)+sqrt(xD2*xD2+yD2*yD2);  

   // if the lengths of both sets of segments are the same as  
   // the lenghts of the two lines the point is actually  
   // on the line segment.  

   // if the point isn’t on the line, return null  
   if(abs(len1-segmentLen1)>0.01 || abs(len2-segmentLen2)>0.01)  
     return null;  

   // return the valid intersection  
   return pt;  
 }  

 class Point{  
   float x,y;  
   Point(float x, float y){  
     this.x = x;  
     this.y = y;  
   }  

   void set(float x, float y){  
     this.x = x;  
     this.y = y;  
   }  
 }  

8
이 코드에는 몇 가지 문제가 있습니다. 0으로 나누면 예외가 발생할 수 있습니다. 그것은 제곱근을 취하기 때문에 느리다; 퍼지 팩터를 사용하기 때문에 때때로 오 탐지를 반환합니다. 이보다 더 잘할 수 있습니다!
Gareth Rees

해결책은 있지만 Jason이 제공하는 솔루션은 계산 속도가 빠르며이 솔루션의 많은 문제를 피할 수 있습니다.
Elemental

6

나는이 답변 중 일부를 시도했지만 그들은 나를 위해 일하지 않았다 (죄송합니다). 더 넷 검색 후 나는 이것을 발견 했다 .

그의 코드를 약간 수정하면 교차점을 반환하거나 교차점이 없으면 -1, -1을 반환하는이 함수가 생겼습니다.

    Public Function intercetion(ByVal ax As Integer, ByVal ay As Integer, ByVal bx As Integer, ByVal by As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal dx As Integer, ByVal dy As Integer) As Point
    '//  Determines the intersection point of the line segment defined by points A and B
    '//  with the line segment defined by points C and D.
    '//
    '//  Returns YES if the intersection point was found, and stores that point in X,Y.
    '//  Returns NO if there is no determinable intersection point, in which case X,Y will
    '//  be unmodified.

    Dim distAB, theCos, theSin, newX, ABpos As Double

    '//  Fail if either line segment is zero-length.
    If ax = bx And ay = by Or cx = dx And cy = dy Then Return New Point(-1, -1)

    '//  Fail if the segments share an end-point.
    If ax = cx And ay = cy Or bx = cx And by = cy Or ax = dx And ay = dy Or bx = dx And by = dy Then Return New Point(-1, -1)

    '//  (1) Translate the system so that point A is on the origin.
    bx -= ax
    by -= ay
    cx -= ax
    cy -= ay
    dx -= ax
    dy -= ay

    '//  Discover the length of segment A-B.
    distAB = Math.Sqrt(bx * bx + by * by)

    '//  (2) Rotate the system so that point B is on the positive X axis.
    theCos = bx / distAB
    theSin = by / distAB
    newX = cx * theCos + cy * theSin
    cy = cy * theCos - cx * theSin
    cx = newX
    newX = dx * theCos + dy * theSin
    dy = dy * theCos - dx * theSin
    dx = newX

    '//  Fail if segment C-D doesn't cross line A-B.
    If cy < 0 And dy < 0 Or cy >= 0 And dy >= 0 Then Return New Point(-1, -1)

    '//  (3) Discover the position of the intersection point along line A-B.
    ABpos = dx + (cx - dx) * dy / (dy - cy)

    '//  Fail if segment C-D crosses line A-B outside of segment A-B.
    If ABpos < 0 Or ABpos > distAB Then Return New Point(-1, -1)

    '//  (4) Apply the discovered position to line A-B in the original coordinate system.
    '*X=Ax+ABpos*theCos
    '*Y=Ay+ABpos*theSin

    '//  Success.
    Return New Point(ax + ABpos * theCos, ay + ABpos * theSin)
End Function

6

Cortijon이 주석 에서 자바 스크립트 버전을 제안 하고 iMalc가 약간 적은 계산을 가진 버전을 제공 한 Gavin의 대답 에 관심이있는 것 같습니다 . 일부는 다양한 코드 제안의 단점을 지적했으며 일부는 일부 코드 제안의 효율성에 대해 언급했습니다.

Gavin의 답변을 통해 iMalc가 제공 한 알고리즘은 현재 자바 스크립트 프로젝트에서 사용중인 알고리즘이며 누군가를 도울 수 있다면 정리 된 버전을 제공하고 싶었습니다.

// Some variables for reuse, others may do this differently
var p0x, p1x, p2x, p3x, ix,
    p0y, p1y, p2y, p3y, iy,
    collisionDetected;

// do stuff, call other functions, set endpoints...

// note: for my purpose I use |t| < |d| as opposed to
// |t| <= |d| which is equivalent to 0 <= t < 1 rather than
// 0 <= t <= 1 as in Gavin's answer - results may vary

var lineSegmentIntersection = function(){
    var d, dx1, dx2, dx3, dy1, dy2, dy3, s, t;

    dx1 = p1x - p0x;      dy1 = p1y - p0y;
    dx2 = p3x - p2x;      dy2 = p3y - p2y;
    dx3 = p0x - p2x;      dy3 = p0y - p2y;

    collisionDetected = 0;

    d = dx1 * dy2 - dx2 * dy1;

    if(d !== 0){
        s = dx1 * dy3 - dx3 * dy1;
        if((s <= 0 && d < 0 && s >= d) || (s >= 0 && d > 0 && s <= d)){
            t = dx2 * dy3 - dx3 * dy2;
            if((t <= 0 && d < 0 && t > d) || (t >= 0 && d > 0 && t < d)){
                t = t / d;
                collisionDetected = 1;
                ix = p0x + t * dx1;
                iy = p0y + t * dy1;
            }
        }
    }
};

나는 선이 좋아하는 당신은 무슨 일인지 이해할 수있는 방법을 이해하지 않습니다 t = dx2 * dy3 - dx3 * dy2;...
Supuhstar

@Supuhstar 그것은 벡터 수학과 내적 및 교차 곱의 정의와 관련이 있습니다. 예를 들어 게시 한 코드는 교차 제품 작업을 나타냅니다. 한 선 세그먼트를 다른 선 세그먼트에 투영하여 다른 선 세그먼트에서 떨어지는 위치를 결정하기 위해 중간 또는 선의 시작점 이전에 있습니다. 따라서 t는 정규화 된 값입니다. 0과 1 사이이면 두 세그먼트가 교차합니다. 0보다 작거나 1보다 크면 그렇지 않습니다.
Nolo

@Supuhstar 또한 투영이 실제 점을 찾으려면 결과의 크기를 조정해야합니다. 그것이 t/d온 곳 입니다.
Nolo

1
그런 변수 이름으로 한 눈에 무슨 일이 일어나고 있는지 이해합니까? 왜 뭔가 같은 crossProduct = (line1XDifference * line2YDifference) - (line2XDifference * line1YDifference)scaledResult = crossProduct / dotProduct?
Supuhstar

1
@Supuhstar 아, 무슨 말인지 알 겠어. 음, 나는 효율성에 대한 집착을 넘어서는 좋은 이유가 없다고 생각하지만, 컴파일러는 당신이 그들에게주는 모든 코드를 가져 와서 효율적으로 만들기 때문에 꽤 좋은 이유는 아닙니다. 계산해야 할 내용을 변경하지 않으면 서 가능합니다. 다른 한편으로, 이름 p1x, p1y등은 x와 y 값으로 포인트를 설명 p1x하기위한 것이므로 에 대한 약어는 point1x마찬가지로 d1x내 마음에 그리스 문자의 약어 deltaX이거나 말할 수 differenceInX있습니다. (더보기)
Nolo

5

이 문제에 대한 훨씬 간단한 해결책이 있다고 생각합니다. 나는 오늘 또 다른 아이디어를 생각해 냈으며 (지금은 적어도 2D에서는) 제대로 작동하는 것 같습니다. 두 선 사이의 교점을 계산 한 다음 계산 된 교점이 두 선분의 경계 상자 내에 있는지 확인하면됩니다. 그렇다면 선분이 교차합니다. 그게 다야.

편집하다:

이것이 교차점을 계산하는 방법입니다 (이 코드 스 니펫을 발견 한 곳을 더 이상 알지 못합니다)

Point3D

에서 오는

System.Windows.Media.Media3D

public static Point3D? Intersection(Point3D start1, Point3D end1, Point3D start2, Point3D end2) {

        double a1 = end1.Y - start1.Y;
        double b1 = start1.X - end1.X;
        double c1 = a1 * start1.X + b1 * start1.Y;

        double a2 = end2.Y - start2.Y;
        double b2 = start2.X - end2.X;
        double c2 = a2 * start2.X + b2 * start2.Y;

        double det = a1 * b2 - a2 * b1;
        if (det == 0) { // lines are parallel
            return null;
        }

        double x = (b2 * c1 - b1 * c2) / det;
        double y = (a1 * c2 - a2 * c1) / det;

        return new Point3D(x, y, 0.0);
    }

그리고 이것은 내 (응답 목적으로 단순화 된) BoundingBox 클래스입니다.

public class BoundingBox {
    private Point3D min = new Point3D();
    private Point3D max = new Point3D();

    public BoundingBox(Point3D point) {
        min = point;
        max = point;
    }

    public Point3D Min {
        get { return min; }
        set { min = value; }
    }

    public Point3D Max {
        get { return max; }
        set { max = value; }
    }

    public bool Contains(BoundingBox box) {
        bool contains =
            min.X <= box.min.X && max.X >= box.max.X &&
            min.Y <= box.min.Y && max.Y >= box.max.Y &&
            min.Z <= box.min.Z && max.Z >= box.max.Z;
        return contains;
    }

    public bool Contains(Point3D point) {
        return Contains(new BoundingBox(point));
    }

}

3

이 솔루션은 도움이 될 수 있습니다

public static float GetLineYIntesept(PointF p, float slope)
    {
        return p.Y - slope * p.X;
    }

    public static PointF FindIntersection(PointF line1Start, PointF line1End, PointF line2Start, PointF line2End)
    {

        float slope1 = (line1End.Y - line1Start.Y) / (line1End.X - line1Start.X);
        float slope2 = (line2End.Y - line2Start.Y) / (line2End.X - line2Start.X);

        float yinter1 = GetLineYIntesept(line1Start, slope1);
        float yinter2 = GetLineYIntesept(line2Start, slope2);

        if (slope1 == slope2 && yinter1 != yinter2)
            return PointF.Empty;

        float x = (yinter2 - yinter1) / (slope1 - slope2);

        float y = slope1 * x + yinter1;

        return new PointF(x, y);
    }

3

Kris의 답변을 JavaScript로 이식했습니다. 수많은 다른 답변을 시도한 후, 그는 올바른 지적을 제공했습니다. 나는 내가 필요한 점수를 얻지 못했다고 미쳤다고 생각했다.

function getLineLineCollision(p0, p1, p2, p3) {
    var s1, s2;
    s1 = {x: p1.x - p0.x, y: p1.y - p0.y};
    s2 = {x: p3.x - p2.x, y: p3.y - p2.y};

    var s10_x = p1.x - p0.x;
    var s10_y = p1.y - p0.y;
    var s32_x = p3.x - p2.x;
    var s32_y = p3.y - p2.y;

    var denom = s10_x * s32_y - s32_x * s10_y;

    if(denom == 0) {
        return false;
    }

    var denom_positive = denom > 0;

    var s02_x = p0.x - p2.x;
    var s02_y = p0.y - p2.y;

    var s_numer = s10_x * s02_y - s10_y * s02_x;

    if((s_numer < 0) == denom_positive) {
        return false;
    }

    var t_numer = s32_x * s02_y - s32_y * s02_x;

    if((t_numer < 0) == denom_positive) {
        return false;
    }

    if((s_numer > denom) == denom_positive || (t_numer > denom) == denom_positive) {
        return false;
    }

    var t = t_numer / denom;

    var p = {x: p0.x + (t * s10_x), y: p0.y + (t * s10_y)};
    return p;
}

2

나는 많은 방법을 시도한 다음 내 자신을 작성하기로 결정했습니다. 그래서 여기 있습니다 :

bool IsBetween (float x, float b1, float b2)
{
   return ( ((x >= (b1 - 0.1f)) && 
        (x <= (b2 + 0.1f))) || 
        ((x >= (b2 - 0.1f)) &&
        (x <= (b1 + 0.1f))));
}

bool IsSegmentsColliding(   POINTFLOAT lineA,
                POINTFLOAT lineB,
                POINTFLOAT line2A,
                POINTFLOAT line2B)
{
    float deltaX1 = lineB.x - lineA.x;
    float deltaX2 = line2B.x - line2A.x;
    float deltaY1 = lineB.y - lineA.y;
    float deltaY2 = line2B.y - line2A.y;

    if (abs(deltaX1) < 0.01f && 
        abs(deltaX2) < 0.01f) // Both are vertical lines
        return false;
    if (abs((deltaY1 / deltaX1) -
        (deltaY2 / deltaX2)) < 0.001f) // Two parallel line
        return false;

    float xCol = (  (   (deltaX1 * deltaX2) * 
                        (line2A.y - lineA.y)) - 
                    (line2A.x * deltaY2 * deltaX1) + 
                    (lineA.x * deltaY1 * deltaX2)) / 
                 ((deltaY1 * deltaX2) - (deltaY2 * deltaX1));
    float yCol = 0;
    if (deltaX1 < 0.01f) // L1 is a vertical line
        yCol = ((xCol * deltaY2) + 
                (line2A.y * deltaX2) - 
                (line2A.x * deltaY2)) / deltaX2;
    else // L1 is acceptable
        yCol = ((xCol * deltaY1) +
                (lineA.y * deltaX1) -
                (lineA.x * deltaY1)) / deltaX1;

    bool isCol =    IsBetween(xCol, lineA.x, lineB.x) &&
            IsBetween(yCol, lineA.y, lineB.y) &&
            IsBetween(xCol, line2A.x, line2B.x) &&
            IsBetween(yCol, line2A.y, line2B.y);
    return isCol;
}

이 두 공식을 기반으로합니다. (선 방정식 및 기타 공식에서 단순화했습니다)

x에 대한 공식

y에 대한 공식


작동하지만이 좌표를 입력하려고하면 (선형 / 겹치는 경우 잘못된 결과를 반환합니다) : PointA1 = (0,0) PointA2 = (0,2) 및 PointB1 = (0,1) PointB2 = (0,5)
dns

@dns 글쎄, 그것은 코드가 병렬 행에 대해 false를 반환하기 때문입니다. 그러나 문제를 보았지만 무한한 대답이 있기 때문에 함수가 무엇을 반환 해야하는지 알지 못합니다.
Soroush Falahati

2

이것은 Gareth Ree의 답변을 기반으로합니다. 또한 선 세그먼트의 겹침을 반환합니다 (있는 경우). C ++로 코딩 된 V는 간단한 벡터 클래스입니다. 2D에서 두 벡터의 교차 곱이 단일 스칼라를 반환합니다. 학교에서 자동 테스트 시스템으로 테스트하여 통과했습니다.

//Required input point must be colinear with the line
bool on_segment(const V& p, const LineSegment& l)
{
    //If a point is on the line, the sum of the vectors formed by the point to the line endpoints must be equal
    V va = p - l.pa;
    V vb = p - l.pb;
    R ma = va.magnitude();
    R mb = vb.magnitude();
    R ml = (l.pb - l.pa).magnitude();
    R s = ma + mb;
    bool r = s <= ml + epsilon;
    return r;
}

//Compute using vector math
// Returns 0 points if the lines do not intersect or overlap
// Returns 1 point if the lines intersect
//  Returns 2 points if the lines overlap, contain the points where overlapping start starts and stop
std::vector<V> intersect(const LineSegment& la, const LineSegment& lb)
{
    std::vector<V> r;

    //http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
    V oa, ob, da, db; //Origin and direction vectors
    R sa, sb; //Scalar values
    oa = la.pa;
    da = la.pb - la.pa;
    ob = lb.pa;
    db = lb.pb - lb.pa;

    if (da.cross(db) == 0 && (ob - oa).cross(da) == 0) //If colinear
    {
        if (on_segment(lb.pa, la) && on_segment(lb.pb, la))
        {
            r.push_back(lb.pa);
            r.push_back(lb.pb);
            dprintf("colinear, overlapping\n");
            return r;
        }

        if (on_segment(la.pa, lb) && on_segment(la.pb, lb))
        {
            r.push_back(la.pa);
            r.push_back(la.pb);
            dprintf("colinear, overlapping\n");
            return r;
        }

        if (on_segment(la.pa, lb))
            r.push_back(la.pa);

        if (on_segment(la.pb, lb))
            r.push_back(la.pb);

        if (on_segment(lb.pa, la))
            r.push_back(lb.pa);

        if (on_segment(lb.pb, la))
            r.push_back(lb.pb);

        if (r.size() == 0)
            dprintf("colinear, non-overlapping\n");
        else
            dprintf("colinear, overlapping\n");

        return r;
    }

    if (da.cross(db) == 0 && (ob - oa).cross(da) != 0)
    {
        dprintf("parallel non-intersecting\n");
        return r;
    }

    //Math trick db cross db == 0, which is a single scalar in 2D.
    //Crossing both sides with vector db gives:
    sa = (ob - oa).cross(db) / da.cross(db);

    //Crossing both sides with vector da gives
    sb = (oa - ob).cross(da) / db.cross(da);

    if (0 <= sa && sa <= 1 && 0 <= sb && sb <= 1)
    {
        dprintf("intersecting\n");
        r.push_back(oa + da * sa);
        return r;
    }

    dprintf("non-intersecting, non-parallel, non-colinear, non-overlapping\n");
    return r;
}

2

다음은 해당 교차로 감지 코드와 함께 C #에서 선 세그먼트의 기본 구현입니다. 라는 2D 벡터 / 점 구조체가 필요 Vector2f하지만 X / Y 속성이있는 다른 유형으로 바꿀 수 있습니다. 또한 대체 할 수 floatdouble그 정장 경우 귀하의 요구에 더 나은.

이 코드는 내 .NET 물리 라이브러리 인 Boing에서 사용 됩니다.

public struct LineSegment2f
{
    public Vector2f From { get; }
    public Vector2f To { get; }

    public LineSegment2f(Vector2f @from, Vector2f to)
    {
        From = @from;
        To = to;
    }

    public Vector2f Delta => new Vector2f(To.X - From.X, To.Y - From.Y);

    /// <summary>
    /// Attempt to intersect two line segments.
    /// </summary>
    /// <remarks>
    /// Even if the line segments do not intersect, <paramref name="t"/> and <paramref name="u"/> will be set.
    /// If the lines are parallel, <paramref name="t"/> and <paramref name="u"/> are set to <see cref="float.NaN"/>.
    /// </remarks>
    /// <param name="other">The line to attempt intersection of this line with.</param>
    /// <param name="intersectionPoint">The point of intersection if within the line segments, or empty..</param>
    /// <param name="t">The distance along this line at which intersection would occur, or NaN if lines are collinear/parallel.</param>
    /// <param name="u">The distance along the other line at which intersection would occur, or NaN if lines are collinear/parallel.</param>
    /// <returns><c>true</c> if the line segments intersect, otherwise <c>false</c>.</returns>
    public bool TryIntersect(LineSegment2f other, out Vector2f intersectionPoint, out float t, out float u)
    {
        var p = From;
        var q = other.From;
        var r = Delta;
        var s = other.Delta;

        // t = (q − p) × s / (r × s)
        // u = (q − p) × r / (r × s)

        var denom = Fake2DCross(r, s);

        if (denom == 0)
        {
            // lines are collinear or parallel
            t = float.NaN;
            u = float.NaN;
            intersectionPoint = default(Vector2f);
            return false;
        }

        var tNumer = Fake2DCross(q - p, s);
        var uNumer = Fake2DCross(q - p, r);

        t = tNumer / denom;
        u = uNumer / denom;

        if (t < 0 || t > 1 || u < 0 || u > 1)
        {
            // line segments do not intersect within their ranges
            intersectionPoint = default(Vector2f);
            return false;
        }

        intersectionPoint = p + r * t;
        return true;
    }

    private static float Fake2DCross(Vector2f a, Vector2f b)
    {
        return a.X * b.Y - a.Y * b.X;
    }
}

1

주어진 두 개의 선분이 교차하는지 확인하는 C ++ 프로그램

#include <iostream>
using namespace std;

struct Point
{
    int x;
    int y;
};

// Given three colinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
bool onSegment(Point p, Point q, Point r)
{
    if (q.x <= max(p.x, r.x) && q.x >= min(p.x, r.x) &&
        q.y <= max(p.y, r.y) && q.y >= min(p.y, r.y))
       return true;

    return false;
}

// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are colinear
// 1 --> Clockwise
// 2 --> Counterclockwise
int orientation(Point p, Point q, Point r)
{
    // See 10th slides from following link for derivation of the formula
    // http://www.dcs.gla.ac.uk/~pat/52233/slides/Geometry1x1.pdf
    int val = (q.y - p.y) * (r.x - q.x) -
              (q.x - p.x) * (r.y - q.y);

    if (val == 0) return 0;  // colinear

    return (val > 0)? 1: 2; // clock or counterclock wise
}

// The main function that returns true if line segment 'p1q1'
// and 'p2q2' intersect.
bool doIntersect(Point p1, Point q1, Point p2, Point q2)
{
    // Find the four orientations needed for general and
    // special cases
    int o1 = orientation(p1, q1, p2);
    int o2 = orientation(p1, q1, q2);
    int o3 = orientation(p2, q2, p1);
    int o4 = orientation(p2, q2, q1);

    // General case
    if (o1 != o2 && o3 != o4)
        return true;

    // Special Cases
    // p1, q1 and p2 are colinear and p2 lies on segment p1q1
    if (o1 == 0 && onSegment(p1, p2, q1)) return true;

    // p1, q1 and p2 are colinear and q2 lies on segment p1q1
    if (o2 == 0 && onSegment(p1, q2, q1)) return true;

    // p2, q2 and p1 are colinear and p1 lies on segment p2q2
    if (o3 == 0 && onSegment(p2, p1, q2)) return true;

     // p2, q2 and q1 are colinear and q1 lies on segment p2q2
    if (o4 == 0 && onSegment(p2, q1, q2)) return true;

    return false; // Doesn't fall in any of the above cases
}

// Driver program to test above functions
int main()
{
    struct Point p1 = {1, 1}, q1 = {10, 1};
    struct Point p2 = {1, 2}, q2 = {10, 2};

    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    p1 = {10, 0}, q1 = {0, 10};
    p2 = {0, 0}, q2 = {10, 10};
    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    p1 = {-5, -5}, q1 = {0, 0};
    p2 = {1, 1}, q2 = {10, 10};
    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    return 0;
}

1

@Gareth Rees 답변, Python 버전 :

import numpy as np

def np_perp( a ) :
    b = np.empty_like(a)
    b[0] = a[1]
    b[1] = -a[0]
    return b

def np_cross_product(a, b):
    return np.dot(a, np_perp(b))

def np_seg_intersect(a, b, considerCollinearOverlapAsIntersect = False):
    # /programming/563198/how-do-you-detect-where-two-line-segments-intersect/565282#565282
    # http://www.codeproject.com/Tips/862988/Find-the-intersection-point-of-two-line-segments
    r = a[1] - a[0]
    s = b[1] - b[0]
    v = b[0] - a[0]
    num = np_cross_product(v, r)
    denom = np_cross_product(r, s)
    # If r x s = 0 and (q - p) x r = 0, then the two lines are collinear.
    if np.isclose(denom, 0) and np.isclose(num, 0):
        # 1. If either  0 <= (q - p) * r <= r * r or 0 <= (p - q) * s <= * s
        # then the two lines are overlapping,
        if(considerCollinearOverlapAsIntersect):
            vDotR = np.dot(v, r)
            aDotS = np.dot(-v, s)
            if (0 <= vDotR  and vDotR <= np.dot(r,r)) or (0 <= aDotS  and aDotS <= np.dot(s,s)):
                return True
        # 2. If neither 0 <= (q - p) * r = r * r nor 0 <= (p - q) * s <= s * s
        # then the two lines are collinear but disjoint.
        # No need to implement this expression, as it follows from the expression above.
        return None
    if np.isclose(denom, 0) and not np.isclose(num, 0):
        # Parallel and non intersecting
        return None
    u = num / denom
    t = np_cross_product(v, s) / denom
    if u >= 0 and u <= 1 and t >= 0 and t <= 1:
        res = b[0] + (s*u)
        return res
    # Otherwise, the two line segments are not parallel but do not intersect.
    return None

0

사각형의 각면이 선분이고 사용자가 그린 부분이 선분이면 네 개의 측면 선분과 교차하는지 사용자가 그린 선분 만 확인하면됩니다. 이것은 각 세그먼트의 시작과 끝점을 감안할 때 상당히 간단한 연습이어야합니다.


3
이것은 원래의 프레임으로 된 질문에 대한 합리적인 대답이지만 이제 질문이 크게 편집되었으므로 그다지 의미가 없습니다.
GS-Monica에게 사과 함

0

t3chb0t의 답변을 바탕으로 :

int intersezione_linee(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int& p_x, int& p_y)
{
   //L1: estremi (x1,y1)(x2,y2) L2: estremi (x3,y3)(x3,y3)
   int d;
   d = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4);
   if(!d)
       return 0;
   p_x = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4))/d;
   p_y = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4))/d;
   return 1;
}

int in_bounding_box(int x1, int y1, int x2, int y2, int p_x, int p_y)
{
    return p_x>=x1 && p_x<=x2 && p_y>=y1 && p_y<=y2;

}

int intersezione_segmenti(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int& p_x, int& p_y)
{
    if (!intersezione_linee(x1,y1,x2,y2,x3,y3,x4,y4,p_x,p_y))
        return 0;

    return in_bounding_box(x1,y1,x2,y2,p_x,p_y) && in_bounding_box(x3,y3,x4,y4,p_x,p_y);
}

0

이 책에서 "다중 뷰 형상"이라는 알고리즘을 읽었습니다.

다음 텍스트를 사용하여

'조옮김 표시로

* 내적 제품으로

연산자로 사용할 때 x를 교차 곱으로

1. 라인 정의

점 x_vec = (x, y) '는 선 ax + by + c = 0에 있습니다.

우리는 L = (a, b, c) ', 점을 (x, y, 1)'으로 동종 좌표로 나타냅니다.

선 방정식은 다음과 같이 쓸 수 있습니다

(x, y, 1) (a, b, c) '= 0 또는 x'* L = 0

2. 선의 교차점

L1 = (a1, b1, c1) ', L2 = (a2, b2, c2)'라는 두 줄이 있습니다.

x는 점, 벡터이며 x = L1 x L2라고 가정합니다 (L1 교차 곱 L2).

주의하십시오. x는 항상 2D 점입니다. (L1xL2)가 3 개의 요소 벡터이고 x가 2D 좌표 인 경우 혼동 좌표를 읽으십시오.

트리플 제품에 따르면

L1, L2 코 플레인으로 인해 L1 * (L1 x L2) = 0 및 L2 * (L1 x L2) = 0

우리는 (L1xL2)를 벡터 x로 대체하고, L1 * x = 0, L2 * x = 0을가집니다. 즉, x는 L1과 L2 모두에 있고 x는 교점입니다.

여기서 x는 동종 좌표입니다. x의 마지막 요소가 0이면 L1과 L2가 평행을 의미합니다.


0

많은 답변이 모든 계산을 단일 함수로 마무리했습니다. 코드의 다른 곳에서 사용하기 위해 선 경사, y 절편 또는 x 절편을 계산해야하는 경우 중복 계산을 수행하게됩니다. 각 기능을 분리하고 명확한 변수 이름을 사용하고 쉽게 따라갈 수 있도록 코드에 주석을 달았습니다. JavaScript에서 선이 끝점을 넘어 무한히 교차하는지 알아야합니다.

http://jsfiddle.net/skibulk/evmqq00u/

var point_a = {x:0, y:10},
    point_b = {x:12, y:12},
    point_c = {x:10, y:0},
    point_d = {x:0, y:0},
    slope_ab = slope(point_a, point_b),
    slope_bc = slope(point_b, point_c),
    slope_cd = slope(point_c, point_d),
    slope_da = slope(point_d, point_a),
    yint_ab = y_intercept(point_a, slope_ab),
    yint_bc = y_intercept(point_b, slope_bc),
    yint_cd = y_intercept(point_c, slope_cd),
    yint_da = y_intercept(point_d, slope_da),
    xint_ab = x_intercept(point_a, slope_ab, yint_ab),
    xint_bc = x_intercept(point_b, slope_bc, yint_bc),
    xint_cd = x_intercept(point_c, slope_cd, yint_cd),
    xint_da = x_intercept(point_d, slope_da, yint_da),
    point_aa = intersect(slope_da, yint_da, xint_da, slope_ab, yint_ab, xint_ab),
    point_bb = intersect(slope_ab, yint_ab, xint_ab, slope_bc, yint_bc, xint_bc),
    point_cc = intersect(slope_bc, yint_bc, xint_bc, slope_cd, yint_cd, xint_cd),
    point_dd = intersect(slope_cd, yint_cd, xint_cd, slope_da, yint_da, xint_da);

console.log(point_a, point_b, point_c, point_d);
console.log(slope_ab, slope_bc, slope_cd, slope_da);
console.log(yint_ab, yint_bc, yint_cd, yint_da);
console.log(xint_ab, xint_bc, xint_cd, xint_da);
console.log(point_aa, point_bb, point_cc, point_dd);

function slope(point_a, point_b) {
  var i = (point_b.y - point_a.y) / (point_b.x - point_a.x);
  if (i === -Infinity) return Infinity;
  if (i === -0) return 0;
  return i;
}

function y_intercept(point, slope) {
    // Horizontal Line
    if (slope == 0) return point.y;
  // Vertical Line
    if (slope == Infinity)
  {
    // THE Y-Axis
    if (point.x == 0) return Infinity;
    // No Intercept
    return null;
  }
  // Angled Line
  return point.y - (slope * point.x);
}

function x_intercept(point, slope, yint) {
    // Vertical Line
    if (slope == Infinity) return point.x;
  // Horizontal Line
    if (slope == 0)
  {
    // THE X-Axis
    if (point.y == 0) return Infinity;
    // No Intercept
    return null;
  }
  // Angled Line
  return -yint / slope;
}

// Intersection of two infinite lines
function intersect(slope_a, yint_a, xint_a, slope_b, yint_b, xint_b) {
  if (slope_a == slope_b)
  {
    // Equal Lines
    if (yint_a == yint_b && xint_a == xint_b) return Infinity;
    // Parallel Lines
    return null;
  }
  // First Line Vertical
    if (slope_a == Infinity)
  {
    return {
        x: xint_a,
      y: (slope_b * xint_a) + yint_b
    };
  }
  // Second Line Vertical
    if (slope_b == Infinity)
  {
    return {
        x: xint_b,
      y: (slope_a * xint_b) + yint_a
    };
  }
  // Not Equal, Not Parallel, Not Vertical
  var i = (yint_b - yint_a) / (slope_a - slope_b);
  return {
    x: i,
    y: (slope_a * i) + yint_a
  };
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.