5

I want to let the compiler build the connections of functions for a physics collision system. I have the test collision function:

template <typename T, typename U>
inline void Collision(T& t, U& u)
{
    if(u.CheckCollision(t.GetCollider()))
    {
        u.HitBy(t);
        t.Hit(u);
    }
}

Then I use a collider object to hold the collision object.

template <typename T>
class Collidable
{
    T m_Collider;
    VictimEffect m_HitBy;
    MenaceEffect m_Hit;
 public:
    void SetCollider(const T& t) { m_Collider = t; }
    void SetVictim(VictimEffect effect) { m_HitBy = effect; }
    void SetMenace(MenaceEffect effect) { m_Hit = effect; }

    T& GetCollider()
    {
        return m_Collider;
    }
    template <typename V>
    void HitBy(V& menace)
    {
        m_HitBy.HitBy(menace.GetCollider());
    }
    template <typename V>
    void Hit(V& victim)
    {
        m_Hit.Hit(victim.GetCollider());
    }
    template <typename V>
    bool CheckCollision(V& menace)
    {
        return m_Collider.CheckCollision(menace);
    }
};

I added a level of indirection for the effect functions

class VictimEffect
{
public:
    template<typename C>
    void HitBy(C&)
    {;}
};

class MenaceEffect
{
public:
    template<typename C>
    void Hit(C&)
    {;}
};

What I want to end up with is a way to call functions in any object that has the right function signature, but does not require types that will not hit.

class Wall;
class Ball : public MenaceEffect
{
public:
    void Hit(Wall& wall);
    void Hit(Ball& ball);
};

class Wall : public VictimEffect
{
public:
    void HitBy(Ball& ball);
};

Notice I do not want to have to have a function for a wall hitting a wall. I want to only write or provide functions for interactions that should happen. I know this does not work because the VictimEffect does not "see" the derived function.

I want to dispatch without having to specify the template parameters everywhere they are used. I want to build my collidable effect apart from collidable and give it to the collidable object generically. I don't want to have different collidable objects for every collection of things that can be collided with. Basically I want

vector<collidable> movingThings;
vector<collidable> staticThings;
// get iterators ...
Collide(Moving, Static);

I need more layers of indirection here, but I just can't see it. I know it can be done because all of the information is available at compile time. There is no need for a dynamic polymorphic solution.

Any help is greatly appreciated!

