Vector Algebra - MATHEMATICAL PREREQUISITES - Introduction to 3D Game Programming with DirectX 12 (Computer Science) (2016)

Introduction to 3D Game Programming with DirectX 12 (Computer Science) (2016)

Part 1

MATHEMATICAL
P
REREQUISITES

“For the things of this world cannot be made
known without a knowledge of mathematics.”

Roger Bacon, Opus Majus part 4 Distinctia Prima cap 1, 1267.

Video games attempt to simulate a virtual world. However, computers, by their very nature, crunch numbers. Thus the problem of how to convey a world to a computer arises. The answer is to describe our worlds, and the interactions therein, completely mathematically. Consequently, mathematics plays a fundamental role in video game development.

In this prerequisites part, we introduce the mathematical tools that will be used throughout this book. The emphasis is on vectors, coordinate systems, matrices, and transformations, as these tools are used in just about every sample program of this book. In addition to the mathematical explanations, a survey and demonstration of the relevant classes and functions from the DirectX Math library are provided.

Note that the topics covered here are only those essential to understanding the rest of this book; it is by no means a comprehensive treatment of video game mathematics, as entire books are devoted to this topic. For readers desiring a more complete reference to video game mathematics, we recommend [Verth04] and [Lengyel02].


Chapter 1, Vector Algebra: Vectors are, perhaps, the most fundamental mathematical objects used in computer games. We use vectors to represent positions, displacements, directions, velocities, and forces, for example. In this chapter, we study vectors and the operations used to manipulate them.


Chapter 2, Matrix Algebra: Matrices provide an efficient and compact way of representing transformations. In this chapter, we become familiar with matrices and the operations defined on them.


Chapter 3, Transformations: This chapter examines three fundamental geometric transformations: scaling, rotation, and translation. We use these transformations to manipulate 3D objects in space. In addition, we explain change of coordinate transformations, which are used to transform coordinates representing geometry from one coordinate system into another.

Chapter 1

VECTOR
A
LGEBRA

Vectors play a crucial role in computer graphics, collision detection, and physical simulation, all of which are common components in modern video games. Our approach here is informal and practical; for a book dedicated to 3D game/graphics math, we recommend [Verth04]. We emphasize the importance of vectors by noting that they are used in just about every demo program in this book.

Objectives:

1. To learn how vectors are represented geometrically and numerically.

2. To discover the operations defined on vectors and their geometric applications.

3. To become familiar with the vector functions and classes of the DirectXMath library.

1.1 VECTORS

A vector refers to a quantity that possesses both magnitude and direction. Quantities that possess both magnitude and direction are called vector-valued quantities. Examples of vector-valued quantities are forces (a force is applied in a particular direction with a certain strength—magnitude), displacements (the net direction and distance a particle moved), and velocities (speed and direction). Thus, vectors are used to represent forces, displacements, and velocities. In addition, we also use vectors to specify pure directions, such as the direction the player is looking in a 3D game, the direction a polygon is facing, the direction in which a ray of light travels, or the direction in which a ray of light reflects off a surface.

A first step in characterizing a vector mathematically is geometrically: We graphically specify a vector by a directed line segment (see Figure 1.1), where the length denotes the magnitude of the vector and the aim denotes the direction of the vector. We note that the location in which we draw a vector is immaterial because changing the location does not change the magnitude or direction (the two properties a vector possesses). Therefore, we say two vectors are equal if and only if they have the same length and they point in the same direction. Thus, the vectors u and v drawn in Figure 1.1a are actually equal because they have the same length and point in the same direction. In fact, because location is unimportant for vectors, we can always translate a vector without changing its meaning (since a translation changes neither length nor direction). Observe that we could translate u such that it completely overlaps with v (and conversely), thereby making them indistinguishable—hence their equality. As a physical example, the vectors u and v in Figure 1.1b both tell the ants at two different points A and B to move north ten meters from where they are. Again we have that u = v. The vectors themselves are independent of position; they simply instruct the ants how to move from where they are. In this example, they tell the ants to move north (direction) ten meters (length).

image

Figure 1.1. (a) Vectors drawn on a 2D plane. (b) Vectors instructing ants to move 10 meters north.

1.1.1 Vectors and Coordinate Systems

We could now define useful geometric operations on vectors, which can then be used to solve problems involving vector-valued quantities. However, since the computer cannot work with vectors geometrically, we need to find a way of specifying vectors numerically instead. So what we do is introduce a 3D coordinate system in space, and translate all the vectors so that their tails coincide with the origin (Figure 1.2). Then we can identify a vector by specifying the coordinates of its head, and write v = (x, y, z) as shown in Figure 1.3. Now we can represent a vector with three floats in a computer program.

image

If working in 2D, then we just use a 2D coordinate system and the vector only has two coordinates: v = (x, y) and we can represent a vector with two floats in a computer program.

Consider Figure 1.4, which shows a vector v and two frames in space. (Note that we use the terms frame, frame of reference, space, and coordinate system to all mean the same thing in this book.) We can translate v so that it is in standard position in either of the two frames. Observe, however, that the coordinates of the vector v relative to frame A are different than the coordinates of the vector v relative to frame B. In other words, the same vector v has a different coordinate representation for distinct frames.

image

Figure 1.2. We translate v so that its tail coincides with the origin of the coordinate system. When a vector’s tail coincides with the origin, we say that it is in standard position.

image

Figure 1.3. A vector specified by coordinates relative to a coordinate system.

image

Figure 1.4. The same vector v has different coordinates when described relative to different frames.

The idea is analogous to, say, temperature. Water boils at 100° Celsius or 212° Fahrenheit. The physical temperature of boiling water is the same no matter the scale (i.e., we can’t lower the boiling point by picking a different scale), but we assign a different scalar number to the temperature based on the scale we use. Similarly, for a vector, its direction and magnitude, which are embedded in the directed line segment, does not change; only the coordinates of it change based on the frame of reference we use to describe it. This is important because it means whenever we identify a vector by coordinates, those coordinates are relative to some frame of reference. Often in 3D computer graphics, we will utilize more than one frame of reference and, therefore, we will need to keep track of which frame a vector’s coordinates are relative to; additionally, we will need to know how to convert vector coordinates from one frame to another.

image

We see that both vectors and points can be described by coordinates (x, y, z) relative to a frame. However, they are not the same; a point represents a location in 3-space, whereas a vector represents a magnitude and direction. We will have more to say about points in §1.5.

1.1.2 Left-Handed Versus Right-Handed Coordinate Systems

Direct3D uses a so-called left-handed coordinate system. If you take your left hand and aim your fingers down the positive x-axis, and then curl your fingers towards the positive y-axis, your thumb points roughly in the direction of the positive z-axis. Figure 1.5 illustrates the differences between a left-handed and right-handed coordinate system.

Observe that for the right-handed coordinate system, if you take your right hand and aim your fingers down the positive x-axis, and then curl your fingers towards the positive y-axis, your thumb points roughly in the direction of the positive z-axis.

image

Figure 1.5. On the left we have a left-handed coordinate system. Observe that the positive z-axis goes into the page. On the right we have a right-handed coordinate system. Observe that the positive z-axis comes out of the page.

