Перейти до основного вмісту

Знаходження перетину двох відрізків

Нам задано два відрізки AB і CD, описані парами своїх кінців. Кожен відрізок може бути окремою точкою, якщо його кінці збігаються. Потрібно знайти перетин цих відрізків, який може бути порожнім (якщо відрізки не перетинаються), окремою точкою або відрізком (якщо задані відрізки перекриваються).

Коли підходить цей алгоритм?
  • Вам потрібно явно побудувати сам перетин (точку чи спільний відрізок) для пари відрізків, а не лише дізнатися факт перетину? (якщо потрібен лише факт «так/ні» → Перевірка перетину відрізків)
  • Відрізків лише два (чи їх небагато), тож достатньо прямого аналітичного розв'язку? (якщо треба знайти перетин серед nn відрізків → Пошук пари перетинних відрізків)
  • Вам потрібно коректно обробити окремі випадки: збіжні/паралельні відрізки, відрізки-точки та перекриття вздовж однієї прямої?

Розв'язок

Знайти точку перетину відрізків ми можемо так само, як і перетин прямих: відновити рівняння прямих за кінцями відрізків і перевірити, чи вони паралельні.

Якщо прямі не паралельні, нам потрібно знайти їхню точку перетину й перевірити, чи належить вона обом відрізкам (для цього достатньо перевірити, що точка перетину належить кожному відрізку, спроєктованому на осі X та Y). У цьому випадку відповіддю буде або «немає перетину», або єдина точка перетину прямих.

Випадок паралельних прямих дещо складніший (сюди ж належить і випадок, коли один або кілька відрізків є окремою точкою). У цьому випадку нам потрібно перевірити, що обидва відрізки належать одній прямій. Якщо ні, відповідь — «немає перетину». Якщо так, то відповіддю є перетин відрізків, що лежать на одній прямій, який отримуємо, впорядкувавши кінці обох відрізків за зростанням певної координати і взявши найправіший із лівих кінців та найлівіший із правих кінців.

Якщо обидва відрізки є окремими точками, ці точки мають бути однаковими, і цю перевірку має сенс виконати окремо.

На початку алгоритму додамо перевірку обмежувального прямокутника (bounding box) — вона необхідна для випадку, коли відрізки належать одній прямій, і (будучи легкою перевіркою) дозволяє алгоритму працювати в середньому швидше на випадкових тестах.

Реалізація

Ось реалізація, що включає всі допоміжні функції для обробки прямих і відрізків.

Головна функція intersect повертає true, якщо відрізки мають непорожній перетин, і зберігає кінці відрізка перетину в аргументах left та right. Якщо відповіддю є окрема точка, значення, записані в left та right, будуть однаковими.

const double EPS = 1E-9;

struct pt {
double x, y;

bool operator<(const pt& p) const
{
return x < p.x - EPS || (abs(x - p.x) < EPS && y < p.y - EPS);
}
};

struct line {
double a, b, c;

line() {}
line(pt p, pt q)
{
a = p.y - q.y;
b = q.x - p.x;
c = -a * p.x - b * p.y;
norm();
}

void norm()
{
double z = sqrt(a * a + b * b);
if (abs(z) > EPS)
a /= z, b /= z, c /= z;
}

double dist(pt p) const { return a * p.x + b * p.y + c; }
};

double det(double a, double b, double c, double d)
{
return a * d - b * c;
}

inline bool betw(double l, double r, double x)
{
return min(l, r) <= x + EPS && x <= max(l, r) + EPS;
}

inline bool intersect_1d(double a, double b, double c, double d)
{
if (a > b)
swap(a, b);
if (c > d)
swap(c, d);
return max(a, c) <= min(b, d) + EPS;
}

bool intersect(pt a, pt b, pt c, pt d, pt& left, pt& right)
{
if (!intersect_1d(a.x, b.x, c.x, d.x) || !intersect_1d(a.y, b.y, c.y, d.y))
return false;
line m(a, b);
line n(c, d);
double zn = det(m.a, m.b, n.a, n.b);
if (abs(zn) < EPS) {
if (abs(m.dist(c)) > EPS || abs(n.dist(a)) > EPS)
return false;
if (b < a)
swap(a, b);
if (d < c)
swap(c, d);
left = max(a, c);
right = min(b, d);
return true;
} else {
left.x = right.x = -det(m.c, m.b, n.c, n.b) / zn;
left.y = right.y = -det(m.a, m.c, n.a, n.c) / zn;
return betw(a.x, b.x, left.x) && betw(a.y, b.y, left.y) &&
betw(c.x, d.x, left.x) && betw(c.y, d.y, left.y);
}
}

Відеоматеріали