Chapter 2: Implementing Vectors
In this chapter, you will learn the basics of vector math. Much of what you will code throughout the rest of this book relies on having a strong understanding of vectors. Vectors will be used to represent displacement and direction.
By the end of this chapter, you will have implemented a robust vector library and will be able to perform a variety of vector operations, including component-wise and non-component-wise operations.
We will cover the following topics in this chapter:
- Introducing vectors
- Creating a vector
- Understanding component-wise operations
- Understanding non-component-wise operations
- Interpolating vectors
- Comparing vectors
- Exploring more vectors
Important information:
In this chapter, you will learn how to implement vectors in an intuitive, visual way that relies on code more than math formulas. If you are interested in math formulas or want some interactive examples to try out, go to https://gabormakesgames...
Introducing vectors
What is a vector? A vector is an n-tuple of numbers. It represents a displacement measured as a magnitude and a direction. Each element of a vector is usually expressed as a subscript, such as (V0, V1, V2, … VN). In the context of games, vectors usually have two, three, or four components.
For example, a three-dimensional vector measures displacement on three unique axes: x, y, and z. Elements of vectors are often subscripted with the axis they represent, rather than an index. (VX, VY, VZ) and (V0, V1, V2) are used interchangeably.
When visualizing vectors, they are often drawn as arrows. The position of the base of an arrow does not matter because vectors measure displacement, not a position. The end of the arrow follows the displacement of the arrow on each axis.
For example, all of the arrows in the following figure represent the same vector:
Figure 2.1: Vector (2, 5) drawn in multiple locations
Each arrow has the same...
Creating a vector
Vectors will be implemented as structures, not classes. The vector struct will contain an anonymous union that allows the vector's components to be accessed as an array or as individual elements.
To declare the vec3
structure and the function headers, create a new file, vec3.h
. Declare the new vec3
structure in this file. The vec3
struct needs three constructors—a default constructor, one that takes each component as an element, and one that takes a pointer to a float array:
#ifndef _H_VEC3_ #define _H_VEC3_ struct vec3 { union { struct { float x; float y; float z; }; &...
Understanding component-wise operations
Several vector operations are just component-wise operations. A component-wise operation is one that you perform on each component of a vector or on like components of two vectors. Like components are components that have the same subscript. The component-wise operations that you will implement are as follows:
- Vector addition
- Vector subtraction
- Vector scaling
- Multiplying vectors
- Dot product
Let's look at each of these in more detail.
Vector addition
Adding two vectors together yields a third vector, which has the combined displacement of both input vectors. Vector addition is a component-wise operation; to perform it, you need to add like components.
To visualize the addition of two vectors, draw the base of the second vector at the tip of the first vector. Next, draw an arrow from the base of the first vector to the tip of the second vector. This arrow represents the vector that is the result of the...
Understanding non-component-wise operations
Not all vector operations are component-wise; some operations require more math. In this section, you are going to learn how to implement common vector operations that are not component-based. These operations are as follows:
- How to find the length of a vector
- What a normal vector is
- How to normalize a vector
- How to find the angle between two vectors
- How to project vectors and what rejection is
- How to reflect vectors
- What the cross product is and how to implement it
Let's take a look at each one in more detail.
Vector length
Vectors represent a direction and a magnitude; the magnitude of a vector is its length. The formula for finding the length of a vector comes from trigonometry. In the following figure, a two-dimensional vector is broken down into parallel and perpendicular components. Notice how this forms a right triangle, with the vector being the hypotenuse:
Figure...
Interpolating vectors
Two vectors can be interpolated linearly by scaling the difference between the two vectors and adding the result back to the original vector. This linear interpolation is often abbreviated to lerp
. The amount to lerp
by is a normalized value between 0 and 1; this normalized value is often represented by the letter t. The following figure shows lerp
between two vectors with several values for t:
Figure 2.13: Linear interpolation
When t = 0, the interpolated vector is the same as the starting vector. When t = 1, the interpolated vector is the same as the end vector.
Implement the lerp
function in vec3.cpp
. Don't forget to add the function declaration to vec3.h
:
vec3 lerp(const vec3 &s, const vec3 &e, float t) { return vec3( s.x + (e.x - s.x) * t, s.y + (e.y - s.y) * t, ...
Comparing vectors
The last operation that needs to be implemented is vector comparison. Comparison is a component-wise operation; each element must be compared using an epsilon. Another way to measure whether two vectors are the same is to subtract them. If they were equal, subtracting them would yield a vector with no length.
Overload the ==
and !=
operators in vec3.cpp
. Don't forget to add the function declarations to vec3.h
:
bool operator==(const vec3 &l, const vec3 &r) { vec3 diff(l - r); return lenSq(diff) < VEC3_EPSILON; } bool operator!=(const vec3 &l, const vec3 &r) { return !(l == r); }
Important note:
Finding the right epsilon value to use for comparison operations is difficult. In this chapter, you declared 0.000001f
as the epsilon. This value is the result of some trial and error. To learn more about comparing floating point values, check out https://bitbashing.io...
Exploring more vectors
At some point later on in this book, you will need to utilize two- and four-component vectors as well. The two- and four-component vectors don't need any mathematical functions defined as they will be used exclusively as containers used to pass data to the GPU.
Unlike the three-component vector you have implemented, the two- and four-component vectors need to exist as both integer and floating point vectors. To avoid duplicating code, both structures will be implemented using a template:
- Create a new file,
vec2.h
, and add the definition of thevec2
struct. All thevec2
constructors are inline; there is no need for acpp
file. TheTVec2
struct is templated andtypedef
is used to declarevec2
andivec2
:template<typename T> struct TVec2 { union { struct { T x; ...
Summary
In this chapter, you have learned the vector math required to create a robust animation system. Animation is a math-heavy topic; the skills you have learned in this chapter are required to complete the rest of this book. You implemented all the common vector operations for three-component vectors. The vec2
and vec4
structures don't have a full implementation like vec3
, but they are only used to send data to the GPU.
In the next chapter, you will continue to learn more about game-related math by learning about matrices.