15. AGX Wire

The agxWire module contains numerous classes for simulating wires, ropes and chains. These classes can be used to simulate cranes, winches, pulleys etc. The wire is an implementation of a lumped element structure with dynamic resolution. The main difference between classical lumped elements, such as chains of bodies and ball socket joints (or hinges), is that the wire will adapt the resolution so that no unwanted vibrations will occur. Whenever a wire slides over a geometry (such as a box, cylinder or trimesh), it will create shape contact nodes, which will stabilize contacts even under very high tension. The Wire is stable even under very high mass ratios/tension. This is necessary to be able to model real life scenarios with wires, chains, ropes etc.

Wire-wire interaction can also be enabled (not enabled by default). The overlap test between wire-wire is performed with sweep tests (continuous collision detection) to reduce the risk for accidental missed overlaps.

15.1. Known limitations

The current limitations of the wire are:

  • A wire cannot handle torsion. However, rotation for Link’s between Wire’s can be controlled. See Section 15.21.6

15.2. agxWire::Wire

agxWire::Wire (wire) has one material for the whole wire. Available operations on a wire are: cut, reverse, merge. It can be winched using the class agxWire::WireWinchController.

Below is a table of classes in the agxWire namespace.

15.3. Modeling primitives

Table 15.1 Classes related to wire simulation

CLASS

Description

agxWire::Wire

Class for simulating a wire of a single segment type.

agxWire::WireWinchController

A winch that can spool in/out wire

agxWire::WireSimpleDrumController

A drum implementation for use with the agxWire::Wire class.

15.4. Wire nodes

Table 15.2 Node classes used for routing wires

CLASS

DESCRIPTION

agxWire::Node

Base class for a node used when routing an agxWire::Wire. A wire is forced to pass through this specified point.

agxWire::FreeNode

Nodes used for routing a wire at points in the world coordinate system.

agxWire::BodyFixedNode

Nodes used at end of wires to attach rigid bodies.

agxWire::EyeNode

A node which defines a “hole” through which the wire can slide. A Link can not slide through an EyeNode.

agxWire::ContactNode

OBSOLETE! A node which is either created by the user, or by the collision detection system to any edge where the line is overlapping geometries. Can only exist on geometries with single shapes. The shape can only be box, cylinder or some type of mesh.

agxWire::ShapeContactNode

A node which is either created by the user, or by the collision detection system to any edge where the line is overlapping geometries. No limitation on on the number of shapes for a geometry. Can exist on all shapes, expect lines, rays and WireShapes.

agxWire::ConnectingNode

A node commonly used to attach a rigid body between two agxWire::Wire. This node has additional stabilization constraints.

15.5. Rendering a wire

Table 15.3 Classes used for rendering/Storage

CLASS

DESCRIPTION

agxOSG::WireRenderer

Class for rendering an agxWire::Wire in OpenSceneGraph

agxWire::NodeConstIterator

Iterator for accessing nodes in a Wire.

15.6. Overall description of a Wire

15.6.1. Dynamic Resolution

A wire’s route is defined by its nodes. There are various nodes for different purposes. Nodes are explained later in the document.

A very important concept of a Wire is the ability to change resolution of nodes per length unit. A Wire with a certain material (radius/density) will have a certain mass. If this mass is distributed along the wire in many nodes (lumped elements), we will get small masses in each node. If a Wire with many small masses is affected by a large tension, we will get oscillations due to the large ration between tension and mass of each mass element. Therefore, the Wire will automatically remove nodes during high tension.

The maximum resolution for a Wire is specified in its constructor

agx::Real resolution = 2; // Two lumped elements per meter is the max resolution.
agxWire::WireRef wire = new agxWire::Wire(
                       0.01,         // Radius
                       resolution ); // Number of elements per length unit

or through the call:

void agxWire::setResolutionPerUnitLength( agx::Real resolutionPerUnitLength );

15.6.2. Constraint model of a Wire

A wire consists of a set of bodies and constraints. From the attributes in the agxWire::Wire class, a structure is created which contain the physical model of a wire.

../_images/agxwire__simulating_wires_1.png

Fig. 15.1 The various constraints involved in the modeling of a Wire.

Fig. 15.1 illustrates the schematic structure of a simulated Wire. The constraints involved are:

Table 15.4 Constraints involved in a wire simulation

Constraint model

Description

Distance constraint

A distance constraint will try to keep the two bodies locked at a specified distance. There are distance constraints between all nodes (bodies) in a wire.

Bend constraint

A bend constraint is a three body constraint that will try to restore the wire into a straight line. A Wire with a large radius is of course much more resistant against bending than a thin rope. A bend constraint also increases stability for a Wire, especially for Links.

