3

I have different geometries, which all derived from a base class in order to collect them in a vector. Now I would like to detect collisions between two geometries in this vector. The intersect function is templated with the two geometry types (polymorphism works only with one object as far as I know).

How is it possible to call intersect() correctly? Is there a way without dynamic_cast and checking for != nullptr? Save the enum as constexpr inside the geometry class and use static_cast?

Thank you very much.

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<...,...>(...,...)?
}

EDIT: The intersect function is provided by the FCL library, so I cannot change it...

2
  • read about visitor pattern Commented Dec 13, 2013 at 14:17
  • I've fixed a presumed typo. Please review the edit. Commented Dec 13, 2013 at 14:32

4 Answers 4

4

The guideline

Polymorphism should generally be preferred over switch statements which check for a type, because the resulting code is much more type-safe and you get compile-time errors instead of runtime-errors which is generally a good thing. You have the interesting case of a function which takes two objects and which should be dispatched depending dynamically on both argument types.

This is how it's done

Here's one way how to do that: First you need to forward declare all derived classes and write the base class in the following way:

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;
    // ...
};

The function collidesWith() must be implemented to call the dispatchCollidesWith() on other with *this as the argument. Note that *this has different types in the derived classes and hence the different overloads are called. In order to omit too much typing we use a template which does the implementation for us:

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) );
    }
};

Now the derived classes can be implemented in the following way:

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 ...
};

Given two geometries geom1 and geom2 you can now test for collision with

geom1.collidesWith( geom2 );

And everything is perfectly type-safe.

A more extensible pattern

There's a backside to this approach: You have to add tons of functions to your base class and it will get crowded easily. Here's how you can make your base class extensible for virtual dispatch, so you don't need to add a virtual function to it everytime you need new functionality:

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;
};

By deriving from GeometryDispatcher you can add a new functionality to your class hierarchy. The run() function must be implemented by the derived classes of GeometryBase in the following way:

void Box::run( GeometryDispatcher & dispatcher ) const
{
    dispatcher.dispatch( *this );
}

(This is also known as the first half of the visitor pattern. Read about it, so you can understand my code!) Now you could add a function collidesWithBox() in the following way

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;
}

You can implement the functions collidesWithSphere() and collidesWithCone() analogously. Finally, you can implement the function collidesWith() this way:

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;
}

A lot of code to write but you get a double dispatch this way by facilitating the visitor pattern. Happy end. :)

Sign up to request clarification or add additional context in comments.

1 Comment

@user2968115 Note you can even have the compiler write the virtual bool dispatchCollidesWith( const Box & other ) const = 0; interface and implementation methods using templates and type lists. Linearly inherit from classes bool dispatchCollidesWith( const T & )=0 unrolled from your Ts.... In GeometryImpl do something similar that implements dispatchCollidesWith calling the template function. This lets you use the runtime virtual functions to dispatch to the compile time templates without having to directly write n^2 code. The constant amount of code is, however, large.
0

In intersect, I'm guessing that:

// math stuff

is doing three things:

  1. Get some values from geom1
  2. Get some values from geom2
  3. Do some math using the above values and process the result

If that's the case, here's how I'd do this.

First, make intersect a non-template function that takes two parameters, both of type GeometryBase*.

Add pure virtual methods to GeometryBase which defines an interface to return the "values" needed in steps 1 & 2 above. You may need to give a bit of a think to how best to represent these values in a sufficiently generic way so that all subclasses can return the same type of things.

Implement the pure virtaul in each of the GeometryBase-derived concrete classes.

Implement intersect in terms of calling these virtual methods (via the GeometryBase pointer; no need to cast in any way or apply the visitor pattern).

You could make intersect a free function (in a namespace, please!), or possibly implement it in a class. It might (or might not) to make this a member function of GeometryBase, in which case this would be used in leiu of one of the GeometryBase* parameters.

2 Comments

I edited my question, the intersect function is defined in a library which I'm using, so I cannot change it...
@user2968115: Ah. Well, that sure makes things quite a bit more crappy.
0

You should better use inheritance instead of using an enum. Then you could create a generic intersect function that takes GeometryBase objects as arguments: it will be generic for GeometryBase children.

It means that you must provide the needed interface in GeometryBase in order to compute the intersection:

class GeometryBase
{
    //...
public: // your interface
    virtual Vertices getVertices() const; // can be virtual, or not
    //...
}

void intersect(GeometryBase* geom1, GeometryBase* geom2)
{
    // math stuff, calling geom1->getVertices() & ...
}

//specializations
class Box: public GeometryBase
{
public:
    virtual Vertices getVertices() const; // implement what you need to specialize
}
class Cone : public GeometryBase
{
}
// etc...

Then you vector should look like:

std::vector<GeometryBase*> geometries;

2 Comments

The calculations done inside the intersect function are strongly dependent on the geometries. You think that the correct calculation block is chosen by a switch statement?
No, I precisely wanted to avoid this. I have now read the other answers and I am convinced you should definitely follow the visitor pattern.
0

The way I would approach this would be manual double dispatch with help from a type mapping.

Here is a really simple version:

class GeometryBase
{
public:
  GeometryBase() {}
  virtual GeometryBase() {}
  virtual void doIntersect( GeometryBase* other ) = 0;
  virtual GeometryType GetGeometryType() const = 0;
};

// forward decl:
template<enum GeometryType GeomType1, enum GeometryType GeomType2>
void intersect(Geometry<GeomType1>* geom1, Geometry<GeomType2>* geom2);

template<enum GeometryType GeomType>
class Geometry : public GeometryBase
{
public:
  Geometry() {}
  virtual Geometry() {}
  GeometryType GetGeometryType() const { return GeomType; }
  virtual void doIntersect( GeometryBase* other ) {
    switch (other->GetGeometryType()) {
      case GeometryType::BOX:
        intersect( this, static_cast<GeometryType<GeometryType::BOX>*>(other) );
        break;
      // etc
    }
  }
};

where we do manual dynamic double dispatch in a switch statement.

We can build some infrastructure to make this easier in a number of ways. You can write a fast_cast that supports casting your base class into a number of subclasses using only a virtual function lookup and call (which is faster than a dynamic_cast typically). You can write a function that replaces the copy/paste case expressions above with a magic switch that does it (but the boilerplate to do that is longer than the above code).

You could write a macro to write out the cases (thereby obeying DRY, and reducing some categories of errors).

But the basic problem is one of double dispatch, and double dispatch requires manual work on the part of the programmer in C++.

2 Comments

I guess the GetGeometryType() function is evaluated run-time and works similar to the fast_cast you suggested. This would reduce the speed you gain with templates, however I will give it a try. Thank you for your answer!
It has a branching and virtual function call overhead, but you want run time dynamic behavior, so you need some kind of run time dynamic overhead. The template body itself knows the full type of both arguments, so any optimization there required can occur, and the virtual overhead is only incurred once per intersect, which is better than making every property virtual performance wise. The above is a maintenance headache, however.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.