Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Hands-On C++ Game Animation Programming

You're reading from   Hands-On C++ Game Animation Programming Learn modern animation techniques from theory to implementation with C++ and OpenGL

Arrow left icon
Product type Paperback
Published in Jun 2020
Publisher Packt
ISBN-13 9781800208087
Length 368 pages
Edition 1st Edition
Languages
Tools
Concepts
Arrow right icon
Author (1):
Arrow left icon
Gabor Szauer Gabor Szauer
Author Profile Icon Gabor Szauer
Gabor Szauer
Arrow right icon
View More author details
Toc

Table of Contents (17) Chapters Close

Preface 1. Chapter 1: Creating a Game Window 2. Chapter 2: Implementing Vectors FREE CHAPTER 3. Chapter 3: Implementing Matrices 4. Chapter 4: Implementing Quaternions 5. Chapter 5: Implementing Transforms 6. Chapter 6: Building an Abstract Renderer 7. Chapter 7: Exploring the glTF File Format 8. Chapter 8: Creating Curves, Frames, and Tracks 9. Chapter 9: Implementing Animation Clips 10. Chapter 10: Mesh Skinning 11. Chapter 11: Optimizing the Animation Pipeline 12. Chapter 12: Blending between Animations 13. Chapter 13: Implementing Inverse Kinematics 14. Chapter 14: Using Dual Quaternions for Skinning 15. Chapter 15: Rendering Instanced Crowds 16. Other Books You May Enjoy

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 2.5: A vector broken down into parallel and perpendicular components

Figure 2.5: A vector broken down into parallel and perpendicular components

The length of the hypotenuse of a right triangle can be found with the Pythagorean theorem, A2 + B2 = C2. This function extends to three dimensions by simply adding a Z component—X2 + Y2 + Z2 = length2.

You may have noticed a pattern here; the squared length of a vector equals the sum of its components. This could be expressed as a dot product—Length2(A) = dot(A, A):

Important note:

Finding the length of a vector involves a square root operation, which should be avoided when possible. When checking the length of a vector, the check can be done in squared space to avoid the square root. For example, if you wanted to check if the length of vector A is less than 5, that could be expressed as (dot(A, A) < 5 * 5).

  1. To implement the square length function, sum the result of squaring each component of the vector. Implement the lenSq function in vec3.cpp. Don't forget to add the function declaration to vec3.h:
    float lenSq(const vec3& v) {
        return v.x * v.x + v.y * v.y + v.z * v.z;
    }
  2. To implement the length function, take the square root of the result of the square length function. Take care not to call sqrtf with 0. Implement the lenSq function in vec3.cpp. Don't forget to add the function declaration to vec3.h:
    float len(const vec3 &v) {
        float lenSq = v.x * v.x + v.y * v.y + v.z * v.z;
        if (lenSq < VEC3_EPSILON) {
            return 0.0f;
        }
        return sqrtf(lenSq);
    }

    Important note:

    You can find the distance between two vectors by taking the length of the difference between them. For example, float distance = len(vec1 - vec2).

Normalizing vectors

A vector with a length of 1 is called a normal vector (or unit vector). Generally, unit vectors are used to represent a direction without a magnitude. The dot product of two unit vectors will always fall in the -1 to 1 range.

Aside from the 0 vector, any vector can be normalized by scaling the vector by the inverse of its length:

  1. Implement the normalize function in vec3.cpp. Don't forget to add the function declaration to vec3.h:
    void normalize(vec3 &v) {
        float lenSq = v.x * v.x + v.y * v.y + v.z * v.z;
        if (lenSq < VEC3_EPSILON) { return; }
        float invLen = 1.0f / sqrtf(lenSq);    
        v.x *= invLen;
        v.y *= invLen;
        v.z *= invLen;
    }
  2. Implement the normalized function in vec3.cpp. Don't forget to add the function declaration to vec3.h:
    vec3 normalized(const vec3 &v) {
        float lenSq = v.x * v.x + v.y * v.y + v.z * v.z;
        if (lenSq < VEC3_EPSILON) { return v; }
        float invLen = 1.0f / sqrtf(lenSq);
        return vec3(
            v.x * invLen,
            v.y * invLen,
            v.z * invLen
        );
    }

The normalize function takes a reference to a vector and normalizes it in place. The normalized function, on the other hand, takes a constant reference and does not modify the input vector. Instead, it returns a new vector.

The angle between vectors

If two vectors are of unit length, the angle between them is the cosine of their dot product:

If the two vectors are not normalized, the dot product needs to be divided by the product of the length of both vectors:

To find the actual angle, not just the cosine of it, we need to take the inverse of the cosine on both sides, which is the arccosine function:

Implement the angle function in vec3.cpp. Don't forget to add the function declaration to vec3.h:

float angle(const vec3 &l, const vec3 &r) {
    float sqMagL = l.x * l.x + l.y * l.y + l.z * l.z;
    float sqMagR = r.x * r.x + r.y * r.y + r.z * r.z;
    if (sqMagL<VEC3_EPSILON || sqMagR<VEC3_EPSILON) {
        return 0.0f;
    }
    float dot = l.x * r.x + l.y * r.y + l.z * r.z;
    float len = sqrtf(sqMagL) * sqrtf(sqMagR);
    return acosf(dot / len);
}

