Ближайший край к прямоугольнику в плоскости прямоугольников

У меня есть 2D-плоскость (с дискретными точками), содержащая прямоугольники произвольного размера, и все прямоугольники выровнены по осям. У меня есть их координаты (вверху слева) и размеры (длина и ширина).

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

Моим первым решением было проверить каждую сторону каждого прямоугольника одну за другой и вычислить расстояние до каждой стороны данного прямоугольника. Но это кажется очень затратным в вычислительном отношении (особенно если учесть это в графическом контексте, то есть в каждом кадре ввода).

Во-вторых, нужно было проверить, не сталкивается ли этот прямоугольник с другим (с AABB), если нет, то по относительному положению их верхней левой координаты я могу определить, какое ребро будет ближайшим к этой паре.

Может ли кто-нибудь предложить лучшее решение этой проблемы?

Примечание 1. Вы можете предположить, что никакие другие прямоугольники, кроме данного прямоугольника, не могут пересекаться (с любым другим).

Обновлено: Спасибо @user24714692 и @Clement44 за ответы. Я смог многому научиться из этой проблемы. И я благодарен вам за то, что мне удалось реализовать эту функцию привязки в своем проекте:

Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
87
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

array[N,4] # N rectangles, with X coordinate, Y coordinate, width and heigth
distance = inf # Initialize distance with high value    
i = chosenRectangle
closest = 0 # Initialize index of closest rectangle

# Vertical colliding
for j=1,N
    if (i/=j) and (array[j,1] > array[i,1] - array[j,3]) and (array[j,1] < array[i,1] + array[i,3]) then
        evaluate distanceTemp # Distance between rectangles i and j
        if distanceTemp < distance then
            distance = distanceTemp 
            closest = j
        end if
    end if
end for

# Same for horizontal colliding, without reinitialization

Спасибо за ответ! Под расстоянием между прямоугольниками вы подразумеваете расстояние между их ближайшим краем, верно?

Harsh 16.05.2024 15:52

Ну, я не совсем понял, какое расстояние вы хотели вычислить. Это горизонтальная (соответственно вертикальная) проекция кратчайшего отрезка между двумя горизонтально (соответственно вертикально) сталкивающимися прямоугольниками? В любом случае, на этом этапе алгоритма вычисляйте его так, как хотите!

Clement44 16.05.2024 16:25
Ответ принят как подходящий

Кажется, задач много.

  • Во-первых, игнорируйте тот факт, что это находится в 2D-плоскости.
  • Представьте, у нас есть линия с несколькими точками на ней.
  • Решите задачу «пересечение» прямой. Назовем это x_axis.
  • Повторите то же решение для второй оси y.
  • Мы хотим найти «пересечения» каждых двух линий с общим пересечением.

  • Предположим, вы выбрали для сравнения один «исходный» прямоугольник.
  • Давайте сейчас добавим все остальные прямоугольники во вторую группу.
  • Обратите внимание: порядок не имеет значения. Кроме того, это алгоритм выше O(N^2), поэтому мы всегда сортируем, как прямоугольники, так и пересечения.

  • После того, как вы нашли пересечения, все, что вам нужно, это найти один прямоугольник с минимальным расстоянием от вашего «исходного» прямоугольника. Я оставлю эту часть тебе.
import random


def get_intersections(A, B):
    get_sorted_by_element(A, 1)
    get_sorted_by_element(A, 0)
    get_sorted_by_element(B, 1)
    get_sorted_by_element(B, 0)
    res = []
    a, b = 0, 0
    if A and B:
        while a < len(A) and b < len(B):
            lo = max(A[a][0], B[b][0])
            hi = min(A[a][1], B[b][1])
            if lo <= hi:
                res.append((lo, hi))

            if A[a][1] < B[b][1]:
                a += 1
            else:
                b += 1
    return res


def get_random_rects(n):
    random.seed(1)
    rects = []
    for i in range(n):
        a = c = random.randint(31, 60)
        b = random.randint(70, 90)
        d = random.randint(70, 110)
        rects.append((a, b, c, d))
    return rects


def get_sorted_by_element(A, i):
    return sorted(A, key=lambda x: x[i])


