Як виявити, де перетинаються два відрізки рядків? [зачинено]


518

Як визначити, перетинаються чи ні дві лінії, і якщо вони є, в якій точці x, y?


Можливо, це допоможе розглядати краї прямокутника як окремі лінії замість цілого багатокутника.
Райан Грем

Примітка модератора : обговорення того, чи належить це повідомлення на тему, належить до Meta Stack Overflow. Далі коментарі з цього приводу буде видалено.
Мартійн Пітерс

Відповіді:


659

До цієї проблеми є хороший підхід, який використовує векторні крос-продукти. Визначте двовимірний векторний поперечний продукт 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  ×  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 )

Зараз є чотири випадки:

  1. Якщо r  ×  s  = 0 і ( q  -  p ) ×  r  = 0, то дві лінії є колінеарними.

    У цьому випадку висловіть кінцеві точки другого відрізка ( q і q  +  s ) через рівняння першого відрізка рядка ( p + t r ):

    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 1 , t 0 ], а не [ t 0 , t 1 ].

  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. Інакше два відрізки рядків не паралельні, але не перетинаються.

Кредит: цей метод полягає в двовимірній спеціалізації алгоритму перетину тривимірних ліній із статті "Перетин двох ліній у трьох просторах" Рональда Голдмана, опублікованої в " Graphics Gems" , сторінка 304. У трьох вимірах звичайний випадок: лінії є косими (ні паралельними, ні пересічними), і в цьому випадку метод дає точки найближчого наближення двох прямих.


5
@myrkos: Ні. Сегмент першого рядка працює "від p до p + r", тому коли він параметрично представлений як "p + tr", то сегмент відповідає 0 ≤ t ≤ 1. Аналогічно і для іншого сегмента.
Гарет Різ

7
Ґарет, я відчуваю, що мені щось не вистачає, але як ти ділиш (вектор) на вектор? Ваші рішення для t і u закінчуються / (r × s), але (r × s)чи вектором, правда? Вектор (0, 0, rx * sy - ry * sx). А ліва частина аналогічно вектору, паралельному осі z. Отже ... я просто поділяю компонент z на інший компонент z? Чи є насправді формула для t |(q − p) × s| / |(r × s)|?
LarsH

7
@LarsH: див. Перший абзац.
Гарет Різ

35
Для тих, хто цікавиться, ось проста C # реалізація, яка бере координати точки PointF і кінця для ліній, які, здається, працюють: ideone.com/PnPJgb
Мет

24
Я зібрав реалізацію JavaScript після @Matt. Я вніс виправлення помилок, на які вказував Tekito.
pgkelley

230

FWIW, наступна функція (в С) обидва виявляє перетини ліній і визначає точку перетину. В його основі лежить алгоритм у «Трюках Гуру програмування ігор Windows» Андре Лемоте . Це не відрізняється від алгоритму в інших відповідях (наприклад, у Гарета). Тоді Лемот використовує правило Креймера (не питайте мене), щоб самостійно вирішити рівняння.

Я можу засвідчити, що він працює в моєму неміцному клоні астероїдів і, здається, справляється з кращими випадками, описаними в інших відповідях Елементаль, Дана та Водзу. Це також, ймовірно, швидше, ніж код, опублікований KingNestor, тому що це все множення та ділення, немає квадратних коренів!

Я думаю, що є певний потенціал поділу на нуль, хоча це не було проблемою в моєму випадку. Досить просто змінити, щоб уникнути аварії в будь-якому випадку.

// 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
}

До речі, я мушу сказати, що в книзі Лемота, хоча він, мабуть, правильний алгоритм, конкретний приклад, він показує пробки в неправильних числах і робить обчислення неправильно. Наприклад:

(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]; } повернути нуль; // Без зіткнення}
cortijon

13
хороший алгоритм, однак fyi, він не обробляє випадки, коли визначником є ​​0. (вище -s2_x * s1_y + s1_x * s2_y вище). Якщо це 0 (або близько 0), лінії паралельні або колінеарні. Якщо він колінеарний, то перетином може бути інший відрізок лінії.
seand

