4 Mechanical Models II

4.6 Collision Implementation and Rendering

As mentioned in Section 4.5.4, the leaf collidables which actually provide collision interaction are also instances of CollidableBody, which provides methods returning the information needed to compute collisions and their response. Some of these methods include:

   PolygonalMesh getCollisionMesh();
   boolean hasDistanceGrid();
   DistanceGridComp getDistanceGridComp();

getCollisionMesh() returns the surface mesh which is used as the basis for collision. If hasDistanceGrid() returns true, then the body also maintains a signed distance grid for the mesh, which can be obtained using getDistanceGridComp() and is used by the collider type SIGNED_DISTANCE (Section 4.6.1).

4.6.1 Collision methods and collider types

The ArtiSynth collision mechanism works by using a collider (described further below) to find the intersections between the surface meshes of each collidable object. Information provided by the collider is then used to generate contact constraints, according to a collision method, that prevent further collision and resolve any existing interpenetration.

Collision methods are specified by collision behavior’s method property, which is an instance of CollisionBehavior.Method, as mentioned in Section 4.5.3. Some of the standard methods are:

VERTEX_PENETRATION

A contact constraint is generated for each mesh vertex that interpenetrates the other mesh, with the normal direction determined by finding the nearest point on the opposing mesh. This method is the default for collisions involving deformable bodies which have enough degrees of freedom (DOF) that the resulting number of contacts does not overconstrain the system. Using this method for collisions between rigid bodies, or low DOF deformable bodies, will generally result in an overconstrained system unless the constraints are either regularized using compliance or constraint reduction is enabled (see Section 4.6.3).

CONTOUR_REGION

Contact constraints are generated by fitting a plane to each mesh contact region and then projecting the region onto that plane. Constraints are then created from points on the perimeter of this projection, with the normal direction being given by the plane normal. This is the default method for collision between rigid bodies or low DOF deformable bodies. Trying to use this method for FEM-based deformable bodies will result in an error.

DEFAULT

Selects the method most appropriate depending on the DOFs of the colliding bodies and whether they are rigid or deformable . This is the default setting.

INACTIVE

No constraints are generated. This will result in no collision response.

The contact information used by the collision method is generated by a collider. Colliders are described by instances of CollisionManager.ColliderType and can be specified by the colliderType property in either the collision manager (as the default), or in the collision behavior for a specific pair of collidables. Since different colliders may provide different collision information, some colliders may restrict the type of collision method that may be used.

Three collider types are presently supported:

AJL_CONTOUR

A bounding-box hierarchy is used to locate all triangle intersections between the two surface meshes, and the intersection points are then connected to find the (piecewise linear) intersection contours. The surface meshes must (at present) be triangular, closed, and manifold. The contours are then used to identity the contact regions on each surface mesh, which can be used to determine interpenetrating vertices and contact area. Intersection contours and the contact constraints generated from them are shown in Figure 4.7.

TRI_INTERSECTION

A legacy method which also uses a bounding-box hierarchy to locate all triangle intersections between the two surface meshes. However, contours are not generated. Instead, contact regions are estimated by grouping the intersection points together, and penetrating vertices are computed separately using point-mesh distance queries based on the bounding-box hierarchy. This latter step requires iterating over all mesh vertices, which may be slow for large meshes.

SIGNED_DISTANCE

Uses a grid-based signed distance field on one mesh to quickly determine the penetrating vertices of the opposite mesh, along with the penetration distance and normals. This is only available for collidable pairs where at least one of the bodies maintains a signed distance grid which can be obtained using getDistanceGridComp(). Advantages include speed (often an order of magnitude faster that the colliders based on triangle intersection) and the fact that the opposite mesh does not have to be triangular, closed, or manifold. However, signed distance fields can (at present) only be computed for fixed meshes, and so at least one colliding body must be rigid. The signed distance field also does not yet yield contact region information, and so the collision method is restricted to VERTEX_PENETRATION. Contacts generated from a signed distance field are illustrated in Figure Figure 4.8.

