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.
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:
The components can be accessed or changed using the public data
members
Vector3D< int > ivec(1, 2, 3);
Vector3D< double > dvec(1.2, 3.4, 5.6);
Vector3D< char > cvec('a', 'b', 'c'); // strange but legal
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.
To construct a 3D vector with units associated with it, use the Unit3D
template class. For example:
The components can be accessed or changed using the public member
functions
Unit3D< Length > lvec(MetersLength(1), MetersLength(2), MetersLength(3));
Unit3D< Speed > svec(KnotsSpeed(100), KnotsSpeed(200), KnotsSpeed(300));
x(), y(), and z():
lvec.x() = MetersLength(2.1);
lvec.y() = 2 * lvec.x();
lvec.z() = lvec.x() + lvec.y();
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 Unit3D template class is the
default tolerance for the base unit (the template argument). There is no
default tolerance for the Vector3D template class; the
application must always provide a tolerance. 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.
Once constructed, a 3D vector can be manipulated using linear algebra
operators. The application developer should look at
Vector3D.h or
Unit3D.h
to see the full list of valid operators. Some examples are:
Unit3D< Length > a(MetersLength(1), MetersLength(2), MetersLength(3));
Unit3D< Length > b = 2 * a / 3.4;
b += a;
Unit3D< Length > c = (a - 3 * b);
The Vector3D and Unit3D template classes
share all arithmetic operators, with the exception that Unit3D
does not have a public dot product or cross product operator because the units
for these products are squared. Since Vector3D has no units,
the products are available:
Remember that in 3D, the cross product is a vector; in 2D, it's
a scalar.
Vector3D< double > a(1.1, 2.2, 3.3);
Vector3D< double > b(3.3, 2.2, 1.1);
double dot = a * b; // dot product
Vector3D< double > cross = a % b; // cross product
The application developer should look at
Vector3D.h or
Unit3D.h
to see the full list of valid functions. The functions
are shared by both Vector3D and Unit3D
and are fairly straight forward. The distance functions may be
a little counter-intuitive for unit vectors other than
Unit3D< Length >, but they represent a mathematically
useful concept. For instance:
In this case,
Unit3D< Speed > a(KnotsSpeed(20), KnotsSpeed(-20), KnotsSpeed(2));
Unit3D< Speed > b(KnotsSpeed(22), KnotsSpeed(-15), KnotsSpeed(1));
Speed c = distance(a, b);
c can be interpreted as the magnitude of
the "error" between a and b, with the
appropriate units.
Some Unit3D classes have arithmetic operators defined so
that they can interact with other units. These interactions are the common
relationships between units:
| Unit3D< Speed > | = Unit3D< Length > | / Time |
| Unit3D< Acceleration > | = Unit3D< Speed > | / Time |
| Unit3D< Force > | = Mass | * Unit3D< Acceleration > |
These relationships can be rearranged to suit the application. For
instance:
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 velocity.
Unit3D< Speed > velocity = Unit3D< Length >(MetersLength(5),
MetersLength(7),
MetersLength(9)) / SecondsTime(2);
// Compute position after a duration.
Unit3D< Length > position = velocity * SecondsTime(10);
// Compute time to travel to a position.
Time time = velocity.magnitude() / position.magnitude();
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":
Unit3D< Length > 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:
Unit3D< Speed > speed(KnotsSpeed(200), KnotsSpeed(-200), KnotsSpeed(2));
cout << Speed3DFormat< StatuteMilesLength,
MinutesTime >(speed) << endl;
Hopefully, applications won't need to explicitly convert
Unit3D 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:
Note that we could have used
const double factor = 1.234; // N / m
Unit3D< Length > length(FeetLength(1.1), FeetLength(2.2), FeetLength(3.3));
Vector3D< double > newtons = Length3DFormat< MetersLength >(length).vector();
Unit3D< Force > force(NewtonsForce(newtons.x),
NewtonsForce(newtons.y),
NewtonsForce(newtons.z));
But this is dangerous because the compiler will not know how to promote
a
Unit3D< NewtonsForce > force(newtons.x,
newtons.y,
newtons.z);
Unit3D< NewtonsForce > to a Unit3D< Force >,
which may cause problems. Always use the base unit class for
Unit3D instantiations.
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:
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:
Unit3D< Length > length(FeetLength(1.1), FeetLength(2.2), FeetLength(3.3));
Direction3D length_d = length.direction();
Direction3D dir(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.
Unit3D< Length > length = FeetLength(10) * Direction3D(1, 2, 3);
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.
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 cosine matrix.
These are represented by the two subclasses of Rotation3D:
QuaternionRotation3D and MatrixRotation3D.
Both have their strong-points and weak-points:
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:
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
Unit3D< Length > 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:
Unit3D< Length > rotated = quaternion * length;
Unit3D< Length > rotated = matrix * length;
rotate
convenience function:
Note that rotation multiplication can only be done at the subclass level.
This is because the abstract
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));
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.
A Unit3D< AngularSpeed > 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 is 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.