7 Muscle Wrapping and Via Points

7.3 General Surfaces and Distance Grids

As mentioned in Section 7.2, wrapping around general mesh geometries is implemented using a quadratically interpolated signed distance grid. By default, for a rigid body, this grid is generated automatically from the body’s collision mesh (as returned by getCollisionMesh(); see Section 4.6.2).

Using a distance grid allows very efficient collision handling between the body and the wrap segment knots. However, it also means that the true wrapping surface is not actually the collision mesh itself, but instead the zero-valued isosurface associated with quadratic grid interpolation. Well-behaved wrapping behavior requires that this isosurface be smooth and free of sharp edges, so that knot motions remain relatively smooth as they move across it. Quadratic interpolation helps with this, which is the reason for employing it. Otherwise, one should try to ensure that (a) the collision mesh from which the grid is generated is itself smooth and free of sharp edges, and (b) the grid has sufficient resolution to not introduce discretization artifacts.

Muscle wrapping is often performed around structures such as bones, for which the representing surface mesh is often insufficiently smooth (especially if segmented from medical image data). In some cases, the distance grid’s quadratic interpolation may provide sufficient smoothing on its own; to determine this, one should examine the quadratic isosurface as described below. In other cases, it may be necessary to explicitly smooth the mesh itself, either externally or within ArtiSynth using the LaplacianSmoother class, which can apply iterations of either Laplacian or volume-preserving Taubin smoothing, via the method

  LaplacianSmoother.smooth (mesh, numi, lam, mu);

Here numi is the number of iterations and tau and mu are the Taubin parameters. Setting {\tt lam}=1 and {\tt mu}=0 results in traditional Laplacian smoothing. If this causes the mesh to shrink more than desired, one can counter this by setting tau and mu to values used for Taubin smoothing, as described in [18].

If the mesh is large (i.e., has many vertices), then smoothing it may take noticeable computational time. In such cases, it is generally best to simply save and reuse the smoothed mesh.

By default, if a rigid body contains only one polygonal mesh, then its surface and collision meshes (returned by getSurfaceMesh() and getCollisionMesh(), respectively) are the same. However, if it is necessary to significantly smooth or modify the collision mesh, for wrapping or other purposes, it may be desirable to use different meshes for the surface and collision. This can be done by making the surface mesh non-collidable and adding an additional mesh that is collidable, as discussed in Section 3.2.8 as illustrated by the following code fragment:

  PolygonalMesh surfaceMesh;
  PolygonalMesh wrappingMesh;
  // ... initialize surface and wrapping meshes ...
  // create the body from the surface mesh
  RigidBody body = RigidBody.createFromMesh (
      "body", mesh, /*density=*/1000, /*scale=*/1.0);
  // set the surface mesh to be non-collidable, and add the wrapping mesh as
  // collidable but not having mass
  body.getSurfaceMeshComp().setIsCollidable (false);
  RigidMeshComp wcomp = body.addMesh (
      wrappingMesh, /*hasMass=*/false, /*collidable=*/true);
  RenderProps.setVisible (wcomp, false); // hide the wrapping mesh

Here, to ensure that the wrapping mesh does not to contribute to the body’s inertia, its hasMass property is set to false.

Although it is possible to specify a collision mesh that is separate from the surface mesh, there is currently no way to specify separate collision meshes for wrapping and collision handling. If this is desired for some reason, then one alternative is to create a separate body for wrapping purposes, and then attach it to the main body, as described in Section 7.5.

To verify that the distance grid’s quadratic isosurface is sufficiently smooth for wrapping purposes, it is useful to visualize the both distance grid and its isosurface directly, and if necessary adjust the resolution used to generate the grid. This can be accomplished using the body’s DistanceGridComp, which is a subcomponent named distanceGrid and which may be obtained using the method

   DistanceGridComp getDistanceGridComp()

A DistanceGridComp exports a number of properties that can be used to control the grid’s visualization, resolution, and fit around the collision mesh. These properties are described in detail in Section 4.7, and can be set either in code using their set/get accessors, or interactively using custom control panels or by selecting the grid component in the GUI and choosing Edit properties ... from the right-click context menu.

When rendering the mesh isosurface, it is usually desirable to also disable rendering of the collision meshes within the rigid body. For convenience, this can be accomplished by setting the body’s gridSurfaceRendering property to true, which will cause the grid isosurface to be rendered instead of the body’s meshes. The isosurface type will be that indicated by the grid component’s surfaceType property (which should be QUADRATIC for the quadratic isosurface), and the rendering will occur independently of the visibility settings for the meshes or the grid component.

7.3.1 Example: wrapping around a bone

Figure 7.9: TalusWrapping model, with a dragger being used to move p0 (left), and the knots visible and grid visible with restricted range (right).

An example of wrapping around a general mesh is given by artisynth.demos.tutorial.TalusWrapping. It consists of a MultiPointSpring anchored by two via points and wrapped around a rigid body representing a talus bone. The code, with include directives omitted, is given below:

1 public class TalusWrapping extends RootModel {
2
3    private static Color BONE = new Color (1f, 1f, 0.8f);
4    private static double DTOR = Math.PI/180.0;
5
6    public void build (String[] args) {
7
8       MechModel mech = new MechModel ("mech");
9       addModel (mech);
10
11       // read in the talus bone mesh
12       PolygonalMesh mesh = null;
13       try {
14          mesh = new PolygonalMesh (
15             PathFinder.findSourceDir(this) + "/data/TalusBone.obj");
16       }
17       catch (Exception e) {
18          System.out.println ("Error reading mesh:" + e);
19       }
20       // smooth the mesh using 20 iterations of regular Laplacian smoothing
21       LaplacianSmoother.smooth (mesh, /*count=*/20, /*lambda=*/1, /*mu=*/0);
22       // create the talus body from the mesh
23       RigidBody talus = RigidBody.createFromMesh (
24          "talus", mesh, /*density=*/1000, /*scale=*/1.0);
25       mech.addRigidBody (talus);
26       talus.setDynamic (false);
27       RenderProps.setFaceColor (talus, BONE);
28
29       // create start and end points for the spring
30       Particle p0 = new Particle (/*mass=*/0, /*x,y,z=*/2, 0, 0);
31       p0.setDynamic (false);
32       mech.addParticle (p0);
33       Particle p1 = new Particle (/*mass=*/0, /*x,y,z=*/-2, 0, 0);
34       p1.setDynamic (false);
35       mech.addParticle (p1);
36
37       // create a wrappable spring using a SimpleAxialMuscle material
38       MultiPointSpring spring = new MultiPointSpring ("spring");
39       spring.setMaterial (
40          new SimpleAxialMuscle (/*k=*/0.5, /*d=*/0, /*maxf=*/0.04));
41       spring.addPoint (p0);
42       // add an initial point to the wrappable segment to make sure it wraps
43       // around the bone the right way
44       spring.setSegmentWrappable (
45          100, new Point3d[] { new Point3d (0.0, -1.0, 0.0) });
46       spring.addPoint (p1);
47       spring.addWrappable (talus);
48       spring.updateWrapSegments(); // update the wrapping path
49       mech.addMultiPointSpring (spring);
50
51       // set render properties
52       DistanceGridComp gcomp = talus.getDistanceGridComp();
53       RenderProps.setSphericalPoints (mech, 0.05, Color.BLUE); // points
54       RenderProps.setLineWidth (gcomp, 0); // normal rendering off
55       RenderProps.setCylindricalLines (spring, 0.03, Color.RED); // spring
56       RenderProps.setSphericalPoints (spring, 0.05, Color.WHITE); // knots
57
58       // create a control panel for interactive control
59       ControlPanel panel = new ControlPanel();
60       panel.addWidget (talus, "gridSurfaceRendering");
61       panel.addWidget (gcomp, "resolution");
62       panel.addWidget (gcomp, "maxResolution");
63       panel.addWidget (gcomp, "renderGrid");
64       panel.addWidget (gcomp, "renderRanges");
65       panel.addWidget (spring, "drawKnots");
66       panel.addWidget (spring, "wrapDamping");
67       addControlPanel (panel);
68    }
69 }

The mesh describing the talus bone is loaded from the file "data/TalusBone.obj" located beneath the model’s source directory (lines 11-19), with the utility class PathFinder used to determine the file path (Section 2.6). To ensure better wrapping behavior, the mesh is smoothed using Laplacian smoothing (line 21) before being used to create the rigid body (lines 23-27). The spring and its anchor points p0 and p1 are created between lines 30-49, with the talus added as a wrappable. The spring contains a single segment which is made wrappable using 100 knots, and initialized with an intermediate point (line 45) to ensure that it wraps around the bone in the correct way. Intermediate points are described in more detail in Section 7.4.

Render properties are set at lines 52-56; this includes turning off rendering for grid normals by zeroing the lineWidth render property for the grid component.

Finally, lines 59-67 create a control panel (Section 5.1) for interactively controlling a variety of properties, including gridSurfaceRendering for the talus (to see the grid isosurface instead of the bone mesh), resolution, maxResolution, renderGrid, and renderRanges for the grid component (to control its resolution and visibility), and drawKnots and wrapDamping for the spring (to make knots visible and to adjust the wrap damping as described in Section 7.6).

To run this example in ArtiSynth, select All demos > tutorial > TalusWrapping from the Models menu. Since all of the dynamic components are fixed, running the model will not cause any initial motion. However, while simulating, one can use the viewer’s graphical dragger fixtures (see the section “Transformer Tools” in the ArtiSynth User Interface Guide) to move p0 or p1 and hence pull the spring across the bone surface (Figure 7.9, left). One can also interactively adjust the property settings in the control panel to view the grid, isosurface, and knots, and the adjust the grid’s resolution. Figure 7.9, right, shows the model with renderGrid and drawKnots set to true and renderRanges set to "10:12 * *".