8 Contact and Collision

8.3 Collision meshes and compound collidables

Contact between collidable bodies is determined by finding intersections between their collision meshes. Collision meshes must be instances of PolygonalMesh, and must also be triangular, manifold, oriented, and non-self-intersecting (at least in the region of contact). The oriented requirement helps the collision detector differentiate inside from outside. Collision meshes should also be closed, if possible, although collision may sometimes work with the open meshes (such as those that arise with shell elements) under conditions described in Section 8.1.4. Collision meshes do not need to be connected; a collision mesh may be composed of separate parts.

Commonly, a collidable body has a single collision mesh which is the same as its surface mesh. However, some collidables, such as rigid bodies and FEM models, allow an application to specify a collidable mesh that is different from its surface mesh. This can be useful in situations such as the following:

  • Collisions should be enabled for only part of a collidable, perhaps because other parts are in contact due to joints or other types of attachment.

  • The collision mesh requires a different resolution than the surface mesh, possibly for computational reasons.

  • The collision mesh requires a different geometry, such as in situations where the collidable’s physical surface is sufficiently thin that it may otherwise pass through other collidables undetected (Section 8.9.2).

For a rigid body, the collision mesh is returned by its getCollisionMesh() method (see Section 8.3.4). By default, this is the same as the surface mesh returned by getSurfaceMesh(). However, the collision mesh can be changed by adding one (or more) additional mesh components to the meshes component list and setting its isCollidable property to true, usually while also setting isCollidable to false for the surface mesh (Section 3.2.9). The collision mesh returned by getCollisionMesh() is then formed from the union of all rigid body meshes for which isCollidable 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. Care should be taken to ensure that the resulting mesh is not self-intersecting in the regions that will be exposed to contact.

For FEM models, the mechanism is a little different, as discussed below in Section 8.3.2. An FEM model can have multiple collision meshes, depending on the setting of the collidable property of each of its mesh components. The ability to have multiple collision meshes permits self-collision intersection of the FEM model with itself.

8.3.1 Example: redefining a rigid body collision mesh

Figure 8.3: JointedBallCollide showing the ball at the tip of bodyA colliding with bodyB.

A model illustrating how to redefine the collision mesh for a rigid body is defined in

  artisynth.demos.tutorial.JointedBallCollide

Like JointedCollide in Section 8.1.3, this model is simply a subclass of RigidBodyJoint that overrides the build() method, adding a ball to the tip of bodyA to enable it to collide with bodyB:

1    public void build (String[] args) {
2
3       super.build (args); // build the RigidBodyJoint model
4
5       // create a ball mesh
6       PolygonalMesh ball = MeshFactory.createIcosahedralSphere (2.5, 1);
7       // translate it to the tip of bodyA, add it to bodyA, and make it blue-gray
8       ball.transform (new RigidTransform3d (5, 0, 0));
9       RigidMeshComp mcomp = bodyA.addMesh (ball);
10       RenderProps.setFaceColor (mcomp, new Color (.8f, .8f, 1f));
11
12       // disable collisions for the main surface mesh of bodyA
13       bodyA.getSurfaceMeshComp().setIsCollidable (false);
14       // enable collisions between bodyA and bodyB
15       mech.setCollisionBehavior (bodyA, bodyB, true);
16    }

The superclass build() method called at line 3 creates everything contained in RigidBodyJoint. The remaining code then alters that model: A ball mesh is created, translated to the tip of bodyA, added as an additional mesh (Section 3.2.9), and set to render as blue-gray (lines 5-10). Next, collisions are disabled for bodyA’s main surface mesh by setting its isCollidable property to false (line 13); this will ensure that the collision mesh associated with bodyA will consist solely of the ball mesh, which is necessary because the surface mesh is permanently in contact with bodyB. Lastly, collisions are enabled between bodyA and bodyB (line 15).

To run this example in ArtiSynth, select All demos > tutorial > JointedBallCollide from the Models menu. Running the model will cause bodyA to fall, pivot about the hinge joint, and collide with bodyB (Figure 8.3).

8.3.2 Compound collidables and self-collision