1.1.3 Basic Vector Operations

We now define equality, addition, scalar multiplication, and subtraction on vectors using the coordinate representation. For these four definitions, let u = (ux, uy, uz) and v = (vx, vy, vz).

1. Two vectors are equal if and only if their corresponding components are equal. That is, u = v if and only if ux = vx, uy = vy, and uz = vz.

2. We add vectors component-wise: u + v = (ux + vx, uy + vy, uz + vz). Observe that it only makes sense to add vectors of the same dimension.

3. We can multiply a scalar (i.e., a real number) and a vector and the result is a vector. Let k be a scalar, then ku = (kux, kuy, kuz). This is called scalar multiplication.

4. We define subtraction in terms of vector addition and scalar multiplication. That is, uv = u + (−1 · v) = u + (−v) = (uxvx, uyvy, uzvz).

image Example 1.1

Let u = (1, 2, 3), v = (1, 2, 3), w = (3, 0, −2), and k = 2. Then,

1. u + w = (1, 2, 3) + (3, 0, −2) = (4, 2, 1);

2. u = v;

3. uv = u + (−v) = (1, 2, 3) + (−1, −2, −3) = (0, 0, 0) = 0;

4. kw = 2(3, 0, −2) = (6, 0, −4)

The difference in the third bullet illustrates a special vector, called the zero-vector, which has zeros for all of its components and is denoted by 0.

image Example 1.2

We will illustrate this example with 2D vectors to make the drawings simpler. The ideas are the same as in 3D; we just work with one less component in 2D.

1. Let v = (2, 1) How do v and image

compare geometrically? We note that image

. Graphing both v and image

(Figure 1.6a), we notice that image

is in the direction directly opposite of v and its length is 1/2 that of v. Thus, geometrically, negating a vector can be thought of as “flipping" its direction, and scalar multiplication can be thought of as scaling the length of a vector.

2. Let image

and v = (1, 2). Then image

. Figure 1.6b shows what vector addition means geometrically: We parallel translate u so that its tail coincided with the head of v. Then, the sum is the vector originating at the tail of v and ending at the head of the translated u. (We get the same result if we keep u fixed and translate v so that its tail coincided with the head of u. In this case, u + v would be the vector originating at the tail of u and ending at the head of the translated v.) Observe also that our rules of vector addition agree with what we would intuitively expect to happen physically when we add forces together to produce a net force: If we add two forces (vectors) in the same direction, we get another stronger net force (longer vector) in that direction. If we add two forces (vectors) in opposition to each other, then we get a weaker net force (shorter vector). Figure 1.7 illustrates these ideas.

3. Let image

and v = (1, 2). Then image

. Figure 1.6c shows what vector subtraction means geometrically. Essentially, the difference vu gives us a vector aimed from the head of u to the head of v. If we instead interpret u and v as points, then vu gives us a vector aimed from the point u to the point v; this interpretation is important as we will often want the vector aimed from one point to another. Observe also that the length of vu is the distance from u to v, when thinking of u and v as points.

image

Figure 1.6. (a) The geometric interpretation of scalar multiplication. (b) The geometric interpretation of vector addition. (c) The geometric interpretation of vector subtraction.

image

Figure 1.7. Forces applied to a ball. The forces are combined using vector addition to get a net force.

1.2 LENGTH AND UNIT VECTORS

Geometrically, the magnitude of a vector is the length of the directed line segment. We denote the magnitude of a vector by double vertical bars (e.g., ||u|| denotes the magnitude of u). Now, given a vector u = (x, y, z), we wish to compute its magnitude algebraically. The magnitude of a 3D vector can be computed by applying the Pythagorean theorem twice; see Figure 1.8.

First, we look at the triangle in the xz-plane with sides x, z, and hypotenuse a. From the Pythagorean theorem, we have image

. Now look at the triangle with sides a, y, and hypotenuse ||u||. From the Pythagorean theorem again, we arrive at the following magnitude formula:

image

For some applications, we do not care about the length of a vector because we want to use the vector to represent a pure direction. For such direction-only vectors, we want the length of the vector to be exactly 1. When we make a vector unit length, we say that we are normalizing the vector. We can normalize a vector by dividing each of its components by its magnitude:

image

image

Figure 1.8. The 3D length of a vector can be computed by applying the Pythagorean theorem twice.

To verify that this formula is correct, we can compute the length of û:

image

So û is indeed a unit vector.

image Example 1.3

Normalize the vector v = (−1, 3, 4). We have image. Thus,

image

To verify that image is indeed a unit vector, we compute its length:

image

1.3 THE DOT PRODUCT

The dot product is a form of vector multiplication that results in a scalar value; for this reason, it is sometimes referred to as the scalar product. Let u = (ux, uy, uz) and v = (vx, vy, vz), then the dot product is defined as follows:

image

In words, the dot product is the sum of the products of the corresponding components.

The dot product definition does not present an obvious geometric meaning. Using the law of cosines (see Exercise 10), we can find the relationship,

image

image

Figure 1.9. In the left figure, the angle θ between u and v is an acute angle. In the right figure, the angle θ between u and v is an obtuse angle. When we refer to the angle between two vectors, we always mean the smallest angle, that is, the angle θ such that 0 ≤ θ ≤ π.

where θ is the angle between the vectors u and v such that 0 ≤ θ ≤ π; see Figure 1.9. So, Equation 1.4 says that the dot product between two vectors is the cosine of the angle between them scaled by the vectors’ magnitudes. In particular, if both u and v are unit vectors, then u · v is the cosine of the angle between them (i.e., u · v = cos θ).

Equation 1.4 provides us with some useful geometric properties of the dot product:

1. If u · v = 0, then uv (i.e., the vectors are orthogonal).

2. If u · v > 0, then the angle θ between the two vectors is less than 90 degrees (i.e., the vectors make an acute angle).

3. If u · v < 0, the angle θ between the two vectors is greater than 90 degrees (i.e., the vectors make an obtuse angle).

image

The word “orthogonal" can be used as a synonym for “perpendicular."

image Example 1.4

Let u = (1, 2, 3) and v = (−4, 0, −1). Find the angle between u and v. First we compute:

image

Now, applying Equation 1.4 and solving for theta, we get:

image

image Example 1.5

Consider Figure 1.10. Given v and the unit vector n, find a formula for p in terms of v and n using the dot product.

First, observe from the figure that there exists a scalar k such that p = kn; moreover, since we assumed ||n|| = 1, we have ||p|| = ||kn|| = |k|||n|| = |k|. (Note that k may be negative if and only if p and n aim in opposite directions.) Using trigonometry, we have that k = ||v|| cosθ; therefore, p = kn = (||v|| cosθ)n. However, because n is a unit vector, we can say this in another way:

image

In particular, this shows k = v · n, and this illustrates the geometric interpretation of v · n when n is a unit vector. We call p the orthogonal projection of v on n, and it is commonly denoted by

image