Because the SIGNED_DISTANCE collider type currently supports only the VERTEX_PENETRATION method, its use between rigid bodies, or low DOF deformable bodies, will generally result in an overconstrained system unless the constraints are either regularized using compliance or constraint reduction is enabled. See Section 4.6.3.

\mathrm{t}=0s \mathrm{t}=0.25s \mathrm{t}=0.5s
Figure 4.7: Time sequence of contact handling between two deformable models falling under gravity, showing the intersection contours (yellow) and the contact normals (green lines).
Figure 4.8: Contacts generated by a signed distance field. A deformable ellipsoid (red, top) is colliding with a solid prism (cyan, bottom). A signed distance field for the prism (the grid points and normals of which are shown partially by the dark cyan arrows) is used to locate penetrating vertices of the ellipsoid and hence generate contact constraints (yellow lines).

4.6.2 Collision meshes and signed distance grids

As mentioned above, collidable bodies declare the method getCollisionMesh(), which returns a mesh defining the collision surface. This is either used directly, or, for SIGNED_DISTANCE collision handling, to compute a signed distance grid which is in turn used to handle collisions.

For RigidBody objects, the mesh returned by getCollisionMesh() is typically the same as the surface mesh. However, it is possible to change this, by adding additional meshes to the body and modifying mesh collidability (see Section 3.2.8). The collision mesh is then formed as the sum of all polygonal meshes in the body’s meshes list whose collidable property is true.

The sum operation used to create the RigidBody collision mesh uses addMesh(), which simply adds all vertices and faces together. While the result works correctly for collisions, it does not represent a proper CSG union operation (such as that described in Section 2.5.7) and may contain interpenetrating and overlapping features.

Collidable bodies also declare the methods hasDistanceGrid() and getDistanceGridComp(). If the former returns true, then the body has a distance grid and the latter returns a DistanceGridComp containing it. A distance grid is a regular 3D grid, with uniformly arranged vertices and cells, that is used to represent a scalar distance field, with distance values stored implicitly at each vertex and interpolated within cells. Distance grids are used for SIGNED_DISTANCE collision handling, and by default are generated on-demand, using an automatically chosen resolution and the mesh returned by getCollisionMesh() as the surface against which distances are computed.

When used for collision handling, values within a distance grid are interpolated trilinearly within each cell. This means that the effective collision surface is actually the trilinearly interpolated isosurface corresponding to a distance of 0. This surface will differ somewhat from the original surface returned by getCollisionMesh(), in a manner that depends on the grid resolution. Consequently, when using SIGNED_DISTANCE collision handling, it is important to be able to visualize the trilinear isosurface, and possibly modify it by adjusting the grid resolution. The DistanceGridComp returned by getDistanceGridComp() exports properties to facilitate both of these requirements, as described in detail in Section 4.7.

4.6.3 Overconstrained contact and regularization

An issue that can arise with the VERTEX_PENETRATION collision method (Section 4.6.1), which by default is employed for deformable bodies but can also be set for rigid bodies, is the problem of overconstrained contact, in which the number of contact constraints exceeds the number of degrees of freedom (DOF) available amongst the dynamic components for handling the contact. When this occurs, an error message will typically appear in the application’s terminal output, such as

Pardiso: num perturbed pivots=12

and the simulation itself may go unstable.

Overconstrained contact does not typically occur with regular FEM models because the number of contact constraints does not usually exceed the number of FEM nodes impacted by the collision. However, it can occur for collisions involving embedded meshes (Section 6.3.2) when the mesh has a finer resolution than the embedding FEM, or skinned meshes (Chapter 8) when the number of contacts exceeds the available DOF of the underlying master bodies.

At present there are two general ways to handle overconstrained contact:

  1. 1.

    Contact constraint reduction

  2. 2.

    Contact regularization

