У меня есть 2D-плоскость (с дискретными точками), содержащая прямоугольники произвольного размера, и все прямоугольники выровнены по осям. У меня есть их координаты (вверху слева) и размеры (длина и ширина).
Предположим, я выбираю прямоугольник в этой плоскости и хочу найти ближайшее к этому прямоугольнику ребро (которое также пересекается хотя бы с одной из проекций соседней стороны прямоугольника). Вот пример:
Моим первым решением было проверить каждую сторону каждого прямоугольника одну за другой и вычислить расстояние до каждой стороны данного прямоугольника. Но это кажется очень затратным в вычислительном отношении (особенно если учесть это в графическом контексте, то есть в каждом кадре ввода).
Во-вторых, нужно было проверить, не сталкивается ли этот прямоугольник с другим (с AABB), если нет, то по относительному положению их верхней левой координаты я могу определить, какое ребро будет ближайшим к этой паре.
Может ли кто-нибудь предложить лучшее решение этой проблемы?
Примечание 1. Вы можете предположить, что никакие другие прямоугольники, кроме данного прямоугольника, не могут пересекаться (с любым другим).
Обновлено: Спасибо @user24714692 и @Clement44 за ответы. Я смог многому научиться из этой проблемы. И я благодарен вам за то, что мне удалось реализовать эту функцию привязки в своем проекте:





Я бы начал с поиска сталкивающихся прямоугольников, а затем нашел ближайший. Вот алгоритм, который должен работать:
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
Ну, я не совсем понял, какое расстояние вы хотели вычислить. Это горизонтальная (соответственно вертикальная) проекция кратчайшего отрезка между двумя горизонтально (соответственно вертикально) сталкивающимися прямоугольниками? В любом случае, на этом этапе алгоритма вычисляйте его так, как хотите!
Кажется, задач много.
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 Это правильно. Технически мы «заменим» вашу первоначальную проблему на новую. Новая задача также имеет две подзадачи (для осей Y и X).
но не придется ли мне все равно перебирать весь массив, чтобы вычислить расстояния? Какой тогда смысл сортировки? Я думаю, что мне все еще чего-то не хватает.
Ой, я не заметил, что под сортировкой вы имели в виду сортировку во время расчета пересечения. Теперь все это имеет смысл (до сих пор я до сих пор не понимаю, как связаны деревья сегментов). Пора это реализовать!
Предположим, у вас есть две группы прямоугольников по 10^4 элементов в каждой группе (нижняя левая и верхняя правая точки): rects_a:[[0, 0], [1, 1]], [[2, 2], [3, 3]], ... и rects_b[[1000, 1000], [1001, 1001]], [[1002, 1002], [1003, 1003]], ... . Эти две группы находятся далеко друг от друга. Никакое сравнение между ними бесполезно. Для решения подобных задач можно использовать двоичный поиск и дерево сегментов.
Можете ли вы объяснить, как работает ваш алгоритм пересечения? Насколько я вижу, вы используете AABB для расчета пересечений, но здесь что-то не так.
Спасибо за все рекомендации! Я смог многому научиться. Мне удалось реализовать это в своей программе (см. GIF в отредактированном вопросе).
@Harsh Впечатляет! Кажется, ты очень талантлив и у тебя блестящее будущее. Не за что!
Я постараюсь. На первый взгляд это кажется очень уникальным вопросом. Вам придется (возможно) выборочно использовать разные типы точности, чтобы минимизировать ошибку. Но как на самом деле вопрос. (Я имею в виду метафорическое Ты здесь)
@Harsh Они ищут три типа точности. По ссылке есть zip-файл с данными, надо посмотреть и подумать.
Спасибо за ответ! Под расстоянием между прямоугольниками вы подразумеваете расстояние между их ближайшим краем, верно?