If we interpret v as a force, p can be thought of as the portion of the force v that acts in the direction n. Likewise, the vector w = perpn(v) = vp is the portion of the force v that acts orthogonal to the direction n (which is why we also denote it by perpn(v) for perpendicular). Observe that image

, which is to say we have decomposed the vector v into the sum of two orthogonal vectors p and w.

If n is not of unit length, we can always normalize it first to make it unit length. Replacing n by the unit vector image

gives us the more general projection formula:

image

image

Figure 1.10. The orthogonal projection of v on n.

1.3.1 Orthogonalization

A set of vectors {v0, …, vn-1} is called orthonormal if the vectors are mutually orthogonal (every vector in the set is orthogonal to every other vector in the set) and unit length. Sometimes we have a set of vectors that are almost orthonormal, but not quite. A common task is to orthogonalize the set and make it orthonormal. In 3D computer graphics we might start off with an orthonormal set, but due to numerical precision issues, the set gradually becomes un-orthonormal. We are mainly concerned with the 2D and 3D cases of this problem (that is, sets that contain two and three vectors, respectively).

We examine the simpler 2D case first. Suppose we have the set of vectors {v0, v1} that we want to orthogonalize into an orthonormal set {w0, w1} as shown in Figure 1.11. We start with w0 = v0 and modify v1 to make it orthogonal to w0; this is done by subtracting out the portion of v1 that acts in the w0 direction:

image

We now have a mutually orthogonal set of vectors {w0, w1}; the last step to constructing the orthonormal set is to normalize w0 and w1 to make them unit length.

The 3D case follows in the same spirit as the 2D case, but with more steps. Suppose we have the set of vectors {v0, v1, v2} that we want to orthogonalize into an orthonormal set {w0, w1, w2}as shown in Figure 1.12. We start with w0 = v0 and modify v1 to make it orthogonal to w0; this is done by subtracting out the portion of v1 that acts in the w0 direction:

image

image

Figure 1.11. 2D orthogonalization.

image

Figure 1.12. 3D orthogonalization.

Next, we modify v2 to make it orthogonal to both w0 and w1. This is done by subtracting out the portion of v2 that acts in the w0 direction and the portion of v2 that acts in the w1 direction:

image

We now have a mutually orthogonal set of vectors {w0, w1, w2}; the last step to constructing the orthonormal set is to normalize w0, w1 and w2 to make them unit length.

For the general case of n vectors {v0, …, vn−1} that we want to orthogonalize into an orthonormal set {w0, …, wn−1}, we have the following procedure commonly called the Gram-Schmidt Orthogonalization process:

image

Again, the intuitive idea is that when we pick a vector vi from the input set to add to the orthonormal set, we need to subtract out the components of vi that act in the directions of the other vectors (w0, w1,…, wi−1) that are already in the orthonormal set to ensure the new vector being added is orthogonal to the other vectors already in the orthonormal set.

1.4 THE CROSS PRODUCT

The second form of multiplication vector math defines is the cross product. Unlike the dot product, which evaluates to a scalar, the cross product evaluates to another vector; moreover, the cross product is only defined for 3D vectors (in particular, there is no 2D cross product). Taking the cross product of two 3D vectors u and v yields another vector, w that is mutually orthogonal to u and v. By that we mean w is orthogonal to u, and w is orthogonal to v; see Figure 1.13. If u = (ux, uy, uz) and v = (vx, vy, vz), then the cross product is computed like so:

image

image

If you are working in a right-handed coordinate system, then you use the right-hand-thumb rule: If you take your right hand and aim the fingers in the direction of the first vector u, and then curl your fingers toward v along an angle 0 ≤ θ ≤ π then your thumb roughly points in the direction of w = u × v.

image

Figure 1.13. The cross product of two 3D vectors u and v yields another vector w that is mutually orthogonal to u and v. If you take your left hand and aim the fingers in the direction of the first vector u, and then curl your fingers toward v along an angle 0 ≤ θ ≤ π , then your thumb roughly points in the direction of w = u × v ; this is called the left-hand-thumb rule.

image Example 1.6

Let u = (2, 1,3) and v = (2, 0, 0). Compute w = u × v and z = v × u, and then verify that w is orthogonal to u and that w is orthogonal to v. Applying Equation 1.5 we have,

w = u × v

= (2, 1, 3) × (2, 0, 0)

=(1·0 − 3·0, 3·2 − 2·0, 2·0 − 1·2)

= (0, 6, −2)

And

z = v × u

= (2, 0, 0) × (2, 1, 3)

=(0·3 − 0·1, 0·2 − 2·3, 2·1 − 0·2)

= (0, − 6, 2)

This result makes one thing clear, generally speaking u × vv × u. Therefore, we say that the cross product is anti-commutative. In fact, it can be shown that u × v = − v × u. You can determine the vector returned by the cross product by the left-hand-thumb rule. If you first aim your fingers in the direction of the first vector, and then curl your fingers towards the second vector (always take the path with the smallest angle), your thumb points in the direction of the returned vector, as shown in Figure 1.11.

To show that w is orthogonal to u and that w is orthogonal to v, we recall from §1.3 that if u · v = 0, then uv (i.e., the vectors are orthogonal). Because

image

image

Figure 1.14. The 2D Pseudo Cross Product of a vector u evaluates to an orthogonal vector v.

and

image

we conclude that w is orthogonal to u and that w is orthogonal to v.

1.4.1 Pseudo 2D Cross Product

The cross product allows us to find a vector orthogonal to two given 3D vectors. In 2D we do not quite have the same situation, but given a 2D vector u = (ux, uy) it can be useful to find a vector v orthogonal to u. Figure 1.14 shows the geometric setup from which it is suggested that v = (−uy, ux). The formal proof is straightforward:

image

Thus uv. Observe that u·−v = uxuy + uy (-ux)=0, too, so we also have that u ⊥ −v.

1.4.2 Orthogonalization with the Cross Product

In §1.3.1, we looked at a way to orthogonalize a set of vectors using the Gram-Schmidt process. For 3D, there is another strategy to orthogonalize a set of vectors {v0, v1, v2} that are almost orthonormal, but perhaps became un-orthonormal due to accumulated numerical precision errors, using the cross product. Refer to Figure 1.15 for the geometry of this process:

1. Set image

.

2. Set image

3. Set w1 = w2 × w0 By Exercise 14,||w2 × w0|| = 1 because w2 w0 and ||w2|| = ||w0|| = 1, so we do not need to do any normalization in this last step.

image

Figure 1.15. 3D orthogonalization with the cross product.

At this point, the set of vectors {w0, w1, w2} is orthonormal.

image

In the above example, we started with image

which means we did not change the direction when going from v0 to w0; we only changed the length. However, the directions of w1 and w2 could be different from v1 and v2, respectively. Depending on the specific application, the vector you choose not to change the direction of might be important. For example, later in this book we represent the orientation of the camera with three orthonormal vectors {v0, v1, v2} where the third vector v2 describes the direction the camera is looking. When orthogonalizing these vectors, we often do not want to change the direction we are looking, and so we will start the above algorithm with v2 and modify v0and v1 to orthogonalize the vectors.

1.5 POINTS

