Finite Element Models

July 17, 2024

Contents

Chapter 1 Finite Element Models

This chapter details how to construct three-dimensional finite element models, and how to couple them with the other simulation components described in previous sections (e.g. particles and rigid bodies). Finite element muscles, which have additional properties that allow them to contract given activation signals, are discussed in Section 1.9. An example FEM model of the masseter, coupled to a rigid jaw and maxilla, is shown in Figure 1.1.

Figure 1.1: Finite element model of the masseter, coupled to the jaw and maxilla.

1.1 Overview

The finite element method (FEM) is a numerical technique used for solving a system of partial differential equations (PDEs) over some domain. The general approach is to divide the domain into a set of building blocks, referred to as elements. These partition the space, and form local domains over which the system of equations can be locally approximated. The corners of these elements, the nodes, become control points in a discretized system. The solution is then assumed to be smoothly interpolated across the elements based on values determined at the nodes. Using this discretization, the differential system is converted into an algebraic one, which is often linearized and solved iteratively.

In ArtiSynth, the PDEs considered are the governing equations of continuum mechanics: the conservation of mass, momentum, and energy. To complete the system, a constitutive equation is required that describes the stress-strain response of the material. This constitutive equation is what distinguishes between material types. The domain is the three-dimensional space that the model occupies. This must be divided into small elements which accurately represent the geometry. Within each element, the PDEs are sampled at a set of points, referred to as integration points, and terms are numerically integrated to form an algebraic system to solve.

The purpose of the rest of this chapter is to describe the construction and use of finite elements models within ArtiSynth. It does not further discuss the mathematical framework or theory. For an in-depth coverage of the nonlinear finite element method, as applied to continuum mechanics, the reader is referred to the textbook by Bonet and Wood [bonet:fem:2000].

1.1.1 FemModel3d

The basic type of finite element model is implemented in the class FemModel3d. This class controls some properties that are used by the model as a whole. The key ones that affect simulation dynamics are:

Property Description
density The density of the model
material An object that describes the material’s constitutive law (i.e. its stress-strain relationship).
particleDamping Proportional damping associated with the particle-like motion of the FEM nodes.
stiffnessDamping Proportional damping associated with the system’s stiffness term.

These properties can be set and retrieved using the methods

void setDensity (double density)

Sets the density.

double getDensity()

Gets the density.

void setMaterial (FemMaterial mat)

Sets the FEM’s material.

FemMaterial getMaterial()

Gets the FEM’s material.

void setParticleDamping (double d)

Sets the particle (mass) damping.

double getParticleDamping()

Gets the particle (mass) damping.

void setStiffnessDamping (double d)

Sets the stiffness damping.

double getStiffnessDamping()

Gets the stiffness damping.

Keep in mind that ArtiSynth is essentially “unitless” (Section LABEL:sec:mechii:units), so it is the responsibility of the developer to ensure that all properties are specified in a compatible way.

The density of the model is used to compute the mass distribution throughout the volume. Note that we use a diagonally lumped mass matrix (DLMM) formulation, so the mass is assumed to be concentrated at the location of the discretized FEM nodes. To allow for a spatially-varying density, densities can be explicitly set for individual elements, or masses can be explicitly set for individual nodes.

The FEM’s material property is a delegate object used to compute stress and stiffness within individual elements. It handles the constitutive component of the model, as described in more detail in Sections 1.1.3 and 1.10. In addition to the main material defined for the model, it is also possible set a material on a per-element basis, and to define additional materials which augment the behavior of the main materials (Section 1.8).

The two damping parameters are related to Rayleigh damping, which is used to dissipate energy within finite element models. There are two proportional damping terms: one related to the system’s mass, and one related to stiffness. The resulting damping force applied is

\displaystyle{\bf f}_{d} \displaystyle=-(d_{M}{\bf M}+d_{K}{\bf K}){\bf v}, (1.1)

where d_{M} is the value of particleDamping, d_{K} is the value of stiffnessDamping, {\bf M} is the FEM model’s lumped mass matrix, {\bf K} is the FEM’s stiffness matrix, and {\bf v} is the concatenated vector of FEM node velocities. Since the lumped mass matrix is diagonal, the mass-related component of damping can be applied separately to each FEM node. Thus, the mass component reduces to the same system as Equation (LABEL:eqn:pointdamping), which is why it is referred to as “particle damping”.

1.1.2 Component Structure

Each FemModel3d contains several lists of subcomponents:

nodes

The particle-like dynamic components of the model. These lie at the corners of the elements and carry all the mass (due to DLMM formulation).

elements

The volumetric model elements. These define the 3D sub-units over which the system is numerically integrated.

shellElements

The shell elements. These define additional 2D sub-units over which the system is numerically integrated.

meshes

The geometry in the model. This includes the surface mesh, and any other embedded geometries.

materials

Optional additional materials which can be added to the model to augment the behavior of the model’s material property. This is described in more detail in Section 1.8.

fields

Optional field components which can be used to interpolate application-defined quantities over the FEM model’s domain. Fields are described in detail in Chapter LABEL:sec:fields.

The nodes, elements and meshes components are illustrated in Figure 1.2.

(a) FEM model (b) Nodes (c) Elements (d) Geometry
Figure 1.2: Subcomponents of FemModel3d.
1.1.2.1 Nodes

The set of nodes belong to a finite element model can be obtained by the method

PointList<FemNode3d> getNodes()

Returns the list of FEM nodes.

Nodes are implemented in the class FemNode3d, which is a subclass of Particle (Section LABEL:ParticlesAndSprings:sec). They are the main dynamic components of the finite element model. The key properties affecting simulation dynamics are:

Property Description
restPosition The initial position of the node.
position The current position of the node.
velocity The current velocity of the node.
mass The mass of the node.
dynamic Whether the node is considered dynamic or parametric (e.g. boundary condition).

Each of these properties has corresponding getXxx() and setXxx(...) functions to access and modify them.

The restPosition property defines the node’s position in the FEM model’s “natural” or “undeformed” state. Rest positions are used to compute an initial configuration for the model, from which strains are determined. A node’s rest position can be updated in code using the method: FemNode3d.setRestPosition(Point3d).

If any node’s rest positions are changed, the current values for stress and stiffness will become invalid. They can be manually updated using the method FemModel3d.updateStressAndStiffness() for the parent model. Otherwise, stress and stiffness will be automatically updated at the beginning of the next time step.

The properties position and velocity give the node’s current 3D state. These are common to all point-like particles, as is the mass property. Here, however, mass represents the lumped mass of the immediately surrounding material. Its value is initialized by equally dividing mass contributions from each adjacent element, given their densities. For a finer control of spatially-varying density, node masses can be set manually after FEM creation.

The FEM node’s dynamic property specifies whether or not the node is considered when computing the dynamics of the system. If not, it is treated as being parametrically controlled. This has implications when setting boundary conditions (Section 1.1.4).

1.1.2.2 Elements

Elements are the 3D volumetric spatial building blocks of the domain. Within each element, the displacement (or strain) field is interpolated from displacements at nodes:

\displaystyle{\bf u}({\bf x}) \displaystyle=\sum_{i=1}^{N}\phi_{i}({\bf x}){\bf u}_{i}, (1.2)

where {\bf u}_{i} is the displacement of the ith node that is adjacent to the element, and \phi_{i}(\cdot) is referred to as the shape function (or basis function) associated with that node. Elements are classified by their shape, number of nodes, and shape function order (Table 1.1). ArtiSynth supports the following element types, shown below with their node numberings:

TetElement PyramidElement WedgeElement HexElement
QuadtetElement QuadpyramidElement QuadwedgeElement QuadhexElement
Table 1.1: Supported element types
Element Type # Nodes Order # Integration Points
TetElement 4 linear 1
PyramidElement 5 linear 5
WedgeElement 6 linear 6
HexElement 8 linear 8
QuadtetElement 10 quadratic 4
QuadpyramidElement 13 quadratic 5
QuadwedgeElement 15 quadratic 9
QuadhexElement 20 quadratic 14

The base class for all of these is FemElement3d. A numerical integration is performed within each element to create the (tangent) stiffness matrix. This integration is performed by evaluating the stress and stiffness at a set of integration points within each element, and applying numerical quadrature. The list of elements in a model can be obtained with the method

RenderableComponentList<FemElement3d> getElements()

Returns the list of volumetric elements.

All objects of type FemElement3d have the following properties:

Property Description
density Density of the element
material An object that describes the constitutive law within the element (i.e. its stress-strain relationship).

If left unspecified, the element’s density is inherited from the containing FemModel3d object. When set, the mass of the element is computed and divided amongst all its nodes, updating the lumped mass matrix.

Each element’s material property is also inherited by default from the containing FemModel3d. Specifying a material here allows for spatially-varying material properties across the model. Materials are discussed further in Sections 1.1.3 and 1.10.

1.1.2.3 Shell elements

Shell elements are additional 2D spatial building blocks which can be added to a model. They are typically used to model structures which are too thin to be easily represented by 3D volumetric elements, or to provide additional internal stiffness within a set of volumetric elements.

ArtiSynth presently supports the following shell element types, with the number of nodes, shape function order, and integration point count described in Table 1.2:

Table 1.2: Supported shell element types
Element Type # Nodes Order # Integration Points
ShellTriElement 3 linear 9 (3 if membrane)
ShellQuadElement 4 linear 8 (4 if membrane)

The base class for all shell elements is ShellElement3d, which contains the same density and material properties as FemElement3d, as well as the additional property defaultThickness, whose use will be described below.

The list of shell elements in a model can be obtained with the method

RenderableComponentList<ShellElement3d> getShellElements()

Returns the list of shell elements.

Both the volumetric elements (FemElement3d) and the shell elements (ShellElement3d) derive from the base class FemElement3dBase. To obtain all the elements in an FEM model, both shell and volumetric, one may use the method

ArrayList<FemElement3dBase> getAllElements()

Returns a list of all elements.

Each shell element can actually be instantiated in two forms:

  • As a regular shell element, which has a bending stiffness;

  • As a membrane element, which does not have bending stiffness.

Regular shell elements are implemented using the same extensible director formulation used by FEBio [MaasFEBio2012], and more specifically the front/back node formulation [FEBioTheory2018]. Each node associated with a (regular) shell element is assigned a director, which is a 3D vector providing a normal direction and virtual thickness at that node (Figure 1.3). This virtual thickness allows us to continue to use 3D materials to provide the constitutive laws that determine the shell’s stress/strain response, including its bending behavior. It also allows us to continue to use the element’s density to determine its mass.

Figure 1.3: ShellQuadElement with the directors (dark blue lines) visible at the nodes.

Director information is automatically assigned to a FemNode3d whenever one or more regular shell elements is connected to it. This information includes both the current value of the director, its rest value, and its velocity, with the difference between the first two determining the element’s bending strain. These quantities can be queried using the methods

  Vector3d getDirector ();        // return the current director value
  void getDirector (Vector3d dir);
  Vector3d getRestDirector ();    // return the rest director value
  void getRestDirector (Vector3d dir);
  Vector3d getDirectorVel ();     // return the director velocity
  boolean hasDirector();          // does this node have a director?

For nodes which are not connected to regular shell elements, and therefore do not have director information assigned, these methods all return a zero-valued vector.

If not otherwise specified, the current and rest director values are computed automatically from the surrounding (regular) shell elements, with their value {\bf d} being computed from

{\bf d}=\sum t_{i}\,{\bf n}_{i}

where t_{i} is the value of the defaultThickness property and {\bf n}_{i} is the surface normal of the i-th surrounding regular shell element. However, if necessary, it is also possible to explicitly assign these values, using the methods

  setDirector (Vector3d dir);     // set the current director
  setRestDirector (Vector3d dir); // set the rest director

ArtiSynth FEM nodes can currently support only one director, which is shared by all regular shell elements associated with that node. This effectively means that all such elements must belong to the same “surface”, and that two intersecting surfaces cannot share the same nodes.

As indicated above, shell elements can also be instantiated as membrane elements, which do not exhibit bending stiffness and therefore do not require director information. The regular/membrane distinction is specified in the element’s constructor. For example, ShellTriElement and ShellQuadElement each have constructors with the signatures:

  ShellTriElement (FemNode3d n0, FemNode3d n1, FemNode3d n2,
    double thickness, boolean membrane);
  ShellQuadElement (FemNode3d n0, FemNode3d n1, FemNode3d n2, FemNode3d n3,
    double thickness, boolean membrane);

The thickness argument specifies the defaultThickness property, while membrane determines whether or not the element is a membrane element.

While membrane elements do not require explicit director information stored at the nodes, they do make use of an inferred director that is parallel to the element’s surface normal, and has a constant length equal to the element’s defaultThickness property. This gives the element a virtual volume, which (as with regular elements) is used to determine 3D strains and to compute the element’s mass from it’s density.

1.1.2.4 Meshes

The geometry associated with a finite element model consists of a collection of meshes (e.g. PolygonalMesh, PolylineMesh, PointMesh) that move along with the model in a way that maintains the shape function interpolation equation (1.2) at each vertex location. These geometries can be used for visualizations, or for physical interactions like collisions. However, they have no physical properties themselves. FEM geometries will be discussed in more detail in Section 1.3. The list of meshes can be obtained with the method

  MeshComponentList<FemMeshComp> getMeshComps();

1.1.3 Materials

The stress-strain relationship within each element is defined by a “material” delegate object, implemented by a subclass of FemMaterial. This material object is responsible for implementing the functions

   void computeStressAndTangent (...)

which computes the stress tensor and (optionally) the tangent stiffness matrix at each integration point, based on the current local deformation at that point.

All supported materials are presented in detail in Section 1.10. The default material type is LinearMaterial, which linearly maps Cauchy strain \boldsymbol{\epsilon} onto Cauchy stress \boldsymbol{\sigma} via

\boldsymbol{\sigma}=\mathcal{D}:\boldsymbol{\epsilon},

where \mathcal{D} is the elasticity tensor determined from Young’s modulus and Poisson’s ratio. Anisotropic linear materials are provided by TransverseLinearMaterial and AnisotropicLinearMaterial. Linear materials in ArtiSynth implement corotation, which removes the rotation from the deformation gradient and allows Cauchy strain to be applied to large deformations [ngan:fem:2008, muller2004interactive]. Nonlinear hyperelastic materials are also supported, including MooneyRivlinMaterial, OgdenMaterial, YeohMaterial, ArrudaBoyceMaterial, and VerondaWestmannMaterial.

1.1.4 Boundary conditions

Boundary conditions can be implemented in one of several ways:

  1. 1.

    Explicitly setting FEM node positions/velocities

  2. 2.

    Attaching FEM nodes to other dynamic components

  3. 3.

    Enabling collisions

To enforce an explicit (Dirichlet) boundary condition for a set of nodes, their dynamic property must be set to false. This notifies ArtiSynth that the state of these nodes (both position and velocity) will be controlled parametrically. By disabling dynamics, a fixed boundary condition is applied. For a moving boundary, positions and velocities of the boundary nodes must be explicitly set every timestep. This can be accomplished with either a Controller (see Section LABEL:ControllersAndMonitors:sec) or an InputProbe (see Section LABEL:Probes:sec). Note that both the position and velocity of the nodes should be explicitly set for consistency.

Another type of supported boundary condition is to attach FEM nodes to other components, including particles, springs, rigid bodies, and locations within other FEM elements. Here, the node is still considered dynamic, but its motion is coupled to that of the attached component through a constraint mechanism. Attachments will be discussed further in Section 1.4.

Finally, the boundary of an FEM can be constrained by enabling collisions with other components. This will be covered in Chapter LABEL:ContactAndCollision:sec.

1.2 FEM model creation

Creating a finite element model in ArtiSynth typically follows the pattern:

   // Create and add main MechModel
   MechModel mech = new MechModel("mech");
   addModel(mech);
   // Create FEM
   FemModel3d fem = new FemModel3d("fem");
   /* ... Setup FEM structure and properties ... */
   // Add FEM to model
   mech.addModel(fem);

The main code block for the FEM setup should do the following:

  • Build the node/element structure

  • Set physical properties

    • density

    • damping

    • material

  • Set boundary conditions

  • Set render properties

Building the FEM structure can be done with the use of factory methods for simple shapes, by loading external files, or by writing code to manually assemble the nodes and elements.

1.2.1 Factory methods

For simple shapes such as beams and ellipsoids, there are factory methods to automatically build the node and element structure. These methods are found in the FemFactory class. Some common methods are

FemFactory.createGrid(...)          // basic beam
FemFactory.createCylinder(...)      // cylinder
FemFactory.createTube(...)          // hollowed cylinder
FemFactory.createEllipsoid(...)     // ellipsoid
FemFactory.createTorus(...)         // torus

The inputs specify the dimensions, resolution, and potentially the type of element to use. The following code creates a basic beam made up of hexahedral elements:

// Create FEM
FemModel3d beam = new FemModel3d("beam");
// Build FEM structure
double[] size = {1.0, 0.25, 0.25};  // widths
int[] res = {8, 2, 2};              // resolution (# elements)
FemFactory.createGrid(beam, FemElementType.Hex,
   size[0], size[1], size[2],
   res[0], res[1], res[2]);
/* ... Set FEM properties ... */
// Add FEM to model
mech.addModel(beam);

1.2.2 Loading external FEM meshes

For more complex geometries, volumetric meshes can be loaded from external files. A list of supported file types is provided in Table 1.3. To load a geometry, an appropriate file reader must be created. Readers capable of reading FEM models implement the interface FemReader, which has the method

readFem( FemModel3d fem )   // populates the FEM based on file contents

Additionally, many FemReader classes have static methods to handle the loading of files for convenience.

Table 1.3: Supported FEM geometry files
Format File extensions Reader Writer
ANSYS .node, .elem AnsysReader AnsysWriter
TetGen .node, .ele TetGenReader TetGenWriter
Abaqus .inp AbaqusReader AbaqusWriter
VTK (ASCII) .vtk VtkAsciiReader

The following code snippet demonstrates how to load a model using the AnsysReader.

// Create FEM
FemModel3d tongue = new FemModel3d("tongue");
// Read FEM from file
try {
   // Get files relative to THIS class
   String nodeFileName = PathFinder.getSourceRelativePath(this,
                            "data/tongue.node");
   String elemFileName = PathFinder.getSourceRelativePath(this,
                            "data/tongue.elem");
   AnsysReader.read(tongue, nodeFileName, elemFileName);
} catch (IOException ioe) {
   // Wrap error, fail to create model
   throw new RuntimeException("Failed to read model", ioe);
}
// Add to model
mech.addModel(tongue);

The method PathFinder.getSourceRelativePath() is used to find a path within the ArtiSynth source tree that is relative to the current model’s source file (Section LABEL:PathFinder:sec). Note the try-catch block. Most of these readers throw an IOException if the read fails.

1.2.3 Generating from surfaces

There are two ways an FEM model can be generated from a surface: by using a FEM mesh generator, and by extruding a surface along its normal direction.