Friction constraints

Whenever a Wire collides with the edge of another geometry, a shape contact node is created. Depending on the material attributes (friction coefficient) a friction constraint will try to work against moving the wire over the edge, both along the edge and orthogonal to it.

15.6.3. Geometry model of a Wire

../_images/agxwire__simulating_wires_2.png

Fig. 15.2 Geometries associated to a Wire.

To make the wire interact with the rest of the simulation, it is also consisting of geometries. The geometries cause the wire to be able to collide with other geometries. A wire consists of two types of geometries:

  1. Sphere geometries: Each lumped element (node with a rigid body) will be represented as a sphere (blue in above figure), with a radius derived from the radius of the wire/segment. This geometry collides with other geometry and creates contacts which the solver will see.

  2. Cylinder geometries: Between each lumped element (node), a cylinder (red in the above figure) will be kinematically transformed between time steps. This cylinder is used for detecting contacts between the Wire and edges on other geometries. There are no contacts generated between this cylinder and other geometries that the solver will see.

Similar to an ordinary agxCollide::Geometry, the API for the wire has a number of methods to control which other geometries can collide with the geometries in the wire:

void setEnableCollisions(const agx::RigidBody* body, bool flag);
void setEnableCollisions(const agxCollide::Geometry* geometry, bool flag);
void addGroup(unsigned id);
void addGroup(const agx::Name& name);

15.6.4. Operations on a Wire

A wire is created in a routing process described below. When a wire is created it can be merged/cut/split/reversed for more information see Section 15.11.

15.6.5. Devices used with wires

A wire can be routed together with various devices, for example a Winch and WireSimpleDrumController. A winch is a device which resembles a real life winch. It is capable of pulling in and feeding out wire just as a winch would. The mass of the wire that is pulled into a winch will not be simulated.

15.7. agxWire::Node

A Node is used for routing a wire and to attach a wire to a certain RigidBody. A Node defines a point through which a wire is forced to pass. This means that a Node attached to a RigidBody will give a 1-1 interaction between the RigidBody and the wire.

The nodes are stored in an ordered list. If a wire hasn’t been cut or detached, the first and last nodes are the ones the user used when creating the wire.

BodyFixed nodes (or lumped element nodes) are nodes that will appear at a certain fix distance from each other or a multiple of that distance. Each body fixed node is placed in the center of mass of a rigid body, which carry some of the mass of the wire. The dynamic resolution makes body fixed nodes (and their bodies) disappear and appear as the tension increases and decreases to keep the simulation numerically stable. A distance constraint is only defined between two body fixed nodes.

Note

Body fixed nodes can also be used as fixed connection points on bodies. They can be located at arbitrary relative positions to that body.

Note

If collisions between the rigid body and the wire are enabled, it is important to place the BodyFixedNode so that the whole wire is initially non-overlapping. This includes especially one extra wire radius around the attachment point. So:

agx::RigidBodyRef body = new agx::RigidBody(new agxCollide::Geometry(new agxCollide::Box(agx::Vec3(1, 1, 1))));
// ...
agxWire::WireRef wire = new agxWire::Wire(0.1, 10);
wire->add(new agxWire::BodyFixedNode(body, agx::Vec3(1 + 0.1, 0, 0))); // Note the extra 0.1

Eye nodes are nodes that can never start or end a wire. They consist of two points that both are fixed relative a rigid body which is NOT part of the wire. Their purpose is then to make the rigid body slide along the wire.

ShapeContact nodes work as eye nodes except for two major differences: they come and go as the wire collides and slips off geometries and they slide (with friction) along edges (see Section 15.8.1) of shapes.

Free nodes (could also be called temporary lumped nodes) are used for routing. They are kept as long as the wire is numerically stable. If the actual position of a free node along the wire will give us stability problems but still is considered necessary to keep to retain the orientation and most of the energy, the node may travel along the wire to a position where the tension could be handled by the solver. If the free node isn’t necessary to keep it is removed forever. When a contact between the wire and geometry is supposed to be removed, the contact node is transformed into a FreeNode instead, to preserve the wire orientation.

15.7.1. agxWire::BodyFixedNode

BodyFixedNode is a node that attaches a wire to a point relative to a RigidBody.

// Arguments: the body and an attachment point relative the body
agx::BodyFixedNodeRef bf = new agxWire::BodyFixedNode( body, Vec3( 1,0,1 ));

15.7.2. agxWire::EyeNode

