34. agxSDK::MergeSplitHandler – AMOR

AGX Adaptive Model Order Reduction (AMOR) are algorithms where a simulated system adapts to a size where the dynamics is presumed to be preserved. It aims to reduce the number of degrees of freedoms given conditions of steady states. I.e., if the relative motion between two objects is zero, it’s possible to consider these two objects as one, as long as the local system isn’t affected by external interactions that may change their relative motion.

The purpose of AMOR is solely to reduce the workload of the dynamics solver and collision detection (broad phase excluded).

In the previous section we covered how to explicitly merge and split rigid bodies to/from each other, using the agx::MergedBody object. This section is about an automatic version of merging and splitting rigid bodies, where the adding, removing and managing of the agx::MergedBody objects is handled by an agxSDK::MergeSplitHandler object.

34.1. Enabling and disabling the agxSDK::MergeSplitHandler

Every instance of an agxSDK::Simulation has an instance of agxSDK::MergeSplitHandler. To enable or disable the merge split handler, simply:

simulation->getMergeSplitHandler()->setEnable( true );
simulation->getMergeSplitHandler()->setEnable( false );

Disabling the handler will split all objects merged by it.

34.2. agxSDK::MergeSplitProperties

The agxSDK::MergeSplitProperties defines if and how an object may merge and/or split to and from other objects. By default, the objects doesn’t carry an instance of the agxSDK::MergeSplitProperties, and this is interpreted as ‘merge and split of this object is disabled’.

To get an already created or to create a new instance of MergeSplitProperties for an object (RigidBody, Geometry, Constraint or Wire):

agxSDK::MergeSplitProperties* properties = agxSDK::MergeSplitHandler::getOrCreateProperties( obj );
agxAssert( obj == nullptr || properties != nullptr );

Given obj != nullptr then properties != nullptr.

With the merge split properties it’s possible to:
  • Enable and disable merge (disabled by default). When merge is enabled, it means that this object may merge to other objects.

  • Enable and disable split (disabled by default). When split is enabled, it means that this object (if merged), may split from the object it’s merged to.

  • Enable and disable merge and split (disabled by default). Convenience method to enable/disable both merge and split.

  • Manage thresholds used by the AMOR algorithms. Get, create and set thresholds used by contact, constraint and wire AMOR algorithms.

  • Manage merge ignore groups. Merge ignore groups are used to prevent merging between bodies that would otherwise have merged.

Objects that can carry an agxSDK::MergeSplitProperties instance are; agx::RigidBody, agxCollide::Geometry, agx::Constraint and agxWire::Wire:

agxSDK::MergeSplitProperties* rbProperties         = agxSDK::MergeSplitHandler::getOrCreateProperties( rb );
agxSDK::MergeSplitProperties* geometryProperties   = agxSDK::MergeSplitHandler::getOrCreateProperties( geometry );
agxSDK::MergeSplitProperties* constraintProperties = agxSDK::MergeSplitHandler::getOrCreateProperties( constraint );
agxSDK::MergeSplitProperties* wireProperties       = agxSDK::MergeSplitHandler::getOrCreateProperties( wire );

To check if an objects has properties or to inspect the current state:

const agxSDK::MergeSplitProperties* rbProperties         = agxSDK::MergeSplitHandler::getProperties( rb );
const agxSDK::MergeSplitProperties* geometryProperties   = agxSDK::MergeSplitHandler::getProperties( geometry );
const agxSDK::MergeSplitProperties* constraintProperties = agxSDK::MergeSplitHandler::getProperties( constraint );
const agxSDK::MergeSplitProperties* wireProperties       = agxSDK::MergeSplitHandler::getProperties( wire );

34.2.1. Properties carried by agxCollide::Geometry or its parent – agx::RigidBody

Since the context of AMOR is ‘bodies’, the properties for agxCollide::Geometry behaves a bit different. It is, as shown above, possible to create and change the merge split properties of a geometry object making it possible to, for example, have merge enabled/disabled in different parts of a rigid body.

A geometry inherits the merge split properties of its parent rigid body. Assume we’ve created merge split properties for the rigid body and enabled merge:

agx::RigidBodyRef rb = new agx::RigidBody();
agxCollide::GeometryRef geometry = new agxCollide::Geometry( new agxCollide::Sphere( 0.5 ) );
rb->add( geometry );
...
// Create merge split properties and enable merge for 'rb'.
agxSDK::MergeSplitProperties* rbProperties = agxSDK::MergeSplitHandler::getOrCreateProperties( rb );
rbProperties->setEnableMerge( true );

Now, for geometries, the semantics of the getProperties method differs from the rigid body, constraint and wire versions in two ways:

  1. There’s a mutable version.

  2. If the geometry hasn’t got any merge split properties (i.e., getOrCreateMergeSplitProperties hasn’t been called), the merge split properties of the parent rigid body will be returned (if created).

// 1.
agxSDK::MergeSplitProperties* geometryProperties = agxSDK::MergeSplitHandler::getProperties( geometry );
// 2.
agxAssert( geometryProperties == agxSDK::MergeSplitHandler::getProperties( rb ) );

Also, in the call to getOrCreateProperties, given a geometry, the merge split properties (if created) of the parent rigid body (if present) will be cloned. Continuing the example from above (remember merge is set to be enabled for rb):

agxSDK::MergeSplitProperties* newProperties = agxSDK::MergeSplitHandler::getOrCreateProperties( geometry );
// We should get a new instance of the merge split properties.
agxAssert( newProperties != geometryProperties );
// The new properties should be a clone of 'rbProperties'.
agxAssert( newProperties->getEnableMerge() == rbProperties->getEnableMerge() );

34.3. Merge Split Thresholds

Similar to agxSDK::MergeSplitProperties, each object may carry a set of parameters/thresholds that controls the behavior when merging and splitting the object. The global (simulation specific) merge split thresholds are used by default.

34.3.1. Global (simulation specific) merge split thresholds

The agxSDK::MergeSplitHandler has the default thresholds used for objects without explicitly created thresholds:

auto globalContactThresholds = simulation->getMergeSplitHandler()->getGlobalContactThresholds();
// Set global contact thresholds for all objects without explicitly created thresholds.
configureGlobalContactThresholds( globalContactThresholds );

34.4. Object specific merge split thresholds

All objects that may have agxSDK::MergeSplitProperties may have a list of thresholds. E.g., agx::RigidBody, agxCollide::Geometry, agxWire::Wire and agx::Constraint. The thresholds may be accessed, created and removed via the merge split properties API.

There are two ways to assign object specific thresholds:

auto rbProperties = agxSDK::MergeSplitHandler::getOrCreateProperties( rb );
// By default the thresholds should be null meaning global thresholds are used.
agxAssert( rbProperties->getContactThresholds() == nullptr );
auto explicitContactThresholds = new agxSDK::GeometryContactMergeSplitThresholds();
rbProperties->setContactThresholds( explicitContactThresholds );
auto rbProperties = agxSDK::MergeSplitHandler::getOrCreateProperties( rb );
// By default the thresholds should be null meaning global thresholds are used.
agxAssert( rbProperties->getContactThresholds() == nullptr );
auto explicitContactThresholds = rbProperties->getOrCreateContactThresholds();

To remove the thresholds and go back to the global default, simply assign null:

rbProperties->setContactThresholds( nullptr );

The explicit thresholds instance may be shared arbitrarily:

auto explicitContactThresholds = new agxSDK::GeometryContactMergeSplitThresholds();
auto rbProperties = agxSDK::MergeSplitHandler::getOrCreateProperties( rb );
auto wireProperties = agxSDK::MergeSplitHandler::getOrCreateProperties( wire );
auto geometryProperties = agxSDK::MergeSplitHandler::getOrCreateProperties( geometry );

rbProperties->setContactThresholds( explicitContactThresholds );
wireProperties->setContactThresholds( explicitContactThresholds );
geometryProperties->setContactThresholds( explicitContactThresholds );

34.5. Wire merge split thresholds

The wire merge split properties and thresholds are shared with all objects created/owned by the wire instance. There are currently two occasions with predefined behaviors:

  1. Cutting a wire - When cutting a wire the properties and thresholds for that wire are cloned, i.e., new instances of the properties and thresholds are created. The newly created wire will have identical values as before but when one, e.g., changes thresholds of the original wire it won’t affect the part that has been cut from the original one.

  2. Merging wires - When merging two wires wire1->merge( wire2 ) the properties and thresholds of wire1 will be used for the whole wire.