An FEM model is an example of a compound collidable, which may contain subcollidable descendant components which are also collidable. Compound collidables are identified by having their isCompound() method return true. For an FEM model, the subcollidables are the mesh components in its meshes list. A non-compound collidable which is not a subcollidable of some other (compound) collidable is called solitary. If A is a subcollidable of a compound collidable C, then C is an ancestor collidable of A.

Subcollidables do not need to be immediate child components of the compound collidable; they need only be descendants.

One of the main purposes of compound collidables is to enable self-collisions. While ArtiSynth does not currently support the detection of self-collisions within a single mesh, self-collision can be implemented within a compound collidable C by detecting collisions amount the (separate) meshes of its subcollidables.

Compound collidables and their subcollidables are assumed to be deformable; otherwise, subcollision would not be possible.

Figure 8.4: A collection of collidable components, where A is compound, with subcollidables A1, A2, and A3, B is solitary, and C is compound with subcollidables C1 and C2. The non-compound collidables, including the leaf nodes of the collidable trees, are also instances of CollidableBody, indicated by a double outline.

In general, an ArtiSynth model will contain a number of collidable components, some of which are compound (Figure 8.4). The non-compound collidables, including both solitary collidables and leaf nodes of a compound collidable’s tree, are also instances of the subinterface CollidableBody, which provide the collision meshes, along with other information used to compute the collision response (Section 8.3.4).

Actual collisions happen only between CollidableBodys; compound collidables are used only for determining grouping and specifying collision behaviors. The rules for determining whether two collidable bodies A and B will actually collide are as follows:

  1. 1.

    Internal (self) collisions: If A and B are both subcollidables of some compound collidable C, then A and B will collide if (a) both their collidable properties are set to either ALL or INTERNAL, and (b) an explicit collision behavior is set between A and B, or self-collision is enabled for C (as described below).

  2. 2.

    External collisions: Otherwise, if A and B are either solitary or subcollidables of different compound collidables, then they will collide if (a) both their collidable properties are set to either ALL or EXTERNAL, and (b) a specific or default collision behavior is set between them or their collidable ancestors.

As mentioned above, the subcollidables of an FEM are its mesh components (Section 6.3), each of which is a collidable implemented by FemMeshComp. When a mech component is added to an FEM model, using either addMeshComp() or one of the addMesh() methods, its collidable property is automatically set to INTERNAL, so if a different setting is required, this must be specified after the component has been added to the model.

Subject to the above conditions, self-collision can be enabled for a specific compound collidable C by calling the setCollisionBehavior() methods with collidable0 set to C and collidable1 set to either C or Collidable.SELF, as in for example:

   mech.setCollisionBehavior (C, Collidable.SELF, true, mu);
   ... OR ...
   mech.setCollisionBehavior (C, C, true, mu);

It can also be enabled, by default, for all compound collidables by calling one of the setDefaultCollisionBehavior() methods with collidable0 set to Collidable.DEFORMABLE and collidable1 set to Collidable.SELF, e.g.:

   mech.setDefaultCollisionBehavior (
      Collidable.DEFORMABLE, Collidable.SELF, true, mu);

For an example of how collision interactions can be set, refer to Figure 8.4, assume that components A, B and C are deformable, and that the collidable property for all collidables is set to ALL except for A3, where it is set to EXTERNAL (implying that A3 cannot self-collide with A1 and A2). Then if mech is the MechModel containing the collidables, the call

   mech.setDefaultCollisionBehavior (
      Collidable.DEFORMABLE, Collidable.DEFORMABLE, true, 0.2);

will enable collisions between A1, A2, and A3 and each of B, C1, and C2, and between B and C1 and C2, but not among A1, A2, and A3 or C1 and C2. The subsequent calls

   mech.setDefaultCollisionBehavior (
      Collidable.DEFORMABLE, Collidable.SELF, true, 0);
   mech.setCollisionBehavior (B, A3, false);

will enable self-collision between A1 and A2 and C1 and C2 with zero friction, and disable collision between B and A3. Finally, the calls

   mech.setCollisionBehavior (A3, C, true, 0.3);
   mech.setCollisionBehavior (A, A, false);

will enable collision between A3 and each of C1 and C2 with friction 0.3, and disable all self-collisions among A1, A2 and A3.

8.3.3 Example: FEM model with self-collision

