5

I’m trying to make a custom collision engine for academic purposes and i'm stuck on a general c++ programming issue I already have all the geometries which work properly and for the scope of the question i have this function:

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

I also have the following class which I need to finish implementing

template<typename geometry_type>
class collidable_object
{
public:
    explicit collidable_object( geometry_type& geometry ) :
        m_geometry( geometry )
    {

    }

    ~collidable_object()
    {

    }

private:
    geometry_type& m_geometry;
};

Where my issue rises is when I want to create a list of collidable_object and test them for intersection 2 by 2.

I have made some research on Google and found having a base class for collidable_object will allow me to store objects into the list. But after that how do I test the object depending on their specific geometries?

I have tried to implement the visitor pattern but I get stuck every time because i don't want to hardcode every possible geometry type since I will always just call intersetcs().

I also found an article on cooperative visitor but this seems way to complicated.

Does anyone have a simple and efficient solution?

EDIT: the reason I wanted to avoid having a list of geometries is because I want it to be relatively easy to add new geometries without having to find files in the arborescence.

EDIT2: Here is more information on the intersetcs method : the intersects method is based on tag dispatching to find the right geometry but almost all convex shape use the GJK algorithm which only require that the object can return the point furthest in a given direction. For non-convex shapes, the shapes are fragmented into convex sub-shapes and the process restarts.

there is no uniform criteria to see if intersects will be able to process a given shape most use furthest_along but sphere on sphere does not and sphere agragations would also not require the use of furthest_along

Additional information: I use VS2012 and C++11