34.6. Merge conditions

Two objects may merge when the objects involved has merge split properties and property merge enabled.

agxSDK::MergeSplitHandler::getOrCreateProperties( rb1 )->setEnableMerge( true );
agxSDK::MergeSplitHandler::getOrCreateProperties( rb2 )->setEnableMerge( true );

34.6.1. Resting contact

The contacts points carries three directions (an orthonormal basis), namely the normal N, the tangent U and the tangent V directions. U is the primary friction direction and V the secondary friction direction. Given two rigid bodies

\(rb_1={p_1^{cm},v_1,\omega_1 },\)

\(rb_2={p_2^{cm},v_2,\omega_2 },\)

where \(p_i^{cm}\) is center of mass position, \(v_i\) the linear velocity and \(\omega_i\) the angular velocity. The speed \(s_d\) along a given direction \(d\) at point \(p_d\) is given by

\(s_d=d*[v_1+ \omega_1\times(p_d-p_1^{cm} )-[v_2+\omega_2\times(p_d-p_2^{cm} )]].\)

Given the speeds \(s_N\), \(s_U\) and \(s_V\) along each direction of the contact points \(p_i\), the two objects may merge if:

\(\underset{p_i}{\max} \mid s_N \mid \le \epsilon_N\)

\(\underset{p_i}{\max} \mid s_U \mid \le \epsilon_{UV}\)

\(\underset{p_i}{\max} \mid s_V \mid \le \epsilon_{UV}\)

Where \(\epsilon_N\) is called MAX_RELATIVE_NORMAL_SPEED and \(\epsilon_{UV}\) MAX_RELATIVE_TANGENT_SPEED. Since the conditions above recovers the rolling condition there’s a third condition regarding rolling,

\(\parallel \omega_1 - \omega_2 \parallel \le \epsilon_\omega\)

and \(\epsilon_\omega\) is named MAX_ROLLING_SPEED.

auto contactThresholds = objProperties->getOrCreateContactThresholds();

// Assign maximum speed along a contact normal for a contact
// to be considered resting. Default: 0.01.
contactThresholds->setMaxRelativeNormalSpeed( nS );

// Assign maximum (sliding) speed along a contact tangent
// for a contact to be considered resting. Default 0.01.
contactThresholds->setMaxRelativeTangentSpeed( tS );

// Assign maximum (rolling) speed for a contact to be considered
// resting. Default 0.01.
contactThresholds->setMaxRollingSpeed( rS );

When two objects merge due to a resting contact state, the current contact data (such as contact point, normal, normal- and friction forces) is stored and is used when the objects splits and to determine if they should split due to external interactions.

34.6.2. Constraint in a steady state

Similar to contact points, constraints keeps track of the speeds at the anchor point of the constraint. Since constraints (like Hinge, Prismatic, LockJoint etc.) are persistent on a different level compared to contacts, it’s possible to include ‘time’ dependent data. It’s not time in seconds, rather “this constraint has been under constant load for some time - merge!”. The ‘time’ concept is introduced by using an Exponential Moving Average (EMA) operation on the relative speeds.

Given a statistic S and an i’th observation \(O_i\) the i’th EMA statistic is given by

\(S_i= \alpha O_i + ( 1 - \alpha ) S_{i-1}\)

where \(0 \le \alpha \le 1\) is a smoothing factor, controlling the sensitivity of the new observations. When \(\alpha\) is small, the relative speeds has to be small for a (much) longer time than when \(\alpha\) is close to one.

\(\alpha\) isn’t exposed to be manipulated by the user. Instead the more intuitive threshold of maximum relative speed may be changed to make the constrained system merge more or less aggressively.

auto constraintThresholds = objProperties->getOrCreateContactThresholds();

// Assign maximum relative speed between the constrained objects
// for the system to be considered at rest. I.e., when the relative
// motion between the objects is less than this threshold, the
// objects may merge. Default: 0.005.
constraintThresholds->setMaxRelativeSpeed( maxRelSpeed );

34.6.3. Splitting Wires

