/*
 * Copyright (c) 2019 nicemath contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */
#pragma once

#include <cmath>
#include <stdint.h>

/**
 * \mainpage Reference Manual
 * 
 * \section Introduction
 *
 * nicemath is a compact single-header C++ library that provides data types and
 * routines for basic linear algebra operations often encountered in computer
 * graphics and game development.
 *
 * To use the library, simply place
 * <a href = "https://github.com/nicebyte/nicemath/blob/master/nicemath.h">nicemath.h</a> 
 * anywhere within your C++ project's include path. 
 *
 * Please see the <a href="namespacenm.html">list of `nm` namespace members</a> 
 * for detailed documentation.
 * \section Intended Usage
 *
 * This is not a generic linear algebra library. It is mostly intended to
 * help users deal with 3D and 2D affine transforms, quaternions and basic
 * vector operations. 
 * If you require e.g. support for arbitrarily large, sparse matrices,
 * you should look elsewhere.
 *
 * \section Example
 *
 * ```
 * #include "nicemath.h"
 * 
 * void vec_demo(const nm::float4 &a, const nm::float4 &b) {
 *   // basic operations.
 *   const nm::float4 sum = a + b,
 *                    dif = a - b,
 *                    prd = a * b, // elementwise product
 *                    div = a / b; // elementwise division
 *   
 *   // dot product
 *   const float dot = nm::dot(a, b);
 *
 *   // cross product
 *   const nm::float3 cross = nm::cross(a.xyz(), b.xyz());
 *
 *   // arbitrary vector swizzles are supported
 *   const nm::float3 a_wyx = a.wyx(),
 *                    b_zyy = b.zyy();
 *
 *   // vector expressions may be used in c++ constexprs.
 *   constexpr float scale = 2.0f;
 *   constexpr nm::float4 scaled_vector =
 *       scale * nm::float4 { 1.0f, .0f, .0f, .0f};
 * }
 *
 * void mat_demo(nm::float4x4 a, nm::float4x4 b) {
 *   // basic operations
 *   const nm::float4x4 prd = a * b;
 *   
 *   // matrix-vector mul
 *   const nm::float4 v = a * nm::float4 { 1.f, .0f, .0f, .0f };
 *
 *   // inverse
 *   const nm::float4x4 inv = nm::inverse(a);
 *
 *   // determinant
 *   const float det = nm::det(a);
 *
 *   // matrix expressions can be used in c++ contstexprs as well
 *   constexpr nm::float4x4 id = nm::float4x4::identity();
 *   constexpr nm::float4x4 scaled5x = 5.f * id;
 *   constexpr nm::float4x4 m = scaled5x * nm::float4x4::from_rows(nm::float4 { 2.f, 3.f, .0f, .0f },
 *                                                                 nm::float4 { 9.f, 1.f, 2.f, .0f },
 *                                                                 nm::float4 { 8.f, 4.f, .7f, .0f },
 *                                                                 nm::float4 { 6.f, 7.f, 1.f, 1.f });
 * }
 * ```
 */

/**
 * Namespace for all nicemath types and routines.
 */
