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.
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:
The components can be accessed or changed using the public data
members
Vector2D< int > ivec(1, 2);
Vector2D< double > dvec(1.2, 3.4);
Vector2D< char > cvec('a', 'b'); // strange but legal
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.
To construct a 2D vector with units associated with it, use the Unit2D
template class. For example:
Or you can use the convenience typedefs, so this is equivalent to the
above lines:
Unit2D< Length > lvec(MetersLength(1), MetersLength(2));
Unit2D< Speed > svec(KnotsSpeed(100), KnotsSpeed(200));
The components can be accessed or changed using the public data
members
Length2D lvec(MetersLength(1), MetersLength(2));
Speed2D svec(KnotsSpeed(100), KnotsSpeed(200));
x
and y
:
You can also use the zero, infinity, or negInfinity instances:
lvec.x = MetersLength(2.1);
lvec.y = 2 * lvec.x;
Length2D lvec = zero;
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 Unit2D
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 square around the other vector. The side of
the square is 2 times the tolerance.
Once constructed, a 2D vector can be manipulated using linear algebra
operators. Some examples are:
Length2D a(MetersLength(1), MetersLength(2));
Length2D b = 2 * a / 3.4;
b += a;
Length2D c = (a - 3 * b);
Vector dot product and cross product operator give appropriate squared
units:
Remember that in 2D, the cross product is a scalar; in 3D, it's
a vector.
Length2D a(MetersLength(1.1), MetersLength(2.2));
Length2D b(MetersLength(2.2), MetersLength(1.1));
Area dot = a * b; // dot product
Area cross = a % b; // cross product
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 |
Because units of measure are handled type-safely, Unit2D vectors of different types can be combined. For instance,
Speed2D | = Length2D | / Time |
Acceleration2D | = Speed2D | / Time |
Force2D | = Mass | * Acceleration2D > |
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 could assume that the velocity is in the same
direction as the position (if that's what we really want):
// Compute velocity.
Speed2D velocity = Length2D(MetersLength(5),
MetersLength(7)) / SecondsTime(2);
// Compute position after a duration.
Length2D 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 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:
Speed2D speed(KnotsSpeed(200), KnotsSpeed(-200));
cout << Speed2DFormat< StatuteMilesLength,
MinutesTime >(speed) << endl;
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:
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:
Length2D vec(FeetLength(1.1), FeetLength(2.2));
Direction2D vec_d = vec.direction();
Direction2D dir(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.
Length2D length = FeetLength(10) * Direction2D(1, 2);
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.
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:
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
Length2D vec(FeetLength(1.1), FeetLength(2.2));
Rotation2D rotation(DegreesAngle(45));
Length2D rotated = rotation * vec;
rotate
convenience function:
The inverse of a rotation is such that
Rotation2D a(DegreesAngle(20));
Rotation2D b(DegreesAngle(30));
// These are equivalent:
Rotation2D c = a * b;
Rotation2D c = a.rotate(DegreesAngle(30));
(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
.
A Angle2D
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.