Skip to content

File vector.hpp

File List > vector.hpp

Go to the documentation of this file

#pragma once

#include <concepts>
#include <type_traits>

template <typename T, typename U>
concept Number = requires(T obj, U thi) {
    requires std::is_arithmetic_v<T> || requires {
        obj + thi;
        obj - thi;
        obj * thi;
        obj / thi;
    };
};

template <typename Numeric, typename Derived>
class Vector3D;

template <typename T, typename Numeric>
concept VectorCompatible = requires(T vec) {
    { vec.X_coord } -> std::same_as<Numeric&>;
    { vec.Y_coord } -> std::same_as<Numeric&>;
    { vec.Z_coord } -> std::same_as<Numeric&>;
};

template <typename ExplicitType, typename Default>
using Either = std::conditional_t<std::is_same_v<ExplicitType, void>, Default, ExplicitType>;

template <typename Numeric, typename Derived = void>
class Vector3D {
public:
    using NumericType = Numeric;
    using ClassType = Either<Derived, Vector3D<NumericType>>;
    constexpr static NumericType rad2DegFactor = NumericType(57.2957795131);

public:
    constexpr static Vector3D<Numeric> Up = Vector3D<Numeric>(0, 0, 1);
    constexpr static Vector3D<Numeric> Down = Vector3D<Numeric>(0, 0, -1);
    constexpr static Vector3D<Numeric> Left = Vector3D<Numeric>(-1, 0, 0);
    constexpr static Vector3D<Numeric> Right = Vector3D<Numeric>(1, 0, 0);
    constexpr static Vector3D<Numeric> Forward = Vector3D<Numeric>(0, 1, 0);
    constexpr static Vector3D<Numeric> Backward = Vector3D<Numeric>(0, -1, 0);

    NumericType X_coord = 0; 
    NumericType Y_coord = 0; 
    NumericType Z_coord = 0; 

    constexpr explicit Vector3D(): X_coord(0), Y_coord(0), Z_coord(0) {}

    constexpr Vector3D(NumericType initial_X_coord, NumericType initial_Y_coord, NumericType initial_Z_coord):
        X_coord(initial_X_coord), Y_coord(initial_Y_coord), Z_coord(initial_Z_coord) {}

    constexpr ClassType FromPolarDegrees(NumericType Pitch, NumericType Yaw, NumericType Radius) {
        return Vector3D(Pitch, Yaw, Radius);
    }

    friend std::ostream& operator<<(std::ostream& os, const ClassType& obj) {
        os << "Vec<" << obj.X_coord << ", " << obj.Y_coord << ", " << obj.Z_coord << ">";
        return os;
    }

    operator bool() const { return X_coord || Y_coord || Z_coord; }

    ClassType operator+(const VectorCompatible<NumericType> auto& other) const {
        return ClassType(X_coord + other.X_coord, Y_coord + other.Y_coord, Z_coord + other.Z_coord);
    }

    ClassType operator-(const VectorCompatible<NumericType> auto& other) const {
        return ClassType(X_coord - other.X_coord, Y_coord - other.Y_coord, Z_coord - other.Z_coord);
    }

    constexpr ClassType operator*(const NumericType& scalar) const {
        return ClassType(X_coord * scalar, Y_coord * scalar, Z_coord * scalar);
    }

    ClassType operator/(NumericType scalar) const {
        return ClassType(X_coord / scalar, Y_coord / scalar, Z_coord / scalar);
    }

    NumericType dot(const VectorCompatible<NumericType> auto& other) const {
        return X_coord * other.X_coord + Y_coord * other.Y_coord + Z_coord * other.Z_coord;
    }

    ClassType cross(const VectorCompatible<NumericType> auto& other) const {
        return ClassType(
            Y_coord * other.Z_coord - Z_coord * other.Y_coord,
            Z_coord * other.X_coord - X_coord * other.Z_coord,
            X_coord * other.Y_coord - Y_coord * other.X_coord
        );
    }

    NumericType magnitude() const {
        auto xs = X_coord * X_coord;
        auto ys = Y_coord * Y_coord;
        auto zs = Z_coord * Z_coord;
        return sqrt(xs + ys + zs);
    }

    NumericType magnitudeXY() const { return sqrt(X_coord * X_coord + Y_coord * Y_coord); }

    NumericType magnitudeXZ() const { return sqrt(X_coord * X_coord + Z_coord * Z_coord); }

    NumericType magnitudeYZ() const { return sqrt(Y_coord * Y_coord + Z_coord * Z_coord); }

    ClassType normalize() const {
        NumericType mag = magnitude();
        if (mag != 0) {
            return ClassType(X_coord / mag, Y_coord / mag, Z_coord / mag);
        }
        return ClassType(X_coord, Y_coord, Z_coord);
    }

    NumericType pitch() const { return atan2(Z_coord, magnitudeXY()) * rad2DegFactor; }

    NumericType yaw() const { return atan2(X_coord, Y_coord) * rad2DegFactor; }

    NumericType angleTo(const VectorCompatible<NumericType> auto& other) const {
        if (!(*this && other)) {
            return NumericType(0);
        }
        auto normalThis = normalize();
        auto normalOther = other.normalize(); // Need a way to efficiently do this without precision loss.  Need to
                                              // change types on the fly?

        auto dotted = normalThis.dot(normalOther);
        return acos(dotted) * rad2DegFactor;
    }
};