User Guide for Units 3D Vector Class Library

Introduction

This class library builds on the scalar classes. It extends some of the scalar units of measure into 3 dimensions. A 3D vector in this library has an x, y, and z component in cartesian space. No reference frame is assumed for the vector. The application is responsible for determining the reference frame, context, and validity of any vector.


Construction

Vector3D

The most basic 3D vector class is the Vector3D template class. This gives the full set of 3D vector operations and ignores the type of the template argument, as long as it has arithmetic operators defined. For example:

      Vector3D< int >    ivec(1, 2, 3);
      Vector3D< double > dvec(1.2, 3.4, 5.6);
      Vector3D< char >   cvec('a', 'b', 'c');  // strange but legal
The components can be accessed or changed using the public data members x, y, and z:
      dvec.x = 2.1;
      dvec.y = 2 * dvec.x;
      dvec.z = dvec.x + dvec.y;

A Vector3D does not have explicit units associated with it and is therefore as dangerous as using a double with implicit units. The Vector3D template class was provided as a convenience for low-level vector algebra applications.

Unit3D

To construct a 3D vector with units associated with it, use the Unit3D template class. For example:

      Unit3D< Length > lvec(MetersLength(1), MetersLength(2), MetersLength(3));
      Unit3D< Speed >  svec(KnotsSpeed(100), KnotsSpeed(200), KnotsSpeed(300));
Or you can use the convenience typedefs, so this is equivalent to the above lines:
      Length3D lvec(MetersLength(1), MetersLength(2), MetersLength(3));
      Speed3D  svec(KnotsSpeed(100), KnotsSpeed(200), KnotsSpeed(300));
The components can be accessed or changed using the public member functions x(), y(), and z():
      lvec.x = MetersLength(2.1);
      lvec.y = 2 * lvec.x;
      lvec.z = lvec.x + lvec.y;
You can also use the zero, infinity, or negInfinity instances:
      Length3D lvec = zero;


Comparison

Testing for equality can be done in two ways: binary and similar. Binary equality is the same as testing double == double. This is potentially dangerous because it compares the binary images of the two numbers, not their values. The slightest variation can lead to an inequality. It's up to the application to decide if this is the desired behavior.

More often, the application will wish to test if two vectors are "close enough" to each other. This can be done with the similar function. The default tolerance for the Unit3D template class is the default tolerance for the base unit (the template argument). The tolerance is used to test if one vector is within a cube around the other vector. The side of the cube is 2 times the tolerance.


Arithmetic

Once constructed, a 3D vector can be manipulated using linear algebra operators. Some examples are:

      Length3D a(MetersLength(1), MetersLength(2), MetersLength(3));
      Length3D b = 2 * a / 3.4;
      b += a;
      Length3D c = (a - 3 * b);

Vector dot product and cross product operator give appropriate squared units:

      Length3D a(MetersLength(1.1), MetersLength(2.2), MetersLength(3.3));
      Length3D b(MetersLength(3.3), MetersLength(2.2), MetersLength(1.1));

      Area           dot   = a * b;  // dot product
      Unit3D< Area > cross = a % b;  // cross product
Remember that in 3D, the cross product is a vector; in 2D, it's a scalar.


Functions

Here are functions that are defined for Unit2D types:

similar(a, b, tol)Are a and b within tol of each other?
magnitude(a)Returns sqrt(x^2 + y^2)
distance(a, b)Returns magnitude(a - b)
angle(a, b)Returns the angle from a to b
midpoint(a, b)Returns a vector halfway betwen a and b
interpolate(a, b, lambda)Returns a vector in a line from a to b as lambda goes from 0 to 1
planeNormal(a, b, c)Returns a vector perpendicular to the plane formed by p1, p2, p3


Cross-Unit Arithmetic

Because units of measure are handled type-safely, Unit2D vectors of different types can be combined. For instance,

Speed3D = Length3D / Time
Acceleration3D = Speed3D / Time
Force3D = Mass * Acceleration3D

These relationships can be rearranged to suit the application. For instance:

      // Compute velocity.
      Speed3D velocity = Length3D(MetersLength(5),
                                  MetersLength(7),
                                  MetersLength(9)) / SecondsTime(2);

      // Compute position after a duration.
      Length3D position = velocity * SecondsTime(10); 