EyeNode is a node that can slide along a wire. It has two friction parameters, in two separate directions. Positive is specifying friction when a body is sliding along the wire towards the end of the wire, Negative, is when a body is sliding towards the beginning of a wire. The beginning of a wire is determined as the first point of routing.

An EyeNode can also specify a point and a normal, which makes it possible to attach a RigidBody and make it rotate as the wire moves and vice versa.

agxWire::EyeNodeRef eye = new agxWire::EyeNode( slidingObject.getRigidBody(),
                                                    Vec3( 1,0,1 ),    // First point relative to body.
                                                    Vec3( -2,0,0 ) ); // Vector pointing from first point
                                                                      // To the second point

The third argument is a vector pointing out the direction and distance to the second point of attachment between the wire and the rigid body.

An EyeNode can also have a radius which determines whether a Link with a specified radius can pass through or not.

To specify the friction of the wire sliding through the eye, use the wire material:

eyeNode->getMaterial()->setFrictionCoefficient( 0.5 );

It is also possible to specify different friction in different directions:

eyeNode->getMaterial()->setFrictionCoefficient( 0.5, agxWire::NodeMaterial::NEGATIVE );
eyeNode->getMaterial()->setFrictionCoefficient( 0.5, agxWire::NodeMaterial::POSITIVE );

To have the eye node constrained to an area instead of a point the node can be attached to an agxWire::EyeNodeArea. The eye node is in this case attached to a rigid body that is constrained to another rigid body.

// Rigid body that is associated with the area in which the eye node can move
agx::RigidBodyRef eyeNodeAreaBody = new agx::RigidBody( eyeNodeAreaGeometry );
simulation->add( eyeNodeAreaBody );

agx::RigidBodyRef eyeBody = new agx::RigidBody(); // Eye node body
eyeBody->setPosition( position ); // Initial position should be inside the constraining area.
simulation->add( eyeBody );