So far we have been discussing vectors, which do not describe positions. However, we will also need to specify positions in our 3D programs, for example, the position of 3D geometry and the position of the 3D virtual camera. Relative to a coordinate system, we can use a vector in standard position (Figure 1.16) to represent a 3D position in space; we call this a position vector. In this case, the location of the tip of the vector is the characteristic of interest, not the direction or magnitude. We will use the terms “position vector” and “point” interchangeably since a position vector is enough to identify a point.

One side effect of using vectors to represent points, especially in code, is that we can do vector operations that do not make sense for points; for instance, geometrically, what should the sum of two points mean? On the other hand, some operations can be extended to points. For example, we define the difference of two points qp to be the vector from p to q. Also, we define a point p plus a vector v to be the point q obtained by displacing p by the vector v. Conveniently, because we are using vectors to represent points relative to a coordinate system, no extra work needs to be done for the point operations just discussed, as the vector algebra framework already takes care of them; see Figure 1.17.

image

Figure 1.16. The position vector, which extends from the origin to the point, fully describes where the point is located relative to the coordinate system.

image

Figure 1.17. (a) The difference qp between two points is defined as the vector from p to q. (b) A point p plus the vector v is defined to be the point q obtained by displacing p by the vector v.

image

Actually there is a geometric meaningful way to define a special sum of points, called an affine combination, which is like a weighted average of points.

1.6 DIRECTX MATH VECTORS

For Windows 8 and above, DirectX Math is a 3D math library for Direct3D application that is part of the Windows SDK. The library uses the SSE2 (Streaming SIMD Extensions 2) instruction set. With 128-bit wide SIMD (single instruction multiple data) registers, SIMD instructions can operate on four 32-bit floats or ints with one instruction. This is very useful for vector calculations; for example, if you look at vector addition:

image

we see that we just add corresponding components. By using SIMD, we can do 4D vector addition with one SIMD instruction instead of four scalar instructions. If we only required three coordinates for 3D work, we can still use SIMD, but we would just ignore the fourth coordinate; likewise, for 2D we would ignore the third and fourth coordinates.

Our coverage of the DirectX Math library is not comprehensive, and we only cover the key parts needed for this book. For all the details, we recommend the online documentation [DirectXMath]. For readers wishing to understand how an SIMD vector library might be developed optimally, and, perhaps, to gain some insight why the DirectX Math library made some of the design decisions that it did, we recommend the article Designing Fast Cross-Platform SIMD Vector Libraries by [Oliveira2010].

To use the DirectX Math library, you need to #include <DirectXMath.h>, and for some additional data types #include <DirectXPackedVector.h>. There are no additional library files, as all the code is implemented inline in the header file. The DirectXMath.h code lives in the DirectX namespace, and the DirectXPackedVector.h code lives in the DirectX::PackedVector namespace. In addition, for the x86 platform you should enable SSE2 (Project Properties > Configuration Properties > C/C++ > Code Generation > Enable Enhanced Instruction Set), and for all platforms you should enable the fast floating point model /fp:fast (Project Properties > Configuration Properties > C/C++ > Code Generation > Floating Point Model). You do not need to enable SSE2 for the x64 platform because all x64 CPUs support SSE2 (http://en.wikipedia.org/wiki/SSE2).

1.6.1 Vector Types

In DirectX Math, the core vector type is XMVECTOR, which maps to SIMD hardware registers. This is a 128-bit type that can process four 32-bit floats with a single SIMD instruction. When SSE2 is available, it is defined like so for x86 and x64 platforms:

typedef __m128 XMVECTOR;

where __m128 is a special SIMD type. When doing calculations, vectors must be of this type to take advantage of SIMD. As already mentioned, we still use this type for 2D and 3D vectors to take advantage of SIMD, but we just zero out the unused components and ignore them.

XMVECTOR needs to be 16-byte aligned, and this is done automatically for local and global variables. For class data members, it is recommended to use XMFLOAT2 (2D), XMFLOAT3 (3D), and XMFLOAT4 (4D) instead; these structures are defined below:

struct XMFLOAT2

{

float x;

float y;

XMFLOAT2() {}

XMFLOAT2(float _x, float _y) : x(_x), y(_y) {}

explicit XMFLOAT2(_In_reads_(2) const float *pArray) :

x(pArray[0]), y(pArray[1]) {}

XMFLOAT2& operator= (const XMFLOAT2& Float2)

{ x = Float2.x; y = Float2.y; return *this; }

};

struct XMFLOAT3

{

float x;

float y;

float z;

XMFLOAT3() {}

XMFLOAT3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}

explicit XMFLOAT3(_In_reads_(3) const float *pArray) :

x(pArray[0]), y(pArray[1]), z(pArray[2]) {}

XMFLOAT3& operator= (const XMFLOAT3& Float3)

{ x = Float3.x; y = Float3.y; z = Float3.z; return *this; }

};

struct XMFLOAT4

{

float x;

float y;

float z;

float w;

XMFLOAT4() {}

XMFLOAT4(float _x, float _y, float _z, float _w) :

x(_x), y(_y), z(_z), w(_w) {}

explicit XMFLOAT4(_In_reads_(4) const float *pArray) :

x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {}

XMFLOAT4& operator= (const XMFLOAT4& Float4)

{ x = Float4.x; y = Float4.y; z = Float4.z; w = Float4.w; return *this; }

};

However, if we use these types directly for calculations, then we will not take advantage of SIMD. In order to use SIMD, we need to convert instances of these types into the XMVECTOR type. This is done with the DirectX Math loading functions. Conversely, DirectX Math provides storage functions which are used to convert data from XMVECTOR into the XMFLOATn types above.

To summarize,

1. Use XMVECTOR for local or global variables.

2. Use XMFLOAT2, XMFLOAT3, and XMFLOAT4 for class data members.

3. Use loading functions to convert from XMFLOATn to XMVECTOR before doing calculations.

4. Do calculations with XMVECTOR instances.

5. Use storage functions to convert from XMVECTOR to XMFLOATn.

1.6.2 Loading and Storage Methods

We use the following methods to load data from XMFLOATn into XMVECTOR:

// Loads XMFLOAT2 into XMVECTOR

XMVECTOR XM_CALLCONV XMLoadFloat2(const XMFLOAT2 *pSource);

// Loads XMFLOAT3 into XMVECTOR

XMVECTOR XM_CALLCONV XMLoadFloat3(const XMFLOAT3 *pSource);

// Loads XMFLOAT4 into XMVECTOR

XMVECTOR XM_CALLCONV XMLoadFloat4(const XMFLOAT4 *pSource);

We use the following methods to store data from XMVECTOR into XMFLOATn:

// Loads XMVECTOR into XMFLOAT2

void XM_CALLCONV XMStoreFloat2(XMFLOAT2 *pDestination, FXMVECTOR V);

// Loads XMVECTOR into XMFLOAT3

void XM_CALLCONV XMStoreFloat3(XMFLOAT3 *pDestination, FXMVECTOR V);

// Loads XMVECTOR into XMFLOAT4

void XM_CALLCONV XMStoreFloat4(XMFLOAT4 *pDestination, FXMVECTOR V);