Constraint reduction involves having the collision manager explicitly try to reduce the number of collision constraints to match the available DOFs, and is enabled by setting the reduceConstraints property for either the collision manager or the CollisionBehavior for a particular collision pair to true. The default value of reduceConstraints is false. As with all properties, it can be set interactively in the GUI, or in code using the property’s accessor methods,

  boolean getReduceContraints()
  void setReduceContraints (boolean enable)

as illustrated by the following code fragments:

  MechModel mech;
  ...
  // enable constraint reduction for all collisions:
  mech.getCollisionManager().setReduceContraints (true);
  // enable constraint reduction for collisions between bodyA and bodyB:
  CollisionBehavior behav =
     mech.setCollisionBehavior (bodyA, bodyB, true);
  behav.setReduceContraints (true);

Contact regularization is a different technique in which contact constraints are made “soft” by adding compliance and damping terms. This means that they no longer remove DOFs from the system, and so overconstraining cannot occur, but contact penetration is no longer strictly enforced and the overall computation time can be increased.

The regularization described here is analogous to that used for joints and connectors, discussed in Section 3.3.7.

Regularization can be enabled by setting the compliance and damping properties for either the collision manager or the CollisionBehavior for a particular collision pair. Compliance is the inverse of stiffness, and so a value of 0 (the default) implies “infinitely” stiff contact constraints. Regularization is enabled whenever the compliance is set to a value greater than 0, with stiffness decreasing as compliance increases. While it is not required to set the damping property, it is usually desirable to set it to a value that approximates critical damping in order to prevent “bouncing” contact behavior. Compliance and damping can be set in the GUI by editing the properties of either the collision manager or a specific collision behavior, or set in code using the accessor methods

  double getCompliance()
  void setCompliance (double c)
  double getDamping()
  void setDamping (double d)

as is illustrated by the following code fragments:

  MechModel mech;
  double compliance;
  double damping;
  ... determine compliance and damping values ...
  // set damping and compliance for all collisions:
  mech.getCollisionManager().setCompliance (compliance);
  mech.getCollisionManager().setDamping (damping);
  // enable compliance and damping between bodyA and bodyB:
  CollisionBehavior behav =
     mech.setCollisionBehavior (bodyA, bodyB, true);
  behav.setCompliance (compliance);
  behav.setDamping (damping);

An important question is what values to choose for compliance and damping. At present, there is no automatic way to determine these, and so some experimental parameter tuning is often necessary. Compliance introduces an approximately linear stiffness into each contact constraint, with a stiffness constant K that results in a force acting along the constraint direction (i.e., the contact normal) with a magnitude f is given by

f=Kd (4.5)

where d is the contact penetration depth. Appropriate values of K will depend on the desired maximum penetration depth and other loadings acting on the body. The mass of the collidable bodies may also be relevant if one wants to control how fast the contact stabilizes. (The mass of every CollidableBody can be determined by its getMass() method.) Since K acts on a per-contact basis, the resulting total force will increase with the number of contacts.

Once K is determined, the compliance value C is simply the inverse: C=1/K. Furthermore, the damping D can then be estimated based on the desired damping ratio \zeta, using the formula

D=2\zeta\sqrt{KM} (4.6)

where M is the combined mass of the collidable bodies (or the mass of one body if the other is non-dynamic). Typically, the desired damping will be close to critical damping, for which \zeta=1.

An example that allows a user to play with contact regularization is given in Section 4.6.6.

4.6.4 Penetration tolerance and limitations

ArtiSynth’s attempt to separate colliding bodies at the end of each time step may cause a jittering behavior around the colliding area, as the surface collides, separates, and re-collides. This can usually be stabilized by maintaining a certain interpenetration distance during contact. This distance is controlled by the MechModel property penetrationTol. ArtiSynth attempts to compute a suitable default value for this property, but for some applications it may be necessary to control the value explicitly using the MechModel methods

   setPenetrationTol (double dist);
   double getPenetrationTol();

