我有不同的几何形状,它们都是从基类派生的,以便将它们收集在 vector 中。现在,我想检测此 vector 中两个几何之间的碰撞。相交函数以两种几何类型为模板(据我所知,多态只对一个对象起作用)。
如何正确调用intersect()?有没有办法没有dynamic_cast并检查!= nullptr?将枚举另存为constant几何类内,并使用static_cast吗?
非常感谢你。
enum class GeometryType
{
BOX,
SPHERE,
CAPSULE,
CONE,
CYLINDER,
HALFSPACE
};
class GeometryBase
{
public:
GeometryBase() {}
virtual ~GeometryBase() {}
};
template<enum GeometryType GeomType>
class Geometry : public GeometryBase
{
public:
Geometry() {}
virtual ~Geometry() {}
};
template<enum GeometryType GeomType1, enum GeometryType GeomType2>
void intersect(Geometry<GeomType1>* geom1, Geometry<GeomType2>* geom2)
{
// math stuff
}
void detectCollisions(GeometryBase* geomBase1, GeometryBase* geomBase2)
{
// what can I do here to call the correct intersect<...,...>(...,...)?
}
编辑:相交功能由FCL库提供,所以我无法更改...
最佳答案
指导方针
通常,与检查类型的switch语句相比,多态性通常更可取,因为生成的代码更加类型安全,并且您会得到编译时错误,而不是运行时错误,这通常是一件好事。您有一个有趣的情况,该函数带有两个对象,应根据两种参数类型动态地分派(dispatch)。
这是怎么做的
这是执行此操作的一种方法:首先,您需要以以下方式向前声明所有派生类并编写基类:
class Box;
class Sphere;
class Cone;
// ...
class GeometryBase
{
public:
virtual bool collidesWith( const GeometryBase & other ) const = 0;
protected:
virtual bool dispatchCollidesWith( const Box & other ) const = 0;
virtual bool dispatchCollidesWith( const Sphere & other ) const = 0;
virtual bool dispatchCollidesWith( const Cone & other ) const = 0;
// ...
};
必须实现
collidesWith()
函数,以dispatchCollidesWith()
作为参数来调用other
上的*this
。请注意,*this
在派生类中具有不同的类型,因此将调用不同的重载。为了省去太多的输入,我们使用一个模板来为我们实现:template <typename T>
class GeometryImpl : public GeometryBase
{
public:
virtual bool collidesWith( const GeometryBase & other ) const
{
assert( typeid(*this) == typeid(T) );
return other.dispatchCollidesWith( static_cast<const T&>(*this) );
}
};
现在可以通过以下方式实现派生类:
class Box : public GeometryImpl<Box>
{
protected:
virtual bool dispatchCollidesWith( const Box & other ) const { /* do the math */ }
virtual bool dispatchCollidesWith( const Sphere & other ) const { /* do the math */ }
virtual bool dispatchCollidesWith( const Cone & other ) const { /* do the math */ }
// ...
private:
// data ...
};
给定两个几何
geom1
和geom2
,您现在可以测试与geom1.collidesWith( geom2 );
一切都是完全类型安全的。
更具扩展性的模式
这种方法有一个缺点:您必须在基类中添加大量的函数,否则它很容易拥挤。这是使基类可扩展以进行虚拟调度的方法,因此无需在每次需要新功能时都向其添加虚拟功能:
class GeometryDispatcher;
class GeometryBase
{
public:
void run( GeometryDispatcher & dispatcher ) const = 0;
};
class GeometryDispatcher
{
public:
virtual void dispatch( const Box & shape ) = 0;
virtual void dispatch( const Sphere & shape ) = 0;
virtual void dispatch( const Cone & shape ) = 0;
};
通过从
GeometryDispatcher
派生,您可以向类层次结构添加新功能。 run()
函数必须由GeometryBase
的派生类以以下方式实现:void Box::run( GeometryDispatcher & dispatcher ) const
{
dispatcher.dispatch( *this );
}
(这也被称为访问者模式的前半部分。阅读它,以便您理解我的代码!)现在,您可以通过以下方式添加函数
collidesWithBox()
class CollidesWithBoxDispatcher : public GeometryDispatcher
{
public:
CollidesWithBoxDispatcher( const Box & box ) : box(box) {}
bool getResult() const { return result; }
virtual void dispatch( const Box & shape ) { ... }
virtual void dispatch( const Sphere & shape ) { ... }
virtual void dispatch( const Cone & shape ) { ... }
private:
bool result;
const Box & box;
};
bool collidesWithBox( const GeometryBase & shape, const Box & box )
{
CollidesWithBoxDispatcher d( box );
shape.run( d );
return d.result;
}
您可以类似地实现
collidesWithSphere()
和collidesWithCone()
函数。最后,您可以通过以下方式实现功能collidesWith()
:class CollidesWithDispatcher : public GeometryDispatcher
{
public:
CollidesWithDispatcher( const GeometryBase & shape ) : shape(shape) {}
bool getResult() const { return result; }
virtual void dispatch( const Box & box )
{
result = collidesWithBox( shape, box );
}
virtual void dispatch( const Sphere & sphere ) { ... }
virtual void dispatch( const Cone & cone ) { ... }
private:
bool result;
const GeometryBase & shape;
};
bool collidesWith( const GeometryBase & shape1, const GeometryBase shape2 )
{
CollidesWithDispatcher d( shape2 );
shape1.run( d );
return d.result;
}
很多代码要编写,但是通过简化访问者模式,您可以通过这种方式进行双重调度。好结局。 :)