In modeling applications, particularly those employing FEM methods, situations often arise where it is necessary to describe numeric quantities that vary over some spatial domain. For example, the stiffness parameters of an FEM material may vary at different points within the volumetric mesh. When modeling muscles, the activation direction vector will also typically vary over the mesh.
ArtiSynth provides field components which can be used to represent such spatially varying quantities, together with mechanisms to attach these to certain properties within various materials. Field components can implement either scalar or vector fields. Scalar field components implement the interface ScalarFieldComponent and supply the method
while vector fields implement VectorFieldComponent and supply the method
where T parameterizes a class implementing maspack.matrix.VectorObject. In both cases, the idea is to provide values at arbitrary points over some spatial domain.
For vector fields, the VectorObject represented by T can generally be any fixed-size vector or matrix located in the package maspack.matrix, such as Vector2d, Vector3d, or Matrix3d. T can also be one of the variable-sized objects VectorNd or MatrixNd, although this requires using special wrapper classes and the sizing must remain constant within the field (Section 6.10.4).
Both ScalarFieldComponent and VectorFieldComponent are subclassed from FieldComponent. The reason for separating scalar and vector fields is simply efficiency: having scalar fields work with the primitive type double, instead of the object Double, requires considerably less storage and somewhat less computational effort.
A field is typically implemented by specifying a finite set of values at discrete locations on an underlying spatial grid and then using an interpolation method to determine values at arbitrary points. The field components themselves are defined within the package artisynth.core.fields.
At present, three types of fields are implemented:
The most basic type of field, in which the values are specified at the vertices of a regular Cartesian grid and then interpolated between vertices. As discussed further in Section 6.10.1, there are two main types grid field component: ScalarGridField and VectorGridField<T>.
Fields for which values are specified at the features of an FEM mesh (e.g., nodes, elements or element integration points). As discussed further in Section 6.10.2, there are six primary types of FEM field component: ScalarNodalField and VectorNodalField<T> (nodes), ScalarElementField and VectorElementField<T> (elements), and ScalarSubElemField and VectorSubElemField<T> (integation points).
Fields in which values are specified for either the vertices or faces of a triangular mesh. As discussed further in Section 6.10.3, there are four primary types of mesh field component: ScalarVertexField and VectorVertexField<T> (vertices), and ScalarFaceField and VectorFaceField<T> (faces).
Within an application model, field deployment typically involves the following steps:
Create the field component and add it to the model. Grid and mesh fields are usually added to the fields component list of a MechModel, while FEM fields are added to the fields list of the FemModel3d for which the field is defined.
Specify field values for the appropriate features (e.g., grid vertices, FEM nodes, mesh faces).
If necessary, bind any needed material properties to the field, as discussed further in Section 6.10.5.
As a simple example of the first two steps, the following code fragment constructs a scalar nodal field associated with an FEM model named fem:
More details on specific field components are given in the sections below.
A grid field specifies scalar or vector values at the vertices of a regular Cartesian grid and interpolates values between vertices. ArtiSynth currently provides two grid field components in artisynth.core.fields:
where T is any class implementing
maspack.matrix.VectorObject. For both grid types, the
value returned by getValue(p) is determined by finding the grid
cell containing and then using trilinear interpolation of the
surrounding nodal values. If
lies outside the grid volume,
then getValue(p) either returns the value at the nearest grid
point
(if the component property clipToGrid is set to true), or else returns a special value indicating that
is
outside the grid. This special value is ScalarGridField.OUTSIDE_GRID for scalar fields or null for
vector fields.
Grid field components are implemented as wrappers around the more basic objects ScalarGrid and VectorGrid<T> defined in maspack.geometry. Applications first create one of these primary grid objects and then use it to create the field component. Instances of ScalarGrid and VectorGrid<T> can be created with constructors such as
where widths gives the grid widths along each of the ,
and
axes, res gives the number of cells along each axis, and
for vector grids type is the class type of the
maspack.matrix.VectorObject object parameterized by T. TCL is an optional argument which, if not null,
describes the position and orientation of the grid center with respect
to local coordinates; otherwise, in local coordinates the grid is
centered at the origin and aligned with the
,
and
axes.
For the vector grid constructors above, T should be a fixed-size vector or matrix, such as Vector3d or Matrix3d. The variable sized objects VectorNd and MatrixNd can also be used with the aid of special wrapper classes, as described in Section 6.10.4.
By default, grids are axis-aligned and centered at the origin of the world coordinate system. A transform TLW can be specified to place the grid at a different position and/or orientation. TLW represents the transform from local to world coordinates and can be controlled with the methods
If not specified, TLW is the identity and local and world coordinates are the same.
Once a grid is created, it can be used to instantiate a grid field component using one of the constructors
where grid is the primary grid and name is an optional component name. Note that the primary grid is not copied, so any subsequent changes to it will be relected in the enclosing field component.
Once the grid field component is created, its values can be set by specifying the values at its vertices. The methods to query and set vertex values for scalar and vector fields are
where xi, yj, and zk are the vertex’s indices along the x, y, and z axes, and vi is a general index that should be in the range 0 to field.numVertices()-1 and is related to xi, yj, and zk by
vi = xi + nx*yj + (nx*ny)*zk,
where nx and ny are the number of vertices along the
and
axes.
When computing a grid value using getValue(p), the point p is assumed to be in either grid local or world coordinates, depending on whether the field component’s property localValuesForField is true or false (local and world coordinates are the same unless the primary grid’s local-to-world transform TLW has been set as described above).
To find the spatial position of a vertex within a grid field component, one may use the methods
which return the vertex position in either local or world coordinates depending on the setting of localValuesForField.
The following code example shows the creation of a ScalarGridField, with widths and a cell
resolution of
, centered at the origin and
whose vertex values are set to their distance from the origin,
in order to create a simple distance field:
As shown in the example and as mentioned earlier, grid field are generally added to the fields list of a MechModel.
A FEM field specifies scalar or vector values for the features of an FEM mesh. These can be either the nodes (nodal fields), elements (element fields), or element integration points (sub-element fields). As such, FEM fields provide a way to augment the mesh to store application-specific quantities, and are typically used to bind properties of FEM materials that need to vary over the domain (Section 6.10.5).
To evaluate getValue(p) at an arbitrary point , the field
finds the FEM element containing
(or nearest to
, if
is
outside the FEM mesh), and either interpolates the value from the
surrounding nodes (nodal fields), uses the element value directly
(element fields), or interpolates from the integration point values
(sub-element fields).
When finding a FEM field value at a point
, getValue(p) is evaluated with respect to the FEM model’s current spatial position, as opposed to its rest position.
FEM fields maintain a default value which describes the value at features for which values are not explicitly set. If unspecified, the default value itself is zero.
In the remainder of this section, it is assumed that vector fields are constructed using fixed-size vectors or matrices, such as Vector3d or Matrix3d. However, it is also possible to construct fields using VectorNd or MatrixNd, with the aid of special wrapper classes, as described in Section 6.10.4. That section also details a convenience wrapper class for Vector3d.
The three field types are now described in detail.
Implemented by ScalarNodalField, VectorNodalField<T>, or subclasses of the latter, nodal fields specify their values at the nodes of an FEM mesh. They can be created with constructors such as
where fem is the associated FEM model, defaultValue is the default value, and name is a component name. For vector fields, the maspack.matrix.VectorObject type is parameterized by T, and type gives its actual class type (e.g., Vector3d.class).
Once the grid has been created, values can be queried and set at the nodes using methods such as
Here nodeNum refers to an FEM node’s number (which can be obtained using node.getNumber()). Numbers are used instead of indices to identity FEM nodes and elements because they are guaranteed to be persistent in case of mesh editing, and will remain unchanged as long as the node or element is not removed from the FEM model. Note that values don’t need to be set at all nodes, and set values can be unset using the clear methods. For nodes with no value set, the getValue() methods will return the default value.
As a simple example, the following code fragment constructs a ScalarNodalField for an FEM model fem that describes stiffness values at every node of an FEM:
As noted earlier, FEM fields should be stored in the fields list of the associated FEM model.
Another example shows the creation of a field of 3D direction vectors:
When creating a vector field, the constructor needs the class type of the field’s VectorObject. However, for the special case of Vector3d, one may also use the convenience wrapper class Vector3dNodalField, described in Section 6.10.4.
Implemented by
ScalarElementField,
VectorElementField<T>,
or subclasses of the latter, element fields specify their values at
the elements of an FEM mesh, and these values are assumed to be constant within the element. To evaluate getValue(p), the field
finds the containing (or nearest) element for , and then returns
the value for that element.
Because values are assume to be constant within each element, element fields are inherently discontinuous across element boundaries.
The constructors for element fields are analogous to those for nodal fields:
and the methods for setting values at elements are similar as well:
The elements associated with an element field can be either volume or shell elements, which are stored in the FEM component lists elements and shellElements, respectively. Since volume and shell elements may share element numbers, the separate methods getElementValue() and getShellElementValue() are used to access values by element number.
The following code fragment constructs a VectorElementField based on Vector2d that stores a 2D coordinate value at all regular and shell elements:
Implemented by ScalarSubElemField, VectorSubElemField<T>, or subclasses of the latter, sub-element fields specify their values at the integration points within each element of an FEM mesh. These fields are used when we need precisely computed information at each of the element’s integration points, and we can’t assume that nodal interpolation will give an accurate enough approximation.
To evaluate getValue(p), the field finds the containing (or
nearest) element for , extrapolating the intergration point values
back to the nodes, and then using nodal interpolation.
Constructors are similar to those for element fields:
Value accessors are also similar, except that an additional argument k is required to specify the index of the integration point:
The integration point index k should be in the range 0 to ,
where
is the total number of integration points for the element in
question and can be queried by the method
numAllIntegrationPoints().
The total number of integration points
includes both the regular integration point as well as the warping point, which is located at the element center, is indexed by
, and is used for corotated linear materials. If a SubElemField is being using to supply mesh-varying values to one of the linear material parameters (Section 6.10.5), then it is important to supply values at the warping point.
The example below shows the construction of a ScalarSubElemField:
First, the field is instantiated with the name "modulus". The code then iterates first through the FEM elements, and then through each element’s integration points, computing values appropriate to each one. A list of each element’s integration points is returned by e.getAllIntegrationPoints(). Having access to the actual integration points is useful in case information about them is needed to compute the field values. In particular, if it is necessary to obtain an integration point’s rest or current (spatial) position, these can be queried as follows:
The regular integration points, excluding the warping point, can be queried using getIntegrationPoints() and numIntergrationPoints() instead of getAllIntegrationPoints() and numAllIntegrationPoints().
A mesh field specifies scalar or vector values for the features of an FEM mesh. These can be either the vertices (vertex fields) or faces (face fields). Mesh fields have been introduced in particular to allow properties of contact force behaviors (Section 7.7.2), such as LinearElasticContact, to vary over the contact mesh. Mesh fields can currently be defined only for triangular polyhedral meshes.
To evaluate getValue(p) at an arbitrary point , the field
finds the mesh face nearest to
, and then either interpolates the
value from the surrounding vertices (vertex fields) or uses the face
value directly (face fields).
Mesh fields make use of the classes PolygonalMesh, Vertex3d, and Face, defined in the package maspack.geometry. They maintain a default value which describes the value at features for which values are not explicitly set. If unspecified, the default value itself is zero.
In the remainder of this section, it is assumed that vector fields are constructed using fixed-size vectors or matrices, such as Vector3d or Matrix3d. However, it is also possible to construct fields using VectorNd or MatrixNd, with the aid of special wrapper classes, as described in Section 6.10.4. That section also details a convenience wrapper class for Vector3d.
The two field types are now described in detail.
Implemented by ScalarVertexField, VectorVertexField<T>, or subclasses of the latter, vertex fields specify their values at the vertices of a mesh. They can be created with constructors such as
where mcomp is a mesh component containing the mesh, defaultValue is the default value, and name is a component name. For vector fields, the maspack.matrix.VectorObject type is parameterized by T, and type gives its actual class type (e.g., Vector3d.class).
Once the field has been created, values can be queried and set at the vertices using methods such as
where vidx is the vertex’s index. Note that values don’t need to be set at all vertices, and set values can be unset using the clear methods. For vertices with no value set, getValue() will return the default value.
As a simple example, the following code fragment constructs a ScalarVertexField for a mesh contained in the MeshComponent mcomp that describes contact stiffness values at every vertex:
As noted earlier, mesh fields are usually stored in the fields list of a MechModel.
Another example shows the creation of a field of 3D direction vectors:
When creating a vector field, the constructor needs the class type of the field’s VectorObject. However, for the special case of Vector3d, one may also use the convenience wrapper class Vector3dVertexField, described in Section 6.10.4.
Implemented by
ScalarFaceField,
VectorFaceField<T>,
or subclasses of the latter, face fields specify their values at the
faces of a polygonal mesh, and these values are assumed to be constant within the face. To evaluate getValue(p), the field
finds the containing (or nearest) face for , and then returns the
value for that face.
Because values are assume to be constant within each face, face fields are inherently discontinuous across face boundaries.
The constructors for face fields are analogous to those for vertex fields:
and the methods for setting values at faces are similar as well, where fidx refers to the face index:
The following code fragment constructs a VectorFaceField based on Vector2d that stores a 2D coordinate value at all faces of a mesh:
The vector fields described above can be implemented for most fixed-size vectors and matrices defined in the package maspack.matrix (e.g., Vector2d, Vector3d, Matrix3d). However, if vectors or matrices with different size are needed, it is also possible to create vector fields using VectorNd and MatrixNd, provided that the sizing remain constant within any given field. This is acheived using special wrapper classes that contain the sizing information, which is supplied in the constructors. For convenience, wrapper classes are also provided for vector fields that use Vector3d, since that vector size is quite common.
For vector FEM and mesh fields, the complete set of wrapper classes is listed below:
Class | base class | vector type T |
---|---|---|
Vector FEM fields: | ||
Vector3dNodalField | VectorNodalField<T> | Vector3d |
VectorNdNodalField | VectorNodalField<T> | VectorNd |
MatrixNdNodalField | VectorNodalField<T> | MatrixNd |
Vector3dElementField | VectorElementField<T> | Vector3d |
VectorNdElementField | VectorElementField<T> | VectorNd |
MatrixNdElementField | VectorElementField<T> | MatrixNd |
Vector3dSubElemField | VectorSubElemField<T> | Vector3d |
VectorNdSubElemField | VectorSubElemField<T> | VectorNd |
MatrixNdSubElemField | VectorSubElemField<T> | MatrixNd |
Vector mesh fields: | ||
Vector3dVertexField | VectorVertexField<T> | Vector3d |
VectorNdVertexField | VectorVertexField<T> | VectorNd |
MatrixNdVertexField | VectorVertexField<T> | MatrixNd |
Vector3dFaceField | VectorFaceField<T> | Vector3d |
VectorNdFaceField | VectorFaceField<T> | VectorNd |
MatrixNdFaceField | VectorFaceField<T> | MatrixNd |
These all behave identically to their base classes, except that their constructors omit the type argument (since this is built into the class definition) and, for VectorNd and MatrixNd, supply information about the vector or matrix sizes. For example, constructors for the FEM nodal fields include
where vecSize, rowSize and colSize give the sizes of the VectorNd or MatrixNd objects within the field. Constructors for other field types are equivalent and are described in their API documentation.
For grid fields, one can use either VectorNdGrid or MatrixNdGrid, both defined in maspack.geometry, as the primary grid used to construct the field. Constructors for these include
A principal purpose of field components is to enable certain FEM material properties to vary spatially over the FEM geometry. Many material properties can be bound to a field, so that when they are queried internally by the solver at the integration points for each FEM element, the field value at that integration point is used instead of the regular property value. Likewise, some properties of contact force behaviors, such as the YoungsModulus and thickness properties of LinearElasticContact (Section 7.7.3) can be bound to a field that varies over the contact mesh geometry.
To bind a property to a field, it is necessary that
The type of the field matches the value of the property;
The property is itself bindable.
If the property has a double value, then it can be bound to any ScalarFieldComponent. Otherwise, if the property has a value T, where T is an instance of maspack.matrix.VectorObject, then it can be bound to a vector field.
Bindable properties export two methods with the signature
where XXX is the property name and F is an appropriate field type.
As long as a property XXX is bound to a field, its regular value will still appear (and can be set) through widgets in control or property panels, or via its getXXX() and setXXX() accessors. However, the regular value won’t be used internally by the FEM simulation.
For example, consider the YoungsModulus property, which is present in several FEM materials, including LinearMaterial and NeoHookeanMaterial. This has a double value, and is bindable, and so can be bound to a ScalarFieldComponent as follows:
It is important to perform field bindings on materials before they are set in an FEM model (or one of its subcomponents, such as MuscleBundles). That’s because the setMaterial() method copies the input material, and so any settings made on it afterwards won’t be seen by the FEM:
// works: field will be seen by the copied version of ’mat’ mat.setYoungsModulusField (field); fem.setMaterial (mat); // does NOT work: field not seen by the copied version of ’mat’ fem.setMaterial (mat); mat.setYoungsModulusField (field);
To unbind YoungsModulus, one can call
Usually, FEM fields are used to bind properties in FEM materials while mesh fields are used for contact properties, but other fields can be used, particularly grid fields. To understand this a bit better, we discuss briefly some of the internals of how binding works. When evaluating stresses, FEM materials have access to a FemFieldPoint object that provides information about the integation point, including its element, nodal weights, and integration point index. This can be used to rapidly evaluate values within nodal, element and sub-element FEM fields. Likewise, when evaluating contact forces, contact materials like LinearElasticContact have access to a MeshFieldPoint object that describes the contact point position as a weighted sum of mesh vertex positions, which can then be used to rapidly evaluate evaluate values within vertex or face mesh fields. However, all fields, regardless of type, implement methods for determining values at both FemFieldPoints and MeshFieldPoints:
If necessay, these methods simply fall back on the getValue(Point3d) method, which is defined for all fields and works relatively fast for grid fields.
There are some additional details to consider when binding FEM material properties:
When binding to a grid field, one has the choice of whether to use the grid to represent values with respect to the FEM’s rest position or spatial position. This can be controlled by setting the useFemRestPositions property of the grid field, the default value for which is true.
When binding the bulkModulus property of an incompressible material, it is best to use a nodal field if the FEM model is using nodal-based soft incompressibility (i.e., the model’s softIncompMethod property is set to either NODAL or AUTO; Section 6.7.3). That’s because soft nodal incompressibility require evaluating the bulk modulus property at nodes instead of integration points, which is much easier to do with a nodal-based field.
A simple model demonstrating a stiffness that varies over an FEM mesh is defined in
artisynth.demos.tutorial.VariableStiffness
It consists of a simple thin hexahedral beam with a linear material
for which the Young’s modulus is made to vary nonlinearly along the
x axis of the rest position according to the formula
![]() |
(6.10) |
The model’s build method is given below:
Lines 6-10 create the hex FEM model and shift it so that the left side
is aligned with the origin, while lines 12-17 fix the leftmost
nodes. Lines 21-27 create a scalar nodal field for the Young’s modulus,
with lines 23-24 computing according to (6.10).
The field is then bound to a linear material which is then set in the
model (lines 30-32).
The example can be run in ArtiSynth by selecting All demos > tutorial > VariableStiffness from the Models menu. When run, the beam will bend under gravity, but mostly on the right side, due to the much higher stiffness on the left (Figure 6.15).
![]() |
![]() |
Another example involves using a Vector3d field to specify the muscle activation directions over an FEM model and is defined in
artisynth.demos.tutorial.RadialMuscle
When muscles are added using MuscleBundles ( Section 6.9), the muscle directions are stored and handled internally by the muscle bundle itself. However, it is possible to add a MuscleMaterial directly to the elements of an FEM model, using a MaterialBundle (Section 6.8), in which case the directions need to be set explicitly using a field.
The model’s build method is given below:
Lines 5-19 create a thin cylindrical FEM model, centered on the
origin, with radius and height
, consisting of hexes with
wedges at the center, with two layers of elements along the
axis
(which is parallel to the cylinder axis). Its base material is set to
a neo-hookean material. To keep the model from falling under gravity,
all nodes whose distance to the
axis is less than
are fixed.
Next, a Vector3d field is created to specify the directions, on a
per-element basis, for the muscle material which will be added subsequently
(lines 21-32). While we could create an instance of VectorElementField<Vector3d>, we use
Vector3dElementField, since this
is available and provides additional functionality (such as the
ability to render the directions). Directions are set to lie outward
in a radial direction perpendicular to the axis, and since the
model is centered on the origin, they can be computed easily by first
computing the element centroids, removing the
component, and then
normalizing. In order to give the muscle action an upward bias, we
only set directions for elements in the upper layer. Direction values
for elements in the lower layer will then automatically have a default
value of 0, which will cause the muscle material to not apply any
stress.
We next add to the model a muscle material whose directions will be determined by the field. To hold the material, we first create and add a MaterialBundle which is set to act on all elements (line 35-36). Then we set this bundle’s material to SimpleForceMuscle, which adds a stress along the muscle direction that equals the excitation value times the value of its maxStress property, and bind the material’s restDir property to the direction field (lines 37-39).
Finally, we create and add a control panel to allow interactive control over the muscle material’s excitation property (lines 42-44), and set some rendering properties for the FEM model.
The example can be run in ArtiSynth by selecting All demos > tutorial > RadialMuscle from the Models menu. When it is first run, it falls around the edges under gravity (Figure 6.16, top). Applying an excitation causes a radial contraction which pulls the edges upward and, if high enough, causes then to buckle (Figure 6.16, bottom).