Figure 8.5: FemSelfCollide showing collision between the left and right contact meshes (left). Without these meshes, the FEM model intersects with itself as show on the right.

A model illustrating self-collision for an FEM model is defined in

  artisynth.demos.tutorial.FemSelfCollide

It creates a simple FEM based on a partial torus that self intersects when it falls under gravity. Internal surface meshes are added to the left and right ends of the model to prevent interpenetration. The code for the build() method is listed below:

1 public class FemSelfCollide extends RootModel {
2
3    public void build (String[] args) {
4       MechModel mech = new MechModel ("mech");
5       addModel (mech);
6
7       // create FEM model based on a partial (open) torus, with an
8       // opening (gap) of angle of PI/4.
9       FemModel3d ptorus = new FemModel3d("ptorus");
10       FemFactory.createPartialHexTorus (
11          ptorus, 0.15, 0.03, 0.06, 10, 20, 2, 7*Math.PI/4);
12       // rotate the model so that the gap is at the botom
13       ptorus.transformGeometry (
14          new RigidTransform3d (0, 0, 0, 0, 3*Math.PI/8, 0));
15       // set material and particle damping
16       ptorus.setMaterial (new LinearMaterial (5e4, 0.45));
17       ptorus.setParticleDamping (1.0);
18       mech.addModel (ptorus);
19
20       // anchor the FEM by fixing the top center nodes
21       for (FemNode3d n : ptorus.getNodes()) {
22          if (Math.abs(n.getPosition().x) < 1e-15) {
23             n.setDynamic(false);
24          }
25       }
26
27       // Create sub-meshes to resist collison at the left and right ends of the
28       // open torus. At each end, create a mesh component, and use its
29       // createVolumetricSurface() method to create the mesh from the
30       // elements near the end.
31       LinkedHashSet<FemElement3d> elems =
32          new LinkedHashSet<>(); // elements for mesh bulding
33       FemMeshComp leftMesh = new FemMeshComp (ptorus, "leftMesh");
34       // elements near the left end have numbers in the range 180 - 199
35       for (int n=180; n<200; n++) {
36          elems.add (ptorus.getElementByNumber(n));
37       }
38       leftMesh.createVolumetricSurface (elems);
39       ptorus.addMeshComp (leftMesh);
40
41       FemMeshComp rightMesh = new FemMeshComp (ptorus, "rightMesh");
42       elems.clear();
43       // elements at the right end have numbers in the range 0 - 19
44       for (int n=0; n<20; n++) {
45          elems.add (ptorus.getElementByNumber(n));
46       }
47       rightMesh.createVolumetricSurface (elems);
48       ptorus.addMeshComp (rightMesh);
49
50       // Create a collision behavior and use it to enable self collisions for
51       // the FEM model. Since the model has low resolution and sharp edges, use
52       // VERTEX_EDGE_PENETRATION, which requires the AJL_CONTOUR collider type.
53       CollisionBehavior behav = new CollisionBehavior (true, 0);
54       behav.setMethod (CollisionBehavior.Method.VERTEX_EDGE_PENETRATION);
55       behav.setColliderType (CollisionManager.ColliderType.AJL_CONTOUR);
56       mech.setCollisionBehavior (ptorus, ptorus, behav);
57
58       // render properties: render the torus using element widgets
59       ptorus.setElementWidgetSize (0.8);
60       RenderProps.setFaceColor (ptorus, new Color(.4f, 1f, .6f));
61       // enable rendering of the left and right contact meshes
62       leftMesh.setSurfaceRendering (SurfaceRender.Shaded);
63       rightMesh.setSurfaceRendering (SurfaceRender.Shaded);
64       RenderProps.setFaceColor (leftMesh, new Color(.78f, .78f, 1f));
65       RenderProps.setFaceColor (rightMesh, new Color(.78f, .78f, 1f));
66    }
67 }

The model creates an FEM model based on an open torus, using a factory method in FemFactory, and rotates it so that the gap is located at the bottom (lines 7-18). The torus is then anchored by fixing the nodes located at the top-center (lines 20-25). Next, mesh components are created to enforce self-collision at the left and right gap end points (lines 27-47). First, a FemMeshComp is created (Section 6.3), and then its createVolumetricSurface() method is used to create a local surface mesh wrapping the elements specified in elems. When selecting the elements, we use the convenient fact that for this particular FEM model, the elements near the left and right ends have numbers in the ranges 180\ldots 199 and 0\ldots 19, respectively.

