шаблон, подобный посетителю, для объектов шаблона

Я пытаюсь создать собственный движок столкновений для академических целей, и я застрял в общей проблеме программирования на C ++
У меня уже есть все геометрии, которые работают должным образом, и для объема вопроса у меня есть эта функция:

template<typename lhs_geometry, typename rhs_geometry>
bool intersects( const lhs_geometry& lhs, const rhs_geometry& rhs )
{
//returns true if does objects intersects
//(assume this functions works perfectly with every geometry type)
}

У меня также есть следующий класс, который мне нужно закончить реализацию

template<typename geometry_type>
class collidable_object
{
public:
explicit collidable_object( geometry_type& geometry ) :
m_geometry( geometry )
{

}

~collidable_object()
{

}

private:
geometry_type& m_geometry;
};

Где моя проблема возникает, когда я хочу создать список collidable_object и проверить их на пересечение 2 на 2.

Я провел небольшое исследование в Google и обнаружил, что у него есть базовый класс для collidable_object позволит мне хранить объекты в списке. Но после этого, как я могу проверить объект в зависимости от его конкретной геометрии?

Я пытался реализовать шаблон посетителя, но каждый раз застреваю, потому что я не хочу жестко кодировать каждый возможный тип геометрии, так как я всегда буду просто вызывать intersetcs(),

Я также нашел статью о кооператив посетитель но это кажется способом сложным.

У кого-нибудь есть простое и эффективное решение?

РЕДАКТИРОВАТЬ: причина, по которой я хотел избежать списка геометрий, заключается в том, что я хочу, чтобы было достаточно легко добавлять новые геометрии без необходимости поиска файлов в древовидной структуре.

EDIT2: вот больше информации о intersetcs Метод: метод пересечений основан на диспетчеризации тегов, чтобы найти правильную геометрию, но почти все выпуклые формы используют алгоритм GJK, который требует только, чтобы объект мог вернуть точку, наиболее удаленную в заданном направлении. Для невыпуклых форм формы фрагментированы в выпуклые подформы, и процесс возобновляется.

нет единых критериев, чтобы увидеть, intersects сможет обрабатывать заданную форму чаще всего furthest_along но сфера на сфере нет и сфера агграгации также не требует использования furthest_along

Дополнительная информация: я использую VS2012 и C ++ 11

4

Решение

Вы не можете уйти, не сохранив список всех возможных геометрий в немного место. В противном случае компилятор не будет знать, какие экземпляры шаблона генерировать. Но я придумал некоторый код, где вы должны указать этот список только в одном месте, typedef of GeometryTypes, Все остальное строится на этом. Здесь я не использую шаблон vistor, который имеет то преимущество, что вам не нужно добавлять шаблонный код в различные реализации классов геометрии. Внедрение intersects для всех комбинаций достаточно.

Сначала некоторые включают: я буду использовать shared_ptr позже, и печать материала, и прерывание в случае неизвестных типов геометрии.

#include <memory>
#include <iostream>
#include <cstdlib>

Теперь определите некоторые геометрии с общим базовым классом, который вы можете использовать для полиморфных указателей. Вы должны включить хотя бы одну виртуальную функцию, чтобы получить таблицу виртуальных функций, которую можно использовать для dynamic_cast позже. Полиморфность деструктора гарантирует, что производные классы будут очищены должным образом, даже если они будут удалены с помощью полиморфного указателя.

struct Geometry {
virtual ~Geometry() { }
};
struct Circle : public Geometry { };
struct Rectangle : public Geometry { };

Теперь приходит твой intersects шаблон. Я пишу только одну реализацию для всего этого демо.

template<typename lhs_geometry, typename rhs_geometry>
bool intersects(const lhs_geometry& lhs, const rhs_geometry& rhs) {
std::cout << __PRETTY_FUNCTION__ << " called\n"; // gcc-specific?
return false;
}

Это место, где мы объявляем список всех геометрий. Если у вас есть геометрии, полученные друг от друга, сначала убедитесь, что у вас есть наиболее конкретные, поскольку они будут пробоваться для динамического приведения.

template<typename... Ts> class TypeList { };
typedef TypeList<Circle, Rectangle> GeometryTypes;

Теперь куча вспомогательного кода. Основная идея состоит в том, чтобы перебрать один такой TypeList и попробуйте динамическое приведение для каждого типа. Первый помощник выполняет итерацию для аргумента lhs, второй — для аргумента rhs. Если совпадение не найдено, у вас есть неполный список, что приведет к прерыванию работы приложения с, надеюсь, полезным сообщением об ошибке.

template<typename TL1, typename TL2> struct IntersectHelper1;
template<typename T1, typename TL2> struct IntersectHelper2;

template<typename TL2, typename T1, typename... Ts>
struct IntersectHelper1<TypeList<T1, Ts...>, TL2> {
static bool isects(Geometry* lhs, Geometry* rhs) {
T1* t1 = dynamic_cast<T1*>(lhs);
if (!t1)
return IntersectHelper1<TypeList<Ts...>, TL2>::isects(lhs, rhs);
else
return IntersectHelper2<T1, TL2>::isects(t1, rhs);
}
};

