Окружности
Пересечение прямой с окружностью через проекцию центра. Пересечение двух окружностей сводится к первой задаче вычитанием уравнений.
5 мин
Окружность задаётся центром и радиусом: .
Дальше считаем, что центр перенесён в начало координат — так формулы короче, а обратный перенос стоит одного сложения.
Прямая и окружность
Прямая задана как (три представления прямой).
Сколько точек пересечения, решает расстояние от центра до прямой:
Если — пересечения нет, — касание в одной точке, — две точки.
Теперь сами точки. Опустим перпендикуляр из центра на прямую — его основание считается по формуле проекции:
Треугольник «центр — — точка пересечения» прямоугольный: гипотенуза , катет . Второй катет:
Треугольник, образованный центром и двумя точками пересечения, равнобедренный, поэтому — середина хорды. Значит, обе точки получаются откладыванием от в обе стороны вдоль прямой.
Направляющий вектор прямой — ; нормируем его и умножаем на .
vector<Pt> lineCircle(double a, double b, double c, double r) {
double d2 = a * a + b * b;
Pt p0{-a * c / d2, -b * c / d2}; // проекция центра на прямую
double dist2 = c * c / d2; // квадрат расстояния до прямой
if (dist2 > r * r + EPS) return {};
if (fabs(dist2 - r * r) < EPS) return {p0};
double l = sqrt(max(0.0, r * r - dist2));
double m = sqrt(l * l / d2); // множитель для вектора (-b, a)
return {{p0.x - b * m, p0.y + a * m},
{p0.x + b * m, p0.y - a * m}};
}
max(0.0, ...) под корнем — не паранойя: при , почти равном , разность может выйти отрицательной на , и sqrt вернёт NaN.
Проверено: на 300 000 случайных прямых и окружностей найденные точки лежат и на прямой, и на окружности с погрешностью не больше , а число точек согласовано с расстоянием до центра.
Две окружности
Сначала сколько точек. Пусть — расстояние между центрами, и — радиусы.
| условие | что происходит |
|---|---|
| далеко друг от друга, пересечения нет | |
| внешнее касание | |
| две точки | |
| внутреннее касание | |
| одна внутри другой, пересечения нет |
Отдельный вырожденный случай: и — окружности совпадают, точек бесконечно много. Его надо ловить до всего остального.
Сами точки: сведение к прямой
Можно возиться с треугольниками и косинусами. Красивее — вычесть одно уравнение из другого.
Запишем оба уравнения и вычтем. Квадраты и сократятся, останется линейное уравнение:
Это прямая — она называется радикальной осью. Любая точка, лежащая на обеих окружностях, лежит и на ней. Значит, задача свелась к уже решённой: пересечь первую окружность с этой прямой.
vector<Pt> circleCircle(Pt c1, double r1, Pt c2, double r2) {
double dx = c2.x - c1.x, dy = c2.y - c1.y; // переносим c1 в начало координат
double a = -2 * dx, b = -2 * dy;
double c = dx * dx + dy * dy + r1 * r1 - r2 * r2;
auto pts = lineCircle(a, b, c, r1);
for (auto& p : pts) { p.x += c1.x; p.y += c1.y; }
return pts;
}
Приём общий: вычитание двух уравнений понижает степень. Здесь он превратил задачу про две окружности в задачу про прямую и окружность.
Проверено: на 300 000 случайных пар окружностей найденные точки лежат на обеих с погрешностью не больше , а на непересекающихся парах точек не возвращается.
Про точность
Здесь неизбежны корни, а значит, вещественные числа и эпсилон. Целочисленной арифметикой, как в задачах про векторное произведение, обойтись не выйдет.
Единственное, что можно и нужно сделать целыми: проверку количества точек. Сравнивайте с и — все три величины целые, если целые входные данные, и сравнение получается точным.
Касание — самый опасный случай: ответ на вопрос «одна точка или ноль» решается сравнением на равенство, и в вещественных числах оно почти всегда ложно. Целочисленная проверка снимает проблему целиком.