agxWire::WireRef wire = new agxWire::Wire( wireRadius, wireResolution );
wire->add( new agxWire::FreeNode( startPosition ) );
wire->add( new agxWire::EyeNode( eyeBody, agx::Vec3() ); // Body position should be the same as node position.
wire->add(  new agxWire::FreeNode( endPosition )  );
simulation->add( wire );

// An area definition that specifies the constraining area. It is specified in body coordinates.
agxWire::AreaDefinitionRef areaDefinition = new agxWire::CircularAreaDefinition( localPoint,
                                                                                 radius,
                                                                                 eyeNodeAreaBody,
                                                                                 localDir,
                                                                                 eyeNode );

agxWire::EyeNodeAreaRef eyeNodeArea = new agxWire::eyeNodeArea( areaDefinition );
simulation->add( eyeNodeArea );

The area definitions available are agxWire::CircularAreaDefinition and agxWire::ConvexAreaDefinition.

The circular area definition is defined by a local point on the constraining body that will be center of the circle, a local direction on the constraining body and a radius.

CircularAreaDefinition::CircularAreaDefinition( const agx::Vec3& localCenter,
                                                      agx::Real radius,
                                                      agx::RigidBody* constrainingBody,
                                                const agx::Vec3& localDir,
                                                      agxWire::EyeNode* eyeNode );

The convex area definition is defined by a local point on the constraining body, a local direction on the constraining body and a set of local points that is projected onto the plane specified by the point and direction.

ConvexAreaDefinition::ConvexAreaDefinition( const Vec3& localCenter,
                                                  Vector< Vec3 > localAreaCoordinates,
                                                  RigidBody* constrainingBody,
                                            const Vec3& localDir,
                                                  EyeNode* eyeNode )

It is also possible to create a custom area by creating a class that inherits from agxWire::AreaDefinition.

An example of how an eyeNodeArea is used is available in tutorial_wire1.cpp, Tutorial 8.

15.7.3. agxWire::FreeNode

FreeNode are nodes that can be used during the routing procedure of a wire. They are nodes that will disappear when the tension increases in the wire.

wire->add( new agx::FreeNode( Vec3( 52,2,10 ) );  // Position in world coordinates

15.8. Wire Material

The class agxWire::Wire stores an instance of an agx::Material. This material is used for two things:

Bulk attributes: Density is used to calculate the internal material properties for a wire. Whenever this material is updated with new properties, the physical properties are recalculated.

Surface attributes: The SurfaceMaterial part is used during contact processing to calculate restitution and friction like any other geometry collision. It is possible at any point in time to change the material of a segment:

const agx::Real resolution = 1.0;
const agx::Real radius = 0.01;
// Create a material
agx::MaterialRef wire_material = new agx::Material("wire_material");
simulation->add( wire_material );

agxWire::WireRef wire1 = new agxWire::Wire( radius, resolution )

// Associate the new material to this Segment
wire1->setMaterial( wire_material );

// Set properties
wire_material->getWireMaterial()->setYoungsModulusBend( 1E11 );
wire_material->getSurfaceMaterial()->setRoughness( 0.2 ); // Friction

It also possible to use the wire material when creating explicit ContactMaterials:

// Create an explicit contact material for this wire_material colliding
// with another material
agx::ContactMaterialRef wire_plastic_material = new agx::ContactMaterial( wire_material, plastic_material );

// Set the attributes of the explicit material
wire_plastic_material->setFrictionCoefficient( 0.5 );

simulation->add( wire_plastic_material );

The material used for a Wire is internally modified (for point friction) which makes it unsuitable for use with other geometries in a simulation.

Attention

Avoid using the same material for an agxWire::Wire as other agxCollide::Geometries.

15.8.1. Wire Friction

A wire can interact with other geometries in two ways:

  1. A wire segment intersects a geometry and a ShapeContactNode is inserted, with an edge to move along. This when using the kinematic wire contact algorithm, which solve the dynamics, including friction, of shape contact nodes in a co-simulation.

  2. A wire collides on the surface of another geometry. Either a rigid wire segment when using the dynamic wire contact algorithm, described in Section 15.17.2. or a BodyFixedNode.

Wire friction for BodyFixedNode’s and for the DynamicWireContact is defined as friction for any two geometries colliding, see Section 11.15 about materials.

Wire friction for a ShapeContactNode is defined both along the wire and along an edge defined orthogonal to the wire direction. See Fig. 15.3 for an illustration. When the wire (iii) is in contact (v) with a geometry (iv) and sliding along its edge (vii) we find a friction force (ii) along the edge. If the normal force (i) is small enough we see sliding friction, otherwise the contact will not move. The algorithm for defining edges for a ShapeContactNode make use of the wire direction and the local curvature of the shape the wire is in contact with.

To distinguish friction along the wire and along the edge for a ShapeContactNode primary (along the wire) and secondary (along the edge) friction direction is specified to set them differently. By default the wire friction coefficients are the same as defined for the contact material for a wire-geometry contact. Creating an explicit contact material the default wire friction coefficients can be overridden by setting the wire friction coefficient.

// Create an explicit contact material for this wire_material colliding
// with another material
agx::ContactMaterialRef wire_plastic_material = new agx::ContactMaterial( wire_material, plastic_material );

// Set the wire friction coefficient along the wire
wire_plastic_material->setWireFrictionCoefficient( 0.5, ContactMaterial.PRIMARY_DIRECTION );
// Set the wire friction coefficient along the shape contact edge
wire_plastic_material->setWireFrictionCoefficient( 0.1, ContactMaterial.SECONDARY_DIRECTION);

simulation->add( wire_plastic_material );
../_images/agxwire__simulating_wires_3.png

Fig. 15.3 Illustration of wire friction.

15.9. Creating a agxWire::Wire

Below is a description of the process of creating a wire.

15.9.1. Routing a wire

A wire is placed into the world by a routing process. During the routing, winch controllers and nodes of various types are created and positioned attached to rigid bodies. The nodes are then added to the wire.

During wire routing, the route nodes added are stored locally in the wire. I.e. no communication occurs with the constraints. The wire has not been added to a simulation at this point. The routing ends when the wire is added to the simulation.

Attention

This means that adding a wire to the simulation should be done after all the routing is done.

When the first node is added, a check for a possible winch controller at the beginning of the wire is done. If one is present, and it for example does not have auto-feed enabled, we have to take care of the rolled in length and count that to the total routed length.

For all nodes added after the first one, we calculate the current route length. If the route length does not exceed the total length of the wire, we add the node to the local list of routed nodes.

15.9.1.1. Process for routing a wire

  1. Initialization

  1. Create all bodies (involved in routing)

  2. Create WinchControllers at relative position to bodies (or in the world)

  3. Create eyes, at positions relative to bodies

  4. Position bodies at their correct positions.

  1. Routing:

  1. Route beginning of wire either through a winch, a BodyFixedNode, a ConnectingNode or a FreeNode.

  2. Route through WinchController (wire->add())

  3. Route through eyes (wire->add())

  4. Route through final WinchController (wire->add()) or

  5. You always have to end the route with a WinchController, BodyFixedNode, ConnectingNode or a FreeNode.

  1. Add wire to simulation

  2. Simulate.

It is always important to remember to always position the rigid bodies/Nodes before the routing process starts. This is the same requirement as when configuring other constraints in AGX.

15.9.2. Add to simulation

When a wire is added to the simulation it will be assumed that the routing is finalized.

Starting at the end we check for winch controllers, if present, one of the following scenarios may occur:

  1. Winch at both ends, both with auto-feed enabled.

    Pulled in length for the end winch is set to zero and all superfluous wire is moved to the first winch.

  2. Winch at both ends, first with auto-feed enabled and second with auto-feed disabled.

    If this is the case, errors (warning messages, return false and ignoring the last winch during routing) occurs if the wire outside the winch is not long enough. Other than that this is a well-defined case.

  3. Winches at both ends, first with auto-feed disabled and second with auto-feed enabled.

    This means that auto-feed flag is used wrong. The purpose of the flag is to pull in all wire inside the first winch and then route while the length inside the first winch is changed during routing. If this case appears, all superfluous wire is put in the first winch, the one that does not have auto-feed enabled.

15.10. Rendering a agxWire::Wire

Rendering a wire is done using RenderIterators. Basically, a RenderIterator contains enough information to get a world position and tension of a control point in the wire. The code for agxOSG::WireRenderer can be found in <agx-dir>/agxOSG/src/WireRenderer.cpp,.h

agxWire::WireRef wire = createAWire();
simulation->add(wire);
simulation->stepForward();       // Take a time step
agx::Real refMaxTension = 1E4; // Just to get some reference for setting color/weight of a
                               // controlpoint in the spline

// Create a graphical representation of a spline in your rendering engine
Rendering::Spline *spline = new Rendering::Spline();

agxWire::RenderIterator it        = wire->getRenderBeginIterator();
const agxWire::RenderIterator end = wire->getRenderEndIterator();

// Loop through all the nodes in the wire
while ( it != end ) {
  const agxWire::Node* node = *it;

  // We can also read tension at the node to scale, control the rendering
  spline->add( node->getWorldPosition(), Real( 0.9 ) );
  ++it;
}

15.11. Wire operations

15.11.1. Cut a wire

To cut a wire there are two methods available:

// Cut a wire at the specified wire length
agxWire::Wire* agxWire::Wire::cut( const agx::Real& length , size_t minimumResolution, bool includeWinchPulledInLength = true );

// Cut the wire at the closest point one the wire
// from the specified position
agxWire::Wire* Wire::cut( const agx::Vec3& position, size_t minimumResolution );

Both methods return a pointer to the second part of the cut wire. The wire returned is already inserted into the simulation and is ready to use. The two wires does not share any information, so they can be removed independently from the simulation.

15.11.2. Merge two wires

Two wires can be merged into one using the merge method:

bool agxWire::Wire::merge( agxWire::Wire* wireToMergeWith );

An example:

agxWire::WireRef a = createAWire();
agxWire::WireRef b = createAWire();

a->merge( b );

In the above example b will be added to the end of a. This method will only work if the last node in a and the first node in b are FreeNodes.

Attention

To be able to merge two wires wire A and wire B, the last node of wire A and the first node of wire B have to be FreeNodes.

If the two end points to be merged are not at the same position, additional wire will be added to the last wire segment as a straight wire between the end of the first and beginning of the first. This means that the resulting wire length after a merge is always >= length1+length2

The wire given as an argument to merge() will be deleted from the simulation. If no references are holding it, it will be deallocated.

15.12. Controlling wire resolution

The wire has an overall resolution controlled at construction or by using the method Wire::setResolutionPerUnitLength( agx::Real resolutionPerUnitLength ); However, in some cases you might want to have different resolution along the wire. One typical scenario is where you have wire on a ship deck, and the wire goes into water for several kilometers and then up to another ship deck. It would be a waste of resources to have the same resolution along the whole wire. On deck, you might want to have higher resolution and in the water, it can be enough with a lot less nodes during simulation.

This can be controlled using the HighResolutionRange class that is available for nodes.

First you need to identify a node on the wire which acts as the reference. Usually the begin or the end of a wire but intermediate nodes, such as eye nodes, may also be reference nodes for higher resolution. The high resolution range may be at any distance before or after the reference node along the wire (not necessarily overlapping the reference node).

agx::Real default_resolution = 0.1; // One node every 10 meters
agxWire::WireRef wire = new agxWire::Wire(0.01, default_resolution);
agxWire::WireWinchControllerref winch = new agxWire::WireWinchController(winch_body, agx::Vec3(0,0,0), agx::Vec3(1,0,0));
wire->add(winch);

double start_distance = 2; // Start of the range with higher resolution
double end_distance = 10;  // End of the range with higher resolution
agx::Real new_resolution = 2; // Two nodes every meter

  // Set the high resolution range. In this range the wire will get higher resolution.
winch->getStopNode()->getHighResolutionRange()->set( start_distance, end_distance, new_resolution );

You can also have several high resolution ranges along a wire. Also, you cannot reduce the resolution, only increase.

15.13. agxWire::WireWinchController

The class agxWire::WireWinchController is an implementation of a winch that can pull in and winch out wire. It is based on a constraint (Prismatic) which means that it applies forces on a wire just as any other constraint or RigidBody in the system. It has functionality to haul in Links (which it then automatically disables when it is winched in the winch). A Winch can only be routed to the beginning or the end of a wire. A winch has a motor and a brake that can be controlled independently from each other. Remember that a motor cannot hold a load a specific position by setting the speed to zero. This is related to the problem of non-holonomic constraints that occurs with all velocity constraints. To hold a load a certain position, a holonomic (position) constraint should be used, in this case the winch brake.

For composite wires (wires with links in between) there is also the agxWire::Winch class. (see Section 15.21.3)

A winch is always attached to a RigidBody:

agxWire::WireWinchControllerRef winch = new agxWire::WireWinchController(
                              rigidBody,                              // RigidBody onto which sd is attached
                        agx::Vec3( 0, 0, -1 ),  // Position relative to rigidBody
                        agx::Vec3( 0, 0.0, -1); // Normal direction

A winch can be told to pull in any “loose” wire so that the wire is stretched after the routing:

winch->setAutoFeed( true );

Routing a wire to the winch is done by adding the winch to the wire:

// Add the winch to the wire, the second argument is the amount of wire pulled
// in to the winch at start.
wire->add( winch, wireLength );

The above call will result in that the winch will pull in all the resulting wire after it is routed.

A winch can be detached from a wire with a call to:

wire->detach(bool front);

where front has to be set to true if the winch is in the front end of the wire, false for the back.

The winching mechanism of a winch can be activated and controlled through the following API:

Set the maximum force for holding the wire (keeping velocity at 0):

void Winch::setBrakeForceRange( agx::Real brakeForce );

Set the desired winch in/out speed:

void Winch::setSpeed( agx::Real speed );

Set the maximum force by which the desired speed will be obtained:

void Winch::setForceRange( agx::Real winchForce );

The force applied by the winch motor and the winch brake can be accessed via:

auto winchForce = winch->getCurrentForce();

auto winchBrakeForce = winch->getCurrentBrakeForce();

Note

If all wire has been spooled out (agxWire::WireWinchController::getPulledInWireLength() reports zero), the forces reported will not be consistent. We recommend that you avoid spooling out all wire. Instead stop the winch before all wire is spooled out, or detach the wire from the winch.

15.14. Wire-wire interaction

Wires can also collide with other wires. This functionality has to be explicitly enabled between pairs of wires that are supposed to be able to interact. The class agxWire::WireController controls this behavior.

To enable wire-wire interaction between two instances of agxWire::Wire:

agxWire::WireRef wire1 = createWire(); // Create a wire
agxWire::WireRef wire2 = createWire(); // Create a wire

// Enable wire-wire collisions between the two wires
agxWire::WireController::instance()->setEnableCollisions(wire1, wire2, true);

For stability of wire-wire interaction, the coefficient of restitution for the contact material between the two wires should be set to 0.

auto* cm = simulation->getMaterialManager()->getOrCreateContactMaterial(wire1->getMaterial(),
                                                                        wire2->getMaterial()); // Obtain the contact material
cm->setRestitution(0);

Also, the value of YoungsModulusBend for material of the respective wires should be set to realistic values.

wire->getMaterial()->getWireMaterial()->setYoungsModulusBend(1E9);

If a chain should be simulated instead of a relatively stiff wire, this can be achieved by turning off the bend resistance altogether, instead of setting it to zero:

wire->getMaterialController()->setIsBendResistant(false);

15.15. Reading tension/forces

After a time step (a call to simulation->stepForward()), the tension involving the wire is updated and available.

The tension at each node along the wire can be read through the specified node in the wire or through a point in space or a length along the wire.

15.15.1. Get tension given distance along wire or point in the world

The tension can be queried given a point in space, or a length from start along the wire. These two methods will do a linear search to find the correct point along the wire. The data will be returned into a struct: agxWire::WireSegmentTensionData containing various data. The data will be linearly interpolated between the nodes in the wire:

../_images/agxwire__simulating_wires_4.png
// Closest point to the wire will be used
double tension = wire->getTension( agx::Vec3(1,1,1) ).getAverageRaw();

// Get the tension in the wire a certain length along the wire
double tension = wire->getTension( 123 ).getAverageSmoothed(); // 123 m from start

15.15.1.1. Get tension given node

The node tension data, agxWire::WireNodeTensionData, contains data for two segments - the segment before and the segment after the node. I.e., the tension exactly before and exactly after the node.

../_images/agxwire__simulating_wires_5.png
for ( agxWire::RenderIterator i = wire->getRenderBeginIterator(); i != wire->getRenderEndIterator(); ++i )
{
  double tension = 0;
  agxWire::WireNodeTensionData data = wire->getTension( *i ); // Get tension at the node
  tension = data.getAverageSmoothed();
}

In comparison to “get tension given distance along wire”, fetching data given node is computationally much cheaper.

15.15.1.2. Tension at nodes

The tension along a wire isn’t continuous - so beware of, and think of, what it means to read tension given nodes. Consider a closed Shark Jaw, as the two pictures below, modeled with an eye node (green sphere) that has infinite friction.

../_images/agxwire__simulating_wires_6.png ../_images/agxwire__simulating_wires_7.png

In this example, tension drops instantaneously from 504 kN to 7 kN over the eye node making it hard to interpret the exact tension value at the node. By definition (internally) the node at the end of a segment is carrying the tension data, i.e., if one ask the node what tension it feels it can either be 504 kN or 7 kN depending on in which way the wire is routed (or after reverse). It’s important to be consistent and think twice which value one expects in this special case.

Of course it becomes more convenient with our data holder, agxWire::WireNodeTensionData, which contains data for the segment before and after the node. But following the above definition, agxWire::WireNodeTensionData::getRaw(), returns the raw tension value from the segment before the node.

15.15.2. Normal force between a geometry and the wire

To get the normal force between a specified geometry and a wire you first have to locate a shape contact node in the wire which is interacting with the specific geometry, then you can query the node’s contact material for the normal force magnitude.

for ( agxWire::RenderIterator i = wire->getRenderBeginIterator(); i != wire->getRenderEndIterator(); ++i )
{
  double magnitude = 0;
  // Tension is only available at body fixed nodes
  agxWire::ShapeContactNode *cn = i.get()->getAsShapeContactContact();
  if (cn)
    magnitude = cn->getMaterial()->getNormalForceMagnitude();
}

15.16. Applying forces to a wire

If you want to simulate for example drag in water, wind, buoyancy or other external forces on a wire, you can do this with a ForceField. A ForceField will be updated inside the solve stage. This means that the callback in a user ForceField must never make any structural changes to the system. The only allowed operations is applying torque/forces onto rigid bodies, all other operations (except reading data) should be strictly avoided.

Below is a code snipped that creates a custom ForceField which applies forces to each rigid body of a wire.

// Derive from baseclass agx::ForceField
class MyForceField : public agx::ForceField
{
  public:
    MyForceField( agxWire::WireRef wire )
      : m_wire( wire ) {}

    // Virtual method called from within the solver
    virtual void updateForce( agx::DynamicsSystem* ) AGX_OVERRIDE
    {
      if ( m_wire == 0L )
        return;

      // Iterate over all nodes in the wire
      agxWire::RenderIterator it = m_wire->getRenderBeginIterator();
      while ( it != m_wire->getRenderEndIterator() ) {
        agxWire::Node* node = *it;
        // Check for internal, dynamic, mass nodes.
        if ( node->getType() == agxWire::Node::BODY_FIXED )
          node->getRigidBody()->addForce( calculateForce( node ) ); // Apply the force

        ++it;
      }
    }

  protected:

    // Some method for calculating a force
    // Could be calculating gravity, windforce, buoyancy or something else
    agx::Vec3 calculateForce( const agxWire::Node* node ) const
    {
      return agx::Vec3( agx::PI );
    }

  protected:
    agx::observer_ptr< agxWire::Wire > m_wire;
};

...
// Add the custom force field to a simulation
simulation->add( new MyForceField( wire ) );

15.17. Wire collisions and contacts

Up until this point the described agxWire::Wire contact behavior assumes that the default wire contact model is used. This model is called “default kinematic contact model”. There are two other models to consider, resulting in improved interaction fidelity.

  • Default kinematic contact model – Default model with unconditionally stable behavior

  • Dynamics wire contact model – Most dynamic/realistic behavior

15.17.1. Default kinematic contact model

One of the features of a kinematic model is that it is unconditionally stable. Wire collisions result in kinematic nodes (agxWire::ShapeContactNode) on the collided geometry.

To achieve highest performance combined with stability under high tension the default kinematic contact model is recommended.

When modeling of complex scenes with complex geometry wire collision can be computationally expensive. Collision between wires and complex geometry is not recommended when high performance is prioritized. Instead specific wire collision geometries are recommended to be used. I.e. position geometries containing one box or one cylinder to represent the complex geometry for the wire collisions (low interaction fidelity).

15.17.1.1. Wire kinematic model for AGX versions previous 2.19

For AGX versions previous 2.19 the kinematic contact model generated agxWire::ContactNodes at collision. The ContactNode is now deprecated and replaced with the ShapeContactNode which handle all shapes and has no restrictions on the number of shapes per geometry. New from 2.19 is that the wire has a class called WireShapeContactController replacing a previous class called WireContactController.

Restoring a wire stored from a version of AGX lower than 2.19 could include ContactNodes. The restored wire by default use the old wire contact controller. All contact nodes can be converted to shape contact nodes for a specific wire. For the wire to generate shape contact nodes from future interactions, the contact controller type must be changed from OLD_CONTACT_CONTROLLER to SHAPE_CONTACT_CONTROLLER.

// Saves simulation including a wire
agxStream::InputArchive& storedScene;
// Wire object to restore
agxWire::Wire* wire;

// Choose to use the shape wire contact controller,
// for shape contact node generation at collision
wire->setActiveContactControllerType(agxWire::SHAPE_CONTACT_CONTROLLER);

//convert all contact nodes to shape contact nodes
wire-replaceContactNodesWithShapeContacts();

15.17.2. Dynamic wire contact model

For a more realistic/fully dynamic behavior of wire/geometry interaction is required the Dynamic wire contact model is recommended. This model introduces new bodies, as part of the wire, wherever the wire has collided. It is therefore not expected to demonstrate the same stability under high tension as the kinematic model.

The dynamic wire contact model is enabled per geometry:

agxCollide::GeometryRef geometry = new agxCollide::Geometry();
agxWire::WireController::instance()->setEnableDynamicWireContacts(geometry, true);

This means that all wires will generate dynamic contacts when colliding with a geometry where the dynamic wire contacts are enabled. A limitation of the current implementation is that if different behavior is wanted from two different wires two different geometries must be used.

15.18. Automatic wire split for performance enhancement

agxWire::Wire support a notion of kinematic splitting during simulation. AGX Dynamics has an automatic partitioner which can analyze a system and split a simulation into subsystems (Section 31.5.2.3). This allows for the task/threading system to solve different parts in different threads. Thus improving performance in the overall system.

// Enable kinematic splitting for a wire
wire->setEnableSplitting( true );

By enabling the kinematic splitting for a wire, we allow for the system to introduce kinematic bodies along the wire, but only intermittent during one time step. Which of the bodies in the wire that are made kinematic will be determined by the splitting algorithm. To avoid artifacts, the place for splitting will be moved between time steps.

For wires under high tension it might not be possible for the splitting algorithm to introduce a kinematic body. This would potentially create unstable/unrealistic simulations.

15.19. Simulating a Pulley/Sheave/Gypsy Wheel

A sheave is a pulley with a grooved wheel for holding a belt, wire rope, or rope. The grooved wheel spins on an axle or bearing inside the frame of the block. This allows the wire or rope to move freely minimizing friction and wear on the cable. Sheaves can be used to redirect a cable or rope, lift loads, and transmit power. ( from Wikipedia)

A Gypsy wheel is usually a motorized Sheave, meaning it is used for pulling in/out chain. The chain will have infinite friction due to mechanical jamming.

Commonly a cylinder Shape is used for modeling a Sheave. However a problem is that the wire can easily slip of the cylinder, especially if the cylinder is narrow in comparison to the wire radius. To assign a sheave geometry the “Pulley” property:

// Assign the "Pulley" property to a Geometry (which contains a Cylinder shape)
sheaveGeometry->getPropertyContainer()->addPropertyBool("Pulley", true);

This will tell the Wire contact algorithms to only create contact nodes at the center-line of a cylinder.

A Gypsy wheel, which is a motorized pulley can be defined in a similar manner:

// Assign the "Gypsy" property to a Geometry (which contains a Cylinder shape)
gypsyGeometry->getPropertyContainer()->addPropertyBool("Gypsy", true);
../_images/wire_pulley.png

Fig. 15.6 Screenshot from the script wire_pulley.agxPy illustrating the use of the “Pulley” property. Here we can see that the wire is not slipping of the pulleys, even if the winch is pulling the wire sideways.

15.20. Hydro- and aerodynamics

A wire is affected by the fluid that surrounds it. A WindAndWaterController can be used to simulate the affects in water, air and other fluids, see Section 22.