template<typename T1, typename T2, typename... Ts>
struct IntersectHelper2<T1, TypeList<T2, Ts...>> {
static bool isects(T1* lhs, Geometry* rhs) {
T2* t2 = dynamic_cast<T2*>(rhs);
if (!t2)
return IntersectHelper2<T1, TypeList<Ts...>>::isects(lhs, rhs);
else
return intersects(*lhs, *t2);
}
};

// Catch unknown types, where all dynamic casts failed:

bool unknownIntersects(Geometry* g) {
std::cerr << "Intersection with unknown type: "<< typeid(*g).name() << std::endl;
std::abort();
return false; // should be irrelevant due to abort
}

template<typename TL2>
struct IntersectHelper1<TypeList<>, TL2> {
static bool isects(Geometry* lhs, Geometry* rhs) {
return unknownIntersects(lhs);
}
};

template<typename T1>
struct IntersectHelper2<T1, TypeList<>> {
static bool isects(T1* lhs, Geometry* rhs) {
return unknownIntersects(rhs);
}
};

Теперь, когда все эти помощники на месте, вы можете провести тест на полиморфное пересечение. Я представляю shared_ptr хранить такие полиморфные указатели, и я предлагаю вам сделать то же самое в вашем collidable_object учебный класс. В противном случае вы должны были бы взять на себя ответственность за обеспечение того, чтобы указанные геометрии оставались в живых до тех пор, пока жив объект, подверженный столкновению, но в конечном итоге будет очищен. Вы хотите такую ​​ответственность?

typedef std::shared_ptr<Geometry> GeomPtr;

bool intersects(GeomPtr lhs, GeomPtr rhs) {
return IntersectHelper1<GeometryTypes, GeometryTypes>::
isects(lhs.get(), rhs.get());
}

И, наконец, несколько основных, так что вы можете запустить весь приведенный выше код в крошечном примере.

int main() {
GeomPtr g1(new Rectangle), g2(new Circle);
std::cout << intersects(g1, g2) << std::endl;
return 0;
}
2

Другие решения

Ваше второе редактирование указывает, что базовая процедура пересечения будет работать с использованием некоторых furthest_along код. Вы можете использовать это таким образом, чтобы обычные проверки пересечения работали на общем базовом классе, который включает furthest_along в его интерфейсе. Вам понадобятся специальные функции только для особых случаев, для которых вам нужны другие алгоритмы.

В следующем примере избегаются все динамические приведения и вместо этого выполняется два вызова виртуальных методов (также известный как «двойная отправка», Который, кстати, также доступен как тег, так что добавление этого к вашему вопросу может быть полезным).

struct Geometry {
virtual ~Geometry() { }
virtual Point furthest_along(Vector& v) const = 0;
virtual bool intersects(const Geometry& other) const {
return other.intersects_impl(*this);
}
virtual bool intersects_impl(const Geometry& other) const { // default impl
// compute intersection using furthest_along
}
virtual bool intersects_impl(const Circle& other) const {
return intersects_impl(static_cast<const Geometry&>(other)); // use default
}
};

struct Circle : public Geometry {
bool intersects(const Geometry& other) const {
return other.intersects_impl(*this); // call intersects_impl(const Circle&)
}
bool intersects_impl(const Circle& other) const {
// do circle-circle intersection
}
Point furthest_along(Vector& v) const {
// implement for default intersection
}
};
struct Rectangle : public Geometry {
Point furthest_along(Vector& v) const {
// implement for default intersection
}
};

Если вы позвоните a.intersects(b)тогда intersects метод будет выбран из таблицы виртуальных функций aтогда как intersects_impl метод будет выбран из b, Если вы хотите добавить специальный случай для комбинации типов A а также B, вам придется добавить

  1. виртуальный метод Geometry::intersects_impl(const A&)делегирование по умолчанию
  2. метод переопределения A::intersects делегирование intersects_impl(const A&)
  3. метод переопределения B::intersects_impl(const A&) с фактическим пользовательским кодом

Если вам нужно добавить много типов со многими специальными алгоритмами, это может привести к довольно большому количеству модификаций в разных местах. Однако, если большинство добавляемых вами фигур будет использовать реализацию по умолчанию, все, что вам нужно сделать, это правильно реализовать furthest_along для каждого из них.

Конечно, вы можете делать более умные вещи, чем это. Вы можете создать промежуточный класс ConvexGeometry который использует furthest_along подход и класс NonConvexGeometry который обеспечил бы некоторые средства для разделения на выпуклые части. Вы могли бы реализовать intersects в обоих из них и сделать реализацию в Geometry чисто абстрактно (= 0). Вы могли бы тогда избежать intersects_impl(const Geometry&) и вместо этого использовать intersects_impl(const ConvexGeometry&) а также intersects_impl(const NonConvexGeometry&) в качестве механизмов по умолчанию, оба из которых могут быть = 0 в Geometry и реализовано надлежащим образом в ConvexGeometry а также NonConvexGeometry, Но если вы понимаете идею, лежащую в основе приведенного выше кода, то добавление этих расширений должно быть достаточно простым. Если нет, спросите.

0

По вопросам рекламы [email protected]