Wires contains bodies, geometries and constraints so when a wire object is merging, the specific objects state and properties are checked. E.g., when a mass node is in contact with another object, the contact thresholds and algorithms are used to determine if the node should be merged. Similar for the wire attachments - the constraint thresholds and algorithms are used.

auto wireProperties = agxSDK::MergeSplitHandler::getOrCreateProperties( wire );

// Thresholds controlling the wire attachments.
auto wireAttachmentsThresholds = wireProperties->getOrCreateConstraintThresholds();

// Thresholds controlling the merging and splitting of
// mass/lumped nodes relative other objects.
auto wireContactThresholds = wireProperties->getOrCreateContactThresholds();

The wires has its own thresholds as well but they are currently classified as split thresholds.

34.6.4. Merge ignore filters

A merge ignore filter is used to prevent merges between bodies that would otherwise have merged. The filtering is done based on groups which are added to MergeSplitProperties. A group is specified by either a group ID, which is an agx::UInt32, or a group name:

agx::UInt32 groupById = agx::UInt32(4);
agx::Name groupByName = "group";
properties->addGroup(groupById);
properties->addGroup(groupByName);

A filter is a pair of such groups and is registered with the MergeSplitHandler:

simulation->getMergeSplitHandler()->setEnableMergePair(groupById, groupByName, false);

After this the MergeSplitHandler will not automatically merge any pair of bodies where one body contains the group groupById and the other contains the group groupByName. Constraints with merge split enabled is a special case. In this case the constraint’s MergeSplitProperties takes precedence and its set of groups is used when comparing against the registered filters.

The filters are checked only for the groups of the two bodies that are currently being considered for merging. If either body has already been merged with something else then there may be other merge ignore group within that merged body, but those are not considered. This means that two bodies for which there is a merge ignore filter may still become part of the same merged body if they both merge with some third body against which neither of the two has a merge ignore filter.

Filters cannot be used to enable merging between pairs of bodies, it is an ignore filter only. Passing true to setEnableMergePair will only remove the ignore filter for the given pair and restore the default behavior.

Named groups are given an ID as well. In order to separate user specified group IDs from IDs generated from group names, a user specified group ID must be less than MergeIgnoreFilter::MAX_UNAMED_GROUP_ID. The collection of IDs return by MergeSplitProperties::getGroupIds will include both user specified group IDs and the group IDs generated from the named groups added to the MergeSplitProperties. The ID for a named group can be added only by adding the name, not by adding the ID directly. However, removal based on named group ID is allowed and results in both the ID and the name being removed.

The relationship between merge ignore groups on geometries and the owning rigid body is the same as for any MergeSplitProperties property: the one in the geometry, if any, takes precedence over the one in the rigid body.

34.7. Split conditions

An object may split, i.e., leave a merged state, if it has merge split properties and property split is enabled.

agxSDK::MergeSplitHandler::getOrCreateProperties( rb )->setEnableSplit( true );

34.7.1. Impacting contact

There are two different concepts to choose from:
  • Physical impact

  • Logical impact

The logical impact is determined by the state of the contact. I.e., if the state were “no contact” and the geometries are overlapping, the state becomes “impact”. It’s at this state the agxSDK::ContactEventListener::impact callbacks are executed.

The physical impact condition is tied to a speed threshold, preventing objects from splitting too often.

The logical impact concept is disabled by default. If enabled, the merged object will split when the state of the contact is “impact”. If the state is different from “impact” the physical impact approach is testing the contact

The impact approach uses the relative speed \(s_N\) (from the Resting contact section), considering the sign of the speed as well:

\(\underset{p_i}{\max} s_N \le - \epsilon_{impact}\)

Note that \(s_N\) is positive when the objects are separating and negative when they’re approaching each other. The latter is the one we’re catching and \(\epsilon_{impact}\ge0\) is a threshold called MAX_IMPACT_SPEED.

auto contactThresholds = objProperties->getOrCreateContactThresholds();

// Assign maximum impact speed (along a contact normal) a merged
// object can resist without being split. Default 0.01.
contactThresholds->setMaxImpactSpeed( value );

