Как определить, где пересекаются два отрезка? [закрыто]

518

Как определить, пересекаются ли две линии, и если они это делают, в какой точке x, y?

KingNestor
источник
Это может помочь представить края прямоугольника как отдельные линии, а не как полный многоугольник.
Райан Грэм
Примечание модератора : обсуждение того, относится ли этот пост к теме, относится к переполнению стека Meta. Другие комментарии по этому поводу будут удалены.
Мартин Питерс

Ответы:

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 = ( 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? Формула для т на самом деле |(q − p) × s| / |(r × s)|?
LarsH
7
@LarsH: см. Первый абзац.
Гарет Рис
35
Для тех, кто заинтересован, вот простая реализация на C # с начальными и конечными координатами PointF для линий, которая, кажется, работает: ideone.com/PnPJgb
Matt
24
Я собрал реализацию JavaScript после @Matt. Я исправил ошибки, указанные Текито.
pgkelley
230

FWIW, следующая функция (в C) обнаруживает пересечения линий и определяет точку пересечения. Он основан на алгоритме Андре ЛеМота " Уловки гуру программирования игр для Windows ". Это не отличается от некоторых алгоритмов в других ответах (например, Гарет). Затем ЛеМот использует правило Крамера (не спрашивайте меня), чтобы решить сами уравнения.

Я могу засвидетельствовать, что это работает в моем слабом клоне астероидов, и, кажется, правильно справляется с крайними случаями, описанными в других ответах Elemental, Dan и Wodzu. Это также, вероятно, быстрее, чем код, опубликованный 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

Это смущало меня часами . :(

Gavin
источник
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; вар с, т; 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); return [intX, intY]; } return null; // Нет столкновения}
cortijon
13
хороший алгоритм, однако, к сожалению, он не обрабатывает случаи, когда детерминант равен 0. (-s2_x * s1_y + s1_x * s2_y выше). Если это 0 (или около 0), линии параллельны или коллинеарны. Если он коллинеарен, то пересечение может быть другим отрезком линии.
13
16
Две операции деления можно избежать для скорости (деление стоит больше, чем умножение); если линии пересекаются, вам нужно одно деление, если они не пересекаются, вам нужен ноль. Сначала нужно вычислить знаменатель и рано останавливаться, если он равен нулю (возможно, добавляя код для определения коллинеарности.) Затем, вместо непосредственного вычисления sи вычисления t, проверьте взаимосвязь между двумя числителями и знаменателем. Только если линии подтверждены для пересечения, вам действительно нужно вычислить значение t(но не s).
Qwertie
18
Я провел тестирование производительности на всех алгоритмах, размещенных здесь, и этот по крайней мере в два раза быстрее, чем любой другой. Спасибо за публикацию!
Лайос
63

Проблема сводится к этому вопросу: пересекаются ли две линии от A до B и от C до D? Затем вы можете задать это четыре раза (между линией и каждой из четырех сторон прямоугольника).

Вот векторная математика для этого. Я предполагаю, что линия от A до B является рассматриваемой линией, а линия от C до 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 прямоугольная линия находится «перед» данной линией.

Вывод:

A и C - векторы, указывающие на начало линии; E и F - векторы от концов A и C, которые образуют линию.

Для любых двух непараллельных линий на плоскости должна быть ровно одна пара скаляров gи hтакая, чтобы это уравнение выполнялось:

A + E*g = C + F*h

Почему? Поскольку две непараллельные линии должны пересекаться, это означает, что вы можете масштабировать обе линии на некоторое количество и касаться друг друга.

( Сначала это выглядит как одно уравнение с двумя неизвестными! Но это не так, если учесть, что это двумерное векторное уравнение, что означает, что это действительно пара уравнений в xи y.)

Мы должны устранить одну из этих переменных. Самый простой способ - Eобнулить термин. Чтобы сделать это, возьмем скалярное произведение обеих сторон уравнения, используя вектор, который укажет на ноль точку E. Этот вектор я назвал 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
Является ли этот алгоритм численно устойчивым? Я попробовал подобный подход, и он показал странные результаты при работе на поплавках.
Милош
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}
Тим Купер
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 является perp-dot-product, который используется в этом коде трижды. Переключение на 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;
}

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

Вы также можете рассмотреть возможность использования теста эпсилон, а не сравнение с нулем. Линии, которые очень близки к параллельным, могут давать результаты, которые немного смещены. Это не ошибка, это ограничение по математике с плавающей точкой.

