3 Mechanical Models I

3.2 Rigid bodies

Rigid bodies are implemented in ArtiSynth by the class RigidBody, which is a dynamic component containing a six-dimensional position and orientation state, a corresponding velocity state, an inertia, and an optional surface mesh.

A rigid body is associated with its own 3D spatial coordinate frame, and is a subclass of the more general Frame component. The combined position and orientation of this frame with respect to world coordinates defines the body’s pose, and the associated 6 degrees of freedom describe its “position” state.

3.2.1 Frame markers

Figure 3.2: A force {\bf f} applied to a frame marker attached to a rigid body. The marker is located at the point {\bf r} with respect to the body coordinate frame B.

ArtiSynth makes extensive use of markers, which are (massless) points attached to dynamic components in the model. Markers are used for graphical display, implementing attachments, and transmitting forces back onto the underlying dynamic components.

A frame marker is a marker that can be attached to a Frame, and most commonly to a RigidBody (Figure 3.2). They are frequently used to provide the anchor points for attaching springs and, more generally, applying forces to the body.

Frame markers are implemented by the class FrameMarker, which is a subclass of Point. The methods

  Point3d getLocation();
  void setLocation (Point3d r);

get and set the marker’s location {\bf r} with respect to the frame’s coordinate system. When a 3D force {\bf f} is applied to the marker, it generates a spatial force \hat{\bf f} (Section A.5) on the frame given by

\hat{\bf f}=\left(\begin{matrix}{\bf f}\\
{\bf r}\times{\bf f}\end{matrix}\right). (3.4)

Frame markers can be created using a variety of constructors, including

  FrameMarker ();
  FrameMarker (String name);
  FrameMarker (Frame frame, Point3d loc);

where FrameMarker() creates an empty marker, FrameMarker(name) creates an empty marker with a name, and FrameMarker(frame,loc) creates an unnamed marker attached to frame at the location loc with respect to the frame’s coordinates. Once created, a marker’s frame can be set and queried with

  void setFrame (Frame frame);
  Frame getFrame ();

A frame marker can be added to a MechModel with the MechModel methods

  void addFrameMarker (FrameMarker mkr);
  void addFrameMarker (FrameMarker mkr, Frame frame, Point3d loc);

where addFrameMarker(mkr,frame,loc) also sets the frame and the marker’s location with respect to it.

MechModel also supplies convenience methods to create a marker, attach it to a frame, and add it to the model:

  FrameMarker addFrameMarker (Frame frame, Point3d loc);
  FrameMarker addFrameMarkerWorld (Frame frame, Point3d locw);

Both methods return the created marker. The first, addFrameMarker(frame,loc), places it at the location loc with respect to the frame, while addFrameMarkerWorld(frame,pos) places it at pos with respect to world coordinates.

3.2.2 Example: a simple rigid body-spring model

Figure 3.3: RigidBodySpring model loaded into ArtiSynth.

A simple rigid body-spring model is defined in

  artisynth.demos.tutorial.RigidBodySpring

This differs from ParticleSpring only in the build() method, which is listed below:

1    public void build (String[] args) {
2
3       // create MechModel and add to RootModel
4       MechModel mech = new MechModel ("mech");
5       addModel (mech);
6
7       // create the components
8       Particle p1 = new Particle ("p1", /*mass=*/2, /*x,y,z=*/0, 0, 0);
9       // create box and set it’s pose (position/orientation):
10       RigidBody box =
11          RigidBody.createBox ("box", /*wx,wy,wz=*/0.5, 0.3, 0.3, /*density=*/20);
12       box.setPose (new RigidTransform3d (/*x,y,z=*/0.75, 0, 0));
13       // create marker point and connect it to the box:
14       FrameMarker mkr = new FrameMarker (/*x,y,z=*/-0.25, 0, 0);
15       mkr.setFrame (box);
16
17       AxialSpring spring = new AxialSpring ("spr", /*restLength=*/0);
18       spring.setPoints (p1, mkr);
19       spring.setMaterial (
20          new LinearAxialMaterial (/*stiffness=*/20, /*damping=*/10));
21
22       // add components to the mech model
23       mech.addParticle (p1);
24       mech.addRigidBody (box);
25       mech.addFrameMarker (mkr);
26       mech.addAxialSpring (spring);
27
28       p1.setDynamic (false);               // first particle set to be fixed
29
30       // increase model bounding box for the viewer
31       mech.setBounds (/*min=*/-1, 0, -1, /*max=*/1, 0, 0);
32       // set render properties for the components
33       RenderProps.setSphericalPoints (p1, 0.06, Color.RED);
34       RenderProps.setSphericalPoints (mkr, 0.06, Color.RED);
35       RenderProps.setCylindricalLines (mkr, 0.02, Color.BLUE);
36    }

