10. Creating a Simulation

The namespace agxSDK contains classes that bridges agx (dynamics) and agxCollide (collision). All insertion/deletion of objects such as rigid bodies, geometries, and materials should go through the main class agxSDK::Simulation to make sure that that everything is registered/executed in the correct manner.

../_images/building_simulations__agxsdk_1.png

Fig. 10.1 Structural description of a Simulation

Fig. 10.1 show the structure of a simulation with the n-ary relationships between the various classes. An agxSDK::Simulation contain one agxCollide::Space and one agx::DynamicsSystem, it also contains any number of agx::Material and any number of agxSDK::EventListeners etc.

10.1. Simulation

The class agxSDK::Simulation is the class encapsulating most aspects regarding building and executing a simulation. This class holds a reference to an agx::DynamicsSystem that is responsible for integrating physical bodies and solving constraint equations, and an agxCollide::Space which generate contacts between geometries. All entities that should be part of a running simulation, such as Geometry, Constraint, and EventListener have to be added to the simulation. There can be several instances of the class Simulation, and they will each have their own Space and DynamicsSystem.

Attention

Always add/remove objects through the agxSDK::Simulation interface, not via DynamicsSystem or Space. However Space/DynamicsSystem can be used to access objects, such as getting a vector of all Geometries etc.

10.1.1. Timeline for Simulation::stepForward()

../_images/building_simulations__agxsdk_2.png

Fig. 10.2 Timeline illustrated for a Simulation::stepForward() call. (* indicates parallelizable steps).

Fig. 10.2 show a general picture of flow of events during a call to Simulation::stepForward(). An asterisk (*) indicate that the step is possible to parallelize. User events mean that it is possible to inject user code using EventListeners. In reality, there are many, many more tasks executed, many of them parallelizable.

Note

During the call to stepForward, the simulation time will be kept unmodified, except for the call to the last() callback. This means that the simulation time \(t\) will be incremented to \(d + \delta t\) when it is time to call the StepEventListener::last() callbacks.

10.1.2. Initialization and shutdown of AGX

To properly initialize AGX, an initialization method needs to be called before creating an AGX object (rigid bodies, geometries, constraints etc.):

agx::init();

Various resources in AGX are handled by Singletons, classes that ensure that only one instance exists. AGX depends on various plugins (components etc.) which are all initialized at the call to agx::init. To properly call the destructors and shut down any threads the last call to AGX should be a call to agx::shutdown():

agx::shutdown();

The shutdown method is used to release any resources currently allocated by AGX. This includes loaded plugins, running threads, search paths (Environment) etc. New in 2.3.0.0: agx:init() can be called again after a call to agx::shutdown() to re-initialize AGX for use.

Attention

To properly shutdown any running threads, a call to agx::shutdown() should be done before the end of scope of main(). You should not register an atExit() callback and execute the call there, as this callback will be executed after the scope of main has ended. So always end your application with a call to the shutdown() method.

agx::AutoInit is a class that will upon construction call agx::init(), and upon its destruction call agx::shutdown(). This class can be used for handling init/shutdown within a scope. Hence, if an exception is thrown and not caught, the method agx::shutdown will still be called.

Just remember that any paths specified for agxIO::Environment will be lost after a call to the shutdown method, this goes for other resources too. You will need to re-initialize these to whatever value you want them to have, just as you did when you first started the application (initialized AGX).

10.1.3. Shutting down threads

Having running threads when leaving the scope of main can on the Windows platform cause hanging processes. Therefore, always make sure you either call agx::shutdown() or call the static method agx::Thread::shutdown() before end of the scope of main.

10.2. agx::DynamicsSystem

The DynamicsSystem is responsible for all rigid bodies and constraints in a simulation. It sets up a toolchain with solver, integration, etc. The DynamicsSystem stores an instance of an agx::TimeGovernor which is responsible for supplying a time step.

DynamicsSystem also has an instance agx::MergeSplit (Section 32.1) which handles the merging/splitting of rigid bodies.

10.3. agxCollide::Space

Space handles geometric overlap tests such as broad phase tests for testing bounding volume overlaps, and near phase tests which result in detailed contact information: contact point, normal and penetration depth.

10.4. Events

Events are used for adding user code into the simulation. Types of Events implemented in AGX: add/remove events, collision events, step events and gui events. Event listeners are classes that can be implemented by the user through specialization of base classes supplied by the agxSDK namespace. All listeners are registered to the Simulation class and are triggered by the system whenever appropriate. All instances of the class EventListener have the methods:

  • addNotification

  • removeNotification

