Я пытаюсь создать собственный движок столкновений для академических целей, и я застрял в общей проблеме программирования на 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
Вы не можете уйти, не сохранив список всех возможных геометрий в немного место. В противном случае компилятор не будет знать, какие экземпляры шаблона генерировать. Но я придумал некоторый код, где вы должны указать этот список только в одном месте, 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;
}
Ваше второе редактирование указывает, что базовая процедура пересечения будет работать с использованием некоторых 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
, вам придется добавить
Geometry::intersects_impl(const A&)
делегирование по умолчаниюA::intersects
делегирование intersects_impl(const A&)
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
, Но если вы понимаете идею, лежащую в основе приведенного выше кода, то добавление этих расширений должно быть достаточно простым. Если нет, спросите.