namespace nm {

constexpr float PI = 3.1415926f;

/**
 * Generic N-dimensional vector. Do not use this class directly.
 */
template <class S, unsigned N>
struct vec;

namespace detail {

constexpr int cestrlen(const char *p) {
  int r = 0;
  while(*(p++)) ++r;
  return r;
}
#define l2i(c) ((c-'w'+3)%4)
#define l2m(c) (uint16_t)(1u << l2i(c))
constexpr uint16_t swzl_mask(const char *p) {
  uint16_t mask = 0x0;
  for (const char *q = p; *q; q++)
    mask |= l2m(*q);
  return mask;
}
#define NMSWZL(swzl) \
  constexpr vec<S, cestrlen( #swzl )> swzl() const { \
    constexpr uint16_t msk = swzl_mask(#swzl); \
    static_assert(!(msk & l2m('z')) || ((msk & l2m('z')) && N >= 3), \
                  "swizzled vector has no z component"); \
    static_assert(!(msk & l2m('w')) || ((msk & l2m('w')) && N >= 4), \
                  "swizzled vector has no w component"); \
    vec<S, cestrlen(#swzl)> result {}; \
    constexpr int r_N = decltype(result)::Dimensionality; \
    for (int i = 0; i < r_N; ++i) { \
      result.data[i] = data[l2i(#swzl[i])]; \
    } \
    return result; \
  }

template <unsigned N, class S>
struct vbase {
  static constexpr unsigned Dimensionality = N;
  using Scalar = S;

  S data[N];

  constexpr vbase() = default;

  template <class... Args>
  constexpr vbase(Args... args) : data{ args... } {
    static_assert(sizeof...(args) == N, "Wrong number of initializers.");
  }

  NMSWZL(xx)
  NMSWZL(xy)
  NMSWZL(xz)
  NMSWZL(xw)
  NMSWZL(yx)
  NMSWZL(yy)
  NMSWZL(yz)
  NMSWZL(yw)
  NMSWZL(zx)
  NMSWZL(zy)
  NMSWZL(zz)
  NMSWZL(zw)
  NMSWZL(wx)
  NMSWZL(wy)
  NMSWZL(wz)
  NMSWZL(ww)
  NMSWZL(xxx)
  NMSWZL(xxy)
  NMSWZL(xxz)
  NMSWZL(xxw)
  NMSWZL(yxx)
  NMSWZL(yxy)
  NMSWZL(yxz)
  NMSWZL(yxw)
  NMSWZL(zxx)
  NMSWZL(zxy)
  NMSWZL(zxz)
  NMSWZL(zxw)
  NMSWZL(wxx)
  NMSWZL(wxy)
  NMSWZL(wxz)
  NMSWZL(wxw)
  NMSWZL(xyx)
  NMSWZL(xyy)
  NMSWZL(xyz)
  NMSWZL(xyw)
  NMSWZL(yyx)
  NMSWZL(yyy)
  NMSWZL(yyz)
  NMSWZL(yyw)
  NMSWZL(zyx)
  NMSWZL(zyy)
  NMSWZL(zyz)
  NMSWZL(zyw)
  NMSWZL(wyx)
  NMSWZL(wyy)
  NMSWZL(wyz)
  NMSWZL(wyw)
  NMSWZL(xzx)
  NMSWZL(xzy)
  NMSWZL(xzz)
  NMSWZL(xzw)
  NMSWZL(yzx)
  NMSWZL(yzy)
  NMSWZL(yzz)
  NMSWZL(yzw)
  NMSWZL(zzx)
  NMSWZL(zzy)
  NMSWZL(zzz)
  NMSWZL(zzw)
  NMSWZL(wzx)
  NMSWZL(wzy)
  NMSWZL(wzz)
  NMSWZL(wzw)
  NMSWZL(xwx)
  NMSWZL(xwy)
  NMSWZL(xwz)
  NMSWZL(xww)
  NMSWZL(ywx)
  NMSWZL(ywy)
  NMSWZL(ywz)
  NMSWZL(yww)
  NMSWZL(zwx)
  NMSWZL(zwy)
  NMSWZL(zwz)
  NMSWZL(zww)
  NMSWZL(wwx)
  NMSWZL(wwy)
  NMSWZL(wwz)
  NMSWZL(www)
  NMSWZL(xxxx)
  NMSWZL(xxxy)
  NMSWZL(xxxz)
  NMSWZL(xxxw)
  NMSWZL(yxxx)
  NMSWZL(yxxy)
  NMSWZL(yxxz)
  NMSWZL(yxxw)
  NMSWZL(zxxx)
  NMSWZL(zxxy)
  NMSWZL(zxxz)
  NMSWZL(zxxw)
  NMSWZL(wxxx)
  NMSWZL(wxxy)
  NMSWZL(wxxz)
  NMSWZL(wxxw)
  NMSWZL(xyxx)
  NMSWZL(xyxy)
  NMSWZL(xyxz)
  NMSWZL(xyxw)
  NMSWZL(yyxx)
  NMSWZL(yyxy)
  NMSWZL(yyxz)
  NMSWZL(yyxw)
  NMSWZL(zyxx)
  NMSWZL(zyxy)
  NMSWZL(zyxz)
  NMSWZL(zyxw)
  NMSWZL(wyxx)
  NMSWZL(wyxy)
  NMSWZL(wyxz)
  NMSWZL(wyxw)
  NMSWZL(xzxx)
  NMSWZL(xzxy)
  NMSWZL(xzxz)
  NMSWZL(xzxw)
  NMSWZL(yzxx)
  NMSWZL(yzxy)
  NMSWZL(yzxz)
  NMSWZL(yzxw)
  NMSWZL(zzxx)
  NMSWZL(zzxy)
  NMSWZL(zzxz)
  NMSWZL(zzxw)
  NMSWZL(wzxx)
  NMSWZL(wzxy)
  NMSWZL(wzxz)
  NMSWZL(wzxw)
  NMSWZL(xwxx)
  NMSWZL(xwxy)
  NMSWZL(xwxz)
  NMSWZL(xwxw)
  NMSWZL(ywxx)
  NMSWZL(ywxy)
  NMSWZL(ywxz)
  NMSWZL(ywxw)
  NMSWZL(zwxx)
  NMSWZL(zwxy)
  NMSWZL(zwxz)
  NMSWZL(zwxw)
  NMSWZL(wwxx)
  NMSWZL(wwxy)
  NMSWZL(wwxz)
  NMSWZL(wwxw)
  NMSWZL(xxyx)
  NMSWZL(xxyy)
  NMSWZL(xxyz)
  NMSWZL(xxyw)
  NMSWZL(yxyx)
  NMSWZL(yxyy)
  NMSWZL(yxyz)
  NMSWZL(yxyw)
  NMSWZL(zxyx)
  NMSWZL(zxyy)
  NMSWZL(zxyz)
  NMSWZL(zxyw)
  NMSWZL(wxyx)
  NMSWZL(wxyy)
  NMSWZL(wxyz)
  NMSWZL(wxyw)
  NMSWZL(xyyx)
  NMSWZL(xyyy)
  NMSWZL(xyyz)
  NMSWZL(xyyw)
  NMSWZL(yyyx)
  NMSWZL(yyyy)
  NMSWZL(yyyz)
  NMSWZL(yyyw)
  NMSWZL(zyyx)
  NMSWZL(zyyy)
  NMSWZL(zyyz)
  NMSWZL(zyyw)
  NMSWZL(wyyx)
  NMSWZL(wyyy)
  NMSWZL(wyyz)
  NMSWZL(wyyw)
  NMSWZL(xzyx)
  NMSWZL(xzyy)
  NMSWZL(xzyz)
  NMSWZL(xzyw)
  NMSWZL(yzyx)
  NMSWZL(yzyy)
  NMSWZL(yzyz)
  NMSWZL(yzyw)
  NMSWZL(zzyx)
  NMSWZL(zzyy)
  NMSWZL(zzyz)
  NMSWZL(zzyw)
  NMSWZL(wzyx)
  NMSWZL(wzyy)
  NMSWZL(wzyz)
  NMSWZL(wzyw)
  NMSWZL(xwyx)
  NMSWZL(xwyy)
  NMSWZL(xwyz)
  NMSWZL(xwyw)
  NMSWZL(ywyx)
  NMSWZL(ywyy)
  NMSWZL(ywyz)
  NMSWZL(ywyw)
  NMSWZL(zwyx)
  NMSWZL(zwyy)
  NMSWZL(zwyz)
  NMSWZL(zwyw)
  NMSWZL(wwyx)
  NMSWZL(wwyy)
  NMSWZL(wwyz)
  NMSWZL(wwyw)
  NMSWZL(xxzx)
  NMSWZL(xxzy)
  NMSWZL(xxzz)
  NMSWZL(xxzw)
  NMSWZL(yxzx)
  NMSWZL(yxzy)
  NMSWZL(yxzz)
  NMSWZL(yxzw)
  NMSWZL(zxzx)
  NMSWZL(zxzy)
  NMSWZL(zxzz)
  NMSWZL(zxzw)
  NMSWZL(wxzx)
  NMSWZL(wxzy)
  NMSWZL(wxzz)
  NMSWZL(wxzw)
  NMSWZL(xyzx)
  NMSWZL(xyzy)
  NMSWZL(xyzz)
  NMSWZL(xyzw)
  NMSWZL(yyzx)
  NMSWZL(yyzy)
  NMSWZL(yyzz)
  NMSWZL(yyzw)
  NMSWZL(zyzx)
  NMSWZL(zyzy)
  NMSWZL(zyzz)
  NMSWZL(zyzw)
  NMSWZL(wyzx)
  NMSWZL(wyzy)
  NMSWZL(wyzz)
  NMSWZL(wyzw)
  NMSWZL(xzzx)
  NMSWZL(xzzy)
  NMSWZL(xzzz)
  NMSWZL(xzzw)
  NMSWZL(yzzx)
  NMSWZL(yzzy)
  NMSWZL(yzzz)
  NMSWZL(yzzw)
  NMSWZL(zzzx)
  NMSWZL(zzzy)
  NMSWZL(zzzz)
  NMSWZL(zzzw)
  NMSWZL(wzzx)
  NMSWZL(wzzy)
  NMSWZL(wzzz)
  NMSWZL(wzzw)
  NMSWZL(xwzx)
  NMSWZL(xwzy)
  NMSWZL(xwzz)
  NMSWZL(xwzw)
  NMSWZL(ywzx)
  NMSWZL(ywzy)
  NMSWZL(ywzz)
  NMSWZL(ywzw)
  NMSWZL(zwzx)
  NMSWZL(zwzy)
  NMSWZL(zwzz)
  NMSWZL(zwzw)
  NMSWZL(wwzx)
  NMSWZL(wwzy)
  NMSWZL(wwzz)
  NMSWZL(wwzw)
  NMSWZL(xxwx)
  NMSWZL(xxwy)
  NMSWZL(xxwz)
  NMSWZL(xxww)
  NMSWZL(yxwx)
  NMSWZL(yxwy)
  NMSWZL(yxwz)
  NMSWZL(yxww)
  NMSWZL(zxwx)
  NMSWZL(zxwy)
  NMSWZL(zxwz)
  NMSWZL(zxww)
  NMSWZL(wxwx)
  NMSWZL(wxwy)
  NMSWZL(wxwz)
  NMSWZL(wxww)
  NMSWZL(xywx)
  NMSWZL(xywy)
  NMSWZL(xywz)
  NMSWZL(xyww)
  NMSWZL(yywx)
  NMSWZL(yywy)
  NMSWZL(yywz)
  NMSWZL(yyww)
  NMSWZL(zywx)
  NMSWZL(zywy)
  NMSWZL(zywz)
  NMSWZL(zyww)
  NMSWZL(wywx)
  NMSWZL(wywy)
  NMSWZL(wywz)
  NMSWZL(wyww)
  NMSWZL(xzwx)
  NMSWZL(xzwy)
  NMSWZL(xzwz)
  NMSWZL(xzww)
  NMSWZL(yzwx)
  NMSWZL(yzwy)
  NMSWZL(yzwz)
  NMSWZL(yzww)
  NMSWZL(zzwx)
  NMSWZL(zzwy)
  NMSWZL(zzwz)
  NMSWZL(zzww)
  NMSWZL(wzwx)
  NMSWZL(wzwy)
  NMSWZL(wzwz)
  NMSWZL(wzww)
  NMSWZL(xwwx)
  NMSWZL(xwwy)
  NMSWZL(xwwz)
  NMSWZL(xwww)
  NMSWZL(ywwx)
  NMSWZL(ywwy)
  NMSWZL(ywwz)
  NMSWZL(ywww)
  NMSWZL(zwwx)
  NMSWZL(zwwy)
  NMSWZL(zwwz)
  NMSWZL(zwww)
  NMSWZL(wwwx)
  NMSWZL(wwwy)
  NMSWZL(wwwz)
  NMSWZL(wwww)

  /**
   * Access the i-th scalar value in the vector.
   */
  S& operator[](const int i) { return data[i]; }

  /**
   * Access the i-th scalar value in the vector as a constant expression.
   */
  constexpr const S& operator[](const int i) const { return data[i]; }
};

}

/**
 * Specialization of \ref vec for two-dimensional vectors.
 */
template <class S>
struct vec<S, 2> : public detail::vbase<2, S> {
  using BaseT = detail::vbase<2, S>;

  vec() = default;
  explicit constexpr vec(const S v) : BaseT{v, v} {}
  constexpr vec(const S x, const S y) : BaseT{x, y} {}

  constexpr S x() const { return this->data[0]; }
  constexpr S y() const { return this->data[1]; }
};

/**
 * A two-dimensional vector of 32-bit floating point values.
 */
using float2 = vec<float, 2>;

/**
 * Specialization of \ref vec for two-dimensional vectors.
 */
template <class S>
struct vec<S, 3> : public detail::vbase<3, S> {
  using BaseT = detail::vbase<3, S>;

  vec() = default;
  explicit constexpr vec(const S v) : BaseT{v, v, v} {}
  constexpr vec(const S x, const S y, const S z) : BaseT{x, y, z} {}
  constexpr vec(const vec<S, 2> &xy, const S z) : BaseT{xy[0], xy[1], z} {}
  constexpr vec(const S x, const vec<S, 2> &yz) : BaseT{x, yz[0], yz[1]} {} 

  constexpr S x() const { return this->data[0]; }
  constexpr S y() const { return this->data[1]; }
  constexpr S z() const { return this->data[2]; }
};

/**
 * A three-dimensional vector of 32-bit floating point values.
 */
using float3 = vec<float, 3>;

/**
 * Specialization of \ref vec for four-dimensional vectors.
 */
template <class S>
struct vec<S, 4> : public detail::vbase<4, S> {
  using BaseT = detail::vbase<4, S>;

  vec() = default;
  constexpr vec(const BaseT &b) : BaseT(b) {}
  explicit constexpr vec(const S v) : BaseT{v, v, v, v} {}
  constexpr vec(const S x, const S y, const S z, const S w) : BaseT{x, y, z, w} {}
  constexpr vec(const S x, const S y, const vec<S, 2> &zw) : BaseT{x, y, zw[0], zw[1]} {}
  constexpr vec(const S x, const vec<S, 3> &yzw) : BaseT{x, yzw[0], yzw[1], yzw[2]} {}
  constexpr vec(const S x, const vec<S, 2> &yz, const S w) : BaseT{x, yz[0], yz[1], w} {}
  constexpr vec(const vec<S, 2> &xy, const S z, const S w) : BaseT{xy[0], xy[1], z, w} {}
  constexpr vec(const vec<S, 2> &xy, const vec<S, 2> &zw) : BaseT{xy[0], xy[1], zw[0], zw[1]} {}
  constexpr vec(const vec<S, 3> &xyz, const S w) : BaseT{xyz[0], xyz[1], xyz[2], w} {}

  constexpr S x() const { return this->data[0]; }
  constexpr S y() const { return this->data[1]; }
  constexpr S z() const { return this->data[2]; }
  constexpr S w() const { return this->data[3]; }
};

/**
 * A four-dimensional vector of 32-bit floating point values.
 */
using float4 = vec<float, 4>;

/**
 * A square matrix. Elements are laid out in memory column-by-column in a
 * contiguous manner. For example, for a 2x2 matrix, the memory layout would
 * be: [m00 m01 m10 m11] where [m00 m01] is the first column, and [m10 m11] is
 * the second column.
 */
template <class S, unsigned N>
struct mat {
  /** Underlying column type. */
  using ColumnT = vec<S, N>;

  /** Number of rows/columns. */
  static constexpr int Size = N;

  ColumnT column[Size];

  constexpr mat(){};

  /**
   * Create a new matrix from the given column vectors.
   *
   * Example usage:
   * `float2x2::from_columns(float2 {.0f, .0f}, float2 {.1f, 1.f})`
   */
  template<class... Args>
  static constexpr auto from_columns(const Args&... cols) {
    static_assert(sizeof...(cols) == N, "Wrong number of columns given.");
    return mat { cols... };
  }

  /**
   * Create a new matrix from the given row vectors.
   *
   * Example usage:
   * `float2x2::from_rows(float2 {.0f, .0f}, float2 {.1f, 1.f})`
   */
  template<class... Args>
  static constexpr mat from_rows(const Args&... rows) {
    static_assert(sizeof...(rows) == Size, "Wrong number of rows given.");
    mat result;
    for (unsigned i = 0u; i < N; ++i) {
      result.column[i] = ColumnT { rows.data[i]... };
    }
    return result;
  }

  /**
   * Create a new identity matrix (i.e. with all coefficients set to 0, except
   * the main diagonal coefficients, which are set to 1).
   */
  static constexpr mat identity() {
    mat result;
    for (unsigned i = 0u; i < N; ++i)
      for (unsigned j = 0u; j < N; ++j)
        result.column[i].data[j] = (S)(i == j ? 1.0f : 0.0f);
    return result;
  }

  /**
   * Access the i-th column of the matrix.
   */
  constexpr ColumnT& operator[](const unsigned i) { return column[i]; }

  /**
   * Access the i-th column of the matrix (read-only).
   */
  constexpr const ColumnT& operator[](const int i) const { return column[i]; }

private:
  template<class... Args>
  constexpr mat(const Args&... cols) : column { cols... } {
    static_assert(sizeof...(cols) == N, "Wrong number of columns given.");
  }
};


/**
 * 2x2 matrix.
 */
template <class S>
using mat2x2 = mat<S, 2>;

/**
 * A 2x2 matrix of 32-bit floating point coefficients.
 */
using float2x2 = mat2x2<float>;

/**
 * 3x3 matrix.
 */
template <class S>
using mat3x3 = mat<S, 3>;

/**
 * A 3x3 matrix of 32-bit floating point coefficients.
 */
using float3x3 = mat3x3<float>;

/**
 * 4x4 matrix.
 */
template <class S>
using mat4x4 = mat<S, 4>;

/**
 * A 4x4 matrix of 32-bit floating point coefficients.
 */
using float4x4 = mat4x4<float>;

/**
 * A quaternion.
 */
template <class S>
struct quat : public vec<S, 4> {
  quat() = default;

  /**
   * Constructs a unit quaternion representing a rotation by the given amount
   * around an axis.
   */
  constexpr quat(const S theta, const vec<S, 3> &axis) : 
      vec<S, 4>(std::sin(theta / (S)2.0) * normalize(axis),
                cos(theta / (S) 2.0)) {}

  /**
   * Explicitly construct a quaternion from components.
   */
   constexpr quat(const S x, const S y, const S z, const S w) :
      vec<S, 4>(x, y, z, w) {}
};

/**
 * A quaternion with 32-bit floating point coefficients.
 */
using quatf = quat<float>;

/**
 * @return The conjugate of a given quaternion.
 */
template <class S>
inline constexpr quat<S> conjugate(const quat<S> &q) {
  return quat<S> { -q.data[0], -q.data[1], -q.data[2], q.data[3] };
}

/**
 * @return The product of two quaternions.
 */
template <class S>
inline constexpr quat<S> operator*(const quat<S> &lhs, const quat<S> &rhs) {
  const S x1 = lhs.data[0],
          x2 = rhs.data[0],
          y1 = lhs.data[1],
          y2 = rhs.data[1],
          z1 = lhs.data[2],
          z2 = rhs.data[2],
          w1 = lhs.data[3],
          w2 = rhs.data[3];
  return quat<S> {
    x1 * w1 + y1 * z2 - z1 * y2 + x2 * w1,
    y1 * w2 - x1 * z2 + z1 * x2 + y2 * w1,
    x1 * y2 - y1 * x2 + z1 * w2 + z2 * w1,
    w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
  };
}

/**
 * Rotates the given vector using the given unit quaternion.
 * @param vv the vector to rotate
 * @param q  the quaternion representing the rotation.
 * @return rotated vv
 */
template <class S>
inline constexpr vec<S, 3> rotate(const vec<S, 3> &vv, const quat<S> &q) {
  const quat<S> v { v.x, v.y, v.z, (S)0.0 };
  return q * v * q.conjugate();
}

template<class S>
auto quat2mat(const quat<S> &q) {
  return mat4x4<S> {
    { 
      1.0 - 2.0 * (q.y() * q.y() + q.z() * q.z()),
      2.0 *(q.x() * q.y() - q.z() * q.w()),
      2.0 * (q.x() * q.z() + q.y() * q.w()), 
      0.0
    },
    {
      2.0 * (q.x() * q.y() + q.z() * q.w()),
      1.0 - 2.0 * (q.x() * q.x() + q.z() * q.z()),
      2.0 * (q.y() * q.z() + q.x() * q.w()), 
      0.0
    },
    {
      2.0 * (q.x() * q.z() + q.y() * q.w()),
      2.0 * (q.y() * q.z() - q.x() * q.w()),
      1.0 - 2.0 * (q.x() * q.x() + q.y() * q.y()),
      0.0
    },
    { 0.0, 0.0, 0.0, 1.0 }
  };
}

/**
 * @return The cross product of two three-dimensional vectors.
 */
template <class S>
inline constexpr auto cross(const vec<S, 3> &lhs, const vec<S, 3> &rhs) {
  return vec<S, 3>(lhs.data[1] * rhs.data[2] - lhs.data[2] * rhs.data[1],
                   lhs.data[2] * rhs.data[0] - lhs.data[0] * rhs.data[2],
                   lhs.data[0] * rhs.data[1] - lhs.data[1] * rhs.data[0]);
}

template<>
inline constexpr auto cross(const vec<float, 3> &lhs, const vec<float, 3> &rhs) {
	const vec<double, 3> a { (double)lhs.x(), (double)lhs.y(), (double)lhs.z() },
                       b { (double)rhs.x(), (double)rhs.y(), (double)rhs.z() },
                       c = cross(a, b);
    return vec<float, 3> { (float)c.x(), (float)c.y(), (float)c.z() };
}


/**
 * @return The dot product of two vectors.
 */
template <unsigned N, class S>
inline constexpr S dot(const vec<S, N> &lhs, const vec<S, N> &rhs) {
  S result = (S)0.0f;
  for (unsigned i = 0u; i < N; ++i) result += lhs.data[i] * rhs.data[i];
  return result;
}

/**
 * @return The squared magnitude of a vector.
 */
template <unsigned N, class S>
inline constexpr S lengthsq(const vec<S, N> &v) { return dot(v, v); }

/**
 * @return The magnitude of a vector.
 */
template <unsigned N, class S>
inline constexpr S length(const vec<S, N> &v) { 
  return (S)std::sqrt(lengthsq(v));
}

/**
 * @return The projection of vector `a` onto `b`.
 */
template <class V>
inline constexpr V project(const V &a, const  V &b) {
  return b * (dot(a, b) / lengthsq(b));
}

/**
 * @return The part of `a` that is orthogonal to `b`, in other words,
 *         `a - project(a, b)`
 */
template <class V>
inline constexpr V reject(const V &a, const  V &b) { return a - project(a, b); }

/**
 * Reflect a vector against a plane.
 *
 * @param d The vector being reflected.
 * @param n The normal of the reflection plane. This is expected to be of
 *          magnitude 1.
 * @return Reflection of vector `d`.
 */
template <class V>
inline constexpr V reflect(const V &d, const V &n) {
  return d - n * (static_cast<typename V::Scalar>(2.0f) * dot(d, n));
}

/**
 * Vector normalization.
 * @param v the vector to normalize.
 * @return Vector with the same direction as `v` and a magnitude of 1.
 */
template <class V>
inline constexpr V normalize(const V &v) { return v / length(v); }

/**
 * @return The sum of two vectors.
 */
template <class S, unsigned N>
inline constexpr vec<S, N> operator+(const vec<S, N> &lhs,
                                     const vec<S, N> &rhs) {
  vec<S, N> result {};
  for (unsigned i = 0; i < N; ++i)
    result.data[i] = lhs.data[i] + rhs.data[i];
  return result;
}

/**
 * @return The difference of two vectors.
 */
template <class S, unsigned N>
inline constexpr vec<S, N> operator-(const vec<S, N> &lhs,
                                     const vec<S, N> &rhs) {
  vec<S, N> result {};
  for (unsigned i = 0; i < N; ++i)
    result.data[i] = lhs.data[i] - rhs.data[i];
  return result;
}

/**
 * @return A vector in which each element is the result of multiplying the
 *         corresponding elements of lhs and rhs.
 */
template <class S, unsigned N>
inline constexpr vec<S, N> operator*(const vec<S, N> &lhs, const vec<S, N> &rhs) {
  vec<S, N> result {};
  for (unsigned i = 0; i < N; ++i)
    result.data[i] = lhs.data[i] * rhs.data[i];
  return result;
}

/**
 * @return A vector in which each element is the result of dividing the
 *         corresponding element of lhs by the corresponding element of rhs.
 */
template <class S, unsigned N>
inline constexpr vec<S, N> operator/(const vec<S, N> &lhs, const vec<S, N> &rhs) {
  vec<S, N> result {};
  for (unsigned i = 0; i < N; ++i)
    result.data[i] = lhs.data[i] / rhs.data[i];
  return result;
}

/**
 * @return The given vector multiplied by the given scalar.
 */
template <class S, unsigned N>
inline constexpr vec<S, N> operator*(const vec<S, N> &lhs, const S rhs) {
  vec<S, N> result = lhs;
  for (unsigned i = 0; i < N; ++i) result.data[i] *= rhs;
  return result;
}

template <class S, unsigned N>
inline constexpr vec<S, N> operator*(const S lhs, const vec<S, N> &rhs) {
  return rhs * lhs;
}

/**
 * @return The given vector divided by the given scalar.
 */
template <class S, unsigned N>
inline constexpr vec<S, N> operator/(const vec<S, N> &lhs, const S rhs) {
  const S inv = (S)1.0 / rhs;
  return lhs * inv;
}

/**
 * Multiply the given vector by the given scalar.
 */
template <class S, unsigned N>
inline vec<S, N>& operator*=(vec<S, N> &v, const S s) {
  v = v * s;
  return v;
}

/**
 * Divide the given vector by the given scalar.
 */
template <class S, unsigned N>
inline vec<S, N>& operator/=(vec<S, N> &v, const S s) {
  v = v / s;
  return v;
}

/**
 * Vector negation.
 * @param v vector to negate
 * @return a vector pointing in the direction opposite of `v`, with the same 
 *         magnitude as `v`.
 */
template<class S, unsigned N>
inline vec<S, N> operator-(const vec<S, N> &v) {
  vec<S, N> result;
  for (unsigned i = 0u; i < N; ++i) result.data[i] = -v.data[i];
  return result;
}

/**
 * @return true if the given two vectors are strictly equal.
 */
template <class S, unsigned N>
inline constexpr bool operator==(const vec<S, N> &lhs, const vec<S, N> &rhs)  {
  for (unsigned i = 0; i < N; ++i)
    if(lhs.data[i] != rhs.data[i])
      return false;
  return true;
}

template <class S, unsigned N>
inline constexpr bool operator!=(const vec<S, N> &lhs, const vec<S, N> &rhs) {
  return !(lhs == rhs);
}

/**
 * @return transpose of `src`.
 */
template <class S, unsigned N>
inline constexpr mat<S, N> transpose(const mat<S, N> &src) {
  mat<S, N> result;
  for (unsigned i = 0u; i < N; ++i)
    for (unsigned j = 0u; j < N; ++j)
      result.column[i].data[j] = src.column[j].data[i];
  return result;
}

/**
 * @return Determinant of the given matrix.
 */
template<unsigned N, class S>
S det(const mat<S, N> &mat) = delete;

template <class S>
constexpr S det(const mat<S, 2> &mat) {
  return mat.column[0].data[0] * mat.column[1].data[1] 
       - mat.column[1].data[0] * mat.column[0].data[1]; 
}

template <class S>
constexpr S det(const mat<S, 3> &mat) {
  S m00 = mat.column[0].data[0];
  S m01 = mat.column[0].data[1];
  S m02 = mat.column[0].data[2];
  S m10 = mat.column[1].data[0];
  S m11 = mat.column[1].data[1];
  S m12 = mat.column[1].data[2];
  S m20 = mat.column[2].data[0];
  S m21 = mat.column[2].data[1];
  S m22 = mat.column[2].data[2];

  return m00 * (m11 * m22 - m21 * m12) 
       - m10 * (m01 * m22 - m21 * m02) 
       + m20 * (m01 * m12 - m11 * m02);
}

template <class S>
constexpr S det(const mat<S, 4> &mat) {
  S m00 = mat.column[0].data[0];
  S m01 = mat.column[0].data[1];
  S m02 = mat.column[0].data[2];
  S m03 = mat.column[0].data[3];
  S m10 = mat.column[1].data[0];
  S m11 = mat.column[1].data[1];
  S m12 = mat.column[1].data[2];
  S m13 = mat.column[1].data[3];
  S m20 = mat.column[2].data[0];
  S m21 = mat.column[2].data[1];
  S m22 = mat.column[2].data[2];
  S m23 = mat.column[2].data[3];
  S m30 = mat.column[3].data[0];
  S m31 = mat.column[3].data[1];
  S m32 = mat.column[3].data[2];
  S m33 = mat.column[3].data[3];
  return m30 * m21 * m12 * m03 - m20 * m31 * m12 * m03 -
         m30 * m11 * m22 * m03 + m10 * m31 * m22 * m03 +
         m20 * m11 * m32 * m03 - m10 * m21 * m32 * m03 -
         m30 * m21 * m02 * m13 + m20 * m31 * m02 * m13 +
         m30 * m01 * m22 * m13 - m00 * m31 * m22 * m13 -
         m20 * m01 * m32 * m13 + m00 * m21 * m32 * m13 +
         m30 * m11 * m02 * m23 - m10 * m31 * m02 * m23 -
         m30 * m01 * m12 * m23 + m00 * m31 * m12 * m23 +
         m10 * m01 * m32 * m23 - m00 * m11 * m32 * m23 -
         m20 * m11 * m02 * m33 + m10 * m21 * m02 * m33 +
         m20 * m01 * m12 * m33 - m00 * m21 * m12 * m33 -
         m10 * m01 * m22 * m33 + m00 * m11 * m22 * m33;
}

/**
 * @return Inverse of a given 2x2 matrix.
 */
template <class S>
constexpr mat<S, 2> inverse(const mat<S, 2> &m) {
  using V = typename mat<S, 2>::ColumnT;
  const S d = det(m);
  constexpr S _1 = 1.0;
  return mat<S, 2>::from_columns( 
      V {  m.column[1].data[1], -m.column[0].data[1] },
      V { -m.column[1].data[0],  m.column[0].data[0] } ) * (_1/d);
}

/**
 * @return Inverse for a given 3x3 matrix.
 */
template <class S>
constexpr mat<S, 3> inverse(const mat<S, 3> &m) {
  using V = typename mat<S, 3>::ColumnT;
  const S inv_d = (S)1.0 / det(m);
  const V &a = m.column[0],
          &b = m.column[1],
          &c = m.column[2];
  const V  r0 = cross(b, c),
           r1 = cross(c, a),
           r2 = cross(a, b);
  return mat<S, 3>::from_columns(V { r0.data[0], r1.data[0], r2.data[0] },
                                 V { r0.data[1], r1.data[1], r2.data[1] },
                                 V { r0.data[2], r1.data[2], r2.data[2] }) * inv_d;
}

/**
 * @return Inverse for a given 4x4 matrix.
 */
template <class S>
inline constexpr mat<S, 4> inverse(const mat<S, 4> &m) {
  const vec<S, 3> a { m.column[0].data[0], m.column[0].data[1], m.column[0].data[2] },
                  b { m.column[1].data[0], m.column[1].data[1], m.column[1].data[2] },
                  c { m.column[2].data[0], m.column[2].data[1], m.column[2].data[2] },
                  d { m.column[3].data[0], m.column[3].data[1], m.column[3].data[2] };
  const S x = m.column[0].data[3],
          y = m.column[1].data[3],
          z = m.column[2].data[3],
          w = m.column[3].data[3];
  vec<S, 3> s = cross(a, b),
            t = cross(c, d),
            u = a * y - b * x,
            v = c * w - d * z;
  const S inv_d = (S)1.0 / (dot(s, v) + dot(t, u));
  s *= inv_d; t *= inv_d; u *= inv_d; v *= inv_d;
  const vec<S, 3> r0 = cross(b, v) + t * y,
                  r1 = cross(v, a) - t * x,
                  r2 = cross(d, u) + s * w,
                  r3 = cross(u, c) - s * z;
  
  using V = typename mat<S, 4>::ColumnT;
  return mat<S, 4>::from_columns(
    V { r0.data[0], r1.data[0], r2.data[0], r3.data[0] },
    V { r0.data[1], r1.data[1], r2.data[1], r3.data[1] },
    V { r0.data[2], r1.data[2], r2.data[2], r3.data[2] },
    V {-dot(b, t), dot(a, t),   -dot(d, s), dot(c, s)  });
}

/**
 * @return A matrix with elements on the main diagonal set to the values from
 *         the given vector, and all the other elements set to 0.
 */
template <class S, unsigned N>
inline constexpr mat<S, N> scale(const vec<S, N> &factors) {
  auto result = mat<S, N>::identity();
  for (unsigned r = 0u; r < N; ++r) {
    result.column[r].data[r] = factors.data[r];
  }
  return result;
}

/**
 * @param theta Angle of rotation, in radians.
 * @return A 2x2 matrix representing rotation by a given angle around the origin
 *         in two dimensions.
 */
template <class S>
inline constexpr auto rotation(const S theta) {
  return mat<S, 2>::from_columns(vec<S, 2> {  std::cos(theta), std::sin(theta) },
                                  vec<S, 2> { -std::sin(theta), std::cos(theta) });
}

/**
 * @param theta Angle of rotation in radians.
 * @param axis Axis of rotation, which is assumed to be of unit magnitude.
 * @return A 3x3 matrix representing a rotation by a given angle around a given
 *         axis in three dimensions.
 */
template <class S>
inline constexpr auto rotation(const S theta, const vec<S, 3> &axis) {
  constexpr S _1 = (S)1.0;
  const     S  c  = std::cos(theta),
               s  = std::sin(theta),
               ax = axis.data[0],
               ay = axis.data[1],
               az = axis.data[2];

  return mat<S, 3>::from_columns(
    vec<S, 3> {
      c + (_1 - c) * ax * ax,
      (_1 - c) * ax * ay + s * az, 
      (_1 - c) * ax * az - s * ay

    },
    vec<S, 3> {
      (_1 - c) * ax * ay - s * az,
      c + (_1 - c) * ay * ay,
      (_1 - c) * ay * az + s * ax
    },
    vec<S, 3> {
      (_1 - c) * ax * az + s * ay,
      (_1 - c) * ay * az - s * ax,
      c + (_1 - c) * az * az
    });
}

/**
 * @param theta Angle of rotation, in radians.
 * @param axis Vector representing the axis of rotation. It is assumed to be a
 *             unit vector.
 * @return A 4x4 matrix representing a three-dimensional rotation by a given
 *         angle around a given axis.
 */
template <class S>
inline constexpr auto rotation(const S theta, const vec<S, 4> &axis) {
  constexpr S _1 = (S)1.0, 
              _0 = (S)0.0;
  const     S  c  = std::cos(theta),
               s  = std::sin(theta),
               ax = axis.data[0], 
               ay = axis.data[1],
               az = axis.data[2];

  return mat<S, 4>::from_columns(
    vec<S, 4> {
      c + (_1 - c) * ax * ax,
      (_1 - c) * ax * ay + s * az, 
      (_1 - c) * ax * az - s * ay,
      _0
    },
    vec<S, 4> {
      (_1 - c) * ax * ay - s * az,
      c + (_1 - c) * ay * ay,
      (_1 - c) * ay * az + s * ax,
      _0
    },
    vec<S, 4> {
      (_1 - c) * ax * az + s * ay,
      (_1 - c) * ay * az - s * ax,
      c + (_1 - c) * az * az,
      _0
    },
    vec<S, 4> { _0, _0, _0, _1 });
}

/**
 * @param theta Angle of rotation, in radians.
 * @return A 4x4 matrix representing a three-dimensional rotation by a given
 *         angle around the X axis.
 */
template <class S>
inline constexpr auto rotation_x(const S theta) {
  const S s = std::sin(theta),
          c = std::cos(theta),
         _0 = (S)0.0,
         _1 = (S)1.0;
  return mat<S, 4>::from_columns(
    vec<S, 4> { _1, _0, _0, _0 },
    vec<S, 4> { _0,  c,  s, _0 },
    vec<S, 4> { _0, -s,  c, _0 },
    vec<S, 4> { _0, _0, _0, _1 });
}

/**
 * @param theta Angle of rotation, in radians.
 * @return A 4x4 matrix representing a three-dimensional rotation by a given
 *         angle around the Y axis.
 */
template <class S>
inline constexpr auto rotation_y(const S theta) {
  const S s = std::sin(theta),
          c = std::cos(theta),
         _0 = (S)0.0,
         _1 = (S)1.0;
  return mat<S, 4>::from_columns(
    vec<S, 4> {  c, _0, -s, _0 },
    vec<S, 4> { _0, _1, _0, _0 },
    vec<S, 4> {  s, _0,  c, _0 },
    vec<S, 4> { _0, _0, _0, _1 });
}

/**
 * @param theta Angle of rotation, in radians.
 * @return A 4x4 matrix representing a three-dimensional rotation by a given
 *         angle around the Z axis.
 */
template <class S>
inline constexpr auto rotation_z(const S theta) {
  const S s = std::sin(theta),
          c = std::cos(theta),
         _0 = (S)0.0,
         _1 = (S)1.0;
  return mat<S, 4>::from_columns(
    vec<S, 4> {  c,  s, _0, _0 },
    vec<S, 4> { -s,  c, _0, _0 },
    vec<S, 4> { _0, _0, _1, _0 },
    vec<S, 4> { _0, _0, _0, _1 });
}

/**
 * @return A 3x3 matrix representing translation in two dimensions.
 */
template <class S>
inline constexpr auto translation(const vec<S, 2> &offset) {
  constexpr S _0 = (S)0.0, _1 = (S)1.0;
  return mat<S, 3>::from_columns(
    vec<S, 3> { _1, _0, _0 },
    vec<S, 3> { _0, _1, _0 },
    vec<S, 3> { offset, _1 });
}

/**
 * @return A 4x4 matrix representing a translation in three dimensions.
 */
template <class S>
inline constexpr auto translation(const vec<S, 3> &offset) {
  const S _0 = (S)0.0, _1 = (S)1.0;
  return mat<S, 4>::from_columns(
    vec<S, 4> { _1, _0, _0, _0 },
    vec<S, 4> { _0, _1, _0, _0 },
    vec<S, 4> { _0, _0, _1, _0 },
    vec<S, 4> { offset, _1 });
}

/**
 * @param eye The location of the camera.
 * @param target The location the camera is pointed at.
 * @param up A vector pointing towards the top of the camera.
 * @returns A matrix representing the view transform corresponding to the camera
 *          setup described by the above three parameters.
 */
template <class S>
inline constexpr mat<S, 4> look_at(const vec<S, 3> &eye,
                                   const vec<S, 3> &target,
                                   const vec<S, 3> &up) {
  constexpr S _0 = (S)0.0, _1 = (S)1.0;
  const vec<S, 3> z = -normalize(target - eye),
                  x =  normalize(cross(up, z)),
                  y =  cross(z, x);
  return mat<S, 4>::from_rows(
    vec<S, 4> { x, -dot(eye, x) },
    vec<S, 4> { y, -dot(eye, y) },
    vec<S, 4> { z, -dot(eye, z) },
    vec<S, 4> { _0, _0, _0, _1 });
}

/**
 * @param l Left boundary.
 * @param r Right boundary.
 * @param b Bottom boundary.
 * @param t Top boundary.
 * @param n Near boundary.
 * @param f Far boundary.
 * @return An orthographic projection matrix.
 */
template <class S>
inline constexpr auto ortho(const S l, const S r,
                            const S b, const S t,
                            const S n, const S f) {
  constexpr S _2 = (S)2.0, _0 = (S)0.0, _1 = (S)1.0;
  return mat<S, 4>::from_columns(
    vec<S, 4> { _2 / (r - l), _0, _0, _0 },
    vec<S, 4> { _0, _2 / (t - b), _0, _0 },
    vec<S, 4> { _0, _0, _2 / (f - n), _0, },
    vec<S, 4> { -(r + l) / (r - l), -(t + b) / (t - b), -(n + f) / (f - n), _1 });
}

/**
 * @param l Left boundary.
 * @param r Right boundary.
 * @param b Bottom boundary.
 * @param t Top boundary.
 * @param ndist Near boundary.
 * @param fdist Far boundary.
 * @return A perspective projection matrix described by the above parameters.
 */
template <class S>
inline constexpr auto perspective(const S l, const S r,
                                  const S b, const S t,
                                  const S ndist, const S fdist) {
  constexpr S _0 = (S)0.0, _1 = (S)1.0, _2 = (S)2.0;
  return mat<S, 4>::from_columns(
    vec<S, 4> { _2 * ndist / (r - l), _0, _0, _0 },
    vec<S, 4> { _0, _2 * ndist / (t - b), _0, _0 },
    vec<S, 4> { (r + l) /(r - l), (t + b)/(t - b), -(ndist + fdist)/(fdist - ndist), -_1},
    vec<S, 4> { _0, _0, -_2 * ndist * fdist / (fdist - ndist), _0 });
}

/**
 * @param fovy Vertical field-of-view angle, in radians.
 * @param aspect Aspect ratio.
 * @param ndist Near boundary.
 * @param fdist Far boundary.
 * @return A perspective projection matrix described by the above parameters.
 */ 
template <class S>
inline constexpr auto perspective(const S fovy, const S aspect,
                                  const S ndist, const S fdist) {
  constexpr S _0 = (S)0.0, _1 = (S)1.0, _2 = (S)2.0;
  const S t = _1 / std::tan(fovy / _2);
  return mat<S, 4>::from_columns(
    vec<S, 4> { t / aspect, _0, _0, _0},
    vec<S, 4> { _0, t, _0, _0 },
    vec<S, 4> { _0, _0, -(fdist + ndist)/(fdist - ndist), -_1 },
    vec<S, 4> { _0, _0, -_2 * ndist * fdist / (fdist - ndist), _0 });
}

/**
 * (Matrix) X (Vector) multiplication.
 */
template<class S, unsigned N>
constexpr vec<S, N> operator*(const mat<S, N> &lhs, const vec<S, N> &rhs) {
  vec<S, N> result { (S)0.0 };
  for (unsigned c = 0; c < N; ++c) {
    for (unsigned i = 0; i < N; ++i)
      result.data[i] += lhs.column[c].data[i] * rhs.data[c];
  }
  return result;
}

/**
 * (Matrix) X (Matrix) multiplication.
 */
template<class S, unsigned N>
constexpr mat<S, N> operator*(const mat<S, N> &lhs, const mat<S, N> &rhs) {
  mat<S, N> result;
  for (unsigned c = 0; c < N; ++c) {
    for (unsigned r = 0; r < N; ++r) {
      result.column[c].data[r] = 0.0f;
      for (unsigned i = 0; i < N; ++i) {
        result.column[c].data[r] +=
          (lhs.column[i].data[r]) * (rhs.column[c].data[i]);
      }
    }
  }
  return result;
}

/**
 * Strict equality comparison for matrices.
 */
template <class S, unsigned N>
constexpr bool operator==(const mat<S, N> &lhs, const mat<S, N> &other) {
  for (unsigned i = 0u; i < N; ++i)
    if (lhs.column[i] != other.column[i])
      return false;
  return true;
}

/**
 * Multiplies all matrix elements by a given scalar value.
 */
template <class S, unsigned N>
constexpr mat<S, N> operator*(const mat<S, N> &lhs, const S rhs) {
  mat<S, N> result {};
  for (unsigned i = 0; i < N; ++i) result.column[i] = lhs.column[i] * rhs;
  return result;
}

template <class S, unsigned N>
constexpr mat<S, N> operator*(const S lhs, const mat<S, N> &rhs) {
  return rhs * lhs;
}

/**
 * Divides all matrix elements by a given scalar value.
 */
template <class S, unsigned N>
constexpr mat<S, N> operator/(const mat<S, N> &lhs, const S rhs) {
  return lhs * ((S)1.0f / rhs);
}

template <class S, unsigned N>
constexpr mat<S, N> operator/(const S lhs, const mat<S, N> &rhs) {
  return rhs / lhs;
}

/**
 * Degree-to-radian conversion.
 * @param deg angle in degrees.
 * @return angle in radians.
 */
template <class S>
inline constexpr S deg2rad(const S deg) { return deg *  ((S)PI/(S)180.0); }

}