Another issue is that because ArtiSynth currently uses static collision detection, it is possible for objects that are fast enough or thin enough to completely pass through each other in one simulation step. This means that for thin objects, it is important to keep the step size small enough to prevent such undetected interpenetration.

ArtiSynth also uses a “box” friction approximation [7] to compute dry friction, instead of the polyhedralized friction cones common in the multibody dynamics literature [1, 15]. This allows for a less expensive and more robust computation at the expense of some accuracy.

4.6.5 Contact rendering

As mentioned above, CollisionBehavior also contains properties that can enable and control the rendering of artifacts associated with the contact. These include intersection contours and contact points and normals, as described below. Colors, line widths, and other graphical details are controlled by the generic render properties (Section 4.3) of the collision manager, or by render properties which can be set individually within the behavior components.

By default, contact rendering is disabled. To enable it, one must set the collision manager to be visible, which can be done using a code fragment like the following:

  RenderProps.setVisible (mechModel.getCollisionManager(), true);

Specific properties of CollisionBehavior that control contract rendering include:

drawIntersectionContours

Boolean value requesting drawing of the intersection contours between collidable meshes, using the edgeWidth and edgeColor render properties.

drawIntersectionPoints

Boolean value requesting drawing of the interpenetrating vertices on each collidable mesh, using the pointStyle, pointSize, pointRadius, and pointColor render properties.

drawContactNormals

Boolean value requesting drawing of the normals associated with each contact constraint, using the lineStyle, lineRadius, lineWidth, and lineColor render properties. The length of the normals is controlled by the contactNormalLen property of the collision manager, which will be set to an appropriate default value if not set explicitly.

drawContactForces

Boolean value requesting drawing of the forces associated with each contact constraint, using the lineStyle, lineRadius, edgeWidth, and edgeColor (or lineColor if edgeColor is null) render properties. The forces are drawn as line segments starting at each contact point and running parallel to the contact normal, with a length given by the current contact impulse value multiplied by the contactForceLenScale property of the collision manager (which has a default value of 1). The reason for using edgeWidth and edgeColor instead of lineWidth and lineColor is to allow the application to set the render properties such that both normals and forces can be visible if both are being rendered at the same time.

drawColorMap

An enum of type CollisionBehavior.ColorMapType requesting that a color map be drawn over the contact regions showing a scalar value such as penetration depth or contact force pressure. The collidable (0 or 1) onto which the color map is drawn is controlled by the colorMapCollidable property (described below). The range of values used for generating the map is controlled by the colorMapRange property, also described below. The values are mapped onto colors using the colorMap property of the collision manager.

Values of CollisionBehavior.ColorMapType include:

NONE

The color map is disabled. This is the default.

PENETRATION_DEPTH

The color map shows penetration depth of one collidable mesh with respect to the other. If one or both collidable meshes are open, then the penetration computation works more reliably if the colliderType property is set to AJL_CONTOUR (Section 4.5.3).

CONTACT_PRESSURE

The color map shows the contact pressures over the contact region. Contact pressures are determined by examining the forces at the contact constraints and then distributing these over the surrounding faces.

The color map itself is drawn as a patch formed from the collidable’s collision mesh, using faces and vertices associated with the collision region. The vertices of the patch are set to colors corresponding to the associated value (e.g., penetration depth or pressure) at that vertex, and the surrounding faces are colored appropriately. The resolution of the color map is thus determined by the resolution of the collision mesh, and so the application must ensure this is high enough to ensure proper results. If the mesh has only a few triangles (e.g., a rectangle with two triangles per face), the color interpolation may be spread over an unreasonably large area. Examples of color map usage are given in Sections 4.6.7 and 6.12.2.

colorMapCollidable