Sometimes we just want to get or set one component of an XMVECTOR; the following getter and setter functions facilitate this:

float XM_CALLCONV XMVectorGetX(FXMVECTOR V);

float XM_CALLCONV XMVectorGetY(FXMVECTOR V);

float XM_CALLCONV XMVectorGetZ(FXMVECTOR V);

float XM_CALLCONV XMVectorGetW(FXMVECTOR V);

XMVECTOR XM_CALLCONV XMVectorSetX(FXMVECTOR V, float x);

XMVECTOR XM_CALLCONV XMVectorSetY(FXMVECTOR V, float y);

XMVECTOR XM_CALLCONV XMVectorSetZ(FXMVECTOR V, float z);

XMVECTOR XM_CALLCONV XMVectorSetW(FXMVECTOR V, float w);

1.6.3 Parameter Passing

For efficiency purposes, XMVECTOR values can be passed as arguments to functions in SSE/SSE2 registers instead of on the stack. The number of arguments that can be passed this way depends on the platform (e.g., 32-bit Windows, 64-bit Windows, and Windows RT) and compiler. Therefore, to be platform/compiler independent, we use the types FXMVECTOR, GXMVECTOR, HXMVECTOR and CXMVECTOR for passing XMVECTOR parameters; these are defined to the right type based on the platform and compiler. Furthermore, the calling convention annotation XM_CALLCONV must be specified before the function name so that the proper calling convention is used, which again depends on the compiler version.

Now the rules for passing XMVECTOR parameters are as follows:

1. The first three XMVECTOR parameters should be of type FXMVECTOR;

2. The fourth XMVECTOR should be of type GXMVECTOR;

3. The fifth and sixth XMVECTOR parameter should be of type HXMVECTOR;

4. Any additional XMVECTOR parameters should be of type CXMVECTOR.

We illustrate how these types are defined on 32-bit Windows with a compiler that supports the __fastcall calling convention and a compiler that supports the newer __vectorcall calling convention:

// 32-bit Windows __fastcall passes first 3 XMVECTOR arguments

// via registers, the remaining on the stack.

typedef const XMVECTOR FXMVECTOR;

typedef const XMVECTOR& GXMVECTOR;

typedef const XMVECTOR& HXMVECTOR;

typedef const XMVECTOR& CXMVECTOR;

// 32-bit Windows __vectorcall passes first 6 XMVECTOR arguments

// via registers, the remaining on the stack.

typedef const XMVECTOR FXMVECTOR;

typedef const XMVECTOR GXMVECTOR;

typedef const XMVECTOR HXMVECTOR;

typedef const XMVECTOR& CXMVECTOR;

For the details on how these types are defined for the other platforms, see “Calling Conventions” under “Library Internals” in the DirectX Math documentation [DirectXMath]. The exception to these rules is with constructor methods. [DirectXMath] recommends using FXMVECTOR for the first three XMVECTOR parameters and CXMVECTOR for the rest when writing a constructor that takes XMVECTOR parameters. Furthermore, do not use the annotation XM_CALLCONV for constructors

Here is an example from the DirectXMath library:

inline XMMATRIX XM_CALLCONV XMMatrixTransformation(

FXMVECTOR ScalingOrigin,

FXMVECTOR ScalingOrientationQuaternion, .

FXMVECTOR Scaling,

GXMVECTOR RotationOrigin,

HXMVECTOR RotationQuaternion,

HXMVECTOR Translation);

This function takes 6 XMVECTOR parameters, but following the parameter passing rules, it uses FXMVECTOR for the first three parameters, GXMVECTOR for the fourth, and HXMVECTOR for the fifth and sixth.

You can have non-XMVECTOR parameters between XMVECTOR parameters. The same rules apply and the XMVECTOR parameters are counted as if the non-XMVECTOR parameters were not there. For example, in the following function, the first three XMVECTOR parameters are of type FXMVECTOR as and the fourth XMVECTOR parameter is of type GXMVECTOR.

inline XMMATRIX XM_CALLCONV XMMatrixTransformation2D(

FXMVECTOR ScalingOrigin,

float ScalingOrientation,

FXMVECTOR Scaling,

FXMVECTOR RotationOrigin,

float Rotation,

GXMVECTOR Translation);

The rules for passing XMVECTOR parameters apply to “input” parameters. “Output” XMVECTOR parameters (XMVECTOR& or XMVECTOR*) will not use the SSE/SSE2 registers and so will be treated like non-XMVECTOR parameters.

1.6.4 Constant Vectors

Constant XMVECTOR instances should use the XMVECTORF32 type. Here are some examples from the DirectX SDK’s CascadedShadowMaps11 sample:

static const XMVECTORF32 g_vHalfVector = { 0.5f, 0.5f, 0.5f, 0.5f };

static const XMVECTORF32 g_vZero = { 0.0f, 0.0f, 0.0f, 0.0f };

XMVECTORF32 vRightTop = {

vViewFrust.RightSlope,

vViewFrust.TopSlope,

1.0f,1.0f

};

XMVECTORF32 vLeftBottom = {

vViewFrust.LeftSlope,

vViewFrust.BottomSlope,

1.0f,1.0f

};

Essentially, we use XMVECTORF32 whenever we want to use initialization syntax.

XMVECTORF32 is a 16-byte aligned structure with a XMVECTOR conversion operator; it is defined as follows:

// Conversion types for constants

__declspec(align(16)) struct XMVECTORF32

{

union

{

float f[4];

XMVECTOR v;

};

inline operator XMVECTOR() const { return v; }

inline operator const float*() const { return f; }

#if !defined(_XM_NO_INTRINSICS_) && defined(_XM_SSE_INTRINSICS_)

inline operator __m128i() const { return _mm_castps_si128(v); }

inline operator __m128d() const { return _mm_castps_pd(v); }

#endif

};

You can also create a constant XMVECTOR of integer data using XMVECTORU32:

static const XMVECTORU32 vGrabY = {

0x00000000,0xFFFFFFFF,0x00000000,0x00000000

};

1.6.5 Overloaded Operators

The XMVECTOR has several overloaded operators for doing vector addition, subtraction, and scalar multiplication.

XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V);

XMVECTOR XM_CALLCONV operator- (FXMVECTOR V);

XMVECTOR& XM_CALLCONV operator+= (XMVECTOR& V1, FXMVECTOR V2);

XMVECTOR& XM_CALLCONV operator-= (XMVECTOR& V1, FXMVECTOR V2);

XMVECTOR& XM_CALLCONV operator*= (XMVECTOR& V1, FXMVECTOR V2);

XMVECTOR& XM_CALLCONV operator/= (XMVECTOR& V1, FXMVECTOR V2);

XMVECTOR& operator*= (XMVECTOR& V, float S);

XMVECTOR& operator/= (XMVECTOR& V, float S);

XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V1, FXMVECTOR V2);

XMVECTOR XM_CALLCONV operator- (FXMVECTOR V1, FXMVECTOR V2);

XMVECTOR XM_CALLCONV operator* (FXMVECTOR V1, FXMVECTOR V2);