16
Двох операцій ділення можна уникнути за швидкістю (поділ коштує більше, ніж множення); якщо лінії перетинаються, вам потрібен один поділ, якщо вони не перетинаються, вам потрібен нуль. Спершу слід обчислити знаменник і зупинитися на ранньому терміні, якщо він дорівнює нулю (можливо, додавання коду для виявлення узгодженості.) Далі, замість обчислення sта tбезпосередньо, перевірити зв’язок між двома чисельниками та знаменником. Тільки якщо лінії підтверджені для перетину, вам потрібно насправді обчислити значення t(але ні s).
Qwertie

18
Я робив тестування продуктивності на всіх алгоритмах, розміщених тут, і цей принаймні вдвічі швидший за будь-який з інших. Дякуємо за публікацію!
lajos

63

Проблема зводиться до цього питання: чи перетинаються два прямі від А до В і від С до D? Потім ви можете задати це чотири рази (між лінією та кожною з чотирьох сторін прямокутника).

Ось векторна математика для цього. Я припускаю, що лінія від А до В є відповідною лінією, а лінія від С до Д - одна з прямокутних ліній. Моє позначення таке, що Ax"x-координата A" і Cyє "y-координата C." І « *» означає точку-продукт, так наприклад 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, це означає, що прямокутна лінія знаходиться «позаду» заданої лінії (при цьому «напрямок» є «від А до В»), а якщо h>1прямокутна лінія знаходиться «попереду» заданої лінії.

Виведення:

А і С - вектори, які вказують на початок рядка; E і F - вектори від кінців A і C, які утворюють пряму.

Для будь-яких двох непаралельних прямих у площині має бути рівно одна пара скалярних gі hтака, що це рівняння має місце:

A + E*g = C + F*h

Чому? Оскільки дві непаралельні лінії повинні перетинатися, це означає, що ви можете масштабувати обидві лінії на деяку кількість кожна та торкатися один одного.

( Спочатку це виглядає як єдине рівняння з двома невідомими! Але це не тоді, коли ви вважаєте, що це двовимірне векторне рівняння, це означає, що це дійсно пара рівнянь в xі y.)

Ми повинні усунути одну з цих змінних. Простий спосіб - зробити Eтермін нульовим. Для цього візьміть крапковий добуток обох сторін рівняння, використовуючи вектор, який буде крапок нулю з E. Цей вектор я назвав Pвище, і я зробив очевидну трансформацію Е.

Тепер у вас є:

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
Чи чисельно цей алгоритм стабільний? Я спробував подібний підхід, і виявилося, що це дає дивні результати при роботі над поплавками.
milosz

3
Здається, є ще одна проблема з цим алгоритмом. Коли його подають точки A = {1, 0} B = {2, 0} C = {0, 0} D = {1,0}, хоча відрізки рядків чітко торкаються в кінці, F P (а також E Q, відповідно до виправленого нижче користувача) є обома 0, таким чином, приводячи поділ на 0, щоб знайти h і g. Я все ще працюю над вирішенням цього, але я вважав, що проблему варто було вказати.
свічки

12
Ця відповідь просто неправильна. Спробуйте A = {0,0}, B = {0,1}, C = {0,2} D = {2,0}
Тім Купер

6
A + E*g = C + F*hДві лінії перетинаються тоді і лише тоді, коли рішення цього рівняння (якщо вони не паралельні) має і те, gіh між 0 і 1 (вхідне чи виключне, залежно від того, чи вважаєте ви торкатися до кінця точки).
Даніель Фішер

46

Я спробував реалізувати алгоритм, настільки елегантно описаний Джейсоном вище; на жаль, працюючи, хоча математика в налагодженні я знайшов багато випадків, для яких це не працює.

Наприклад, розглянемо точки A (10,10) B (20,20) C (10,1) D (1,10) дає h = .5, але, зрозумівши, досліджуючи, що ці відрізки є ні-де поблизу кожного інший.

