25. AGX Util

AGXUtil is a namespace that is part of the agxPhysics shared library. AGX Util provides different utility functions and classes.

25.1. Jump Request

The method agxUtil::jumpRequest provides a way to move a collection of items, including wires, that are part of an agxSDK::Assembly.

/**
 This method will perform a "jump request" will all bodies in the assembly,
 including bodies which are part of wires. parentBody is the main body
 which will work as a parent, all other bodies will move relative to this
 rigid body.
 The transform parentBodyWorldTransform defines the new transform for
 the parent body, the target transform for parentBody.
 Also, make sure parentBody is part of the assembly collection

 \param collection - An Assembly which contain all bodies (including parentBody) and wires.
 \param parentBody - The main rigid body, all other bodies will move relative to this body
 \param parentBodyWorldTransform - The new requested transformation for the parentBody.
 \param wireOptions - options how to handle wire nodes
 \return the number of bodies that where moved, including parentBody
 */
 size_t jumpRequest( agxSDK::Assembly* collection,
                     agx::RigidBody* parentBody,
                     const agx::AffineMatrix4x4& parentBodyWorldTransform,
                     agx::UInt32 wireOptions = agx::UInt32( 0 ) );

The wireOptions allows for specifying how wire nodes should be handled when the node parent is not part of the collection:

  • Contact nodes can be detached and turned into lumped nodes instead.

  • Eye nodes can be changed into lumped nodes.

When this method executes, the items in collection will be updated.

25.2. Reconfigure Request

Reconfigure request have some similarities with a jump request, a set of items are specified and there is a reference body. But they have different purpose:

  • The jump request maintain the relative transforms of the input items while changing all transforms so that the parent/reference body gets a new specified transform.

  • The reconfigure request allow for changing the relative transforms while keeping the reference body fixed.

The reconfigure functionality can be used to change the pose of a machine with kinematic loops. For simpler structures which can be defined as serial chains one should consider if Forward kinematics can be used instead to perform the computation more efficiently.

Important

There are limitations regarding what ReconfigureRequest can handle:

Wires are not supported. A change of pose for a mechanical structure could lead to the wire coming in contact with geometry, new contact nodes added, leading to resolution changes and this does not fit well with the output format for the computed results. It is recommended to remove the wire(s), reconfigure the machine and then add wires again so they can be re-routed.

Tracks are partially supported. They are seen as ordinary bodies and constraints in the background simulation and to maintain stability, they are merged with AMOR with the sprocket. Hence requesting a new angle for the joint driving the sprocket will not work. What can be done is for example keeping the chassi fixed and rotating the base of an excavator and the tracks will still be positioned correctly on the excavator base.

The functionality is available in the class ReconfigureRequest where the main member function is computeTransforms:

/*
\param collection The bodies and constraint making up the structure that
                  should have a new configuration
\param referenceBody This body will be kept fixed when performing the movements.
\param positions Vector with constraint positions.
\param results Vector with (body,transform) pairs.
\param maxViolation Optional output of largest constraint violation found at new pose.
\param includeGeometry If geometry should be included in the copies and used
                       in the computations.
\param additionalSteps User configurable number of extra steps to take when moving
                       non-stiff structures. Increases computation time, but improves
                       results for e.g cables.
*/
bool computeTransforms(const agxSDK::Collection* collection,
                       const agx::RigidBody* referenceBody,
                       const agxUtil::ConstraintPositionVector& positions,
                       agxUtil::BodyTransformVector& results,
                       agx::Real* maxViolation = nullptr,
                       bool includeGeometry = false,
                       size_t additionalSteps = 0);

What this method does is that it creates clones of the supported items in the input collection. Then the clones are used in a background simulation that uses constraint controllers to drive the constraints to the requested positions (translational or rotational).

The resulting body transforms are then available in the results vector.

In contrast to jumpRequest, so far nothing is changed in the user Simulation. The next step is to update the bodies. This can either be done manually or by using applyTransforms:

/**
This method applies the results from computeTransforms by updating body transforms.
To maintain a stable simulation and reduce risk of artifacts after the instant
modification of transforms, additional steps are also performed.
- Hinges which had new joint positions requested in the input positions vector will
  have their winding number checked and corrected if needed.
- Constraints in the input positions vector that had Lock controllers enabled
  will have them disabled and instead motor with 0 speed enabled.
- All constraints part of the input collection will be checked to avoid an old
  position being used by a motor controllers if setLockedAtZero is enabled.

\warning This method can perform additional steps to help with maintaining a
         good state after updating the body transforms, but only for features
         supported by ReconfigureRequest that also are in the agxPhysics shared library.

         Therefore extra handling can be needed by the user even if this
         method is used. One such example is when hydraulics is used
         with PistonActuators. If the constraint connected to the piston
         have its position changed when body transforms are updated,
         then there will be a mismatch between the hydraulic cylinder and
         the body state. An example of how this can be adjusted
         by updating fluid in the piston chambers is available in the
         python tutorial tutorial_machine_reconfiguration.agxPy.
*/
void applyTransforms(agxSDK::Collection* collection,
                     const agxUtil::ConstraintPositionVector& positions,
                     agxUtil::BodyTransformVector& transforms,
                     bool zeroVelocities = true);

Changing body transforms instantaneous when the bodies are part of constraints can cause side effects when simulating forward using the new transforms.

If a constraint have an enabled motor with zero speed as well as setLockedAtZero enabled, then that controller will have an angle held internally. The new pose after reconfigure likely does no longer match that angle. Toggling the flag off and on again will update the angle.

Rotational changes for hinges can result in unexpected winding numbers due to the new pose alone can not provide information about the rotation direction. A rotation of +270 degrees or -90 degrees appear the same. applyTransforms can make sure winding numbers have the expected value for constraints part of the positions.

It is important to note that applyTransforms can not check and make all adjustments that might be needed after updating transforms. Powerline components, especially hydraulics, is something the user must handle themself. The main thing to check is that piston positions agree with the new pose. See tutorial_machine_reconfiguration.agxPy for an example of how this can be done.

25.3. Sphere-Mesh Skeletonisation

Under the agxUtil namespace lies the helper function agxUtil::skeletoniseMesh:

/**
Utilises the SphereSkeletoniser class to generate a SphereSkeleton from a given
trimesh structure.
\param vertices The vertices of the mesh
\param indices The indices of the mesh (always 3 per triangle, see agxCollide/Trimesh.h)
*/
SphereSkeleton skeletoniseMesh(const agx::Vec3Vector& vertices,
                               const agx::UInt32Vector& indices);

and a class agxUtil::SphereSkeletoniser which both allow using a trimesh to generate a skeleton of a mesh. This skeleton can then be traversed directly or processed using additional methods. It can be segmented using agxUtil::SphereSkeleton::segmentSkeleton or be processed to produce the longest path using agxUtil::getLongestContinousPath.

The main intended use of these functions are to aid the the creation of routes for agxCable and agxWire instances and an example of this can be seen in example_mesh_to_cable.cpp (here seen simplified):

auto cable = new agxCable::Cable(radius, resolution);
for (SphereSkeleton::dfs_iterator it = skeleton.begin(); !it.isEnd(); ++it)
    cable->add(new agxCable::FreeNode(it->position));
simulation->add(cable);

The quality of the skeletons produced relies heavily on the quality of the input mesh and as such it is recommended to use meshes with evenly distributed vertices along the meshes surface and to use simple shapes for the best results. Other meshes still produce results but some experimentation using the parameters available in agxUtil::SphereSkeletoniser, post-processing by hand, or using the processing functions may be necessary.

Note

AGX uses Assimp for importing meshes and by default allows splitting vertices for both normals and UV-coordinates. This behaviour can create non-manifold meshes so to ensure this doesn’t affect the skeletonisation, we advise to use the REMOVE_DUPLICATE_VERTICES flag when importing meshes for this purpose.