rects_a = get_random_rects(1)
rects_b = get_random_rects(20)
x_axis = get_intersections([(a, b) for a, b, _, _ in rects_a],
                           [(a, b) for a, b, _, _ in rects_b])
y_axis = get_intersections([(c, d) for _, _, c, d in rects_a],
                           [(c, d) for _, _, c, d in rects_b])


print(f"rectangle to compare: {rects_a}")
print(f"other rectangles: {rects_b}")
print(f"x_axis intersections: {x_axis}")
print(f"y_axis intersections: {y_axis}")

x_axis = get_sorted_by_element(x_axis, 0)
y_axis = get_sorted_by_element(y_axis, 1)
print(f"sorted x_axis intersections: {x_axis}")
print(f"sorted y_axis intersections: {y_axis}")

print(x_axis, y_axis)


Принты

прямоугольник для сравнения: [(35, 88, 35, 74)] другие прямоугольники: [(35, 88, 35, 74), (39, 73, 39, 101), (55, 84, 55, 100), (51, 82, 51, 83), (34, 85, 34, 71), (59, 82, 59, 97), (50, 70, 50, 98), (39, 77, 39, 107), (34, 80, 34, 71), (31, 70, 31, 104), (31, 82, 31, 83), (44, 70, 44, 103), (38, 84, 38, 101), (48, 77, 48, 92), (38, 77, 38, 99), (40, 70, 40, 96), (57, 87, 57, 76), (36, 90, 36, 88), (34, 80, 34, 102), (60, 83, 60, 102)] пересечения оси x: [(35, 88), (39, 73), (55, 84), (51, 82), (35, 85), (59, 82), (50, 70), (39, 77), (35, 80), (35, 70), (35, 82), (44, 70), (38, 84), (48, 77), (38, 77), (40, 70), (57, 87), (36, 88)] пересечения оси y: [(35, 74), (39, 74)] отсортированы по оси x пересечения: [(35, 88), (35, 85), (35, 80), (35, 70), (35, 82), (36, 88), (38, 84), (38, 77), (39, 73), (39, 77), (40, 70), (44, 70), (48, 77), (50, 70), (51, 82), (55, 84), (57, 87), (59, 82)] отсортировано по оси y пересечения: [(35, 74), (39, 74)] [(35, 88), (35, 85), (35, 80), (35, 70), (35, 82), (36, 88), (38, 84), (38, 77), (39, 73), (39, 77), (40, 70), (44, 70), (48, 77), (50, 70), (51, 82), (55, 84), (57, 87), (59, 82)] [(35, 74), (39, 74)]


  • Обычно мы определяем прямоугольник с двумя точками: верхней правой и нижней левой точками.

  • Здесь я использовал три пункта:


Примечание

  • На первый взгляд эта проблема может показаться простой, но, на мой взгляд, это сложный вопрос.

  • По сути, мы здесь делаем зеркальное отображение прямоугольников по осям x и y.

  • Находим каждые две линии, имеющие пересечения. Технически это принадлежит этим двум прямоугольникам. Если любые два прямоугольника имеют пересечения, они находятся в нашем пространстве поиска.

  • Затем у нас есть третий или главный прямоугольник для сравнения с любым из этих двух прямоугольников. Здесь мы сравниваем оси Y и X и видим, что наш основной прямоугольник ближе к расстоянию X или Y.


Примечание

  • Дерево сегментов используется для суммы диапазона:
class SegmentTree:
    def __init__(self, nums):
        self.n = len(nums)
        self.tree = [0] * (2 * self.n)
        self.build(nums)

    def build(self, nums):
        for i in range(self.n):
            self.tree[self.n + i] = nums[i]
        for i in range(self.n - 1, 0, -1):
            self.tree[i] = self.tree[2 * i] + self.tree[2 * i + 1]
        print(self.tree)

    def update(self, idx, val):
        idx += self.n
        self.tree[idx] = val
        while idx > 1:
            idx //= 2
            self.tree[idx] = self.tree[2 * idx] + self.tree[2 * idx + 1]

    def query(self, l, r):
        l += self.n
        r += self.n
        sums = 0
        while l <= r:
            if l % 2 == 1:
                sums += self.tree[l]
                l += 1
            if r % 2 == 0:
                sums += self.tree[r]
                r -= 1
            l //= 2
            r //= 2
        return sums


