A
vector is a mathematical structure that is used to represent a
direction. There are different types of vectors, and the most common
kind you will see in computer graphics are 2D, 3D, and 4D vectors. The
type of vector determines the number of axes or dimensions it
represents. Therefore, a 3D vector is a vector that exists in 3D space
and has X, Y, and Z axes.
At its heart a vector is a
direction, and in computer graphics the structures used to define
vectors are often used to represent positions as well. Taking a 3D
vector as an example, the X, Y, and Z axes can be used to mark a
position just as they can mark a direction. The difference lies in what
the programmer intends to use the data for. In math and physics a vector
is an object with a direction and a length. A simple visual of this is
shown in Figure 1,
where the vector moves from the origin (the origin in 3D space has an X
axis of 0, Y of 0, and Z of 0) along the positive Y axis 10 units,
making its final position X:0, Y:10, Z:0. The direction of the vector is
X:0, Y:1, and Z:0. Since the vector only moves long the Y axis (Y:1), only the Y has a value.
In this article we’ll look
at 3D vectors, but keep in mind that everything discussed here also
applies to 2D and 4D vectors. A 2D vector is made up of X and Y axes,
and a 4D vector is made up of X, Y, Z, and W axes. The Direct3D 3D
vector is called D3DXVECTOR3, and it has the following structure
according to the DirectX 10 documentation.
typedef struct D3DXVECTOR3 {
FLOAT x;
FLOAT y;
FLOAT z;
} D3DXVECTOR3, *LPD3DXVECTOR3;
This
structure can be used to represent both positions and directions. In
the case of a vertex, this structure is often used to represent the
position attribute of each vertex of a primitive. In the upcoming
subsections of this discussion on vectors, we will briefly discuss a few
of the most common mathematical operations performed on vector objects.
To view a complete list of the mathematical functions offered by the
DirectX SDK vector objects, refer to the DirectX documentation.
Vectors
are used for all types of mathematical objects, especially in game
physics. This includes directions, positions, tensors, pseudovectors,
and other vector-like objects. In other words, if it has an X, Y, and Z
property (using 3D vectors as an example), then most likely programmers
will use their vector code to represent it rather than creating another
structure with the same properties (member variables). This makes it
easier to get started if you do not have strong math skills. |
Vector Addition and Subtraction
The first operations we’ll
look at in vector mathematics are adding and subtracting.
Mathematically, adding and subtracting are very elementary. To add two
vectors together, again using 3D vectors as an example, you add each of
the matching axes together, and the result is stored in a new vector
holding the solution. In other words, you take the X axis from vectors A
and B and add them together and store answer in a result vector’s X
axis. You do the same with the Y and Z axes. This is shown in the
following example.
Vector3D A = (10, 5, 8)
Vector3D B = (3, 1, 11)
Vector3D Result = A + B
or
or
Result.x = A.x + B.x
Result.y = A.y + B.y
Result.z = A.z + B.z
Using
the vectors from the addition example, subtraction is the same, but
instead of adding you are literally subtracting each axis from its
matching counterpart in the other vector. If vector A is (10, 5, 8) and
vector B is (3, 1, 11), the resulting vector when subtracting is (7, 4,
–3).
Vectors in DirectX are added using the function D3DXVec3Add() and subtracted using the D3DXVec3Subtract
function. These functions have the following function prototypes where
the function returns the result as a vector. The first parameter is the
address for the vector that will store the result of the operation, the
second parameter is the first vector in the operation (vector A), and
the last parameter is the second vector in the operation (vector B).
D3DXVECTOR3 * D3DXVec3Add(
D3DXVECTOR3 * pOut,
CONST D3DXVECTOR3 * pV1,
CONST D3DXVECTOR3 * pV2
);
D3DXVECTOR3 * D3DXVec3Subtract(
D3DXVECTOR3 * pOut,
CONST D3DXVECTOR3 * pV1,
CONST D3DXVECTOR3 * pV2
);
The result from these
functions can be obtained by their return value or by passing the
address of the object to hold the result in the first parameter.
Alternatively, you can use the structure’s overloaded operators to
perform addition and subtraction instead of calling these functions.
This would result in the following in code.
D3DXVECTOR3 vectorA, vectorB, result;
result = vectorA + vectorB;
result = vectorA - vectorB;
Vector Normalization
The length of a vector is
called its magnitude. To find the length of a vector you multiply each
component of a vector with itself and add all of the axes. The square
root of this result is the magnitude of the vector. This is shown in the
following pseudo-code example.
length = square_root(vector.x * vector.x + vector.y * vector.y +
vector.z * vector.z)
This
equation gives you the inner product. The square root of this is the
length of a vector. When a vector has a length that equals 1, it is said
that the vector is unit-length. Another term for this is normalized vector (normal for short). The length itself is a floating-point value.
A normal has many uses
in video game development. Later in this book you’ll see how normal
vectors contribute to the lighting equation. In this article we will
briefly discuss how to convert a vector to a normal.
To convert a vector to a
normal, the first step is to find the vector’s length. If the length
equals 1, the vector is already unit-length, and nothing else needs to
be calculated. If the length is not 1, you can divide each axis of the
vector by the length, which will result in scaling the vector to
unit-length. This is done as follows.
D3DXVECTOR3 vectorA, normal;
float length = D3DXVec3Length(&vectorA);
normal = vectorA / length;
The D3DXVec3Length() function can be used to find the length of a D3DXVECTOR3 object. Alternatively, you can normalize a vector by calling the DirectX function D3DXVec3Normalize(),
which takes as parameters the address of the vector that will store the
result of the operation and the vector to normalize. Normalized vectors
are used for many mathematical equations, such as lighting for example.
The function prototype for the D3DXVec3Normalize() function is shown as follows.
D3DXVECTOR3 * D3DXVec3Normalize(
D3DXVECTOR3 *pOut,
CONST D3DXVECTOR3 *pV
);
Common Additional Vector Operations
We’ll look at a few other
vector operations in this article that will come up later in this book
in discussions of various topics. These operations include the
following.
Dot product
Cross product
Lerp
The
dot product is result of multiplying two vectors and adding the
resulting axes. The dot product of two vectors can be found as follows
using 3D vectors as an example.
float dot = vectorA.x * vectorB.x + vectorA.y * vectorB.y + vectorA.z *
vectorB.z;
The cross product of two
vectors, put simply, is obtained by cross multiplying the axes of one
vector with the axes of another. The cross product is used to find a
vector that is perpendicular to two source vectors, which can be useful
when you need such a vector in relation to two other vectors. The cross
product, also known as the vector product, is shown below, where the
axes that are multiplied are cross multiplied with one vector to
another.
cross.x = vectorA.y * vectorB.z - vectorA.z * vectorB.y
cross.y = vectorA.z * vectorB.x - vectorA.x * vectorB.z
cross.z = vectorA.x * vectorB.y - vectorA.y * vectorB.x
Lerp is short for linear
interpolation. It is an operation that is used to find a value that lies
somewhere between two source values. The idea is to take a start value,
an end value, and a percentage from 0.0 to 1.0 (i.e., 0 to 100%). If
the percentage supplied is 0.0, the start vector is returned. If the
percentage is 1.0, the ending value is returned, but if the percentage
is a value between the two, a vector that lies in the percentage between
the two will be returned.
For example, using single
values, let’s say we have a start value of 6 and an end value of 18. If
we supply a percentage of 50%, that is like saying what is 50% into the
range of 6 and 18. The answer is 12, since 12 lies halfway between 6 and
18. The same concept is used for linear interpolation with vectors, but
this concept is applied to each axis of the vector. When using linear
interpolation, you are linearly finding a value between two vectors
based on the percentage, which often has the notation t
in game development books. The equation for finding the linear
interpolated vector between vector A and B is shown in the following
example.
result = (vectorB - vectorA) * percentage + vectorA
The equation for the
linear interpolation is quite simple. It works by finding the range
total between values (vectors) A and B, multiplying that by the
percentage (t), and adding that to the starting vector. So if we wanted
to lerp between the values 13 and
57 by 0.4 (40%), we would first find the range (57 – 13, which equals
44) and then multiply 44 by 0.4, which is 17.6. We would then add 17.6
to the starting value to get the value that lies 40% into the range,
which would result in 30.6.
Linear interpolation is sometimes used for animations, where time is used as t.
So if an animation had to occur within a certain time frame, for
example, you could interpolate between two vertex positions to find
where the vertex would be at a specific time. If you do this for all
vertices in a model, you get the type of animation used in many early 3D
video games before bone animation (discussed later in the book) became
the standard.
DirectX 3D Vector Functions
The DirectX SDK offers a
number of functions for vector objects. In this section we will look
briefly at the 3D vector functions. The 2D and 4D vectors have
equivalent functions, although a few 3D functions do not have a 2D or 4D
counterpart. For example, there is no D3DXVec2Cross(). Table 1 lists the 3D vector functions in the DirectX SDK.
Table 1. The 3D Vector Functions from the DirectX SDK
Function | Definition |
---|
D3DXVec3Add() | Vector addition |
D3DXVec3BaryCentric() | Returns a point in Barycentric coordinates |
D3DXVec3CatmullRom() | Performs CatmullRom interpolation |
D3DXVec3Cross() | Performs the cross product of two vectors |
D3DXVec3Dot() | Performs the dot product between two vectors |
D3DXVec3Hermite() | Performs Hermite spline interpolation |
D3DXVec3Length() | Calculates the length (magnitude) of a vector |
D3DXVec3LengthSq() | Calculates the square of the vector’s length |
D3DXVec3Lerp() | Performs linear interpolation |
D3DXVec3Maximize() | Finds the maximum vector of two source vectors |
D3DXVec3Minimize() | Finds the minimum vector of two source vectors |
D3DXVec3Normalize() | Normalizes a vector to unit-length |
D3DXVec3Project() | Projects a vector from object space to screen space |
D3DXVec3ProjectArray() | Projects a float array from object space to screen space |
D3DXVec3Scale() | Scales a vector |
D3DXVec3Subract() | Vector subtraction |
D3DXVec3TransformArray() | Transforms a float array by a matrix |
D3DXVec3TransformCoord() | Transforms a vector by a matrix and projects back into the w = 1 |
D3DXVec3TransformCoordArray() | Transforms a float array by a matrix and projects back into the w = 1 |
D3DXVec3TransformNormal() | Performs a 3 × 3 vector/matrix transformation to transform a normal vector by a matrix |
D3DXVec3TransformNormalArray() | Performs a 3 × 3 vector/matrix transformation to transform a normal vector represented as a float array by a matrix |
D3DXVec3UnProject() | Projects a vector from screen space back to object space |
D3DXVec3UnProjectArray() | Projects a vector represented as a float array from screen space back to object space |
D3DXVec3Transform() | Transforms a vector by a matrix |
Planes
A plane can be thought
of as an infinitely thin surface that extends forever alone two axes.
Planes have many uses in video games, many of which fall under the
subject of collision detection, where a plane can be used to test if an
object of some type travels from one side of the plane to the other.
Planes are not rendered;
they are used mathematically for tests. These tests are essentially set
up to test which side of the plane an object is on or if the object
penetrates the plane. Take, for example, a game in which a cut scene is
triggered when a
player walks into a room. This can be done as simply as defining a
plane and testing every frame to see if the player is on a different
side of the plane than before. If so, the cut scene is triggered.
The plane equation is defined
as ax + by + cz + dw = 0. In code a plane can be defined as a structure
with coefficients a, b, c, and d. These coefficients are usually
floating-point values. In DirectX the D3DXPLANE structure is defined as follows:
typedef struct D3DXPLANE {
FLOAT a;
FLOAT b;
FLOAT c;
FLOAT d;
} D3DXPLANE, *LPD3DXPLANE;
You can think of a plane as
a normal that is defined by the first three coefficients, a, b, and c,
and a distance defined by the last coefficient d. You can manually
specify this information or you can create a plane from a primitive or
surface. It is very common to create a plane out of a triangle and then
use that plane for some purpose such as collision detection.
Plane Operations
The DirectX SDK has several functions that can be used with plane objects. The definition of these functions (see Table 2) can give you an idea of what you can do with planes.
Table 2. DirectX SDK Plane Object
Function | Definition |
---|
D3DXPlaneDot(const D3DXPLANE *pP, const D3DXVECTOR4 *pV) | Computes the dot product of a plane and a 4D vector. |
D3DXPlaneDotCoord(const D3DXPLANE *pP, const D3DXVECTOR3 *pV) | Computes the dot product of a plane and a 3D vector. This is the same as the D3DXPlaneDot() function but assumes a w of 1. |
D3DXPlaneDotNormal(const D3DXPLANE *pP, const D3DXVECTOR3 *pV) | The same as D3DXPlaneDotCoord() but assumes a w of 0. |
D3DXPlaneFromPointNormal(D3DXPLANE *pP, const D3DXVECTOR3 *pPoint, const D3DXVECTOR3 *pNormal) | Computes a plane from a point and a normal. |
D3DXPlaneFromPoints(D3DXPLANE *pP, const D3DXVECTOR3 *v1, const D3DXVECTOR3 *v2, const D3DXVECTOR3 *v3) | Creates a plane from three points. This can be used to define a plane from a triangle. |
D3DXPlaneIntersectLine (const D3DXVECTOR3 *pOut, const D3DXPLANE *pP, const D3DXVECTOR3 *v1, const D3DXVECTOR3 *v2) | Tests
to see if a line intersects with the plane. If it does, the point of
intersection in 3D space is returned. The point v1 is the start of the
line, and v2 is the end of the line. The pOut parameter stores the
position of the intersection. |
D3DXPlaneNormalize(D3DXPLANE *pOut, const D3DXPLANE *pP) | Normalizes
a plane so that its coefficients are unit-length. If the a, b, and c of
a plane are its normal, this function essentially normalizes it. |
D3DXPlaneScale(D3DXPLANE *pOut, const D3DXPLANE *pP, FLOAT s) | Scales a plane by a specified amount. |
D3DXPlaneTransform(D3DXPLANE *pOut, const D3DXPLANE *pP, const D3DXMATRIX *pM) | Transforms a plane by a matrix (see the upcoming “Matrices” section). |
D3DXPlaneTransformArray (D3DXPLANE *pOut, UINT OutStride, const D3DXPLANE *pP, UINT PStride, const D3DXMATRIX * pM, UINT n) | Transforms an array of planes by a matrix. |