Как найти ближайшую точку периметра прямоугольника к заданной точке?

Это вопрос, не зависящий от языка. Учитывая размеры прямоугольника с l,t,w,h (слева, сверху, ширина, высота) и точкой x,y, как мне найти ближайшую точку по периметру прямоугольника к этой точке?

Я пытался решить это на Lua, но подойдет и любой другой язык. Пока это моя лучшая попытка:

local function nearest(x, a, b)
  if a <= x and x <= b then
    return x
  elseif math.abs(a - x) < math.abs(b - x) then
    return a
  else
    return b
  end
end

local function getNearestPointInPerimeter(l,t,w,h, x,y)
  return nearest(x, l, l+w), nearest(y, t, t+h)
end

Это работает для точки за пределами периметра или в самом периметре. Но для точек внутри периметра он терпит неудачу (он просто возвращает x,y)

Моя интуиция подсказывает мне, что решение должно быть простым, но я, кажется, не нахожу его.


person kikito    schedule 08.12.2013    source источник


Ответы (5)


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

local abs, min, max = math.abs, math.min, math.max

local function clamp(x, lower, upper)
  return max(lower, min(upper, x))
end

local function getNearestPointInPerimeter(l,t,w,h, x,y)
  local r, b = l+w, t+h

  x, y = clamp(x, l, r), clamp(y, t, b)

  local dl, dr, dt, db = abs(x-l), abs(x-r), abs(y-t), abs(y-b)
  local m = min(dl, dr, dt, db)

  if m == dt then return x, t end
  if m == db then return x, b end
  if m == dl then return l, y end
  return r, y
end
person Keeper    schedule 08.12.2013
comment
К сожалению, это не так. Ваш алгоритм возвращает координаты ближайшего угла. Ближайшая точка периметра не всегда является одним из углов. - person kikito; 08.12.2013
comment
Я отредактировал свой ответ другим алгоритмом. Я протестировал его с некоторыми значениями на lua.org/cgi-bin/demo, и он кажется, работает хорошо. - person Keeper; 08.12.2013
comment
Извините, я не думаю, что это работает. Я получаю очень случайные значения - иногда даже за пределами самого прямоугольника. - person kikito; 08.12.2013
comment
Какие значения вы использовали? Пробовал очки внутри и снаружи и у меня работает - person Keeper; 08.12.2013
comment
Я думаю, что исправил ошибку. Пожалуйста, вернитесь, если это действительно не лучше. - person David Cary; 08.12.2013
comment
@DavidCary Я изменил свой ответ, как вы предложили (ваше редактирование было отклонено) - person Keeper; 08.12.2013
comment
Спасибо Keeper, это работает великолепно. Вы не возражаете, если я отредактирую ваш ответ, чтобы сделать его более похожим на Lua? - person kikito; 08.12.2013
comment
Сделаны правки. Я думаю, это выглядит великолепно. Спасибо еще раз. - person kikito; 08.12.2013
comment
Очень хорошая редакция! Я написал более агностическую версию, чтобы любой, кто не разбирается в Lua, мог легче понять решение :-) - person Keeper; 08.12.2013

Пусть C1,C2,C3,C4 — вершины прямоугольника.
Из заданной точки A проведите 2 линии,
перпендикулярные сторонам прямоугольника. Пусть B1, B2, B3, B4
— их точки пересечения с линиями, определяемыми
сторонами прямоугольника (некоторые из этих Bk тоже могут совпадать
, например, если A = Ck для некоторого k). Ваше решение — одна из точек Bk
или одна из точек Ck, просто перебором проверьте 8 точек
(опять же, некоторые из этих 8 точек могут совпадать, но это не имеет значения).

