9 Muscle Wrapping and Via Points

9.2 Obstacle Wrapping

As mentioned in Section 9.1, segments between pairs of via points can be declared wrappable, allowing them to interact with wrappable obstacles. This can be done as via points are added to the spring, using the methods

   void setSegmentWrappable (int numKnots)
   void setSegmentWrappable (int numKnots, Point3d[] initialPoints)

These make wrappable the next segment to be created (i.e., the segment between the most recently added point and the next point to be added), with numKnots specifying the number of knots that should be used to implement the wrapping. Knots are points that divide the wrappable segment into a piecewise linear curve, and are used to check for collisions with the wrapping surfaces (Figure 9.5). The argument initialPoints used by the second method is an optional argument which, if non-null, can be used to specify intermediate guide points to give the segment an initial path around around any obstacles (for more details, see Section 9.4).

Each wrappable segment will be capable of colliding with any of the wrappable obstacles that are known to the spring. Wrappables can be added, queried and removed using the following methods:

   void addWrappable (Wrappable wrappable)
   Wrappable getWrappable (int idx)
   int numWrappables()
   boolean removeWrappable (Wrappable wrappable)

Unlike points, however, there is no implied ordering and wrappables can be added in any order and at any time during the spring’s construction.

Wrappable spring construction is illustrated by the following code fragment:

   MultiPoint spring = new MultiPointSpring();
   spring.setMaterial (new LinearAxialMaterial (stiffness, damping));
   spring.addPoint (p0); // start point
   spring.setSegmentWrappable (50);  // wrappable segment
   spring.addPoint (p1); // via point
   spring.addPoint (p2); // end point
   spring.addWrappable (wrappable1);
   spring.addWrappable (wrappable2);
   spring.updateWrapSegments(); // “shrink wrap” spring to the obstacles

This creates a new MultiPointSpring with a linear material and three points p0, p1, and p2, forming a start point, via point, and stop point. The segment between p0 and p1 is set to be wrappable with 50 knot points. Two wrappable obstacles are added next, each of which will interact with the p0-p1 segment, but not with the non-wrappable p1-p2 segment. Finally, updateWrapSegments() is called to do an initial solve for the wrapping segments, so that they will be “pulled tight” around any obstacles before simulation begins.

It is also possible to make a segment wrappable after spring construction, using the method

   void setSegmentWrappable (int segIdx, int numKnots, Point3d[] initialPoints)

where segIdx identifies the segment between points {\tt segIdx} and {\tt segIdx}+1.

How many knots should be specified for a wrappable segment? Enough so that the resulting piecewise-linear approximation to the wrapping curve is sufficiently "smooth", and also enough to adequately detect contact with the obstacles without passing through them. Values between 50 and 100 generally give good results. Obstacles that are small with respect to the segment length may necessitate more knots. Making the number of knots very large will slow down the computation (although the computational cost is only O(n) with respect to the number of knots).

At the time of this writing, ArtiSynth implements two types of Wrappable object, both of which are instances of RigidBody. The first are specialized analytic subclasses of RigidBody, listed in Table 9.1, which define specific geometries and use analytic methods for the collision handling with the knot points. The use of analytic methods allows for greater accuracy and (possibly) computational efficiency, and so because of this, these special geometry wrappables should be used whenever possible.

Wrappable Description
RigidCylinder A cylinder with a specified height and radius
RigidSphere A sphere with a specified radius
RigidEllipsoid An ellipsoid with specified semi-axis lengths
RigidTorus A torus with specified inner and outer radii
Table 9.1: Specialized analytic subclasses of RigidBody
Figure 9.7: Muscle strands wrapped around general bone-shaped meshes: a humerus (left), and a pelvis (right)

The second are general rigid bodies which are not analytic subclasses, and for which the wrapping surface is determined directly from the geometry of its collision mesh returned by getCollisionMesh(). (Typically the collision mesh corresponds to the surface mesh, but it is possible to specify alternates; see Section 3.2.9.) This is useful in that it permits wrapping around arbitrary mesh geometries (Figure 9.7), but in order for the wrapping to work well, these geometries should be smooth, without sharp edges or corners. Wrapping around general meshes is implemented using a quadratically interpolated signed-distance grid (Section 4.6), and the resolution of this grid also affects the effective smoothness of the wrapping surface. More details on this are given in Section 9.3.

9.2.1 Example: wrapping around a cylinder

Figure 9.8: CylinderWrapping model loaded into ArtiSynth.