Integer value of either 0 or 1 specifying the collidable onto which color maps should be drawn when the drawColorMap property is set to a value other than NONE. 0 or 1 selects either the first or second collidable associated with the collision behavior.

colorMapRange

Composite property of the type ScalarRange that controls the range of values used for color map rendering. Sub properties include interval, which gives the value range itself, and updating, which specifies how this interval should be updated: FIXED (no updating), AUTO_EXPAND (interval is expanded as values increase or decrease), and AUTO_FIT (interval is reset to fit the values at every render step).

colorMapInterpolation

An enum of type Renderer.ColorInterpolation that explicitly specifies how colors in any rendered color map should be interpolated. RGB and HSV (the default) specify interpolation in RGB and HSV space, respectively. HSV interpolation is the default as it is generally better suited to rendering maps that are purely color-based.

Most of the above properties are also present in the CollisionManager, from which they are inherited by all behaviors that do not set them explicitly.

Generic render properties within the collision manager can be set in the same way as the visibility, using the RenderProps methods presented in Section 4.3.2:

  Renderable cm = mechModel.getCollisionManager();
  RenderProps.setEdgeWidth (cm, 2);
  RenderProps.setEdgeColor (cm, Color.Red);

As mentioned above, generic render properties can also be set individually for specific behaviors. This can be done using code fragments like this:

  CollisionBehavior behav = mechModel.getCollisionBehavior (bodA, bodB);
  RenderProps.setLineWidth (behav, 2);
  RenderProps.setLineColor (behav, Color.Blue);

To access these properties on a read-only basis, one can do

  RenderProps props = mechModel.getCollisionManager().getRenderProps();
  ... OR ...
  RenderProps props = behav.getRenderProps();

4.6.6 Example: Rendering normals and contours

A simple model illustrating contact rendering is defined in

  artisynth.demos.tutorial.BallPlateCollide

This shows a ball colliding with a plate, while rendering the resulting contact normals in red and the intersection contours in blue. This demo also allows the user to experiment with contact regularization (Section 4.6.3) by setting compliance and damping properties in a control panel.

Figure 4.9: BallPlateCollide showing contact normals (red) and collision contour (blue) of the ball colliding with the plate.

The complete source code is shown below:

1 package artisynth.demos.tutorial;
2
3 import java.awt.Color;
4
5 import artisynth.core.gui.ControlPanel;
6 import artisynth.core.mechmodels.CollisionManager;
7 import artisynth.core.mechmodels.MechModel;
8 import artisynth.core.mechmodels.RigidBody;
9 import artisynth.core.workspace.RootModel;
10 import maspack.matrix.RigidTransform3d;
11 import maspack.render.RenderProps;
12 import maspack.render.Renderer;
13
14 public class BallPlateCollide extends RootModel {
15
16    public void build (String[] args) {
17
18       // create MechModel and add to RootModel
19       MechModel mech = new MechModel ("mech");
20       addModel (mech);
21
22       // create and add the ball and plate
23       RigidBody ball = RigidBody.createIcosahedralSphere ("ball", 0.8, 0.1, 1);
24       ball.setPose (new RigidTransform3d (0, 0, 2, 0.4, 0.1, 0.1));
25       mech.addRigidBody (ball);
26       RigidBody plate = RigidBody.createBox ("plate", 5, 5, 0.4, 1);
27       plate.setDynamic (false);
28       mech.addRigidBody (plate);
29
30       // turn on collisions
31       mech.setDefaultCollisionBehavior (true, 0.20);
32
33       // make ball transparent so that contacts can be seen more clearly
34       RenderProps.setFaceStyle (ball, Renderer.FaceStyle.NONE);
35       RenderProps.setShading (ball, Renderer.Shading.NONE);
36       RenderProps.setDrawEdges (ball, true);
37       RenderProps.setEdgeColor (ball, Color.WHITE);
38
39       // enable rendering of contacts normals and contours
40       CollisionManager cm = mech.getCollisionManager();
41       RenderProps.setVisible (cm, true);
42       RenderProps.setLineWidth (cm, 3);
43       RenderProps.setLineColor (cm, Color.RED);
44       RenderProps.setEdgeWidth (cm, 3);
45       RenderProps.setEdgeColor (cm, Color.BLUE);
46       cm.setDrawContactNormals (true);
47       cm.setDrawIntersectionContours (true);
48
49       // create a control panel to allow contact regularization to be set
50       ControlPanel panel = new ControlPanel();
51       panel.addWidget (mech.getCollisionManager(), "compliance");
52       panel.addWidget (mech.getCollisionManager(), "damping");
53       addControlPanel (panel);
54    }
55 }