Once the submeshes have been added to the FEM model, we create a collision behavior and use it to enable self-collisions (lines 49-55). An explicit behavior is created so that we can enable the VERTEX_EDGE_PENETRATION contact method (Section 8.4.1), because the meshes are coarse and the additional edge-edge collisions will improve behavior; this method also requires the AJL_CONTOUR collider type. While self-collision is enabled by calling

   mech.setCollisionBehavior (ptorus, ptorus, behav);

it could also have been enabled by calling

   mech.setCollisionBehavior (ptorus, Collidable.Self, behav);
   ... OR ...
   mech.setCollisionBehavior (leftMesh, rightMesh, behav);

Note that there is no need to set the collidable property of the collision meshes since it is set to INTERNAL by default when they are added to the FEM model.

Render properties are set at lines 57-64, with the torus rendered as light green and the collision meshes as blue-gray. The torus is drawn using its elements widgets instead of its surface mesh, to prevent the latter from obscuring the collision meshes.

To run this example in ArtiSynth, select All demos > tutorial > FemSelfCollide from the Models menu. Running the model will cause the FEM model to self-collide as shown in Figure 8.5.

8.3.4 Collidable bodies

As mentioned in Section 8.3.2, non-compound collidables, including both solitary collidables and leaf nodes of a compound collidable’s tree, are also instances of CollidableBody, which provide the actual collision meshes and other information used to compute the collision response. This is done using various methods, including:

  • PolygonalMesh getCollisionMesh()

    Returns the actual surface mesh to be used for computing collisions.

  • boolean hasDistanceGrid()

    If this method returns true, the body also maintains a signed distance grid for the mesh, which can be used by the collider type SIGNED_DISTANCE (Section 8.4.1).

  • DistanceGridComp getDistanceGridComp()

    If hasDistanceGrid() returns true, this method return the distance grid.

  • double getMass()

    Returns the mass of this body (or a suitable estimate), for use in automatically computing certain collision parameters.

  • int getCollidableIndex()

    Returns the index of this collidable body within the set of all CollidableBodys in the MechModel. The index is determined by the body’s depth-first ordering within the model component hierarchy. For components within the same component list, this ordering will be determined by the order in which the components are added in the model’s build() method.

8.3.5 Nested MechModels

It is possible in ArtiSynth for one MechModel to contain other nested MechModels within its component hierarchy. This raises the question of how collisions within the nested models are controlled. The general rule for this is the following:

The collision behavior for two collidables colA and colB is determined by whatever behavior (either default or override) is specified by the lowest MechModel in the component hierarchy that contains both colA and colB.

Figure 8.6: A MechModel containing collidables B1 and B2, nested within another containing collidables A1 and A2.

For example, consider Figure 8.6 where we have a MechModel (mechB) containing collidables B1 and B2, nested within another MechModel (mechA) containing collidables A1 and A2. Then consider the following code fragment:

   mechB.setDefaultCollisionBehavior (true, 0.2);
   mechA.setCollisionBehavior (B1, Collidable.AllBodies, true, 0.0);
   mechA.setCollisionBehavior (B1, A1, false);
   mechA.setCollisionBehavior (B1, B2, false); // Error!

The first line enables default collisions within mechB (with \mu=0), controlling the interaction between B1 and B2. However, collisions are still disabled within mechA, meaning A1 and A2 will not collide either with each other or with B1 or B2. The second line enables collisions between B1 and any other body within mechA (i.e., A1 and A2), while the third line disables collisions between B1 and A1. Finally, the fourth line results in an error, since B1 and B2 are both contained within mechB and so their collision behavior cannot be controlled from mechA.

While it is not legal to specify a specific behavior for collidables contained in a MechModel from a higher level MechModel, it is legal to create collision response components for the same pair within both models. So the following code fragment would be allowed and would create response components in both mechA and mechB:

   mechB.setCollisionResponse (B1, B2);
   mechA.setCollisionResponse (B1, B2);