Original Post
Hello. Here's a quick poll for you: how do you handle multiple-dispatch polymorphism when detecting collisions? To clarify, how do you decide that the collision that just occured happened between an Enemy and a Bullet, instead of an ICollideable and another ICollideable? [Edited by - ToohrVyk on August 27, 2006 5:08:05 PM]
- You use RTTI and dynamic_cast (in C++) or as/is (in C#).
- You use your own in-house version of RTTI.
- You use the visitor pattern and a large amount of virtual functions.
- You perform collision detection separately for each possible pair of types.
- You use another method (please explain).
#include <iostream>
#include <string>
#include "collider.hpp"
/* ========================================================================
* PRELIMINARY DEFINITIONS
* ======================================================================== */
// Step 0: decide how the position and aspect of entities are
// represented, by creating a hull type and typedef'ing the
// important classes.
struct Hull {
// Horizontal position
float x;
// Vertical position
float y;
// Radius
float r;
// Construct a new hull
Hull(float x, float y, float r) : x(x), y(y), r(r) { }
// Do two hulls collide?
static bool Collide(const Hull a, const Hull b) {
const float dx = a.x - b.x;
const float dy = a.y - b.y;
const float tr = a.r + b.r;
return dx * dx + dy * dy < tr * tr;
}
};
typedef Collider::Detector<Hull> Detector;
typedef Collider::Entity<Hull> Entity;
typedef Collider::Resolver<Hull> Resolver;
// Step 1: create a custom implementation of the collision detector,
// which will perform the actual geometry checks to see when collisions
// occur.
//
// This requires simply to overload the required methods of the
// collision detector.
//
// It may be as simple as checking every hull against all others,
// or as complex as a hierarchy of bounding boxes, or even worse.
class CollisionDetector : public Detector {
private:
std::list<Entity> hulls;
protected:
void AddHull(Entity& s) {
hulls.push_back(s);
}
void RemoveHull(Entity& s) {
hulls.remove(s);
}
void ResolveAllCollisions() {
std::list<Entity>::iterator outer, inner;
for (outer = hulls.begin(); outer != hulls.end(); ++outer) {
for (inner = outer; inner != hulls.end(); ++inner) {
if (inner == outer) continue;
if (!this->CanCollide(*inner,*outer)) continue;
if (!Hull::Collide(inner->hull, outer->hull)) continue;
this->Collide(*inner,*outer);
}
}
}
};
// Step 2: define the various data that will be associated to the
// colliding objects. In general, these are the objects that own the
// hulls. In our case, they will be simple strings and integers.
typedef std::string A;
typedef int B;
// Step 3: define the behaviour of various types of collisions
// statically. This is used to differentiate Player-Enemy,
// Player-World and other collision types.
class C_AA {
public:
void Resolve(const A & a, const A & b) {
std::cout << "A-A : " << a << " " << b << std::endl;
}
} aa_callback;
class C_AB {
public:
void Resolve(const A & a, const B & b) {
std::cout << "A-B : " << a << " " << b << std::endl;
}
} ab_callback;
class C_BB {
public:
void Resolve(const B & a, const B & b) {
std::cout << "B-B : " << a << " " << b << std::endl;
}
} bb_callback;
/* ========================================================================
* MAIN FUNCTION
* ======================================================================== */
int main(int argc, char** argv) {
// Step 4: create a collision detector
CollisionDetector detector;
// Step 5: define the various groups of objects. In general, they
// are PlayerBullets, EnemyBullets, Player, Enemy, World... here,
// we define two: a and b.
Detector::Group* a = detector.CreateGroup();
Detector::Group* b = detector.CreateGroup();
{
// Step 6: create resolver instances using the aforementioned
// callbacks.
detector.CreateResolver(a,a,aa_callback);
detector.CreateResolver(a,b,ab_callback);
detector.CreateResolver(b,b,bb_callback);
// Step 7: enjoy! Add and remove objects and their associated
// hulls to the groups above, and launch collision testing
// with the appropriate detector method.
//
// The first constraint here is that the Hull must always be
// an lvalue. This is necessary so that the collision detector
// can take into account modifications to the hull without
// requiring a reinsertion every frame. The second constraint
// is that a given hull may only be used once in a given group.
Hull h1(0.0,0.0,10.0);
Hull h2(0.0,2.0,1.0);
Hull h3(2.0,0.0,1.0);
Hull h4(2.0,5.0,5.0);
Hull h5(6.0,6.0,3.0);
a->Add(h1, "1");
b->Add(h2, 2);
a->Add(h3, "3");
b->Add(h4, 4);
a->Add(h5, "5");
detector.ResolveAll();
}
}