// True to split when geometry contact state is agxCollide::GeometryContact::IMPACT_STATE,
// i.e., the first time the objects collide. Default is false and "max impact speed" will
// be used instead.
contactThresholds->setSplitOnLogicalImpact( flag );
// Disable physical impacts completely when using logical impacts.
// This is optional and it's valid to combine the two.
if ( flag )
  contactThresholds->setMaxImpactSpeed( agx::Infinity );

34.7.2. Constraints

34.7.2.1. Changing state

Constraints can split bodies when the internal state of the constraint has been changed in a way that the dynamics may be affected. Currently, this functionality is focused to the secondary constraints/controllers and the constraint will split the bodies involved when:

  • The motor is enabled with speed different from zero.

  • The position of the lock has been changed (i.e., by calling constraint->getLock1D()->setPosition( newPosition )).

  • The range of the range controller has been changed.

  • When a controller state goes from inactive to active, e.g., when a range is hit or a lock is enabled.

It’s, unfortunately, often not enough to just split the two bodies in the constraint. Consider an articulated, hydraulic crane where the whole structure is merged. To rise the boom the prismatic motor is activated, but nothing will move since it’s only the hydraulic cylinder and piston that are free to move.

../_images/agxsdk__mergesplithandler_1.png

Fig. 34.1 Articulated crane where a prismatic motor is used to rise the boom.

What happens is that the split algorithm first splits the two bodies directly involved in the constraint. The structure is now in a “jammed state” where it still cannot move. The split algorithm continues to traverse the merged structure, splitting all constrained bodies it finds. The split-traversal stops when it reaches a body that may not be split or when the edges connecting two bodies aren’t related to constraints (i.e., different from agx::MergedBody::BinaryConstraintEdgeInteraction).

If, for example, hundreds of these cranes were to stand on a static ground or a dynamic ship, it’s important that the ground or the ship doesn’t have merge split property split enabled. In general there aren’t any reasons to have that property enabled for “parent” objects.

34.7.2.2. Applying forces

When a constraint is applying forces on a merged object, it may split that object if that object has merge split property split enabled and is merged due to resting contacts.

Mentioned in the Resting contact section – contact and interaction data is saved when two objects merge. This data can be used to approximately determine “how much” external force is needed for the object to start to move, and we can call it the strength of a contact edge.

When the force exceeds the contact edge strength, the edge can be considered removed and if all edges are removed, the objects splits.

../_images/agxsdk__mergesplithandler_2.png

There are some thresholds in the contact thresholds that controls the strength of a merged contact. In general contacts splits due to impacting objects but when a constraint is applying forces it’s possible to add some adhesiveness to the merged objects - preventing them from splitting at the default moment.

The merged contact adhesive forces are given in unit of force:

auto contactThresholds = objProperties->getOrCreateContactThresholds();

// Adhesive force in the normal directions preventing the object to split (if > 0)
// when the object is subject to external interactions (e.g., constraints).
// Default: 0.0
contactThresholds->setNormalAdhesion( value );

// Adhesive force in the tangential directions preventing the object to split (if > 0)
// when the object is subject to external interactions (e.g., constraints).
// Default: 0.0
contactThresholds->setTangentialAdhesion( value );

34.7.2.3. Split when applying external forces

As mentioned in the previous section, only external forces from constraints may split merged objects. I.e., rb->addForce( largeForce ); will by default not split rb given no constraints involved. Disabled by default, but possible to enable, is a flag called may split in gravity field, meaning monitor this objects added external forces and interpret them in the same way as if a constraint was interacting with it.

auto contactThresholds = objProperties->getOrCreateContactThresholds();

// Check split given external forces for all objects merged (i.e., rb->getForce()
// the sum of rb->addForce(), including the gravity force). Performance warning,
// disabled by default.
contactThresholds->setMaySplitInGravityField( flag );

Note

Use this feature with as few objects as possible (if needed to begin with), since merged objects with this feature enabled are going through split tests each time step.

34.7.3. Merging Wires

Wire nodes merge when the contact between the wire node and another object is considered resting (thresholds in agxSDK::GeometryContactMergeSplitThresholds). When the contact is in a resting state we store a value of the current tension round the node, giving a measure of how the wire is looking before the merge, then we merge the node. Due to state changes in the simulation, the tension may change, resulting in a split of the wire node.