XMVECTOR XM_CALLCONV operator/ (FXMVECTOR V1, FXMVECTOR V2);

XMVECTOR XM_CALLCONV operator* (FXMVECTOR V, float S);

XMVECTOR XM_CALLCONV operator* (float S, FXMVECTOR V);

XMVECTOR XM_CALLCONV operator/ (FXMVECTOR V, float S);

1.6.6 Miscellaneous

The DirectX Math library defined the following constants useful for approximating different expressions involving π:

const float XM_PI = 3.141592654f;

const float XM_2PI = 6.283185307f;

const float XM_1DIVPI = 0.318309886f;

const float XM_1DIV2PI = 0.159154943f;

const float XM_PIDIV2 = 1.570796327f;

const float XM_PIDIV4 = 0.785398163f;

In addition, it defines the following inline functions for converting between radians and degrees:

inline float XMConvertToRadians(float fDegrees)

{ return fDegrees * (XM_PI / 180.0f); }

inline float XMConvertToDegrees(float fRadians)

{ return fRadians * (180.0f / XM_PI); }

It also defines min/max functions:

template<class T> inline T XMMin(T a, T b) { return (a < b) ? a : b; }

template<class T> inline T XMMax(T a, T b) { return (a > b) ? a : b; }

1.6.7 Setter Functions

DirectX Math provides the following functions to set the contents of an XMVECTOR:

// Returns the zero vector 0

XMVECTOR XM_CALLCONV XMVectorZero();

// Returns the vector (1, 1, 1, 1)

XMVECTOR XM_CALLCONV XMVectorSplatOne();

// Returns the vector (x, y, z, w)

XMVECTOR XM_CALLCONV XMVectorSet(float x, float y, float z, float w);

// Returns the vector (s, s, s, s)

XMVECTOR XM_CALLCONV XMVectorReplicate(float Value);

// Returns the vector (vx, vx, vx, vx)

XMVECTOR XM_CALLCONV XMVectorSplatX(FXMVECTOR V);

// Returns the vector (vy, vy, vy, vy)

XMVECTOR XM_CALLCONV XMVectorSplatY(FXMVECTOR V);

// Returns the vector (vz, vz, vz, vz)

XMVECTOR XM_CALLCONV XMVectorSplatZ(FXMVECTOR V);

The following program illustrates most of these functions:

#include <windows.h> // for XMVerifyCPUSupport

#include <DirectXMath.h>

#include <DirectXPackedVector.h>

#include <iostream>

using namespace std;

using namespace DirectX;

using namespace DirectX::PackedVector;

// Overload the "<<" operators so that we can use cout to

// output XMVECTOR objects.

ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR v)

{

XMFLOAT3 dest;

XMStoreFloat3(&dest, v);

os << "(" << dest.x << ", " << dest.y << ", " << dest.z << ")";

return os;

}

int main()

{

cout.setf(ios_base::boolalpha);

// Check support for SSE2 (Pentium4, AMD K8, and above).

if (!XMVerifyCPUSupport())

{

cout << "directx math not supported" << endl;

return 0;

}

XMVECTOR p = XMVectorZero();

XMVECTOR q = XMVectorSplatOne();

XMVECTOR u = XMVectorSet(1.0f, 2.0f, 3.0f, 0.0f);

XMVECTOR v = XMVectorReplicate(-2.0f);

XMVECTOR w = XMVectorSplatZ(u);

cout << "p = " << p << endl;

cout << "q = " << q << endl;

cout << "u = " << u << endl;

cout << "v = " << v << endl;

cout << "w = " << w << endl;

return 0;

}

image

Figure 1.18. Output for the above program.

1.6.8 Vector Functions

DirectX Math provides the following functions to do various vector operations. We illustrate with the 3D versions, but there are analogous versions for 2D and 4D; the 2D and 4D versions have the same names as the 3D versions, with the exception of a 2 and 4 substituted for the 3, respectively.

XMVECTOR XM_CALLCONV XMVector3Length( // Returns ||v||

FXMVECTOR V); // Input v

XMVECTOR XM_CALLCONV XMVector3LengthSq( // Returns ||v||2

FXMVECTOR V); // Input v

XMVECTOR XM_CALLCONV XMVector3Dot( // Returns v1·v2

FXMVECTOR V1, // Input v1

FXMVECTOR V2); // Input v2

XMVECTOR XM_CALLCONV XMVector3Cross( // Returns v1 × v2

FXMVECTOR V1, // Input v1

FXMVECTOR V2); // Input v2

XMVECTOR XM_CALLCONV XMVector3Normalize( // Returns v/||v||

FXMVECTOR V); // Input v

XMVECTOR XM_CALLCONV XMVector3Orthogonal( // Returns a vector orthogonal to v

FXMVECTOR V); // Input v

XMVECTOR XM_CALLCONV

XMVector3AngleBetweenVectors( // Returns the angle between v1 and v2

FXMVECTOR V1, // Input v1

FXMVECTOR V2); // Input v2

void XM_CALLCONV XMVector3ComponentsFromNormal(

XMVECTOR* pParallel, // Returns projn(v)

XMVECTOR* pPerpendicular, // Returns perpn(v)

FXMVECTOR V, // Input v

FXMVECTOR Normal); // Input n

bool XM_CALLCONV XMVector3Equal( // Returns v1 = v2

FXMVECTOR V1, // Input v1

FXMVECTOR V2); // Input v2

bool XM_CALLCONV XMVector3NotEqual( // Returns v1 ≠ v2

FXMVECTOR V1, // Input v1

FXMVECTOR V2); // Input v2

image

Observe that these functions return XMVECTORs even for operations that mathematically return a scalar (for example, the dot product k = v1 · v2). The scalar result is replicated in each component of the XMVECTOR. For example, for the dot product, the returned vector would be (v1 · v2, v1 · v2,v1 · v2,v1 · v2). One reason for this is to minimize mixing of scalar and SIMD vector operations; it is more efficient to keep everything SIMD until you are done with your calculations.

The following demo program shows how to use most of these functions, as well as some of the overloaded operators:

#include <windows.h> // for XMVerifyCPUSupport

#include <DirectXMath.h>

#include <DirectXPackedVector.h>

#include <iostream>

using namespace std;

using namespace DirectX;

using namespace DirectX::PackedVector;

// Overload the "<<" operators so that we can use cout to

// output XMVECTOR objects.

ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR v)

{

XMFLOAT3 dest;

XMStoreFloat3(&dest, v);

os << "(" << dest.x << ", " << dest.y << ", " << dest.z << ")";

return os;

}

int main()

{

cout.setf(ios_base::boolalpha);

// Check support for SSE2 (Pentium4, AMD K8, and above).

if (!XMVerifyCPUSupport())

{

cout << "directx math not supported" << endl;

return 0;

}

XMVECTOR n = XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f);

XMVECTOR u = XMVectorSet(1.0f, 2.0f, 3.0f, 0.0f);

XMVECTOR v = XMVectorSet(-2.0f, 1.0f, -3.0f, 0.0f);

XMVECTOR w = XMVectorSet(0.707f, 0.707f, 0.0f, 0.0f);