The differences from ParticleSpring begin at line 9. Instead of creating a second particle, a rigid body is created using the factory method RigidBody.createBox(), which takes x, y, z widths and a (uniform) density and creates a box-shaped rigid body complete with surface mesh and appropriate mass and inertia. As the box is initially centered at the origin, moving it elsewhere requires setting the body’s pose, which is done using setPose(). The RigidTransform3d passed to setPose() is created using a three-argument constructor that generates a translation-only transform. Next, starting at line 14, a FrameMarker is created for a location (-0.25,0,0)^{T} relative to the rigid body, and attached to the body using its setFrame() method.

The remainder of build() is the same as for ParticleSpring, except that the spring is attached to the frame marker instead of a second particle.

To run this example in ArtiSynth, select All demos > tutorial > RigidBodySpring from the Models menu. The model should load and initially appear as in Figure 3.3. Running the model (Section 1.5.3) will cause the rigid body to fall and swing about under gravity.

3.2.3 Creating rigid bodies

As illustrated above, rigid bodies can be created using factory methods supplied by RigidBody. Some of these include:

  createBox (name, widthx, widthy, widthz, density);
  createCylinder (name, radius, height, density, nsides);
  createSphere (name, radius, density, nslices);
  createEllipsoid (name, radx, rady, radz, density, nslices);

The bodies do not need to be named; if no name is desired, then name and can be specified as null.

In addition, there are also factory methods for creating a rigid body directly from a mesh:

  createFromMesh (name, mesh, density, scale);
  createFromMesh (name, meshFileName, density, scale);

These take either a polygonal mesh (Section 2.5), or a file name from which a mesh is read, and use it as the body’s surface mesh and then compute the mass and inertia properties from the specified (uniform) density.

Alternatively, one can create a rigid body directly from a constructor, and then set the mesh and inertia properties explicitly:

  PolygonalMesh femurMesh;
  SpatialInertia inertia;
  ... initialize mesh and inertia appropriately ...
  RigidBody body = new RigidBody ("femur");
  body.setMesh (femurMesh);
  body.setInertia (inertia);

3.2.4 Pose and velocity

A body’s pose can be set and queried using the methods

  setPose (RigidTransform3d T);   // sets the pose to T
  getPose (RigidTransform3d T);   // gets the current pose in T
  RigidTransform3d getPose();     // returns the current pose (read-only)

These use a RigidTransform3d (Section 2.2) to describe the pose. Body poses are described in world coordinates and specify the transform from body to world coordinates. In particular, the pose for a body A specifies the rigid transform {\bf T}_{AW}.

Rigid bodies also expose the translational and rotational components of their pose via the properties position and orientation, which can be queried and set independently using the methods

  setPosition (Point3d p);       // sets the position to p
  getPosition (Point3d p);       // gets the current position in p
  Point3d getPosition();         // returns the current position (read-only)
  setOrientation (AxisAngle a);  // sets the orientation to a
  getOrientation (AxisAngle a);  // gets the current orientation in a
  AxisAngle getOrientation();    // returns the current orientation (read-only)

The velocity of a rigid body is described using a Twist (Section 2.4), which contains both the translational and rotational velocities. The following methods set and query the spatial velocity as described with respect to world coordinates:

  setVelocity (Twist v);         // sets the spatial velocity to v
  getVelocity (Twist v);         // gets the current spatial velocity in v
  Twist getVelocity();           // returns current spatial velocity (read-only)

During simulation, unless a rigid body has been set to be parametric (Section 3.1.3), its pose and velocity are updated in response to forces, so setting the pose or velocity generally makes sense only for setting initial conditions. On the other hand, if a rigid body is parametric, then it is possible to control its pose during the simulation, but in that case it is better to set its target pose and/or target velocity, as described in Section 5.3.1.