person peter.petrov    schedule 08.12.2013
comment
некоторые или все могут также не существовать, рассмотрим прямоугольник 0,0,1,1 и точку 2,2 - person mornfall; 08.12.2013
comment
Хорошо, я отредактировал свой ответ, я считаю, что это работает. Мне просто нужно доказать это :), что не должно быть слишком сложно, если только моя интуиция не сыграла со мной злую шутку. - person peter.petrov; 08.12.2013
comment
Да, хорошо, доказательство тривиально, если разбить плоскость на 9 секторов (что происходит, если удлинить стороны прямоугольника) и подумать, где может лежать точка А (в каком секторе). - person peter.petrov; 08.12.2013
comment
Я бы действительно предпочел что-то, что не требует перебора всех возможных кандидатов. Этот фрагмент кода будет использоваться в критически важном по времени пути библиотеки, которую я создаю (обнаружение коллизий), поэтому время имеет существенное значение. - person kikito; 08.12.2013
comment
@mornfall Не существовать - нет. Я имел в виду эти Bk - точки пересечения с линиями, определяемыми сторонами прямоугольника, а не самими сторонами. - person peter.petrov; 08.12.2013
comment
@kikito Тогда 1) разбейте самолет на эти 9 секторов, которые я упомянул, посмотрите, в каком секторе находится точка A, и дайте свой ответ непосредственно на основе этого. Но я думаю, что это займет больше времени, чем 2) всегда вычислять 8 баллов и просто проверять 8 баллов в конце (что в любом случае является операцией O (1)). Я бы всегда выбирал 2) (исходя из моего предыдущего опыта). Более общее решение не означает медленнее во всех случаях. Это мое мнение. - person peter.petrov; 08.12.2013
comment
@peter.petrov, ну, вы используете термин «линия» очень запутанным образом - есть только 2 линии, перпендикулярные сторонам прямоугольника; хотя может быть до 4 таких сегментов линии (и, следовательно, мое предположение, что вы используете линию для обозначения сегмента линии). - person mornfall; 08.12.2013
comment
@mornfall Я говорил в общем. Если у вас есть 1 точка и 4 различные линии на плоскости, у вас обычно есть 4 перпендикулярные линии от точки к заданным 4 линиям. Но здесь, поскольку стороны прямоугольника попарно параллельны, да, действительно у вас есть только 2 перпендикулярные линии (что на самом деле немного упрощает задачу). В любом случае, возможно, я не очень хорошо объяснил. - person peter.petrov; 08.12.2013

Другой возможный алгоритм (похожий на мой 1-й ответ) можно найти здесь - от Dinre.

Вычисление расстояния между многоугольником и точкой в ​​R

Выглядит довольно просто, на самом деле это упрощенная (возможно, лучшая) версия моего первого ответа здесь.

Найдите две ближайшие вершины прямоугольника Ci и Cj к данной точке A.

Найдите точку M, в которой перпендикулярная прямая из A к прямой (Ci,Cj) пересекает прямую (Ci,Cj).

Ваше решение либо Ci, либо Cj, либо M.

Мне кажется, что это работает для всех случаев (независимо от того, где точка A лежит на плоскости).

person peter.petrov    schedule 08.12.2013
comment
Это хорошая альтернатива. Я считаю, что это то, что @Keeper делает (неявно) в своем ответе. - person kikito; 08.12.2013
comment
Я не смотрел на этот код. Скорее всего это так. Реализация должна быть тривиальной, если вы используете этот алгоритм. Я могу написать это на Java или C# или на чем-то, что я знаю чуть позже ;) Я действительно не знаю ваш язык. В любом случае, удачи. - person peter.petrov; 08.12.2013

Вы ищете что-то подобное? Вдохновленный кодом Keeper:

local function getNearestPointInPerimeter(l,t,w,h, x,y)
  -- x axis increases to the right
  -- y axis increases down
  local r = l + w
  local b = t + h
  local inside = true -- unless later proven otherwise
  -- if the point (x,y) is outside the rectangle,
  -- push it once to the nearest point on the perimeter, or
  -- push it twice to the nearest corner.
  if x < l then x = l; inside = false; end
  if x > r then x = r; inside = false; end
  if y < t then y = t; inside = false; end
  if y > b then y = b; inside = false; end
  -- if the point (x,y) is inside the rectangle,
  -- push it once to the closest side.
  if inside then
      local dt = math.abs (y - t)
      local db = math.abs (y - b)
      local dl = math.abs (x - l)
      local dr = math.abs (x - r)
      if dt <= db and dt <= dl and dt <= dr then
        y = t
      elseif db <= dl and db <= dr then
        y = b
      elseif dl <= dr then
        x = l
      else
        x = r
      end
  end
  return x,y
end
person David Cary    schedule 08.12.2013
comment
Спасибо за ответ, я приму ответ Хранителя. - person kikito; 08.12.2013

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

Я успешно использовал это в графическом интерфейсе с QRect и QPoint Qt, чтобы убедиться, что что-то появилось в QGraphcsView.

def getNearestPointInPerimeter(self, left, top, width, height, x, y):
    right = left + width
    bottom = top + height

    clamp = lambda value, minv, maxv: max(min(value, maxv), minv)
    x = clamp(x, left, right)
    y = clamp(y, top, bottom)

    dl = abs(x - left)
    dr = abs(x - right)
    dt = abs(y - top)
    db = abs(y - bottom)
    m = min(dl, dr, dt, db)

    if m == dt:
        result = (x, top)
    elif m == db:
        result = (x, bottom)
    elif m == dl:
        result = (left, y)
    else:
        result = (right, y)

    return result
person Rafe    schedule 20.03.2017