tiridactil
  • 389
  • 1
  • 11
  • possible duplicate of [Implementing the visitor pattern using C++ Templates](http://stackoverflow.com/questions/11796121/implementing-the-visitor-pattern-using-c-templates) – user93353 Jun 19 '13 at 01:41
  • I have seen that post and the author seems explicitly specify the type of the visitables which I would rather not do... – tiridactil Jun 19 '13 at 02:16
  • I think you can't avoid having a list of all possible geometries in *some* place. This is because you'll have to write some function or method which dispatches based on the runtime types of *two* argument types, which can't be done using a simple template member or similar. With any advanced template magic, the compiler wouldn't know which templates need instantiation without such a list being present somewhere. – MvG Jun 19 '13 at 06:29
  • Would a list of all visitors be possible? – Yakk - Adam Nevraumont Jun 19 '13 at 12:52
  • @Yakk I'm not sure what you mean but if you have a solution which uses a known fixed amount of visitor that would be ok. – tiridactil Jun 19 '13 at 12:56
  • 2
    Hmm. You are requesting double dispatch, which the technique I had in mind doesn't handle. To understand why you need a list of the types at hand, imagine you had 3 DLLs. The first defines the base class and the function, and is known to the other two, but does not know about them. The second defines `Circle` geometry, and does not know of the third. The third defines `Rectangle` geometry, and does not know of the second. Where does the runtime code that represents `Circle` intersect `Rectangle` go in this scenario? – Yakk - Adam Nevraumont Jun 19 '13 at 13:24
  • So as your abstract goal isn't possible, the next question is, *how* does `intersects` guarantee that it works for all pairs of geometry types? Because if the geometry types have duck type guarantees that `intersects` implicitly depends upon, we might be able to use type erasure to create an `intersects` that interacts with a fixed runtime interface. – Yakk - Adam Nevraumont Jun 19 '13 at 13:32
  • @Yakk in the 3DLL scenario it would fall in the general case and only require an overload of the method `furthest_along`... – tiridactil Jun 19 '13 at 16:30
  • I understand that you want to do it without the need to maintain separate set of function specializations and list of types for which your framework would work. C++ is generally unfriendly to such write-and-go approach. What if somebody writes his own class but other translation units will not get to see it? Without any global list they will not be able to know what code they should call. Secondly if you don't want geometry list but you want compile-time checking if collision-checks are available for each geometry, then how compiler should know how to do it? – j_kubik Jul 07 '13 at 23:47

2 Answers2

3

You can't get away without storing the list of all possible geometries in some place. Otherwise the compiler won't know which template instantiations to generate. But I've come up with some code where you have to state that list in only a single location, the typedef of GeometryTypes. Everything else builds on that. I'm not using a vistor pattern here, which has the benefit that I you don't have to add boilerplate code to your different geometry class implementations. Implementing intersects for all combinations is enough.

First some includes: I'll be using shared_ptr later on, and printing stuff, and aborting in case of unknown geometry types.

#include <memory>
#include <iostream>
#include <cstdlib>

Now define some geometries, with a common base class which you can use for polymorphic pointers. You have to include at least one virtual function, so that you get a virtual function table which can be used for dynamic_cast later on. Making the destructor polymorphic ensures that derived classes will be cleaned up properly even if deleted via a polymorphic pointer.

struct Geometry {
  virtual ~Geometry() { }
};
struct Circle : public Geometry { };
struct Rectangle : public Geometry { };

Now comes your intersects template. I only write a single catch-all implementation for this demo.

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

This is the place where we declare the list of all geometries. If you have geometries derived from one another, make sure to have the most specific ones first, as these will be tried in order for dynamic casts.

template<typename... Ts> class TypeList { };
typedef TypeList<Circle, Rectangle> GeometryTypes;

Now a bunch of helper code. The basic idea is to iterate over one such TypeList and try a dynamic cast for every type. The first helper iterates for the lhs argument, the second for the rhs argument. If no match is found, you have an incomplete list, which will cause the application to abort with a hopefully useful error message.

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

With all these helpers in place, you can now do a polymorphic intersection test. I'm introducing a shared_ptr to store such polymorphic pointers, and I suggest you do the same in your collidable_object class. Otherwise you'd have to take responsibility of ensuring that the referenced geometries stay alive as long as the collidable object is alive, but will get cleaned up eventually. Do you want that kind of responsibility?

typedef std::shared_ptr<Geometry> GeomPtr;

bool intersects(GeomPtr lhs, GeomPtr rhs) {
  return IntersectHelper1<GeometryTypes, GeometryTypes>::
    isects(lhs.get(), rhs.get());
}

And finally some main so you can actually run all of the above code in a tiny example.

int main() {
  GeomPtr g1(new Rectangle), g2(new Circle);
  std::cout << intersects(g1, g2) << std::endl;
  return 0;
}
MvG
  • 57,380
  • 22
  • 148
  • 276
  • Your solution is interesting and I really like the simplicity of use in the main but do you think it would be possible to 1) have the list auto generated so adding geometries does not require editing existing files, 2) have an indexed list since it won't change at runtime and finally 3) replace the dynamic_cast by something which would fail at compile time instead? If you have a solution for these 3 points (or even the first 2) that would probably be a perfect method of solving my issue... – tiridactil Jun 19 '13 at 12:26
  • @tiridactil the first sentence answers (1). – Yakk - Adam Nevraumont Jun 19 '13 at 12:31
  • possible solution for (1) would it be possible to create a base class with CRTP which creates a list containing it would create some kind of recursive list. I'm not sure this can be done however – tiridactil Jun 19 '13 at 12:52
  • 1) You need to edit only a single existing file, probably a header file, since that list is only in a single place. On the other hand, you have to ensure that all template specializations of `intersects` are in scope in the compile unit of this polymorphic `intersects`. Unless you instantiate templates explicitely, which again would mean listing all specializations explicitely. So I see little gain. 2) and 3) would be possible if you maintain a central registry. I'll think about methods to enforce a one-to-one mapping between an enum and a set of classes. – MvG Jun 19 '13 at 13:35
  • 1
    Compile-time completeness checks would likely be difficult and ugly. Particularly if [compile-time iteration over enumerators](http://stackoverflow.com/q/17195352/1468366) turns out to be impossible. And you still won't know the actual type you're dealing with at runtime, so you still need some form of branching based on the runtime type. You could perhaps replace `dynamic_cast` by some other RTTI approach, perhaps a virtual function returning an enum. You might even boost complexity from O(n) to O(1). But you can't avoid using RTTI completely. – MvG Jun 19 '13 at 15:52
  • In response to your EDIT2: you can declare a base class (e.g. `ConvexGeometry`) for everything that implements `furthest_along` and include that in `GeometryTypes`. That way, you could add new classes derived from `ConvexGeometry` and wouldn't have to add them to the list unless you'd want to special-case the intersection code. – MvG Jun 19 '13 at 18:11
1

Your second edit indicates that the basic intersection routine will operate using some furthest_along code. You could make use of that, in such a way that normal intersection checks operate on a common base class which includes this furthest_along in its interface. You would need special functions only for special cases, for which you want other algorithms.

The following example avoids all dynamic casts and performs two virtual method invocations instead (also known as “double dispatch”, which by the way is also available as a tag, so adding that to your question might be useful).

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

If you call a.intersects(b), then the intersects method will be chosen from the virtual function table of a, whereas the intersects_impl method will be chosen from b. If you want to add a special case for the type combination A and B, you'll have to add

  1. a virtual method Geometry::intersects_impl(const A&), delegating to the default
  2. an override method A::intersects delegating to intersects_impl(const A&)
  3. an override method B::intersects_impl(const A&) with the actual custom code

If you have to add many types with many special case algorithms, this might amount to a fairly high number of modifications in various places. If, however, most shapes that you add will be using the default implementation, then all you have to do is properly implementing furthest_along for each of these.

You can of course do more clever things than this. You can create an intermediate class ConvexGeometry which uses the furthest_along approach, and a class NonConvexGeometry which would provide some means for partitioning into convex pieces. You could implement intersects in both of these and make the implementation in Geometry purely abstract (= 0). You could then avoid intersects_impl(const Geometry&) and instead use intersects_impl(const ConvexGeometry&) and intersects_impl(const NonConvexGeometry&) as the default mechanisms, both of which could be = 0 in Geometry and implemented appropriately in ConvexGeometry and NonConvexGeometry. But if you understand the idea behind the above code, then adding these extensions should be simple enough. If not, ask.

MvG
  • 57,380
  • 22
  • 148
  • 276