These methods are called whenever an event listener is added/removed from a simulation and can be used for initialization etc. Those methods cannot be filtered away, they will always be executed.

The method EventListener::getSimulation() is used to get a reference to the Simulation that the listener is currently registered to.

../_images/building_simulations__agxsdk_3.png

Fig. 10.3 Available event listeners and methods.

10.4.1. Filter events

Event listeners can be masked/filtered, so that they only are invoked for selected events. Each sub-class of agxSDK::EventListener has a method setMask() and a number of enums specifying for which events the class will be activated For example, specifying that a ContactEventListener should only react to IMPACT and SEPARATION events can be done as follows:

agx::ref_ptr<MyContactEventListener> l = new MyContactEventListener;
unsigned int mask = agxSDK::ContactEventListener::IMPACT|
       agxSDK::ContactEventListener::SEPARATION;

// Only react to IMPACT and SEPARATION events.
l->setMask( mask );

Some event listeners have more detailed filters. For ContactEventListener, see the subchapter “Filters” below.

10.4.2. Order of execution

The order in which event listeners are executed can be controlled using a priority. This is available for ContactEventListeners and StepEventListener, but not currently for GuiEventListeners. For EventListeners with the same priority, the order should be considered undefined.

simulation->add( listenerA );
simulation->add( listenerB );

In the above example, listenerA can be executed before listenerB, but the opposite is equally true.

Attention

The order in which Contact/Step event listeners are triggered should be considered undefined when they all have the same priority. This can affect for example a ContactEventListener that removes a contact.

There is no guarantee that other ContactListeners will not see that contact.

simulation->add( listenerA, 10 ) ;
simulation->add( listenerB, 20 ) ;

In the example above, listener B will be executed before listener A. Priority is only valid within the range:

[EventManager::LOWEST_PRIORITY, EventManager::HIGHEST_PRIORITY]

10.4.3. Collision Events

During the discrete simulation of a dynamic system, geometric interference calculations are done. They are done in two different steps, one for simpler bounding volumes (currently Axis-Aligned-Bounding-Boxes, AABB) testing for overlap, a process called broad phase test.

For each pair of AABBs that overlap, a more detailed narrow phase test is performed, which can have two outcomes: intersect or disjoint. Disjoint means that even though the bounding volumes overlap, there is still no intersection between the underlying geometries. If the result is intersect, contact data such as contact points, normals and penetration depth are calculated in a class named agxCollide::GeometryContact. Each intersection is stored in a list for later use (events and simulation).

The events generated by the narrow phase test are: IMPACT, CONTACT and SEPARATION. IMPACT occurs when two geometries intersect for the first time. CONTACT occurs when two geometries that in the previous time step caused an IMPACT event to occur. SEPARATION occurs when two geometries that have intersected (and generated either an IMPACT or CONTACT event) are now disjoint.

Examples of event orders are:

IMPACT->SEPARATION->IMPACT->SEPARATION
IMPACT->CONTACT->CONTACT->SEPARATION

Collision events are caught by implementing a specialization of a class named agxSDK::ContactEventListener and overriding virtual methods with names corresponding to the events.

A ContactEventListener can also be told to listen only to a few of the above events by using the setState() method.

Table 10.1 Available virtual methods for ContactEventListener

EVENT

INTERFACE

DESCRIPTION

IMPACT

KeepContactPolicy impact( const agx::TimeStamp& agxCollide::GeometryContact *gc)

Called upon first impact between two geometries. No prior contact.

CONTACT

KeepContactPolicy contact( const agx::TimeStamp&, agxCollide::GeometryContact *)

Called when two geometries have narrow phase contact. Called after impact.

SEPARATION

void separation(const agx::TimeStamp& t, agxCollide::GeometryPair& cd);

Called when two previously overlapping geometries separates from narrow phase test.

class MyContactListener : public agxSDK::ContactEventListener
{
public:
  KeepContactPolicy impact( const agx::TimeStamp&,
                            agxCollide::GeometryContact *gc);
};

Attention

EventListeners only operate together with an agxSDK::Simulation. Explicitly calling Space::update() will NOT generate any callbacks to event listeners and might result in missing events.

10.4.3.1. Filters

A ContactEventListener is activated during contact between two geometries. The question is how to specify for which two geometries the listener should be activated?