A example showing multipoint spring wrapping is given by artisynth.demos.tutorial.CylinderWrapping. It consists of a MultiPointSpring passing through a single via point, with both segments on either side of the point made wrappable. Two analytic wrappables are used: a fixed RigidCylinder, and a moving RigidEllipsoid attached to the end of the spring. The code, excluding include directives, is given below:

1 public class CylinderWrapping extends RootModel {
2
3     public void build (String[] args) {
4       MechModel mech = new MechModel ("mech");
5       addModel (mech);
6
7       mech.setFrameDamping (100.0); // set damping parameters
8       mech.setRotaryDamping (10.0);
9
10       double density = 150;
11
12       Particle via0 = new Particle (/*mass=*/0, /*x,y,z=*/-1.0, 0.0, 4.0);
13       via0.setDynamic (false);
14       mech.addParticle (via0);
15       Particle p1 = new Particle (/*mass=*/0, /*x,y,z=*/-3.0, 0.0, 0.0);
16       p1.setDynamic (false);
17       mech.addParticle (p1);
18
19       // create cylindrical wrapping object
20       RigidCylinder cylinder = new RigidCylinder (
21          "cylinder", /*rad=*/0.5, /*height=*/3.5, density, /*nsides=*/50);
22       cylinder.setPose (new RigidTransform3d (0, 0, 1.5, 0, 0, Math.PI/2));
23       cylinder.setDynamic (false);
24       mech.addRigidBody (cylinder);
25
26       // create ellipsoidal wrapping object
27       double rad = 0.6;
28       RigidEllipsoid ellipsoid = new RigidEllipsoid (
29          "ellipsoid", /*a,b,c=*/rad, 2*rad, rad, density, /*nslices=*/50);
30       ellipsoid.transformGeometry (new RigidTransform3d (3, 0, 0));
31       mech.addRigidBody (ellipsoid);
32
33       // attach a marker to the ellipsoid
34       FrameMarker p0 = new FrameMarker ();
35       double halfRoot2 = Math.sqrt(2)/2;
36       mech.addFrameMarker (
37          p0, ellipsoid, new Point3d (-rad*halfRoot2, 0, rad*halfRoot2));
38
39       // enable collisions between the ellipsoid and cylinder
40       mech.setCollisionBehavior (cylinder, ellipsoid, true);
41
42       // create the spring, making both segments wrappable with 50 knots
43       MultiPointSpring spring = new MultiPointSpring ("spring", 300, 1.0, 0);
44       spring.addPoint (p0);
45       spring.setSegmentWrappable (50);
46       spring.addPoint (via0);
47       spring.setSegmentWrappable (50);
48       spring.addPoint (p1);
49       spring.addWrappable (cylinder);
50       spring.addWrappable (ellipsoid);
51       mech.addMultiPointSpring (spring);
52
53       // set various rendering properties
54       RenderProps.setSphericalPoints (mech, 0.1, Color.WHITE);
55       RenderProps.setSphericalPoints (p1, 0.2, Color.BLUE);
56       RenderProps.setSphericalPoints (spring, 0.1, Color.GRAY);
57       RenderProps.setCylindricalLines (spring, 0.03, Color.RED);
58
59       createControlPanel (spring);
60    }
61
62    private void createControlPanel (MultiPointSpring spring) {
63       ControlPanel panel = new ControlPanel ("options", "");
64       // creates a panel to control knot and A/B point visibility
65       panel.addWidget (spring, "drawKnots");
66       panel.addWidget (spring, "drawABPoints");
67       addControlPanel (panel);
68    }
69 }

Lines 4-17 of the build() method create a MechModel with two fixed particles via0 and p1 to be used as via and stop points. Next, two analytic wrappables are created: a RigidCylinder and a RigidEllipsoid, with the former fixed in place and the latter connected to the start of the spring via the marker p0 (lines 20-37). Collisions are enabled between these two wrappables at line 40. The spring itself is created (lines 44-52), using setSegmentWrappable() to make the segments (p0, via0) and (via0, p1) wrappable with 50 knots each, and addWrappable() to make it aware of the two wrappables. Finally, render properties at set (lines 55-58), and a control panel (Section 5.1) is added that allows the spring’s drawKnots and drawABPoints properties to be interactively set.

To run this example in ArtiSynth, select All demos > tutorial > CylinderWrapping from the Models menu. The model should load and initially appear as in Figure 9.8. Running the model will cause the ellipsoid to fall and the spring to wrap around the cylinder. Using the pull tool (Section “Pull Manipulation” in the ArtiSynth User Interface Guide) on the ellipsoid can cause additional motions and make it also collide with the spring. Selecting drawKnots or drawABPoints in the control panel will cause the spring to render its knots and/or A/B points.