Центр круга: Cx, Cy
Радиус круга: a
Точка, от которой нам нужно провести касательную: Px, Py
Мне нужна формула, чтобы найти две касательные (t1x, t1y) и (t2x, t2y) с учетом всего вышеизложенного.
Редактировать: Есть ли какое-нибудь более простое решение с использованием векторной алгебры или чего-то еще, вместо того, чтобы найти уравнение двух прямых и затем решить уравнение двух прямых, чтобы найти две касательные по отдельности? Также этот вопрос не не по теме, потому что мне нужно написать код, чтобы найти это оптимально.
Я вижу два основных вычислительных метода. Один из них легче понять (если вы знаете тригонометрию) и объяснить, но он использует тригонометрические функции и, следовательно, может не дать точного ответа, когда точный ответ возможен. Другой способ сложнее понять и вычислить, но он использует только 4 основные операции и квадратные корни, поэтому он с большей вероятностью даст точные результаты, когда это возможно. Какой ты предпочитаешь? (В следующий раз объясните отношение к программированию, когда впервые зададите вопрос.)
Оба или любые. Спасибо за голосование, чтобы открыть снова :)
@RoryDaulton в 2D вам не нужна гониометрия, поскольку уравнение вращения - это всего лишь один обмен и одно отрицание ... в 3D вам также не нужно ничего, вы просто используете кросс-продукт ... для ND есть перпендикулярная векторная формула (все еще нет гониометрии)
@Spektre: Я понимаю, что тригонометрические функции не требуются в этой задаче - я думал, что ясно дал это в своем комментарии. Я просто считаю, что тригонометрический подход проще и понятнее для людей, разбирающихся в тригонометрии. Я считаю, что полный код в моем ответе показывает, что это правда.





