Получите окружность, определяемую двумя точками и касательной линией Lua

Я написал код, чтобы получить круг (или два круга) по заданным двум точкам и касательной линии.

Я создаю алгоритм создания круга с заданными двумя точками и касательной линией для этого круга.

Задача: Для заданных точек A и B и касательной линии, определенной как точки T, постройте окружность G, которая проходит через точки A и B и касается линии T.

Это: PPL: Задача Аполлония с двумя точками и линией

Решение:

  1. Найдите точку пересечения P для AB и T1T2.
  2. Создайте круг с центром в точке между И B с радиусом CB.
  3. Найдите касательную, идущую от P к окружности C, точка касания — D.
  4. Получите радиус PD. Радиус принадлежит окружности P.
  5. Найдите две точки E1 и E2, пересекая линию T и окружность P.
  6. Получите окружности G1 и G2 по заданным трем точкам (A, B, G) как описанную окружность.
  7. Оба круга G являются решением задачи.

Desmos с одним простым решением: ox2zww716t

Изображение:

Функции помощи:

function tangentCircle (cx, cy, cr, px, py) -- circle, point
    local dx, dy = px-cx, py-cy
    local dist = math.sqrt(dx*dx+dy*dy)
    if dist <= cr then return end
    local a, b = math.atan2 (dy, dx), math.acos (cr/dist)   
    local x1 = cx + cr * math.cos (a-b)
    local y1 = cy + cr * math.sin (a-b)
    local x2 = cx + cr * math.cos (a+b)
    local y2 = cy + cr * math.sin (a+b)
    return x1, y1, x2, y2
end

function linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4)
    local dx1, dy1 = x2 - x1, y2 - y1
    local dx2, dy2 = x4 - x3, y4 - y3
    local det = dx2 * dy1 - dx1 * dy2
    if det == 0 then return end
    local c1 = x1 * y2 - y1 * x2
    local c2 = x3 * y4 - y3 * x4
    local x = (c1 * dx2 - c2 * dx1) / det
    local y = (c1 * dy2 - c2 * dy1) / det
    return x, y
end

function circleWithTwoPoints (x1, y1, x2, y2)
    local cx = (x1 + x2) / 2
    local cy = (y1 + y2) / 2
    local cr = math.sqrt((cx - x1)^2 + (cy - y1)^2)
    return cx, cy, cr
end


function intersectCircleCenterLine(cx, cy, cr, tx1, ty1, tx2, ty2)
    local angle = math.atan2 (ty2-ty1, tx2-tx1)
    local ex1, ey1 = cx + cr * math.cos (angle), cy + cr * math.sin (angle)
    local ex2, ey2 = cx + cr * math.cos (angle+math.pi), cy + cr * math.sin (angle+math.pi)
    return ex1, ey1, ex2, ey2
end

function nearestPointOnLine(tx1, ty1, tx2, ty2, cx, cy)
    local dx = tx2 - tx1
    local dy = ty2 - ty1
    local t = ((cx - tx1) * dx + (cy - ty1) * dy) / (dx * dx + dy * dy)
    local nx = tx1 + t * dx
    local ny = ty1 + t * dy
    return nx, ny
end

function circleFromThreePoints(x1, y1, x2, y2, x3, y3)
    local function distance(x1, y1, x2, y2)
        return math.sqrt((x1-x2)^2 + (y1-y2)^2)
    end

    local a = distance(x2, y2, x3, y3)
    local b = distance(x1, y1, x3, y3)
    local c = distance(x1, y1, x2, y2)
    local s = (a + b + c) / 2
    local gr = (a * b * c) / (4 * math.sqrt(s * (s - a) * (s - b) * (s - c)))
    local A = x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2
    local B = (x1*x1 + y1*y1)*(y3-y2) + (x2*x2 + y2*y2)*(y1-y3) + (x3*x3 + y3*y3)*(y2-y1)
    local C = (x1*x1 + y1*y1)*(x2-x3) + (x2*x2 + y2*y2)*(x3-x1) + (x3*x3 + y3*y3)*(x1-x2)
    local D = 2*((x1-x2)*(y3-y2) - (y1-y2)*(x3-x2))

    local gx = B / D
    local gy = C / D
    return gx, gy, gr
