我有不同的几何形状,它们都是从基类派生的,以便将它们收集在 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 ...
};

给定两个几何geom1geom2,您现在可以测试与
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;
}

很多代码要编写,但是通过简化访问者模式,您可以通过这种方式进行双重调度。好结局。 :)

09-30 14:08
查看更多