2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




 
 нахождение плиток(тайлов) в области просмотра.
Сообщение26.06.2025, 11:02 
Сразу скажу задача мной решена, но хочется найти более элегантное и/или быстрое(оптимальное с точки зрения вычислений) решение.
Ниже скрин, по сути имея прямоугольник зеленый, нужно найти квадраты на рисунке серые.
Изображение
Дано:
Сетка квадратов, с стороной квадрата side_square
Квадраты пронумерованы от нуля, по рядам(горизонтально) и столбцам(вертикально).
Внутри сетки прямоугольник, с сторонами width_rectangle и height_rectangle
Координаты центра прямоугольника center_rectangle_x и center_rectangle_y
Угол поворота прямоугольника относительно центра a_rectangle
Найти:
Все квадраты(их номера по горизонтали и вертикали) которые частично или полностью внутри прямоугольника.

 
 
 
 Re: нахождение плиток(тайлов) в области просмотра.
Сообщение26.06.2025, 12:09 
Аватара пользователя
В каком виде нужен ответ? Список клеток, список столбцов с верхней и нижней клеткой, что-то еще?
Для каждой стороны находим список клеток, через которые она проходит. Сортируем эти 4 списка в один (можно в принципе строить их сразу отсортированными). Теперь для каждой абсциссы находим минимальную и максимальную ординату, и перечисляем клетки между ними.

 
 
 
 Re: нахождение плиток(тайлов) в области просмотра.
Сообщение26.06.2025, 12:27 
rx200
Можно проверить попадание в зеленый квадрат перекрестий. Если перекрестие попадает - записываем все четыре окружающих ее квадрата в наш список попадающих. И отдельно нужно рассмотреть четыре точки углов зеленого квадрата.

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

 
 
 
 Re: нахождение плиток(тайлов) в области просмотра.
Сообщение26.06.2025, 12:42 
Еще метод:

дискретизуем ваш прямоугольник на сетке, примерно в 2 раза меньшей единице. Для каждой дискретной точки вычисляем к какой квадрат она попала, строим список и проверяем есть ли в этом списке уже этот квадрат (вычислительная сложность каждого шага $O(1)$). Далее любой на выбор из следующих способов:
1. по построенному списку ищем те квадраты, у которых есть не помеченные соседи и проверяем этих соседей на предмет пересечения с границами;
2. проходим каждую границу Брезенхеймом и добавляем из полученных клеток те, что еще не в списке.

Имхо второй способ будет быстрее, при больших прямоугольниках, а первый - при маленьких.

 
 
 
 Re: нахождение плиток(тайлов) в области просмотра.
Сообщение26.06.2025, 13:03 
mihaild в сообщении #1692333 писал(а):
Для каждой стороны находим список клеток, через которые она проходит. Сортируем эти 4 списка в один (можно в принципе строить их сразу отсортированными).

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

 
 
 
 Re: нахождение плиток(тайлов) в области просмотра.
Сообщение26.06.2025, 13:04 
Аватара пользователя
Можно заметить, что крайние квадраты в каждой строке и столбце задаются пересечением стороны зелёного прямоугольника с квадратной сеткой, каждый номер минимум или максимум из двух пересечений. А номера самых крайних столбцов и сток задаются положением вершин зелёного прямоугольника. По этим вершинам можно определить границы, содержащие затемнённую фигуру, выбрать в ней кратчайшую сторону, и уже по этому направлению (вертикальному или горизонтальному) пройтись по всем пересечениям. Это не уменьшит степень трудоёмкости, но уменьшит константу перед этой степенью, потому что число сравнений будет минимальным, если выбирать пачки квадратов в самых высоких/широких столбцах/строках. А дальше действовать, как сказал mihaild, добавляя результат в сортированных список (или другую структуру).

 
 
 
 Re: нахождение плиток(тайлов) в области просмотра.
Сообщение26.06.2025, 13:58 
Спасибо mihaild, sergey zhukov, zgemm, B@R5uk и 12d3 мне кажется ваши решения более затратны по вычислениям.
12d3 да тангенс угла я тоже к этому пришел.

Опишу в общем что делаю.
Делаю картографическую библиотеку, подобные используются в гугл, яндекс, ОСМ карт. Для лучшей гибкости и контроля.
Задача которую щас решаю это вращение карты, тоесть когда север не сверху карты.
Экран это по сути прямоугольник, а карта состоит из плиток(квадратных картинок).
Таким образом задача находить плитки которые внутри области экрана прямоугольника. так как загрузка плитки наиболее затратна по времени, лучше определить точно те плитки которые требуется загрузить.
Сделал для тестов выше на скрине, где зеленый прямоугольник имитирует экран, красная точка верхний левый угол экрана, справа угол поворота экрана, квадратики это имитация плиток, которые как и плитки карты пронумерованы по рядам(горизонтально), и столбцам вертикально. закрашенные это те которые нужно загружать.

