Як виявити 2D лінію при зіткненні лінії?


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

Тоді, дозвольте спробувати ще раз, я, можливо, змішав математику в цьому. Я незабаром повідомлю вас. Дякую тонну, ніколи :)
Vishnu

1
Я отримав бажаний результат від вашого алгоритму, дякую @DavidGouveia.
Вішну

1
Ну, але тепер у мене є ще одна проблема :)! Мені потрібно зробити пересічені лінії червоним кольором і зеленим. Перехрестя працює чудово. Але, як я зрозумів зараз, (не математично, хоча), що простий if-else не буде працювати, як для розміщення червоних і зелених ліній для пересічених і не пересічних ліній. Вузол, який я перетягую, має і ліву, і праву лінію. Отже, десь щось пішло не так, змінюючи колір непересічних ліній назад на зелений. Я думаю, мені потрібна ще одна умова. Гммм, все-таки спасибі тонну, я відзначаю це як правильну відповідь.
Вишну

4

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

Відрізок лінії 1 - від A до B, відрізок 2 - від C до D

Рядок - це нескінченний рядок, сегмент лінії - це визначена частина цього рядка.

Перевірте, чи перетинаються два обмежувальні поля: якщо немає перетину -> Ні хреста! (розрахунок зроблено, повернути помилково)

Перевірте, чи рядок рядка 1 перетинає рядок 2 та чи рядок рядок 2 простежує рядок 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), якщо таких немає "Ні хреста!" то у вас перехрестя!

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


1
Вибачте, але мені не дуже добре з векторною математикою, я реалізував цей алгоритм як такий, але не отримав результату, вибачте!
Вішну

1
Це має працювати так, можливо, якщо ви можете показати нам свій код, ми можемо допомогти вам там?
Валмонд

Приємно! однак посилання розірвана
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));

Я думаю, що ця відповідь може дійсно покращитися, якби ви пояснили, що робить код.
TomTsagk

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
}

(Я НЕ ПРАВИТИ ЦЕ)


Чи є версія цього, що повертає точку перетину?
SeanRamey
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.