In this example, I wrote two methods for allowing rtree.query
to work:
indexable
for the Entity<float>
Which one would be better in this case?
#include <SFML/Graphics.hpp>
#include <iostream>
#include <sstream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <random>
#define METHOD_WRAPPER 0
#define SPECIALIZATION 1
#define SOLUTION METHOD_WRAPPER
using point = boost::geometry::model::d2::point_xy<float>;
template<typename T, typename IndexableGetter=boost::geometry::index::indexable<T>>
class Collection {
public:
using box = boost::geometry::model::box<point>;
void insert(T const& t) { rtree.insert(t); }
auto begin() const { return rtree.begin(); }
auto end() const { return rtree.end(); }
auto find(auto const& search, float radius) {
std::vector<std::reference_wrapper<T const>> result;
rtree.query(boost::geometry::index::satisfies([&](auto const& el) {
return boost::geometry::distance(el.position, search) < radius ;
}), std::back_inserter(result));
return result;
}
private:
boost::geometry::index::rtree<T, boost::geometry::index::quadratic<32>, IndexableGetter> rtree;
};
template<typename T>
struct Entity {
using result_type = point;
point position;
Entity(T x, T y) : position(x, y) {}
#if SOLUTION == METHOD_WRAPPER
struct ByPosition {
using result_type = point;
result_type const& operator()(Entity const& boid) const { return boid.position; }
};
#endif
operator sf::Vector2f() const { return sf::Vector2f(position.x(), position.y()); }
};
#if SOLUTION == SPECIALIZATION
namespace boost { namespace geometry { namespace index {
template<>
struct indexable<Entity<float>> {
using result_type = point;
result_type operator()(Entity<float> const& e) const {
return result_type(e.position);
}
};
}}}
#endif
int main() {
using Entity = Entity<float>;
#if SOLUTION == METHOD_WRAPPER
Collection<Entity, Entity::ByPosition> collection;
#elif SOLUTION == SPECIALIZATION
Collection<Entity> collection;
#endif
for (int i = 0; i < 10; i++)
collection.insert(Entity(
static_cast<float>(rand() % 100),
static_cast<float>(rand() % 100))
);
for (auto& el : collection)
std::cout << el.position.x() << ", " << el.position.y() << std::endl;
auto result = collection.find(point(50, 50), 30);
for (auto& el : result)
std::cout << el.get().position.x() << ", " << el.get().position.y() << std::endl;
}
In your case they are (obviously?) exactly equivalent. The indexable
trait merely makes the indexable-getter the default for the element type.
You can make that more obvious by removing the duplication:
template <typename T> struct bgi::indexable<Entity<T>> : Entity<T>::ByPosition {};
This improves on your specialization by being generic for all T
instead of hardcoding T = float
. Using a simplified version of your code to demonstrate side-by-side:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <iostream>
#include <random>
namespace bg = boost::geometry;
namespace bgi = bg::index;
using point = bg::model::d2::point_xy<float>;
template <typename T, typename IndexableGetter = bgi::indexable<T>> class Collection {
public:
using value_type = T;
void insert(T const& t) { rtree.insert(t); }
auto begin() const { return rtree.begin(); }
auto end() const { return rtree.end(); }
auto find(auto const& search, float radius) {
std::vector<std::reference_wrapper<T const>> result;
rtree.query(
bgi::satisfies([&](auto const& el) { return bg::distance(el.position, search) < radius; }),
back_inserter(result));
return result;
}
private:
// using box = bg::model::box<point>;
bgi::rtree<T, bgi::quadratic<32>, IndexableGetter> rtree;
};
template <typename T> struct Entity {
point position;
Entity(T x, T y) : position(x, y) {}
struct ByPosition {
using result_type = point;
constexpr result_type const& operator()(Entity const& boid) const { return boid.position; }
};
// operator sf::Vector2f() const { return sf::Vector2f(position.x(), position.y()); }
};
template <typename T> struct bgi::indexable<Entity<T>> : Entity<T>::ByPosition {};
template <typename Coll> void foo(int seed) {
std::cout << "\n" << __PRETTY_FUNCTION__ << "\n";
using Entity = Coll::value_type;
auto dist = bind(std::uniform_int_distribution(0, 99), std::mt19937(seed));
auto gen = [&] { return Entity(dist(), dist()); };
auto dump = [](auto caption, auto const& ee) {
std::cout << " -- " << caption << ":\t";
for (auto sep = " "; Entity const& el : ee)
std::cout << std::exchange(sep, ", ") << bg::dsv(el.position);
std::cout << "\n";
};
Coll coll;
for (int i = 0; i < 5; i++)
coll.insert(gen());
dump("coll", coll);
dump("result", coll.find(point(50, 50), 30));
for (int i = 0; i < 5; i++)
coll.insert(gen());
dump("coll", coll);
}
int main() {
using Entity = Entity<float>;
foo<Collection<Entity, Entity::ByPosition>>(42);
foo<Collection<Entity>>(42);
}
Printing e.g.
void foo(int) [with Coll = Collection<Entity<float>, Entity<float>::ByPosition>]
-- coll: (79, 37), (18, 95), (77, 73), (59, 59), (44, 15)
-- result: (59, 59)
-- coll: (79, 37), (18, 95), (77, 73), (59, 59), (44, 15), (9, 15), (45, 5), (33, 86), (14, 60), (65, 70)
void foo(int) [with Coll = Collection<Entity<float> >]
-- coll: (79, 37), (18, 95), (77, 73), (59, 59), (44, 15)
-- result: (59, 59)
-- coll: (79, 37), (18, 95), (77, 73), (59, 59), (44, 15), (9, 15), (45, 5), (33, 86), (14, 60), (65, 70)
The trait makes it easier to default, so if you have generic code that should be able to figure out a suitable indexablegetter, that's your ticket. You might also want to be able to reuse the getter for any entity template instantiation: Live On Coliru:
struct GetPosition {
using result_type = point;
template <typename T> point const& operator()(Entity<T> const& e) const { return e.position; }
template <typename T> point const& operator()(Resource<T> const& r) const { return r.position; }
};
template <typename T> struct bgi::indexable<Entity<T>> : GetPosition {};
That said, if you want to express the intent that the user of Collection
needs to choose between different getters available, it may be best to not default to a single getter. See also: The Pit Of Success design guide.