При решении задачи пошел достаточно примитивным путем.
Далее относительно координат плиток.
1. определяю верхний угол прямоугольника(он же экран). Это чтобы потом в циклах перебирать плитки с верхнего ряда вниз, с лева направо.
2. определяю высоту прямоугольника(сумма на син, кос, сторон прямоугольника)
3. определяю правую и левую сторону прямоугольника(при вращении они могут менятся).
4. определяю правое и левое расстояние прямоугольника по оси x относительно верхнего угла прямоугольника(минимум и максимум по x).
5. определяю смещение верхнего угла относительно плитки.
6. определяю смещение в верхнем ряде(ряд верхнего угла).
7. определяю смещение на один ряд, для каждой стороны(смещение между углами).
8. определяю смещение для правого и левого угла прямоугольника.
9. проходим каждый ряд, с лева на право по определенным смещениям(минимум максимум плитка для каждого ряда сверху вниз).
10. при привышении левой или правой границы меняю направление смещения и само смещение.
11. в итоге заполняю массив с номерами(рядов и столбцов) плиток, для загрузки. в тестовом примере они окрашены в серый цвет.

Мне кажется должен быть более элегантный и/или быстрый способ решения данной задачи.
При скорости стоит учитывать что всякие син, кос, более затратны, умножений делений, которве более затратны сложений вычитаний, которые более затратны бинарных операций.
Понимаю решений много данной задачи, но хотелось бы найти самое быстрое и/или самое элегантное решение. В этом и вопрос.
За ваши решения всеравно спасибо рассмотрю их более подробно. Но по вычислениям на вскидку они мне кажутся более затратными пока.
Моё решение именно в коде JavaScript с комментариями попробую отправить в отдельном сообщении, нето как непробовал тут отправлять сообщение с кодом не отправлялось, как увидите оно достаточно громоздкое в коде.

-- 26.06.2025, 13:59 --