ArtiSynth has the ability to interface directly with the TetGen library (http://tetgen.org) to create a tetrahedral volumetric mesh given a closed and manifold surface. The main Java class for calling TetGen directly is TetgenTessellator. The tessellator has several advanced options, allowing for the computation of convex hulls, and for adding points to a volumetric mesh. For simply creating an FEM from a surface, there is a convenience routine within FemFactory that handles both mesh generation and constructing a FemModel3d:

// Create an FEM from a manifold mesh with a given quality
FemFactory.createFromMesh( PolygonalMesh mesh, double quality );

If quality >0, then points will be added in an attempt to bound the maximum radius-edge ratio (see the -q switch for TetGen). According to the TetGen documentation, the algorithm usually succeeds for a quality ratio of 1.2.

It’s also possible to create thin layer of elements by extruding a surface along its normal direction.

// Create an FEM by extruding a surface
FemFactory.createExtrusion(
      FemModel3d model, int nLayers, double layerThickness, double zOffset,
      PolygonalMesh surface);

For example, to create a two-layer slice of elements centered about a surface of a tendon mesh, one might use

// Load the tendon surface mesh
PolygonalMesh tendonSurface = new PolygonalMesh("tendon.obj");
// Create the tendon
FemModel3d tendon = new FemModel3d("tendon");
int layers = 2;             // 2 layers
double thickness = 0.0005;  // 0.5 mm layer thickness
double offset = thickness;  // center the layers about the surface
// Create the extrusion
FemFactory.createExtrusion( tendon, layers, thickness, offset, tendonSurface );

For this type of extrusion, triangular faces become wedge elements, and quadrilateral faces become hexahedral elements.

Note: for extrusions, no care is taken to ensure element quality; if the surface has a high curvature relative to the total extrusion thickness, then some elements will be inverted.

1.2.4 Building elements in code

A finite element model’s structure can also be manually constructed in code. FemModel3d has the methods:

addNode ( FemNode3d );       // add a node to the model
addElement ( FemElement3d )  // add an element to the model

For an element to successfully be added, all its nodes must already have been added to the model. Nodes can be constructed from a 3D location, and elements from an array of nodes. A convenience routine is available in FemElement3d that automatically creates the appropriate element type given the number of nodes (Table 1.1):

// Creates an element using the supplied nodes
FemElement3d FemElement3d.createElement( FemNode3d[] nodes );

Be aware of node orderings when supplying nodes. For linear elements, ArtiSynth uses a clockwise convention with respect to the outward normal for the first face, followed by the opposite node(s). To determine the correct ordering for a particular element, check the coordinates returned by the function FemElement3dBase.getNodeCoords(). This returns the concatenated coordinate list for an “ideal” element of the given type.

1.2.5 Example: a simple beam model

Figure 1.4: FemBeam model loaded into ArtiSynth.

A complete application model that implements a simple FEM beam is given below.

1 package artisynth.demos.tutorial;
2
3 import java.awt.Color;
4 import java.io.IOException;
5
6 import maspack.render.RenderProps;
7 import artisynth.core.femmodels.FemFactory;
8 import artisynth.core.femmodels.FemModel.SurfaceRender;
9 import artisynth.core.femmodels.FemModel3d;
10 import artisynth.core.femmodels.FemNode3d;
11 import artisynth.core.materials.LinearMaterial;
12 import artisynth.core.mechmodels.MechModel;
13 import artisynth.core.workspace.RootModel;
14
15 public class FemBeam extends RootModel {
16
17    // Models and dimensions
18    FemModel3d fem;
19    MechModel mech;
20    double length = 1;
21    double density = 10;
22    double width = 0.3;
23    double EPS = 1e-15;
24
25    public void build (String[] args) throws IOException {
26
27       // Create and add MechModel
28       mech = new MechModel ("mech");
29       addModel(mech);
30
31       // Create and add FemModel
32       fem = new FemModel3d ("fem");
33       mech.add (fem);
34
35       // Build hex beam using factory method
36       FemFactory.createHexGrid (
37          fem, length, width, width, /*nx=*/6, /*ny=*/3, /*nz=*/3);
38
39       // Set FEM properties
40       fem.setDensity (density);
41       fem.setParticleDamping (0.1);
42       fem.setMaterial (new LinearMaterial (4000, 0.33));
43
44       // Fix left-hand nodes for boundary condition
45       for (FemNode3d n : fem.getNodes()) {
46          if (n.getPosition().x <= -length/2+EPS) {
47             n.setDynamic (false);
48          }
49       }
50
51       // Set rendering properties
52       setRenderProps (fem);
53
54    }
55
56    // sets the FEM’s render properties
57    protected void setRenderProps (FemModel3d fem) {
58       fem.setSurfaceRendering (SurfaceRender.Shaded);
59       RenderProps.setLineColor (fem, Color.BLUE);
60       RenderProps.setFaceColor (fem, new Color (0.5f, 0.5f, 1f));
61    }
62
63 }

This example can be found in artisynth.demos.tutorial.FemBeam. The build() method first creates a MechModel and FemModel3d. A FEM beam is created using a factory method on line 36. This beam is centered at the origin, so its length extends from -length/2 to length/2. The density, damping and material properties are then assigned.

On lines 45–49, a fixed boundary condition is set to the left-hand side of the beam by setting the corresponding nodes to be non-dynamic. Due to numerical precision, a small EPSILON buffer is required to ensure all left-hand boundary nodes are identified (line 46).

Rendering properties are then assigned to the FEM model on line 52. These will be discussed further in Section 1.12.

1.3 FEM Geometry

Associated with each FEM model is a list of geometry with the heading meshes. This geometry can be used for either display purposes, or for interactions such as collisions (Section LABEL:CompoundCollidablesAndSelf:sec). A geometry itself has no physical properties; its motion is entirely governed by the FEM model that contains it.

All FEM geometries are of type FemMeshComp, which stores a reference to a mesh object (Section LABEL:Meshes:sec), as well as attachment information that links vertices of the mesh to points within the FEM. The attachments enforce the shape function interpolation in Equation (1.2) to hold at each mesh vertex, with constant shape function coefficients.

1.3.1 Surface meshes

By default, every FemModel3d has an auto-generated geometry representing the “surface mesh”. The surface mesh consists of all un-shared element faces (i.e. the faces of individual elements that are exposed to the world), and its vertices correspond to the nodes that make up those faces. As the FEM nodes move, so do the mesh vertices due to the attachment framework.

The surface mesh can be obtained using one of the following functions in FemModel3d:

FemMeshComp getSurfaceMeshComp ();     // returns the FemMeshComp surface component
PolygonalMesh getSurfaceMesh ();  // returns the underlying polygonal surface mesh

The first returns the surface complete with attachment information. The latter method directly returns the PolygonalMesh that is controlled by the FEM.

It is possible to manually set the surface mesh:

setSurfaceMesh ( PolygonalMesh surface );  // manually set surface mesh

However, doing so is normally not necessary. It is always possible to add additional mesh geometries to a finite element model, and the visibility settings can be changed so that the default surface mesh is not rendered.

1.3.2 Embedding geometry within an FEM

Any geometry of type MeshBase can be added to a FemModel3d. To do so, first position the mesh so that its vertices are in the desired locations inside the FEM, then call one of the FemModel3d methods:

FemMeshComp addMesh ( MeshBase mesh );                // creates and returns FemMeshComp
FemMeshComp addMesh ( String name, MeshBase mesh );

The latter is a convenience routine that also gives the newly embedded FemMeshComp a name.

1.3.3 Example: a beam with an embedded sphere

Figure 1.5: FemEmbeddedSphere model loaded into ArtiSynth.

A complete model demonstrating embedding a mesh is given below.

1 package artisynth.demos.tutorial;
2
3 import java.awt.Color;
4 import java.io.IOException;
5
6 import maspack.geometry.*;
7 import maspack.render.RenderProps;
8 import artisynth.core.mechmodels.Collidable.Collidability;
9 import artisynth.core.femmodels.*;
10 import artisynth.core.femmodels.FemModel.SurfaceRender;
11 import artisynth.core.materials.LinearMaterial;
12 import artisynth.core.mechmodels.MechModel;
13 import artisynth.core.workspace.RootModel;
14
15 public class FemEmbeddedSphere extends RootModel {
16
17    // Internal components
18    protected MechModel mech;
19    protected FemModel3d fem;
20    protected FemMeshComp sphere;
21
22    @Override
23    public void build(String[] args) throws IOException {
24       super.build(args);
25
26       mech = new MechModel("mech");
27       addModel(mech);
28
29       fem = new FemModel3d("fem");
30       mech.addModel(fem);
31
32       // Build hex beam and set properties
33       double[] size = {0.4, 0.4, 0.4};
34       int[] res = {4, 4, 4};
35       FemFactory.createHexGrid (fem,
36          size[0], size[1], size[2], res[0], res[1], res[2]);
37       fem.setParticleDamping(2);
38       fem.setDensity(10);
39       fem.setMaterial(new LinearMaterial(4000, 0.33));
40
41       // Add an embedded sphere mesh
42       PolygonalMesh sphereSurface = MeshFactory.createOctahedralSphere(0.15, 3);
43       sphere = fem.addMesh("sphere", sphereSurface);
44       sphere.setCollidable (Collidability.EXTERNAL);
45
46       // Boundary condition: fixed LHS
47       for (FemNode3d node : fem.getNodes()) {
48          if (node.getPosition().x == -0.2) {
49             node.setDynamic(false);
50          }
51       }
52
53       // Set rendering properties
54       setFemRenderProps (fem);
55       setMeshRenderProps (sphere);
56    }
57
58    // FEM render properties
59    protected void setFemRenderProps ( FemModel3d fem ) {
60       fem.setSurfaceRendering (SurfaceRender.Shaded);
61       RenderProps.setLineColor (fem, Color.BLUE);
62       RenderProps.setFaceColor (fem, new Color (0.5f, 0.5f, 1f));
63       RenderProps.setAlpha(fem, 0.2);   // transparent
64    }
65
66    // FemMeshComp render properties
67    protected void setMeshRenderProps ( FemMeshComp mesh ) {
68       mesh.setSurfaceRendering( SurfaceRender.Shaded );
69       RenderProps.setFaceColor (mesh, new Color (1f, 0.5f, 0.5f));
70       RenderProps.setAlpha (mesh, 1.0);   // opaque
71    }
72
73 }

This example can be found in artisynth.demos.tutorial.FemEmbeddedSphere. The model is very similar to FemBeam. A MechModel and FemModel3d are created and added. At line 41, a PolygonalMesh of a sphere is created using a factory method. The sphere is already centered inside the beam, so it does not need to be repositioned. At Line 42, the sphere is embedded inside model fem, creating a FemMeshComp with the name “sphere”. The full model is shown in Figure 1.5.

1.4 Connecting FEM models to other components

To couple FEM models to other dynamic components, the “attachment” mechanism described in Section LABEL:PhysicsSimulation:sec is used. This involves creating and adding to the model attachment components, which are instances of DynamicAttachment, as described in Section LABEL:Attachments:sec. Common point-based attachment classes are listed in Table 1.4.

Table 1.4: Point-based attachments
Attachment Description
PointParticleAttachment Attaches one “point” to one “particle”
PointFrameAttachment Attaches one “point” to one “frame”
PointFem3dAttachment Attaches one “point” to a linear combination of FEM nodes

FEM models are connected to other model components by attaching their nodes to various components. This can be done by creating an attachment object of the appropriate type, and then adding it to the MechModel using

  addAttachment (DynamicAttachment attach); // adds an attachment constraint

There are also convenience routines inside MechModel that will create the appropriate attachments automatically (see Section LABEL:sec:mech:pointattachments).

All attachments described in this section are based around FEM nodes. However, it is also possible to attach frame-based components (such as rigid bodies) directly to an FEM, as described in Section 1.6.

1.4.1 Connecting nodes to rigid bodies or particles

Since FemNode3d is a subclass of Particle, the same methods described in Section LABEL:sec:mech:pointattachments for attaching particles to other particles and frames are available. For example, we can attach an FEM node to a rigid body using a either a statement of the form

   mech.addAttachment (new PointFrameAttachment(body, node));

or the following equivalent statement which does the same thing:

   mech.attachPoint (node, body);

Both of these create a PointFrameAttachment between a rigid body (called body) and an FEM node (called node) and then adds it to the MechModel mech.

One can also attach the nodes of one FEM model to the nodes of another using statements like

   mech.addAttachment (new PointParticle (node1, node2));

or

   mech.attachPoint (node2, node1);

which attaches node2 to node1.

1.4.2 Example: connecting a beam to a block

Figure 1.6: FemBeamWithBlock model loaded into artisynth.

The following model demonstrates attaching an FEM beam to a rigid block.

1 package artisynth.demos.tutorial;
2
3 import java.io.IOException;
4
5 import maspack.matrix.RigidTransform3d;
6 import artisynth.core.femmodels.FemNode3d;
7 import artisynth.core.mechmodels.PointFrameAttachment;
8 import artisynth.core.mechmodels.RigidBody;
9
10 public class FemBeamWithBlock extends FemBeam {
11
12    public void build (String[] args) throws IOException {
13
14       // Build simple FemBeam
15       super.build (args);
16
17       // Create a rigid block and move to the side of FEM
18       RigidBody block = RigidBody.createBox (
19          "block", width/2, 1.2*width, 1.2*width, 2*density);
20       mech.addRigidBody (block);
21       block.setPose (new RigidTransform3d (length/2+width/4, 0, 0));
22
23       // Attach right-side nodes to rigid block
24       for (FemNode3d node : fem.getNodes()) {
25          if (node.getPosition().x >= length/2-EPS) {
26             mech.addAttachment (new PointFrameAttachment (block, node));
27          }
28       }
29    }
30
31 }

This model extends the FemBeam example of Section 1.2.5. The build() method then creates and adds a RigidBody block (lines 18–20). On line 21, the block is repositioned to the side of the beam to prepare for the attachment. On lines 24–28, all right-most nodes of the beam are then set to be attached to the block using a PointFrameAttachment. In this case, the attachments are explicitly created. They could also have been attached using

   mech.attachPoint (node, block);  // attach node to rigid block

1.4.3 Connecting nodes directly to elements

Typically, nodes do not align in a way that makes it possible to connect them to other FEM models and/or points based on simple point-to-node attachments. Instead, we use a different mechanism that allows us to attach a point to an arbitrary location within an FEM model. This is done using an attachment component of type PointFem3dAttachment, which implements an attachment where the position {\bf p} and velocity {\bf u} of the attached point is determined by a weighted sum of the positions {\bf x}_{k} and velocities {\bf u}_{k} of selected fem nodes:

{\bf p}=\sum\alpha_{k}{\bf x}_{k} (1.3)

Any force {\bf f} acting on the attached point is then propagated back to the nodes, according to the relation

{\bf f}_{k}=\alpha_{k}{\bf f} (1.4)

where {\bf f}_{k} is the force acting on node k due to {\bf f}. This relation can be derived based on the conservation of energy. If {\bf p} is embedded within a single element, then the {\bf x}_{k} are simply the element nodes and the \alpha_{i} are corresponding shape function values; this is known as an element-based attachment. On the other hand, as described below, it is sometimes desirable to form an attachment using a more general set of nodes that extends beyond a single element; this is known as a nodal-based attachment (Section 1.4.8).

An element-based attachment can be created using a code fragment of the form

   PointFem3dAttachment ax = new PointFem3dAttachment(pnt);
   ax.setFromElement (pnt.getPosition(), elem);
   mech.addAttachment (ax);

First, a PointFem3dAttachment is created for the point pnt. Next, setFromElement() is used to determine the nodal weights within the element elem for the specified position (which in this case is simply the point’s current position). To do this, it computes the “natural coordinates” coordinates of the position within the element. For this to be guaranteed to work, the position should be on or inside the element. If natural coordinates cannot be found, the method will return false and the nearest estimates coordinates will be used instead. However, it is sometimes possible to find natural coordinates outside a given element as long as the shape functions are well-defined. Finally, the attachment is added to the model.

More conveniently, the exact same functionality is provided by the attachPoint() method in MechModel:

   mech.attachPoint (pnt, elem);

This creates an attachment identical to that created by the previous code fragment.

Often, one does not want to have to determine the element to which a point should be attached. In that case, one can call

   PointFem3dAttachment ax = new PointFem3dAttachment(pnt);
   ax.setFromFem (pnt.getPosition(), fem);
   mech.addAttachment (ax);

or, equivalently,

   mech.attachPoint (pnt, fem);

This will find the nearest element to the node in question and use that to create the attachment. If the node is outside the FEM model, then it will be attached to the nearest point on the FEM’s surface.

1.4.4 Example: connecting two FEMs together

Figure 1.7: FemBeamWithFemSphere model loaded into ArtiSynth.

The following model demonstrates how to attach two FEM models together:

1 package artisynth.demos.tutorial;
2
3 import java.io.IOException;
4
5 import maspack.matrix.RigidTransform3d;
6 import artisynth.core.femmodels.*;
7 import artisynth.core.materials.LinearMaterial;
8 import maspack.util.PathFinder;
9
10 public class FemBeamWithFemSphere extends FemBeam {
11
12    public void build (String[] args) throws IOException {
13
14       // Build simple FemBeam
15       super.build (args);
16
17       // Create a FEM sphere
18       FemModel3d femSphere = new FemModel3d("sphere");
19       mech.addModel(femSphere);
20       // Read from TetGen file
21       TetGenReader.read(femSphere,
22          PathFinder.getSourceRelativePath(FemModel3d.class, "meshes/sphere2.1.node"),
23          PathFinder.getSourceRelativePath(FemModel3d.class, "meshes/sphere2.1.ele"));
24       femSphere.scaleDistance(0.22);
25       // FEM properties
26       femSphere.setDensity(10);
27       femSphere.setParticleDamping(2);
28       femSphere.setMaterial(new LinearMaterial(4000, 0.33));
29
30       // Reposition FEM to side of beam
31       femSphere.transformGeometry( new RigidTransform3d(length/2+width/2, 0, 0) );
32
33       // Attach sphere nodes that are inside beam
34       for (FemNode3d node : femSphere.getNodes()) {
35          // Find element containing node (if exists)
36          FemElement3d elem = fem.findContainingElement(node.getPosition());
37          // Add attachment if node is inside "fem"
38          if (elem != null) {
39             mech.attachPoint(node, elem);
40          }
41       }
42
43       // Set render properties
44       setRenderProps(femSphere);
45
46    }
47
48 }

This example can be found in artisynth.demos.tutorial.FemBeamWithFemSphere. The model extends FemBeam, adding a finite element sphere and coupling them together. The sphere is created and added on lines 18–28. It is read from TetGen-generated files using the TetGenReader class. The model is then scaled to match the dimensions of the current model, and transformed to the right side of the beam. To create attachments, the code first checks for any nodes that belong to the sphere that fall inside the beam using the FemModel3d.findContainingElement(Point3d) method (line 36), which returns the containing element if the point is inside the model, or null if the point is outside. Internally, this spatial search uses a bounding volume hierarchy for efficiency (see BVTree and BVFeatureQuery). If the point is contained within the beam, then mech.attachPoint() is used to attach it to the nodes of the element (line 39).

1.4.5 Finding which nodes to attach

While it is straightforward to connect nodes to rigid bodies or other FEM nodes or elements, it is often necessary to determine which nodes to attach. This was evident in the example of Section 1.4.4, which attached nodes of an FEM sphere that were found to be inside an FEM beam.

As in that example, finding the nodes to attach can often be done using geometric queries. For example, we often select nodes based on how close they are to the other body we wish to attach to.

Various prolixity queries are available for this task. To find the distance of a point to a polygonal mesh, we can use the following PolygonalMesh methods,

double distanceToPoint(Point3d pnt)

Returns the distance of pnt to the mesh.

int pointIsInside (Point3d pnt)

Returns true if pnt is inside the mesh.

where the latter method returns 1 if the point is inside and 0 otherwise. For checking the distance of an FEM node, pnt can be obtained from node.getPosition() (or possibly node.getRestPosition()). For example, to find all nodes within a distance tol of the surface of a rigid body, we could use the code fragment:

   RigidBody body;
   FemModel3d fem;
   ...
   double tol = 0.001;
   PolygonalMesh surface = body.getSurfaceMesh();
   ArrayList<FemNode3d> nearNodes = new ArrayList<FemNode3d>();
   for (FemNode3d n : fem.getNodes()) {
      if (surface.distanceToPoint (n.getPosition()) < tol) {
         nearNodes.add (n);
      }
   }

If we want to check only nodes that lie on the FEM surface, then we can filter them using the FemModel3d method isSurfaceNode():

   for (FemNode3d n : fem.getNodes()) {
      if (fem.isSurfaceNode (n) &&
          surface.distanceToPoint (n.getPosition()) < tol) {
         nearNodes.add (n);
      }
   }

Most of the mesh-based query methods work only for triangular meshes. The PolygonalMesh method isTriangular() can be used to determine if the mesh is triangular. If it is not, it can made triangular by calling triangulate(), although in general this should be done during model construction before the mesh-based component has been added to the model.

For connecting an FEM model to another FEM model, FemModel3d provides a number of query methods:

Nearest element queries:
FemElement3dBase findNearestElement( MPoint3d near, Point3d pnt)

Find nearest element (shell or volumetric) to pnt.

FemElement3dBase findNearestElement( MPoint3d near, Point3d pnt, ElementFilter filter)

Find nearest filtered element (shell or volumetric) to pnt.

FemElement3dBase findNearestSurfaceElement( MPoint3d near, Point3d pnt)

Find nearest surface element (shell or volumetric) to pnt.

FemElement3d findNearestVolumetricElement ( MPoint3d near, Point3d pnt)

Find nearest volumetric element to pnt.

ShellElement3d findNearestShellElement ( MPoint3d near, Point3d pnt)

Find nearest shell element to pnt.

FemElement3d findContainingElement(Point3d pnt)

Find volumetric element (if any) containing pnt.

Nearest node queries:
FemNode3d findNearestNode ( MPoint3d pnt, double maxDist)

Find nearest node to pnt that is within maxDist.

ArrayList<FemNode3d> findNearestNodes ( MPoint3d pnt, double maxDist)

Find all nodes that are within maxDist of pnt.

All the above queries are based on the FEM model’s current nodal positions. The method
findNearestElement(near,pnt,filter) allows the application to specify a FemModel.ElementFilter to restrict the elements that are searched.

The argument near that appears in some of the queries is an optional argument which, if not null, returns the location of the corresponding nearest point on the element. The distance from pnt to the element can then be found using

   near.distance (pnt);

If the resulting distance is 0, then the point is on or inside the element. Otherwise, the point is outside the element, and if no element filters were used in the query, outside the FEM model itself.

Typically, it is preferred attach a point to an element only if it lies on or inside an element. However, it is possible to attach points outside an element as long as the system is able to determine appropriate element “coordinates” for that point (which it may not be able to do if the point is far away). In addition, the motion behavior of an exterior attached point may sometimes appear counterintuitive.

The FemModel3d element and node queries can be used in a variety of ways.

findNearestNodes() can be used to find all nodes within a certain distance of a point, as part of the process of making nodal-based attachments (Section 1.4.8).

findNearestNode() is used in the FemMuscleBeam example (Section 1.9.5) to determine if a desired muscle point is near enough to a node to use that node directly, or if a marker should be created.

As another example, suppose we wish to connect the surface nodes of an FEM model femA to the surface elements of another model femB if they lie within a prescribed distance tol of the surface of femB. Then we could use the following code:

   MechModel mech;
   FemModel3d femA;
   FemModel3d femB;
   ...
   double tol = 0.001;
   Point3d near = new Point3d();
   for (FemNode3d n : femA.getNodes()) {
      if (femA.isSurfaceNode(n)) {
         FemElement3dBase elem =
            femB.findNearestSurfaceElement (near, n.getPosition());
         if (elem != null && near.distance(n.getPosition()) <= tol) {
            // attach if within distance
            mech.attachPoint (n, elem);
         }
      }
   }

Finally, it is possible to identify nodes on the surface of an FEM model according to whether they belong to specific features, such as a smooth patch or a sharp edge line. Methods for doing this are provided as static methods in the class FemQuery, and include:

Feature based node queries:
ArrayList<FemNode3d> findPatchNodes ( MFemModel3d fem, FemNode3d node0, double maxBendAng)

Find nodes in patch defined by a maximum bend angle.

ArrayList<FemNode3d> findPatchBoundaryNodes ( MFemModel3d fem, FemNode3d node0, double maxBendAng)

Find the border nodes of a patch.

ArrayList<FemNode3d> findEdgeLineNodes ( MFemModel3d fem, FemNode3d node0, double minBendAng, Mdouble maxEdgeAng, double allowBranching)

Find the nodes along an edge defined by a minimum bend angle.

Details of how these methods work are given in their API documentation. They use the notion of a bend angle, which is the absolute value of the angle between two faces about their common edge. A patch is defined by a collection of faces whose bend angles do not exceed a minimum value, while an edge line is a collection of edges with bend angles not below a maximum value. The feature methods start with an initial node (node0) and then grow the requested feature out from there. For example, suppose we have a regular hexahedral FEM grid, and we wish to find all the nodes on one of the faces. If we know one of the nodes on the face, then we can find all of the nodes using findPatchNodes:

  FemModel3d fem =
     FemFactory.createHexGrid (null, 1.0, 0.5, 0.5, 40, 20, 20);
  // find any point on the left face
  FemNode3d node0 = fem.findNearestNode (new Point3d (-0.5, 0, 0), /*tol=*/1e-8);
  // use this to find all nodes on the left face
  ArrayList<FemNode3d> nodes =
     FemQuery.findPatchNodes (fem, node0, /*maxBendAngle=*/Math.toRadians(10));

Note that the feature query above uses a maximum bend angle of 10^{\circ}. Because grid faces are flat, this choice is somewhat arbitrary; any angle larger than 0 (within machine precision) would also work.

1.4.6 Selecting nodes in the viewer

Often, it is most convenient to select nodes in the ArtiSynth viewer. For this, a node selection tool is available (Figure 1.8), as described in the section “Selecting FEM nodes” of the ArtiSynth User Interface Guide). It allows nodes to be selected in various ways, including the usual click, drag box and elliptical selection tools, as well as specialized operations that select nodes based on patches, edge lines, and distances to other bodies. Once nodes have been selected, their numbers can be saved to a node number file which can then be read by a model’s build() method to determine which nodes to connect to some body.

