Пересечение прямых
Система из двух уравнений, определитель и три случая ответа. Плюс способ, который не требует помнить формулу.
4 мин
Даны две прямые. Найти точку их пересечения — или сказать, что её нет.
Возможных ответов три: одна точка, ни одной (параллельны), бесконечно много (совпадают). Разбирать нужно все три.
Через определители
Прямые заданы уравнениями
Это система двух линейных уравнений. По формулам Крамера:
bool intersect(Line l1, Line l2, Point& res) {
ld det = l1.a * l2.b - l2.a * l1.b;
if (eq(det, 0)) return false; // параллельны или совпадают
res.x = (l1.b * l2.c - l2.b * l1.c) / det;
res.y = (l2.a * l1.c - l1.a * l2.c) / det;
return true;
}
Проверено: на двухстах тысячах случайных пар прямых найденная точка удовлетворяет обоим уравнениям.
Определитель — это векторное произведение нормалей. Он равен нулю ровно тогда, когда нормали коллинеарны, то есть прямые параллельны или совпадают.
Проверять на ноль нужно до деления. Иначе получите бесконечность или NaN, и ошибка проявится далеко от места возникновения.
Параллельны или совпадают
Если , разбираемся дальше. Прямые совпадают, когда все три коэффициента пропорциональны. Без деления это
Оба условия нужны: одного мало, если соответствующий коэффициент нулевой.
Проще и надёжнее — взять любую точку первой прямой и подставить во второе уравнение. Если получился ноль, прямые совпадают.
Через параметризацию
Способ, который не требует помнить формулы Крамера. Пусть первая прямая задана уравнением, а вторая — точкой и направляющей . Любая точка второй прямой имеет вид . Подставим:
Это уравнение с одной неизвестной:
ld denom = l.a * s.x + l.b * s.y; // (нормаль, направляющая)
if (eq(denom, 0)) { /* параллельны */ }
ld t = -(l.a * p.x + l.b * p.y + l.c) / denom;
Point res = p + s * t;
Знаменатель — скалярное произведение нормали первой прямой и направляющей второй. Он нулевой ровно тогда, когда направляющая перпендикулярна нормали, то есть прямые параллельны. Тот же критерий, записанный иначе.
Этот способ удобнее двумя вещами: формулу не надо помнить (она выводится за строчку) и значение полезно само по себе — по нему сразу видно, попадает ли точка пересечения внутрь отрезка или луча.
Точность
Пересечение прямых — то место, где вещественные числа неизбежны: там есть деление. Отсюда две практические оговорки.
Почти параллельные прямые дают огромную погрешность. Определитель близок к нулю, деление на него разносит любую неточность. Если в задаче такое возможно, стоит проверять определитель не на строгий ноль, а на малость — и думать, что делать в этом случае по условию.
Считайте определитель в целых числах, если координаты целые. Коэффициенты , , строятся без деления, значит и определитель целый, и проверка на ноль точная. Вещественными делайте только последнее деление.
Родственные задачи
Пересечение луча и прямой. Считаем параметризацией, проверяем .
Пересечение отрезка и прямой. То же, но .
Пересечение двух отрезков. Формально сводится к пересечению прямых с проверкой обоих параметров, но на практике надёжнее другой способ — через знаки векторных произведений, без деления вовсе.
Точка, равноудалённая от трёх точек (центр описанной окружности). Пересечение двух серединных перпендикуляров: каждый строится по середине отрезка и нормали, равной направляющей этого отрезка.