end

Основная функция:

function circlesFromTwoPointsAndTangent(ax, ay, bx, by, tx1, ty1, tx2, ty2)
    local px, py = linesIntersect(ax, ay, bx, by, tx1, ty1, tx2, ty2)
    print ('px, py', px, py) -- must be -10, 0, ok

    if px then
        -- two solutions
        local cx, cy, cr = circleWithTwoPoints (ax, ay, bx, by)
        print ('cx, cy, cr', cx, cy, cr) -- must be 65, 75, 49.497475, ok

        local dx1, dy1, dx2, dy2 = tangentCircle (cx, cy, cr, px, py)
        print ('dx1, dy1', dx1, dy1) -- must be 17.7115, 89.6218, ok
        print ('dx2, dy2', dx2, dy2) -- must be 79.6218, 27.7115, ok


--  radius from P to D or from P to E
        local pr = math.sqrt ((dx1-px)^2+(dy1-py)^2)
        print ('pr', pr) -- must be 93.808315

        local ex1, ey1, ex2, ey2 = intersectCircleCenterLine(px, py, pr, tx1, ty1, tx2, ty2)
        local gx1, gy1, gr1 = circleFromThreePoints(ax, ay, bx, by, ex1, ey1)
        print ('gx1, gy1, gr1', gx1, gy1, gr1)
        -- must be 83.8083, 56.1917, 56.191685, ok

        local gx2, gy2, gr2 = circleFromThreePoints(ax, ay, bx, by, ex2, ey2)
        print ('gx2, gy2, gr2', gx2, gy2, gr2)
        -- must be -103.80831519647 243.80831519647 243.80831519647 ok

        return gx1, gy1, gr1, gx2, gy2, gr2
    else -- parallel
        local cx, cy, cr = circleWithTwoPoints (ax, ay, bx, by)

        local ex, ey = nearestPointOnLine(tx1, ty1, tx2, ty2, cx, cy)

        local gx, gy, gr = circleFromThreePoints(ax, ay, bx, by, ex, ey)

        return gx, gy, gr
    end
end

Пример:

local x1, y1 = 30, 40
local x2, y2 = 100, 110
local tx1, ty1 = -100, 0
local tx2, ty2 = 100, 0 -- horizontal line

local gx1, gy1, gr1, gx2, gy2, gr2 = circlesFromTwoPointsAndTangent(x1, y1, x2, y2, tx1, ty1, tx2, ty2)

print ('circle 1', gx1, gy1, gr1) -- must be 83.8083    56.1917 56.191685
print ('circle 2', gx2, gy2, gr2) -- must be -103.808   243.808 243.808315

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

Я не уверен, что понимаю, что вы делаете: если у вас есть только две точки и касательная линия, можете ли вы сначала объяснить, как превратить их в один или два круга, прежде чем углубляться в код? Потому что в вашем сообщении отсутствует объяснение конструкции, а это означает, что невозможно определить, делает ли ваш код то, что, по вашему мнению, он должен делать.

Mike 'Pomax' Kamermans 12.04.2024 02:31

Для заданных точек A и B и касательной линии, определенной как точки T, постройте круг G, который проходит через точки A и B и касается линии T. Итак, сначала найдите точку пересечения P для AB и T1T2. Затем найдите касательные от P к окружности между AB. Это дает радиус PD, найдите точку E с этим радиусом на линии T. И просто создайте круг с точками A,B, E.

darkfrei 12.04.2024 07:37
В своем посте, пожалуйста, а не в комментарии. В посте отсутствуют важные детали, которые каждый может найти там, а не в комментариях. Кроме того, здесь описано, как получить один круг. Вы спрашиваете об «одном или двух», поэтому уточните, как вы ожидаете найти два круга по двум точкам, а также по касательной линии.
Mike 'Pomax' Kamermans 12.04.2024 07:53
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
104
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я реализовал решение на Python.