Код:
function getTileArr(){
            let arr_tile = [];// возвращаемый функцией массив номеров(рядов и столбцов) плиток// надо заполнить.
            /*
            для работы функции используются внешние данные.
            a_view = 0;// угол поворота дисплея(Дисплей это прямоугольник).
            win_out_w = 300;// ширина дисплея.
            win_out_h = 200;// высота дисплея.
            center_x = win_out_x + win_out_w / 2 | 0;// центер дисплея относительно которого вращается
            center_y = win_out_y + win_out_h / 2 | 0;// центер дисплея относительно которого вращается
            point_radius = (win_out_w ** 2 + win_out_h ** 2) ** .5 / 2;// радиус описывающей окружности, расстояние от центра до углов прямоугольника
            point_a = hf_pi - Math.atan(win_out_h / win_out_w);// угол диагонали дисплея
            tile_size = 32;// размер высоты и ширины плитки
            hf_pi = Math.PI / 2;// half PI // половина Пи // угол 90 градусов в радианах
            */

            let size_left_border, size_right_border;// относительно координат плиток, размер левой и правой стороны дисплея
            let a_relative;// угол поворота экрана, относительно верхнего угла дисплея, относительно координат плиток

            // определяем верхний угол.
            //*
            let x, y;// координаты верхнего угла дисплея, относительно координат плиток
            if(a_view >= 0){// определяем соответствующие значения относительно верхнего угла дисплея
               if(a_view <= hf_pi){
                  a_relative = a_view;
                  size_left_border = win_out_h;
                  size_right_border = win_out_w;
                  x = center_x + point_radius * Math.sin(a_view - point_a);
                  y = center_y - point_radius * Math.cos(a_view - point_a);
               }else{
                  a_relative = a_view - hf_pi;
                  size_left_border = win_out_w;
                  size_right_border = win_out_h;
                  x = center_x - point_radius * Math.sin(a_view + point_a);
                  y = center_y + point_radius * Math.cos(a_view + point_a);
               }
            }else{
               if(a_view >= -hf_pi){
                  a_relative = a_view + hf_pi;
                  size_left_border = win_out_w;
                  size_right_border = win_out_h;
                  x = center_x + point_radius * Math.sin(a_view + point_a);
                  y = center_y - point_radius * Math.cos(a_view + point_a);
               }else{
                  a_relative = a_view + Math.PI;
                  size_left_border = win_out_h;
                  size_right_border = win_out_w;
                  x = center_x - point_radius * Math.sin(a_view - point_a);
                  y = center_y + point_radius * Math.cos(a_view - point_a);
               }
            }



            //*/
            // определяем left right в ряде
            let sin_a = Math.sin(a_relative);
            let cos_a = Math.cos(a_relative);
            let tan_a = sin_a / cos_a;

            // относительно координат плиток
            let shift_y_tile = tile_size - y % tile_size;// смещение верхнего угла дисплея относительно плитки по оси y
            let shift_x_tile = x % tile_size;// смещение верхнего угла дисплея относительно плитки по оси x

            let max_left_shift_w = size_left_border * sin_a;// размер левой стороны дисплея по оси x
            let max_left_shift_h = size_left_border * cos_a;// размер левой стороны дисплея по оси y
            let max_right_shift_w = size_right_border * cos_a;// размер правой стороны дисплея по оси x
            let max_right_shift_h = size_right_border * sin_a;// размер правой стороны дисплея по оси y

            let max_h = max_left_shift_h + max_right_shift_h;// высота дисплея по оси y
            let tile_y = y / tile_size | 0;// ряд верхней плитки дисплея.
            let tile_bottom = Math.ceil((max_h - shift_y_tile) / tile_size) + tile_y;// ряд нижней плитки дисплея.

            //let tile_x = x / tile_size | 0;
            let x_min = x - max_left_shift_w;// минимальная координата дисплея по оси x
            let x_max = x + max_right_shift_w;// максимальная координата дисплея по оси x
            let l = tile_size * tan_a;// смещение по оси x на один ряд с левой стороны
            let l_1 = shift_y_tile * tan_a;// смещение по оси x на один ряд, относительно верхнего угла дисплея с левой стороны
            let r_2 = tan_a === 0 ? Infinity: (tile_size - (y + max_left_shift_h) % tile_size) / tan_a;// смещение по оси x на один ряд, относительно левого угла дисплея с левой стороны в право

            let r = tan_a === 0 ? Infinity: tile_size / tan_a;// смещение по оси x на один ряд с правой стороны
            let r_1 = tan_a === 0 ? Infinity: shift_y_tile / tan_a;// смещение по оси x на один ряд, относительно верхнего угла дисплея с правой стороны
            let l_2 = (tile_size - (y + max_right_shift_h) % tile_size) * tan_a;// смещение по оси x на один ряд, относительно правого угла дисплея с правой стороны в лево

            let l_pos = x - l_1;// левая позиция в ряде.
            let r_pos = x + r_1;// правая позиция в ряде.


            let l_shift = -l;// смещение левой позиции в ряде, относительно позиции в предыдущем ряде.
            let r_shift = r;// смещение правой позиции в ряде, относительно позиции в предыдущем ряде.
            let flag_l = false;// флаг достижения левого края дисплея и смены направления
            let flag_r = false;// флаг достижения правого края дисплея и смены направления
            if(l_pos < x_min){// если достигнут левый край экрана
               l_pos = x_min;
               l_shift = r_2;
               flag_l = true;
            }
            if(r_pos > x_max){// если достигнут правый край экрана
               r_pos = x_max;
               r_shift = -l_2;
               flag_r = true;
            }

            if(l_pos < x_min)l_pos = x_min;
            if(r_pos > x_max)r_pos = x_max;
            // определяем плиток с лева и права.
            let left_tile_xx = l_pos / tile_size | 0;// номер левой плитки
            let right_tiles_xx = r_pos / tile_size | 0;// номер правой плитки

            for(let t_x = left_tile_xx; t_x <= right_tiles_xx; t_x++)arr_tile.push([t_x, tile_y]);

            for(let t_y = tile_y + 1; t_y <= tile_bottom; t_y++){

               l_pos += l_shift;
               r_pos += r_shift;

               if(flag_l){
                  l_shift = r;
                  flag_l = false;
               }
               if(flag_r){
                  r_shift = -l;
                  flag_r = false;
               }

               if(l_pos < x_min){// если достигнут левый край экрана
                  l_pos = x_min;
                  l_shift = r_2;
                  flag_l = true;
               }
               if(r_pos > x_max){// если достигнут правый край экрана
                  r_pos = x_max;
                  r_shift = -l_2;
                  flag_r = true;
               }

               // определяем плиток с лева и права.

               let left_tile = l_pos / tile_size | 0;// номер левой плитки
               let right_tiles = r_pos / tile_size | 0;// номер правой плитки

               for(let t_x = left_tile; t_x <= right_tiles; t_x++)arr_tile.push([t_x, t_y]);

            }

            tileColor(arr_tile);// return arr_tile;
         }

 
 
 [ Сообщений: 7 ] 


Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group