The vector 3 class

The vector 3 class

Most graphics programs have classes for storing vectors and colors, typically 4D with a homogeneous coordinate for geometry and an alpha channel for transparency. However, for simplicity, we will use a single vec3 class for all purposes, including colors, locations, directions, and offsets. This approach may allow for some invalid operations, such as adding a color to a location, but we prefer the simplicity of having one class. To distinguish between intended uses, we declare two aliases for vec3: point3 and color. These aliases serve to clarify the purpose of the vector and do not generate warnings if used interchangeably.

Variables and Methods

#ifndef VEC3_H
#define VEC3_H

#include <cmath>
#include <iostream>

using std::sqrt;

class vec3 {
    public:
        vec3() : e{0,0,0} {}
        vec3(double e0, double e1, double e2) : e{e0, e1, e2} {}

        double x() const { return e[0]; }
        double y() const { return e[1]; }
        double z() const { return e[2]; }

        vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); }
        double operator[](int i) const { return e[i]; }
        double& operator[](int i) { return e[i]; }

        vec3& operator+=(const vec3 &v) {
            e[0] += v.e[0];
            e[1] += v.e[1];
            e[2] += v.e[2];
            return *this;
        }

        vec3& operator*=(const double t) {
            e[0] *= t;
            e[1] *= t;
            e[2] *= t;
            return *this;
        }

        vec3& operator/=(const double t) {
            return *this *= 1/t;
        }

        double length() const {
            return sqrt(length_squared());
        }

        double length_squared() const {
            return e[0]*e[0] + e[1]*e[1] + e[2]*e[2];
        }

    public:
        double e[3];
};

// Type aliases for vec3
using point3 = vec3;   // 3D point
using color = vec3;    // RGB color

#endif

The above is a basic implementation of the vector class. It has some basic constructors and some basic vector functions

Operator overloading

One of the main concepts that we learned in C++ was operator overloading. This can be used to perform quicker and more efficient operations with much fewer lines of code.

The below code contains some basic vector overloaded functions. This can be used to add subtract, multiply etc.

// vec3 Utility Functions

inline std::ostream& operator<<(std::ostream &out, const vec3 &v) {
    return out << v.e[0] << ' ' << v.e[1] << ' ' << v.e[2];
}

inline vec3 operator+(const vec3 &u, const vec3 &v) {
    return vec3(u.e[0] + v.e[0], u.e[1] + v.e[1], u.e[2] + v.e[2]);
}

inline vec3 operator-(const vec3 &u, const vec3 &v) {
    return vec3(u.e[0] - v.e[0], u.e[1] - v.e[1], u.e[2] - v.e[2]);
}

inline vec3 operator*(const vec3 &u, const vec3 &v) {
    return vec3(u.e[0] * v.e[0], u.e[1] * v.e[1], u.e[2] * v.e[2]);
}

inline vec3 operator*(double t, const vec3 &v) {
    return vec3(t*v.e[0], t*v.e[1], t*v.e[2]);
}

inline vec3 operator*(const vec3 &v, double t) {
    return t * v;
}

inline vec3 operator/(vec3 v, double t) {
    return (1/t) * v;
}

inline double dot(const vec3 &u, const vec3 &v) {
    return u.e[0] * v.e[0]
         + u.e[1] * v.e[1]
         + u.e[2] * v.e[2];
}

inline vec3 cross(const vec3 &u, const vec3 &v) {
    return vec3(u.e[1] * v.e[2] - u.e[2] * v.e[1],
                u.e[2] * v.e[0] - u.e[0] * v.e[2],
                u.e[0] * v.e[1] - u.e[1] * v.e[0]);
}

inline vec3 unit_vector(vec3 v) {
    return v / v.length();
}

Color Functions

We use the functions in vec3.h to implement the color functions.

#ifndef COLOR_H
#define COLOR_H

#include "vec3.h"

#include <iostream>

void write_color(std::ostream &out, color pixel_color) {
    // Write the translated [0,255] value of each color component.
    out << static_cast<int>(255.999 * pixel_color.x()) << ' '
        << static_cast<int>(255.999 * pixel_color.y()) << ' '
        << static_cast<int>(255.999 * pixel_color.z()) << '\n';
}

#endif

The write_color function in the color.h header file is used to write a color value to an output stream, such as std::cout or a file. The function takes two parameters: an output stream out, which specifies where to write the color, and a color value pixel_color, which is the color to be written.

The function first converts the x, y, and z components of the pixel_color value from the range [0,1] to the range [0,255] by multiplying each component by 255.999 and casting the result to an integer using static_cast<int>(). It then writes these three integers to the output stream separated by spaces, followed by a newline character \n.

To use this function, you would include the color.h header file in your source code and call the write_color function, passing in an output stream and a color value. For example:

#include <iostream>
#include "color.h"

int main() {
    color pixel_color(0.5, 0.7, 1.0); // Create a color with RGB values of (0.5, 0.7, 1.0)
    write_color(std::cout, pixel_color); // Write the color to the console
    return 0;
}

The output would be:

127 178 255

Finally in the main program we include the header files an the modified way to display color

#include "color.h"
#include "vec3.h"
..
..
..
for (int i = 0; i < image_width; ++i) {
/*
            auto r = double(i) / (image_width-1);
            auto g = double(j) / (image_height-1);
            auto b = 0.25;

            int ir = static_cast<int>(255.999 * r);
            int ig = static_cast<int>(255.999 * g);
            int ib = static_cast<int>(255.999 * b);

            std::cout << ir << ' ' << ig << ' ' << ib << '\n';
        }

the above lines of code has become: 
*/
color pixel_color(double(i)/(image_width-1), double(j)/(image_height-1), 0.25);
            write_color(std::cout, pixel_color);


..
..
..