3.2.5 Inertia and the surface mesh

The “mass” of a rigid body is described by its spatial inertia (Section A.6), implemented by a SpatialInertia object, which specifies its mass, center of mass, and rotational inertia with respect to the center of mass.

Most rigid bodies are also associated with a polygonal surface mesh, which can be set and queried using the methods

  setSurfaceMesh (PolygonalMesh mesh);
  setSurfaceMesh (PolygonalMesh mesh, String meshFileName);
  PolygonalMesh getSurfaceMesh();

The second method takes an optional fileName argument that can be set to the name of a file from which the mesh was read. Then if the model itself is saved to a file, the model file will specify the mesh using the file name instead of explicit vertex and face information, which can reduce the model file size considerably.

Rigid bodies can also have more than one mesh, as described in Section 3.2.8.

The inertia of a rigid body can be explicitly set using a variety of methods including

  setInertia (M)                    // set using SpatialInertia M
  setInertia (mass, Jxx, Jyy, Jzz); // mass and diagonal rotational inertia
  setInertia (mass, J);             // mass and full rotational inertia
  setInertia (mass, J, com);        // mass, rotational inertia, center-of-mass

and can be queried using

  getInertia (M);                   // get SpatialInertia in M
  getInertia ();                    // return read-only SpatialInertia

In practice, it is often more convenient to simply specify a mass or a density, and then use the geometry of the surface mesh (and possibly other meshes, Section 3.2.8) to compute the remaining inertial values. How a rigid body’s inertia is computed is determined by its inertiaMethod property, which can be one

EXPLICIT

Inertia is set explicitly.

MASS

Inertia is determined implicitly from the mesh geometry and the body’s mass.

DENSITY

Inertia is determined implicitly from the mesh geometry and the body’s density (which is multiplied by the mesh volume(s) to determine a mass).

When using DENSITY to determine the inertia, it is generally assumed that the contributing meshes are both polygonal and closed. Meshes which are either open or non-polygonal generally do not have a well-defined volume which can be multiplied by the density to determine the mass.

The inertiaMethod property can be set and queried using

  setInertiaMethod (InertiaMethod method);
  InertiaMethod getInertiaMethod();

and its default value is DENSITY. Explicitly setting the inertia using one of setInertia() methods described above will set inertiaMethod to EXPLICIT. The method

  setInertiaFromDensity (density);

will (re)compute the inertia using the mesh geometry and a density value and set inertiaMethod to DENSITY, and the method

  setInertiaFromMass (mass);

will (re)compute the inertia using the mesh geometry and a mass value and set inertiaMethod to MASS.

Finally, the (assumed uniform) density of the body can be queried using

   getDensity();

There are some subtleties involved in determining the inertia using either the DENSITY or MASS methods when the rigid body contains more than one mesh. Details are given in Section 3.2.8.

3.2.6 Damping parameters

As with particles, it is possible to set damping parameters for rigid bodies.

MechModel provides two properties, frameDamping and rotaryDamping, which generate a spatial force centered on each rigid body’s coordinate frame

\hat{\bf f}_{i}=\left(\begin{matrix}-d_{f}{\bf v}_{i}\\
-d_{r}\boldsymbol{\omega}_{i}\end{matrix}\right), (3.5)

where d_{f} and d_{r} are the frameDamping and rotaryDamping values, and {\bf v}_{i} and \boldsymbol{\omega}_{i} are the translational and angular velocity of the body’s coordinate frame. The damping parameters can be set and queried using the MechModel methods

  setFrameDamping (double df);
  setRotaryDamping (double dr);
  double getFrameDamping();
  double getRotaryDamping();

For models involving rigid bodies, it is often necessary to set rotaryDamping to a non-zero value because frameDamping will provide no damping at all when a rigid body is simply rotating about its coordinate frame origin.

Frame and rotary damping can also be set for individual bodies using their own (inherited) frameDamping and rotaryDamping properties.

3.2.7 Rendering rigid bodies

A rigid body is rendered in ArtiSynth by drawing its mesh (or meshes, Section 3.2.8) and/or coordinate frame.

