Vec3: A Simple Vector Class in C++

This is really the second post of three updating an earlier post on how to write some simple FPS-type controls for OpenGL – last post we looked at a FpsManager class so we could get frame timings to implement framerate independent movement. This time, we’re looking at a simple Vec3 class which stores 3D coordinates and helps to perform some operations on them. This is going to form part of the Camera class in the final part, but I thought I’d put it here on its own so it can be re-used.

There’s probably about ten million different vector classes out there, but I really wanted to write my own so I understood exactly how it worked, and I’ve commented it pretty heavily in the process – so hopefully if you’re looking for a vector class you might be able to look at this one, see how it works and what it does, and take and modify it for your own work.

Before skipping to the code, let’s just take a quick look at what it does – it packages up 3 values called x, y and z, and allows us to easily manipulate them. Quite what you store in these values is up to you, it could be a vertex position, or a direction vector, or a RGB colour – anything that has three values, really.

If we were dealing with two vertexes, we can do stuff like this:

    // Define two initial vectors
    Vec3<double> vector1(1, 2, 3); // A coordinate +1 on the X axis (horiz), +2 on the Y axis (vert), and +3 on the Z axis (depth)
    Vec3<double> vector2(4, 5, 6); // A coordinate +4 on the X axis (horiz), +5 on the Y axis (vert), and +6 on the Z axis (depth)
 
    // Create a new vector which is the sum of these two vectors added together
    Vec3<double>result = vector1 + vector2;
    result.display();           // (5, 7, 9)
 
    // Add the first vector to our result vector
    result += vector1;
    result.display();           // 6, 9, 12
 
    // Subtract the second vector from our result vector
    result -= vector2;
    result.display();           // 2, 4, 6
 
    // Divide our result vector by the scalar value 2
    result /= 2;
    result.display();           // 1, 2, 3
 
    // Multiply both vectors together and assign to our result vector (this is a dot-product
    // operation, but we have specific dot product functions we can use, too)
    result = vector1 * vector2;
    result.display();           // 4, 10, 18
 
    // Multiply our result vector by 2
    result *= 2;
    result.display();           // 8, 20, 36
 
    // Normalise our result vector so that all values fall within the range -1 to +1
    result.normalise();
    result.display();           // 0.190693, 0.476731, 0.858116
 
     // Calculate the distance between two points in 3D space
    double distance = Vec3<double>::getDistance(vector1, vector2);
    cout << "Distance between points: " << distance << endl; // 5.19615
 
    // Dot products only work on normalised values (i.e. each x/y/z value in the vector must be in the range -1 to +1)
    // So remember to normalise your vectors before computing the dot product!
    vector1.normalise();
    vector2.normalise();
    double dotProduct = Vec3<double>::dotProduct(vector1, vector2);
    cout << "Dot product: " << dotProduct << endl;           // 0.974632
 
    // Define some vectors pointing up, down, left, and right
    Vec3<double> up(   0,  1, 0);
    Vec3<double> down( 0, -1, 0);
    Vec3<double> left(-1,  0, 0);
    Vec3<double> right(1,  0, 0);
 
    // ------------ Dot Product Tests ------------
 
    // The dot product of two vectors pointing the same direction is 1
    dotProduct = Vec3<double>::dotProduct(up, up);
    cout << "Dot product of up and up: " << dotProduct << endl;
 
    // The dot product of two vectors which are perpendicular to each other is is 0
    dotProduct = Vec3<double>::dotProduct(up, right);
    cout << "Dot product of up and right: " << dotProduct << endl;
 
    // The dot product of two vectors pointing in opposite directions is -1
    dotProduct = Vec3<double>::dotProduct(up, down);
    cout << "Dot product of up and down: " << dotProduct << endl;
 
    // ------------ Cross Product Tests ------------
 
    // The cross product of a vector is the vector which is perpendicular to the plane made
    // by the two vectors specified. Whether it points "up" or "down" depends on the
    // handedness of the coordinate system and/or the order of vectors provided.
 
    // Test 1
    Vec3<double> crossProduct = Vec3<double>::crossProduct(up, right);
 
    // x = 0, y = 0, z = -1 (i.e. the vector perpendicular to up and right points INTO the screen)
    crossProduct.display(); 
 
    // Test 2
    crossProduct = Vec3<double>::crossProduct(right, up);
 
    // x = 0, y = 0, z = 1 (i.e. the vector perpendicular to right and up points OUT from screen)
    crossProduct.display();