iMalc
источник
1
Сбой, если некоторые из точек имеют значение 0 .. что не должно произойти, верно?
hfossli
1
Я исправил ошибку, возникшую при отсрочке деления. Т может быть положительным, когда число и число отрицательны.
iMalc
2
Не работает, если p0-p1 вертикальный, а p2-p3 горизонтальный и два сегмента пересекаются. (выполнено первое возвращение)
Фабио Далла Либера
Кулинейный корпус имеет две возможности: не перекрытие и перекрытие. Первое плечо возвращает ложь, второе верно. В вашем коде это не проверено. он всегда возвращает ложь, так как большинство ответов здесь. Обидно, что ни одно решение действительно не работает.
AlexWien
3
Можете ли вы объяснить мне, почему все они используют такие расплывчатые имена переменных s32_yвместо того, чтобы описывать, что это такое point2YDifference?
Супухстар
40

Вопрос C: Как вы определяете, пересекаются ли два отрезка?

Я искал ту же тему, и я не был доволен ответами. Поэтому я написал статью, в которой очень подробно объясняется, как проверить, пересекаются ли два отрезка с большим количеством изображений. Существует полный (и проверенный) Java-код.

Вот статья, обрезанная до самых важных частей:

Алгоритм, который проверяет, пересекается ли отрезок линии a с отрезком линии b, выглядит следующим образом:

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

Что такое ограничивающие рамки? Вот два ограничивающих прямоугольника из двух отрезков:

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

Если оба ограничивающих прямоугольника имеют пересечение, вы перемещаете отрезок линии a так, чтобы одна точка была в (0 | 0). Теперь у вас есть строка через начало координат, определенную как. Теперь передвиньте сегмент линии 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 определены двумя точками для каждой линии. Вы можете применить ту же логику, что и в вопросе А.

Мартин Тома
источник
15
Чтобы быть ясным, Вопрос B в этом ответе действительно о двух пересекающихся линиях, а не отрезках. Я не жалуюсь; это не правильно. Просто не хочу, чтобы кого-то вводили в заблуждение.
phord
1
Там нет "вопрос C". И вопрос 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
Kris
источник
Помните, что вам нужно, чтобы ваши числа плавали или изменили строку на 8denom = float(...)
Jonno_FTW
11

Нахождение правильного пересечения двух отрезков линии - нетривиальная задача с множеством краевых случаев. Вот хорошо документированное, работающее и протестированное решение на Java.

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

  1. Сегменты не пересекаются

  2. Существует уникальная точка пересечения

  3. Пересечение другой сегмент

ПРИМЕЧАНИЕ . В коде я предполагаю, что отрезок (x1, y1), (x2, y2) с x1 = x2 и y1 = 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);
  }
will.fiset
источник
это сработало для моей системы гео-координат! Спасибо! Но это для пересечения бесконечных линий, и я больше ищу пересечение конечных линий.
М. Усман Хан
8

Просто хотел упомянуть, что хорошее объяснение и явное решение можно найти в серии Numeric Recipes. У меня третье издание, и ответ на стр. 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;
}
marcp
источник
Не работает для 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.

Более конкретно, 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 представляют против часовой стрелки, которая возвращает истину / ложь на основе ориентации точек.

Источник: http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf Page 2

zstring
источник
2
Я думаю, что вы должны быть более конкретным: как CCWопределяется тест? С признаком внешнего продукта?
ocramz
Спасибо; этот псевдокод допускает очень простую реализацию в Scratch; см. этот проект: scratch.mit.edu/projects/129319027
Рууд Хелдерман,
8

C и Objective-C

Основано на ответе Гарета Риса

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/

hfossli
источник
Откуда 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;  
   }  
 }  
KingNestor
источник
8
Есть несколько проблем с этим кодом. Это может вызвать исключение из-за деления на ноль; это медленно, потому что это принимает квадратные корни; и иногда он возвращает ложные срабатывания, потому что он использует фактор выдумки. Вы можете сделать лучше, чем это!
Гарет Рис
Хорошо, как решение, но решение, данное Джейсоном, определенно быстрее в вычислительном отношении и позволяет избежать многих проблем с этим решением
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

Кажется, есть некоторый интерес к ответу Гэвина, для которого Кортиджон предложил версию 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
@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));
    }

}
t3chb0t
источник
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;
}
ColacX
источник
2

Вот базовая реализация линейного сегмента в C # с соответствующим кодом обнаружения пересечения. Для этого требуется двумерная векторная / точечная структура 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);
}
volperossa
источник
0

Я прочитал эти алгоритмы из книги "Геометрия с несколькими видами"

следующий текст, используя

'как транспонировать знак

* как точечный продукт

х в качестве перекрестного произведения, при использовании в качестве оператора

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) - это вектор из трех элементов, а 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
  };
}
skibulk
источник