我有各种类型的对撞机类(SphereCollider,AABBCollider等),都是从基类Collider继承而来的。我使用指向std :: vector中基类的指针来存储它们。
std::vector<std::shared_ptr<Collider>> colliders;
然后我有各种各样的功能来确定对撞机是否相互交叉。
bool Colliding(const SphereCollider& colliderOne, const SphereCollider& colliderTwo);
bool Colliding(const AABBCollider& colliderOne, const AABBCollider& colliderTwo);
bool Colliding(const AABBCollider& colliderOne, const SphereCollider& colliderTwo);
bool Colliding(const SphereCollider& colliderOne, const AABBCollider& colliderTwo);
我的想法是简单地遍历对撞机矢量,然后将每个碰撞器与列表中的所有其他碰撞器进行比较。
for (unsigned int a = 0; a < colliders.size(); ++a)
{
for (unsigned int b = a + 1; b < colliders.size(); ++b)
{
// Store the two colliders we're current comparing
std::shared_ptr<Collider> colliderOne = colliders[a];
std::shared_ptr<Collider> colliderTwo = colliders[b];
// Somehow cast pointers back to correct derived type
// Check colliding
if(Colliding(*colliderOneDerived, *colliderTwoDerived))
{
// Colliding
}
}
}
但要成功完成此操作,我需要将指针转换回正确的派生类型,以便调用正确的函数,但我不知道每次碰撞器的类型。
基础碰撞器类有一个Type()函数,它正确地从基指针返回派生类型。
std::type_index Collider::Type() { return typeid(*this); }
有没有办法可以使用它来将指针强制转换为正确的派生类型?
答案 0 :(得分:1)
您想要做的是称为双重调度,C ++本身不支持。您可以考虑以下几个选项:
你可以用虚函数来实现它,这意味着添加虚函数,将每种类型的对象带到每个对象。这将导致相对快速的查找,但实现它的努力呈二次方式增长。
class Obj1;
class Obj2;
class Base{
virtual bool collide(Base* p ) =0;
virtual bool collide(Obj1*) = 0;
virtual bool collide(Obj2*) = 0;
};
class Obj1{
virtual bool collide(Base* p) override { return p->collide(this); }
virtual bool collide(Obj1* p) override { return colliding(p,this); }
virtual bool collide(Obj2* p) override;
};
class Obj2{
virtual bool collide(Base* p) override { return p->collide(this); }
virtual bool collide(Obj1* p) override { return colliding(p,this); }
virtual bool collide(Obj2* p) override { return colliding(p,this); }
};
bool Obj1::collide(Obj2* p) override { return colliding(p,this); }
你也可以使用Visitor pattern获得同样的效果。
您可以向每个返回对象typeid
的对象添加一个虚函数,并生成由一对std::type_index
索引的函数指针的已排序平面映射。这将允许更大的灵活性,因为您可以在项目的多个位置向地图添加对象。
您可以为每个对象添加一个唯一的constexpr索引,每个对象都有一个虚函数返回该索引,并创建一个元编程,该编程在编译时从对象列表中生成查找表。这几乎与我的第一个例子一样快,但每个对象只需要一个虚函数,并且在程序的某一点上可以知道所有对象类型。用户将负责索引的唯一性,但元程序可以检测到双索引,从而减少错误的变化。
答案 1 :(得分:0)
使用dynamic_pointer_cast
获取派生类的共享指针。如果派生类的类型错误,则返回的指针将为nullptr
。
std::shared_ptr<SphereCollider> sc = std::dynamic_pointer_cast<SphereCollider>(colliderOne);
如果sc
不属于nullptr
,则 colliderOne
将为SphereCollider
。否则它将指向SphereCollider
。
通过使用此方法,您可以实现动态分派。
<强>更新强>
以下是使用智能指针向下转换实现动态分派的示例。
std::shared_ptr<SphereCollider> sc1, sc2;
std::shared_ptr<AABBCollider> aabc1, aabc2;
...
if (sc1 = std::dynamic_pointer_cast<SphereCollider>(colliderOne))
{
if (sc2 = std::dynamic_pointer_cast<SphereCollider>(colliderTwo))
Colliding(*sc1, *sc2);
else if (aabc2 = std::dynamic_pointer_cast<AABBCollider>(colliderTwo))
Colliding(*sc1, *aabc2);
...
}
else if (aabc1 = std::dynamic_pointer_cast<AABBCollider>(colliderOne))
{
if (sc2 = std::dynamic_pointer_cast<SphereCollider>(colliderTwo))
Colliding(*aabc1, *sc2);
else if (aabc2 = std::dynamic_pointer_cast<AABBCollider>(colliderTwo))
Colliding(*aabc1, *aabc2);
...
}
...
如果你有许多不同类型的Collider类,上面的代码可能效率不高。它在类中执行线性搜索以执行调度。
通过向返回类名的Collider类添加一个字段,您可以连接两个类名来构建一个键。然后你会使用
std::unordered_map
以lambda作为关联值。 lambda会收到两个std::shared_ptr<Collider>
作为参数。它会执行向下转换并调用Collider函数。
如果需要,我会显示代码。
答案 2 :(得分:0)
您可以为将在循环中调用的基类Colliding
提供一般Collider
函数。该通用函数基于传递的对象的动态类型进行区分,从而基于动态类型模拟过载。一个或多或少丑陋的宏可以帮助减少代码。
#define call_dynamic(T1,T2,o1,o2) if( dynamic_cast<T1*>(&o1) && dynamic_cast<T2*>(&o2) ) return Colliding( *((T1*)&o1),*((T2*)&o2) )
bool Colliding(const Collider& colliderOne, const Collider& colliderTwo) {
call_dynamic(SphereCollider,SphereCollider,colliderOne,colliderTwo);
/***/
call_dynamic(SphereCollider,AABBCollider,colliderOne,colliderTwo);
/* this line should never be reached */
return false;
}
根据您拥有的课程数量,这仍然需要一些代码行。如果性能很重要,这也不是最佳解决方案。但至少这是解决问题的一种可能性。
如果将对象传递给函数的顺序无关紧要,则可以进行一些改进。
答案 3 :(得分:0)
另一种方法是将Colliding
函数移动到类中,并使用Double Dispatch
方法解析动态类型。
这适用于c1.collideWith(c2);
如下:
c1
是一个引用,因此解析为其动态类型(此处为:SphereCollider
)。因此,调用方法SphereCollider::collideWith(Collider& c )
。
要同时解析现在位于c2
的{{1}}类型,我们再次调用成员函数(此时为c
)以强制进行类型解析。我们将c2
作为现在属于*this
类型的参数传递。这将调用方法SphereCollider
。
所以你唯一需要的是对你的对象的两个引用,这个方法将起作用。下面给出一个例子。
AABBCollider::collideWith(SphereCollider& c )
输出如下:
#include <iostream>
class SphereCollider;
class AABBCollider;
class Collider {
public:
virtual bool collideWith(Collider& c ) {
std::cout << "Collider <> Collider" << std::endl;
};
virtual bool collideWith(SphereCollider& c ) {};
virtual bool collideWith(AABBCollider& c ) {};
};
class SphereCollider : public Collider {
public:
virtual bool collideWith(Collider& c ) {
c.collideWith(*this);
}
virtual bool collideWith(SphereCollider& c ) {
std::cout << "SphereCollider <> SphereCollider" << std::endl;
}
virtual bool collideWith(AABBCollider& c ) {
std::cout << "AABBCollider <> SphereCollider" << std::endl;
}
};
class AABBCollider : public Collider {
public:
virtual bool collideWith(Collider& c ) {
c.collideWith(*this);
}
virtual bool collideWith(AABBCollider& c ) {
std::cout << "AABBCollider <> AABBCollider" << std::endl;
}
virtual bool collideWith(SphereCollider& c ) {
std::cout << "SphereCollider <> AABBCollider" << std::endl;
}
};
int main( int argc, char **argv ) {
Collider& c1 = * new SphereCollider;
Collider& c2 = * new AABBCollider;
c1.collideWith(c1);
c1.collideWith(c2);
c2.collideWith(c2);
c2.collideWith(c1);
return 0;
}