nums = [1, 3, 5, 7, 9, 11]
segment_tree = SegmentTree(nums)
queries = [(0, 2), (1, 4), (2, 5)]
for query in queries:
    left, right = query
    result = segment_tree.query(left, right)
    print(result)


Принты

[0, 36, 32, 4, 12, 20, 1, 3, 5, 7, 9, 11]

9

24

32


Посмотрите, как можно использовать дерево сегментов для решения этой задачи, чтобы повысить эффективность? Посмотрите на зеленые линии ниже.

Из дерева отрезков

Аналогично проверить бинарный поиск?


Сортировка

  • Сортировка обязательна. Например, представьте, что ваши два прямоугольника, которые вы хотите проверить на наличие общих пересечений, находятся слишком далеко друг от друга. Следовательно, два прямоугольника не находятся в вашем пространстве поиска для сравнения с вашим основным прямоугольником.

  • Поэтому, если вы отсортируете, нам не придется проверять каждый прямоугольник. Нам нужно проверить только «ближние» прямоугольники, если хотите. Эти прямоугольники, находящиеся рядом друг с другом, могут иметь только «пересечения».

Спасибо за ответ! Не могли бы вы объяснить, что именно представляют собой эти пересечения? Пересечение каждой стороны (проекция) с любой другой стороной? А что такое а, б, в, г? (x1, y1, x2, y2)? (вверху слева и внизу справа)

Harsh 16.05.2024 15:51

Таким образом, пересечения — это диапазон точек, которые являются общими для исходного прямоугольника и других прямоугольников. Итак, для этих прямоугольников я вычисляю их расстояния по соседней оси, чтобы сравнить?

Harsh 16.05.2024 17:31

@Harsh Это правильно. Технически мы «заменим» вашу первоначальную проблему на новую. Новая задача также имеет две подзадачи (для осей Y и X).

user24714692 16.05.2024 17:36

но не придется ли мне все равно перебирать весь массив, чтобы вычислить расстояния? Какой тогда смысл сортировки? Я думаю, что мне все еще чего-то не хватает.

Harsh 16.05.2024 18:20

Ой, я не заметил, что под сортировкой вы имели в виду сортировку во время расчета пересечения. Теперь все это имеет смысл (до сих пор я до сих пор не понимаю, как связаны деревья сегментов). Пора это реализовать!

Harsh 16.05.2024 18:32

Предположим, у вас есть две группы прямоугольников по 10^4 элементов в каждой группе (нижняя левая и верхняя правая точки): rects_a:[[0, 0], [1, 1]], [[2, 2], [3, 3]], ... и rects_b[[1000, 1000], [1001, 1001]], [[1002, 1002], [1003, 1003]], ... . Эти две группы находятся далеко друг от друга. Никакое сравнение между ними бесполезно. Для решения подобных задач можно использовать двоичный поиск и дерево сегментов.

user24714692 16.05.2024 19:17

Можете ли вы объяснить, как работает ваш алгоритм пересечения? Насколько я вижу, вы используете AABB для расчета пересечений, но здесь что-то не так.

Harsh 17.05.2024 11:54

Спасибо за все рекомендации! Я смог многому научиться. Мне удалось реализовать это в своей программе (см. GIF в отредактированном вопросе).

Harsh 17.05.2024 22:20

@Harsh Впечатляет! Кажется, ты очень талантлив и у тебя блестящее будущее. Не за что!

user24714692 17.05.2024 22:23
Эта задача интересна, если вы хотите решить и отправить свой код.
user24714692 17.05.2024 22:24

Я постараюсь. На первый взгляд это кажется очень уникальным вопросом. Вам придется (возможно) выборочно использовать разные типы точности, чтобы минимизировать ошибку. Но как на самом деле вопрос. (Я имею в виду метафорическое Ты здесь)

Harsh 17.05.2024 22:56

@Harsh Они ищут три типа точности. По ссылке есть zip-файл с данными, надо посмотреть и подумать.

user24714692 17.05.2024 22:59

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