The build() method starts by creating and adding a MechModel in the usual way (lines 19-20). The ball and plate are both created as rigid bodies (lines 22-28), with the ball pose set so that its origin is above the plate at (0, 0, 2) and its orientation is perturbed so that it will not land on the plate symmetrically (line 24). Collisions between the ball and plate are enabled at line 31, with a friction coefficient of 0.2. To allow better visualization of the contacts, the ball is made transparent by disabling the drawing of faces, and instead enabling the drawing of edges in white with no shading (lines 33-37).

Rendering of contacts and normals is established by setting the render properties of the collision manager (lines 39-47). First, the collision manager is set visible (which it is not by default). Then lines (used to render the contact normals) and edges (used to render to intersection contour) are set to red and blue, each with a pixel width of 3. Drawing of the normals and contour is enabled at lines 46-47.

Lastly, for interactively controlling registration, a control panel is built to allow users to adjust the collision manager’s compliance and damping properties (lines 49-53).

To run this example in ArtiSynth, select All demos > tutorial > BallPlateCollide from the Models menu. When run, the ball will collide with the plate and the contact normals and collision contours will be drawn as shown in Figure 4.9.

To enable contact regularization, set the compliance to a non-zero value. A value of 0.001 (which corresponds to a contact stiffness of 1000) causes the ball to bounce considerably when it lands. To counteract this bouncing, the damping should be set to a non-zero value. Since the ball has a mass of  0.21, formula (4.6) suggests that critical damping (for which \zeta=1) can be achieved with D\approx 30. This does in fact stop the bouncing. Increasing the compliance to 0.01 results in the ball penetrating the plate by a noticeable amount.

4.6.7 Example: Rendering a color map

As described above, it is possible to use the drawColorMap property of the collision behavior to render a color map over the contact area showing a scalar value such as penetration depth or contact pressure. A simple example of this is defined in

  artisynth.demos.tutorial.PenetrationRender

which sets drawColorMap to PENETRATION_DEPTH in order to display the penetration depth of one hemispherical mesh with respect to another.

The code to render contact pressure is very similar, with drawColorMap instead set to CONTACT_PRESSURE. An example for finite elements models is shown in Section 6.12.2.

As mentioned above, proper results require that the collision mesh for the collidable on which the map is being drawn has a sufficiently high resolution.

Figure 4.10: PenetrationRender showing the penetration depth of the bottom mesh with respect to the top, with red indicating greater depth. A translation dragger fixture at the top is being used to move the top mesh around, while the penetration range and associated colors are displayed on the color bar at the right.

The complete source code is shown below:

1 package artisynth.demos.tutorial;
2
3 import java.awt.Color;
4
5 import maspack.geometry.PolygonalMesh;
6 import maspack.geometry.MeshFactory;
7 import maspack.matrix.RigidTransform3d;
8 import maspack.render.*;
9 import maspack.render.Renderer.FaceStyle;
10 import maspack.render.Renderer.Shading;
11 import artisynth.core.mechmodels.*;
12 import artisynth.core.mechmodels.CollisionManager.ColliderType;
13 import artisynth.core.mechmodels.CollisionBehavior.ColorMapType;
14 import artisynth.core.util.ScalarRange;
15 import artisynth.core.workspace.RootModel;
16 import artisynth.core.renderables.ColorBar;
17 import maspack.render.color.JetColorMap;
18
19 public class PenetrationRender extends RootModel {
20
21    // Convenience method for creating colors from [0-255] RGB values
22    private static Color createColor (int r, int g, int b) {
23       return new Color (r/255.0f, g/255.0f, b/255.0f);
24    }
25
26    private static Color CREAM = createColor (255, 255, 200);
27    private static Color GOLD = createColor (255, 150, 0);
28
29    // Creates and returns a rigid body built from a hemispherical mesh.  The
30    // body is centered at the origin, has a radius of ’rad’, and the z axis is
31    // scaled by ’zscale’.
32    RigidBody createHemiBody (
33       MechModel mech, String name, double rad, double zscale, boolean flipMesh) {
34
35       PolygonalMesh mesh = MeshFactory.createHemisphere (
36          rad, /*slices=*/20, /*levels=*/10);
37       mesh.scale (1, 1, zscale); // scale mesh in the z direction
38       if (flipMesh) {
39          // flip upside down is requested
40          mesh.transform (new RigidTransform3d (0, 0, 0, 0, 0, Math.PI));
41       }
42       RigidBody body = RigidBody.createFromMesh (
43          name, mesh, /*density=*/1000, /*scale=*/1.0);
44       mech.addRigidBody (body);
45       body.setDynamic (false);  // body is only parametrically controlled
46       return body;
47    }
48
49    // Creates and returns a ColorBar renderable object
50    public ColorBar createColorBar() {
51       ColorBar cbar = new ColorBar();
52       cbar.setName("colorBar");
53       cbar.setNumberFormat("%.2f");      // 2 decimal places
54       cbar.populateLabels(0.0, 1.0, 10); // Start with range [0,1], 10 ticks
55       cbar.setLocation(-100, 0.1, 20, 0.8);
56       cbar.setTextColor (Color.WHITE);
57       addRenderable(cbar);               // add to root model’s renderables
58       return cbar;
59    }
60
61    public void build (String[] args) {
62       MechModel mech = new MechModel ("mech");
63       addModel (mech);
64
65       // create first body and set its rendering properties
66       RigidBody body0 = createHemiBody (mech, "body0", 2, -0.5, false);
67       RenderProps.setFaceStyle (body0, FaceStyle.FRONT_AND_BACK);
68       RenderProps.setFaceColor (body0, CREAM);
69
70       // create second body and set its pose and rendering properties
71       RigidBody body1 = createHemiBody (mech, "body1", 1, 2.0, true);
72       body1.setPose (new RigidTransform3d (0, 0, 0.75));
73       RenderProps.setFaceStyle (body1, FaceStyle.NONE); // set up
74       RenderProps.setShading (body1, Shading.NONE);     // wireframe
75       RenderProps.setDrawEdges (body1, true);           // rendering
76       RenderProps.setEdgeColor (body1, GOLD);
77
78       // create and set a collision behavior between body0 and body1, and make
79       // collisions INACTIVE since we only care about graphical display
80       CollisionBehavior behav = new CollisionBehavior (true, 0);
81       behav.setMethod (CollisionBehavior.Method.INACTIVE);
82
83       behav.setDrawColorMap (ColorMapType.PENETRATION_DEPTH);
84       behav.setColorMapCollidable (1); // show penetration of mesh 0
85       behav.getColorMapRange().setUpdating (
86          ScalarRange.Updating.AUTO_FIT);
87       mech.setCollisionBehavior (body0, body1, behav);
88
89       CollisionManager cm = mech.getCollisionManager();
90       // works better with open meshes if AJL_CONTOUR is selected
91       cm.setColliderType (ColliderType.AJL_CONTOUR);
92       // set other rendering properities in the collision manager:
93       RenderProps.setVisible (cm, true);    // enable collision rendering
94       cm.setDrawIntersectionContours(true); // draw contours ...
95       RenderProps.setEdgeWidth (cm, 3);     // with a line width of 3
96       RenderProps.setEdgeColor (cm, Color.BLUE); // and a blue color
97       // create a custom color map for rendering the penetration depth
98       JetColorMap map = new JetColorMap();
99       map.setColorArray (
100          new Color[] {
101             CREAM,                       // no penetration
102             createColor (255, 204, 153),
103             createColor (255, 153, 102),
104             createColor (255, 102, 51),
105             createColor (255, 51, 0),
106             createColor (204, 0, 0),     // most penetration
107          });
108       cm.setColorMap (map);
109
110       // create a separate color bar to show depth values associated with the
111       // color map
112       ColorBar cbar = createColorBar();
113       cbar.setColorMap (map);
114    }
115
116    public void prerender(RenderList list) {
117       // In prerender, we update the color bar labels based on the updated
118       // penetration range stored in the collision behavior.
119       //
120       // Object references are obtained by name using ’findComponent’. This is
121       // more robust than using class member variables, since the latter will
122       // be lost if we save and restore this model from a file.
123       ColorBar cbar = (ColorBar)(renderables().get("colorBar"));
124       MechModel mech = (MechModel)findComponent ("models/mech");
125       RigidBody body0 = (RigidBody)mech.findComponent ("rigidBodies/body0");
126       RigidBody body1 = (RigidBody)mech.findComponent ("rigidBodies/body1");
127
128       CollisionBehavior behav = mech.getCollisionBehavior(body0, body1);
129       ScalarRange range = behav.getPenetrationDepthRange();
130       cbar.updateLabels(0, 1000*range.getUpperBound());
131       super.prerender(list); // call the regular prerender method
132    }
133 }

