I'm currently working on a hobby project in which I have several thousand stars in a 2D fictional universe. I need to render these stars to the screen, but clearly I don't want to have to operate on all of them -- only the ones that are visible at any given time.
For proof of concept, I wrote a brute force algorithm that would look at every star and test its coordinates against the bounds of the player's screen:
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);
}
While this clunky method does, indeed, draw only the visible stars to the screen, it clearly operates in linear time. Since stars are only part of the background and, frankly, aren't the most important thing for the processor to be spending time filtering through, I'd like to devise a faster algorithm to reduce some of the load.
So, my current train of thought is along the lines of using binary search to find the relevant stars. For this, I would clearly need to sort my data. However, I wasn't really sure how I could go about sorting my coordinate data -- I couldn't think of any absolute ordering that would allow me to properly sort my data in ascending order (with regards to both x and y coordinates).
So, I implemented two new containers -- one for the data sorted by x coordinate, and the other by y coordinate. My original thought was to take the intersection of these two sorted sets and draw the resulting stars to screen (stars whose x and y coordinates lie within the screen bounds):
struct SortedStars {
std::vector<std::shared_ptr<Star>>::iterator begin, end;
std::vector<std::shared_ptr<Star>> stars;
} stars_x_, stars_y_;
I then sorted these containers:
// 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;
}
Unfortunately, I cannot seem to either come up with an appropriate comparison function for std::set_intersection or a method through which I could manually compare coordinates using my iterators.
Could you guys point me in the right direction? Feedback on my methodology or implementation is very welcome.
Thanks for your time!