Хм, не совсем вопрос алгоритма (люди склонны ошибаться в алгоритме и уравнении). Если вы хотите написать код, сделайте это (вы не указали язык и то, что мешает вам это сделать, что является причиной близких голосов) ... Без этого информация, ваш OP просто запрашивает математическое уравнение, которое здесь действительно не по теме, и, отвечая на это, я тоже рискую (справа) проголосовать против (но это / спрашивали здесь много, с гораздо меньшей информацией и 4 повторными голосами против Я закрыл свое решение снова открыть и ответить на это в любом случае).
Вы можете использовать тот факт, что вы находитесь в 2D, поскольку в 2D перпендикулярные векторы к вектору a(x,y) вычисляются следующим образом:
c = (-y, x)
d = ( y,-x)
c = -d
поэтому вы меняете местами x,y и инвертируете один (который определяет, является ли перпендикулярный вектор CW или Против часовой стрелки). На самом деле это формула вращения, но поскольку мы вращаемся на 90 градусов, cos,sin - это просто +1 и -1.
Теперь нормальный n к любой точке окружности на окружности лежит на линии, проходящей через эту точку, и в центре окружности. Итак, сложив все это вместе, ваши касательные:
// normal
nx = Px-Cx
ny = Py-Cy
// tangent 1
tx = -ny
ty = +nx
// tangent 2
tx = +ny
ty = -nx
Если вам нужны единичные векторы, а не просто деление на радиус a (не уверен, почему вы не называете его r, как остальной математический мир), так что:
// normal
nx = (Px-Cx)/a
ny = (Py-Cy)/a
// tangent 1
tx = -ny
ty = +nx
// tangent 2
tx = +ny
ty = -nx
Я не понимаю. Предполагая, что (Cx, Cy) равняется (1,1) a равному 1, а (Px, Py) равняется (3,1), я получаю (0,2) и (0, -2) как касательные. Вы что-то не так думаете? P - внешняя точка вне круга.
Уравнение @cegprakash правильное. Ваш ввод неверен. Если у вас есть C(1,1) и a=1, то Px не может быть 3, так как это было бы слишком далеко от вашего центра (|Px-Cx| в два раза больше радиуса, что неверно), поэтому ваш тестовый P не лежит на вашем круге. но касательные в порядке, даже для вашего неправильного P, они просто неправильного размера ... P(3,1) находится прямо справа от центра круга C (1,1), поэтому касательные указывают только вверх и вниз, следовательно, (0,+/-2) или (0,+/-1) для правильного a=2, поэтому компонент tx отсутствует
Переместите круг в начало координат, поверните, чтобы переместить точку на X, и уменьшите масштаб на R, чтобы получить единичный круг.
Теперь касание достигается, когда начало координат (0, 0), (приведенная) заданная точка (d, 0) и произвольная точка на единичной окружности (cos t, sin t) образуют прямоугольный треугольник.
cos t (cos t - d) + sin t sin t = 1 - d cos t = 0
Из этого вы рисуете
cos t = 1 / d
а также
sin t = ±√(1-1/d²).
Чтобы получить точки касания в исходной геометрии, масштабируйте, откручивайте и непереводите. (Это простые операции линейной алгебры.) Обратите внимание, что нет необходимости выполнять прямое преобразование явно. Все, что вам нужно, это d, отношение центра расстояния к радиусу.
Вот еще один способ использования комплексных чисел. Если a - направление (комплексное число длины 1) точки касания на окружности от центра c, а d - (реальная) длина по касательной до точки p, то (поскольку направление касательной равно Я)
p = c + r*a + d*I*a
перестановка
(r+I*d)*a = p-c
Но длина a равна 1, поэтому, взяв длину, мы получим
|r+I*d| = |p-c|
Мы знаем все, кроме d, поэтому можем решить относительно d:
d = +- sqrt( |p-c|*|p-c| - r*r)
а затем найдите а и точки на круге, по одной для каждого значения d, указанного выше:
a = (p-c)/(r+I*d)
q = c + r*a
Я понял все твои шаги, кроме первого. что я? Каким будет направление касательной к окружности I * a?
@cegprakash I - квадратный корень из -1. Умножение комплексного числа на другое длиной один означает поворот первого числа, Умножение на 1 - это поворот на 0 градусов, на -1 - на поворот на 180 градусов, а на I - на поворот на 90 градусов. Обычно умножение на cos (a) + I * sin (a) означает поворот на a (радианы). Итак, то, что я сказал, сводится к тому, что касательная к окружности в точке находится под прямым углом к радиальному вектору точки.
Как мне избавиться от комплексного числа, чтобы найти?
Упрощая, я получаю a = ((p - c) r - (p-c) Id) / (r ^ 2 + d ^ 2) Итак, достаточно ли мне повернуть вектор p-c на 90 градусов?
@cegprakash ну, вам нужно вычислить и (p-c) * r, и (p-c) * I * d. Я не уверен, что это большое упрощение, я думаю, что проще просто выполнить сложное разделение, если ваш язык поддерживает это. Обратите внимание, что a - комплексное число. Если вам нужен угол в радианах в C, это будет carg (a).
Я думаю, что могу повернуть вектор CP на Theta, где cos theta = r / | CP | найти. Аналогичным образом поворот -a на 90 градусов должен найти эквивалентный вектор I * a. Это должно избегать комплексных чисел, верно?
добавил здесь реализацию C# stackoverflow.com/a/53252024/175592
Вот один из способов использования тригонометрии. Если вы разбираетесь в триггере, этот метод легко понять, хотя он может не дать точного правильного ответа, когда это возможно, из-за отсутствия точности в триггерных функциях.
Даны точки C = (Cx, Cy) и P = (Px, Py), а также радиус a. На моей диаграмме радиус показан дважды, как a1 и a2. Вы можете легко вычислить расстояние b между точками P и C, и вы увидите, что отрезок b образует гипотенузу двух прямоугольных треугольников со стороной a. Угол theta (также дважды показанный на моей диаграмме) находится между гипотенузой и соседней стороной a, поэтому его можно вычислить с помощью арккосинуса. Угол направления вектора от точки C к точке P также легко найти с помощью арктангенса. Направляющие углы точек касания представляют собой сумму и разность исходного направляющего угла и рассчитанного треугольного угла. Наконец, мы можем использовать эти углы направления и расстояние a, чтобы найти координаты этих точек касания.
Вот код на Python 3.
# Example values
(Px, Py) = (5, 2)
(Cx, Cy) = (1, 1)
a = 2
from math import sqrt, acos, atan2, sin, cos
b = sqrt((Px - Cx)**2 + (Py - Cy)**2) # hypot() also works here
th = acos(a / b) # angle theta
d = atan2(Py - Cy, Px - Cx) # direction angle of point P from C
d1 = d + th # direction angle of point T1 from C
d2 = d - th # direction angle of point T2 from C
T1x = Cx + a * cos(d1)
T1y = Cy + a * sin(d1)
T2x = Cx + a * cos(d2)
T2y = Cy + a * sin(d2)
Есть очевидные способы объединить эти вычисления и сделать их немного более оптимизированными, но я оставлю это вам. Также можно использовать формулы сложения и вычитания углов тригонометрии с некоторыми другими тождествами, чтобы полностью удалить тригонометрические функции из вычислений. Однако результат более сложный и трудный для понимания. Без тестирования я не знаю, какой подход более «оптимизирован», но в любом случае это зависит от ваших целей. Сообщите мне, нужен ли вам этот другой подход, но другие ответы здесь все равно дают вам другие подходы.
Обратите внимание, что если a > b, то acos(a / b) выдаст исключение, но это означает, что точка P является внутри окружностью и точки касания нет. Если a == b, то точка P - это окружность на, и есть только одна точка касания, а именно сама точка P. Мой код предназначен для корпуса a < b. Я оставлю вам кодировать другие случаи и определять необходимую точность, чтобы решить, равны ли a и b.
Очень хорошее объяснение, спасибо! Я считаю, что есть ошибка, d должен быть d = atan2(Py - Cy, Px - Cx)
@AlbertoMalagoli: Да, вы правы, большое вам спасибо! Я отредактировал свой код, протестировал его и изменил свой ответ выше. Во-первых, мне следовало провести более тщательное тестирование!
Давайте рассмотрим процесс вывода:


Как видите, если внутренняя часть квадрата <0, это потому, что точка находится внутри окружности. Когда точка находится за пределами окружности, есть два решения, в зависимости от знака квадрата.
Остальное легко. Возьмите atan(solution) и будьте осторожны со знаками, возможно, вам лучше сделать некоторые проверки.
Используйте (2), а затем отмените (1) преобразования, и все.
C# реализация ответа dmuir:
static void FindTangents(Vector2 point, Vector2 circle, float r, out Line l1, out Line l2)
{
var p = new Complex(point.x, point.y);
var c = new Complex(circle.x, circle.y);
var cp = p - c;
var d = Math.Sqrt(cp.Real * cp.Real + cp.Imaginary * cp.Imaginary - r * r);
var q = GetQ(r, cp, d, c);
var q2 = GetQ(r, cp, -d, c);
l1 = new Line(point, new Vector2((float) q.Real, (float) q.Imaginary));
l2 = new Line(point, new Vector2((float) q2.Real, (float) q2.Imaginary));
}
static Complex GetQ(float r, Complex cp, double d, Complex c)
{
return c + r * (cp / (r + Complex.ImaginaryOne * d));
}
Я отредактировал вопрос. Пожалуйста, проголосуйте за повторное открытие. Спасибо.