Как обнаружить двухмерную линию на линии столкновения?

13

Я разработчик флеш-сценариев, немного отсталый в математике, хотя я нахожу физику интересной и классной.

Для справки это игра, похожая на ту, которую я делаю: Флеш игра

Я сделал эту распутанную игру почти до полного завершения логики. Но когда две линии пересекаются, мне нужны эти пересекающиеся или «запутанные» линии, чтобы показать другой цвет; красный.

Было бы очень любезно с вашей стороны, если бы вы могли предложить алгоритм обнаружения отрезка столкновений. Я в основном человек, который любит думать «визуально», а не «арифметически» :)

Изменить: я хотел бы добавить несколько диаграмм, чтобы сделать идею более ясной

нет пересечения нет пересечения пересечение нет пересечения

PS я пытаюсь сделать функцию как

private function isIntersecting(A:Point, B:Point, C:Point, D:Point):Boolean

Заранее спасибо.

Вишну
источник
6
Это неутешительное не визуальное объяснение проблемы, но это алгоритм, и он имеет смысл, если вы можете заставить себя читать их математику: local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d Это может будь тяжелым, если твоя векторная математика слаба. Я понимаю - я также предпочитаю визуальные объяснения. Я постараюсь найти время позже, чтобы нарисовать это, но если кто-то вообще склонен к искусству, увидит эту ссылку и найдет время, прежде чем я это сделаю, доберитесь до нее!
Анко

Ответы:

18

Я использую следующий метод, который в значительной степени является просто реализацией этого алгоритма . Это в C #, но перевод его в ActionScript должен быть тривиальным.

bool IsIntersecting(Point a, Point b, Point c, Point d)
{
    float denominator = ((b.X - a.X) * (d.Y - c.Y)) - ((b.Y - a.Y) * (d.X - c.X));
    float numerator1 = ((a.Y - c.Y) * (d.X - c.X)) - ((a.X - c.X) * (d.Y - c.Y));
    float numerator2 = ((a.Y - c.Y) * (b.X - a.X)) - ((a.X - c.X) * (b.Y - a.Y));

    // Detect coincident lines (has a problem, read below)
    if (denominator == 0) return numerator1 == 0 && numerator2 == 0;

    float r = numerator1 / denominator;
    float s = numerator2 / denominator;

    return (r >= 0 && r <= 1) && (s >= 0 && s <= 1);
}

Однако в алгоритме есть небольшая проблема, в которой две строки совпадают, но не пересекаются. Алгоритм по-прежнему возвращает перемычки в этом случае. Если вы заботитесь об этом случае, я полагаю, что этот ответ на stackoverflow имеет более сложную версию, которая обращается к нему.

редактировать

Я не получил результат от этого алгоритма, извините!

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

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

Дэвид Гувея
источник
Я не получил результат от этого алгоритма, извините!
Вишну
4
@ Vish Какая у тебя была проблема? Я проверил эту точную копию алгоритма перед публикацией, и он работал безупречно, за исключением одного описанного случая.
Дэвид Гувея
Тогда, позвольте мне попробовать еще раз, я мог бы перепутать немного математики. Я дам вам знать в ближайшее время. Спасибо огромное, nyways :)
Вишну
1
Я получил желаемый результат от вашего алгоритма, спасибо @DavidGouveia.
Вишну
1
Ну а теперь у меня другая проблема :)! Мне нужно сделать пересекающиеся линии с красным цветом и зеленым. Пересечение работает отлично. Но, как я понял сейчас (хотя и не математически), простое if-else не сработает, если поставить красные и зеленые линии для пересекающихся и непересекающихся линий. Узел, который я перетаскиваю, имеет как левую, так и правую линию. Итак, что-то пошло не так при изменении цвета непересекающихся линий обратно на зеленый. Я думаю, мне нужно другое условие тоже. Хм, в любом случае, спасибо огромное, я отмечаю это как правильный ответ.
Вишну
4

Без дивизий! Так что нет проблем ни с точностью, ни с делением на ноль.

Сегмент 1 линии от A до B Сегмент 2 линии от C до D

Линия - это бесконечная линия, сегмент линии - это определенная часть этой линии.

Проверьте, пересекаются ли две ограничивающие рамки: если пересечения нет -> Нет пересечения! (расчет выполнен, верните false)