To improve visibility, the example uses two rigid bodies, each created from an open hemispherical mesh using the method createHemiBody() (lines 32-47). Because this example is strictly graphical, the bodies are set to be non-dynamic so that they can be moved around using the viewer’s graphical dragger fixtures (see the section “Transformer Tools” in the ArtiSynth User Interface Guide). Rendering properties for each body are set at lines 67-68 and 73-76, with the top body being rendered as a wireframe to improve visibility.

Lines 80-87 create and set a collision behavior between the two bodies with the drawColorMap property set to PENETRATION_DEPTH. Because for this example we want to show only the penetration and don’t want a collision response, we set the collision method to be Method.INACTIVE. The updating of the penetration range is also set to AUTO_FIT so that it will be recomputed at every time step. At line 91, the collider type used by the collision manager is set to ColliderType.AJL_CONTOUR, since this provides more reliable penetration calculations for open meshes. Other rendering properties are set for the collision manager at lines 93-108, including a custom color map that varies between CREAM (the color of the mesh) for no penetration and dark red for maximum penetration.

At line 112, a color bar is created and added to the scene, using the method createColorBar() (lines 50-59), to explicitly show the depth that corresponds to the different colors. The color bar is given the same color map that is used to render the depth. Since the depth range is updated every time step, it is also necessary to update the corresponding labels in the color bar. This is done by overriding the root model’s prerender() method (lines 116-133), where we obtain the collision behavior between the two bodies, and use its depth range to update the color bar labels. References to the color bar, MechModel, and bodies are obtained using the CompositeComponent methods get() and findComponent(). This is more robust that storing these references in member variables, since the latter would be lost if the model is saved to and reloaded from a file.

To run this example in ArtiSynth, select All demos > tutorial > PenetrationRender from the Models menu. When run, the meshes will collide and render the penetration depth of the bottom mesh, as shown in Figure 4.10.

When defining the color map for rendering (lines 99-108 in the example), it is recommended that the color corresponding to zero be set to the face color of the collidable mesh. This will allow the color map to blend properly to the regular mesh color.