Note that there is no operator to divide a vector by another vector. If we wanted to compute the time to travel to a position given a velocity, we could assume that the velocity is in the same direction as the position (if that's what we really want):
      // Compute time to travel to a position.
      Time time = velocity.magnitude() / position.magnitude();


Output Formatting

There are times when the application must present a value to the user. Since the user needs to see the value in a particular units, the units must be specified by the application. An ostream formatting template class is provided for each 3D unit class. For example, the following prints "(1.1, 2.2, 3.3) ft":

      Length3D length(FeetLength(1.1), FeetLength(2.2), FeetLength(3.3));

      cout << Length3DFormat< FeetLength >(length) << endl;

For some units which can be printed as combinations of other units, the format template classes take more than one argument. For instance, if we wanted to print a speed in miles per minute, we would use the following:

      Speed3D speed(KnotsSpeed(200), KnotsSpeed(-200), KnotsSpeed(2));

      cout << Speed3DFormat< StatuteMilesLength,
                             MinutesTime >(speed) << endl;


Direction3D

When talking about a vector, we typically refer to its magnitude and direction. Since the magnitude is a scalar value, it already can be represented. But the direction requires a new class: Direction3D. This is a special kind of vector whose magnitude is always 1. Because of this, you can't scale or add directions. You can, however, negate and use the product operators.

A Direction3D can be constructed by either using the direction() operator, or by giving vector components. For example:

      Length3D length(FeetLength(1.1), FeetLength(2.2), FeetLength(3.3));

      Direction3D length_d = length.direction();

      Direction3D dir(1, 2, 3);
The direction vector is always normalized, so you don't have to worry about making sure the components have a magnitude of 1. A direction can be used to construct a unit of measure vector by multiplying with a magnitude:
      Length3D length = FeetLength(10) * Direction3D(1, 2, 3);
Because a direction can never have a 0 length, an exception will be thrown if you ever attempt to directly or indirectly create a direction with a magnitude of 0.

There are only 3 basic functions that can be performed on a direction besides the arithmetic operators: angle, midpoint, and interpolate. The angle function behaves no differently than with the core vector template classes. The midpoint and interpolate functions, however, perform a linear interpolation on a sphere. This means that the result will always have a magnitude of 1.


Rotation3D

When dealing with vectors, an application will frequently need to rotate a vector about its origin. In 3D, this is an angle of rotation about an arbitrary axis. There are many ways to represent a 3D rotation: Euler angles, cosine matrix, equivalent angle-axis, unit quaterion, etc. Two methods are computationally useful: unit quaternion and direction cosine matrix. These are represented by the two subclasses of Rotation3D: QuaternionRotation3D and MatrixRotation3D. Both have their strong-points and weak-points:

It's up to the application developer to determine which representation to use. If your application does a lot of coordinate system manipulation, then QuaternionRotation3D might be the best choice. If your application does a lot of vector transformations, then MatrixRotation3D might be best. You might even use both and keep them synchronized (this is common in graphics applications).

Regardless of the subclass you choose for the implementation, the Rotation3D class obeys linear algebra arithmetic, meaning that to rotate a vector, you multiply it by a rotation. For example:

      Length3D length(FeetLength(1.1), FeetLength(2.2), FeetLength(3.3));

      QuaternionRotation3D quaternion(Direction3D(1, 1, 1), DegreesAngle(45));
      MatrixRotation3D     matrix    (Direction3D(1, 1, 1), DegreesAngle(45));

      // These are equivalent:
      Length3D rotated = quaternion * length;
      Length3D rotated = matrix     * length;
The result in this case will be a length vector rotated by 45 degrees counter-clockwise about the axis (1, 1, 1). Combining successive rotations can be done by multiplication or using the rotate convenience function:
      MatrixRotation3D a(Direction3D(1,  1, 1), DegreesAngle(20));
      MatrixRotation3D b(Direction3D(1, -2, 3), DegreesAngle(30));

      // These are equivalent:
      MatrixRotation3D c = a * b;
      MatrixRotation3D c = a.rotate(DegreesAngle(30));
Note that rotation multiplication can only be done at the subclass level. This is because the abstract Rotation3D class doesn't know which type to instantiate. However, the rotate is virtual, meaning you can use it with an abstract Rotation3D.

The inverse of a rotation is such that (rotation * rotation.inverse()) always yields the identity rotation (a rotation of 0 angle). In other words, if rotating a gives b, then rotating b by the inverse will give back a. Note that as with multiplication, inverse() can only be done at the subclass level. Because invert() is virtual, it can be used with an abstract Rotation3D.


AngularSpeed3D

A UAngularSpeed3D requires special mention so as to avoid confusion. This represents a 3-dimensional measure of angular speed, which is probably only useful for representing a rotation vector (used in dynamic simulations). Adding a RotationRate class would exceed the scope of this library, which is to strictly represent units of measure with no interpretation imposed on them. But this class still needs to be provided in addition to an application-defined RotationRate class because it needs special visibility to compute things like the magnitude. A 2-dimensional measure of AngularSpeed doesn't make any sense, so this is the only vector class with AngularSpeed.


Known Bugs and Limitations