Проверьте, пересекает ли линия seg 1 линию seg 2 и не пересекает ли линия seg 2 линию seg 1 (то есть сегмент линии 1 находится по обе стороны от линии, определяемой сегментом линии 2).

Это можно сделать, переместив все точки на -A (т.е. вы переместите 2 строки, чтобы A находилось в оригинале (0,0))

Затем вы проверяете, находятся ли точки C и D по разные стороны линии, определенной от 0,0 до B

//Cross Product (hope I got it right here)
float fC= (B.x*C.y) - (B.y*C.x); //<0 == to the left, >0 == to the right
float fD= (B.x*D.y) - (B.y*D.x);

if( (fc<0) && (fd<0)) //both to the left  -> No Cross!
if( (fc>0) && (fd>0)) //both to the right -> No Cross!

Если у вас еще нет «Нет креста», продолжайте использовать не A, B против C, D, а C, D против A, B (те же кальки, просто поменяйте местами A и C, B и D), если нет "Нет Креста!" тогда у вас есть пересечение!

Я искал точные расчеты для перекрестного продукта и нашел этот пост в блоге, который также объясняет метод.

Valmond
источник
1
Извините, но я не очень хорош с векторной математикой, я реализовал этот алгоритм как таковой, но не получил результата, извините!
Вишну
1
Это должно сработать, так что, может быть, если вы покажете нам свой код, мы вам поможем?
Valmond
Ницца! однако ссылка не работает
clabe45
Есть ли что-то, что вы можете добавить к этому, чтобы получить точку пересечения?
SeanRamey
1

Я просто хочу сказать, что мне это нужно для моей игры Gamemaker Studio, и она работает хорошо:

///scr_line_collision(x1,y1,x2,y2,x3,y3,x4,y4)

var denominator= ((argument2 - argument0) * (argument7 - argument5)) - ((argument3 - argument1) * (argument6 - argument4));
var numerator1 = ((argument1 - argument5) * (argument6 - argument4)) - ((argument0 - argument4) * (argument7 - argument5));
var numerator2 = ((argument1 - argument5) * (argument2 - argument0)) - ((argument0 - argument4) * (argument3 - argument1));

// Detect coincident lines
if (denominator == 0) {return (numerator1 == 0 && numerator2 == 0)}

var r = numerator1 / denominator;
var s = numerator2 / denominator;

return ((r >= 0 && r <= 1) && (s >= 0 && s <= 1));
Лукаш Чулен
источник
Я думаю, что этот ответ мог бы действительно улучшиться, если бы вы объяснили, что делает код.
ТомЦагк
1

Принятый ответ дал неправильный ответ в этом случае:

x1 = 0;
y1 = 0;
x2 = 10;
y2 = 10;

x3 = 10.1;
y3 = 10.1;
x4 = 15;
y4 = 15;

Эти линии, очевидно, не пересекаются, но в соответствии с функцией «правильного ответа» линии пересекаются.

Это то, что я использую:

function do_lines_intersect(px1,py1,px2,py2,px3,py3,px4,py4) {
  var ua = 0.0;
  var ub = 0.0;
  var ud = (py4 - py3) * (px2 - px1) - (px4 - px3) * (py2 - py1);


  if (ud != 0) {
    ua = ((px4 - px3) * (py1 - py3) - (py4 - py3) * (px1 - px3)) / ud;
    ub = ((px2 - px1) * (py1 - py3) - (py2 - py1) * (px1 - px3)) / ud;
        if (ua < 0.0 || ua > 1.0 || ub < 0.0 || ub > 1.0) ua = 0.0;
  }

  return ua;
}

возвращает 0 = линии не пересекаются

возвращает> 0 = линии пересекаются


Обновление, чтобы ответить на вопрос:

Я не создавал этот код сам. Ему более 5 лет, и я не знаю, каков первоисточник. Но..

Я думаю, что возвращаемое значение - это относительная позиция первой строки, где они пересекаются (это плохо объясняется). Чтобы вычислить точку пересечения, вы, вероятно, можете использовать lerp следующим образом:

l = do_lines_intersect(...)
if (l > 0) {
    intersect_pos_x = l * (px2-px1);
    intersect_pos_y = l * (py2-py1);
} else {
    // lines do not cross
}

(Я не проверял это)

Jorammer
источник
Есть ли версия этого, которая возвращает точку пересечения?
SeanRamey