It’s not really an issue because in fact by working with tracks in this way you are doing what the authoring software (Maya for example) is doing under its hood, meaning you are actually going to stay closer to the real animation.It'll work fine for basic types like vectors and euler angles but not as well for types with less separable components such as quaternions and matrices (and they also have to stay normalised).
In Maya, for example, you have an X, Y, and Z rotation. 3 tracks.
These 3 components can be any values each.
As mentioned near the end of the workflow, if you need a rotation matrix or a quaternion, you generate them from these 3 values (since these are the native 3 values of the authoring software it is guaranteed you have some method to convert these values into matrices or quaternions or whatever else you want to use) as a post-processing step after the animations are finished.
It is more efficient for at least 2 reasons (using a matrix as an example but it applies mostly to quaternions as well):
- Because the rotation is broken into 3 tracks you don’t update components of the rotation that are not actually being animated. A track that works on full matrices has to update rotation, scale, and translation all together if just one component anywhere changes.
- A track working on full-sized matrices must interpolate from matrix to matrix (same for quaternions). In the case of matrices this means decomposing, interpolating scale (vector), rotation (quaternion), and translation (vector) separately, and recomposing the matrix. In the case of quaternions interpolation is a fairly complex function call with a branch for small angles. In both cases it is easier to simply rebuild the primitive (matrix or quaternion) after doing basic linear scalar interpolation.
As I mentioned our joints have XYZ translations, XYZ scales, and XYZ rotations, each stored separately (they could be 3 vectors or 9 floats; if using vectors, a Track will simply attach directly to the .x, .y, or .z of any given vector, modifying only one component at a time) and the rotational part of the matrix is made by rotating around the axis in XYZ order (first the X axis, then the Y, then the Z).
To construct a rotation matrix around the XYZ axes is as follows:
CMatrix4x4Base<_tType, _tVector3Type, _tVector4Type> & LSE_FCALL MatrixRotationXZY( _tType _tX, _tType _tY, _tType _tZ ) {
LSREAL fS1, fC1;
CMathLib::SinCos( static_cast<LSREAL>(_tX), fS1, fC1 );
LSREAL fS2, fC2;
CMathLib::SinCos( static_cast<LSREAL>(_tY), fS2, fC2 );
LSREAL fS3, fC3;
CMathLib::SinCos( static_cast<LSREAL>(_tZ), fS3, fC3 );
_11 = fC1 * fC3;
_12 = fS1 * fS3 + fC1 * fC3 * fS2;
_13 = fC3 * fS1 * fS2 - fC1 * fS3;
_14 = _tType( 0.0 );
_21 = -fS2;
_22 = fC1 * fC2;
_23 = fC2 * fS1;
_24 = _tType( 0.0 );
_31 = fC2 * fS3;
_32 = fC1 * fS2 * fS3 - fC3 * fS1;
_33 = fC1 * fC3 + fS1 * fS2 * fS3;
_34 = _tType( 0.0 );
// Zero the position.
_41 = _tType( 0.0 );
_42 = _tType( 0.0 );
_43 = _tType( 0.0 );
_44 = _tType( 1.0 );
return (*this);
}
In Maya you have animated only the Rotation.X from 0 to 360.So only a single track is updating for the entire joint.
So the entire workload is as follows:
- Update 1 track, interpolating a single float value.
- In post-processing after animation is finished updating, reconstruct matrix as shown below.
float fRotX = DEG_TO_RAD( ROT.x ); // Can be optimized so that the tracks do this conversion only when they update values so that non-modified values are already in radians. float fRotY = DEG_TO_RAD( ROT.y ); float fRotZ = DEG_TO_RAD( ROT.z ); float fS1, fC1; CMathLib::SinCos( fRotX, fS1, fC1 ); // Calculates sin and cos in 1 instruction. float fS2, fC2; CMathLib::SinCos( fRotY, fS2, fC2 ); float fS3, fC3; CMathLib::SinCos( fRotY, fS3, fC3 ); _11 = (fC1 * fC3) * SCALE.x; _12 = (fS1 * fS3 + fC1 * fC3 * fS2) * SCALE.x; _13 = (fC3 * fS1 * fS2 - fC1 * fS3) * SCALE.x; _14 = 0.0f; _21 = -fS2 * SCALE.y; _22 = (fC1 * fC2) * SCALE.y; _23 = (fC2 * fS1) * SCALE.y; _24 = 0.0f; _31 = (fC2 * fS3) * SCALE.z; _32 = (fC1 * fS2 * fS3 - fC3 * fS1) * SCALE.z; _33 = (fC1 * fC3 + fS1 * fS2 * fS3) * SCALE.z; _34 = 0.0f; _41 = TRANS.x; _42 = TRANS.y; _43 = TRANS.z; _44 = 1.0f;
In the end, it’s simply more efficient and it has no drawbacks related to more complex types such as matrices or quaternions. These are rebuilt as the last step of the flow.
L. Spiro