Important note:

The acosf function returns angles in radians. To convert radians to degrees, multiply by 57.2958f. To convert degrees to radians, multiply by 0.0174533f.

Vector projection and rejection

Projecting vector A onto vector B yields a new vector that has the length of A in the direction of B. A good way to visualize vector projection is to imagine that vector A is casting a shadow onto vector B, as shown:

Figure 2.6: Vector A casting a shadow onto vector B

Figure 2.6: Vector A casting a shadow onto vector B

To calculate the projection of A onto B (projB A), vector A must be broken down into parallel and perpendicular components with respect to vector B. The parallel component is the length of A in the direction of B—this is the projection. The perpendicular component is the parallel component subtracted from A—this is the rejection:

Figure 2.7: Vector projection and rejection showing parallel and perpendicular vectors

Figure 2.7: Vector projection and rejection showing parallel and perpendicular vectors

If the vector that is being projected onto (in this example, vector B) is a normal vector, then finding the length of A in the direction of B is a simple dot product between A and B. However, if neither input vector is normalized, the dot product needs to be divided by the length of vector B (the vector being projected onto).

Now that the parallel component of A with respect to B is known, vector B can be scaled by this component. Again, if B wasn't of unit length, the result will need to be divided by the length of vector B.

Rejection is the opposite of projection. To find the rejection of A onto B, subtract the projection of A onto B from vector A:

  1. Implement the project function in vec3.cpp. Don't forget to add the function declaration to vec3.h:
    vec3 project(const vec3 &a, const vec3 &b) {
        float magBSq = len(b);
        if (magBSq < VEC3_EPSILON) {
            return vec3();
        }
        float scale = dot(a, b) / magBSq;
        return b * scale;
    }
  2. Implement the reject function in vec3.cpp. Don't forget to declare this function in vec3.h:
    vec3 reject(const vec3 &a, const vec3 &b) {
        vec3 projection = project(a, b);
        return a - projection;
    }

Vector projection and rejection are generally used for gameplay programming. It is important that they are implemented in a robust vector library.

Vector reflection

Vector reflection can mean one of two things: a mirror-like reflection or a bounce-like reflection. The following figure shows the different types of reflections:

Figure 2.8: A comparison of the mirror and bounce reflections

Figure 2.8: A comparison of the mirror and bounce reflections

The bounce reflection is more useful and intuitive than the mirror reflection. To make a bounce projection work, project vector A onto vector B. This will yield a vector that points in the opposite direction to the reflection. Negate this projection and subtract it twice from vector A. The following figure demonstrates this:

Figure 2.9: Visualizing a bounce reflection

Figure 2.9: Visualizing a bounce reflection

Implement the reflect function in vec3.cpp. Don't forget to add the function declaration to vec3.h:

vec3 reflect(const vec3 &a, const vec3 &b) {
    float magBSq = len(b);
    if (magBSq < VEC3_EPSILON) {
        return vec3();
    }
    float scale = dot(a, b) / magBSq;
    vec3 proj2 = b * (scale * 2);
    return a - proj2;
}

Vector reflection is useful for physics and AI. We won't need to use reflection for animation, but it's good to have the function implemented in case it is needed.

Cross product

When given two input vectors, the cross product returns a third vector that is perpendicular to both input vectors. The length of the cross product equals the area of the parallelogram formed by the two vectors.

The following figure demonstrates what the cross product looks like visually. The input vectors don't have to be 90 degrees apart, but it's easier to visualize them this way:

Figure 2.10: Visualizing the cross product

Figure 2.10: Visualizing the cross product

Finding the cross product involves some matrix math, which will be covered in more depth in the next chapter. For now, you need to create a 3x3 matrix, with the top row being the result vector. The second and third rows should be filled in with the input vectors. The value of each component of the result vector is the minor of that element in the matrix.

What exactly is the minor of an element in a 3x3 matrix? It's the determinant of a smaller, 2x2 sub-matrix. Assuming you want to find the value of the first component, ignore the first row and column, which yields a smaller 2x2 sub-matrix. The following figure shows the smaller sub-matrix for each component:

Figure 2.11: The submatrix for each component

Figure 2.11: The submatrix for each component

To find the determinant of a 2x2 matrix, you need to cross multiply. Multiply the top-left and bottom-right elements, then subtract the product of the top-right and bottom-left elements. The following figure shows this for each element of the resulting vector:

Figure 2.12: The determinant of each component in the result vector

Figure 2.12: The determinant of each component in the result vector

Implement the cross product in vec3.cpp. Don't forget to add the function declaration to vec3.h:

vec3 cross(const vec3 &l, const vec3 &r) {
    return vec3(
        l.y * r.z - l.z * r.y,
        l.z * r.x - l.x * r.z,
        l.x * r.y - l.y * r.x
    );
}

The dot product has a relationship to the cosine of the angle between two vectors and the cross product has a relationship to the sine of the angle between the two vectors. The length of the cross product between the two vectors is the product of both vectors, lengths, scaled by the sine of the angle between them:

In the next section, you will learn how to interpolate between vectors using three different techniques.

You have been reading a chapter from
Hands-On C++ Game Animation Programming
Published in: Jun 2020
Publisher: Packt
ISBN-13: 9781800208087
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image