Meshes are drawn using the face rendering properties described in more detail in Section 4.3. The most commonly used of these are:

  • faceColor: A value of type java.awt.Color giving the color of mesh faces. The default value is GRAY.

  • shading: A value of type Renderer.Shading indicating how the mesh should be shaded, with the options being FLAT, SMOOTH, METAL, and NONE. The default value is FLAT.

  • alpha: A double value between 0 and 1 indicating transparency, with transparency increasing as value decreases from 1. The default value is 1.

  • faceStyle: A value of type Renderer.FaceStyle indicating which face sides should be drawn, with the options being FRONT, BACK, FRONT_AND_BACK, and NONE. The default value is FRONT.

  • drawEdges: A boolean indicating whether the mesh edges should also be drawn, using either the edgeColor rendering property, or the lineColor property if edgeColor is not set. The default value is false.

These properties, and others, can be set either interactively in the GUI, or in code. To set the render properties in the GUI, select the rigid body or its mesh component, and then right click the mouse and choose Edit render props .... More details are given in the section “Render properties” in the ArtiSynth User Interface Guide.

Figure 3.4: Different rendering settings for a rigid body hip mesh showing the default (left), smooth rendering with a lighter color (center), and wireframe (right).

Properties can also be set in code, usually during the build() method. Typically this is done using a static method of the RenderProps class that has the form

  RenderProps.setXXX (comp, value)

where XXX is the property name, comp is the component for which the property should be set, and value is the desired value. Some examples are shown in Figure 3.4 for a rigid body hip representation with a fairly coarse mesh. The left image shows the default rendering, using a gray color and flat shading. The center image shows a lighter color and smooth shading, which could be set by the following code fragment:

import maspack.render.*;
import maspack.render.Renderer.*;
  ...
  RigidBody hipBody;
  ...
  RenderProps.setFaceColor (hipBody, new Color (255, 255, 204));
  RenderProps.setShading (hipBody, Shading.SMOOTH);

Finally, the right image shows the body rendered as a wire frame, which can by done by setting faceStyle to NONE and drawEdges to true:

  RenderProps.setFaceStyle (hip, FaceStyle.NONE);
  RenderProps.setDrawEdges (hip, true);
  RenderProps.setEdgeWidth (hip, 2);
  RenderProps.setEdgeColor (hip, Color.CYAN);

Render properties can also be set in higher level model components, from which their values will be inherited by lower level components that have not explicitly set their own values. For example, setting the faceColor render property in the MechModel will automatically set the face color for all subcomponents which have not explicitly set faceColor. More details on render properties are given in Section 4.3.

Figure 3.5: Rigid body axes rendered with axisDrawStyle set to LINE (left) and ARROW (right).

In addition to mesh rendering, it is often useful to draw a rigid body’s coordinate frame, which can be done using its axisLength and axisDrawStyle properties. Setting axisLength to a positive value will cause the body’s three coordinate axes to be drawn, with the indicated length, with the x, y and z axes colored red, green, and blue, respectively. The axisDrawStyle property controls how the axes are rendered (Figure 3.5). It has the type Renderer.AxisDrawStyle, and can be set to the following values:

OFF

Axes are not rendered.

LINE

Axes are rendered as simple red-green-blue lines, with a width given by the joint’s lineWidth rendering property.

ARROW

Axes are rendered as solid red-green-blue arrows.

As with the rendering proprieties, the axisLength and axisDrawStyle properties can be managed either interactively in the GUI (by selecting the body, right clicking and choosing Edit properties ...), or in code, using the following methods:

  double getAxisLength()
  void setAxisLength (double len)
  AxisDrawStyle getAxisDrawStyle()
  void setAxisDrawStyle (AxisDrawStyle style)

3.2.8 Multiple meshes

A RigidBody may contain multiple meshes, which can be useful for various reasons:

  • It may be desirable to use different meshes for collision detection, inertia computation, and visual presentation;

  • Different render properties can be set for different mesh components, allowing the body to be rendered in a more versatile way;

  • Different mesh components can be selected individually.

Each rigid body mesh is encapsulated inside a RigidMeshComp component, which is in turn stored in a subcomponent list called meshes. Meshes do not need to be instances of PolygonalMesh; instead, they can be any instance of MeshBase, including PointMesh and PolylineMesh.