З графіку це ясно, що 0 <h <1 критерій вказує лише на те, що точка перехоплення лежала б на CD, якщо вона існувала, але нічого не говорить про те, чи лежить ця точка на AB. Щоб переконатися, що існує перехресна точка, ви повинні зробити симетричний розрахунок для змінної g, а вимога для перехоплення: 0 <g <1 І 0 <h <1


2
Я витягнув волосся, намагаючись зрозуміти, чому прийнята відповідь не працює для мене. Дуже дякую!
Метт Бріджз

1
Також слід зазначити, що в цьому випадку працюють граничні умови (тобто для h = 0 або h = 1 або g = 0 або g = 1 рядки "просто" дотику
Елементальний

Для людей, які мають проблеми із візуалізацією результату, я здійснив це в Javascript: jsfiddle.net/ferrybig/eokwL9mp
Ferrybig

45

Ось покращення відповіді Гевіна. Рішення marcp також схоже, але поділ не відкладає.

Це фактично виявляється практичним застосуванням відповіді Гарета Різа, тому що еквівалент крос-продукту в 2D є продуктом перп-крапки, для чого цей код використовує три. Перехід на 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;
}

В основному це відкладає поділ до останнього моменту і переміщує більшість тестів до того, як будуть зроблені певні розрахунки, тим самим додавши початкові результати. Нарешті, це також дозволяє уникнути поділу на нульовий випадок, який відбувається, коли лінії паралельні.

Ви також можете розглянути можливість використання тесту на епсилон, а не порівняння з нулем. Лінії, які є надзвичайно близькими до паралельних, можуть давати результати, які трохи відключаються. Це не помилка, це обмеження з математикою з плаваючою комою.


1
Виходить з ладу, якщо деякі точки мають значення 0 .. це не повинно статися правильно?
hfossli

1
Я вніс виправлення помилки, введеної під час відстрочки поділу. t може бути позитивним, коли число і номінал були негативними.
iMalc

2
Не працює, якщо p0-p1 вертикальний, а p2-p3 горизонтальний і два сегменти перехрещуються. (перше повернення виконано)
Фабіо Далла Лібера

Прохолодний корпус має дві можливості: не перекриваючись і перекриваючись. Перший повинен повернути помилковим, другий - правдивим. У вашому коді це не перевірено. він завжди повертає помилкове значення, як більшість відповідей тут. Прикро, що, здається, жодне рішення не працює.
AlexWien

3
Чи можете ви прояснити, чому всі вони використовують такі розпливчасті назви змінних, s32_yа не щось, що описує, що це таке point2YDifference?
Супухстар

40

Питання С: Як визначити, чи перетинаються два відрізки рядків?

Я шукав ту саму тему, і не був задоволений відповідями. Тому я написав статтю, в якій дуже докладно пояснюється, як перевірити, чи перетинаються два рядки з великою кількістю зображень. Є повний (і перевірений) Java-код.

Ось стаття, обрізана на найважливіші частини:

Алгоритм, який перевіряє, чи відрізок лінії перетинається з відрізком b, виглядає так:

Введіть тут опис зображення

Що таке обмежувальні коробки? Ось два обмежувальні коробки з двох відрізків рядків:

введіть тут опис зображення

Якщо обидва обмежувальні поля мають перетин, ви переміщуєте відрізок лінії a таким чином, що одна точка знаходиться у (0 | 0). Тепер у вас є рядок через початок, визначений a. Тепер перемістіть відрізок рядка b таким же чином і перевірте, чи є нові точки відрізка b на різних сторонах лінії a. Якщо це так, перевірте це навпаки. Якщо це теж так, відрізки рядків перетинаються. Якщо ні, вони не перетинаються.

Питання A: Де перетинаються два відрізки рядків?

Ви знаєте, що два відрізки рядків a і b перетинаються. Якщо ви цього не знаєте, перевірте це за допомогою інструментів, які я вам дав у "Питання С".

Тепер ви можете пройти кілька випадків і отримати рішення з математики 7 класу (див. Код та інтерактивний приклад ).

Питання В: Як ви виявите, чи перетинаються дві лінії чи ні?