Как это работает: чтобы упростить формулы, я выполняю сдвиг координат, чтобы поместить первую точку в начало координат, затем поворачиваю, чтобы поместить вторую точку на ось OX, затем сдвигаю влево на половину расстояния. Значит центр круга будет лежать на оси OY, а его координаты (0,y).

Теперь я применяю преобразования к точкам линии, а затем преобразую уравнение линии в форму нормального уравнения (px+qy+r=0).

Затем решите квадратное уравнение: квадрат радиуса должен быть равен квадрату расстояния до линии (простая формула для выбранной формы уравнения линии).

Квадратное уравнение может не давать решений (например, линия между точками), одно решение (например, линия, проходящая через вторую точку и перпендикулярно вектору первый-второй) или два решения.

Затем преобразуйте координаты обратно. Пример приведен для моей картинки и для ваших данных.

import math
def circle2ptstangenettoline(ax, ay, bx, by, tx1, ty1, tx2, ty2):
    dist = math.hypot(ax-bx, ay-by)
    h = dist/2
    ang = math.atan2(by-ay, bx-ax)
    ux1 = tx1 - ax
    uy1 = ty1 - ay
    ux2 = tx2 - ax
    uy2 = ty2 - ay
    vx1 = ux1 * math.cos(-ang) - uy1 * math.sin(-ang) - h
    vy1 = ux1 * math.sin(-ang) + uy1 * math.cos(-ang)
    vx2 = ux2 * math.cos(-ang) - uy2 * math.sin(-ang) - h
    vy2 = ux2 * math.sin(-ang) + uy2 * math.cos(-ang)
    vd = math.hypot(vx2-vx1, vy2-vy1)
    p = (vy2-vy1) / vd
    q = (vx1-vx2) / vd
    r = (vx2*vy1-vx1*vy2) / vd
    a = q*q-1
    b = 2*q*r
    c = r*r - h*h
    if a==0:
        if b==0:
            return None
        else:
            y = -c/b
            cr = math.hypot(y, h)
            cx = h * math.cos(ang) - y * math.sin(ang) + ax
            cy = h * math.sin(ang) + y * math.cos(ang) + ay
            return (cx, cy, cr)
    Dis = b*b-4*a*c
    if Dis < 0:
        return None
    if Dis == 0:
        y = -0.5*b/a
        cr = math.hypot(y, h)
        cx = h * math.cos(ang) - y * math.sin(ang) + ax
        cy = h * math.sin(ang) + y * math.cos(ang) + ay
        return (cx, cy, cr)
    y1 = 0.5*(-b - math.sqrt(Dis))/a
    cr1 = math.hypot(y1, h)
    cx1 = h * math.cos(ang) - y1 * math.sin(ang) + ax
    cy1 = h * math.sin(ang) + y1 * math.cos(ang) + ay

    y2 = 0.5*(-b + math.sqrt(Dis))/a
    cr2 = math.hypot(y2, h)
    cx2 = h * math.cos(ang) - y2 * math.sin(ang) + ax
    cy2 = h * math.sin(ang) + y2 * math.cos(ang) + ay
    return ((cx1,cy1,cr1), (cx2,cy2,cr2))


print(circle2ptstangenettoline(-3, 0, 3, 0, 5, 4, -7, -11))
print(circle2ptstangenettoline(-3, 0, 3, 0, 5, 4, 7, -11))
print(circle2ptstangenettoline(30, 40, 100, 110, -100, 0, 100, 0))

None
((0.0, 3.9528611794604025, 4.962369545296388), (0.0, -5.428416735015959, 6.202234133681292))
((-103.8083151964687, 243.8083151964687, 243.80831519646873), (83.80831519646861, 56.1916848035314, 56.191684803531416))

Можно ли преобразовать систему координат в горизонтальную касательную T с y=0? Таким образом, для горизонтальной линии AB будет один круг gx = (ax+bx)/2, gy=gr = ((bx-gx)^2/by+by)/2.

darkfrei 12.04.2024 13:56

Да, конечно. Возможный способ - сдвинуть на -tx1, -ty1, затем повернуть на -math.atan2(ty2-ty1, tx2-tx1).

MBo 12.04.2024 14:06

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