User Guide for units::vector2D Class Library

Introduction

This class library builds on the scalar classes. It extends some of the scalar units of measure into 2 dimensions. A 2D vector in this library has an x and y 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

Vector2D

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

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

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

Unit2D

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

      Unit2D< Length > lvec(MetersLength(1), MetersLength(2));
      Unit2D< Speed >  svec(KnotsSpeed(100), KnotsSpeed(200));
The components can be accessed or changed using the public member functions x() and y():
      lvec.x() = MetersLength(2.1);
      lvec.y() = 2 * lvec.x();


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 provided with all vector template classes. The default tolerance for the Unit2D template class is the default tolerance for the base unit (the template argument). There is no default tolerance for the Vector2D template class; the application must always provide a tolerance. The tolerance is used to test if one vector is within a square around the other vector. The side of the square is 2 times the tolerance.


Arithmetic

Once constructed, a 2D vector can be manipulated using linear algebra operators. The application developer should look at Vector2D.h or Unit2D.h to see the full list of valid operators. Some examples are:

      Unit2D< Length > a(MetersLength(1), MetersLength(2));

      Unit2D< Length > b = 2 * a / 3.4;

      b += a;

      Unit2D< Length > c = (a - 3 * b);

The Vector2D and Unit2D template classes share all arithmetic operators, with the exception that Unit2D does not have a public dot product or cross product operator because the units for these products are squared. Since Vector2D has no units, the products are available:

      Vector2D< double > a(1.1, 2.2);
      Vector2D< double > b(2.2, 1.1);

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


Functions

The application developer should look at Vector2D.h or Unit2D.h to see the full list of valid functions. The functions are shared by both Vector2D and Unit2D and are fairly straight forward. The distance functions may be a little counter-intuitive for unit vectors other than Unit2D< Length >, but they represent a mathematically useful concept. For instance:

      Unit2D< Speed > a(KnotsSpeed(20), KnotsSpeed(-20));
      Unit2D< Speed > b(KnotsSpeed(22), KnotsSpeed(-15));

      Speed c = distance(a, b);
In this case, c can be interpreted as the magnitude of the "error" between a and b, with the appropriate units.


Cross-Unit Arithmetic

Some Unit2D classes have arithmetic operators defined so that they can interact with other units. These interactions are the common relationships between units:

Unit2D< Speed > = Unit2D< Length > / Time
Unit2D< Acceleration > = Unit2D< Speed > / Time
Unit2D< Force > = Mass * Unit2D< Acceleration >

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

      // Compute velocity.
      Unit2D< Speed > velocity = Unit2D< Length >(MetersLength(5),
                                                  MetersLength(7)) / SecondsTime(2);

      // Compute position after a duration.
      Unit2D< Length > 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'd have to assume that the velocity is in the same direction as the position (unless the application wants to do a dynamic simulation):
      // 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 2D unit class. For example, the following prints "(1.1, 2.2) ft":

      Unit2D< Length > length(FeetLength(1.1), FeetLength(2.2));

      cout << Length2DFormat< 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:

      Unit2D< Speed > speed(KnotsSpeed(200), KnotsSpeed(-200));

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


Unit Conversion

Hopefully, applications won't need to explicitly convert Unit2D vectors between units, but as we saw in the scalar library, it can sometimes become necessary. In these circumstances, you can use the format template classes, which provide a vector() operator:

      const double factor = 1.234;  // N / m

      Unit2D< Length > length(FeetLength(1.1), FeetLength(2.2));

      Vector2D< double > newtons = Length2DFormat< MetersLength >(length).vector();

      Unit2D< Force > force(NewtonsForce(newtons.x),
                            NewtonsForce(newtons.y));
Note that we could have used
      Unit2D< NewtonsForce > force(newtons.x,
                                   newtons.y);
But this is dangerous because the compiler will not know how to promote a Unit2D< NewtonsForce > to a Unit2D< Force >, which may cause problems. Always use the base unit class for Unit2D instantiations.


Direction2D

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: Direction2D. 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 Direction2D can be constructed by either using the direction() operator, or by giving vector components. For example:

      Unit2D< Length > length(FeetLength(1.1), FeetLength(2.2));

      Direction2D length_d = length.direction();

      Direction2D dir(1, 2);
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:
      Unit2D< Length > length = FeetLength(10) * Direction2D(1, 2);
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 circle. This means that the result will always have a magnitude of 1.


Rotation2D

When dealing with vectors, an application will frequently need to rotate a vector about its origin. In 2D, this is simply an angle of rotation in the plane. The Rotation2D class obeys linear algebra arithmetic, meaning that to rotate a vector, you multiply it by a rotation. For example:

      Unit2D< Length > length(FeetLength(1.1), FeetLength(2.2));

      Rotation2D rotation(DegreesAngle(45));

      Unit2D< Length > rotated = rotation * length;
The result in this case will be a length vector rotated by 45 degrees counter-clockwise. Combining successive rotations can be done by multiplication or using the rotate convenience function:
      Rotation2D a(DegreesAngle(20));
      Rotation2D b(DegreesAngle(30));

      // These are equivalent:
      Rotation2D c = a * b;
      Rotation2D c = a.rotate(DegreesAngle(30));
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.


Unit2D< Angle >

A Unit2D< Angle > requires special mention so as to avoid confusion. This represents a 2-dimensional measure of angle in cartesian space. This is a cartesian "flat" angle vector, not one on a sphere! There will be no wrapping around poles. It is up to the application to interpret a 2D angle vector as coordinates on a sphere.


Known Bugs and Limitations