Скажімо , ваша точка 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: Де перетинаються дві лінії?

Зверніться до питання Б, якщо вони взагалі перетинаються.

Рядки a і b визначаються двома точками для кожного рядка. Ви в основному можете застосувати ту саму логіку, яку було використано у питанні А.


15
Щоб було зрозуміло, питання Б у цій відповіді справді стосується двох прямих, що перетинаються, а не відрізків ліній. Я не скаржусь; це невірно. Просто не хочу, щоб когось ввели в оману.
фрад

1
Немає "питання С". І питання D лише відскакує на питання А.
Конрад Вільтерстен

21

Відповідь, щойно прийнята тут, є невірною (вона з тих пір була неприйнята, так що, ура!). Це неправильно усуває всі неперетинання. Тривіально може здатися, що він працює, але може вийти з ладу, особливо в тому випадку, якщо 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 проти CD, а потім також тестуючи CD з AB, цю проблему було б усунено. Тільки якщо обидва падають між 0 і 1 включно, можна сказати, що вони перетинаються.

Я рекомендую використовувати метод векторного перехресного продукту, якщо потрібно передбачити кінцеві точки.

-Дан


4
"Прийнята" відповідь може змінитися, тому слід назвати її ще чимось. (Насправді, я думаю, що це змінилося після вашого коментаря)
Йоханнес Хофф

14

Версія відповіді Python відповіді iMalc:

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

11

Пошук правильного перетину двох відрізків рядків - нетривіальна задача з великою кількістю крайових випадків. Ось добре документоване, працююче та перевірене рішення на Java.

По суті, три речі можуть статися при знаходженні перетину двох відрізків рядків:

  1. Відрізки не перетинаються

  2. Є унікальна точка перетину

  3. Перехрестя - ще один відрізок

ПРИМІТКА . У коді я припускаю, що відрізок рядка (x1, y1), (x2, y2) з x1 = x2 і y1 = y2 є дійсним відрізком рядка. Математично кажучи, сегмент лінії складається з різних точок, але я дозволяю сегментам бути точками в цій реалізації для повноти.

Код взято з мого github repo

