Tutorial 7: Event Handlers and User Data

 

 

Sometimes it’s nice to be notified when certain events occur.  For example, when two particular Solids collide you may want to play a sound effect, destroy one of the Solids, make the Solids stick together, etc.  OPAL’s event handlers allow you do catch these events and handle them in some application-specific way.  To use an event handler you must derive your own class from OPAL’s base event handler classes.  OPAL will then notify your derived event handlers at the appropriate times.

 

Besides event handlers, this tutorial also demonstrates “user data” pointers.  Simulators, Solids, Joints, Motors, and Sensors all have void pointers to some user-defined data.  This allows OPAL objects to store references to application-specific objects.  OPAL does not touch this data anywhere: it simply stores the pointer for the user.

 

Collision Event Handler

This event handler gets notified when two Solids collide.  Each Solid has its own pointer to a collision event handler.  If this pointer is NULL, it will ignore collision events; otherwise, its collision event handler will be notified whenever the Solid collides with another Solid.

 

Here is an example of a user’s derived collision event handler:

 

 

class MissileCollisionEventHandler : public opal::CollisionEventHandler

{

public:

 

    virtual void OPAL_CALL handleCollisionEvent(const CollisionEvent& e)

    {

        // Get a pointer to the missile object via the Solid’s user data.

        Missile* missile = (MyMissile*)e.thisSolid->getUserData();

 

        // Destroy the missile.

        missile->destroy();

 

        // Trigger a visual explosion effect at the collision point.

        AppParticleManager::triggerExplosion(e.pos);

 

        // Play an explosion sound.

        AppSoundManager::playSound(EXPLOSION_SOUND);

 

        // Calculate and apply an explosion force to the other Solid.

        opal::Force f;

        f.type = opal::GLOBAL_FORCE;

        f.duration = (opal::real)0.1;

        f.vec = e.otherSolid->getPosition() – e.pos;

        otherSolid->addForce(f);

    }

};

 

...

 

opal::Simulator* sim = opal::createSimulator();

opal::Solid* missileSolid = sim->createSolid();

MissileCollisionEventHandler* handler = new MissileCollisionEventHandler();

missileSolid->setCollisionEventHandler(handler);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

There is a lot going on here, so we’ll take it one step at a time. 

 

First, OPAL_CALL is a necessary evil that tells the compiler to use the same calling convention for your function as the one OPAL uses.  Your compiler will probably complain if the calling conventions don’t match.  Just make sure you put OPAL_CALL before the name of all functions that are overridden from the base OPAL class.  This goes for all event handlers.  Second, note the use of the user data pointer.  This lets the user get access to their own application objects, in this case a Missile.  The user data pointer must have already been setup (using the Solid “setUserData” function) prior to the collision.  Finally, the CollisionEvent data structure contains useful information describing the collision, including pointers to the two Solids that collided (note the names of the pointers: “thisSolid” refers to the Solid whose event handler was just called), the position of the collision, the normal of the collision, etc.

 

The end result of this “MissileCollisionEventHandler” is that when a missile collides with another Solid, destroys the missile, displays an explosive particle effect, plays an explosion sound, and applies a force to the other Solid.  A more advanced effect would use OPAL’s VolumeSensor to find and affect all Solids within some radius of the explosion.

 

Keep in mind that this can handle collision events regardless of whether the two Solids generate physical contacts.  This allows the creation of “trigger volumes,” Solids that do not affect other Solids physically but can still generate collision events.  See the tutorial on contact groups for more info on disabling physical contact generation between two Solids.

 

 

Joint Break Event Handler

This event handler gets notified when a breakable Joint breaks.  The following example shows how to play a sound effect when a Joint breaks:

 

 

class HingeBreakEventHandler : public opal::JointBreakEventHandler

{

public:

 

    virtual void OPAL_CALL handleJointBreakEvent(Joint* joint)

    {

        AppSoundManager::playSound(WOOD_BREAK_SOUND);

    }

};

 

...

 

opal::Simulator* sim = opal::createSimulator();

opal::Joint* hingeJoint = sim->createJoint();

HingeBreakEventHandler* handler = new HingeBreakEventHandler();

hingeJoint->setJointBreakEventHandler(handler);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

As soon as the Joint breaks, it becomes disabled, and this event handler gets notified.  In this example, the event handler simply plays a sound effect when the Joint breaks.

 

 

Post Step Event Handler

Sometimes it is helpful to know exactly when a simulation step is finished.  For example, say an application uses a simulation step size of 0.01.  On a particular frame it tells the OPAL Simulator to simulate everything ahead 0.05 seconds which would internally take five simulation steps of 0.01 seconds each.  The application normally has no way of knowing when each step is finished which may be necessary for, say, an AI routine that should get updated after every simulation step.  The PostStepEventHandler solves this problem.

 

 

class AppStepEventHandler : public opal::PostStepEventHandler

{

public:

 

    AppPostStepEventHandler()

    {

        mFunctor = NULL;

    }

 

    // This example only works for void functions with no parameters.

    void registerCallback(void (*func)())

    {

        mFunctor = func;

    }

 

    virtual void OPAL_CALL handlePostStepEvent()

    {

        if (mFunctor)

        {

            mFunctor();

        }

    }

 

private:

    void (*mFunctor)();

};

 

...

 

void stepAI()

{

    AppAIManager::step();

}

 

...

 

opal::Simulator* sim = opal::createSimulator();

AppStepEventHandler* handler = new AppStepEventHandler();

handler->registerCallback(stepAI);

sim->setPostStepEventHandler(handler);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Notice that the Simulator is the object that maintains a pointer to the PostStepEventHandler.  This example gives the user’s event handler a pointer to a function to be called at the end of each simulation step.  Of course the use of callback functors like in this example is not required: you could simply perform all necessary actions within the handlePostStepEvent function.

 

OPAL is Copyright © 2004-2005 Alan Fischer, Andres Reinot, and Tyler Streeter