text: #pragma once
  
  //==============================================================================================
  // Originally written in 2016 by Peter Shirley <ptrshrl@gmail.com>
  //
  // To the extent possible under law, the author(s) have dedicated all copyright and related and
  // neighboring rights to this software to the public domain worldwide. This software is
  // distributed without any warranty.
  //
  // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication
  // along with this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
  //==============================================================================================
  
  #include <cmath>
  #include <iostream>
  //#include "maths.hpp"
  
  using std::sqrt;
  typedef float real_t;
  
  class vec3 {
  public:
      union {
          real_t e[3];
      struct {
          real_t x;
          real_t y;
          real_t z;
      };
      struct {
          real_t r;
          real_t g;
          real_t b;
      };
      };
  
  public:
      vec3() : e{ 0,0,0 } {}
      vec3(real_t e0, real_t e1, real_t e2) : e{ e0, e1, e2 } {}
  
  
      vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); }
      real_t operator[](int i) const { return e[i]; }
      real_t& 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 real_t t) {
          e[0] *= t;
          e[1] *= t;
          e[2] *= t;
          return *this;
      }
  
      vec3& operator/=(const real_t t) {
          return *this *= 1 / t;
      }
  
      constexpr double lengthSquared() const {
          return e[0] * e[0] + e[1] * e[1] + e[2] * e[2];
      }
  
      double length() const {
          return sqrt(lengthSquared());
      }
  
      vec3 unit() {
          double len = length();
          return vec3(x / len, y / len, z / len);
      }
  
  
  };
  
  
  // Type aliases for vec3
  using point3 = vec3;   // 3D point
  using rtcolor = vec3;    // RGB color
  
  
  // 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/(const vec3 &v, const 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]);
  }
  
  
  // Utility Functions
  inline double random_double() {
      return rand() / (RAND_MAX + 1.0);
  }
  
  inline double random_double_range(double min, double max) {
      // Returns a random real in [min,max).
      return min + (max - min) * random_double();
  }
  
  inline int random_int(int low, int high) {
      // Returns a random integer in [min,max].
      return static_cast<int>(random_double_range(low, high + 1));
  }
  
  
  inline vec3 random_vec3() {
      return vec3(random_double(), random_double(), random_double());
  }
  
  inline vec3 random_vec3_range(real_t min, real_t max) {
      return vec3(random_double_range(min, max), random_double_range(min, max), random_double_range(min, max));
  }
  
  
  vec3 random_in_unit_disk() {
      while (true) {
          auto p = vec3(random_double_range(-1, 1), random_double_range(-1, 1), 0);
          if (p.lengthSquared() >= 1) continue;
          return p;
      }
  }
  
  vec3 random_unit_vector() {
      auto a = random_double_range(0, 2 * PI);
      auto z = random_double_range(-1, 1);
      auto r = sqrt(1 - z * z);
      return vec3(r * cos(a), r * sin(a), z);
  }
  
  vec3 random_in_unit_sphere() {
      while (true) {
          //auto p = vec3::random(-1, 1);
          auto p = random_vec3_range(-1, 1);
          if (p.lengthSquared() >= 1) continue;
          return p;
      }
  }
  
  vec3 random_in_hemisphere(const vec3& normal) {
      vec3 in_unit_sphere = random_in_unit_sphere();
      if (dot(in_unit_sphere, normal) > 0.0) // In the same hemisphere as the normal
          return in_unit_sphere;
      else
          return -in_unit_sphere;
  }
  
  vec3 reflect(const vec3& v, const vec3& n) {
      return v - 2 * dot(v, n) * n;
  }
  
  vec3 refract(const vec3& uv, const vec3& n, double etai_over_etat) {
      auto cos_theta = fmin(dot(-uv, n), 1.0);
      vec3 r_out_parallel = etai_over_etat * (uv + cos_theta * n);
      vec3 r_out_perp = -sqrt(1.0 - r_out_parallel.lengthSquared()) * n;
      return r_out_parallel + r_out_perp;
  }
  