/**
 * 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);
  }

він працював для моєї системи геокоординат! Дякую! Але це перетин нескінченних ліній, і я більше шукаю перетину кінцевих ліній.
М. Усман Хан

8

Просто хотілося зазначити, що в серії Цифрові рецепти можна знайти хороше пояснення та чітке рішення. У мене є 3-е видання, а відповідь - на сторінці 1117, розділ 21.4. Інше рішення з іншою номенклатурою можна знайти в роботі Тестування перетину надійних ділянок лінії Маріни Гаврилової . Її рішення, на мій погляд, трохи простіше.

Моя реалізація наведена нижче:

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, тому він поверне помилкове, що здається мені правильним, оскільки вони не перетинаються. Колінеар - це не те саме, що перетинається.
marcp

8

Зверху доступно безліч рішень, але я думаю, що рішення внизу є досить простим і зрозумілим.

Два сегменти вектор AB та Vector CD перетинаються, якщо і лише якщо

  1. Кінцеві точки a і b знаходяться на протилежних сторонах сегмента CD.
  2. Кінцеві точки c і d знаходяться на протилежній стороні відрізка AB.

Більш конкретно, a і b знаходяться на протилежній стороні відрізка CD, якщо і тільки якщо точно одна з двох трійки a, c, d і b, c, d знаходиться в порядку проти годинникової стрілки.

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 представляють проти годинникової стрілки, яка повертає true / false на основі орієнтації точок.

Джерело: http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf Сторінка 2


2
Я думаю, ви повинні бути трохи більш конкретними: як визначається CCWтест? З ознакою зовнішнього виробу?
ocramz

Дякую; цей псевдо-код дозволив дуже просто реалізувати в Scratch; дивіться цей проект: scratch.mit.edu/projects/129319027
Рууд Гельдерман

8

С і Завдання-С

На основі відповіді Гарета Різа

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
У цьому коді є кілька проблем. Він може створити виняток завдяки поділу на нуль; це повільно, оскільки бере квадратні корені; і іноді повертає помилкові позитиви, оскільки використовує фактор викривлення. Ви можете зробити краще, ніж це!
Гарет Різ

Гаразд, як рішення, але те, що дається Джейсоном, безумовно, обчислювально швидше і дозволяє уникнути багатьох проблем з цим рішенням
Елементальний

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 запропонував версію javascript у коментарях, а iMalc надав версію з трохи меншими обчисленнями . Деякі вказали на недоліки в різних кодових пропозиціях, а інші прокоментували ефективність деяких кодових пропозицій.

Алгоритм, наданий iMalc за допомогою відповіді Гевіна, є тим, який я зараз використовую в проекті JavaScript, і я просто хотів надати тут очищену версію, якщо це може допомогти комусь.

// 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 Це стосується векторної математики та визначення крапкового продукту та крос-продукту. Наприклад, опублікований вами код представляє перехресну операцію з продуктом. Це спосіб проектування одного відрізка лінії на інший, щоб визначити, де він потрапляє на інший відрізок рядка, перед початковою точкою десь посередині або після лінії. Отже t - це нормалізоване значення. Якщо він знаходиться між 0 і 1, то два сегменти перетинаються. Якщо це менше 0 або більше, ніж один, то вони не роблять.
Ноло

@Supuhstar Також зауважте, що для того, щоб проекція знайшла фактичну точку, результат потрібно масштабувати. Ось де t/dзаходить.
Ноло

1
Я маю на увазі, як ти розумієш, що відбувається з першого погляду зі змінними назвами на зразок цього? Чому б не щось на кшталт crossProduct = (line1XDifference * line2YDifference) - (line2XDifference * line1YDifference)і scaledResult = crossProduct / dotProduct?
Супухстар

1
@Supuhstar Ах, я бачу, що ти маєш на увазі. Ем, добре, я вважаю, що насправді немає вагомих причин говорити про нав'язливість над ефективністю, але це не дуже хороша причина сама по собі, тому що компілятори роблять досить хорошу роботу, беручи більшість будь-яких кодів, які ви їм надаєте, і роблячи це так само ефективно, як можливо, не змінюючи того, що слід обчислити. З іншого боку, імена p1x, p1yтощо призначені для опису точок за їх значеннями x та y, тому p1xабревіатура для point1x, так само d1x, на мій погляд, це абревіатура для грецької літери, deltaXабо ви можете сказати differenceInX. (детальніше)
Ноло

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

Я переніс вище відповідь Криса на 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;
}

На основі цих двох формул: (я спростив їх з рівняння ліній та інших формул)

формула для х

формула для у


Працює, але спробуйте ввести цю координату (якщо вона колінейна / перекривається, то вона поверне помилковий результат): PointA1 = (0,0) PointA2 = (0,2) і PointB1 = (0,1) PointB2 = (0,5)
Dns

@dns Ну, це тому, що код повертає false для паралельних рядків. Я бачу проблему, однак я все ще не знаю, яку функцію слід повернути, оскільки на неї існує нескінченна кількість відповідей.
Соруш Фалахаті

2

Це грунтується на відповіді Гарета Рі. Він також повертає перекриття сегментів рядків, якщо вони є. Зашифрований у 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. Можна також замінити floatз , doubleякщо що відповідає вашим потребам краще.

Цей код використовується в моїй бібліотеці фізики .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 - Вибачте Моніку

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) 'лежить на прямій осі + на + 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 - це завжди двозначна точка, будь ласка, прочитайте однорідні координати, якщо вас плутають (L1xL2) - вектор з трьома елементами, а x - 2D координати.

відповідно до потрійного продукту, ми це знаємо

L1 * (L1 x L2) = 0, а L2 * (L1 x L2) = 0, через площину L1, L2

ми підставляємо (L1xL2) вектором x, то маємо L1 * x = 0, L2 * x = 0, що означає, що x лежить як на L1, так і на L2, x - точка перетину.

будьте обережні, тут x - однорідні координати, якщо останній елемент x дорівнює нулю, це означає, що 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
  };
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.