В настоящее время я работаю над хобби-проектом, в котором у меня есть несколько тысяч звезд в вымышленной вселенной 2D. Мне нужно визуализировать эти звезды на экране, но, очевидно, я не хочу работать со всеми из них — только с теми, которые видны в любой момент времени.
Для подтверждения концепции я написал алгоритм грубой силы, который бы смотрел на каждую звезду и проверял ее координаты на границах экрана игрока:
for (const std::shared_ptr<Star>& star : stars_) {
if (moved_)
star->MoveStar(starfield_offset_, level_);
position = star->position();
if (position.x >= bounds_[0] &&
position.x <= bounds_[1] &&
position.y >= bounds_[2] &&
position.y <= bounds_[3])
target.draw(*star);
}
Хотя этот неуклюжий метод действительно рисует только видимые звезды на экране, он явно работает за линейное время. Поскольку звезды являются лишь частью фона и, честно говоря, процессору не приходится тратить время на фильтрацию, я бы хотел разработать более быстрый алгоритм для уменьшения некоторой нагрузки.
Итак, мой нынешний ход мыслей заключается в использовании бинарного поиска для поиска соответствующих звезд. Для этого мне явно нужно отсортировать данные. Тем не менее, я не был уверен, как можно сортировать данные координат — я не мог придумать абсолютного порядка, который позволил бы мне правильно отсортировать данные в порядке возрастания (в отношении координат x и y). ,
Итак, я реализовал два новых контейнера — один для данных, отсортированных по координате х, а другой по координате у. Моя первоначальная мысль состояла в том, чтобы взять пересечение этих двух отсортированных наборов и нарисовать получившиеся звезды на экране (звезды, координаты x и y которых лежат в границах экрана):
struct SortedStars {
std::vector<std::shared_ptr<Star>>::iterator begin, end;
std::vector<std::shared_ptr<Star>> stars;
} stars_x_, stars_y_;
Затем я отсортировал эти контейнеры:
// comparison objects
static struct SortX {
bool operator() (const std::shared_ptr<Star>& first, const std::shared_ptr<Star>& second)
{ return (first->position().x < second->position().x); }
bool operator() (const std::shared_ptr<Star>& first, const float val)
{ return (first->position().x < val); }
bool operator() (const float val, const std::shared_ptr<Star>& second)
{ return (val < second->position().x); }
} sort_x;
static struct SortY {
bool operator() (const std::shared_ptr<Star>& first, const std::shared_ptr<Star>& second)
{ return (first->position().y < second->position().y); }
bool operator() (const std::shared_ptr<Star>& first, const float val)
{ return (first->position().y < val); }
bool operator() (const float val, const std::shared_ptr<Star>& second)
{ return (val < second->position().y); }
} sort_y;
void Starfield::Sort() {
// clone original data (shared pointers)
stars_x_.stars = stars_;
stars_y_.stars = stars_;
// sort as needed
std::sort(stars_x_.stars.begin(), stars_x_.stars.end(), sort_x);
std::sort(stars_y_.stars.begin(), stars_y_.stars.end(), sort_y);
// set iterators to the outermost visible stars (defined by screen bounds)
// these are updated every time the screen is moved
stars_x_.begin = std::lower_bound(stars_x_.stars.begin(), stars_x_.stars.end(), bounds_[0], sort_x);
stars_x_.end = std::upper_bound(stars_x_.stars.begin(), stars_x_.stars.end(), bounds_[1], sort_x);
stars_y_.begin = std::lower_bound(stars_y_.stars.begin(), stars_y_.stars.end(), bounds_[2], sort_y);
stars_y_.end = std::upper_bound(stars_y_.stars.begin(), stars_y_.stars.end(), bounds_[3], sort_y);
return;
}
К сожалению, я не могу придумать подходящую функцию сравнения для std :: set_intersection или метод, с помощью которого я мог бы вручную сравнивать координаты, используя мои итераторы.
Не могли бы вы, ребята, указать мне правильное направление? Отзывы о моей методологии или реализации очень приветствуются.
Спасибо за ваше время!
Существуют различные структуры данных о пространственном ускорении, которые помогают ответить на вопросы о том, «какие точки находятся в этом регионе». Quadtrees являются популярным решением для 2D, но могут быть излишними для вашей проблемы. Вероятно, самый простой подход состоит в том, чтобы иметь двумерную сетку с точками (звездами), сгруппированными по квадрату сетки, в который они попадают. Затем вы проверяете, какие квадраты сетки перекрывают ваше окно просмотра, и вам нужно только посмотреть на звезды в корзинах для этих квадратов. Если вы сделаете квадраты сетки немного больше, чем размер окна просмотра, вам нужно будет проверить не более четырех сегментов.
Если вы можете увеличивать и уменьшать масштаб, может подойти более сложная структура, такая как Quadtree.
Я использую настоящие звездные данные для рендеринга (психосоматический стиль) в течение многих лет, и у меня нет проблем со скоростью без упорядочивания / выбора видимости в OpenGL (VBO)
Я обычно использовал звездный каталог BSC в прошлом
Несколько лет назад я перевел свои двигатели в каталог hipparcos
Так что, если вы не используете слишком много звезд, нужно будет просто отрендерить их все
— Сколько звезд вы делаете?
— Как вы звезды отображаются? (Я использую смешанный Quad на звезду)
Какая платформа / настройка …
— это работало хорошо даже на моей старой установке GeForce 4000 Ti, 1,3 ГГц одноядерный AMD
— также в стерео 3D
какой ваш желаемый FPS? … Я в порядке с 30fps для моих симуляций
Если у вас схожие значения и низкая скорость может быть что-то не так с вашим кодом рендеринга (не с количеством данных) …
PS.
если у вас есть большое пространство для покрытия, вы можете выбрать яркие звезды только для просмотра
также вы используете слишком много ifs для выбора звезды
Также я вижу, вы звоните в розыгрыш каждой звезды
Вы можете попробовать пространственное R-дерево, которое теперь является частью Библиотека Boost Geometry.
Приложение может работать следующим образом:
Вы добавляете координату вашей звезды к дереву в некоторой «абсолютной» системе координат. Если ваши звезды имеют разные размеры, вы, вероятно, захотите добавить не точку, а ограничивающий прямоугольник каждой звезды.
#include <boost/geometry/index/rtree.hpp>
#include <boost/geometry/geometries/box.hpp>
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
typedef bg::model::point<float, 2, bg::cs::cartesian> point;
typedef bg::model::box<point> box;
typedef std::pair<box, Star*> value; //here "second" can optionally give the star index in star's storage
bgi::rtree<value> rtree;
Когда вы строите свою вселенную, вы заполняете дерево:
for (auto star: stars)
{
box b(star->position(), star->position()));
bg::expand(b, point(star->radius(), star->radius());
// insert new value
rtree.insert(std::make_pair(b, star));
}
Когда вам нужно их визуализировать, вы вычисляете свое экранное окно в «абсолютной» системе координат и запрашиваете дерево о звездах, которые перекрывают ваше окно:
box query_box(point(0, 0), point(5, 5));
std::vector<value> result_s;
rtree.query(bgi::intersects(query_box), std::back_inserter(result_s));
Здесь result_s перечислит соответствующие звезды и их ограничивающие рамки.
Удачи!