Figure 1.8: FEM node selection tool, described in detail in the User Interface Guide. It allows nodes to be selected, and the selected nodes to be saved to (or loaded from) a node number file.

Node number files are text files with a very simple format, consisting of integers (the node numbers), separated by white space. There is no limit to how many numbers can appear on a line but typically this is limited to ten or so to make the file more readable. Optionally, the numbers can be surrounded by square brackets ([ ]). The special character ‘#’ is a comment character, commenting out all characters from itself to the end of the current line. For a file containing the node numbers 2, 12, 4, 8, 23 and 47, the following formats are all valid:

2 12 4 8 23 47
[ 2 12 4 8 23 47 ]
# this is a node number file
[ 2 12 4 8
  23 47
]

Once node numbers have been identified in the viewer and saved in a file, they can be read by the build() method using a NodeNumberReader. For convenience, this class supplies two static methods for extracting the FEM nodes specified by the numbers in a file:

static ArrayList<FemNode3d> read( MFile file, FemModel3d fem)

Returns nodes in fem corresponding to file’s numbers.

static ArrayList<FemNode3d> read( MString filePath, FemModel3d fem)

Returns nodes in fem corresponding to file’s numbers.

Extracted nodes can be used to set boundary conditions or form connections with other bodies. For example, suppose we wish to connect a face of an FEM model fem to a rigid body block, using a set of nodes specified in a file called faceNodes.txt. A code fragment to accomplish this could be the following:

  FemModel3d fem;   // FEM model to attach
  RigidBody block; // block to attach fem to
  MechModel mech;   // mech model containing fem and block
  ...
  // Get the file path. Assume it is in a folder "geometry" beneath the
  // model source folder:
  String filePath = getSourceRelativePath ("geometry/faceNodes.txt");
  // Read the nodes and attach them to block. Wrap the code in a try/catch
  // block to handle I/O errors
  try {
     ArrayList<FemNode3d> nodes = NodeNumberReader.read (filePath, fem);
     for (FemNode3d n : nodes) {
        mech.attachPoint (n, block);
     }
  }
  catch (IOException e) {
     System.out.println ("Couldn’t read node file " + filePath);
  }

The process of selecting nodes in the viewer, saving them in a file, and using them in a model can be done iteratively: if the selected nodes need to be adjusted, one can reopen the node selection tool, load the selection from file, adjust the selection, and resave the file, without needing to make any modifications to the model’s build() code.

If desired, one can also determine a set of nodes in code, and then write their numbers to a file using the class NodeNumberWriter, which supplies static methods for writing number files:

static void write( MString filePath, Collection<FemNode3d> nodes)

Writes the numbers of nodes to the specified file.

static void write( MFile file, Collection<FemNode3d> nodes)

Writes the numbers of nodes to file.

static void write( MFile file, Collection<FemNode3d> nodes, Mint maxCols, int flags)

Writes the numbers of nodes to file, using the specified number of columns and format flags.

For example:

  FemModel3d fem;   // FEM model
  ...
  // find the nodes to write:
  ArrayList<FemNode3d> nodes = new ArrayList<>();
  for (FemNode3d n : nodes) {
     if (n satisfies appropriate criteria) {
        nodes.add (n);
     }
  }
  // write the node numbers, using a try/catch block to handle I/O errors
  String filePath = getSourceRelativePath ("geometry/special.txt");
  try {
     NodeNumberWriter.write (filePath, nodes);
  }
  catch (IOException e) {
     System.out.println ("Couldn’t write node file " + filePath);
  }

1.4.7 Example: two bodies connected by an FEM “spring”

Figure 1.9: LumbarFEMDisk loaded into ArtiSynth, showing a simplified FEM model connecting two vertebrae.

The LumbarFrameSpring example in Section LABEL:LumbarFrameSpring:sec uses a frame spring to connect two simplified lumbar vertebrae. However, it is also possible to use an FEM model in place of a frame spring, possibly providing a more realistic model of the intervertebral disc. A simple model which does this is defined in

  artisynth.demos.tutorial.LumbarFEMDisk

The initial source code is similar to that for LumbarFrameSpring, but differs in the section where the FEM disk replaces the FrameSpring:

56       // create a torus shaped FEM model for the disk
57       FemModel3d fem = new FemModel3d();
58       fem.setDensity (1500);
59       fem.setMaterial (new LinearMaterial (20000, 0.4));
60       FemFactory.createHexTorus (fem, 0.011, 0.003, 0.008, 16, 30, 2);
61       mech.addModel (fem);
62
63       // position it betweem the disks
64       double DTOR = Math.PI/180.0;
65       fem.transformGeometry (
66          new RigidTransform3d (-0.012, 0.0, 0.040, 0, -DTOR*25, DTOR*90));
67
68       // find and attach nearest nodes to either the top or bottom mesh
69       double tol = 0.001;
70       for (FemNode3d n : fem.getNodes()) {
71          // top vertebra
72          double d = lumbar1.getSurfaceMesh().distanceToPoint(n.getPosition());
73          if (d < tol) {
74             mech.attachPoint (n, lumbar1);
75          }
76          // bottom vertebra
77          d = lumbar2.getSurfaceMesh().distanceToPoint(n.getPosition());
78          if (d < tol) {
79             mech.attachPoint (n, lumbar2);
80          }
81       }
82       // set render properties ...
83       fem.setSurfaceRendering (SurfaceRender.Shaded);
84       RenderProps.setFaceColor (fem, new Color (153/255f, 153/255f, 1f));
85       RenderProps.setFaceColor (mech, new Color (238, 232, 170)); // bone color
86    }
87 }

The simplified FEM model representing the “disk” is created at lines 57-61, using a torus-shaped model created by FemFactory. It is then repositioning using transformGeometry() ( Section LABEL:TransformingGeometry:sec) to place it between the vertebrae (line 64-66). After the FEM model is positioned, we find which nodes are within a distance tol of each vertebral surface and attach them to the appropriate body (lines 69-81).

To run this example in ArtiSynth, select All demos > tutorial > LumbarFEMDisk from the Models menu. The model should load and initially appear as in Figure 1.9. The behavior is best seem by running the model and using the pull controller to exert forces on the upper vertebra.

1.4.8 Nodal-based attachments

The example of Section 1.4.4 uses element-based attachments to connect the nodes of one FEM to elements of another. As mentioned above, element-based attachments assume that the attached point is associated with a specific FEM model element. While this often gives good results, there are situations where it may be desirable to distribute the connection more broadly among a larger set of nodes.

In particular, this is sometimes the case when connecting FEM models to point-to-point springs. The end-point of such a spring may end up exerting a large force on the FEM, and then if the number of nodes to which the end-point is attached are too small, the resulting forces on these nodes (Equation 1.4) may end up being too large. In other words, it may be desirable to distribute the spring’s force more evenly throughout the FEM model.

To handle such situations, it is possible to create a nodal-based attachment in which the nodes and weights are explicitly specified. This involves explicitly creating a PointFem3dAttachment for the point or particle to be attached and the specifying the nodes and weights directly,

   PointFem3dAttachment ax = new PointFem3dAttachment (part);
   ax.setFromNodes (nodes, weights);
   mech.addAttachment (ax);

where nodes and weights are arrays of FemNode and double, respectively. It is up to the application to determine these.

PointFem3dAttachment provides several methods for explicitly specifying nodes and weights. The signatures for these include:

  void setFromNodes (FemNode[] nodes, double[] weights)
  void setFromNodes (Collection<FemNode> nodes, VectorNd weights)
  boolean setFromNodes (Point3d pos, FemNode[] nodes)
  boolean setFromNodes (Point3d pos, Collection<FemNode> nodes)

The last two methods determine the weights automatically, using an inverse-distance-based calculation in which each weight \alpha_{k} is initially computed as

\alpha_{k}=\frac{d_{\text{max}}}{d_{k}+d_{max}} (1.4)

where d_{k} is the distance from node k to pos and d_{\text{max}} is the maximum distance. The weights are then adjusted to ensure that they sum to one and that the weighted sum of the nodes equals pos. In some cases, the specified nodes may not provide enough support for the last condition to be met, in which case the methods return false.

1.4.9 Example: element vs. nodal-based attachments

Figure 1.10: PointFemAttachment loaded into ArtiSynth and run until stable. The top and bottom models are connected to their springs using element and nodal-based attachments, respectively. The nodes associated with each attachment are rendered as white spheres.

The model demonstrating the difference between element and nodal-based attachments is defined in

  artisynth.demos.tutorial.PointFemAttachment

It creates two FEM models, each with a single point-to-point spring attached to a particle at their center. The model at the top (fem1 in the code below) is connected to the particle using an element-based attachment, while the lower model (fem2 in the code) is connected using a nodal-based attachment with a larger number of nodes. Figure 1.10 shows the result after the model is run until stable. The element-based attachment results in significantly higher deformation in the immediate vicinity around the attachment, while for the nodal-based attachment, the deformation is distributed much more evenly through the model.

The build method and some of the auxiliary code for this model is shown below. Code for the other auxiliary methods, including addFem(), addParticle(), addSpring(), and setAttachedNodesWhite(), can be found in the actual source file.

1    // Filter to select only elements for which the nodes are entirely on the
2    // positive side of the x-z plane.
3    private class MyFilter extends ElementFilter {
4       public boolean elementIsValid (FemElement e) {
5          for (FemNode n : e.getNodes()) {
6             if (n.getPosition().y < 0) {
7                return false;
8             }
9          }
10          return true;
11       }
12    }
13
14    // Collect and return all the nodes of an FEM model associated with a
15    // set of elements specified by an array of element numbers
16    private HashSet<FemNode3d> collectNodes (FemModel3d fem, int[] elemNums) {
17       HashSet<FemNode3d> nodes = new LinkedHashSet<FemNode3d>();
18       for (int i=0; i<elemNums.length; i++) {
19          FemElement3d e = fem.getElements().getByNumber (elemNums[i]);
20          for (FemNode3d n : e.getNodes()) {
21             nodes.add (n);
22          }
23       }
24       return nodes;
25    }
26
27    public void build (String[] args) {
28       MechModel mech = new MechModel ("mech");
29       addModel (mech);
30       mech.setGravity (0, 0, 0); // turn off gravity
31
32       // create and add two FEM beam models centered at the specified locations
33       FemModel3d fem1 = addFem (mech,  0.0, 0.0,  0.25);
34       FemModel3d fem2 = addFem (mech,  0.0, 0.0, -0.25);
35
36       // reconstruct the FEM surface meshes so that they show only elements on
37       // the positive side of the x-y plane. Also, set surface rendering to
38       // show strain values.
39       fem1.createSurfaceMesh (new MyFilter());
40       fem1.setSurfaceRendering (SurfaceRender.Strain);
41       fem2.createSurfaceMesh (new MyFilter());
42       fem2.setSurfaceRendering (SurfaceRender.Strain);
43
44       // create and add the particles for the point-to-point springs
45       // that will apply forces to each FEM.
46       Particle p1 = addParticle (mech,  0.9, 0.0,  0.25);
47       Particle p2 = addParticle (mech,  0.9, 0.0, -0.25);
48       Particle m1 = addParticle (mech,  0.0, 0.0,  0.25);
49       Particle m2 = addParticle (mech,  0.0, 0.0, -0.25);
50
51       // attach spring end-point to fem1 using an element-based marker
52       mech.attachPoint (m1, fem1);
53
54       // attach spring end-point to fem2 using a larger number of nodes, formed
55       // from the node set for elements 22, 31, 40, 49, and 58. This is done by
56       // explicitly creating the attachment and then setting it to use the
57       // specified nodes
58       HashSet<FemNode3d> nodes =
59          collectNodes (fem2, new int[] { 22, 31, 40, 49, 58 });
60
61       PointFem3dAttachment ax = new PointFem3dAttachment (m2);
62       ax.setFromNodes (m2.getPosition(), nodes);
63       mech.addAttachment (ax);
64
65       // finally, create the springs
66       addSpring (mech, /*stiffness=*/10000, p1, m1);
67       addSpring (mech, /*stiffness=*/10000, p2, m2);
68
69       // set the attachments nodes for m1 and m2 to render as white spheres
70       setAttachedNodesWhite (m1);
71       setAttachedNodesWhite (m2);
72       // set render properties for m1
73       RenderProps.setSphericalPoints (m1, 0.015, Color.GREEN);
74    }