The default surface mesh, returned by getSurfaceMesh(), is also stored inside a RigidMeshComp in the meshes list. By default, the surface mesh is the first mesh in the list, but is otherwise defined to be the first mesh in meshes which is also an instance of PolygonalMesh. The RigidMeshComp containing the surface mesh can be obtained using the method getSurfaceMeshComp().

A RigidMeshComp contains a number of properties that control how the mesh is displayed and interacts with its rigid body:

renderProps

Render properties controlling how the mesh is rendered (see Section 4.3).

hasMass

A boolean, which if true means that the mesh will contribute to the body’s inertia when the inertiaMethod is either MASS or DENSITY. The default value is true.

massDistribution

An enumerated type defined by MassDistribution which specifies how the mesh’s inertia contribution is determined for a given mass. VOLUME, AREA, LENGTH, and POINT indicate, respectively, that the mass is distributed evenly over the mesh’s volume, area (faces), length (edges), or points. The default value is determined by the mesh type: VOLUME for a closed PolygonalMesh, AREA for an open PolygonalMesh, LENGTH for a PolylineMesh, and POINT for a PointMesh. Applications can specify an alternate value providing the mesh has the features to support it. Specifying DEFAULT will restore the default value.

isCollidable

A boolean, which if true, and if the mesh is a PolygonalMesh, means that the mesh will take part in collision and wrapping interactions (Sections 4.5 and 7.3). The default value is true, and the get/set accessors have the names isCollidable() and setIsCollidable().

volume

A double whose value is the volume of the mesh. If the mesh is a PolygonalMesh, this is the value returned by its computeVolume() method. Otherwise, the volume is 0, unless setVolume(vol) is used to explicitly set a non-zero volume value.

mass

A double whose default value is the product of the density and volume properties. Otherwise, if mass has been explicitly set using setMass(mass), the value is the explicit mass.

density

A double whose default value is the rigid body’s density. Otherwise, if density has been explicitly set using setDensity(density), the value is the explicit density, or if mass has been explicitly set using setMass(mass), the value is the explicit mass divided by volume.

Note that by default, the density of a RigidMeshComp is simply the density setting for the rigid body, and the mass is this times the volume. However, it is possible to set either an explicit mass or a density value that will override this. (Also, explicitly setting a mass will unset any explicit density, and explicitly setting the density will unset any explicit mass.)

When the inertiaMethod of the rigid body is either MASS or DENSITY, then its inertia is computed from the sum of all the inertias {\bf M}_{k} of the component meshes k for which hasMass is true. Each {\bf M}_{k} is computed by the mesh’s createInertia(mass,massDistribution) method, using the mass and massDistribution properties of its RigidMeshComp.

When forming the body inertia from the inertia components of individual meshes, no attempt is made to account for mesh overlap. If this is important, the meshes themselves should be modified in advance so that they do not overlap, perhaps by using the CSG primitives described in Section 2.5.7.

Instances of RigidMeshComp can be created directly, using constructions such as

  PolygonalMesh mesh;
  ... initialize mesh ...
  RigidMeshComp mcomp = new RigidMeshComp (mesh);

or

  RigidMeshComp mcomp = new RigidMeshComp ("meshName");
  mcomp.setMesh (mesh);

after which they can be added or removed from the meshes list using the methods

  void addMeshComp (RigidMeshComp mcomp)
  void addMeshComp (RigidMeshComp mcomp, int idx)
  int numMeshComps()
  boolean removeMeshComp (RigidMeshComp mcomp)
  boolean removeMeshComp (String name)
  void clearMeshComps()

It is also possible to add meshes directly to the meshes list, using the methods

  RigidMeshComp addMesh (MeshBase mesh)
  RigidMeshComp addMesh (MeshBase mesh, boolean hasMass, boolean collidable)

each of which creates a RigidMeshComp, adds it to the mesh list, and returns it. The second method also specifies the values of the hasMass and collidable properties (both of which are true by default).

3.2.9 Example: a composite rigid body

Figure 3.6: RigidCompositeBody loaded into ArtiSynth and run for 0.75 seconds. The ball on the right falls less because it has a lower density than the rest of the body.

