Як виявити 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.