Seems pretty easy to work with to me…

Here’s the code for the Vec3 class itself as a templatised, header-only C++ .hpp file – just include the Vec3.hpp class and you’re good to go:

#ifndef VEC3_HPP
#define VEC3_HPP
 
#include <iostream>
 
template <class T> class Vec3
{
    private:
        // A Vec3 simply has three properties called x, y and z
        T x, y, z;
 
    public:
        // ------------ Constructors ------------
 
        // Default constructor
        Vec3() { x = y = z = 0; };
 
        // Three parameter constructor
        Vec3(T xValue, T yValue, T zValue)
        {
            x = xValue;
            y = yValue;
            z = zValue;
        }
 
        // ------------ Getters and setters ------------
 
        void set(const T &xValue, const T &yValue, const T &zValue)
        {
            x = xValue;
            y = yValue;
            z = zValue;
        }
 
        T getX() const { return x; }
        T getY() const { return y; }
        T getZ() const { return z; }
 
        void setX(const T &xValue) { x = xValue; }
        void setY(const T &yValue) { y = yValue; }
        void setZ(const T &zValue) { z = zValue; }
 
        // ------------ Helper methods ------------
 
        // Method to reset a vector to zero
        void zero()
        {
            x = y = z = 0;
        }
 
        // Method to normalise a vector
        void normalise()
        {
            // Calculate the magnitude of our vector
            T magnitude = sqrt((x * x) + (y * y) + (z * z));
 
            // As long as the magnitude isn't zero, divide each element by the magnitude
            // to get the normalised value between -1 and +1
            if (magnitude != 0)
            {
                x /= magnitude;
                y /= magnitude;
                z /= magnitude;
            }
        }
 
        // Static method to calculate and return the scalar dot product of two vectors
        //
        // Note: The dot product of two vectors tell us things about the angle between
        // the vectors. That is, it tells us if they are pointing in the same direction
        // (i.e. are they parallel? If so, the dot product will be 1), or if they're
        // perpendicular (i.e. at 90 degrees to each other) the dot product will be 0,
        // or if they're pointing in opposite directions then the dot product will be -1.
        //
        // Usage example: double foo = Vec3<double>::dotProduct(vectorA, vectorB);
        static T dotProduct(const Vec3 &vec1, const Vec3 &vec2)
        {
            return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
        }
 
        // Non-static method to calculate and return the scalar dot product of this vector and another vector
        //
        // Usage example: double foo = vectorA.dotProduct(vectorB);
        T dotProduct(const Vec3 &vec) const
        {
            return x * vec.x + y * vec.y + z * vec.z;
        }
 
        // Static method to calculate and return a vector which is the cross product of two vectors
        //
        // Note: The cross product is simply a vector which is perpendicular to the plane formed by
        // the first two vectors. Think of a desk like the one your laptop or keyboard is sitting on.
        // If you put one pencil pointing directly away from you, and then another pencil pointing to the
        // right so they form a "L" shape, the vector perpendicular to the plane made by these two pencils
        // points directly upwards.
        //
        // Whether the vector is perpendicularly pointing "up" or "down" depends on the "handedness" of the
        // coordinate system that you're using.
        //
        // Further reading: http://en.wikipedia.org/wiki/Cross_product
        //
        // Usage example: Vec3<double> crossVect = Vec3<double>::crossProduct(vectorA, vectorB);
        static Vec3 crossProduct(const Vec3 &vec1, const Vec3 &vec2)
        {
            return Vec3(vec1.y * vec2.z - vec1.z * vec2.y, vec1.z * vec2.x - vec1.x * vec2.z, vec1.x * vec2.y - vec1.y * vec2.x);
        }
 
        // Easy adders
        void addX(T value) { x += value; }
        void addY(T value) { y += value; }
        void addZ(T value) { z += value; }
 
        // Method to return the distance between two vectors in 3D space
        //
        // Note: This is accurate, but not especially fast - depending on your needs you might
        // like to use the Manhattan Distance instead: http://en.wikipedia.org/wiki/Taxicab_geometry
        // There's a good discussion of it here: http://stackoverflow.com/questions/3693514/very-fast-3d-distance-check
        // The gist is, to find if we're within a given distance between two vectors you can use:
        //
        // bool within3DManhattanDistance(Vec3 c1, Vec3 c2, float distance)
        // {
        //      float dx = abs(c2.x - c1.x);
        //      if (dx > distance) return false; // too far in x direction
        //
        //      float dy = abs(c2.y - c1.y);
        //      if (dy > distance) return false; // too far in y direction
        //
        //      float dz = abs(c2.z - c1.z);
        //      if (dz > distance) return false; // too far in z direction
        //
        //      return true; // we're within the cube
        // }
        //
        // Or to just calculate the straight Manhattan distance you could use:
        //
        // float getManhattanDistance(Vec3 c1, Vec3 c2)
        // {
        //      float dx = abs(c2.x - c1.x);
        //      float dy = abs(c2.y - c1.y);
        //      float dz = abs(c2.z - c1.z);
        //      return dx+dy+dz;
        // }
        //
        static T getDistance(const Vec3 &v1, const Vec3 &v2)
        {
            T dx = v2.x - v1.x;
            T dy = v2.y - v1.y;
            T dz = v2.z - v1.z;
 
            return sqrt(dx * dx + dy * dy + dz * dz);
        }
 
        // Method to display the vector so you can easily check the values
        void display()
        {
            std::cout << "X: " << x << "\t Y: " << y << "\t Z: " << z << std::endl;
        }
 
        // ------------ Overloaded operators ------------
 
        // Overloaded addition operator to add Vec3s together
        Vec3 operator+(const Vec3 &vector) const
        {
            return Vec3<T>(x + vector.x, y + vector.y, z + vector.z);
        }
 
        // Overloaded add and asssign operator to add Vec3s together
        void operator+=(const Vec3 &vector)
        {
            x += vector.x;
            y += vector.y;
            z += vector.z;
        }
 
        // Overloaded subtraction operator to subtract a Vec3 from another Vec3
        Vec3 operator-(const Vec3 &vector) const
        {
            return Vec3<T>(x - vector.x, y - vector.y, z - vector.z);
        }
 
        // Overloaded subtract and asssign operator to subtract a Vec3 from another Vec3
        void operator-=(const Vec3 &vector)
        {
            x -= vector.x;
            y -= vector.y;
            z -= vector.z;
        }
 
        // Overloaded multiplication operator to multiply two Vec3s together
        Vec3 operator*(const Vec3 &vector) const
        {
            return Vec3<T>(x * vector.x, y * vector.y, z * vector.z);
        }
 
        // Overloaded multiply operator to multiply a vector by a scalar
        Vec3 operator*(const T &value) const
        {
            return Vec3<T>(x * value, y * value, z * value);
        }
 
        // Overloaded multiply and assign operator to multiply a vector by a scalar
        void operator*=(const T &value)
        {
            x *= value;
            y *= value;
            z *= value;
        }
 
        // Overloaded multiply operator to multiply a vector by a scalar
        Vec3 operator/(const T &value) const
        {
            return Vec3<T>(x / value, y / value, z / value);
        }
 
        // Overloaded multiply and assign operator to multiply a vector by a scalar
        void operator/=(const T &value)
        {
            x /= value;
            y /= value;
            z /= value;
        }
};
 
#endif

Next post, we’re going to be implementing a FPS-style camera class using this Vec3 class to move around a 3D scene – and because of all our hard work in getting this vector class together, it’s going to make moving the camera around a doddle =D

Cheers!

One thought on “Vec3: A Simple Vector Class in C++”

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.