An example of constructing a rigid body from multiple meshes is defined in

  artisynth.demos.tutorial.RigidCompositeBody

This uses three meshes to construct a rigid body whose shape resembles a dumbbell. The code, with the include files omitted, is listed below:

1 public class RigidCompositeBody extends RootModel {
2
3    public void build (String[] args) {
4
5       // create MechModel and add to RootModel
6       MechModel mech = new MechModel ("mech");
7       addModel (mech);
8
9       // create the component meshes
10       PolygonalMesh ball1 = MeshFactory.createIcosahedralSphere (0.8, 1);
11       ball1.transform (new RigidTransform3d (1.5, 0, 0));
12       PolygonalMesh ball2 = MeshFactory.createIcosahedralSphere (0.8, 1);
13       ball2.transform (new RigidTransform3d (-1.5, 0, 0));
14       PolygonalMesh axis = MeshFactory.createCylinder (0.2, 2.0, 12);
15       axis.transform (new RigidTransform3d (0, 0, 0, 0, Math.PI/2, 0));
16
17       // create the body and add the component meshes
18       RigidBody body = new RigidBody ("body");
19       body.setDensity (10);
20       body.setFrameDamping (10); // add damping to the body
21       body.addMesh (axis);
22       RigidMeshComp bcomp1 = body.addMesh (ball1);
23       RigidMeshComp bcomp2 = body.addMesh (ball2);
24       mech.addRigidBody (body);
25
26       // connect the body to a spring attached to a fixed particle
27       Particle p1 = new Particle ("p1", /*mass=*/0, /*x,y,z=*/0, 0, 2);
28       p1.setDynamic (false);
29       mech.addParticle (p1);
30       FrameMarker mkr = mech.addFrameMarkerWorld (body, new Point3d (0, 0, 0.2));
31       AxialSpring spring =
32          new AxialSpring ("spr", /*k=*/150, /*d=*/0, /*restLength=*/0);
33       spring.setPoints (p1, mkr);
34       mech.addAxialSpring (spring);
35
36       // set the density for ball1 to be less than the body density
37       bcomp1.setDensity (8);
38
39       // set render properties for the component, with the ball
40       // meshes having different colors
41       RenderProps.setFaceColor (body, new Color (250, 200, 200));
42       RenderProps.setFaceColor (bcomp1, new Color (200, 200, 250));
43       RenderProps.setFaceColor (bcomp2, new Color (200, 250, 200));
44       RenderProps.setSphericalPoints (mech, 0.06, Color.WHITE);
45       RenderProps.setCylindricalLines (spring, 0.02, Color.BLUE);
46    }
47 }

As in the previous examples, the build() method starts by creating a MechModel (lines 6-7). Three different meshes (two balls and an axis) are then constructed at lines 10-15, using MeshFactory methods (Section 2.5) and transforming each result to an appropriate position/orientation with respect to the body’s coordinate frame.

The body itself is constructed at lines 18-24. Its default density is set to 10, and its frame damping (Section 3.2.6) is also set to 10 (the previous rigid body example in Section 3.2.2 relied on spring damping to dissipate energy). The meshes are added using addMesh(), which allocates and returns a RigidMeshComp. For the ball meshes, these are saved in bcomp1 and bcomp2 and used later to adjust density and/or render properties.

Lines 27-34 create a simple linear spring, connected to a fixed point p0 and a marker mkr. The marker is created and attached to the body by the MechModel method addFrameMarkerWorld(), which places the marker at a known position in world coordinates. The spring is created using an AxialSpring constructor that accepts a name, along with stiffness, damping, and rest length parameters to specify a LinearAxialMaterial.

At line 37, bcomp1 is used to set the density of ball1 to 8. Since this is less than the default body density, the inertia component of ball1 will be lighter than that of ball2. Finally, render properties are set at lines 41-45. This includes setting the default face colors for the body and for each ball.

To run this example in ArtiSynth, select All demos > tutorial > RigidCompositeBody from the Models menu. The model should load and initially appear as in Figure 3.6. Running the model (Section 1.5.3) will cause the rigid body to fall and swing about under gravity, with the right ball (ball1) not falling as far because it has less density.