In this section, we provide information on several basic SimTK classes, all based on the small Vec and Mat classes described above, that are used in the OpenSim API to deal with geometrical and mechanical concepts:
Stations are simply points which are fixed in a particular reference frame or body (i.e., they are "stationary" in that frame). They are specified by the position vector which would take the frame's origin to the station. A position is represented by a Vec3 type. SimTK does not provide an explicit Station class; Vec3s are adequate whenever a station is to be specified.
Directions (unit vectors)
Directions are unit vectors, which are Vec3s with the additional property that their lengths are always 1. SimTK provides a class UnitVec3 which behaves identically to Vec3 in most respects but restricts the ways in which values can be assigned to ensure that the length is always 1. This has the practical advantage that you never need to normalize a UnitVec3; it is guaranteed already to have been normalized. A UnitVec3 can be used in any context or operator that would normally take a Vec3 (that is, it has an implicit conversion to Vec3) except where the context would allow the Vec3 to be modified in a way that would change its length. In particular, you can apply a Rotation to a UnitVec3 and get a UnitVec3 back because that operation is known to preserve length.
Note that when you assign a Vec3 to a UnitVec3, normalization will be performed automatically, whereas assigning a UnitVec3 to a Vec3 or to another UnitVec3 requires no computation. If you attempt to set a UnitVec3 to zero, you will get NaNs instead.
There are many ways to express 3D rotations. Examples are: pitch-roll-yaw, azimuth-elevation-twist, axis-angle, and quaternions. Many others are in common use, and SimTK provides extensive support for most of them. However, each way of writing orientation has its own quirks and complexities, and all of them are equivalent to a 3x3 matrix, called a rotation matrix (synonyms: orientation matrix, direction cosine matrix). Rotation matrices have a particularly simple definition and straightforward physical interpretation, and are very easy to work with. OpenSim uses the SimTK rotation matrix as a least common denominator, embodied in a class Rotation. Rotation provides a set of methods which can be used to construct a rotation matrix from a wide variety of commonly-used rotation schemes. These are represented internally in objects of type Rotation as an ordinary SimTK Mat33, and can be used wherever a Mat33 is expected, except that construction, assignment, and writable element access are restricted to ensure that certain properties are maintained. Columns of a SimTK Rotation have type UnitVec3 rather than Vec3.
There can be some confusion as to whether to use a rotation matrix or its inverse in a given context. We use a consistent notation to avoid that confusion, and show here how the notation corresponds to the physical layout of the SimTK::Rotation object. The symbol R with left and right superscripts
(The notation vB indicates a (column) vector quantity v fixed to reference frame B, with measure numbers expressed in B's frame, while the operator indicates that the measure numbers of some physical quantity are re-expressed in coordinate frame F.) So the symbol should be read "the axes of frame B expressed in frame G," or "the orientation of frame B in G," or just "B in G." We never use "R" alone for a rotation matrix and neither should you; that is a recipe for certain disaster. Instead, always provide the two frames. Using this notation, you can simply match up superscripts to rotate vectors or compose rotations. When under tight typographical restrictions, as in source code, we write in "monogram" notation as R_GB. Also, since these are orthogonal, the inverse of a rotation matrix is just its transpose, which serves simply to swap the superscripts. Use the SimTK "~" operator to indicate rotation inversion: . As an example, if you have a rotation and a vector [v]B expressed in B, you can re-express that same vector in G like this: . To go the other direction, we can write . As a C++ code fragment, this can be written:
Composition of rotations is similarly accomplished by lining up superscripts (subject to order reversal with the "~" operator). So given
As is typical for SimTK operations on small quantities, the transpose operator is actually just a change in point of view and involves no computation or copying of data. That is, the operations and are exactly equivalent in both meaning and performance: the cost is just three inline dot products, with no wasted data copying or subroutine calls.
Transforms combine a rotation and a position (translation) and are used to define the configuration of one frame with respect to another. We represent a frame B's configuration with respect to another frame G by giving the measure numbers in G of each of B's axes, and the measure numbers in G of the vector from G's origin point to B's origin point, for a total of 4 vectors, which can be interpreted as a 3x3 Rotation (see above) followed by the origin point location (a Vec3). We call this object a transform (abbreviated xform) and conceptually augment the axes and origin point to create a 4x4 linear operator which can be applied to augmented vectors (4th element is 0) or points (4th element is 1), or composed using matrix multiplication. We define a type Transform which conceptually represents transforms as follows:
(The notation , that is, the vector from the origin of the G frame to the origin of the B frame.) We use the symbol X for transforms, with superscripts so means "the transform from frame G to frame B," or "frame B measured from and expressed in frame G." As for rotations, never write a transform as just X without indicating frames. When under tight typographical restrictions, as in source code, we write in "monogram" notation as X_GB.
Another way to interpret is that it represents the operations that must be performed on G to bring it into alignment with B (a rotation and a translation). Then, as for rotation matrices described above, we can interpret as a composition of operators yielding , and is defined to yield the inverse transform .
The above transform matrix can be considered a matrix of four columns as shown: three augmented vectors and an augmented point. An alternate, and entirely equivalent, way to view this is as a rotation matrix, translation vector, and an extra row:
In our implementation, the physical layout of a Simbody Transform is just the three columns of the rotation matrix followed immediately in memory by the translation vector, that is, . There is no need for the fourth row to be stored in memory since it is always the same.
The multiplication operator * is overloaded to work with transforms on Vec3 objects with the assumption that these are points (stations) to be shifted as well as rotated. That is, they are treated as though there were a fourth element set to 1. If you only want to apply a rotation, you can extract the Rotation matrix from the Transform and then apply that. As an example:
Given a Transform, you can work with it as though it were a 4x4 matrix, or work directly with the rotation matrix R and translation vector p individually, without having to make copies (methods x.R() and x.p() are available to provide references to the contained objects of a transform x). Although a transform defined this way is not orthogonal, its inverse is easy to apply with no additional calculation. SimTK overloads the normal matrix transpose operator "~" to recast a Transform to its inverse so that either the transform or its inverse can be used conveniently in an expression, for example,.
The SimTK Inertia class is a 3x3 symmetric matrix. The class provides some convenient constructors and methods for shifting to and from a body's center of mass. The OpenSim API uses this class for specifying body inertia properties. If you want to see what else you can do with this class, look it up in the Doxygen documents available at https://simtk.org/home/simbody, Documents tab.