agxSDK::ExecuteFilter is a base class which is specialized by a number of classes:

  • agxSDK::GeometryFilter - can be used to specify which Geometry or pair of Geometries should trigger a listener.

  • agxSDK::PropertyFilter (String, Int, Float, …) - can be used to specify a property or pair of properties that should trigger a listener.

  • agxSDK::RigidBodyFilter - can be used to specify which RigidBody or pair of RigidBodies should trigger a listener.

  • agxSDK::RigidBodyGeometryFilter - can be used to specify which combination of RigidBody and Geometry should trigger a listener.

  • agxSDK::CollisionGroupFilter - can be used to specify which combination of collision groups should trigger a listener.

  • Your own – by inheriting from agxSDK::ExecuteFilter and overriding some of its functions, you can specify your own criteria.

Examples of ContactEventListeners can be found in the examples/agxSDK directory. A filter is bound to the ContactEventListener:

agx::ref_ptr<MyContactListener> listener = new MyContactListener();
listener->setFilter( new agxSDK::GeometryFilter(geom1) );

simulation->addEventListener( listener ):

10.4.4. Modifying contacts

The methods impact and contact have a return type of KeepContactPolicy. This is an enum with the following values:

Table 10.2 Values for the enum KeepContactPolicy

KEEP_CONTACT

This contact will be kept and used in the contact solver. (if all other ContactEventListener also return KEEP_CONTACT for this contact)

REMOVE_CONTACT

This contact will be removed AFTER all other ContactEventListener have operated on it.

REMOVE_CONTACT_IMMEDIATELY

This contact will be removed directly after the return of this method, and no other ContactEventListener executed after this listener will see the contact.

So by using the values in Table 10.2 one can specify whether a contact should be kept or not after the callbacks has been executed.

Any data in an agxCollide::GeometryContact structure can be modified in a contact callback. If the methods impact and contact is to return REMOVE or REMOVE_IMMEDIATELY, the contact will be removed from the list of contacts.

10.4.5. Calculating relative velocity

Given that two geometries overlap in space, the result can be one or more intersection points.

The class agxCollide::GeometryContact contain all the information necessary for the solver to calculate the required impulses/forces to restore the overlap. This information can also be of use for the user of the toolkit. At a contact event, such as IMPACT, or CONTACT, the contact information is readily available as an argument to the event methods:

KeepContactPolicy impact( const agx::TimeStamp&,
            agxCollide::GeometryContact* gc);

KeepContactPolicy contact( const agx::TimeStamp&,
              agxCollide::GeometryContact* gc);

The argument gc is a pointer to a buffer containing all overlaps in the system after the last update to the collision detection system.

The actual overlap data is available as:

for( size_t i =0; i < gc->points().size(); i++)
{
  const agxCollide::ContactPoint& p = gc->points()[i];

  p.normal(); // contact normal
  p.point();  // point of contact
  p.depth();  // penetration depth
  agx::Vec3 contactVel = gc->calculateRelativeVelocity( i );
}

The last call to the method calculateRelativeVelocity(i) calculates the relative velocity between two colliding rigid bodies. The argument i specifies the index in the points vector. This method will return a velocity in world coordinates that can be used for calculating sound etc.

After the solver is done, the ContactPointBuffer will also be updated with state and force information for each contact:

const agxCollide::ContactPoint& p = gc->points()[i];
agx::Vec3 normalForce             = p.getNormalForce()();
agx::Vec3 frictionForce           = p.getTangentialForce();
agx::Bool isImpactingPoint        = p.hasState( agxCollide::ContactPoint::IMPACTING );

However, this information is not available in a contact event listener, as this event occurs before the solver is done. So to access this information, you need to get a reference to the vector of contacts directly from agxCollide::Space, or by storing a pointer to the ContactPoint which you are interested in.

Attention

It’s not valid to keep references to contact data (agxCollide::GeometryContact and agxCollide::ContactPoint) between calls to stepForward. The objects are only views into data buffers and becomes invalid when the buffers are resized during collision detection.

Below is an example of how to access the actual vector containing points from within an event listener Remember that all event listeners has a method for accessing the simulation in which they exist:

const agxCollide::GeometryContactPtrVector& contacts = simulation->getSpace()->getGeometryContacts();

10.4.6. Step Events

A step event is generated before and after a discrete step is taken in the simulation. The two events generated are PRE and POST. PRE is generated after the collision detection phase but just before a step is taken in the dynamic simulation. POST is generated directly after a step is taken in the dynamic simulation.

agxSDK::StepEventListener is a base class which can be specialized by the user by inheritance.

Table 10.3 Available StepEventListener events

EVENT

INTERFACE

DESCRIPTION

PRE_COLLIDE

void preCollide(const agx::TimeStamp&)

Called before collision detection is performed. Last chance to add Geometries/Shapes to Space.

PRE_STEP

void pre(const agx::TimeStamp&)

Called before updating the dynamics information in the system (solver, integrating velocity/position).

POST_STEP

void post(const agx::TimeStamp&)