Update What I am trying to do is build my own modern (as in Andrei's book) game library. So far I have most of the relationships worked out, but this one has me stumped. This isn't a time critical issue, but rather a results critical issue. I've been working in games for many many years and I don't like any game engine I've worked on. This one is meant to be simple to use and easy to extend. It will work on any platform now that console compilers have gotten not terrible.

Here's a simple example of what I want to be able to write.

int main()
{
    // System must come first
    System system(640, 480, false);
    Renderer renderer;
    Mouse mouse;
    Keyboard keys;

    // Make the ball
    Bounce ball;
    Sprite ballSprite("02.bmp");
    CollisionBox ballPhysics;
    BallCommand ballBehavior;

    ball.SpriteMover = boost::bind(&Sprite::Observer<Pos2D>, &ballSprite, _1);
    ball.PhysicsMover = boost::bind(&CollisionBox::Move, &ballPhysics, _1);

    // Make the bounds
    Boundary bounds;

    // Set up Physics
    Collidable<Boundary> boundC;
    boundC.SetCollider(bounds);
    boundC.SetVictim(ballBehavior);

    Collidable<CollisionBox> ballC;
    ballC.SetCollider(ballPhysics);

    PretendPhysics physics;
    physics.SetBall(ballC);
    physics.SetBounds(boundC);

    while(!mouse.LeftClick() && !keys.AnyKey())
    {
        ball.MoveRel(ballBehavior.Delta());
        physics.Update();

        renderer.Clear();
        renderer.Render(ballSprite);
        renderer.SwapBuffers();
    }
}

All the Physics is WIP and is not where I want it to end up, but it is getting there. The System object initializes Windows and the platform specific renderer. In this case it's openGL. It creates all the windows components though it is compiled as a console app. I didn't want people to deal with main() vs winmain(). Except for Physics there is no inheritance. The three render calls will be replaced with one after I get Scenes into the system. I plan on using something I wrote a while back combined with Gary Powell's and Martin Weiser's View template library.

I know I'm just not looking at it right. Visitor could help shift my approach.

So that's what I'm trying to do.

Update 2 OK. I've gotten some inspiration from the comments and answer so far and this is where I went, but I'm not finished yet.

template <typename T, typename V = VictimEffect, typename M = MenaceEffect>
class Collidable
{
    T m_Collider;
    V m_HitBy;
    M m_Hit;

public:
    Collidable(T collide, V victim, M menace) : m_Collider(collide), m_HitBy(victim),         m_Hit(menace) {;}
    Collidable(T collide) : m_Collider(collide) {;}
    Collidable(T collide, V victim) : m_Collider(collide), m_HitBy(victim) {;}

    T& GetCollider()
    {
        return m_Collider;
    }

    template <typename V>
    void HitBy(V& menace)
    {
        m_HitBy.HitBy(menace.GetCollider());
    }

    template <typename V>
    void Hit(V& victim)
    {
        m_Hit.Hit(victim.GetCollider());
    }

    template <typename V>
    bool CheckCollision(V& menace)
    {
        return m_Collider.CheckCollision(menace);
    }
};

Then to use it I do this

    Collidable<Boundary, BallCommand> boundC(bounds, ballBehavior);
    Collidable<CollisionBox> ballC(ballPhysics);

Omnifarious was right. I was losing the type info by passing in the base. Now the base is there just to catch things I don't care about.

For my container I think I can use a type list to collect every type that is added to the container and use that to declare type specific containers that then process info. One catch though is I cannot take normal iterators out it would be easier to pass function objects in.

maybe C++ 0X can help with that new unknown type variable. (I forget what it's called).

Tavison
  • 1,523
  • 1
  • 11
  • 19
  • 2
    I do not believe that the information is available at compile time. It's been 'erased' from compile time knowledge by putting base class instances (or, more likely, references (in the general sense, pointers count) of a base type that reference derived instances) in your vectors. I think you're stuck using virtual functions somehow. – Omnifarious Apr 19 '11 at 05:41
  • I know that once I did the inheritance the information is lost and I know there is a further challenge of getting everything into a single collection. Maybe at that point it can't be done, but as to the firing an event that has a common signature and differs only by the argument. I think that can be done. I can do it with Ball and Wall, but I can't do it with (Ball Wall) for one object and Wall for the other if that makes sense. I can also do it by specializing every possible outcome, but that is tedious. – Tavison Apr 19 '11 at 05:53
  • @Tavison - Once you start using virtual functions, it seems like the class Visitor pattern would get you what you want. – Omnifarious Apr 19 '11 at 06:32
  • Tavison can you explain better what are you trying to do please? I don't get your question. Give us complete code of the relationship between Collidable and Victim/MenaceEffect? – Giovanni Funchal Apr 19 '11 at 07:09
  • That might help the right direction. I've managed to templatize the observer pattern for moving the physics and render objects using the command pattern. I'm not in a hurry for a solution. It's more like with Andrei's book trying to find pure generic solutions to game engine design. I'm building a modern game engine in my spare time because I hate all the ones I've used so far. Thanks for the visitor tip. I see in Modern C++ Design Andrei has a visitor template. Great help. – Tavison Apr 19 '11 at 07:13
  • @Giovanni Two collidables are brought together by the Collision(). If there is a collidable, and I term the first to be the collider AKA menace and the other to be the Collidee AKA victim. Then each is called with the collision object to fire a callback function to process the result. It may be to bounce a ball or take damage from a bullet. Whatever the coder registers. – Tavison Apr 19 '11 at 07:39
  • Quoting: `maybe C++ 0X can help with that new unknown type variable. (I forget what it's called).` => **auto** – Giovanni Funchal Apr 19 '11 at 10:54
  • I still don't get why you are using a Collidable container and what is the difference with respect to the Collider class. – Giovanni Funchal Apr 19 '11 at 10:56
  • Once I have all the collidable objects, they need to be stored in a list of static objects, walls, desks, etc. and dynamic objects like soldiers and tanks. The dynamic checks collisions against both sets. If this can be held in one container it is easier to deal with. Since I would prefer not to use inheritance it means specific containers for specific colliders. So, there needs to be an object that knows all the different types it holds, but to the user it seems to be a single container. Further, the container will need to have algorithms applied to cull the scene to a manageable size. – Tavison Apr 19 '11 at 11:25
  • A lot of this may seem overly complicated when the solution could be done with inheritance, but in my experience most of the bugs in games comes from fragile inheritance and strange relationships. It means adding a new collision object becomes far more difficult than it needs to be. – Tavison Apr 19 '11 at 11:29
  • Hmm can you clarify what you mean by "fragile inheritance"? I have 10yrs of experience in C++ and never actually found any problem with virtual methods and inheritance as long as the design is clean. – Giovanni Funchal Apr 19 '11 at 14:59
  • I mean the designs aren't clean. Inheritance is run time polymorphism. Sometimes an employee becomes a boss for example. But a boss introduces functionality that an employee doesn't have, so virtual do nothing functions are added to the base class that allows them all to be employees. Casting down the inheritance chain is an example of fragile design. In games a tank is always a tank. A soldier is always a soldier. When a tank gets blown up it is either still a tank with altered data or a new object is spawned in the same game space. But a tank never becomes something else. – Tavison Apr 20 '11 at 05:03
  • (cont'd) So compile time polymorphism should be the preferred approach. – Tavison Apr 20 '11 at 05:04

2 Answers2

2

Maybe something like this? (only the hit part, the hitBy is symmetric)

class Wall;

class MenaceBase {
    public:
    template<typename T>
    void hit(T& t) {
        t.hitImpl(*this);
    }
};

class VictimBase {
    public:
};

template<typename M>
class Menace {
    public:
    virtual void hit(M&) = 0;
};

template<typename V>
class Victim {
    public:
    virtual void hitBy(V&) = 0;
};

class Ball : public MenaceBase, public Menace<Wall>, public Menace<Ball> {
    public:
    void hit(Wall& wall) {}
    void hit(Ball& ball) {}
};

class Wall : public VictimBase, public Victim<Ball> {
    public:
    void hitBy(Ball& ball) {}
    void hitImpl(Menace<Wall>& menace) {
        menace.hit(*this);
    }
};
Giovanni Funchal
  • 8,934
  • 13
  • 61
  • 110
  • Yeah, that is definitely getting there. I'd still like to do it without virtual. I know it sounds weird, but 12 or 13 years ago I decided to not use inheritance unless absolutely necessary. It has not always been better code, or good code, but it's helped me figure out some interesting techniques. I think this answer is correct for working code, but not what I'm looking for as a thought exercise. It definitely helps. Thanks very much. – Tavison Apr 19 '11 at 09:30
  • Ok, so the challenge is no use of virtual? Can you accept `dynamic_cast`? – Giovanni Funchal Apr 19 '11 at 10:50
  • It took me a minute, but I see where you're going with that. I'd be interested to see what you have, but I don't want that as the final solution. I think we are coming at the solution from two different ends and the final answer is in the middle somewhere. – Tavison Apr 19 '11 at 10:55
1

OK. Here's what I ended with.

class VictimEffect
{
public:
    template<typename C>
    void HitBy(C&)
    {;}
};

class MenaceEffect
{
public:
    template<typename C>
    void Hit(C&)
    {;}
};


template <typename T, typename M = MenaceEffect, typename V = VictimEffect>
class Collidable
{
    T m_Collider;
    M m_WeHit;
    V m_WeWereHit;

public:
    Collidable(T collide, M menaceEffect, V victimEffect) : m_Collider(collide), m_WeHit(menaceEffect), m_WeWereHit(victimEffect) {;}
    Collidable(T collide) : m_Collider(collide) {;}
    Collidable(T collide, M menaceEffect) : m_Collider(collide), m_WeHit(menaceEffect) {;}

    T& GetCollider()
    {
        return m_Collider;
    }

    template <typename V>
    void HitBy(V& menace)
    {
        m_WeWereHit.HitBy(menace.GetCollider());
    }

    template <typename V>
    void Hit(V& victim)
    {
        m_WeHit.Hit(victim.GetCollider());
    }

    template <typename V>
    bool CheckCollision(V& menace)
    {
        return m_Collider.CheckCollision(menace);
    }
};

template <typename T, typename U>
inline void Collision(T& t, U& u)
{
    if(u.CheckCollision(t.GetCollider()))
    {
        u.HitBy(t);
        t.Hit(u);
    }
}

It is not necessary to inherit from Victim or Menace effects. They are there if you want catchall solutions. The M and V types can be any object so if you want to inherit or not or however you want to use it. I have to create all the variations for ptrs etc. If all using objects inherit from base classes then it can be used as if it was written with dynamic polymorphism. If you don't want to inherit and instead keep special separate lists you can do that too.

Using it is easy

class Boundary
{
// Put whatever code here.
    bool CheckCollision(CollisionBox bounce)
};

class BallCommand
{

public:
    void Hit(Boundary& bounds)
    {
        if(bounds.XHit())
        {
            xDir *= -1;
        }
        if(bounds.YHit())
        {
            yDir *= -1;
        }
    }
};

Somewhere in your code you use it.

    Collidable<Boundary> boundC(bounds);
    Collidable<std::shared_ptr<CollisionBox>, BallCommand&> ballC(ballPhysics, ballBehavior);

    Collision(ballC, boundC);

There's more cleanup work to do, but I think it's a pretty good start. I'll convert to std::function to make it even easier.

Thanks to Omnifarious and Giovanni for helping me think it out.

Tavison
  • 1,523
  • 1
  • 11
  • 19