34.7.3.1. Merge tension scale

The merge tension scale threshold (default: 1.0) says something about how much the tension must change before the wire node will split. It’s a scale of the stored merge tension meaning value < 1 it’s more likely, and > 1 less likely for the wire node to split. For system with (locally) low tension but large objects interacting with them, this value could (for example) be round 100. I.e., when the wire/chain should split, the tension is likely to be 100 times larger than when it merged.

auto wireThresholds = wireProperties->getOrCreateWireThresholds();

// When a node is merged the tension is stored and monitored
// to perform split. This threshold scales the merge tension
// making split more likely when < 1 and less likely > 1. When
// this scale is 0 the only force keeping the node merged is
// from the contact - which in most cases isn't enough.
// Default: 1.0
wireThresholds->setMergeTensionScale( value );

34.7.3.2. Split propagation decay scale

The split propagation decay scale threshold (default: 1.0) says something about how far splits, due to external forces, propagates along a merged wire segment.

When a node splits, x amount of energy is consumed, and this threshold scales this energy consumption. So if this threshold is 0.0, all nodes will feel the same interaction forces as the first/last node did (probably splitting the whole wire when one splits). When this threshold is 1.0, the external interactions are receiving a measure of how heavy the merged wire is to pull/push, but not much more. When you’re experiencing undesirable splits back and forth along the wire, increase this value slightly (1.0 - 20.0). To completely disable split propagation, set it to some value >> 1 (1.0E4 on the safe side and infinity to completely disable).

auto wireThresholds = wireProperties->getOrCreateWireThresholds();

// When external forces are acting on a partially merged wire,
// the force will propagate and split several nodes at once.
// This threshold controls the amout of force (of the total
// external force) that is used to split each node. If this
// value is high (> 1), the force will not propagate 'too'
// long, keeping the wire merged. Default: 1.0.
wireThresholds->setForcePropagationDecayScale( value );

34.7.4. Split on separation

There are cases where bodies remain merged after a contact that held the bodies in place disappears. Some of those cases can be handled by enabling splitting on separation events.

simulation->getMergeSplitHandler()->setEnableSplitOnSeparation(true);

The effect of split on separation is that when two bodies separate then they are split from their respective agx::MergedBody, if any. A side effect of this more aggressing splitting is that it becomes more difficult to form stable piles of merged bodies.

34.8. agx::MergedBody with agxSDK::MergeSplitHandler

As mentioned earlier, the agxSDK::MergeSplitHandler manages agx::MergedBody objects. If the agxSDK::MergeSplitHandler encounter a merged rigid body, but the rigid body wasn’t merged by the handler – the rigid body is ignored.

This enables the possibility for the user to manage their own merged bodies. It can, for example, be used to initialize big scenes in a merged state.

For example – create a pile of boxes, enable merge and split and merge them in an agx::MergedBody:

// Create and add a merged body.
agx::MergedBodyRef mergedBody = new agx::MergedBody();
simulation->add( mergedBody );

agx::RigidBodyRef prevBox = nullptr;
for ( agx::UInt i = 0; i < numBoxes; ++i ) {
  // Create, position and add the box to the simulation.
  agx::RigidBodyRef box = createBoxInPile( i, simulation );
  // Enable merge+split for the box.
  agxSDK::MergeSplitHandler::getOrCreateProperties( box )->setEnableMergeSplit( true );
  // Merge with previous box.
  if ( prevBox != nullptr )
    mergedBody->add( new agx::MergedBody::ContactGeneratorEdgeInteraction( prevBox, box ) );
  prevBox = box;
}

Running this simulation, given a ground object with property merge enabled, the set of merged boxes will interact with the ground object, but not merge nor split.

For the agxSDK::MergeSplitHandler to be able to merge and/or split these explicitly merged objects, their agx::MergedBody has to be registered:

// Register 'mergedBody' to the handler. The objects continues to
// be merged after this call.
simulation->getMergeSplitHandler()->registerMergedBody( mergedBody );

Note: After an agx::MergedBody has been registered to the agxSDK::MergeSplitHandler, it’s undefined behavior to add/remove edge interactions/bodies from the agx::MergedBody object.