The build() method begins by creating a MechModel and then adding to it two FEM beams (created using the auxiliary method addFem(). Rendering of each FEM model’s surface is then set up to show strain values (setSurfaceRendering(), lines 41 and 43). The surface meshes themselves are also redefined to exclude the frontmost elements, allowing the strain values to be displayed closer model centers. This redefinition is done using calls to createSurfaceMesh() (lines 40, 41) with a custom ElementFilter defined at lines 3-12.

Next, the end-point particles for the axial springs are created (using the auxiliary method addParticle(), lines 46-49), and particle m1 is attached to fem1 using mech.attachPoint() (line 52), which creates an element-based attachment at the point’s current location. Point m2 is then attached to fem2 using a nodal-based attachment. The nodes for these are collected as the union of all nodes for a specified set of elements (lines 58-59, and the method collectNodes() defined at lines 16-25). These are then used to create a nodal-based attachment (lines 61-63), where the weights are determined automatically using the method associated with equation (1.4).

Finally, the springs are created (auxiliary method addSpring(), lines 66-67), the nodes associated for each attachment are set to render as white spheres (setAttachedNodesWhites(), lines 70-71), and the particles are set to render as green spheres.

To run this example in ArtiSynth, select All demos > tutorial > PointFemAttachment from the Models menu. Running the model will cause it to settle into the state shown in Figure 1.10. Selecting and dragging one of the spring anchor points at the right will cause the spring tension to vary and further illustrate the difference between the element and nodal-based attachments.

1.5 FEM markers

Just as there are FrameMarkers to act as anchor points on a frame or rigid body (Section LABEL:FrameMarkers:sec), there are also FemMarkers that can mark a point inside a finite element. They are frequently used to provide anchor points for attaching springs and forces to a point inside an element, but can also be used for graphical purposes.

FEM markers are implemented by the class FemMarker, which is a subclass of Point. They are essentially massless points that contain their own attachment component, so when creating and adding a marker there is no need to create a separate attachment component.

Within the component hierarchy, FEM markers are typically stored in the markers list of their associated FEM model. They can be created and added using a code fragment of the form

  FemMarker mkr = new FemMarker (1, 0, 0);
  mkr.setFromFem (fem); // attach to the nearest fem element
  fem.addMarker (mkr);  // add to fem

This creates a marker at the location (1,0,0) (in world coordinates), calls setFromFem() to attach it to the nearest element in the FEM model ( which is either the containing element or the nearest element on the model’s surface), and then adds it to the markers list.

If the marker’s attachment has not already been set when addMarker() is called, then addMarker() will call setFromFem() automatically. Therefore the above code fragment is equivalent to the following:

  FemMarker mkr = new FemMarker (1, 0, 0);
  fem.addMarker (mkr);

Alternatively, one may want to explicitly specify the nodes associated with the attachment, as described in Section 1.4.8:

  FemMarker mkr = new FemMarker (1, 0, 0);
  mkr.setFromNodes (nodes, weights);
  fem.addMarker (mkr);

There are a variety of methods available to set the attachment, mirroring those available in the underlying base class PointFem3dAttachment:

  void setFromFem (FemModel3d fem)
  boolean setFromElement (FemElement3d elem)
  void setFromNodes (FemNode[] nodes, double[] weights)
  void setFromNodes (Collection<FemNode> nodes, VectorNd weights)
  boolean setFromNodes (FemNode[] nodes)
  boolean setFromNodes (Collection<FemNode> nodes)

The last two methods compute nodal weights automatically, as described in Section 1.4.8, based on the marker’s currently assigned position. If the supplied nodes do not provide sufficient support, then the methods return false.

Another set of convenience methods are supplied by FemModel3d, which combine these with the addMarker() call:

  void addMarker (FemMarker mkr, FemElement3d elem)
  void addMarker (FemMarker mkr, FemNode[] nodes, double[] weights)
  void addMarker (FemMarker mkr, Collection<FemNode> nodes, VectorNd weights)
  boolean addMarker (FemMarker mkr, FemNode[] nodes)
  boolean addMarker (FemMarker mkr, Collection<FemNode> nodes)

For example, one can do

  FemMarker mkr = new FemMarker (1, 0, 0);
  fem.addMarker (mkr, nodes, weights);

Markers are often used to track movement within an FEM model. For that, one can examine their positions and velocities, as with any other particles, using the methods

  Point3d getPosition();     // returns the current position
  Vector3d getVelocity();     // returns the current velocity

The return values from these methods should not be modified. Alternatively, when a 3D force {\bf f} is applied to the marker, it is distributed to the attached nodes according to the nodal weights, as described in Equation (1.4).

1.5.1 Example: attaching an FEM beam to a muscle

Figure 1.11: FemBeamWithMuscle model loaded into ArtiSynth.

A complete application model that employs a fem marker as an anchor for a spring is given below.

1 package artisynth.demos.tutorial;
2
3 import java.awt.Color;
4 import java.io.IOException;
5
6 import maspack.render.RenderProps;
7 import maspack.render.Renderer;
8 import artisynth.core.femmodels.FemMarker;
9 import artisynth.core.femmodels.FemModel3d;
10 import artisynth.core.materials.SimpleAxialMuscle;
11 import artisynth.core.mechmodels.Muscle;
12 import artisynth.core.mechmodels.Particle;
13 import artisynth.core.mechmodels.Point;
14
15 public class FemBeamWithMuscle extends FemBeam {
16
17    // Creates a point-to-point muscle
18    protected Muscle createMuscle () {
19       Muscle mus = new Muscle (/*name=*/null, /*restLength=*/0);
20       mus.setMaterial (
21          new SimpleAxialMuscle (/*stiffness=*/20, /*damping=*/10, /*maxf=*/10));
22       RenderProps.setLineStyle (mus, Renderer.LineStyle.SPINDLE);
23       RenderProps.setLineColor (mus, Color.RED);
24       RenderProps.setLineRadius (mus, 0.03);
25       return mus;
26    }
27
28    // Creates a FEM Marker
29    protected FemMarker createMarker (
30       FemModel3d fem, double x, double y, double z) {
31       FemMarker mkr = new FemMarker (/*name=*/null, x, y, z);
32       RenderProps.setSphericalPoints (mkr, 0.02, Color.BLUE);
33       fem.addMarker (mkr);
34       return mkr;
35    }
36
37    public void build (String[] args) throws IOException {
38
39       // Create simple FEM beam
40       super.build (args);
41
42       // Add a particle fixed in space
43       Particle p1 = new Particle (/*mass=*/0, -length/2, 0, 2*width);
44       mech.addParticle (p1);
45       p1.setDynamic (false);
46       RenderProps.setSphericalPoints (p1, 0.02, Color.BLUE);
47
48       // Add a marker at the end of the model
49       FemMarker mkr = createMarker (fem, length/2-0.1, 0, width/2);
50
51       // Create a muscle between the point an marker
52       Muscle muscle = createMuscle();
53       muscle.setPoints (p1, mkr);
54       mech.addAxialSpring (muscle);
55    }
56
57 }

This example can be found in artisynth.demos.tutorial.FemBeamWithMuscle. This model extends the FemBeam example, adding a FemMarker for the spring to attach to. The method createMarker(...) on lines 29–35 is used to create and add a marker to the FEM. Since the element is initially set to null, when it is added to the FEM, the model searches for the containing or nearest element. The loaded model is shown in Figure 1.11.

1.6 Frame attachments

It is also possible to attach frame components, including rigid bodies, directly to FEM models, using the attachment component FrameFem3dAttachment. Analogously to PointFem3dAttachment, the attachment is implemented by connecting the frame to a set of FEM nodes, and attachments can be either element-based or nodal-based. The frame’s origin is computed in the same way as for point attachments, using a weighted sum of node positions (Equation 1.3), while the orientation is computed using a polar decomposition on a deformation gradient determined from either element shape functions (for element-based attachments) or a Procrustes type analysis using nodal rest positions (for nodal-based attachments).

An element-based attachment can be created using either a code fragment of the form

   FrameFem3dAttachment ax = new FrameFem3dAttachment(frame);
   ax.setFromElement (frame.getPose(), elem);
   mech.addAttachment (ax);

or, equivalently, the attachFrame() method in MechModel:

   mech.attachFrame (frame, elem);

This attaches the frame frame to the nodes of the FEM element elem. As with PointFem3dAttachment, if the frame’s origin is not inside the element, it may not be possible to accurately compute the internal nodal weights, in which case setFromElement() will return false.

In order to have the appropriate element located automatically, one can instead use

   FrameFem3dAttachment ax = new FrameFem3dAttachment(frame);
   ax.setFromFem (frame.getPose(), fem);
   mech.addAttachment (ax);

or, equivalently,

   mech.attachFrame (frame, fem);

As with point-to-FEM attachments, it may be desirable to create a nodal-based attachment in which the nodes and weights are not tied to a specific element. The reasons for this are generally the same as with nodal-based point attachments (Section 1.4.8): the need to distribute the forces and moments acting on the frame across a broader set of element nodes. Also, element-based frame attachments use element shape functions to determine the frame’s orientation, which may produce slightly asymmetric results if the frame’s origin is located particularly close to a specific node.

FrameFem3dAttachment provides several methods for explicitly specifying nodes and weights. The signatures for these include:

  void setFromNodes (RigidTransform3d TFW, FemNode[] nodes, double[] weights)
  void setFromNodes (RigidTransform3d TFW, Collection<FemNode> nodes,
                        VectorNd weights)
  boolean setFromNodes (RigidTransform3d TFW, FemNode[] nodes)
  boolean setFromNodes (RigidTransform3d TFW, Collection<FemNode> nodes)

Unlike their counterparts in PointFem3dAttachment, the first two methods also require the current desired pose of the frame TFW (in world coordinates). This is because while nodes and weights will unambiguously specify the frame’s origin, they do not specify the desired orientation.

1.6.1 Example: attaching frames to an FEM beam

Figure 1.12: FrameFemAttachment loaded into ArtiSynth and run until stable.

A model illustrating how to connect frames to an FEM model is defined in

  artisynth.demos.tutorial.FrameFemAttachment

It creates an FEM beam, along with a rigid body block and a massless coordinate frame, that are then attached to the beam using nodal and element-based attachments. The build method is shown below:

1    public void build (String[] args) {
2
3       MechModel mech = new MechModel ("mech");
4       addModel (mech);
5
6       // create and add FEM beam
7       FemModel3d fem = FemFactory.createHexGrid (null, 1.0, 0.2, 0.2, 6, 3, 3);
8       fem.setMaterial (new LinearMaterial (500000, 0.33));
9       RenderProps.setLineColor (fem, Color.BLUE);
10       RenderProps.setLineWidth (mech, 2);
11       mech.addModel (fem);
12       // fix leftmost nodes of the FEM
13       for (FemNode3d n : fem.getNodes()) {
14          if ((n.getPosition().x-(-0.5)) < 1e-8) {
15             n.setDynamic (false);
16          }
17       }
18
19       // create and add rigid body box
20       RigidBody box = RigidBody.createBox (
21          "box", 0.25, 0.1, 0.1, /*density=*/1000);
22       mech.add (box);
23
24       // create a basic frame and set its pose and axis length
25       Frame frame = new Frame();
26       frame.setPose (new RigidTransform3d (0.4, 0, 0, 0, Math.PI/4, 0));
27       frame.setAxisLength (0.3);
28       mech.addFrame (frame);
29
30       mech.attachFrame (frame, fem); // attach using element-based attachment
31
32       // attach the box to the FEM, using all the nodes of elements 31 and 32
33       HashSet<FemNode3d> nodes = collectNodes (fem, new int[] { 22, 31 });
34       FrameFem3dAttachment attachment = new FrameFem3dAttachment(box);
35       attachment.setFromNodes (box.getPose(), nodes);
36       mech.addAttachment (attachment);
37
38       // render the attachment nodes for the box as spheres
39       for (FemNode n : attachment.getNodes()) {
40          RenderProps.setSphericalPoints (n, 0.007, Color.GREEN);
41       }
42    }

Lines 3-22 create a MechModel and populate it with an FEM beam and a rigid body box. Next, a basic Frame is created, with a specified pose and an axis length of 0.3 (to allow it to be seen), and added to the MechModel (lines 25-28). It is then attached to the FEM beam using an element-based attachment (line 30). Meanwhile, the box is attached to using a nodal-based attachment, created from all the nodes associated with elements 22 and 31 (lines 33-36). Finally, all attachment nodes are set to be rendered as green spheres (lines 39-41).

To run this example in ArtiSynth, select All demos > tutorial > FrameFemAttachment from the Models menu. Running the model will cause it to settle into the state shown in Figure 1.12. Forces can interactively be applied to the attached block and frame using the pull tool, causing the FEM model to deform (see the section “Pull Manipulation” in the ArtiSynth User Interface Guide).

1.6.2 Adding joints to FEM models

The ability to connect frames to FEM models, as described in Section 1.6, makes it possible to interconnect different FEM models directly using joints, as described in Section LABEL:JointsAndConnectors:sec. This is done internally by using FrameFem3dAttachments to connect frames C and D of the joint (Figure LABEL:jointExample:fig) to their respective FEM models.

As indicated in Section LABEL:CreatingJoints:sec, most joints have a constructor of the form

  JointType (bodyA, bodyB, TDW);

that creates a joint connecting bodyA to bodyB, with the initial pose of the D frame given (in world coordinates) by TDW. The same body and transform settings can be made on an existing joint using the method setBodies(bodyA, bodyB, TDW). For these constructors and methods, it is possible to specify FEM models for bodyA and/or bodyB. Internally, the joint then creates a FrameFem3dAttachment to connect frame C and/or D of the joint (See Figure LABEL:jointExample:fig) to the corresponding FEM model.

However, unlike joints involving rigid bodies or frames, there are no associated {\bf T}_{CA} or {\bf T}_{DB} transforms (since there is no fixed frame within an FEM to define such transforms). Methods or constructors which utilize {\bf T}_{CA} or {\bf T}_{DB} can therefore not be used with FEM models.

1.6.3 Example: two FEM beams connected by a joint

Figure 1.13: JointedFemBeams loaded into ArtiSynth and run until stable.

A model connecting two FEM beams by a joint is defined in

  artisynth.demos.tutorial.JointedFemBeams

It creates two FEM beams and connects them via a special slotted-revolute joint. The build method is shown below:

1    public void build (String[] args) {
2
3       MechModel mech = new MechModel ("mechMod");
4       addModel (mech);
5
6       double stiffness = 5000;
7       // create first fem beam and fix the leftmost nodes
8       FemModel3d fem1 = addFem (mech, 2.4, 0.6, 0.4, stiffness);
9       for (FemNode3d n : fem1.getNodes()) {
10          if (n.getPosition().x <= -1.2) {
11             n.setDynamic(false);
12          }
13       }
14       // create the second fem beam and shift it 1.5 to the right
15       FemModel3d fem2 = addFem (mech, 2.4, 0.4, 0.4, 0.1*stiffness);
16       fem2.transformGeometry (new RigidTransform3d (1.5, 0, 0));
17
18       // create a slotted revolute joint that connects the two fem beams
19       RigidTransform3d TDW = new RigidTransform3d(0.5, 0, 0, 0, 0, Math.PI/2);
20       SlottedRevoluteJoint joint = new SlottedRevoluteJoint (fem2, fem1, TDW);
21       mech.addBodyConnector (joint);
22
23       // set ranges and rendering properties for the joint
24       joint.setShaftLength (0.8);
25       joint.setMinX (-0.5);
26       joint.setMaxX (0.5);
27       joint.setSlotDepth (0.63);
28       joint.setSlotWidth (0.08);
29       RenderProps.setFaceColor (joint, myJointColor);
30    }

Lines 3-16 create a MechModel and populates it with two FEM beams, fem1 and fem2, using an auxiliary method addFem() defined in the model source file. The leftmost nodes of fem1 are set fixed. A SlottedRevoluteJoint is then created to interconnect fem1 and fem2 at a location specified by TDW (lines 19-21). Lines 24-29 set some parameters for the joint, along with various render properties.

To run this example in ArtiSynth, select All demos > tutorial > JointedFemBeams from the Models menu. Running the model will cause it drop and flex under gravity, as shown in 1.13. Forces can interactively be applied to the beams using the pull tool (see the section “Pull Manipulation” in the ArtiSynth User Interface Guide).

1.7 Incompressibility

FEM incompressibility within ArtiSynth is enforced by trying to ensure that the volume of an FEM remains locally constant. This, in turn, is accomplished by constraining nodal velocities so that the local volume change, or divergence, is zero (or close to zero). There are generally two ways to do this:

  • Hard incompressibility, which sets up explicit constraints on the nodal velocities;

  • Soft incompressibility, which uses a restoring pressure based on a potential field to try to keep the volume constant.

Both of these methods operate independently, and both can be used either separately or together. Generally speaking, hard incompressibility will result in incompressibility being more rigorously enforced, but at the cost of increased computation time and (sometimes) less stability. Soft incompressibility allows the application to control the restoring force used to enforce incompressibility, usually by adjusting the value of the bulk modulus material property. As the bulk modulus is increased, soft incompressibility starts to act more like ‘hard’ incompressibility, with an infinite bulk modulus corresponding to perfect incompressibility. However, very large bulk modulus values will generally produce stability problems.

Incompressibility is not currently implemented for shell elements. Applying hard incompressibility to a shell element will have no effect on its behavior. If soft incompressibility is applied, by supplying the element with an incompressible material, then only the deviatoric component of that material will have any effect; the dilational component will generate no stress.

1.7.1 Volume regions and locking

Both hard and soft incompressibility can be applied to different regions of local volume. From larger to smaller, these regions are:

  • Nodal - the local volume surrounding each node;

  • Element - the volume of each element;

  • Full - the volume at each integration point.

Element-based incompressibility is the standard method generally seen in the literature. However, it tends not to work well for tetrahedral meshes, because constraining the volume of each tet in a tetrahedral mesh tends to over constrain the system. This is because the number of tets in a large tetrahedral mesh is often O(5n), where n is the number of nodes, and so putting a volume constraint on each element may result in O(5n) constraints, which exceeds the 3n degrees of freedom (DOF) in the FEM. This overconstraining results in an artificially increased stiffness known as locking. Because of locking, for tetrahedrally based meshes it may be better to use nodal-based incompressibility, which creates a single volume constraint around each node, resulting in only n constraints, leaving 2n DOF to handle the remaining deformation. However, nodal-based incompressibility is computationally more costly than element-based and may not be as stable.

Generally, the best solution for incompressible problems is to use element-based incompressibility with a mesh consisting of hexahedra, or primarily hexahedra and a mix of other elements (the latter commonly being known as a hex dominant mesh). For hex-based meshes, the number of elements is roughly equal to the number of nodes, and so adding a volume constraint for each element imposes n constraints on the model, which (like nodal incompressibility) leaves 2n DOF to handle the remaining deformation.

Full incompressibility tries to control the volume at each integration point within each element, which almost always results in a large number of volumetric constraints and hence locking. It is therefore not commonly used and is provided mostly for debugging and diagnostic purposes.

1.7.2 Hard incompressibility

Hard incompressibility is controlled by the incompressible property of the FEM, which can be set to one of the following values of the enumerated type FemModel.IncompMethod:

OFF

No hard incompressibility enforced.

ELEMENT

Element-based hard incompressibility enforced (Section 1.7.1).

NODAL

Nodal-based hard incompressibility enforced (Section 1.7.1).

AUTO

Selects either ELEMENT or NODAL, with the former selected if the number of elements is less than or equal to the number of nodes.

ON

Same as AUTO.

Hard incompressibility uses explicit constraints on the nodal velocities to enforce the incompressibility, which increases computational cost. Also, if the number of constraints is too large, perturbed pivot errors may be encountered by the solver. However, hard incompressibility can in principle handle situations where complete incompressibility is required. It is equivalent to the mixed u-P formulation used in commercial FEM codes (such as ANSYS), and the Lagrange multipliers computed for the constraints are pressure impulses.

Hard incompressibility can be applied in addition to soft incompressibility, in which case it will provide additional incompressibility enforcement on top of that provided by the latter. It can also be applied to linear materials, which are not themselves able to emulate true incompressible behavior (Section 1.7.4).

1.7.3 Soft incompressibility

Soft incompressibility enforces incompressibility using a restoring pressure that is controlled by a volume-based energy potential. It is only available for FEM materials that are subclasses of IncompressibleMaterial. The energy potential U(J) is a function of the determinant J of the deformation gradient, and is scaled by the material’s bulk modulus \kappa. The restoring pressure p is given by

p=\frac{\partial U}{\partial J}. (1.5)

Different potentials can be selected by setting the bulkPotential property of the incompressible material, whose value is an instance of IncompressibleMaterial.BulkPotential. Currently there are two different potentials:

QUADRATIC

The potential and associated pressure are given by

U(J)=\frac{1}{2}\kappa(J-1)^{2},\quad p=\kappa(J-1). (1.6)
LOGARITHMIC

The potential and associated pressure are given by

U(J)=\frac{1}{2}\kappa(\ln J)^{2},\quad p=\kappa\frac{\ln J}{J} (1.7)

The default potential is QUADRATIC, which may provide slightly improved stability characteristics. However, we have not noticed significant differences between the two potentials in practice.

How soft incompressibility is applied within an FEM model is controlled by the FEM’s softIncompMethod property, which can be set to one of the following values of the enumerated type FemModel.IncompMethod:

ELEMENT

Element-based soft incompressibility enforced (Section 1.7.1).

NODAL

Nodal-based soft incompressibility enforced (Section 1.7.1).

AUTO

Selects either ELEMENT or NODAL, with the former selected if the number of elements is less than or equal to the number of nodes.

FULL

Incompressibility enforced at each integration point (Section 1.7.1).

1.7.4 Incompressibility and linear materials

Within a linear material, incompressibility is controlled by Poisson’s ratio \nu, which for isotropic materials can assume a value in the range [-1,0.5]. This specifies the amount of transverse contraction (or expansion) exhibited by the material as it compressed or extended along a particular direction. A value of 0 allows the material to be compressed or extended without any transverse contraction or expansion, while a value of 0.5 in theory indicates a perfectly incompressible material. However, setting \nu=0.5 in practice causes a division by zero, so only values close to 0.5 (such as 0.49) can be used.

Moreover, the incompressibility only applies to small displacements, so that even with \nu=0.49 it is still possible to squash a linear FEM completely flat if enough force is applied. If true incompressible behavior is desired with a linear material, then one must also use hard incompressibility (Section 1.7.2).

1.7.5 Using incompressibility in practice

As mentioned above, when modeling incompressible models, we have found that the best practice is to use, if possible, either a hex or hex-dominant mesh, along with element-based incompressibility.

Hard incompressibility allows the handling of full incompressibility but at the expense of greater computational cost and often less stability. When modeling biomechanical materials, it is often permissible to use only soft incompressibility, partly since biomechanical materials are rarely completely incompressible. When implementing soft incompressibility, it is common practice to set the bulk modulus to something like 100 times the other (deviatoric) stiffnesses of the material.

We have found stability behavior to be complex, and while hard incompressibility often results in less stable behavior, this is not always the case: in some situations the stronger enforcement afforded by hard incompressibility actually improves stability.

1.8 Varying and augmenting material behaviors

The default material used by all elements of an FEM model is supplied by the model’s material property. However, it is often the case that one wishes to specify different material behaviors for different sets of elements within an FEM model. This may be particularly true when combining volumetric and shell elements.

There are several ways to vary material behavior within a model. These include:

  • Setting an explicit material for specific elements, using their material property. An element’s material is null by default, but if set to a material, it will override the default material supplied by the FEM model. While this method is quite straightforward, it does have one disadvantage: because material settings are copied by each element, subsequent interactive or online changes to the material require resetting the material in all the affected elements.

  • Binding one or more material parameters to a field. Sometimes certain material parameters, such as stiffness quantities or direction information, may need to vary across the FEM domain. While sometimes this can be handled by setting material properties for specific elements, it may be more convenient to bind the varying properties to a field, which can specify varying values over a domain composed of either a regular grid or an FEM mesh. Only one material needs to be used, and any properties which are not bound can be adjusted interactively or online. Fields and their bindings are described in detail in Chapter LABEL:sec:fields.

  • Adding augmenting material behaviors using MaterialBundles. A material bundle may be specified either for all elements, or for a subset of them, and each provides one material (via its own material property) whose behavior is added to that of the indicated elements. This also provides an easy way to combine the behaviors of two of more materials in the same element. One also has the option of setting the material property of the certain elements to NullMaterial, so that only the augmenting material(s) are applied.

  • Adding muscle behaviors using MuscleBundles. This is analogous to using MaterialBundles, except that MuscleBundles are restricted to using an instance of a MuscleMaterial, and include support for handling the excitation value, as well as the activation directions (which usually vary across the FEM domain). MuscleBundles are only present in the FemMuscleModels subclass of FemModel3d, and are described in detail in Section 1.9.

The remainder of this section will discuss MaterialBundles.

Adding a MaterialBundle to an FEM model is illustrated by the following code fragment:

  FemMaterial extraMat;   // material to be added
  FemModel3d fem;         // FEM model
  ...
  MaterialBundle bun = new MaterialBundle ("mybundle", extraMat);
  // add volumetric elements to the bundle
  for (FemElement3d e : fem.getElements()) {
     if (/* e should be added to the bundle */) {
        bun.addElement (e);
     }
  }
  fem.addMaterialBundle (bun);

Once added, the stress computed by the bundle’s material will be added to the stress computed by any other materials which are active for the bundle’s elements.

When deciding what elements to add to a bundle, one is free to choose any means necessary. The example above inspects all the volumetric elements in the FEM. To instead inspect all the shell elements, or all volumetric and shell elements, one could use the code fragments such as the following:

  // add shell elements to the bundle
  for (ShellElement3d e : fem.getShellElements()) {
     if (/* e should be added to the bundle */) {
        bun.addElement (e);
     }
  }
  // add volumetric or shell elements to the bundle
  for (FemElement3dBase e : fem.getAllElements()) {
     if (/* e should be added to the bundle */) {
        bun.addElement (e);
     }
  }

Of course, if the elements are known through other means, then they can be added directly.

The element composition of a bundle can be controlled by the methods

  void addElement (FemElement3dBase e)        // adds an element
  boolean removeElement (FemElement3dBase e)  // removes an element
  void clearElements()                        // removes all elements
  int numElements()                           // gets the number of elements
  FemElment3dBase getElement (int idx)        // gets the idx-th element

It is also possible to create a MaterialBundle whose material is added to all the FEM elements. This can done either by using one of the following constructors

  MaterialBundle (String name, boolean useAllElements)
  MaterialBundle (String name, FemMaterial mat, boolean useAllElements)

with useAllElements set to true, or by calling the method

  void setUseAllElements (boolean enable)

When a bundle is set to use all the FEM elements, it clears its own element list, and one is not permitted to add elements using addElement(elem).

After a bundle has been created, it is possible to get or set its material property using the methods

  FemMateral getMaterial()                  // get the material
  FemMateral setMaterial (FemMaterial mat)  // set the material

Again, because materials are copied internally, any modification to a material after it has been used as an input to setMaterial() will not be noticed by the bundle. Instead, one should modify the material before calling setMaterial(), or modify the copied material which can be obtained by calling getMaterial() or by storing the value returned by setMaterial().

Finally, a MuscleBundle is a renderable component. Setting its elementWidgetSize property to a value greater than zero will cause the rendering of all its elements, using a solid widget representation of each at a scale controlled by elementWidgetSize: 0.5 for half size, 1.0 for full size, etc. The color of the widgets is controlled by the faceColor property of the bundle’s renderProps. Being able to render the elements makes it easy to select the bundle and visualize which elements it contains.

1.8.1 Example: FEM sheet with a stiff spine

Figure 1.14: MaterialBundleDemo model after being run in ArtiSynth.

A simple model demonstrating the use of material bundles is defined in

  artisynth.demos.tutorial.MaterialBundleDemo

It consists of a simple thin hexahedral sheet in which a material bundle is used to stiffen elements close to the x-axis, creating a stiff “spine”. While the same effect could be achieved by simply setting a different material property for the “spine” elements, it does provide a good example of muscle bundle usage. The model’s build method is given below:

1    public void build (String[] args) {
2       MechModel mech = new MechModel ("mech");
3       addModel (mech);
4
5       // create a fem model consisting of a thin sheet of hexes
6       FemModel3d fem = FemFactory.createHexGrid (null, 1.0, 1.0, 0.1, 10, 10, 1);
7       fem.setDensity (1000);
8       fem.setMaterial (new LinearMaterial (10000, 0.45));
9       mech.add (fem);
10       // fix the left-most nodes:
11       double EPS = 1e-8;
12       for (FemNode3d n : fem.getNodes()) {
13          if (n.getPosition().x <= -0.5+EPS) {
14             n.setDynamic (false);
15          }
16       }
17       // create a "spine" of stiffer elements using a MaterialBundle with a
18       // stiffer material
19       MaterialBundle bun =
20          new MaterialBundle ("spine", new NeoHookeanMaterial (5e6, 0.45), false);
21       for (FemElement3d e : fem.getElements()) {
22          // use element centroid to determine which elements are on the "spine"
23          Point3d pos = new Point3d();
24          e.computeCentroid (pos);
25          if (Math.abs(pos.y) <= 0.1+EPS) {
26             bun.addElement (e);
27          }
28       }
29       fem.addMaterialBundle (bun);
30
31       // add a control panel to control both the fem and bundle materials,
32       // as well as the fem and bundle widget sizes
33       ControlPanel panel = new ControlPanel();
34       panel.addWidget ("fem material", fem, "material");
35       panel.addWidget ("fem widget size", fem, "elementWidgetSize");
36       panel.addWidget ("bundle material", bun, "material");
37       panel.addWidget ("bundle widget size", bun, "elementWidgetSize");
38       addControlPanel (panel);
39
40       // set rendering properties, using element widgets
41       RenderProps.setFaceColor (fem, new Color (0.7f, 0.7f, 1.0f));
42       RenderProps.setFaceColor (bun, new Color (0.7f, 1.0f, 0.7f));
43       bun.setElementWidgetSize (0.9);

Lines 6-9 create a thin FEM hex sheet, centered on the origin, with size 1\times 1\times 0.1 and 10\times 10\times 1 elements along each axis. Its material is set to a linear material with a Young’s modulus of 10000. The leftmost nodes are then fixed (lines 11-16).

Next, a MuscleBundle is added and used to apply an additional material to the elements near the x axis (lines 19-29). It is given a neo-hookean material with a much higher stiffness, and the “spine” elements for it are selected by finding those whose centroids have a y value within 0.1 of the x axis.

After the bundle is added, a control panel is created allowing interactive control of the materials for both the FEM model and the bundle, along with their elementWidgetSize properties.

Finally, some render properties are set (lines 41-44). The idea is to render the elements of both the FEM model and the bundle using element widgets (both of which will be visible since there is no surface rendering and the element widget sizes for both are greater than 0). The widget size for the bundle is made larger than that of the FEM model to ensure its widgets will cover those of the latter. Widget colors are controlled by the FEM and bundle face colors, set in lines 41-42.

The example can be run in ArtiSynth by selecting All demos > tutorial > MaterialBundleDemo from the Models menu. When it is run, the sheet will fall under gravity but be much stiffer along the spine (Figure 1.14). The control panel can be used to interactively adjust the material parameters for both the FEM and the bundle. This is one advantage of using bundles: the same material can be used to control multiple elements. The panel also allows interactive adjustment of the widget sizes, to illustrate what they look like and how they are rendered.

1.9 Muscle activated FEM models

Finite element muscle models are an extension to regular FEM models. As such, everything previously discussed for regular FEM models also applies to FEM muscles. Muscles have additional properties that allow them to contract when activated. There are two types of muscles supported:

Fibre-based:

Point-to-point muscle fibres are embedded in the model.

Material-based:

An auxiliary material is added to the constitutive law to embed muscle properties.

In this section, both types will be described.

1.9.1 FemMuscleModel

The main class for FEM-based muscles is FemMuscleModel, a subclass of FemModel3d. It differs from a basic FEM model in that it has the new property

Property Description
muscleMaterial An object that adds an activation-dependent ‘muscle’ term to the constitutive law.

This is a delegate object of type MuscleMaterial, described in detail in Section 1.10.3, that computes activation-dependent stress and stiffness in the muscle. In addition to this property, FemMuscleModel adds two new lists of subcomponents:

bundles


Groupings of muscle sub-units (fibres or elements) that can be activated.

exciters


Components that control the activation of a set of bundles or other exciters.

1.9.1.1 Bundles

Muscle bundles allow for a muscle to be partitioned into separate groupings of fibres/elements, where each bundle can be activated independently. They are implemented in the class MuscleBundle. Bundles have three key properties:

Property Description
excitation Activation level of the muscle, a\in[0,1].
fibresActive Enable/disable “fibre-based” muscle components.
muscleMaterial An object that adds an activation-dependent ‘muscle’ term to the constitutive law (Section 1.10.3).

The excitation property controls the level of muscle activation, with zero being no muscle action, and one being fully activated. The fibresActive property is a boolean variable that controls whether or not to treat any contained fibres as point-to-point-like muscles (“fibre-based”). If false, the fibres are ignored. The third property, muscleMaterial, allows for a MuscleMaterial to be specified per bundle. By default, its value is inherited from FemMuscleModel.

Once a muscle bundle is created, muscle sub-units must be assigned to it. These are either point-to-point fibres, or material-based muscle element descriptors. The two types will be covered in Sections 1.9.2 and 1.9.3, respectively.

1.9.1.2 Exciters

A MuscleExciter component enables you to simultaneously activate a group of “excitation components”. This includes: point-to-point muscles, muscle bundles, muscle fibres, material-based muscle elements, and other muscle exciters. Components that can be excited all implement the ExcitationComponent interface. To add or remove a component to the exciter, use

  addTarget (ExcitationComponent ex);    // adds a component to the exciter
  addTarget (ExcitationComponent ex,     // adds a component with a gain factor
      double gain);
  removeTarget (ExcitationComponent ex); // removes a component

If a gain factor is specified, the activation is scaled by the gain for that component.

1.9.2 Fibre-based muscles

In fibre-based muscles, a set of point-to-point muscle fibres are added between FEM nodes or markers. Each fibre is assigned an AxialMuscleMaterial, just like for regular point-to-point muscles (Section LABEL:sec:mechii:musclematerials). Note that these muscle materials typically have a “rest length” property, that will likely need to be adjusted for each fibre. Once the set of fibres are added to a MuscleBundle, they need to be enabled. This is done by setting the fibresActive property of the bundle to true.

Fibres are added to a MuscleBundle using one of the functions:

addFibre( Muscle muscle );              // adds a point-to-point fibre
Muscle addFibre( Point p0, Point p1,    // creates and adds a fibre
    AxialMuscleMaterial mat);

The latter returns the newly created Muscle fibre. The following code snippet demonstrates how to create a fibre-based MuscleBundle and add it to an FEM muscle.

1 // Create a muscle bundle
2 MuscleBundle bundle = new MuscleBundle("fibres");
3 Point3d[] fibrePoints = ...   //create a sequential list of points
4
5 // Add fibres
6 Point pPrev = fem.addMarker(fibrePoints[0]);    // create an FEM marker
7 for (int i=1; i<=fibrePoints.length; i++) {
8    Point pNext = fem.addMarker(fibrePoint[i]);
9
10    // Create fibre material
11    double l0 = pNext.distance(pPrev);           // rest length
12    AxialMuscleMaterial fibreMat =
13          new BlemkerAxialMuscle(
14                1.4*l0, l0, 3000, 0, 0);
15
16    // Add a fibre between pPrev and pNext
17    bundle.addFibre(pPrev, pNext, fibreMat);     // add fibre to bundle
18    pPrev = pNext;
19 }
20
21 // Enable use of fibres (default is disabled)
22 bundle.setFibresActive(true);
23 fem.addMuscleBundle(bundle);                    // add the bundle to fem

In these fibre-based muscles, force is only exerted between the anchor points of the fibres; it is a discrete approximation. These models are typically more stable than material-based ones.

1.9.3 Material-based muscles

In material-based muscles, the constitutive law is augmented with additional terms to account for muscle-specific properties. This is a continuous representation within the model.

The basic building block for a material-based muscle bundle is a MuscleElementDesc. This object contains a reference to a FemElement3d, a MuscleMaterial, and either a single direction or set of directions that specify the direction of contraction. If a single direction is specified, then it is assumed the entire element contracts in the same direction. Otherwise, a direction can be specified for each integration point within the element. A null direction signals that there is no muscle at the corresponding point. This allows for a sub-element resolution for muscle definitions. The positions of integration points for a given element can be obtained with:

// loop through all integration points for a given element
for ( IntegrationPoint3d ipnt : elem.getIntegrationPoints() ) {
   Point3d curPos = new Point3d();
   Point3d restPos = new Point3d();
   ipnt.computePosition (curPos, elem);          // computes current position
   ipnt.computeRestPosition (restPos, elem);     // computes rest position
}

By default, the MuscleMaterial is inherited from the bundle’s material property. Muscle materials are described in detail in Section 1.10.3 and include GenericMuscle, BlemkerMuscle, and FullBlemkerMuscle. The Blemker-type materials are based on [blemker:2005:muscle].

Elements can be added to a muscle bundle using one of the methods:

// Adds a muscle element
addElement (MuscleElementDesc elem);
// Creates and adds a muscle element
MuscleElementDesc addElement (FemElement3d elem, Vector3d dir);
// Sets a direction per integration point
MuscleElementDesc addElement (FemElement3d elem, Vector3d[] dirs);

The following snippet demonstrates how to create and add a material-based muscle bundle:

1 // Create muscle bundle
2 MuscleBundle bundle = new MuscleBundle("embedded");
3
4 // Muscle material
5 MuscleMaterial muscleMat = new BlemkerMuscle(
6       1.4, 1.0, 3000, 0, 0);
7 bundle.setMuscleMaterial(muscleMat);
8
9 // Muscle direction
10 Vector3d dir = Vector3d.X_UNIT;
11
12 // Add elements to bundle
13 for (FemElement3d elem : beam.getElements()) {
14    bundle.addElement(elem, dir);
15 }
16
17 // Add bundle to model
18 beam.addMuscleBundle(bundle);

1.9.4 Example: toy FEM muscle model

Figure 1.15: ToyMuscleFem, showing the deformation from setting the muscle excitations in the control panel.

A simple example showing an FEM with material-based muscles is given by artisynth.demos.tutorial.ToyMuscleFem. It consists of a hex-element beam, with 12 muscles added in groups along its length, allowing it to flex in the x-z plane. A frame object is also attached to the right end. The code for the model, without the package and include directives, is listed below:

1 public class ToyMuscleFem extends RootModel {
2
3    protected MechModel myMech;      // overall mechanical model
4    protected FemMuscleModel myFem;  // FEM muscle model
5    protected Frame myFrame;         // frame attached to the FEM
6
7    public void build (String[] args) throws IOException {
8       myMech = new MechModel ("mech");
9       myMech.setGravity (0, 0, 0);
10       addModel (myMech);
11
12       // create a FemMuscleModel and then create a hex grid with it.
13       myFem = new FemMuscleModel ("fem");
14       FemFactory.createHexGrid (
15          myFem, /*wx*/1.2, /*wy*/0.3, /*wz*/0.3, /*nx*/12, /*ny*/3, /*nz*/3);
16       // give it a material with Poisson’s ratio 0 to allow it to compress
17       myFem.setMaterial (new LinearMaterial (100000, 0.0));
18       // fem muscles will be material-based, using a SimpleForceMuscle
19       myFem.setMuscleMaterial (new SimpleForceMuscle(/*maxstress=*/100000));
20       // set density + particle and stiffness damping for optimal stability
21       myFem.setDensity (100.0);
22       myFem.setParticleDamping (1.0);
23       myFem.setStiffnessDamping (1.0);
24       myMech.addModel (myFem);
25
26       // fix the nodes on the left side
27       for (FemNode3d n : myFem.getNodes()) {
28          Point3d pos = n.getPosition();
29          if (pos.x == -0.6) {
30             n.setDynamic (false);
31          }
32       }
33       // Create twelve muscle bundles, bottom to top and left to right. Muscles
34       // are material-based, each with a set of 9 elements in the x-y plane and
35       // a rest direction parallel to the x axis.
36       Point3d pe = new Point3d(); // element center point
37       for (int sec=0; sec<4; sec++) {
38          for (int k=0; k<3; k++) {
39             MuscleBundle bundle = new MuscleBundle();
40             // locate elements based on their center positions
41             pe.set (-0.55+sec*0.3, -0.1, -0.1+k*0.1);
42             for (int i=0; i<3; i++) {
43                pe.y = -0.1;
44                for (int j=0; j<3; j++) {
45                   FemElement3d e = myFem.findNearestVolumetricElement(null, pe);
46                   bundle.addElement (e, Vector3d.X_UNIT);
47                   pe.y += 0.1;
48                }
49                pe.x += 0.1;
50             }
51             // set the line color for each bundle using a color from the probe
52             // display palette.
53             RenderProps.setLineColor (
54                bundle, PlotTraceInfo.getPaletteColors()[sec*3+k]);
55             myFem.addMuscleBundle (bundle);
56          }
57       }
58       // create a panel for controlling all 12 muscle excitations
59       ControlPanel panel = new ControlPanel ();
60       for (MuscleBundle b : myFem.getMuscleBundles()) {
61          LabeledComponentBase c = panel.addWidget (
62             "excitation "+b.getNumber(), b, "excitation");
63          // color the exciter labels using the muscle bundle color
64          c.setLabelFontColor (b.getRenderProps().getLineColor());
65       }
66       addControlPanel (panel);
67
68       // create and attach a frame to the right end of the FEM
69       myFrame = new Frame();
70       myFrame.setPose (new RigidTransform3d (0.45, 0, 0));
71       myMech.addFrame (myFrame);
72       myMech.attachFrame (myFrame, myFem); // attach to the FEM
73
74       // render properties:
75       // show muscle bundles by rendering activation directions within elements
76       RenderProps.setLineWidth (myFem.getMuscleBundles(), 4);
77       myFem.setDirectionRenderLen (0.6); //
78       // draw frame by showing its coordinate axis
79       myFrame.setAxisLength (0.3);
80       myFrame.setAxisDrawStyle (AxisDrawStyle.ARROW);
81       // set FEM line and surface colors to blue-gray
82       RenderProps.setLineColor (myFem, new Color (0.7f, 0.7f, 1f));
83       RenderProps.setFaceColor (myFem, new Color (0.7f, 0.7f, 1f));
84       // render FEM surface transparent
85       myFem.setSurfaceRendering (SurfaceRender.Shaded);
86       RenderProps.setAlpha (myFem.getSurfaceMeshComp(), 0.4);
87    }
88 }

Within the build() method, the mech model is created in the usual way (lines 8-10). Gravity is turned off to give the muscles greater control over the FEM model’s configuration. The MechModel reference is stored in the class field myMech to enable any subclasses to have easy access to it; the same will be done for the FEM model and the attached frame.

The FEM model itself is created at lines (12-24). An instance of FemMuscleModel is created and then passed to FemFactory.createHexGrid(); this allows the model to be an instance of FemMuscleModel instead of FemModel3d. The model is assigned a linear material with Poisson’s ratio of 0 to allow it to be easily compressed. Muscle bundles will be material-based and will all use the same SimpleForceMuscle material that is assigned to the model’s muscleMaterial property. Density and damping parameters are defined so as to improve model stability when muscles are excited. Once the model is created, its left-hand nodes are fixed to provide anchoring (lines 27-32).

Muscle bundles are created at lines 36-58. There are 12 of them, arranged in three vertical layers and four horizontal sections. Each bundle is populated with 9 adjacent elements in the x-y plane. To find these elements, their center positions are calculated and then passed to the FEM method findNearestVolumetricElement(). The element is then added to the bundle with a resting activation direction parallel to the x axis. For visualization, each bundle’s line color is set to a distinct color from the numeric probe display palette, based on the bundle’s number (lines 53-54).

After the bundles have been added to the FEM model, a control is created to allow interactive control of their excitations (lines 88-95). Each widget is labeled excitation N, where N is the bundle number, and the label is colored to match the bundle’s line color. Finally, a frame is created and attached to the FEM model (lines 59-62), and render properties are set at lines 106-115: Muscle bundles are drawn by showing the activation direction in each element; the frame is displayed as a solid arrow with axis lengths of 0.3; the FEM line and face colors are set to blue-gray, and the surface is rendered and made transparent.

To run this example in ArtiSynth, select All demos > tutorial > ToyMuscleFem from the Models menu. Running the model and adjusting the exciters in the control panel will cause the FEM model to deform in various ways (Figure 1.15).

1.9.5 Example: comparison with two beam examples

Figure 1.16: FemMuscleBeams model loaded into ArtiSynth.

An example comparing a fibre-based and a material-based muscle is shown in Figure 1.16. The code can be found in artisynth.demos.tutorial.FemMuscleBeams. There are two FemMuscleModel beams in the model: one fibre-based, and one material-based. Each has three muscle bundles: one at the top (red), one in the middle (green), and one at the bottom (blue). In the figure, both muscles are fully activated. Note the deformed shape of the beams. In the fibre-based one, since forces only act between point on the fibres, the muscle seems to bulge. In the material-based muscle, the entire continuous volume contracts, leading to a uniform deformation.

Material-based muscles are more realistic. However, this often comes at the cost of stability. The added terms to the constitutive law are highly nonlinear, which may cause numerical issues as elements become highly contracted or highly deformed. Fibre-based muscles are, in general, more stable. However, they can lead to bulging and other deformation artifacts due to their discrete nature.

1.10 Material types

ArtiSynth FEM models support a variety of material types, including linear, hyperelastic, and activated muscle materials, described in the sections below. These can be used to supply the primary material for either an entire FEM model or for specific elements (using the setMaterial() methods for either). They can also be used to supply auxiliary materials, whose behavior is superimposed on the underlying material, via either material bundles (Section 1.8) or, for FemMuscleModels, muscle bundles (Section 1.9). Many of the properties for a given material can be bound to a scalar or vector field (Section LABEL:sec:femFields) to allow their values to vary across an FEM model. In the descriptions below, properties for which this is true will have a \checkmark indicated under the “Field” entry in the material’s property table.

All materials are defined in the package artisynth.core.materials.

1.10.1 Linear

Linear materials determine Cauchy stress \boldsymbol{\sigma} as a linear mapping from the small deformation Cauchy strain \boldsymbol{\epsilon},

\boldsymbol{\sigma}=\mathcal{D}:\boldsymbol{\epsilon}, (1.8)

where \mathcal{D} is the elasticity tensor. Both the stress and strain are symmetric 3 matrices,

\boldsymbol{\sigma}=\left(\begin{matrix}\sigma_{xx}&\sigma_{xy}&\sigma_{xz}\\
\sigma_{xy}&\sigma_{yy}&\sigma_{yz}\\
\sigma_{xz}&\sigma_{yz}&\sigma_{zz}\end{matrix}\right),\quad\boldsymbol{%
\epsilon}=\left(\begin{matrix}\epsilon_{xx}&\epsilon_{xy}&\epsilon_{xz}\\
\epsilon_{xy}&\epsilon_{yy}&\epsilon_{yz}\\
\epsilon_{xz}&\epsilon_{yz}&\epsilon_{zz}\end{matrix}\right), (1.9)

and can be expressed as 6-vectors using Voigt notation:

\hat{\boldsymbol{\sigma}}\equiv\left(\begin{matrix}\sigma_{xx}\\
\sigma_{yy}\\
\sigma_{zz}\\
\sigma_{xy}\\
\sigma_{yz}\\
\sigma_{xz}\end{matrix}\right),\quad\hat{\boldsymbol{\epsilon}}\equiv\left(%
\begin{matrix}\epsilon_{xx}\\
\epsilon_{yy}\\
\epsilon_{zz}\\
2\epsilon_{xy}\\
2\epsilon_{yz}\\
2\epsilon_{xz}\end{matrix}\right). (1.10)

This allows the constitutive mapping to be expressed in matrix form as

\hat{\boldsymbol{\sigma}}={\bf D}\,\hat{\boldsymbol{\epsilon}}, (1.11)

where {\bf D} is the 6\times 6 elasticity matrix.

Different Voigt notation mappings appear in the literature with regard to off-diagonal matrix entries. We use the one employed by FEBio [FEBioTheory2018]. Another common mapping is

\hat{\boldsymbol{\sigma}}\equiv\left(\begin{matrix}\sigma_{xx}&\sigma_{yy}&%
\sigma_{zz}&\sigma_{yz}&\sigma_{xz}&\sigma_{xy}\end{matrix}\right)^{T}.

Traditionally, Cauchy strain is computed from the symmetric part of the deformation gradient {\bf F} using the formula

\boldsymbol{\epsilon}=\frac{{\bf F}^{T}+{\bf F}}{2}-{\bf I}, (1.12)

where {\bf I} is the 3\times 3 identity matrix. However, ArtiSynth materials support corotation, in which rotations are first removed from {\bf F} using a polar decomposition

{\bf F}={\bf R}{\bf P}, (1.13)

where {\bf R} is a (right-handed) rotation matrix and {\bf P} is symmetric matrix. {\bf P} is then used to compute the Cauchy strain:

\boldsymbol{\epsilon}={\bf P}-{\bf I}. (1.14)

Corotation is the default behavior for linear materials in ArtiSynth and allows models to handle large scale rotational deformations [ngan:fem:2008, muller2004interactive].

For linear materials, the stress/strain response is computed at a single integration point in the center of each element. This is done to improve computational efficiency, as it allows the precomputation of stiffness matrices that map nodal displacements onto nodal forces for each element. This precomputed matrix can then be adjusted during each simulation step to account for the rotation {\bf R} computed at each element’s integration point [ngan:fem:2008, muller2004interactive].

Specific linear material types are listed in the subsections below. All are subclasses of LinearMaterialBase, and in addition to their individual properties, all export a corotation property (default value true) that controls whether corotation is applied.

1.10.1.1 LinearMaterial

LinearMaterial is a standard isotropic linear material, which is also the default material for FEM models. Its elasticity matrix is given by

{\bf D}=\begin{bmatrix}\lambda+2\mu&\lambda&\lambda&0&0&0\\
\lambda&\lambda+2\mu&\lambda&0&0&0\\
\lambda&\lambda&\lambda+2\mu&0&0&0\\
0&0&0&\mu&0&0\\
0&0&0&0&\mu&0\\
0&0&0&0&0&\mu\end{bmatrix}, (1.15)

where the Lamé parameters \lambda and \mu are derived from Young’s modulus E and Poisson’s ratio \nu via

\lambda=\dfrac{E\nu}{(1+\nu)(1-2\nu)},\quad\mu=\dfrac{E}{2(1+\nu)}. (1.16)

The material behavior is controlled by the following properties:

Property Description Default Field
E YoungsModulus Young’s modulus 500000
\nu PoissonsRatio Poisson’s ratio 0.33
corotated applies corotation if true true

LinearMaterials can be created with the following constructors:

LinearMaterial()

Create with default properties.

LinearMaterial (double E, double nu)

Create with specified E and \nu.

LinearMaterial (double E, double nu, boolean corotated)

Create with specified E, \nu and corotation.

1.10.1.2 TransverseLinearMaterial

TransverseLinearMaterial is a transversely isotropic linear material whose behavior is symmetric about a prescribed polar axis. If the polar axis is parallel to the z axis, then the elasticity matrix is specified most easily in terms of its inverse, or compliance matrix, according to

{\bf D}^{-1}=\begin{bmatrix}\dfrac{1}{E_{xy}}&-\dfrac{\nu_{yx}}{E_{xy}}&-%
\dfrac{\nu_{z}}{E_{z}}&0&0&0\\
-\dfrac{\nu_{yx}}{E_{xy}}&\dfrac{1}{E_{xy}}&-\dfrac{\nu_{z}}{E_{z}}&0&0&0\\
-\dfrac{\nu_{z}}{E_{z}}&-\dfrac{\nu_{z}}{E_{z}}&\dfrac{1}{E_{z}}&0&0&0\\
0&0&0&\dfrac{2(1+\nu_{yx})}{E_{xy}}&0&0\\
0&0&0&0&\dfrac{1}{G}&0\\
0&0&0&0&0&\dfrac{1}{G}\end{bmatrix},

where E_{xy} and E_{z} are Young’s moduli transverse and parallel to the axis, respectively, \nu_{xy} and \nu_{z} are Poisson’s ratios transverse and parallel to the axis, and G is the shear modulus.

The material behavior is controlled by the following properties:

Property Description Default Field
(E_{xy},E_{z})^{T} youngsModulus Young’s moduli transverse and parallel to the axis (500000,500000)^{T}
(\nu_{xy},\nu_{z})^{T} poissonsRatio Poisson’s ratios transverse and parallel to the axis (0.33,0.33)^{T}
G shearModulus shear modulus 187970
direction direction of the polar axis (0,0,1)^{T}
corotated applies corotation if true true

The youngsModulus and poissonsRatio properties are both described by Vector2d objects, while direction is described by a Vector3d. The direction property (as well as youngsModulus and shearModulus) can be bound to a field to allow its value to vary over an FEM model (Section LABEL:sec:femFields).

TransverseLinearMaterials can be created with the following constructors:

TransverseLinearMaterial()

Create with default properties.

TransverseLinearMaterial (Vector2d E, double G, MVector2d nu, boolean corotated)

Create with specified E, G, \nu and corotation.

1.10.1.3 AnisotropicLinearMaterial

AnisotropicLinearMaterial is a general anisotropic linear material whose behavior is specified by an arbitrary (symmetric) elasticity matrix {\bf D}. The material behavior is controlled by the following properties:

Property Description Default Field
{\bf D} stiffnessTensor symmetric 6\times 6 elasticity matrix
corotated applies corotation if true true

The default value for stiffnessTensor is an isotropic elasticity matrix corresponding to E=500000 and \nu=0.33.

AnisotropicLinearMaterials can be created with the following constructors:

AnisotropicLinearMaterial()

Create with default properties.

AnisotropicLinearMaterial (Matrix6dBase D)

Create with specified elasticity {\bf D}.

AnisotropicLinearMaterial (Matrix6dBase D, boolean corotated)

Create with specified {\bf D} and corotation.

1.10.2 Hyperelastic materials

A hyperelastic material is defined by a strain energy density function W() that is in general a function of the deformation gradient {\bf F}, and more specifically a quantity derived from {\bf F} that is rotationally invariant, such as the right Cauchy Green tensor {\bf C}\equiv{\bf F}^{T}{\bf F}, the left Cauchy Green tensor {\bf B}\equiv{\bf F}{\bf F}^{T}, or Green strain

{\bf E}=\frac{1}{2}\left({\bf F}^{T}{\bf F}-{\bf I}\right). (1.16)

W() is often described with respect to the first or second invariants of these quantities, denoted by I_{1} and I_{2} and defined with respect to a given matrix {\bf A} by

I_{1}\equiv\operatorname{tr}({\bf A}),\quad I_{2}\equiv\frac{1}{2}\left(%
\operatorname{tr}({\bf A})^{2}-\operatorname{tr}({\bf A}^{2})\right). (1.17)

Alternatively, W() is sometimes defined with respect to the three principal stretches of the deformation, \lambda_{i},i\in{1,2,3}, which are the eigenvalues of the symmetric component {\bf P} of the polar decomposition of {\bf F} in (1.13).

If W() is expressed with respect to {\bf E}, then the second Piola-Kirchhoff stress {\bf S} is given by

{\bf S}=\frac{\partial W}{\partial{\bf E}} (1.18)

from which the Cauchy stress can be obtained via

\boldsymbol{\sigma}=\frac{1}{J}{\bf F}{\bf S}{\bf F}^{T},\quad J\equiv\det{\bf
F}. (1.19)

Many of the hyperelastic materials described below are incompressible, in the sense that W is partitioned into a deviatoric component that is volume invariant and a dilational component that depends solely on volume changes. Volume change is characterized by J=\det{\bf F}, with the change in volume given by dV=J^{1/3} and J=1 indicating no volume change. Deviatoric changes are characterized by \bar{\bf F}, with

\bar{\bf F}=J^{-1/3}{\bf F}, (1.20)

and so the partitioned strain energy density function assumes the form

W({\bf F})=\bar{W}(\bar{\bf F})+U(J), (1.21)

where the \bar{W}() term is the deviatoric contribution and U(J) is the volumetric potential that enforces incompressibility. \bar{W}() may also be expressed with respect to the right deviatoric Cauchy Green tensor \bar{\bf C} or the left deviatoric Cauchy Green tensor \bar{\bf B}, respectively defined by

\bar{\bf C}=J^{-2/3}{\bf C},\quad\bar{\bf B}=J^{-2/3}{\bf B}. (1.22)

ArtiSynth supplies different forms of U(J), as specified by a material’s bulkPotential property and detailed in Section 1.7.3. All of the available potentials depend on a bulkModulus property \kappa, and so U(J) is often expressed as U(\kappa,J). A larger bulk modulus will make the material more incompressible, with effective incompressibility typically achieved by setting \kappa to a value that exceeds the other elastic moduli in the material by a factor of 100 or more. All incompressible materials are subclasses of IncompressibleMaterialBase.

1.10.2.1 St Venant-Kirchoff material

StVenantKirchoffMaterial is a compressible isotropic material that extends isotropic linear elasticity to large deformations. The strain energy density is most easily expressed as a function of the Green strain {\bf E}:

W({\bf E})=\frac{\lambda}{2}\operatorname{tr}({\bf E})^{2}+\mu\operatorname{tr%
}({\bf E}^{2}), (1.23)

where the Lamé parameters \lambda and \mu are derived from Young’s modulus E and Poisson’s ratio \nu according to (1.16). From this definition it follows that the second Piola-Kirchoff stress tensor is given by

{\bf S}=\lambda\operatorname{tr}({\bf E}){\bf I}+2\mu{\bf E}. (1.24)

The material behavior is controlled by the following properties:

Property Description Default Field
E YoungsModulus Young’s modulus 500000
\nu PoissonsRatio Poisson’s ratio 0.33

StVenantKirchoffMaterials can be created with the following constructors:

StVenantKirchoffMaterial()

Create with default properties.

StVenantKirchoffMaterial (double E, double nu)

Create with specified elasticity E and \nu.

1.10.2.2 Neo-Hookean material

NeoHookeanMaterial is a compressible isotropic material with a strain energy density expressed as a function of the first invariant I_{1} of the right Cauchy-Green tensor {\bf C} and J=\det{\bf F}:

W({\bf C},J)=\frac{\mu}{2}\left(I_{1}-3\right)-\mu\ln(J)+\frac{\lambda}{2}\ln(%
J)^{2}, (1.24)

where the Lamé parameters \lambda and \mu are derived from Young’s modulus E and Poisson’s ratio \nu via (1.16). The Cauchy stress can be expressed in terms of the left Cauchy-Green tensor {\bf B} as

\boldsymbol{\sigma}=\frac{\mu}{J}{\bf B}+\frac{\lambda\ln(J)-\mu}{J}{\bf I}. (1.25)

The material behavior is controlled by the following properties:

Property Description Default Field
E YoungsModulus Young’s modulus 500000
\nu PoissonsRatio Poisson’s ratio 0.33

NeoHookeanMaterials can be created with the following constructors:

NeoHookeanMaterial()

Create with default properties.

NeoHookeanMaterial (double E, double nu)

Create with specified elasticity E and \nu.

1.10.2.3 Incompressible neo-Hookean material

IncompNeoHookeanMaterial is an incompressible version of the neo-Hookean material, with a strain energy density expressed in terms of the first invariant \bar{I}_{1} of the deviatoric right Cauchy-Green tensor \bar{\bf C}, plus a potential function U(\kappa,J) to enforce incompressibility:

W(\bar{\bf C},J)=\frac{G}{2}\left(\bar{I}_{1}-3\right)+U(\kappa,J). (1.25)

The material behavior is controlled by the following properties:

Property Description Default Field
G shearModulus shear modulus 150000
\kappa bulkModulus bulk modulus for incompressibility 100000
bulkPotential incompressibility potential function U(\kappa,J) QUADRATIC

IncompNeoHookeanMaterials can be created with the following constructors:

IncompNeoHookeanMaterial()

Create with default properties.

IncompNeoHookeanMaterial (double G, double kappa)

Create with specified elasticity G and \kappa.

1.10.2.4 Mooney-Rivlin material

MooneyRivlinMaterial is an incompressible isotropic material with a strain energy density expressed as a polynomial of the first and second invariants \bar{I}_{1} and \bar{I}_{2} of the right deviatoric Cauchy-Green tensor \bar{\bf C}. ArtiSynth supplies a five parameter version of this model, with a strain energy density given by

W(\bar{\bf C},J)=C_{10}(\bar{I}_{1}-3)+C_{01}(\bar{I}_{2}-3)+C_{11}(\bar{I}_{1%
}-3)(\bar{I}_{2}-3)+C_{20}(\bar{I}_{1}-3)^{2}+C_{02}(\bar{I}_{2}-3)^{2}+U(%
\kappa,J). (1.25)

A two-parameter version (C_{1}, C_{2}) is often found in the literature, consisting of only the first two terms:

W(\bar{\bf C},J)=C_{1}(\bar{I}_{1}-3)+C_{2}(\bar{I}_{2}-3)+U(\kappa,J),\qquad C%
_{1}\equiv C_{10},\quad C_{2}\equiv C_{01}. (1.26)

The material behavior is controlled by the following properties:

Property Description Default Field
C_{10} C10 first coefficient 150000
C_{01} C01 second coefficient 0
C_{11} C11 third coefficient 0
C_{20} C20 fourth coefficient 0
C_{02} C02 fifth coefficient 0
\kappa bulkModulus bulk modulus for incompressibility 100000
bulkPotential incompressibility potential function U(\kappa,J) QUADRATIC

MooneyRivlinMaterials can be created with the following constructors:

MooneyRivlinMaterial()

Create with default properties.

MooneyRivlinMaterial (double C10, double C01, double C11, Mdouble C20, double C02, double kappa)

Create with specified coefficients.

1.10.2.5 Ogden material

OgdenMaterial is an incompressible material with a strain energy density expressed as a function of the deviatoric principal stretches \bar{\lambda}_{i},i\in{1,2,3}:

W(\bar{\lambda}_{1},\bar{\lambda}_{2},\bar{\lambda}_{3},J)=\sum_{k=1}^{N}\frac%
{\mu_{k}}{\alpha_{k}^{2}}\left(\bar{\lambda}_{1}^{\alpha_{k}}+\bar{\lambda}_{2%
}^{\alpha_{k}}+\bar{\lambda}_{3}^{\alpha_{k}}-3\right)+U(\kappa,J). (1.26)

ArtiSynth allows a maximum of six terms, corresponding to N=6, and the material behavior is controlled by the following properties:

Property Description Default Field
\mu_{1} Mu1 first multiplier 300000
\mu_{2} Mu2 second multiplier 0
... ... ... 0
\mu_{6} Mu6 final multiplier 0
\alpha_{1} Alpha1 first multiplier 2
\alpha_{2} Alpha2 second multiplier 2
... ... ... 2
\alpha_{6} Alpha6 final multiplier 2
\kappa bulkModulus bulk modulus for incompressibility 100000
bulkPotential incompressibility potential function U(\kappa,J) QUADRATIC

OgdenMaterials can be created with the following constructors:

OgdenMaterial()

Create with default properties.

OgdenMaterial (double[] mu, double[] alpha, double kappa)

Create with specified \mu, \alpha and \kappa values.

1.10.2.6 Fung orthotropic material

FungOrthotropicMaterial is an incompressible orthotropic material defined with respect to an x-y-z coordinate system expressed by a 3\times 3 rotation matrix {\bf R}. The strain energy density is expressed as a function of the deviatoric Green strain

\bar{\bf E}\equiv\frac{1}{2}\left(\bar{\bf C}-{\bf I}\right) (1.26)

and the three unit direction vectors {\bf a}_{1},\,{\bf a}_{2},\,{\bf a}_{3} representing the columns of {\bf R}. Letting {\bf A}_{i}\equiv{\bf a}_{i}{\bf a}_{i}^{T} and defining

\alpha_{i}\equiv{\bf A}_{i}:\bar{\bf E}=\operatorname{tr}({\bf a}_{i}^{T}\bar{%
\bf E}\,{\bf a}_{i}),\quad\beta_{i}\equiv{\bf A}_{i}:\bar{\bf E}^{2}=%
\operatorname{tr}({\bf a}_{i}^{T}\bar{\bf E}^{2}\,{\bf a}_{i}),\quad (1.27)

the energy density is given by

W(\bar{\bf E},J)=\frac{C}{2}\left(e^{q}-1\right)+U(\kappa,J),\quad q=\sum_{i=1%
}^{3}\left(2\mu_{i}\beta_{i}+\sum_{j=1}^{3}\lambda_{ij}\alpha_{i}\alpha_{j}%
\right), (1.28)

where C is a material coefficient and \mu_{i} and \lambda_{ij} are the orthotropic Lamé parameters.

At present, the coordinate frame {\bf R} is not defined in the material, but can be specified on a per-element basis using the element method setFrame(Matrix3dBase). For example:

   RotationMatrix3d R;
   FemModel3d fem;
   ...
   for (FemElement3d elem : fem.getElements()) {
      ... computer R as required for the element ...
      elem.setFrame (R);
   }

One should be careful to ensure that the argument to setFrame() is in fact an orthogonal rotation matrix as this will not be otherwise enforced.

The material behavior is controlled by the following properties:

Property Description Default Field
\mu_{1} mu1 second Lamé parameter along x 1000
\mu_{2} mu2 second Lamé parameter along y 1000
\mu_{3} mu3 second Lamé parameter along z 1000
\lambda_{11} lam11 first Lamé parameter along x 2000
\lambda_{22} lam22 first Lamé parameter along y 2000
\lambda_{33} lam33 first Lamé parameter along z 2000
\lambda_{12} lam12 first Lamé parameter for x,y 2000
\lambda_{23} lam23 first Lamé parameter for y,z 2000
\lambda_{31} lam31 first Lamé parameter for z,x 2000
C C C coefficient 1500
\kappa bulkModulus bulk modulus for incompressibility 100000
bulkPotential incompressibility potential function U(\kappa,J) QUADRATIC

FungOrthotropicMaterials can be created with the following constructors:

FungOrthotropicMaterial()

Create with default properties.

FungOrthotropicMaterial (double mu1, double mu2, double mu3, Mdouble lam11, double lam22, double lam33, double lam12, Mdouble lam23, double lam31, double C, double kappa)

Create with specified properties.

1.10.2.7 Yeoh material

YeohMaterial is an incompressible isotropic material that implements a Yeoh model with a strain energy density containing up to five terms:

W(\bar{\bf C},J)=\sum_{i=1}^{5}C_{i}(\bar{I}_{1}-3)^{i}, (1.28)

where \bar{I}_{1} is the first invariant of the right deviatoric Cauchy Green tensor and C_{i} are the material coefficients. The material behavior is controlled by the following properties:

Property Description Default Field
C_{1} C1 first coefficient (shear modulus) 150000
C_{2} C2 second coefficient 0
C_{3} C3 third coefficient 0
C_{4} C4 fourth coefficient 0
C_{5} C5 fifth coefficient 0
\kappa bulkModulus bulk modulus for incompressibility 100000
bulkPotential incompressibility potential function U(\kappa,J) QUADRATIC

YeohMaterials can be created with the following constructors:

YeohMaterial()

Create with default properties.

YeohMaterial (double C1, double C2, double C3, double kappa)

Create three term material with C_{1}, C_{2}, C_{3}, \kappa.

YeohMaterial (double C1, double C2, double C3, double C4, Mdouble C5, double kappa)

Create five term material with C_{1}, ..., C_{5}, \kappa.

1.10.2.8 Arruda-Boyce material

ArrudaBoyceMaterial is an incompressible isotropic material that implements the Arruda-Boyce model [arruda1993three]. Its strain energy density is given by

W(\bar{\bf C},J)=\mu\sum_{i=1}^{5}\frac{C_{i}}{\lambda_{L}^{2(i-1)}}(\bar{I}_{%
1}^{i}-3^{i})+U(\kappa,J), (1.28)

where \mu is the initial modulus, \lambda_{L} the locking stretch, \bar{I}_{1} the first invariant of the right deviatoric Cauchy Green tensor, and

C=\left\{\frac{1}{2},\;\frac{1}{20},\;\frac{11}{1050},\;\frac{19}{7000},\;%
\frac{519}{673750}\right\}.

The material behavior is controlled by the following properties:

Property Description Default Field
\mu mu initial modulus 1000
\lambda_{L} lambdaMax locking stretch 2.0
\kappa bulkModulus bulk modulus for incompressibility 100000
bulkPotential incompressibility potential function U(\kappa,J) QUADRATIC

ArrudaBoyceMaterials can be created with the following constructors:

ArrudaBoyceMaterial()

Create with default properties.

ArrudaBoyceMaterial (double mu, double lmax, double kappa)

Create with specified \mu, \lambda_{L}, \kappa.

1.10.2.9 Veronda-Westmann material

VerondaWestmannMaterial is an incompressible isotropic material that implements the Veronda-Westmann model [veronda1970mechanical]. Its strain energy density is given by

{\bf W}(\bar{\bf C},J)=C_{1}\left(e^{C_{2}(\bar{I}_{1}-3)}-1\right)-\frac{C_{1%
}C_{2}}{2}(\bar{I}_{2}-3)+U(\kappa,J), (1.29)

where C_{1} and C_{2} are material coefficients and \bar{I}_{1} and \bar{I}_{2} the first and second invariants of the right deviatoric Cauchy Green tensor.

The material behavior is controlled by the following properties:

Property Description Default Field
C_{1} C1 first coefficient 1000
C_{2} C2 second coefficient 10
\kappa bulkModulus bulk modulus for incompressibility 100000
bulkPotential incompressibility potential function U(\kappa,J) QUADRATIC

VerondaWestmannMaterials can be created with the following constructors:

VerondaWestmannMaterial()

Create with default properties.

VerondaWestmannMaterial (double C1, double C2, double kappa)

Create with specified {\bf C}_{1}, C_{2}, and \kappa.

1.10.2.10 Incompressible material

IncompressibleMaterial is an incompressible isotropic material that implements pure incompressibility, with an energy density function given by

W(J)=U(\kappa,J). (1.29)

Because it responds only to dilational strains, it must be used in conjunction with another material to resist deviatoric strains. In this context, it can be used to provide dilational support to deviatoric-only materials such as the FullBlemkerMuscle (Section 1.10.3.3).

The material behavior is controlled by the following properties:

Property Description Default Field
\kappa bulkModulus bulk modulus for incompressibility 100000
bulkPotential incompressibility potential function U(\kappa,J) QUADRATIC

IncompressibleMaterials can be created with the following constructors:

IncompressibleMaterial()

Create with default values.

IncompressibleMaterial (double kappa)

Create with specified \kappa.

1.10.3 Muscle materials

Muscle materials are used to exert stress along a particular direction within a material. This stress may contain both an active component, which depends on the muscle excitation, and a passive component, which depends solely on the stretch along the prescribed direction. Because most muscle materials act only along one direction, they are typically deployed within an FEM as additional materials that act in addition to an underlying material. This can be done using either muscle bundles within a FemMuscleModel (Section 1.9.1.1) or material bundles within any FEM model (Sections 1.8 and LABEL:RadialMuscle:sec).

All of the muscle materials described below assume (near) incompressibility, and so the directional stress is a function of the deviatoric stretch \bar{\lambda} along the muscle direction instead of the overall stretch \lambda. The former can be determined from the latter via

\bar{\lambda}=\lambda J^{-1/3}, (1.29)

where J is the determinant of the deformation gradient. The directional Cauchy stress \boldsymbol{\sigma}_{d} resulting from the material is computed from

\boldsymbol{\sigma}_{d}=\frac{\bar{\lambda}f_{d}(\bar{\lambda})}{J}\left({\bf a%
}{\bf a}^{T}-\frac{1}{3}{\bf I}\right), (1.30)

where f_{d}(\bar{\lambda}) is a force term, {\bf a} is a unit vector indicating the current muscle direction in spatial coordinates, and {\bf I} is the 3\times 3 identity matrix. In a purely passive case when the force arises from a stress energy density function W(\bar{\lambda}), we have

f_{d}(\bar{\lambda})=\frac{\partial W(\bar{\lambda})}{\partial\bar{\lambda}}. (1.31)

The muscle direction is specified in one of two ways, depending on how the muscle material is deployed. All muscle materials export a property restDir which specifies the direction in material (rest) coordinates. It has a default value of (1,0,0)^{T}, and can be either set explicitly or bound to a field to allow it to vary over the entire model (Section LABEL:RadialMuscle:sec). However, if a muscle material is deployed within a muscle bundle (Section 1.9.1), then the restDir property is ignored and the direction is instead specified by the MuscleElementDesc components contained within the bundle (Section 1.9.3).

Likewise, muscle excitation is specified using the material’s excitation property, unless the material is deployed within a muscle bundle, in which case it is controlled by the excitation property of the bundle.

1.10.3.1 Generic muscle

GenericMuscle is a muscle material whose force term f_{d}(\bar{\lambda}) is given by

f_{d}(\bar{\lambda})=a\sigma_{max}+f_{p}(\bar{\lambda}), (1.32)

where a is the excitation signal, \sigma_{max} is a maximum stress term, and f_{p}(\bar{\lambda}) is a passive force given by

f_{p}(\bar{\lambda})=\begin{cases}0,&\bar{\lambda}<1\\
P_{1}(e^{P_{2}(\bar{\lambda}-1)}-1)/\bar{\lambda},&\bar{\lambda}<\bar{\lambda}%
_{max}\\
(P_{3}\bar{\lambda}+P_{4})/\bar{\lambda},&\mathrm{otherwise}.\end{cases} (1.33)

In the equation above, P_{1} is the exponential stress coefficient, P_{2} is the uncrimping factor, and P_{3} and P_{4} are computed to provide C(1) continuity at \bar{\lambda}=\bar{\lambda}_{max}.

The material behavior is controlled by the following properties:

Property Description Default Field
\sigma_{max} maxStress maximum stress 30000
\bar{\lambda}_{max} maxLambda \bar{\lambda} at upper limit of exponential section of f_{p}(\bar{\lambda}) 1.4
P_{1} expStressCoeff exponential stress coefficient 0.05
P_{2} uncrimpingFactor uncrimping factor 6.6
{\bf a}_{0} restDir direction in material coordinates (1,0,0)^{T}
a excitation activation signal 0

GenericMuscles can be created with the following constructors:

GenericMuscle()

Create with default values.

GenericMuscle (double maxLambda, double maxStress, Mdouble expStressCoef, double uncrimpFactor)

Create with specified \bar{\lambda}_{max}, \sigma_{max}, P_{1}, P_{2}.

The constructors do not specify restDir. If the material is not being deployed within a muscle bundle, then restDir should also be set appropriately, either directly or via a field (Section LABEL:RadialMuscle:sec).

1.10.3.2 Blemker muscle

BlemkerMuscle is a muscle material implementing the directional component of the model proposed by Sylvia Blemker [blemker:2005:muscle]. Its force term f_{d}(\bar{\lambda}) is given by

f_{d}(\bar{\lambda})=\sigma_{max}(af_{a}(\bar{\lambda})+f_{p}(\bar{\lambda}))/%
\bar{\lambda}_{opt}, (1.33)

where \sigma_{max} is a maximum stress term, a is the excitation signal, f_{a}(\bar{\lambda}) is an active force-length curve, and f_{p}(\bar{\lambda}) is the passive force. f_{a}(\bar{\lambda}) and f_{p}(\bar{\lambda}) are described in terms of \hat{\lambda}\equiv\bar{\lambda}/\bar{\lambda}_{opt}:

f_{a}(\hat{\lambda})=\begin{cases}9(\hat{\lambda}-0.4)^{2},&\hat{\lambda}\leq 0%
.6\\
1-4(\hat{\lambda}-1)^{2},&0.6<\hat{\lambda}<1.4\\
9(\hat{\lambda}-1.6)^{2},&\mathrm{otherwise},\end{cases} (1.34)

and

f_{p}(\hat{\lambda})=\begin{cases}0,&\hat{\lambda}\leq 1\\
P_{1}(e^{P_{2}(\hat{\lambda}-1)}-1),&1<\hat{\lambda}<\bar{\lambda}_{max}/\bar{%
\lambda}_{opt}\\
(P_{3}\hat{\lambda}+P_{4}),&\mathrm{otherwise}.\end{cases} (1.35)

In the equation for f_{p}(\bar{\lambda}), P_{1} is the exponential stress coefficient, P_{2} is the uncrimping factor, and P_{3} and P_{4} are computed to provide C(1) continuity at \hat{\lambda}=\bar{\lambda}_{max}/\bar{\lambda}_{opt}.

The material behavior is controlled by the following properties:

Property Description Default Field
\sigma_{max} maxStress maximum stress 300000
\bar{\lambda}_{opt} optLambda \bar{\lambda} at peak of f_{a}(\bar{\lambda}) 1
\bar{\lambda}_{max} maxLambda \bar{\lambda} at upper limit of exponential section of f_{p}(\bar{\lambda}) 1.4
P_{1} expStressCoeff exponential stress coefficient 0.05
P_{2} uncrimpingFactor uncrimping factor 6.6
{\bf a}_{0} restDir direction in material coordinates (1,0,0)^{T}
a excitation activation signal 0

BlemkerMuscles can be created with the following constructors:

BlemkerMuscle()

Create with default values.

BlemkerMuscle (double maxLam, double optLam, Mdouble maxStress, double expStressCoef, double uncrimpFactor)

Create with specified \bar{\lambda}_{max}, \bar{\lambda}_{opt}, \sigma_{max}, P_{1}, P_{2}.

The constructors do not specify restDir. If the material is not being deployed within a muscle bundle, then restDir should also be set appropriately, either directly or via a field (Section LABEL:RadialMuscle:sec).

1.10.3.3 Full Blemker muscle

FullBlemkerMuscle is a muscle material implementing the entire model proposed by Sylvia Blemker [blemker:2005:muscle]. This includes the directional stress described in Section 1.10.3.2, plus additional passive terms to model the tissue material matrix. The latter is based on a strain energy density function W_{m} given by

W_{m}(\bar{\bf B},\lambda)=G_{1}\bar{B}_{1}^{2}+G_{2}\bar{B}_{2}^{2} (1.35)

where G_{1} and G_{2} are the along-fibre and cross-fibre shear moduli, respectively, and \bar{B}_{1} and \bar{B}_{2} are Criscione invariants. The latter are defined as follows: Let \bar{I}_{1} and \bar{I}_{2} be the invariants of the deviatoric left Cauchy Green tensor \bar{\bf B}, {\bf a} the unit vector giving the current muscle direction in spatial coordinates, and \bar{I}_{4} and \bar{I}_{5} additional invariants defined by

\bar{I}_{4}\equiv\bar{\lambda}^{2},\quad\bar{I}_{5}\equiv\bar{I}_{4}\,{\bf a}^%
{T}\bar{\bf B}{\bf a}. (1.36)

Then, defining

g\equiv\frac{\bar{I}_{5}}{\bar{I}_{4}^{2}}-1,\quad y\equiv\frac{\bar{I}_{1}%
\bar{I}_{4}-\bar{I}_{5}}{2\bar{\lambda}},

\bar{B}_{1} and \bar{B}_{2} are given by

\displaystyle\bar{B}_{1} \displaystyle=\begin{cases}\sqrt{g},&g>0\\
0,&\mathrm{otherwise},\\
\end{cases} (1.37)
\displaystyle\bar{B}_{2} \displaystyle=\begin{cases}\mathrm{acosh}(y),&y>1\\
0,&\mathrm{otherwise}.\\
\end{cases} (1.38)

At present, FullBlemkerMuscle does not implement a volumetric potential function U(J). That means it responds solely to deviatoric strains, and so if it is being used as a model’s primary material, it should be augmented with an additional material to provide resistance to compression. A simple choice for this is the purely incompressible material IncompressibleMaterial, described in Section 1.10.2.10.

The material behavior is controlled by the following properties:

Property Description Default Field
\sigma_{max} maxStress maximum stress 300000
\bar{\lambda}_{opt} optLambda \bar{\lambda} at peak of f_{a}(\bar{\lambda}) 1
\bar{\lambda}_{max} maxLambda \bar{\lambda} at upper limit of exponential section of f_{p}(\bar{\lambda}) 1.4
P_{1} expStressCoeff exponential stress coefficient 0.05
P_{2} uncrimpingFactor uncrimping factor 6.6
G_{1} G1 along-fibre shear modulus 0
G_{2} G2 cross-fibre shear modulus 0
{\bf a}_{0} restDir direction in material coordinates (1,0,0)^{T}
a excitation activation signal 0

FullBlemkerMuscles can be created with the following constructors:

FullBlemkerMuscle()

Create with default values.

BlemkerMuscle (double maxLam, double optLam, Mdouble maxStress, double expStressCoef, Mdouble uncrimpFactor, double G1, double G2)

Create with \bar{\lambda}_{max}, \bar{\lambda}_{opt}, \sigma_{max}, P_{1}, P_{2}, G_{1}, G_{2}.

The constructors do not specify restDir. If the material is not being deployed within a muscle bundle, then restDir should also be set appropriately, either directly or via a field (Section LABEL:RadialMuscle:sec).

1.10.3.4 Simple force muscle

SimpleForceMuscle is a very simple muscle material whose force term f_{d}(\bar{\lambda}) is given by

f_{d}(\bar{\lambda})=a\sigma_{max}, (1.39)

where a is the excitation signal and \sigma_{max} is a maximum stress term. It can be useful as a debugging tool.

The material behavior is controlled by the following properties:

Property Description Default Field
\sigma_{max} maxStress maximum stress 30000
{\bf a}_{0} restDir direction in material coordinates (1,0,0)^{T}
a excitation activation signal 0

SimpleForceMuscles can be created with the following constructors:

SimpleForceMuscle()

Create with default values.

SimpleForceMuscle (double maxStress)

Create with specified \sigma_{max}.

The constructors do not specify restDir. If the material is not being deployed within a muscle bundle, then restDir should also be set appropriately, either directly or via a field (Section LABEL:RadialMuscle:sec).

1.11 Stress, strain and strain energy

An ArtiSynth FEM model has the capability to compute a variety of stress and strain measures at each of its nodes, as well as strain energy for each element and for the entire model.

1.11.1 Computing nodal values

Stress, strain, and strain energy density can be computed at each node of an FEM model. By default, these quantities are not computed, in order to conserve computing power. However, their computation can be enabled with the FemModel3d properties computeNodalStress, computeNodalStrain and computeNodalEnergyDensity, which can be set either in the GUI, or using the following accessor methods:

void setComputeNodalStress(boolean)

Enable stress to be computed at each node.

boolean getComputeNodalStress()

Queries if nodal stress computation enabled.

void setComputeNodalStrain(boolean)

Enable strain to be computed at each node.

boolean getComputeNodalStrain()

Queries if nodal strain computation enabled.

void setComputeNodalEnergyDensity(boolean)

Enable strain energy density to be computed at each node.

boolean getComputeNodalEnergyDensity()

Queries if nodal strain energy density computation enabled.

Setting these properties to true will cause their respective values to be computed at the nodes during each subsequent simulation step. This is done by computing values at the element integration points, extrapolating these values to the nodes, and then averaging them across the contributions from all surrounding elements.

Once computed, the values may be obtained at each node using the following FemNode3d methods:

SymmetricMatrix3d getStress()

Returns the current Cauchy stress at the node.

SymmetricMatrix3d getStrain()

Returns the current strain at the node.

double getEnergyStrain()

Returns the current strain energy density at the node.

The stress value is the Cauchy stress. The strain values depend on the primary material within each element: if the material is linear, strain will be the small deformation Cauchy strain (co-rotated if the material’s corotated property is true), while otherwise it will be Green strain {\bf E}:

{\bf E}=\frac{1}{2}({\bf C}-{\bf I}), (1.39)

where {\bf C} is the right Cauchy-Green tensor.

1.11.2 Scalar stress/strain measures

Stress or strain values are tensor quantities represented by symmetric 3\times 3 matrices. If they are being computed at the nodes, a variety of scalar quantities can be extracted from them, using the FemNode3d method

double getStressStrainMeasure(StressStrainMeasure m)

where StressStrainMeasure is an enumerated type with the following entries:

VonMisesStress

von Mises stress, equal to

\sqrt{3{\bf J}_{2}},

where {\bf J}_{2} the second invariant of the average deviatoric stress.

MAPStress

Absolute value of the maximum principle Cauchy stress.

MaxShearStress

Maximum shear stress, given by

\max(|s_{0}-s_{1}|/2,|s_{1}-s_{2}|/2,|s_{2}-s_{0}|/2),

where s_{0}, s_{1} and s_{2} are the eigenvalues of the stress tensor.

VonMisesStrain

von Mises strain, equal to

\sqrt{4/3{\bf J}_{2}},

where {\bf J}_{2} the second invariant of the average deviatoric strain.

MAPStrain

Absolute value of the maximum principle strain.

MaxShearStrain

Maximum shear strain, given by

\max(|s_{0}-s_{1}|/2,|s_{1}-s_{2}|/2,|s_{2}-s_{0}|/2),

where s_{0}, s_{1} and s_{2} are the eigenvalues of the strain tensor.

EnergyDensity

Strain energy density.

These quantities can also be queried with the following convenience methods:

double getVonMisesStress()

Return the von Mises stress.

double getMAPStress()

Return the maximum absolute principle stress.

double getMaxShearStress()

Return the maximum shear stress.

double getVonMisesStrain()

Return the von Mises strain.

double getMAPStrain()

Return the maximum absolute principle strain.

double getMaxShearStrain()

Return the maximum shear strain.

double getEnergyDensity()

Return the strain energy density.

For muscle-activated FEM materials, strain energy density is currently computed only for the passive portions of the material; no energy density is computed for the stress component that depends on muscle activation.

As indicated above, extracting these measures requires that the appropriate underling quantity (stress, strain or strain energy density) is being computed at the nodes; otherwise, 0 will be returned. To ensure that the appropriate underlying quantity is being computed for a specific measure, one may use the FemModel3d method

void setComputeNodalStressStrain (StressStrainMeasure m)

These measures can also be used to produce color maps on FEM mesh components or cut planes, as described in Sections 1.12.3 or 1.12.7. When color maps are being produced, the system automatically enables the computation of the required stress, strain or energy density.

1.11.3 Strain energy

Strain energy density can be integrated over an FEM model’s volume to compute a total strain energy. Again, to save computational resources, this must be explicitly enabled using the FemModel3d property computeStrainEnergy, which can be set either in the GU or using the accessor methods:

void setComputeStrainEnergy(boolean)

Enable strain energy computation.

boolean getComputeStrainEnergy()

Queries strain energy computation.

Once computation is enabled, strain energy is exported for the entire model using the read-only strainEnergy property, which can be accessed using

double getStrainEnergy()

The FemElement method getStrainEnergy() returns the strain energy for individual elements.

1.12 Rendering and Visualizations

Figure 1.17: Component lists of an FEM muscle model displayed in the navigation panel.

An ArtiSynth FEM model can be rendered in a wide variety of ways, by adjusting the rendering of its nodes, elements, meshes (including the surface and other embedded meshes), and, for FemMuscleModel, muscle bundles. Properties for controlling the rendering include both the standard RenderProps (Section LABEL:RenderProperties:sec) as well as more specific properties. These can be set either in code, as described below, or by selecting the desired components (or their lists) and then choosing either Edit render props ... (for standard render properties) or Edit properties ... (for component-specific render properties) from the context menu. As mentioned in Section LABEL:RenderProperties:sec, standard render properties are inherited, and so if not explicitly specified within a given component, will assume whatever value has been set in the nearest ancestor component, or their default value. Some component-specific render properties are inherited as well.

One very direct way to control the rendering is to control the visibility of the various component lists. This can be done by selecting them in the navigation panel (Figure 1.17) and then choosing “Set invisible” or “Set visible”, as appropriate. It can also be done in code, as shown in the fragment below making the elements and nodes of an FEM model invisible:

  FemModel3d fem;
  // ... initialize the model ...
  RenderProps.setVisible (fem.getNodes(), false);    // make nodes invisible
  RenderProps.setVisible (fem.getElements(), false); // make elements invisible

1.12.1 Nodes

Node rendering is controlled by the point render properties of the standard RenderProps. By default, nodes are set to render as gray points one pixel wide, and so are not highly visible. To increase their visibility, and make them easier to select in the viewer, one can make them appear as spheres of a specified radius and color. In code, this can be done using the RenderProps.setSphericalPoints() method, as in this example,

import java.awt.Color;
import maspack.render.RenderProps;
   ...
   FemModel3d fem;
   ...
   RenderProps.setSphericalPoints (fem, 0.01, Color.GREEN);

which sets the default rendering for all points within the FEM model (which includes the nodes) to be green spheres of radius 0.01. To restrict this to the node list specifically, one could supply fem.getNodes() instead of fem to setSphericalPoints().

Figure 1.18: FEM model with nodes rendered as spheres.

It is also possible to set render properties for individual nodes. For instance, one may wish to mark nodes that are non-dynamic using a different color:

import java.awt.Color;
  ...
  for (FemNode3d node : fem.getNodes()) {
     if (!node.isDynamic()) {
        RenderProps.setPointColor (node, Color.RED);
     }
  }

Figure 1.18 shows an FEM model with nodes rendered as spheres, with the dynamic nodes colored green and the non-dynamic ones red.

1.12.2 Elements

By default, elements are rendered as wireframe outlines, using the lineColor and lineWidth properties of the standard render properties, with defaults of “gray” and 1. To improve visibility, one may wish the change the line color or size, as illustrated by the following fragment:

import java.awt.Color;
import maspack.render.RenderProps;
   ...
   FemModel3d fem;
   ...
   RenderProps.setLineColor (fem, Color.CYAN);
   RenderProps.setLineWidth (fem, 2);

This will set the default line color and width for all components within the FEM model. To restrict this to the elements only, one can specify fem.getElements() (and/or fem.getShellElements()) to the set methods. The fragment above is illustrated in Figure 1.19 (left) for a model created with a call to FemFactory.createHexTorus().

Figure 1.19: Elements of a torus shaped FEM model, rendered as wireframe (left), and using element widgets with an elementWidgetSize of 0.7 (right).

Elements can also be rendered as widgets that depict an approximation of their shape displayed at some fraction of the element’s size (this shape will be 3D for volumetric elements and 2D for shell elements). This is controlled using the FemModel3d property elementWidgetSize, which is restricted to the range [0,1] and describes the size of the widgets relative to the element size. If elementWidgetSize is 0 then no widgets are displayed. The widget color is controlled using the faceColor and alpha properties of the standard render properties. The following code fragment enables element widget rendering for an FEM model, as illustrated in Figure 1.19 (right):

import java.awt.Color;
import maspack.render.RenderProps;
   ...
   FemModel3d fem;
   ...
   fem.setElementWidgetSize (0.7);
   RenderProps.setFaceColor (fem, new Color(0.7f, 0.7f, 1f));
   RenderProps.setLineWidth (fem, 0); // turn off element wire frame

Since setting faceColor for the whole FEM model will also set the default face color for its meshes, one may wish to restrict setting this to the elements only, by specifying fem.getElements() and/or fem.getShellElements() to setFaceColor().

Element widgets can provide a easy way to select specific elements. The elementWidgetSize property is also present as an inherited property in the volumetric and shell element lists (elements and shellElements), as well as individual elements, and so widget rendering can be controlled on a per-element basis.

1.12.3 Surface and other meshes

Figure 1.20: Surface mesh of a torus rendered as a regular mesh (left, with surfaceRendering set to Shaded), and as a color map of the von Mises stress (right, with surfaceRendering set to Stress).

A FEM model can also be rendered using its surface mesh and/or other mesh geometry (Section 1.19). How meshes are rendered is controlled by the property surfaceRendering, which can be assigned any of the values shown in Table 1.5. The value None causes nothing to be rendered, while Shaded causes a mesh to be rendered using the standard rendering properties appropriate to that mesh. For polygonal meshes (which include the surface mesh), this will be done according to the face rendering properties in same way as for rigid bodies (Section LABEL:rigidBodyRendering:sec). These properties include faceColor, shading, alpha, faceStyle, drawEdges, edgeWidth, and edgeColor (or lineColor if the former is not set). The following code fragment sets an FEM surface to be rendered with blue-gray faces and dark blue edges (Figure 1.20, left):

import java.awt.Color;
import maspack.render.RenderProps;
import artisynth.core.femmodels.FemModel.SurfaceRender;
   ...
   FemModel3d fem;
   ...
   fem.setSurfaceRendering (SurfaceRender.Shaded);
   RenderProps.setFaceColor (fem, new Color (0.7f. 0.7f, 1f));
   RenderProps.setDrawEdges (fem, true);
   RenderProps.setEdgeColor (fem, Color.BLUE);
Table 1.5: Values of the surfaceRendering property controlling how a polygonal FEM is displayed. Scalar stress/strain measures are described in Section 1.11.2
Value Description
None mesh is not rendered
Shaded rendered as a mesh using the standard rendering properties
Stress color map of the von Mises stress
Strain color map the von Mises strain
MAPStress color map of the maximum absolute value principal stress
MAPStrain color map of the maximum absolute value principal strain
MaxShearStress color map of the maximum shear stress
MaxStearStrain color map of the maximum sheer strain
EnergyDensity color map of the strain energy density

Other values of surfaceRendering cause polygonal meshes to be displayed as a color map showing the various stress or strain measures shown in Table 1.5 and described in greater detail in Section 1.11.2. The fragment below sets an FEM surface to be rendered to show the von Mises stress (Figure 1.20, right), while also rendering the edges as dark blue:

import java.awt.Color;
import maspack.render.RenderProps;
import artisynth.core.femmodels.FemModel.SurfaceRender;
   ...
  FemModel3d fem;
   ...
   fem.setSurfaceRendering (SurfaceRender.Stress);
   RenderProps.setDrawEdges (fem, true);
   RenderProps.setEdgeColor (fem, Color.BLUE);

The color maps used in stress/strain rendering are controlled using the following additional properties:

stressPlotRange

The range of numeric values associated with the color map. These are either fixed or updated automatically, according to the property stressPlotRanging. Values outside the range are truncated.

stressPlotRanging

Describes if and how stressPlotRange is updated:

Fixed

Range does not change and should be set by the application.

Auto

Range automatically expands to contain the most recently computed values. Note that this does not cause the range to contract.

colorMap

Value is a delegate object that converts stress and strain values to colors. Various types of maps exist, including HueColorMap (the default), GreyscaleColorMap, RainbowColorMap, and JetColorMap. These all implement the ColorMap interface.

All of these properties are inherited and exported both by FemModel3d and the individual mesh components (which are instances of FemMeshComp), allowing mesh rendering to be controlled on a per-mesh basis.

1.12.4 FEM-based muscles

FemMuscleModel and its subcomponents MuscleBundle export additional properties to control the rendering of muscle bundles and their associated fibre directions. These include:

directionRenderLen

A scalar in the range [0,1], which if >0 causes the fibre directions to be rendered within the elements associated with a MuscleBundle (Section 1.9.3). The directions are rendered as line segments, controlled by the standard line render properties lineColor and lineWidth, and the value of directionRenderLen specifies the length of this segment relative to the size of the element.

directionRenderType

If directionRenderLen >0, this property specifics where the fibre directions should be rendered within muscle bundle elements. The value ELEMENT causes a single direction to be rendered at the element center, while INTEGRATION_POINTS causes directions to be rendered at each of the element’s integration points.

elementWidgetSize

A scalar in the range [0,1], which if >0 causes the elements within a muscle bundle to be rendered using an element widget (Section 1.12.2).

Since these properties are inherited and exported by both FemMuscleModel and MuscleBundle, they can be set for the FEM muscle model as a whole or for individual muscle bundles.

The code fragment below sets the element fibre directions for two muscle bundles to be drawn, one in red and one in green, using a directionRenderLen of 0.5. Each bundle runs horizontally along an FEM beam, one at the top and the other at the bottom (Figure 1.21, left):

   FemModel3d fem;
   ...
   // set line width and directionRenderLen for all bundles and lines
   RenderProps.setLineWidth (fem, 2);
   fem.setDirectionRenderLen (0.5);
   // set line color for FEM elements
   RenderProps.setLineColor (fem, new Color(0.7f, 0.7f, 1f));
   // set line colors for top and bottom bundles
   MuscleBundle top = fem.getMuscleBundles().get("top");
   MuscleBundle bot = fem.getMuscleBundles().get("bot");
   RenderProps.setLineColor (top, Color.RED);
   RenderProps.setLineColor (bot, new Color(0f, 0.8f, 0f));

By default, directionRenderType is set to ELEMENT and so directions are drawn at the element centers. Setting directionRenderType to INTEGRATION_POINT causes the directions to be drawn at each integration points (Figure 1.21, right), which is useful when directions are specified at integration points (Section 1.9.3).

If we are only interested in seeing the elements associated with a muscle bundle, we can use its elementWidgetSize property to draw the elements as widgets (Figure 1.22, left). For this, the last two lines of the fragment above could be replaced with

   RenderProps.setFaceColor (top, Color.RED);
   top.setElementWidgetSize (0.6);
   RenderProps.setFaceColor (bot, new Color(0f, 0.8f, 0f));
   bot.setElementWidgetSize (0.6);

Finally, if the muscle bundle contains point-to-point fibres (Section 1.9.2), these can be rendered by setting the line properties of the standard render properties. For example, the fibre rendering of Figure 1.22 (right) can be set up using the code

   RenderProps.setSpindleLines (top, /*radius=*/0.13, Color.RED);
   RenderProps.setSpindleLines (bot, /*radius=*/0.13, new Color(0f, 0.8f, 0f));
Figure 1.21: Rendering the element fibre directions for two muscle bundles, one in red and one in green, with directionRenderLen of 0.5 and directionRenderType set to ELEMENT (left) and INTEGRATION_POINT (right).
Figure 1.22: Left: rendering muscle bundle elements using element widgets with elementWidgetSize set to 0.6 (left). Right: rendering muscle bundle fibres.

1.12.5 Color bars

To display values corresponding to colors, a ColorBar needs to be added to the RootModel. Color bars are general Renderable objects that are only used for visualizations. They are added to the display using the

addRenderable (Renderable r)

method in RootModel. Color bars also have a ColorMap associated with it. The following functions are useful for controlling its visualization:

setNumberFormat (String fmtStr );    // C-like numeric format specification
populateLabels (double min, double max, int tick );     // initialize labels
updateLabels (double min, double max );                 // update existing labels
setColorMap (ColorMap map );                            // set color map
// Control position/size of the bar
setNormalizedLocation (double x, double y, double width, double height);
setLocationOverride (double x, double y, double width, double height)

The normalized location specifies sizes relative to the screen size (1 = screen width/height). The location override, if values are non-zero, will override the normalized location, specifying values in absolute pixels. Negative values for position correspond to distances from the left/top. For instance,

setNormalizedLocation(0, 0.1, 0, 0.8);  // set relative positions
setLocationOverride(-40, 0, 20, 0);     // override with pixel lengths

will create a bar that is 10% up from the bottom of the screen, 40 pixels from the right edge, with a height occupying 80% of the screen, and width 20 pixels.

Note that the color bar is not associated with any mesh or finite element model. Any synchronization of colors and labels must be done manually by the developer. It is recommended to do this in the RootModel’s prerender(...) method, so that colors are updated every time the model’s rendering configuration changes.

1.12.6 Example: stress/strain plotting with color bars

Figure 1.23: FemBeamColored model loaded into ArtiSynth.

The following model extends FemBeam to render stress, with an added color bar. The loaded model is shown in Figure 1.23.

1 package artisynth.demos.tutorial;
2
3 import java.io.IOException;
4
5 import maspack.render.RenderList;
6 import maspack.util.DoubleInterval;
7 import artisynth.core.femmodels.FemModel.Ranging;
8 import artisynth.core.femmodels.FemModel.SurfaceRender;
9 import artisynth.core.renderables.ColorBar;
10
11 public class FemBeamColored extends FemBeam {
12
13    @Override
14    public void build(String[] args) throws IOException {
15       super.build(args);
16
17       // Show stress on the surface
18       fem.setSurfaceRendering(SurfaceRender.Stress);
19       fem.setStressPlotRanging(Ranging.Auto);
20
21       // Create a colorbar
22       ColorBar cbar = new ColorBar();
23       cbar.setName("colorBar");
24       cbar.setNumberFormat("%.2f");      // 2 decimal places
25       cbar.populateLabels(0.0, 1.0, 10); // Start with range [0,1], 10 ticks
26       cbar.setLocation(-100, 0.1, 20, 0.8);
27       addRenderable(cbar);
28
29    }
30
31    @Override
32    public void prerender(RenderList list) {
33       super.prerender(list);
34       // Synchronize color bar/values in case they are changed. Do this *after*
35       // super.prerender(), in case values are changed there.
36       ColorBar cbar = (ColorBar)(renderables().get("colorBar"));
37       cbar.setColorMap(fem.getColorMap());
38       DoubleInterval range = fem.getStressPlotRange();
39       cbar.updateLabels(range.getLowerBound(), range.getUpperBound());
40
41
42    }
43
44 }

1.12.7 Cut planes

In addition to stress/strain visualization on its meshes, FEM models can be supplied with one or more cut planes that allow stress or strain values to be visualized on the cross section of the model with the plane.

Cut plane components are implemented by FemCutPlane and can be created with the following constructors:

FemCutPlane()

Creates a cut plane aligned with the world origin.

FemCutPlane (double res)

Creates an origin-aligned cut plane with grid resolution res.

FemCutPlane (RigidTransform3d TPW)

Creates a cut plane with pose TPW a specified grid resolution.

FemCutPlane (double res, RigidTransform3d TPW)

Creates a cut plane with pose TPW and grid resolution res.

The pose and resolution are described further below. Once created, a cut plane can be added to an FEM model using its addCutPlane() method:

  FemModel3d fem;
  RigidTransform3d TPW; // desired pose of the plane
  ... initialize fem and TPW ...
  FemCutPlane cplane = new FemCutPlane (TPW);
  fem.addCutPlane (cplane);

The FEM model methods for handling cut planes include:

void addCutPlane(FemCutPlane cp)

Adds a cut plane to the model.

int numCutPlanes()

Queries number of cut planes in the model.

FemCutPlane getCutPlanes(int idx)

Gets the idx-th cut plane in the model.

boolean removeCutPlane(FemCutPlane cp)

Removes a cut plane from the model.

void clearCutPlanes()

Removes all cut planes from the model.

As with rigid and mesh bodies, the pose of the cut plane gives its position and orientation relative to world coordinates and is described by a RigidTransform3d. The plane itself is aligned with the x-y plane of this local coordinate system. More specifically, if the pose is given by {\bf T}_{PW} such that

{\bf T}_{PW}\,=\,\left(\begin{matrix}{\bf R}_{PW}&{\bf p}_{PW}\\
0&1\end{matrix}\right) (1.39)

then the plane passes through point {\bf p}_{PW} and its normal is given by the z axis (third column) of {\bf R}_{PW}. When rendered in the viewer, FEM model stress/strain values are displayed as a color map within the polygonal region formed by the intersection between the plane and the model’s surface mesh.

The following properties of FemCutPlane are used to control how it is rendered in the viewer:

squareSize

A double value, which if >0 specifies the size of a square that shows the position of the plane.

resolution

A double value, which if >0 specifies an explicit size (in units of distance) for the grid cells created within the FEM surface/plane intersection to interpolate stress/strain values. Smaller values will give more accurate results but may slow down the rendering time. Accuracy will also not be improved if the resolution is significantly less that the size of the FEM elements. If resolution \leq 0, the grid size is determined automatically based on the element sizes.

axisLength, axisDrawStyle

Identical in function to the axisLength and axisDrawStyle properties for rigid bodies (Section LABEL:rigidBodyRendering:sec). Specifies the length and style for rendering the local coordinate frame of the cut plane.

surfaceRendering

Describes what is rendered on the surface/plane intersection polygon, according to table 1.5.

stressPlotRange, stressPlotRanging, colorMap

Identical in function to the stressPlotRange, stressPlotRanging and colorMap properties exported by FemModel3d and FemMeshComp (Section 1.12.3). Controls the range and color map associated with the surface rendering.

As with all properties, the values of the above can be accessed either in code using set/get accessors named after the property (e.g., setSurfaceRendering(), getSurfaceRendering()), or within the GUI by selecting the cut plane and then choosing Edit properties ... from the context menu.

1.12.8 Example: FEM model with a cut plane

Figure 1.24: FemCutPlaneDemo, showing stress values within the plane after the model has run and settled into an equilibrium state.

Cut planes are illustrated by the application model defined in

  artisynth.demos.tutorial.FemCutPlaneDemo

which creates a simple FEM model in the shape of a half-torus, and then adds a cut plane to render its internal stresses. Its build() method is give below:

1    public void build (String[] args) {
2       // create a MechModel to contain the FEM model
3       MechModel mech = new MechModel ("mech");
4       addModel (mech);
5
6       // create a half-torus shaped FEM to illustrate the cut plane
7       FemModel3d fem = FemFactory.createPartialHexTorus (
8         null, 0.1, 0.0, 0.05, 8, 16, 3, Math.PI);
9       fem.setMaterial (new LinearMaterial (20000, 0.49));
10       fem.setName ("fem");
11       mech.addModel (fem);
12       // fix the bottom nodes, which lie on the z=0 plane, to support it
13       for (FemNode3d n : fem.getNodes()) {
14          Point3d pos = n.getPosition();
15          if (Math.abs(pos.z) < 1e-8) {
16             n.setDynamic (false);
17          }
18       }
19
20       // create a cut plane, with stress rendering enabled and a pose that
21       // situates it in the z-x plane
22       FemCutPlane cplane =
23          new FemCutPlane (new RigidTransform3d (0,0,0.03,  0,0,Math.PI/2));
24       fem.addCutPlane (cplane);
25
26       // set stress rendering with a fixed range of (0, 1500)
27       cplane.setSurfaceRendering (SurfaceRender.Stress);
28       cplane.setStressPlotRange (new DoubleInterval (0, 1500.0));
29       cplane.setStressPlotRanging (Ranging.Fixed);
30
31       // create a panel to control cut plane properties
32       ControlPanel panel = new ControlPanel();
33       panel.addWidget (cplane, "squareSize");
34       panel.addWidget (cplane, "axisLength");
35       panel.addWidget (cplane, "stressPlotRanging");
36       panel.addWidget (cplane, "stressPlotRange");
37       panel.addWidget (cplane, "colorMap");
38       addControlPanel (panel);
39
40       // set other render properites ...
41       // make FEM line color white:
42       RenderProps.setLineColor (fem, Color.WHITE);
43       // make FEM elements invisible so they’re not in the way:
44       RenderProps.setVisible (fem.getElements(), false);
45       // render FEM using a wireframe surface mesh so we can see through it:
46       fem.setSurfaceRendering (SurfaceRender.Shaded);
47       RenderProps.setDrawEdges (fem.getSurfaceMeshComp(), true);
48       RenderProps.setFaceStyle (fem.getSurfaceMeshComp(), FaceStyle.NONE);
49       RenderProps.setEdgeWidth (fem.getSurfaceMeshComp(), 2);
50       // render cut plane using both square outline and its axes:
51       cplane.setSquareSize (0.12); // size of the square
52       cplane.setAxisLength (0.08); // length of the axes
53       RenderProps.setLineWidth (cplane, 2); // boost line width for visibility
54    }

After a MechModel is created (lines 27-29), an FEM model consisting of a half-torus is created, with the bottom nodes set non-dynamic to provide support (lines 31-43). A cut plane is then created with a pose that centers it on the world origin and aligns it with the world z-x plane (lines 47-49). Surface rendering is set to Stress, with a fixed color plot range of (0,1500) (lines 52-54). A control panel is created to expose various properties of the plane (lines 57-63), and then other rendering properties are set: the default FEM line color is made white (line 67); to avoid visual clutter FEM elements are made invisible and instead the FEM is rendered using a wireframe representation of its surface mesh (lines 69-74); and in addition to its render surface, the cut plane is also displayed using its coordinate axes (with length 0.08) and a square of size 0.12 (lines 76-78).

To run this example in ArtiSynth, select All demos > tutorial > FemCutPlaneDemo from the Models menu. When loaded and run, the model should appear as in Figure 1.24. The plane can be repositioned by selecting it in the viewer (by clicking on its square, coordinate axes, or render surface) and then using one of the transformer tools to change its position and/or orientation (see the section “Transformer Tools” in the ArtiSynth User Interface Guide).