Найдите точки касания в окружности от точки

Центр круга: Cx, Cy

Радиус круга: a

Точка, от которой нам нужно провести касательную: Px, Py

Мне нужна формула, чтобы найти две касательные (t1x, t1y) и (t2x, t2y) с учетом всего вышеизложенного.

Редактировать: Есть ли какое-нибудь более простое решение с использованием векторной алгебры или чего-то еще, вместо того, чтобы найти уравнение двух прямых и затем решить уравнение двух прямых, чтобы найти две касательные по отдельности? Также этот вопрос не не по теме, потому что мне нужно написать код, чтобы найти это оптимально.

Я отредактировал вопрос. Пожалуйста, проголосуйте за повторное открытие. Спасибо.

cegprakash 22.04.2018 20:19

Я вижу два основных вычислительных метода. Один из них легче понять (если вы знаете тригонометрию) и объяснить, но он использует тригонометрические функции и, следовательно, может не дать точного ответа, когда точный ответ возможен. Другой способ сложнее понять и вычислить, но он использует только 4 основные операции и квадратные корни, поэтому он с большей вероятностью даст точные результаты, когда это возможно. Какой ты предпочитаешь? (В следующий раз объясните отношение к программированию, когда впервые зададите вопрос.)

Rory Daulton 22.04.2018 20:44

Оба или любые. Спасибо за голосование, чтобы открыть снова :)

cegprakash 22.04.2018 21:07

@RoryDaulton в 2D вам не нужна гониометрия, поскольку уравнение вращения - это всего лишь один обмен и одно отрицание ... в 3D вам также не нужно ничего, вы просто используете кросс-продукт ... для ND есть перпендикулярная векторная формула (все еще нет гониометрии)

Spektre 23.04.2018 12:04

@Spektre: Я понимаю, что тригонометрические функции не требуются в этой задаче - я думал, что ясно дал это в своем комментарии. Я просто считаю, что тригонометрический подход проще и понятнее для людей, разбирающихся в тригонометрии. Я считаю, что полный код в моем ответе показывает, что это правда.

Rory Daulton 23.04.2018 21:41
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
3 942
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Хм, не совсем вопрос алгоритма (люди склонны ошибаться в алгоритме и уравнении). Если вы хотите написать код, сделайте это (вы не указали язык и то, что мешает вам это сделать, что является причиной близких голосов) ... Без этого информация, ваш 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 23.04.2018 18:37

Уравнение @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 отсутствует

Spektre 23.04.2018 19:57

Переместите круг в начало координат, поверните, чтобы переместить точку на 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 23.04.2018 17:00

@cegprakash I - квадратный корень из -1. Умножение комплексного числа на другое длиной один означает поворот первого числа, Умножение на 1 - это поворот на 0 градусов, на -1 - на поворот на 180 градусов, а на I - на поворот на 90 градусов. Обычно умножение на cos (a) + I * sin (a) означает поворот на a (радианы). Итак, то, что я сказал, сводится к тому, что касательная к окружности в точке находится под прямым углом к ​​радиальному вектору точки.

dmuir 23.04.2018 17:21

Как мне избавиться от комплексного числа, чтобы найти?

cegprakash 23.04.2018 18:02

Упрощая, я получаю a = ((p - c) r - (p-c) Id) / (r ^ 2 + d ^ 2) Итак, достаточно ли мне повернуть вектор p-c на 90 градусов?

cegprakash 23.04.2018 18:17

@cegprakash ну, вам нужно вычислить и (p-c) * r, и (p-c) * I * d. Я не уверен, что это большое упрощение, я думаю, что проще просто выполнить сложное разделение, если ваш язык поддерживает это. Обратите внимание, что a - комплексное число. Если вам нужен угол в радианах в C, это будет carg (a).

dmuir 23.04.2018 19:59

Я думаю, что могу повернуть вектор CP на Theta, где cos theta = r / | CP | найти. Аналогичным образом поворот -a на 90 градусов должен найти эквивалентный вектор I * a. Это должно избегать комплексных чисел, верно?

cegprakash 04.05.2018 14:30

добавил здесь реализацию C# stackoverflow.com/a/53252024/175592

Bas Smit 11.11.2018 19:51

Вот один из способов использования тригонометрии. Если вы разбираетесь в триггере, этот метод легко понять, хотя он может не дать точного правильного ответа, когда это возможно, из-за отсутствия точности в триггерных функциях.

Даны точки 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)

Alberto Malagoli 17.07.2018 16:01

@AlbertoMalagoli: Да, вы правы, большое вам спасибо! Я отредактировал свой код, протестировал его и изменил свой ответ выше. Во-первых, мне следовало провести более тщательное тестирование!

Rory Daulton 17.07.2018 16:18

Давайте рассмотрим процесс вывода:

Как видите, если внутренняя часть квадрата <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));
}

Другие вопросы по теме