// Vector addition: XMVECTOR operator +

XMVECTOR a = u + v;

// Vector subtraction: XMVECTOR operator -

XMVECTOR b = u - v;

// Scalar multiplication: XMVECTOR operator *

XMVECTOR c = 10.0f*u;

// ||u||

XMVECTOR L = XMVector3Length(u);

// d = u / ||u||

XMVECTOR d = XMVector3Normalize(u);

// s = u dot v

XMVECTOR s = XMVector3Dot(u, v);

// e = u x v

XMVECTOR e = XMVector3Cross(u, v);

// Find proj_n(w) and perp_n(w)

XMVECTOR projW;

XMVECTOR perpW;

XMVector3ComponentsFromNormal(&projW, &perpW, w, n);

// Does projW + perpW == w?

bool equal = XMVector3Equal(projW + perpW, w) != 0;

bool notEqual = XMVector3NotEqual(projW + perpW, w) != 0;

// The angle between projW and perpW should be 90 degrees.

XMVECTOR angleVec = XMVector3AngleBetweenVectors(projW, perpW);

float angleRadians = XMVectorGetX(angleVec);

float angleDegrees = XMConvertToDegrees(angleRadians);

cout << "u = " << u << endl;

cout << "v = " << v << endl;

cout << "w = " << w << endl;

cout << "n = " << n << endl;

cout << "a = u + v = " << a << endl;

cout << "b = u - v = " << b << endl;

cout << "c = 10 * u = " << c << endl;

cout << "d = u / ||u|| = " << d << endl;

cout << "e = u x v = " << e << endl;

cout << "L = ||u|| = " << L << endl;

cout << "s = u.v = " << s << endl;

cout << "projW = " << projW << endl;

cout << "perpW = " << perpW << endl;

cout << "projW + perpW == w = " << equal << endl;

cout << "projW + perpW != w = " << notEqual << endl;

cout << "angle = " << angleDegrees << endl;

return 0;

}

image

Figure 1.19. Output for the above program.

image

The DirectX Math library also includes some estimation methods, which are less accurate but faster to compute. If you are willing to sacrifice some accuracy for speed, then use the estimate methods. Here are two examples of estimate functions:

XMVECTOR XM_CALLCONV XMVector3LengthEst( // Returns estimated ||v||

FXMVECTOR V); // Input v

XMVECTOR XM_CALLCONV XMVector3NormalizeEst( // Returns estimated v/||v||

FXMVECTOR V); // Input v

1.6.9 Floating-Point Error

While on the subject of working with vectors on a computer, we should be aware of the following. When comparing floating-point numbers, care must be taken due to floating-point imprecision. Two floating-point numbers that we expect to be equal may differ slightly. For example, mathematically, we’d expect a normalized vector to have a length of 1, but in a computer program, the length will only be approximately 1. Moreover, mathematically, 1p = 1 for any real number p, but when we only have a numerical approximation for 1, we see that the approximation raised to the pth power increases the error; thus, numerical error also accumulates. The following short program illustrates these ideas:

#include <windows.h> // for XMVerifyCPUSupport

#include <DirectXMath.h>

#include <DirectXPackedVector.h>

#include <iostream>

using namespace std;

using namespace DirectX;

using namespace DirectX::PackedVector;

int main()

{

cout.precision(8);

// Check support for SSE2 (Pentium4, AMD K8, and above).

if (!XMVerifyCPUSupport())

{

cout << "directx math not supported" << endl;

return 0;

}

XMVECTOR u = XMVectorSet(1.0f, 1.0f, 1.0f, 0.0f);

XMVECTOR n = XMVector3Normalize(u);

float LU = XMVectorGetX(XMVector3Length(n));

// Mathematically, the length should be 1. Is it numerically?

cout << LU << endl;

if (LU == 1.0f)

cout << "Length 1" << endl;

else

cout << "Length not 1" << endl;

// Raising 1 to any power should still be 1. Is it?

float powLU = powf(LU, 1.0e6f);

cout << "LU^(10^6) = " << powLU << endl;

}

image

Figure 1.20. Output for the above program.

To compensate for floating-point imprecision, we test if two floating-point numbers are approximately equal. We do this by defining an Epsilon constant, which is a very small value we use as a “buffer.” We say two values are approximately equal if their distance is less than Epsilon. In other words, Epsilon gives us some tolerance for floating-point imprecision. The following function illustrates how Epsilon can be used to test if two floating-point values are equal:

const float Epsilon = 0.001f;

bool Equals(float lhs, float rhs)

{

// Is the distance between lhs and rhs less than EPSILON?

return fabs(lhs - rhs) < Epsilon ? true : false;

}

The DirectX Math library provides the XMVector3NearEqual function when testing the equality of vectors with an allowed tolerance Epsilon parameter:

// Returns

// abs(U.x – V.x) <= Epsilon.x &&

// abs(U.y – V.y) <= Epsilon.y &&

// abs(U.z – V.z) <= Epsilon.z

XMFINLINE bool XM_CALLCONV XMVector3NearEqual(

FXMVECTOR U,

FXMVECTOR V,

FXMVECTOR Epsilon);

1.7 SUMMARY

1. Vectors are used to model physical quantities that possess both magnitude and direction. Geometrically, we represent a vector with a directed line segment. A vector is in standard position when it is translated parallel to itself so that its tail coincides with the origin of the coordinate system. A vector in standard position can be described numerically by specifying the coordinates of its head relative to a coordinate system.

2. If u = (ux, uy, uz) and v = (vx, vy, vz), then we have the following vector operations:

1. Addition: u + v = (ux + vx, uy + vy, uz + vz)

2. Subtraction: u v = (uxvx, uyvy, uzvz)

3. Scalar Multiplication: ku = (kux, kuy, kuz)

4. Length: image

5. Normalization: image

6. Dot Product: image

7. Cross Product: image

3. We use the DirectX Math XMVECTOR type to describe vectors efficiently in code using SIMD operations. For class data members, we use the XMFLOAT2, XMFLOAT3, and XMFLOAT4 classes, and then use the loading and storage methods to convert back and forth between XMVECTOR and XMFLOATn. Constant vectors that require initialization syntax should use the XMVECTORF32 type.

4. For efficiency purposes, XMVECTOR values can be passed as arguments to functions in SSE/SSE2 registers instead of on the stack. To do this in a platform independent way, we use the types FXMVECTOR, GXMVECTOR,HXMVECTOR and CXMVECTOR for passing XMVECTOR parameters. Then the rule for passing XMVECTOR parameters is that the first three XMVECTOR parameters should be of type FXMVECTOR; the fourth XMVECTOR should be of type GXMVECTOR; the fifth and sixth XMVECTOR parameter should be of type HXMVECTOR; and any additional XMVECTOR parameters should be of type CXMVECTOR.

5. The XMVECTOR class overloads the arithmetic operators to do vector addition, subtraction, and scalar multiplication. Moreover, the DirectX Math library provides the following useful functions for computing the length of a vector, the squared length of a vector, computing the dot product of two vectors, computing the cross product of two vectors, and normalizing a vector:

XMVECTOR XM_CALLCONV XMVector3Length(FXMVECTOR V);
XMVECTOR XM_CALLCONV XMVector3LengthSq(FXMVECTOR V);
XMVECTOR XM_CALLCONV XMVector3Dot(FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV XMVector3Cross(FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV XMVector3Normalize(FXMVECTOR V);

1.8 EXERCISES

1. Let u = (1, 2) and v = (3, −4). Perform the following computations and draw the vectors relative to a 2D coordinate system.

1. u + v

2. uv

3. image

4. −2u + v

2. Let u = (−1, 3, 2) and v = (3, −4, 1). Perform the following computations.

1. u + v

2. uv

3. 3u + 2v

4. −2u + v

3. This exercise shows that vector algebra shares many of the nice properties of real numbers (this is not an exhaustive list). Assume u = (ux, uy, uz), v = (vx, vy, vz), and w = (wx, wy, wz). Also assume that c and k are scalars. Prove the following vector properties.

1. u + v = v + u (Commutative Property of Addition)

2. u + (v + w) = (u + v) + w (Associative Property of Addition)

3. (ck)u = c(ku) (Associative Property of Scalar Multiplication)

4. k(u + v) = ku + kv (Distributive Property 1)

5. u(k + c) = ku + cu (Distributive Property 2)

image

Just use the definition of the vector operations and the properties of real numbers. For example,

(ck)u = (ck)(ux, uy, uz)

= ((ck)ux, (ck)uy, (ck)uz)

= (c(kux), c(kuy), c(kuz))

= c(kux, kuy, kuz)

= c(ku)

4. Solve the equation 2((1, 2, 3) – x) − (−2, 0, 4) = −2(1, 2, 3) for x.

5. Let u = (−1, 3, 2) and v = (3, −4, 1) . Normalize u and v.

6. Let k be a scalar and let u = (ux, uy, uz). Prove that ||ku|| = |k|||u||.

7. Is the angle between u and v orthogonal, acute, or obtuse?

1. u = (1, 1, 1), v = (2, 3, 4)

2. u = (1, 1, 0), v = (−2, 2, 0)

3. u = (−1, −1, −1), v = (3, 1, 0)

8. Let u = (−1, 3, 2) and v = (3, −4, 1). Find the angle θ between u and v.

9. Let u = (ux, uy, uz), v = (vx, vy, vz), and w = (wx, wy, wz). Also let c and k be scalars. Prove the following dot product properties.

1. u · v = v · u

2. u·(v + w) = u · v + u · w

3. k(u · v) = (ku) · v = u · (kv)

4. v·v = ||v||2

5. 0 · v = 0

image

Just use the definitions, for example,

image

10.Use the law of cosines (c2 = a2 + b2 – 2abcosθ), where a, b, and c are the lengths of the sides of a triangle and θ is the angle between sides a and b) to show

image

image

Consider Figure 1.9 and set c2 = ||uv||, a2 = ||u||2 and b2 = ||v||2, and use the dot product properties from the previous exercise.

11.Let n = (−2, 1). Decompose the vector g = (0, −9.8) into the sum of two orthogonal vectors, one parallel to n and the other orthogonal to n. Also, draw the vectors relative to a 2D coordinate system.

12.Let u = (−2, 1, 4) and v = (3, −4, 1). Find w = u × v, and show w · u = 0 and w · v = 0.

13.Let the following points define a triangle relative to some coordinate system: A = (0, 0, 0), B = (0, 1, 3), and C = (5, 1, 0). Find a vector orthogonal to this triangle.

image

Find two vectors on two of the triangle’s edges and use the cross product.

14.Prove that image

.

image

Start with image

and use the trigonometric identity image

then apply Equation 1.4.

15.Prove that ||u ×v|| gives the area of the parallelogram spanned by u and v; see Figure 1.21.

image

Figure 1.21. Parallelogram spanned by two 3D vectors u and v; the parallelogram has base || v || and height h

16.Give an example of 3D vectors u, v, and w such that image

. This shows the cross product is generally not associative.

image

Consider combinations of the simple vectors i = (1, 0, 0), j = (0, 1, 0), and k = (0, 0, 1).

17.Prove that the cross product of two nonzero parallel vectors results in the null vector; that is, u × ku = 0.

image

Just use the cross product definition.

18.Orthonormalize the set of vectors {(1, 0, 0), (1, 5, 0), (2, 1, −4)} using the Gram-Schmidt process.

19.Consider the following program and output. Make a conjecture of what each XMVector* function does; then look up each function in the DirectXMath documentation.

#include <windows.h> // for XMVerifyCPUSupport

#include <DirectXMath.h>

#include <DirectXPackedVector.h>

#include <iostream>

using namespace std;

using namespace DirectX;

using namespace DirectX::PackedVector;

// Overload the "<<" operators so that we can use cout to

// output XMVECTOR objects.

ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR v)

{

XMFLOAT4 dest;

XMStoreFloat4(&dest, v);

os << "(" << dest.x << ", " << dest.y << ", "

<< dest.z << ", " << dest.w << ")";

return os;

}

int main()

{

cout.setf(ios_base::boolalpha);

// Check support for SSE2 (Pentium4, AMD K8, and above).

if (!XMVerifyCPUSupport())

{

cout << "directx math not supported" << endl;

return 0;

}

XMVECTOR p = XMVectorSet(2.0f, 2.0f, 1.0f, 0.0f);

XMVECTOR q = XMVectorSet(2.0f, -0.5f, 0.5f, 0.1f);

XMVECTOR u = XMVectorSet(1.0f, 2.0f, 4.0f, 8.0f);

XMVECTOR v = XMVectorSet(-2.0f, 1.0f, -3.0f, 2.5f);

XMVECTOR w = XMVectorSet(0.0f, XM_PIDIV4, XM_PIDIV2, XM_PI);

cout << "XMVectorAbs(v) = " << XMVectorAbs(v) << endl;

cout << "XMVectorCos(w) = " << XMVectorCos(w) << endl;

cout << "XMVectorLog(u) = " << XMVectorLog(u) << endl;

cout << "XMVectorExp(p) = " << XMVectorExp(p) << endl;

cout << "XMVectorPow(u, p) = " << XMVectorPow(u, p) << endl;

cout << "XMVectorSqrt(u) = " << XMVectorSqrt(u) << endl;

cout << "XMVectorSwizzle(u, 2, 2, 1, 3) = "

<< XMVectorSwizzle(u, 2, 2, 1, 3) << endl;

cout << "XMVectorSwizzle(u, 2, 1, 0, 3) = "

<< XMVectorSwizzle(u, 2, 1, 0, 3) << endl;

cout << "XMVectorMultiply(u, v) = " << XMVectorMultiply(u, v) << endl;

cout << "XMVectorSaturate(q) = " << XMVectorSaturate(q) << endl;

cout << "XMVectorMin(p, v = " << XMVectorMin(p, v) << endl;

cout << "XMVectorMax(p, v) = " << XMVectorMax(p, v) << endl;

return 0;

}

image

Figure 1.22 Output for the above program.