Called after the update of dynamics information. Bodies/Geometries have updated transformations.

LAST_STEP

void last(const agx::TimeStamp&)

Called last in Simulation::stepForward(). Simulation time is now incremented to \(time + \delta t\). Operations executed in this stage will not be included into the timing for agxSDK::Simulation::stepForward().

10.4.7. GUI Events

GUI events are keyboard presses and mouse events. The Simulation class does not know of how to read of the mouse/keyboard for any specific system Instead an abstract class agxSDK::GuiEventAdapter can be specialized and implemented for a specific system which will inject appropriate events into the Simulation object An example of an implementation can be found in agxOSG::GuiEventAdapter which can be used together with OpenSceneGraph to generate GUI events.

To be able to receive GUI events a class named agxSDK::GuiEventListener is specialized and implemented. Examples of this can be found in tutorials/ tutorial_basic1.cpp

10.5. agx::Frame

The class agx::Frame is used to handle multiple coordinate systems. This class contains a transformation (AffineMatrix4x4), velocities, parent/child relations and methods for converting points and vectors between local and world coordinate systems. Parent/child relationship can be constructed with the Frame::setParent(Frame *) method. This makes it possible to build up a scene of transformations.

Many classes in AGX such as RigidBody, Geometry, Assembly, etc. use an agx::Frame internally to store transformations and velocities.

An example of how to build a structure with parent/child relationship using Frames:

using namespace agx;
FrameRef parent = new Frame;
FrameRef child = new Frame;

child->setParent(parent);

parent->setTranslate( Vec3( 1, 0, 0) );

child->getTranslate(); // Now returns (1,0,0) due to its parent

parent->setRotate( EulerAngles(0, M_PI/2, 0) ); // Rotate 90 deg around Y.

Vec3 localVelocity( 1, 0, 0 ); // Local velocity, 1 in X.
Vec3 worldVelocity = child->transformVectorToWorld( localVelocity );
// World velocity is now (0,0,-1) as it is rotated around Y 90 deg.

child->getTranslate(); // Now returns (0,0,-1) due to its parents transformation.

10.6. Assembly

An agxSDK::Assembly is a class for logically (and coordinate wise) grouping objects together. It can store a set of:

  • agx::RigidBody

  • agxCollide::Geometry

  • agx::Constraint

  • agxSDK::StepEventListener

  • agxSDK::Assembly

  • agx::Material

  • agx::ContactMaterial

This can be useful when building larger constructions where a logical grouping is needed. An Assembly also contains a Frame which holds a transformation.

Each RigidBody, Geometry and Constraint will derive the transformations specified in the Assembly’s Frame. So if an Assembly is moved (Assembly::setPosition()) all contained objects will also be moved accordingly.

Schematic example of a function creating a car, returning a pointer to an assembly containing the whole car:

agxSDK::Assembly* buildCar()
{
  agxSDK::Assembly* parent = new agxSDK::Assembly();
  agx::RigidBodyRef chassis = new agx::RigidBody();
  // ...
  parent->add(chassis);
  parent->add(new agx::Material("rubber"));
  // ...
  return parent;
}

10.6.1. Adding/Removing an Assembly

An assembly can contain various objects such as RigidBody, Geometry, StepEventListener, GuiEventListener, ContactEventListener and Constraints.

When an Assembly is added to the simulation, all its contained items will also be added to the simulation. The same rule is applied when removing an Assembly from a simulation. This can however be controlled with the second argument to the Simulation::remove method:

bool Simulation::remove(agxSDK::Assembly *assembly, bool removeAllEntries = true );

10.6.2. Derived transformation

When a RigidBody is added to an Assembly, the RigidBody derives the Assembly’s transformation/velocity through a Frame/Parent interface. This means that a transformation made on the Assembly, will affect all rigid bodies contained in the Assembly. For example:

body->getFrame()->setLocalTranslate( agx::Vec3(1,0,0) ); // Set the transformation of the rigidbody

agxSDK::AssemblyRef assembly = new agxSDK::Assembly();
assembly->add( body );

// Move the assembly up in z 1 unit.
assembly->setPosition( agx::Vec3(0,0,1) );

// The line below will be true as body will inherit the assembly's transformation
assert(body->getPosition() == agx::Vec3(1,0,1));

10.7. Collection

The class agxSDK::Collection is derived from Assembly. It has the same functionality except for the transformation. A Collection does not contain any transformation and it will not take ownership of added objects Frame. Changing the transformation of a Collection has no effect on its children.

The Collection class is useful for collecting objects when reading from files, building entities where it is important to keep previous frame/transformation hierarchies.