code:
[Desktop Entry]
Name=@NAME@
GenericName=@CMAKE_PROJECT_NAME@
Comment=@CMAKE_PROJECT_DESCRIPTION@
Exec=@NAME@
Icon=@NAME@_icon
Type=Application
Categories=Miscellaneous;

repo_name: Antidote/openrpgeditor

license: mit


code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
  <asmv3:application>
    <asmv3:windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
    </asmv3:windowsSettings>
  </asmv3:application>
</assembly>


code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIconFile</key>
    <string>icon</string>
    <key>CFBundleIdentifier</key>
    <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
    <key>CFBundleVersion</key>
    <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
    <key>LSMinimumSystemVersion</key>
    <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
    <key>NSHumanReadableCopyright</key>
    <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
    <key>NSMainStoryboardFile</key>
    <string>Main</string>
    <key>NSPrincipalClass</key>
    <string>NSApplication</string>
    <key>NSHighResolutionCapable</key>
    <true/>
  </dict>
</plist>


code: app_icon ICON DISCARDABLE "../assets/icons/icon.ico"


code:
add_library(
  oremath STATIC
  OREMath/Color.cpp
  OREMath/Color.hpp
  OREMath/GenericMatrix.hpp
  OREMath/Matrix4x4.cpp
  OREMath/Matrix4x4.hpp
  OREMath/Point.cpp
  OREMath/Point.hpp
  OREMath/Rect.cpp
  OREMath/Rect.hpp
  OREMath/Size.cpp
  OREMath/Size.hpp
  OREMath/Vector2D.cpp
  OREMath/Vector2D.hpp
  OREMath/Vector3D.cpp
  OREMath/Vector3D.hpp
  OREMath/Vector4D.cpp
  OREMath/Vector4D.hpp)

target_link_libraries(oremath PRIVATE imgui-setup)
target_include_directories(oremath PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})


code:
//
// Created by antidote on 7/19/24.
//

#include "OREMath/Color.hpp"

#include "MathGlobals.hpp"

#include <limits.h>
#include <limits>

Color::Color(const Spec spec) {
  switch (spec) {
  case Spec::Invalid:
    invalidate();
    break;
  case Spec::Rgb:
    setRgb(0, 0, 0);
    break;
  case Spec::Hsv:
    setHsv(0, 0, 0);
    break;
  case Spec::Cmyk:
    setCmyk(0, 0, 0, 0);
    break;
  case Spec::Hsl:
    setHsl(0, 0, 0);
    break;
  }
}

void Color::invalidate() {
  m_spec = Spec::Invalid;
  m_color.argb.alpha = USHRT_MAX;
  m_color.argb.red = 0;
  m_color.argb.green = 0;
  m_color.argb.blue = 0;
  m_color.argb.pad = 0;
}
static constexpr inline int div_255(int x) { return (x + (x >> 8) + 0x80) >> 8; }
static constexpr inline uint32_t div_257_floor(uint32_t x) { return (x - (x >> 8)) >> 8; }
static constexpr inline uint32_t div_257(uint32_t x) { return div_257_floor(x + 128); }
static constexpr inline uint32_t div_65535(uint32_t x) { return (x + (x >> 16) + 0x8000U) >> 16; }

void Color::setRgb(const int r, const int g, const int b, int alpha) {
  m_spec = Spec::Rgb;
  m_color.argb.alpha = alpha * 0x101;
  m_color.argb.red = r * 0x101;
  m_color.argb.green = g * 0x101;
  m_color.argb.blue = b * 0x101;
}

void Color::setHsv(const int h, const int s, const int v, const int alpha) {
  if (h < -1 || static_cast<uint32_t>(s) > 255 || static_cast<uint32_t>(v) > 255 || static_cast<uint32_t>(alpha) > 255) {
    invalidate();
    return;
  }

  m_spec = Spec::Hsv;
  m_color.ahsv.alpha = alpha * 0x101;
  m_color.ahsv.hue = h == -1 ? USHRT_MAX : (h % 360) * 100;
  m_color.ahsv.saturation = s * 0x101;
  m_color.ahsv.value = v * 0x101;
  m_color.ahsl.pad = 0;
}

void Color::setHsl(const int h, const int s, const int l, const int alpha) {
  if (h < -1 || static_cast<uint32_t>(s) > 255 || static_cast<uint32_t>(l) > 255 || static_cast<uint32_t>(alpha) > 255) {
    invalidate();
    return;
  }

  m_spec = Spec::Hsl;
  m_color.ahsl.alpha = alpha * 0x101;
  m_color.ahsl.hue = h == -1 ? USHRT_MAX : (h % 360) * 100;
  m_color.ahsl.saturation = s * 0x101;
  m_color.ahsl.lightness = l * 0x101;
  m_color.ahsl.pad = 0;
}

void Color::setCmyk(const int c, const int m, const int y, const int k, const int alpha) {
  if (c < 0 || c > 255 || m < 0 || m > 255 || y < 0 || y > 255 || k < 0 || k > 255 || alpha < 0 || alpha > 255) {
    return;
  }
  m_spec = Spec::Cmyk;
  m_color.acmyk.alpha = alpha * 0x101;
  m_color.acmyk.cyan = c * 0x101;
  m_color.acmyk.magenta = m * 0x101;
  m_color.acmyk.yellow = y * 0x101;
  m_color.acmyk.black = k * 0x101;
}

Color::Rgb Color::rgba() const {
  if (m_spec != Spec::Invalid && m_spec != Spec::Rgb) {
    return toRgb().rgba();
  }

  return rgba(div_257(m_color.argb.red), div_257(m_color.argb.green), div_257(m_color.argb.blue), div_257(m_color.argb.alpha));
}

Color Color::toRgb() const {
  if (!isValid() || m_spec == Spec::Rgb) {
    return *this;
  }

  Color color;
  color.m_spec = Spec::Rgb;
  color.m_color.argb.alpha = m_color.argb.alpha;
  color.m_color.argb.pad = 0;
  switch (m_spec) {
  case Spec::Hsv: {
    if (m_color.ahsv.saturation == 0 || m_color.ahsv.value == USHRT_MAX) {
      color.m_color.argb.red = color.m_color.argb.green = color.m_color.argb.blue = m_color.ahsv.value;
      break;
    }

    const float h = m_color.ahsv.hue == 36000 ? 0 : m_color.ahsv.hue / 6000.f;
    const float s = m_color.ahsv.saturation / static_cast<float>(USHRT_MAX);
    const float v = m_color.ahsv.value / USHRT_MAX;
    const int i = static_cast<int>(h);
    const float f = h - i;
    const float p = v * (1.f - s);

    if (i & 1) {
      const float q = v * (1.f - s * f);
      switch (i) {
      case 1:
        color.m_color.argb.red = oRound(q * USHRT_MAX);
        color.m_color.argb.green = oRound(v * USHRT_MAX);
        color.m_color.argb.blue = oRound(p * USHRT_MAX);
        break;
      case 3:
        color.m_color.argb.red = oRound(p * USHRT_MAX);
        color.m_color.argb.green = oRound(q * USHRT_MAX);
        color.m_color.argb.blue = oRound(v * USHRT_MAX);
        break;
      case 5:
        color.m_color.argb.red = oRound(v * USHRT_MAX);
        color.m_color.argb.green = oRound(p * USHRT_MAX);
        color.m_color.argb.blue = oRound(q * USHRT_MAX);
        break;
      default:
        break;
      }
    } else {
      const float t = v * (1.f - (s * 1.f - f));
      switch (i) {
      case 0:
        color.m_color.argb.red = oRound(v * USHRT_MAX);
        color.m_color.argb.green = oRound(t * USHRT_MAX);
        color.m_color.argb.blue = oRound(p * USHRT_MAX);
        break;
      case 2:
        color.m_color.argb.red = oRound(p * USHRT_MAX);
        color.m_color.argb.green = oRound(v * USHRT_MAX);
        color.m_color.argb.blue = oRound(t * USHRT_MAX);
        break;
      case 4:
        color.m_color.argb.red = oRound(t * USHRT_MAX);
        color.m_color.argb.green = oRound(p * USHRT_MAX);
        color.m_color.argb.blue = oRound(v * USHRT_MAX);
        break;
      default:
        break;
      }
    }
    break;
  }
  case Spec::Hsl: {
    if (m_color.ahsl.saturation == 0 || m_color.ahsl.hue == USHRT_MAX) {
      color.m_color.argb.red = color.m_color.argb.green = color.m_color.argb.blue = m_color.ahsl.lightness;
    } else if (m_color.ahsl.lightness == 0) {
      color.m_color.argb.red = color.m_color.argb.green = color.m_color.argb.blue = 0;
    } else {
      const double h = m_color.ahsl.hue == 36000 ? 0 : m_color.ahsl.hue / 36000.0;
      const double s = m_color.ahsl.saturation / static_cast<double>(USHRT_MAX);
      const double l = m_color.ahsl.lightness / static_cast<double>(USHRT_MAX);

      const double clampedLightness = l < 0.5 ? l * (1. + s) : l + s - (l * s);
      const double lightnessRange = (2. * l) - clampedLightness;
      double hueOffset[3]{
          h + (1. / 3.),
          h,
          h - (1. / 3.),
      };

      for (int i = 0; i != 3; ++i) {
        if (hueOffset[i] < 0.) {
          hueOffset[i] += 1.;
        } else if (hueOffset[i] > 1.) {
          hueOffset[i] -= 1.;
        }

        if (hueOffset[i] * 6.0 < 1.) {
          color.m_color.array[i + 1] = oRound((lightnessRange + (clampedLightness - lightnessRange) * (hueOffset[i] * 6.)) * USHRT_MAX);
        } else if (hueOffset[i] * 2. < 1.) {
          color.m_color.array[i + 1] = oRound(clampedLightness * USHRT_MAX);
        } else if (hueOffset[i] * 3. < 2.) {
          color.m_color.array[i + 1] = oRound((lightnessRange + (clampedLightness - lightnessRange) * (2. / 3. - hueOffset[i]) * 6.) * USHRT_MAX);
        } else {
          color.m_color.array[i + 1] = oRound(lightnessRange * USHRT_MAX);
        }
      }
      color.m_color.argb.red = color.m_color.argb.red == 1 ? 0 : color.m_color.argb.red;
      color.m_color.argb.green = color.m_color.argb.green == 1 ? 0 : color.m_color.argb.green;
      color.m_color.argb.blue = color.m_color.argb.blue == 1 ? 0 : color.m_color.argb.blue;
    }
    break;
  }
  case Spec::Cmyk: {
    const float c = m_color.acmyk.cyan / static_cast<float>(USHRT_MAX);
    const float m = m_color.acmyk.magenta / static_cast<float>(USHRT_MAX);
    const float y = m_color.acmyk.yellow / static_cast<float>(USHRT_MAX);
    const float k = m_color.acmyk.black / static_cast<float>(USHRT_MAX);

    color.m_color.argb.red = oRound((1.f - (c * (1.f - k) + k)) * USHRT_MAX);
    color.m_color.argb.green = oRound((1.f - (m * (1.f - k) + k)) * USHRT_MAX);
    color.m_color.argb.blue = oRound((1.f - (y * (1.f - k) + k)) * USHRT_MAX);
    break;
  }
  default:
    break;
  }

  return color;
}

Color Color::fromRgb(const int r, const int g, const int b, int alpha) {
  if (!isRgbaValid(r, g, b, alpha)) {
    return {};
  }

  Color color;
  color.m_spec = Spec::Rgb;
  color.m_color.argb.alpha = alpha * 0x101;
  color.m_color.argb.red = r * 0x101;
  color.m_color.argb.green = g * 0x101;
  color.m_color.argb.blue = b * 0x101;
  color.m_color.argb.pad = 0;
  return color;
}
Color Color::fromHsv(const int h, const int s, const int v, const int alpha) {
  if (((h < 0 || h >= 360) && h != -1) || s < 0 || s > 255 || v < 0 || v > 255 || alpha < 0 || alpha > 255) {
    return {};
  }
  Color color;
  color.m_spec = Spec::Hsv;
  color.m_color.ahsv.alpha = alpha * 0x101;
  color.m_color.ahsv.hue = h == -1 ? USHRT_MAX : (h % 360) * 100;
  color.m_color.ahsv.saturation = s * 0x101;
  color.m_color.ahsv.value = v * 0x101;
  color.m_color.ahsv.pad = 0;
  return color;
}

Color Color::fromHsl(const int h, const int s, const int l, const int alpha) {
  if (((h < 0 || h >= 360) && h != -1) || s < 0 || s > 255 || l < 0 || l > 255 || alpha < 0 || alpha > 255) {
    return {};
  }
  Color color;
  color.m_spec = Spec::Hsl;
  color.m_color.ahsl.alpha = alpha * 0x101;
  color.m_color.ahsl.hue = h == -1 ? USHRT_MAX : (h % 360) * 100;
  color.m_color.ahsl.saturation = s * 0x101;
  color.m_color.ahsl.lightness = l * 0x101;
  color.m_color.ahsl.pad = 0;
  return color;
}

Color Color::fromCmyk(const int c, const int m, const int y, const int k, const int alpha) {
  if (c < 0 || c > 255 || m < 0 || m > 255 || y < 0 || y > 255 || k < 0 || k > 255 || alpha < 0 || alpha > 255) {
    return {};
  }
  Color color;
  color.m_spec = Spec::Cmyk;
  color.m_color.acmyk.alpha = alpha * 0x101;
  color.m_color.acmyk.cyan = c * 0x101;
  color.m_color.acmyk.magenta = m * 0x101;
  color.m_color.acmyk.yellow = y * 0x101;
  color.m_color.acmyk.black = k * 0x101;
  return color;
}


code:
#pragma once
#include <cstdint>
#include <sys/types.h>

struct Color {
  using Rgb = uint32_t;
  enum class Spec {
    Invalid,
    Rgb,
    Hsv,
    Cmyk,
    Hsl,
  };

  Color() = default;
  explicit Color(Spec spec);
  Color(const int r, const int g, const int b, const int alpha = 255) { setRgb(r, g, b, alpha); }
  void setRgb(int r, int g, int b, int alpha = 255);
  [[nodiscard]] Rgb rgba() const;
  void setHsv(int h, int s, int v, int alpha = 255);
  void setHsl(int h, int s, int v, int alpha = 255);
  void setCmyk(int c, int m, int y, int k, int alpha = 255);
  void invalidate();

  [[nodiscard]] Color toRgb() const;

  static Color fromRgb(int r, int g, int b, int alpha = 255);
  static Color fromHsv(int h, int s, int v, int alpha = 255);
  static Color fromHsl(int h, int s, int l, int alpha = 255);
  static Color fromCmyk(int c, int m, int y, int k, int alpha = 255);

  static int alpha(const Rgb rgb) { return (rgb >> 24) & 0xff; }
  static int red(const Rgb rgb) { return (rgb >> 16) & 0xff; }
  static int green(const Rgb rgb) { return (rgb >> 8) & 0xff; }
  static int blue(const Rgb rgb) { return rgb & 0xff; }
  static Rgb rgb(const int r, const int g, const int b) { return (255 << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); }
  static Rgb rgba(const int r, const int g, const int b, const int a) { return ((a & 0xFF) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); }

  static int gray(const int r, const int g, const int b) { return (r * 11 + g * 16 + b * 5) / 32; }
  static int gray(const Rgb rgb) { return gray(red(rgb), green(rgb), blue(rgb)); }
  static bool isGray(const Rgb rgb) { return red(rgb) == green(rgb) && red(rgb) == blue(rgb); }

  bool isValid() const { return m_spec != Spec::Invalid; }
  static constexpr bool isRgbaValid(const int r, const int g, const int b, const int a = 255) {
    return static_cast<uint32_t>(r) <= 255 && static_cast<uint32_t>(g) <= 255 && static_cast<uint32_t>(b) <= 255 && static_cast<uint32_t>(a) <= 255;
  }

private:
  Spec m_spec{};
  union {
    struct {
      uint16_t alpha;
      uint16_t red;
      uint16_t green;
      uint16_t blue;
      uint16_t pad;
    } argb;
    struct {
      uint16_t alpha;
      uint16_t hue;
      uint16_t saturation;
      uint16_t value;
      uint16_t pad;
    } ahsv;
    struct {
      uint16_t alpha;
      uint16_t cyan;
      uint16_t magenta;
      uint16_t yellow;
      uint16_t black;
    } acmyk;
    struct {
      uint16_t alpha;
      uint16_t hue;
      uint16_t saturation;
      uint16_t lightness;
      uint16_t pad;
    } ahsl;
    uint16_t array[5];
  } m_color{};
};


code:
#pragma once
#include "MathGlobals.hpp"
#include <assert.h>

template <int N, int M, typename T>
class GenericMatrix {
public:
  GenericMatrix();
  GenericMatrix(const GenericMatrix& other);
  explicit GenericMatrix(const T* values);

  const T& operator()(int row, int column) const;
  T& operator()(int row, int column);

  bool isIdentity() const;
  void setToIdentity();

  void fill(T value);

  GenericMatrix<M, N, T> transposed() const;

  GenericMatrix& operator+=(const GenericMatrix& other);
  GenericMatrix& operator-=(const GenericMatrix& other);
  GenericMatrix& operator*=(T factor);
  GenericMatrix& operator/=(T divisor);
  bool operator==(const GenericMatrix& other) const;
  bool operator!=(const GenericMatrix& other) const;

  void copyDataTo(T* values) const;
  T* data() { return m[0]; }
  const T* data() const { return m[0]; }

  template <int NN, int MM, typename TT>
  friend GenericMatrix<NN, MM, TT> operator+(const GenericMatrix<NN, MM, TT>& m1, const GenericMatrix<NN, MM, TT>& m2);
  template <int NN, int MM, typename TT>
  friend GenericMatrix<NN, MM, TT> operator-(const GenericMatrix<NN, MM, TT>& m1, const GenericMatrix<NN, MM, TT>& m2);
  template <int NN, int M1, int M2, typename TT>
  friend GenericMatrix<M1, M2, TT> operator*(const GenericMatrix<NN, M2, TT>& m1, const GenericMatrix<M1, NN, TT>& m2);
  template <int NN, int MM, typename TT>
  friend GenericMatrix<NN, MM, TT> operator-(const GenericMatrix<NN, MM, TT>& matrix);
  template <int NN, int MM, typename TT>
  friend GenericMatrix<NN, MM, TT> operator*(TT factor, const GenericMatrix<NN, MM, TT>& matrix);
  template <int NN, int MM, typename TT>
  friend GenericMatrix<NN, MM, TT> operator*(const GenericMatrix<NN, MM, TT>& matrix, TT factor);
  template <int NN, int MM, typename TT>
  friend GenericMatrix<NN, MM, TT> operator/(const GenericMatrix<NN, MM, TT>& matrix, TT divisor);

private:
  T m[N][M];

  GenericMatrix(int) {};

  template <int NN, int MM, typename TT>
  friend class GenericMatrix;
};

template <int N, int M, typename T>
GenericMatrix<N, M, T>::GenericMatrix() {
  setToIdentity();
}

template <int N, int M, typename T>
GenericMatrix<N, M, T>::GenericMatrix(const GenericMatrix<N, M, T>& other) {
  for (int col = 0; col < N; ++col)
    for (int row = 0; row < M; ++row)
      m[col][row] = other.m[col][row];
}

template <int N, int M, typename T>
GenericMatrix<N, M, T>::GenericMatrix(const T* values) {
  for (int col = 0; col < N; ++col)
    for (int row = 0; row < M; ++row)
      m[col][row] = values[row * N + col];
}

template <int N, int M, typename T>
const T& GenericMatrix<N, M, T>::operator()(int row, int column) const {
  assert(row >= 0 && row < M && column >= 0 && column < N);
  return m[column][row];
}

template <int N, int M, typename T>
T& GenericMatrix<N, M, T>::operator()(int row, int column) {
  assert(row >= 0 && row < M && column >= 0 && column < N);
  return m[column][row];
}

template <int N, int M, typename T>
bool GenericMatrix<N, M, T>::isIdentity() const {
  for (int col = 0; col < N; ++col) {
    for (int row = 0; row < M; ++row) {
      if (row == col) {
        if (m[col][row] != 1.0f)
          return false;
      } else {
        if (m[col][row] != 0.0f)
          return false;
      }
    }
  }
  return true;
}

template <int N, int M, typename T>
void GenericMatrix<N, M, T>::setToIdentity() {
  for (int col = 0; col < N; ++col) {
    for (int row = 0; row < M; ++row) {
      if (row == col)
        m[col][row] = 1.0f;
      else
        m[col][row] = 0.0f;
    }
  }
}

template <int N, int M, typename T>
void GenericMatrix<N, M, T>::fill(T value) {
  for (int col = 0; col < N; ++col)
    for (int row = 0; row < M; ++row)
      m[col][row] = value;
}

template <int N, int M, typename T>
GenericMatrix<M, N, T> GenericMatrix<N, M, T>::transposed() const {
  GenericMatrix<M, N, T> result(1);
  for (int row = 0; row < M; ++row)
    for (int col = 0; col < N; ++col)
      result.m[row][col] = m[col][row];
  return result;
}

template <int N, int M, typename T>
GenericMatrix<N, M, T>& GenericMatrix<N, M, T>::operator+=(const GenericMatrix<N, M, T>& other) {
  for (int index = 0; index < N * M; ++index)
    m[0][index] += other.m[0][index];
  return *this;
}

template <int N, int M, typename T>
GenericMatrix<N, M, T>& GenericMatrix<N, M, T>::operator-=(const GenericMatrix<N, M, T>& other) {
  for (int index = 0; index < N * M; ++index)
    m[0][index] -= other.m[0][index];
  return *this;
}

template <int N, int M, typename T>
GenericMatrix<N, M, T>& GenericMatrix<N, M, T>::operator*=(T factor) {
  for (int index = 0; index < N * M; ++index)
    m[0][index] *= factor;
  return *this;
}

template <int N, int M, typename T>
bool GenericMatrix<N, M, T>::operator==(const GenericMatrix<N, M, T>& other) const {
  for (int index = 0; index < N * M; ++index) {
    if (m[0][index] != other.m[0][index])
      return false;
  }
  return true;
}

template <int N, int M, typename T>
bool GenericMatrix<N, M, T>::operator!=(const GenericMatrix<N, M, T>& other) const {
  for (int index = 0; index < N * M; ++index) {
    if (m[0][index] != other.m[0][index])
      return true;
  }
  return false;
}

template <int N, int M, typename T>
GenericMatrix<N, M, T>& GenericMatrix<N, M, T>::operator/=(T divisor) {
  for (int index = 0; index < N * M; ++index)
    m[0][index] /= divisor;
  return *this;
}

template <int N, int M, typename T>
GenericMatrix<N, M, T> operator+(const GenericMatrix<N, M, T>& m1, const GenericMatrix<N, M, T>& m2) {
  GenericMatrix<N, M, T> result(1);
  for (int index = 0; index < N * M; ++index)
    result.m[0][index] = m1.m[0][index] + m2.m[0][index];
  return result;
}

template <int N, int M, typename T>
GenericMatrix<N, M, T> operator-(const GenericMatrix<N, M, T>& m1, const GenericMatrix<N, M, T>& m2) {
  GenericMatrix<N, M, T> result(1);
  for (int index = 0; index < N * M; ++index)
    result.m[0][index] = m1.m[0][index] - m2.m[0][index];
  return result;
}

template <int N, int M1, int M2, typename T>
GenericMatrix<M1, M2, T> operator*(const GenericMatrix<N, M2, T>& m1, const GenericMatrix<M1, N, T>& m2) {
  GenericMatrix<M1, M2, T> result(1);
  for (int row = 0; row < M2; ++row) {
    for (int col = 0; col < M1; ++col) {
      T sum(0.0f);
      for (int j = 0; j < N; ++j)
        sum += m1.m[j][row] * m2.m[col][j];
      result.m[col][row] = sum;
    }
  }
  return result;
}

template <int N, int M, typename T>
GenericMatrix<N, M, T> operator-(const GenericMatrix<N, M, T>& matrix) {
  GenericMatrix<N, M, T> result(1);
  for (int index = 0; index < N * M; ++index)
    result.m[0][index] = -matrix.m[0][index];
  return result;
}

template <int N, int M, typename T>
GenericMatrix<N, M, T> operator*(T factor, const GenericMatrix<N, M, T>& matrix) {
  GenericMatrix<N, M, T> result(1);
  for (int index = 0; index < N * M; ++index)
    result.m[0][index] = matrix.m[0][index] * factor;
  return result;
}

template <int N, int M, typename T>
GenericMatrix<N, M, T> operator*(const GenericMatrix<N, M, T>& matrix, T factor) {
  GenericMatrix<N, M, T> result(1);
  for (int index = 0; index < N * M; ++index)
    result.m[0][index] = matrix.m[0][index] * factor;
  return result;
}

template <int N, int M, typename T>
GenericMatrix<N, M, T> operator/(const GenericMatrix<N, M, T>& matrix, T divisor) {
  GenericMatrix<N, M, T> result(1);
  for (int index = 0; index < N * M; ++index)
    result.m[0][index] = matrix.m[0][index] / divisor;
  return result;
}

template <int N, int M, typename T>
void GenericMatrix<N, M, T>::copyDataTo(T* values) const {
  for (int col = 0; col < N; ++col)
    for (int row = 0; row < M; ++row)
      values[row * N + col] = T(m[col][row]);
}

typedef GenericMatrix<2, 2, float> Matrix2x2;
typedef GenericMatrix<2, 3, float> Matrix2x3;
typedef GenericMatrix<2, 4, float> Matrix2x4;
typedef GenericMatrix<3, 2, float> Matrix3x2;
typedef GenericMatrix<3, 3, float> Matrix3x3;
typedef GenericMatrix<3, 4, float> Matrix3x4;
typedef GenericMatrix<4, 2, float> Matrix4x2;
typedef GenericMatrix<4, 3, float> Matrix4x3;


code:
#pragma once
#include <cfloat>
#include <cmath>
#include <cstdint>

enum class AspectRatioMode {
  Ignore,
  Keep,
  KeepByExpanding,
};

static bool fuzzyCompare(const double p1, const double p2) { return std::abs(p1 - p2) <= DBL_EPSILON; }

static bool fuzzyCompare(const float p1, const float p2) { return std::abs(p1 - p2) <= FLT_EPSILON; }

static bool fuzzyIsNull(const double p) { return std::abs(p) <= DBL_EPSILON; }

static bool fuzzyIsNull(const float p) { return std::abs(p) <= FLT_EPSILON; }

static bool isNull(const double d) {
  union U {
    double d;
    uint64_t u;
  };
  U val{};
  val.d = d;
  return (val.u & 0x7fffffffffffffffull) == 0;
}

static bool isNull(const float f) {
  union U {
    float f;
    uint32_t u;
  };
  U val{};
  val.f = f;
  return (val.u & 0x7fffffff) == 0;
}

template <typename T>
constexpr T oAbs(const T& t) {
  return t >= 0 ? t : -t;
}

constexpr int64_t oRound(const double d) {
  return d >= 0.0 ? static_cast<int64_t>(d + 0.5) : static_cast<int64_t>(d - static_cast<double>(static_cast<int64_t>(d - 1)) + 0.5) + static_cast<int64_t>(d - 1);
}
constexpr int oRound(const float d) { return d >= 0.0f ? static_cast<int>(d + 0.5f) : static_cast<int>(d - static_cast<float>(static_cast<int>(d - 1)) + 0.5f) + static_cast<int>(d - 1); }

constexpr int alignValue(int value, const int size) {
  value = value - (value % static_cast<int>(size));
  if (size == 0)
    return value;

  const int remainder = value % size;
  if (remainder == 0)
    return value;

  return value + size - remainder;
}

inline uint32_t nextPowerOfTwo(const uint32_t n) {
  if (n == 0) {
    return 1;
  }
  if ((n & (n - 1)) == 0) {
    return n;
  }

  return std::pow(2, std::ceil(std::log2(n)));
}

static constexpr int nextMultipleOf8(const int num) { return (num + 7) & ~7; }


code:
// ReSharper disable CppTooWideScopeInitStatement
#include "Matrix4x4.hpp"
#define _USE_MATH_DEFINES 1
#include <cmath>
#include <math.h>

static constexpr float InvDistToPlane = 1.f / 1024.f;

Matrix4x4::Matrix4x4(const float* values) {
  for (int row = 0; row < 4; ++row) {
    for (int col = 0; col < 4; ++col) {
      m[col][row] = values[row * 4 + col];
    }
  }
  flagBits = General;
}

Matrix4x4::Matrix4x4(const float* values, const int cols, const int rows) {
  for (int col = 0; col < 4; ++col) {
    for (int row = 0; row < 4; ++row) {
      if (col < cols && row < rows) {
        m[col][row] = values[col * rows + row];
      } else if (col == row) {
        m[col][row] = 1.0f;
      } else {
        m[col][row] = 0.0f;
      }
    }
  }
  flagBits = General;
}

static float matrixDet3(const float m[4][4], const int col0, const int col1, const int col2, const int row0, const int row1, const int row2) {
  return m[col0][row0] * (m[col1][row1] * m[col2][row2] - m[col1][row2] * m[col2][row1]) - m[col1][row0] * (m[col0][row1] * m[col2][row2] - m[col0][row2] * m[col2][row1]) +
         m[col2][row0] * (m[col0][row1] * m[col1][row2] - m[col0][row2] * m[col1][row1]);
}

static float matrixDet4(const float m[4][4]) {
  float det = m[0][0] * matrixDet3(m, 1, 2, 3, 1, 2, 3);
  det -= m[1][0] * matrixDet3(m, 0, 2, 3, 1, 2, 3);
  det += m[2][0] * matrixDet3(m, 0, 1, 3, 1, 2, 3);
  det -= m[3][0] * matrixDet3(m, 0, 1, 2, 1, 2, 3);
  return det;
}

float Matrix4x4::determinant() const { return matrixDet4(m); }

Matrix4x4 Matrix4x4::inverted(bool* invertible) const {
  if (flagBits == Identity) {
    if (invertible) {
      *invertible = true;
    }
    return {};
  }
  if (flagBits == Translation) {
    Matrix4x4 inv;
    inv.m[3][0] = -m[3][0];
    inv.m[3][1] = -m[3][1];
    inv.m[3][2] = -m[3][2];
    inv.flagBits = Translation;
    if (invertible) {
      *invertible = true;
    }
    return inv;
  }
  if (flagBits == Rotation || flagBits == (Rotation | Translation)) {
    if (invertible) {
      *invertible = true;
    }

    return orthonormalInverse();
  }

  Matrix4x4 inv(1); // The "1" says to not load the identity.

  float det = matrixDet4(m);
  if (det == 0.0f) {
    if (invertible)
      *invertible = false;
    return {};
  }
  det = 1.0f / det;

  inv.m[0][0] = matrixDet3(m, 1, 2, 3, 1, 2, 3) * det;
  inv.m[0][1] = -matrixDet3(m, 0, 2, 3, 1, 2, 3) * det;
  inv.m[0][2] = matrixDet3(m, 0, 1, 3, 1, 2, 3) * det;
  inv.m[0][3] = -matrixDet3(m, 0, 1, 2, 1, 2, 3) * det;
  inv.m[1][0] = -matrixDet3(m, 1, 2, 3, 0, 2, 3) * det;
  inv.m[1][1] = matrixDet3(m, 0, 2, 3, 0, 2, 3) * det;
  inv.m[1][2] = -matrixDet3(m, 0, 1, 3, 0, 2, 3) * det;
  inv.m[1][3] = matrixDet3(m, 0, 1, 2, 0, 2, 3) * det;
  inv.m[2][0] = matrixDet3(m, 1, 2, 3, 0, 1, 3) * det;
  inv.m[2][1] = -matrixDet3(m, 0, 2, 3, 0, 1, 3) * det;
  inv.m[2][2] = matrixDet3(m, 0, 1, 3, 0, 1, 3) * det;
  inv.m[2][3] = -matrixDet3(m, 0, 1, 2, 0, 1, 3) * det;
  inv.m[3][0] = -matrixDet3(m, 1, 2, 3, 0, 1, 2) * det;
  inv.m[3][1] = matrixDet3(m, 0, 2, 3, 0, 1, 2) * det;
  inv.m[3][2] = -matrixDet3(m, 0, 1, 3, 0, 1, 2) * det;
  inv.m[3][3] = matrixDet3(m, 0, 1, 2, 0, 1, 2) * det;

  if (invertible)
    *invertible = true;
  return inv;
}

Matrix4x4 Matrix4x4::transposed() const {
  Matrix4x4 result(1);
  for (int row = 0; row < 4; ++row) {
    for (int col = 0; col < 4; ++col) {
      result.m[col][row] = m[row][col];
    }
  }
  return result;
}

Matrix3x3 Matrix4x4::normalMatrix() const {
  Matrix3x3 inv;

  if (flagBits == Identity || flagBits == Translation) {
    return inv;
  }
  if (flagBits == Scale || flagBits == (Translation | Scale)) {
    if (m[0][0] == 0.0f || m[1][1] == 0.0f || m[2][2] == 0.0f)
      return inv;
    inv.data()[0] = 1.0f / m[0][0];
    inv.data()[4] = 1.0f / m[1][1];
    inv.data()[8] = 1.0f / m[2][2];
    return inv;
  }

  float det = matrixDet3(m, 0, 1, 2, 0, 1, 2);
  if (det == 0.0f) {
    return inv;
  }
  det = 1.0f / det;

  float* invm = inv.data();

  // Invert and transpose in a single step.
  invm[0 + 0 * 3] = (m[1][1] * m[2][2] - m[2][1] * m[1][2]) * det;
  invm[1 + 0 * 3] = -(m[1][0] * m[2][2] - m[1][2] * m[2][0]) * det;
  invm[2 + 0 * 3] = (m[1][0] * m[2][1] - m[1][1] * m[2][0]) * det;
  invm[0 + 1 * 3] = -(m[0][1] * m[2][2] - m[2][1] * m[0][2]) * det;
  invm[1 + 1 * 3] = (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * det;
  invm[2 + 1 * 3] = -(m[0][0] * m[2][1] - m[0][1] * m[2][0]) * det;
  invm[0 + 2 * 3] = (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * det;
  invm[1 + 2 * 3] = -(m[0][0] * m[1][2] - m[0][2] * m[1][0]) * det;
  invm[2 + 2 * 3] = (m[0][0] * m[1][1] - m[1][0] * m[0][1]) * det;

  return inv;
}

Matrix4x4& Matrix4x4::operator/=(const float divisor) {
  m[0][0] /= divisor;
  m[0][1] /= divisor;
  m[0][2] /= divisor;
  m[0][3] /= divisor;
  m[1][0] /= divisor;
  m[1][1] /= divisor;
  m[1][2] /= divisor;
  m[1][3] /= divisor;
  m[2][0] /= divisor;
  m[2][1] /= divisor;
  m[2][2] /= divisor;
  m[2][3] /= divisor;
  m[3][0] /= divisor;
  m[3][1] /= divisor;
  m[3][2] /= divisor;
  m[3][3] /= divisor;
  flagBits = General;
  return *this;
}

Matrix4x4 operator/(const Matrix4x4& matrix, const float divisor) {
  Matrix4x4 m(1);
  m.m[0][0] = matrix.m[0][0] / divisor;
  m.m[0][1] = matrix.m[0][1] / divisor;
  m.m[0][2] = matrix.m[0][2] / divisor;
  m.m[0][3] = matrix.m[0][3] / divisor;
  m.m[1][0] = matrix.m[1][0] / divisor;
  m.m[1][1] = matrix.m[1][1] / divisor;
  m.m[1][2] = matrix.m[1][2] / divisor;
  m.m[1][3] = matrix.m[1][3] / divisor;
  m.m[2][0] = matrix.m[2][0] / divisor;
  m.m[2][1] = matrix.m[2][1] / divisor;
  m.m[2][2] = matrix.m[2][2] / divisor;
  m.m[2][3] = matrix.m[2][3] / divisor;
  m.m[3][0] = matrix.m[3][0] / divisor;
  m.m[3][1] = matrix.m[3][1] / divisor;
  m.m[3][2] = matrix.m[3][2] / divisor;
  m.m[3][3] = matrix.m[3][3] / divisor;
  return m;
}

void Matrix4x4::scale(const Vector3D& vector) {
  const auto vx = vector.x();
  const auto vy = vector.y();
  const auto vz = vector.z();
  if (flagBits == Identity) {
    m[0][0] = vx;
    m[1][1] = vy;
    m[2][2] = vz;
    flagBits = Scale;
  } else if (flagBits == Scale || flagBits == (Scale | Translation)) {
    m[0][0] *= vx;
    m[1][1] *= vy;
    m[2][2] *= vz;
  } else if (flagBits == Translation) {
    m[0][0] = vx;
    m[1][1] = vy;
    m[2][2] = vz;
    flagBits |= Scale;
  } else {
    m[0][0] *= vx;
    m[0][1] *= vx;
    m[0][2] *= vx;
    m[0][3] *= vx;
    m[1][0] *= vy;
    m[1][1] *= vy;
    m[1][2] *= vy;
    m[1][3] *= vy;
    m[2][0] *= vz;
    m[2][1] *= vz;
    m[2][2] *= vz;
    m[2][3] *= vz;
    flagBits = General;
  }
}
void Matrix4x4::scale(const float x, const float y) {
  if (flagBits == Identity) {
    m[0][0] = x;
    m[1][1] = y;
    flagBits = Scale;
  } else if (flagBits == Scale || flagBits == (Scale | Translation)) {
    m[0][0] *= x;
    m[1][1] *= y;
  } else if (flagBits == Translation) {
    m[0][0] = x;
    m[1][1] = y;
    flagBits |= Scale;
  } else {
    m[0][0] *= x;
    m[0][1] *= x;
    m[0][2] *= x;
    m[0][3] *= x;
    m[1][0] *= y;
    m[1][1] *= y;
    m[1][2] *= y;
    m[1][3] *= y;
    flagBits = General;
  }
}

void Matrix4x4::scale(const float x, const float y, const float z) {
  if (flagBits == Identity) {
    m[0][0] = x;
    m[1][1] = y;
    m[2][2] = z;
    flagBits = Scale;
  } else if (flagBits == Scale || flagBits == (Scale | Translation)) {
    m[0][0] *= x;
    m[1][1] *= y;
    m[2][2] *= z;
  } else if (flagBits == Translation) {
    m[0][0] = x;
    m[1][1] = y;
    m[2][2] = z;
    flagBits |= Scale;
  } else {
    m[0][0] *= x;
    m[0][1] *= x;
    m[0][2] *= x;
    m[0][3] *= x;
    m[1][0] *= y;
    m[1][1] *= y;
    m[1][2] *= y;
    m[1][3] *= y;
    m[2][0] *= z;
    m[2][1] *= z;
    m[2][2] *= z;
    m[2][3] *= z;
    flagBits = General;
  }
}

void Matrix4x4::scale(const float factor) {
  if (flagBits == Identity) {
    m[0][0] = factor;
    m[1][1] = factor;
    m[2][2] = factor;
    flagBits = Scale;
  } else if (flagBits == Scale || flagBits == (Scale | Translation)) {
    m[0][0] *= factor;
    m[1][1] *= factor;
    m[2][2] *= factor;
  } else if (flagBits == Translation) {
    m[0][0] = factor;
    m[1][1] = factor;
    m[2][2] = factor;
    flagBits |= Scale;
  } else {
    m[0][0] *= factor;
    m[0][1] *= factor;
    m[0][2] *= factor;
    m[0][3] *= factor;
    m[1][0] *= factor;
    m[1][1] *= factor;
    m[1][2] *= factor;
    m[1][3] *= factor;
    m[2][0] *= factor;
    m[2][1] *= factor;
    m[2][2] *= factor;
    m[2][3] *= factor;
    flagBits = General;
  }
}

void Matrix4x4::translate(const Vector3D& vector) {
  const auto vx = vector.x();
  const auto vy = vector.y();
  const auto vz = vector.z();
  if (flagBits == Identity) {
    m[3][0] = vx;
    m[3][1] = vy;
    m[3][2] = vz;
    flagBits = Translation;
  } else if (flagBits == Translation) {
    m[3][0] += vx;
    m[3][1] += vy;
    m[3][2] += vz;
  } else if (flagBits == Scale) {
    m[3][0] = m[0][0] * vx;
    m[3][1] = m[1][1] * vy;
    m[3][2] = m[2][2] * vz;
    flagBits |= Translation;
  } else if (flagBits == (Scale | Translation)) {
    m[3][0] += m[0][0] * vx;
    m[3][1] += m[1][1] * vy;
    m[3][2] += m[2][2] * vz;
  } else {
    m[3][0] += m[0][0] * vx + m[1][0] * vy + m[2][0] * vz;
    m[3][1] += m[0][1] * vx + m[1][1] * vy + m[2][1] * vz;
    m[3][2] += m[0][2] * vx + m[1][2] * vy + m[2][2] * vz;
    m[3][3] += m[0][3] * vx + m[1][3] * vy + m[2][3] * vz;
    if (flagBits == Rotation) {
      flagBits |= Translation;
    } else if (flagBits != (Rotation | Translation)) {
      flagBits = General;
    }
  }
}

void Matrix4x4::translate(const float x, const float y) {
  if (flagBits == Identity) {
    m[3][0] = x;
    m[3][1] = y;
    flagBits = Translation;
  } else if (flagBits == Translation) {
    m[3][0] += x;
    m[3][1] += y;
  } else if (flagBits == Scale) {
    m[3][0] = m[0][0] * x;
    m[3][1] = m[1][1] * y;
    m[3][2] = 0.;
    flagBits |= Translation;
  } else if (flagBits == (Scale | Translation)) {
    m[3][0] += m[0][0] * x;
    m[3][1] += m[1][1] * y;
  } else {
    m[3][0] += m[0][0] * x + m[1][0] * y;
    m[3][1] += m[0][1] * x + m[1][1] * y;
    m[3][2] += m[0][2] * x + m[1][2] * y;
    m[3][3] += m[0][3] * x + m[1][3] * y;
    if (flagBits == Rotation) {
      flagBits |= Translation;
    } else if (flagBits != (Rotation | Translation)) {
      flagBits = General;
    }
  }
}

void Matrix4x4::translate(const float x, const float y, const float z) {
  if (flagBits == Identity) {
    m[3][0] = x;
    m[3][1] = y;
    m[3][2] = z;
    flagBits = Translation;
  } else if (flagBits == Translation) {
    m[3][0] += x;
    m[3][1] += y;
    m[3][2] += z;
  } else if (flagBits == Scale) {
    m[3][0] = m[0][0] * x;
    m[3][1] = m[1][1] * y;
    m[3][2] = m[2][2] * z;
    flagBits |= Translation;
  } else if (flagBits == (Scale | Translation)) {
    m[3][0] += m[0][0] * x;
    m[3][1] += m[1][1] * y;
    m[3][2] += m[2][2] * z;
  } else {
    m[3][0] += m[0][0] * x + m[1][0] * y + m[2][0] * z;
    m[3][1] += m[0][1] * x + m[1][1] * y + m[2][1] * z;
    m[3][2] += m[0][2] * x + m[1][2] * y + m[2][2] * z;
    m[3][3] += m[0][3] * x + m[1][3] * y + m[2][3] * z;
    if (flagBits == Rotation) {
      flagBits |= Translation;
    } else if (flagBits != (Rotation | Translation)) {
      flagBits = General;
    }
  }
}

void Matrix4x4::rotate(const float angle, const Vector3D& vector) { rotate(angle, vector.x(), vector.y(), vector.z()); }

void Matrix4x4::rotate(const float angle, float x, float y, float z) {
  if (angle == 0.0f) {
    return;
  }
  Matrix4x4 m(1);
  float c, s;
  if (angle == 90.0f || angle == -270.0f) {
    s = 1.0f;
    c = 0.0f;
  } else if (angle == -90.0f || angle == 270.0f) {
    s = -1.0f;
    c = 0.0f;
  } else if (angle == 180.0f || angle == -180.0f) {
    s = 0.0f;
    c = -1.0f;
  } else {
    const float a = angle * static_cast<float>(M_PI) / 180.0f;
    c = std::cos(a);
    s = std::sin(a);
  }
  bool quick = false;
  if (x == 0.0f) {
    if (y == 0.0f) {
      if (z != 0.0f) {
        m.setToIdentity();
        m.m[0][0] = c;
        m.m[1][1] = c;
        if (z < 0.0f) {
          m.m[1][0] = s;
          m.m[0][1] = -s;
        } else {
          m.m[1][0] = -s;
          m.m[0][1] = s;
        }
        m.flagBits = General;
        quick = true;
      }
    } else if (z == 0.0f) {
      m.setToIdentity();
      m.m[0][0] = c;
      m.m[2][2] = c;
      if (y < 0.0f) {
        m.m[2][0] = -s;
        m.m[0][2] = s;
      } else {
        m.m[2][0] = s;
        m.m[0][2] = -s;
      }
      m.flagBits = General;
      quick = true;
    }
  } else if (y == 0.0f && z == 0.0f) {
    m.setToIdentity();
    m.m[1][1] = c;
    m.m[2][2] = c;
    if (x < 0.0f) {
      m.m[2][1] = s;
      m.m[1][2] = -s;
    } else {
      m.m[2][1] = -s;
      m.m[1][2] = s;
    }
    m.flagBits = General;
    quick = true;
  }
  if (!quick) {
    if (auto len = x * x + y * y + z * z; !fuzzyIsNull(len - 1.0f) && !fuzzyIsNull(len)) {
      len = sqrtf(len);
      x /= len;
      y /= len;
      z /= len;
    }
    const float ic = 1.0f - c;
    m.m[0][0] = x * x * ic + c;
    m.m[1][0] = x * y * ic - z * s;
    m.m[2][0] = x * z * ic + y * s;
    m.m[3][0] = 0.0f;
    m.m[0][1] = y * x * ic + z * s;
    m.m[1][1] = y * y * ic + c;
    m.m[2][1] = y * z * ic - x * s;
    m.m[3][1] = 0.0f;
    m.m[0][2] = x * z * ic - y * s;
    m.m[1][2] = y * z * ic + x * s;
    m.m[2][2] = z * z * ic + c;
    m.m[3][2] = 0.0f;
    m.m[0][3] = 0.0f;
    m.m[1][3] = 0.0f;
    m.m[2][3] = 0.0f;
    m.m[3][3] = 1.0f;
  }
  const int flags = flagBits;
  *this *= m;
  if (flags != Identity) {
    flagBits = flags | Rotation;
  } else {
    flagBits = Rotation;
  }
}

void Matrix4x4::projectedRotate(const float angle, float x, float y, float z) {
  if (angle == 0.0f) {
    return;
  }
  Matrix4x4 m(1);
  float c, s;
  if (angle == 90.0f || angle == -270.0f) {
    s = 1.0f;
    c = 0.0f;
  } else if (angle == -90.0f || angle == 270.0f) {
    s = -1.0f;
    c = 0.0f;
  } else if (angle == 180.0f || angle == -180.0f) {
    s = 0.0f;
    c = -1.0f;
  } else {
    const float a = angle * static_cast<float>(M_PI) / 180.0f;
    c = std::cos(a);
    s = std::sin(a);
  }
  bool quick = false;
  if (x == 0.0f) {
    if (y == 0.0f) {
      if (z != 0.0f) {
        m.setToIdentity();
        m.m[0][0] = c;
        m.m[1][1] = c;
        if (z < 0.0f) {
          m.m[1][0] = s;
          m.m[0][1] = -s;
        } else {
          m.m[1][0] = -s;
          m.m[0][1] = s;
        }
        m.flagBits = General;
        quick = true;
      }
    } else if (z == 0.0f) {
      m.setToIdentity();
      m.m[0][0] = c;
      m.m[2][2] = 1.0f;
      if (y < 0.0f) {
        m.m[0][3] = -s * InvDistToPlane;
      } else {
        m.m[0][3] = s * InvDistToPlane;
      }
      m.flagBits = General;
      quick = true;
    }
  } else if (y == 0.0f && z == 0.0f) {
    m.setToIdentity();
    m.m[1][1] = c;
    m.m[2][2] = 1.0f;
    if (x < 0.0f) {
      m.m[1][3] = s * InvDistToPlane;
    } else {
      m.m[1][3] = -s * InvDistToPlane;
    }
    m.flagBits = General;
    quick = true;
  }
  if (!quick) {
    if (float len = x * x + y * y + z * z; !fuzzyIsNull(len - 1.0f) && !fuzzyIsNull(len)) {
      len = sqrtf(len);
      x /= len;
      y /= len;
      z /= len;
    }
    const float ic = 1.0f - c;
    m.m[0][0] = x * x * ic + c;
    m.m[1][0] = x * y * ic - z * s;
    m.m[2][0] = 0.0f;
    m.m[3][0] = 0.0f;
    m.m[0][1] = y * x * ic + z * s;
    m.m[1][1] = y * y * ic + c;
    m.m[2][1] = 0.0f;
    m.m[3][1] = 0.0f;
    m.m[0][2] = 0.0f;
    m.m[1][2] = 0.0f;
    m.m[2][2] = 1.0f;
    m.m[3][2] = 0.0f;
    m.m[0][3] = (x * z * ic - y * s) * -InvDistToPlane;
    m.m[1][3] = (y * z * ic + x * s) * -InvDistToPlane;
    m.m[2][3] = 0.0f;
    m.m[3][3] = 1.0f;
  }
  const int flags = flagBits;
  *this *= m;
  if (flags != Identity) {
    flagBits = flags | Rotation;
  } else {
    flagBits = Rotation;
  }
}

void Matrix4x4::ortho(const Rect& rect) {
  ortho(static_cast<float>(rect.x()), static_cast<float>(rect.x() + rect.width()), static_cast<float>(rect.y() + rect.height()), static_cast<float>(rect.y()), -1.0f, 1.0f);
}

void Matrix4x4::ortho(const RectF& rect) { ortho(rect.left(), rect.right(), rect.bottom(), rect.top(), -1.0f, 1.0f); }

void Matrix4x4::ortho(const float left, const float right, const float bottom, const float top, const float nearPlane, const float farPlane) {
  if (left == right || bottom == top || nearPlane == farPlane) {
    return;
  }

  const float width = right - left;
  const float invheight = top - bottom;
  const float clip = farPlane - nearPlane;
  if (clip == 2.0f && nearPlane + farPlane == 0.0f) {
    translate({-(left + right) / width, -(top + bottom) / invheight, 0.0f});
    scale({2.0f / width, 2.0f / invheight, -1.0f});
    return;
  }
  Matrix4x4 m(1);
  m.m[0][0] = 2.0f / width;
  m.m[1][0] = 0.0f;
  m.m[2][0] = 0.0f;
  m.m[3][0] = -(left + right) / width;
  m.m[0][1] = 0.0f;
  m.m[1][1] = 2.0f / invheight;
  m.m[2][1] = 0.0f;
  m.m[3][1] = -(top + bottom) / invheight;
  m.m[0][2] = 0.0f;
  m.m[1][2] = 0.0f;
  m.m[2][2] = -2.0f / clip;
  m.m[3][2] = -(nearPlane + farPlane) / clip;
  m.m[0][3] = 0.0f;
  m.m[1][3] = 0.0f;
  m.m[2][3] = 0.0f;
  m.m[3][3] = 1.0f;

  *this *= m;
}

void Matrix4x4::frustum(const float left, const float right, const float bottom, const float top, const float nearPlane, const float farPlane) {
  if (left == right || bottom == top || nearPlane == farPlane) {
    return;
  }

  Matrix4x4 m(1);
  const float width = right - left;
  const float invheight = top - bottom;
  const float clip = farPlane - nearPlane;
  m.m[0][0] = 2.0f * nearPlane / width;
  m.m[1][0] = 0.0f;
  m.m[2][0] = (left + right) / width;
  m.m[3][0] = 0.0f;
  m.m[0][1] = 0.0f;
  m.m[1][1] = 2.0f * nearPlane / invheight;
  m.m[2][1] = (top + bottom) / invheight;
  m.m[3][1] = 0.0f;
  m.m[0][2] = 0.0f;
  m.m[1][2] = 0.0f;
  m.m[2][2] = -(nearPlane + farPlane) / clip;
  m.m[3][2] = -2.0f * nearPlane * farPlane / clip;
  m.m[0][3] = 0.0f;
  m.m[1][3] = 0.0f;
  m.m[2][3] = -1.0f;
  m.m[3][3] = 0.0f;
  *this *= m;
}

void Matrix4x4::perspective(const float angle, const float aspect, const float nearPlane, const float farPlane) {
  if (nearPlane == farPlane || aspect == 0.0f) {
    return;
  }
  Matrix4x4 m(1);
  const float radians = angle / 2.0f * static_cast<float>(M_PI) / 180.0f;
  const float sine = std::sin(radians);
  if (sine == 0.0f)
    return;
  const float cotan = std::cos(radians) / sine;
  const float clip = farPlane - nearPlane;
  m.m[0][0] = cotan / aspect;
  m.m[1][0] = 0.0f;
  m.m[2][0] = 0.0f;
  m.m[3][0] = 0.0f;
  m.m[0][1] = 0.0f;
  m.m[1][1] = cotan;
  m.m[2][1] = 0.0f;
  m.m[3][1] = 0.0f;
  m.m[0][2] = 0.0f;
  m.m[1][2] = 0.0f;
  m.m[2][2] = -(nearPlane + farPlane) / clip;
  m.m[3][2] = -(2.0f * nearPlane * farPlane) / clip;
  m.m[0][3] = 0.0f;
  m.m[1][3] = 0.0f;
  m.m[2][3] = -1.0f;
  m.m[3][3] = 0.0f;

  *this *= m;
}

void Matrix4x4::lookAt(const Vector3D& eye, const Vector3D& center, const Vector3D& up) {
  const auto forward = (center - eye).normalized();
  const auto side = Vector3D::crossProduct(forward, up).normalized();
  const auto upVector = Vector3D::crossProduct(side, forward);

  Matrix4x4 m(1);

  m.m[0][0] = side.x();
  m.m[1][0] = side.y();
  m.m[2][0] = side.z();
  m.m[3][0] = 0.0f;
  m.m[0][1] = upVector.x();
  m.m[1][1] = upVector.y();
  m.m[2][1] = upVector.z();
  m.m[3][1] = 0.0f;
  m.m[0][2] = -forward.x();
  m.m[1][2] = -forward.y();
  m.m[2][2] = -forward.z();
  m.m[3][2] = 0.0f;
  m.m[0][3] = 0.0f;
  m.m[1][3] = 0.0f;
  m.m[2][3] = 0.0f;
  m.m[3][3] = 1.0f;

  *this *= m;
  translate(-eye);
}

void Matrix4x4::flipCoordinates() {
  if (flagBits == Scale || flagBits == (Scale | Translation)) {
    m[1][1] = -m[1][1];
    m[2][2] = -m[2][2];
  } else if (flagBits == Translation) {
    m[1][1] = -m[1][1];
    m[2][2] = -m[2][2];
    flagBits |= Scale;
  } else if (flagBits == Identity) {
    m[1][1] = -1.0f;
    m[2][2] = -1.0f;
    flagBits = Scale;
  } else {
    m[1][0] = -m[1][0];
    m[1][1] = -m[1][1];
    m[1][2] = -m[1][2];
    m[1][3] = -m[1][3];
    m[2][0] = -m[2][0];
    m[2][1] = -m[2][1];
    m[2][2] = -m[2][2];
    m[2][3] = -m[2][3];
    flagBits = General;
  }
}

void Matrix4x4::copyDataTo(float* values) const {
  for (int row = 0; row < 4; ++row)
    for (int col = 0; col < 4; ++col)
      values[row * 4 + col] = m[col][row];
}

Rect Matrix4x4::mapRect(const Rect& rect) const {
  if (flagBits == (Translation | Scale) || flagBits == Scale) {
    auto x = static_cast<float>(rect.x()) * m[0][0] + m[3][0];
    auto y = static_cast<float>(rect.y()) * m[1][1] + m[3][1];
    auto w = static_cast<float>(rect.width()) * m[0][0];
    auto h = static_cast<float>(rect.height()) * m[1][1];
    if (w < 0) {
      w = -w;
      x -= w;
    }
    if (h < 0) {
      h = -h;
      y -= h;
    }
    return {oRound(x), oRound(y), oRound(w), oRound(h)};
  }
  if (flagBits == Translation) {
    return {oRound(static_cast<float>(rect.x()) + m[3][0]), oRound(static_cast<float>(rect.y()) + m[3][1]), rect.width(), rect.height()};
  }

  const Point tl = map(rect.topLeft());
  const Point tr = map(Point(rect.x() + rect.width(), rect.y()));
  const Point bl = map(Point(rect.x(), rect.y() + rect.height()));
  const Point br = map(Point(rect.x() + rect.width(), rect.y() + rect.height()));

  const int xmin = std::min(std::min(tl.x(), tr.x()), std::min(bl.x(), br.x()));
  const int xmax = std::max(std::max(tl.x(), tr.x()), std::max(bl.x(), br.x()));
  const int ymin = std::min(std::min(tl.y(), tr.y()), std::min(bl.y(), br.y()));
  const int ymax = std::max(std::max(tl.y(), tr.y()), std::max(bl.y(), br.y()));

  return {xmin, ymin, xmax - xmin, ymax - ymin};
}

RectF Matrix4x4::mapRect(const RectF& rect) const {
  if (flagBits == (Translation | Scale) || flagBits == Scale) {
    auto x = rect.x() * m[0][0] + m[3][0];
    auto y = rect.y() * m[1][1] + m[3][1];
    auto w = rect.width() * m[0][0];
    auto h = rect.height() * m[1][1];
    if (w < 0) {
      w = -w;
      x -= w;
    }
    if (h < 0) {
      h = -h;
      y -= h;
    }
    return {x, y, w, h};
  }
  if (flagBits == Translation) {
    return rect.translated(m[3][0], m[3][1]);
  }

  const auto tl = map(rect.topLeft());
  const auto tr = map(rect.topRight());
  const auto bl = map(rect.bottomLeft());
  const auto br = map(rect.bottomRight());

  const auto xmin = std::min(std::min(tl.x(), tr.x()), std::min(bl.x(), br.x()));
  const auto xmax = std::max(std::max(tl.x(), tr.x()), std::max(bl.x(), br.x()));
  const auto ymin = std::min(std::min(tl.y(), tr.y()), std::min(bl.y(), br.y()));
  const auto ymax = std::max(std::max(tl.y(), tr.y()), std::max(bl.y(), br.y()));

  return {PointF{xmin, ymin}, PointF{xmax, ymax}};
}

Matrix4x4 Matrix4x4::orthonormalInverse() const {
  Matrix4x4 result(1);
  result.m[0][0] = m[0][0];
  result.m[1][0] = m[0][1];
  result.m[2][0] = m[0][2];
  result.m[0][1] = m[1][0];
  result.m[1][1] = m[1][1];
  result.m[2][1] = m[1][2];
  result.m[0][2] = m[2][0];
  result.m[1][2] = m[2][1];
  result.m[2][2] = m[2][2];
  result.m[0][3] = 0.0f;
  result.m[1][3] = 0.0f;
  result.m[2][3] = 0.0f;
  result.m[3][0] = -(result.m[0][0] * m[3][0] + result.m[1][0] * m[3][1] + result.m[2][0] * m[3][2]);
  result.m[3][1] = -(result.m[0][1] * m[3][0] + result.m[1][1] * m[3][1] + result.m[2][1] * m[3][2]);
  result.m[3][2] = -(result.m[0][2] * m[3][0] + result.m[1][2] * m[3][1] + result.m[2][2] * m[3][2]);
  result.m[3][3] = 1.0f;

  return result;
}
void Matrix4x4::optimize() {
  if (m[3][3] != 1.0f) {
    flagBits = General;
    return;
  }

  if (m[1][0] != 0.0f || m[2][0] != 0.0f || m[2][1] != 0.0f) {
    flagBits = General;
    return;
  }
  if (m[0][1] != 0.0f || m[0][2] != 0.0f || m[0][3] != 0.0f || m[1][2] != 0.0f || m[1][3] != 0.0f || m[2][3] != 0.0f) {
    flagBits = General;
    return;
  }

  const bool identityAlongDiagonal = m[0][0] == 1.0f && m[1][1] == 1.0f && m[2][2] == 1.0f;
  const bool translationPresent = m[3][0] != 0.0f || m[3][1] != 0.0f || m[3][2] != 0.0f;

  if (translationPresent && identityAlongDiagonal)
    flagBits = Translation;
  else if (translationPresent)
    flagBits = Translation | Scale;
  else if (identityAlongDiagonal)
    flagBits = Identity;
  else
    flagBits = Scale;
}


code:
#pragma once
#include "GenericMatrix.hpp"
#include "MathGlobals.hpp"
#include "Rect.hpp"
#include "Vector3D.hpp"
#include "Vector4D.hpp"

class Matrix4x4 {
public:
  Matrix4x4() { setToIdentity(); }
  explicit Matrix4x4(const float* values);
  Matrix4x4(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44);
  template <int N, int M>
  explicit Matrix4x4(const GenericMatrix<N, M, float>& matrix);
  Matrix4x4(const float* values, int cols, int rows);

  const float& operator()(int row, int col) const;
  float& operator()(int row, int col);

  [[nodiscard]] Vector4D column(int index) const;
  void setColumn(int index, const Vector4D& value);

  [[nodiscard]] Vector4D row(int index) const;
  void setRow(int index, const Vector4D& value);

  [[nodiscard]] bool isIdentity() const;
  void setToIdentity();

  void fill(float value);

  [[nodiscard]] float determinant() const;
  [[nodiscard]] Matrix4x4 inverted(bool* invertible) const;
  [[nodiscard]] Matrix4x4 transposed() const;
  [[nodiscard]] Matrix3x3 normalMatrix() const;

  inline Matrix4x4& operator+=(const Matrix4x4& other);
  inline Matrix4x4& operator-=(const Matrix4x4& other);
  inline Matrix4x4& operator*=(const Matrix4x4& other);
  inline Matrix4x4& operator*=(float factor);
  Matrix4x4& operator/=(float divisor);
  inline bool operator==(const Matrix4x4& other) const;
  inline bool operator!=(const Matrix4x4& other) const;

  friend Matrix4x4 operator+(const Matrix4x4& m1, const Matrix4x4& m2);
  friend Matrix4x4 operator-(const Matrix4x4& m1, const Matrix4x4& m2);
  friend Matrix4x4 operator*(const Matrix4x4& m1, const Matrix4x4& m2);
  friend Vector3D operator*(const Matrix4x4& matrix, const Vector3D& vector);
  friend Vector3D operator*(const Vector3D& vector, const Matrix4x4& matrix);
  friend Vector4D operator*(const Vector4D& vector, const Matrix4x4& matrix);
  friend Vector4D operator*(const Matrix4x4& matrix, const Vector4D& vector);
  friend Point operator*(const Point& point, const Matrix4x4& matrix);
  friend PointF operator*(const PointF& point, const Matrix4x4& matrix);
  friend Matrix4x4 operator-(const Matrix4x4& matrix);
  friend Point operator*(const Matrix4x4& matrix, const Point& point);
  friend PointF operator*(const Matrix4x4& matrix, const PointF& point);
  friend Matrix4x4 operator*(float factor, const Matrix4x4& matrix);
  friend Matrix4x4 operator*(const Matrix4x4& matrix, float factor);
  friend Matrix4x4 operator/(const Matrix4x4& matrix, float divisor);

  friend inline bool fuzzyCompare(const Matrix4x4& m1, const Matrix4x4& m2);

  void scale(const Vector3D& vector);
  void translate(const Vector3D& vector);
  void rotate(float angle, const Vector3D& vector);
  void scale(float x, float y);
  void scale(float x, float y, float z);
  void scale(float factor);
  void translate(float x, float y);
  void translate(float x, float y, float z);
  void rotate(float angle, float x, float y, float z = 0.0f);
  // void rotate(const Quaternion& quaternion);

  void ortho(const Rect& rect);
  void ortho(const RectF& rect);
  void ortho(float left, float right, float bottom, float top, float nearPlane, float farPlane);
  void frustum(float left, float right, float bottom, float top, float nearPlane, float farPlane);
  void perspective(float angle, float aspect, float nearPlane, float farPlane);
  void lookAt(const Vector3D& eye, const Vector3D& center, const Vector3D& up);
  void flipCoordinates();

  void copyDataTo(float* values) const;

  [[nodiscard]] Point map(const Point& point) const;
  [[nodiscard]] PointF map(const PointF& point) const;
  [[nodiscard]] Vector3D map(const Vector3D& point) const;
  [[nodiscard]] Vector3D mapVector(const Vector3D& vector) const;
  [[nodiscard]] Vector4D map(const Vector4D& point) const;
  [[nodiscard]] Rect mapRect(const Rect& rect) const;
  [[nodiscard]] RectF mapRect(const RectF& rect) const;

  template <int N, int M>
  GenericMatrix<N, M, float> toGenericMatrix() const;

  float* data();
  [[nodiscard]] const float* data() const { return m[0]; }

  void optimize();

private:
  float m[4][4]{};
  int flagBits{};
  enum {
    Identity = 1 << 0,
    General = 1 << 1,
    Translation = 1 << 2,
    Scale = 1 << 3,
    Rotation = 1 << 4,
  };

  explicit Matrix4x4(int) { flagBits = General; }

  [[nodiscard]] Matrix4x4 orthonormalInverse() const;
  void projectedRotate(float angle, float x, float y, float z);
};

inline Matrix4x4::Matrix4x4(const float m11, const float m12, const float m13, const float m14, const float m21, const float m22, const float m23, const float m24, const float m31, const float m32,
                            const float m33, const float m34, const float m41, const float m42, const float m43, const float m44) {
  m[0][0] = m11;
  m[0][1] = m21;
  m[0][2] = m31;
  m[0][3] = m41;
  m[1][0] = m12;
  m[1][1] = m22;
  m[1][2] = m32;
  m[1][3] = m42;
  m[2][0] = m13;
  m[2][1] = m23;
  m[2][2] = m33;
  m[2][3] = m43;
  m[3][0] = m14;
  m[3][1] = m24;
  m[3][2] = m34;
  m[3][3] = m44;
  flagBits = General;
}
template <int N, int M>
Matrix4x4::Matrix4x4(const GenericMatrix<N, M, float>& matrix) {
  const float* values = matrix.data();
  for (int matrixCol = 0; matrixCol < 4; ++matrixCol) {
    for (int matrixRow = 0; matrixRow < 4; ++matrixRow) {
      if (matrixCol < N && matrixRow < M)
        m[matrixCol][matrixRow] = values[matrixCol * M + matrixRow];
      else if (matrixCol == matrixRow)
        m[matrixCol][matrixRow] = 1.0f;
      else
        m[matrixCol][matrixRow] = 0.0f;
    }
  }
  flagBits = General;
}
template <int N, int M>
GenericMatrix<N, M, float> Matrix4x4::toGenericMatrix() const {
  GenericMatrix<N, M, float> result;
  float* values = result.data();
  for (int matrixCol = 0; matrixCol < N; ++matrixCol) {
    for (int matrixRow = 0; matrixRow < M; ++matrixRow) {
      if (matrixCol < 4 && matrixRow < 4)
        values[matrixCol * M + matrixRow] = m[matrixCol][matrixRow];
      else if (matrixCol == matrixRow)
        values[matrixCol * M + matrixRow] = 1.0f;
      else
        values[matrixCol * M + matrixRow] = 0.0f;
    }
  }
  return result;
}

inline const float& Matrix4x4::operator()(const int row, const int col) const {
  assert(row >= 0 && row < 4 && col >= 0 && col < 4);
  return m[col][row];
}

inline float& Matrix4x4::operator()(const int row, const int col) {
  assert(row >= 0 && row < 4 && col >= 0 && col < 4);
  flagBits = General;
  return m[col][row];
}

inline Vector4D Matrix4x4::column(const int index) const {
  assert(index >= 0 && index < 4);
  return {m[index][0], m[index][1], m[index][2], m[index][3]};
}

inline void Matrix4x4::setColumn(const int index, const Vector4D& value) {
  assert(index >= 0 && index < 4);
  m[index][0] = value.x();
  m[index][1] = value.y();
  m[index][2] = value.z();
  m[index][3] = value.w();
  flagBits = General;
}

inline Vector4D Matrix4x4::row(const int index) const {
  assert(index >= 0 && index < 4);
  return {m[0][index], m[1][index], m[2][index], m[3][index]};
}

inline void Matrix4x4::setRow(const int index, const Vector4D& value) {
  assert(index >= 0 && index < 4);
  m[0][index] = value.x();
  m[1][index] = value.y();
  m[2][index] = value.z();
  m[3][index] = value.w();
  flagBits = General;
}

Matrix4x4 operator/(const Matrix4x4& matrix, float divisor);

inline bool Matrix4x4::isIdentity() const {
  if (flagBits == Identity)
    return true;
  if (m[0][0] != 1.0f || m[0][1] != 0.0f || m[0][2] != 0.0f)
    return false;
  if (m[0][3] != 0.0f || m[1][0] != 0.0f || m[1][1] != 1.0f)
    return false;
  if (m[1][2] != 0.0f || m[1][3] != 0.0f || m[2][0] != 0.0f)
    return false;
  if (m[2][1] != 0.0f || m[2][2] != 1.0f || m[2][3] != 0.0f)
    return false;
  if (m[3][0] != 0.0f || m[3][1] != 0.0f || m[3][2] != 0.0f)
    return false;
  return m[3][3] == 1.0f;
}

inline void Matrix4x4::setToIdentity() {
  m[0][0] = 1.0f;
  m[0][1] = 0.0f;
  m[0][2] = 0.0f;
  m[0][3] = 0.0f;
  m[1][0] = 0.0f;
  m[1][1] = 1.0f;
  m[1][2] = 0.0f;
  m[1][3] = 0.0f;
  m[2][0] = 0.0f;
  m[2][1] = 0.0f;
  m[2][2] = 1.0f;
  m[2][3] = 0.0f;
  m[3][0] = 0.0f;
  m[3][1] = 0.0f;
  m[3][2] = 0.0f;
  m[3][3] = 1.0f;
  flagBits = Identity;
}

inline void Matrix4x4::fill(const float value) {
  m[0][0] = value;
  m[0][1] = value;
  m[0][2] = value;
  m[0][3] = value;
  m[1][0] = value;
  m[1][1] = value;
  m[1][2] = value;
  m[1][3] = value;
  m[2][0] = value;
  m[2][1] = value;
  m[2][2] = value;
  m[2][3] = value;
  m[3][0] = value;
  m[3][1] = value;
  m[3][2] = value;
  m[3][3] = value;
  flagBits = General;
}

inline Matrix4x4& Matrix4x4::operator+=(const Matrix4x4& other) {
  m[0][0] += other.m[0][0];
  m[0][1] += other.m[0][1];
  m[0][2] += other.m[0][2];
  m[0][3] += other.m[0][3];
  m[1][0] += other.m[1][0];
  m[1][1] += other.m[1][1];
  m[1][2] += other.m[1][2];
  m[1][3] += other.m[1][3];
  m[2][0] += other.m[2][0];
  m[2][1] += other.m[2][1];
  m[2][2] += other.m[2][2];
  m[2][3] += other.m[2][3];
  m[3][0] += other.m[3][0];
  m[3][1] += other.m[3][1];
  m[3][2] += other.m[3][2];
  m[3][3] += other.m[3][3];
  flagBits = General;
  return *this;
}

inline Matrix4x4& Matrix4x4::operator-=(const Matrix4x4& other) {
  m[0][0] -= other.m[0][0];
  m[0][1] -= other.m[0][1];
  m[0][2] -= other.m[0][2];
  m[0][3] -= other.m[0][3];
  m[1][0] -= other.m[1][0];
  m[1][1] -= other.m[1][1];
  m[1][2] -= other.m[1][2];
  m[1][3] -= other.m[1][3];
  m[2][0] -= other.m[2][0];
  m[2][1] -= other.m[2][1];
  m[2][2] -= other.m[2][2];
  m[2][3] -= other.m[2][3];
  m[3][0] -= other.m[3][0];
  m[3][1] -= other.m[3][1];
  m[3][2] -= other.m[3][2];
  m[3][3] -= other.m[3][3];
  flagBits = General;
  return *this;
}

inline Matrix4x4& Matrix4x4::operator*=(const Matrix4x4& other) {
  if (flagBits == Identity) {
    *this = other;
    return *this;
  }
  if (other.flagBits == Identity) {
    return *this;
  }
  *this = *this * other;
  return *this;
}

inline Matrix4x4& Matrix4x4::operator*=(const float factor) {
  m[0][0] *= factor;
  m[0][1] *= factor;
  m[0][2] *= factor;
  m[0][3] *= factor;
  m[1][0] *= factor;
  m[1][1] *= factor;
  m[1][2] *= factor;
  m[1][3] *= factor;
  m[2][0] *= factor;
  m[2][1] *= factor;
  m[2][2] *= factor;
  m[2][3] *= factor;
  m[3][0] *= factor;
  m[3][1] *= factor;
  m[3][2] *= factor;
  m[3][3] *= factor;
  flagBits = General;
  return *this;
}

inline bool Matrix4x4::operator==(const Matrix4x4& other) const {
  return m[0][0] == other.m[0][0] && m[0][1] == other.m[0][1] && m[0][2] == other.m[0][2] && m[0][3] == other.m[0][3] && m[1][0] == other.m[1][0] && m[1][1] == other.m[1][1] &&
         m[1][2] == other.m[1][2] && m[1][3] == other.m[1][3] && m[2][0] == other.m[2][0] && m[2][1] == other.m[2][1] && m[2][2] == other.m[2][2] && m[2][3] == other.m[2][3] &&
         m[3][0] == other.m[3][0] && m[3][1] == other.m[3][1] && m[3][2] == other.m[3][2] && m[3][3] == other.m[3][3];
}

inline bool Matrix4x4::operator!=(const Matrix4x4& other) const {
  return m[0][0] != other.m[0][0] || m[0][1] != other.m[0][1] || m[0][2] != other.m[0][2] || m[0][3] != other.m[0][3] || m[1][0] != other.m[1][0] || m[1][1] != other.m[1][1] ||
         m[1][2] != other.m[1][2] || m[1][3] != other.m[1][3] || m[2][0] != other.m[2][0] || m[2][1] != other.m[2][1] || m[2][2] != other.m[2][2] || m[2][3] != other.m[2][3] ||
         m[3][0] != other.m[3][0] || m[3][1] != other.m[3][1] || m[3][2] != other.m[3][2] || m[3][3] != other.m[3][3];
}

inline Matrix4x4 operator+(const Matrix4x4& m1, const Matrix4x4& m2) {
  Matrix4x4 m(1);
  m.m[0][0] = m1.m[0][0] + m2.m[0][0];
  m.m[0][1] = m1.m[0][1] + m2.m[0][1];
  m.m[0][2] = m1.m[0][2] + m2.m[0][2];
  m.m[0][3] = m1.m[0][3] + m2.m[0][3];
  m.m[1][0] = m1.m[1][0] + m2.m[1][0];
  m.m[1][1] = m1.m[1][1] + m2.m[1][1];
  m.m[1][2] = m1.m[1][2] + m2.m[1][2];
  m.m[1][3] = m1.m[1][3] + m2.m[1][3];
  m.m[2][0] = m1.m[2][0] + m2.m[2][0];
  m.m[2][1] = m1.m[2][1] + m2.m[2][1];
  m.m[2][2] = m1.m[2][2] + m2.m[2][2];
  m.m[2][3] = m1.m[2][3] + m2.m[2][3];
  m.m[3][0] = m1.m[3][0] + m2.m[3][0];
  m.m[3][1] = m1.m[3][1] + m2.m[3][1];
  m.m[3][2] = m1.m[3][2] + m2.m[3][2];
  m.m[3][3] = m1.m[3][3] + m2.m[3][3];
  return m;
}

inline Matrix4x4 operator-(const Matrix4x4& m1, const Matrix4x4& m2) {
  Matrix4x4 m(1);
  m.m[0][0] = m1.m[0][0] - m2.m[0][0];
  m.m[0][1] = m1.m[0][1] - m2.m[0][1];
  m.m[0][2] = m1.m[0][2] - m2.m[0][2];
  m.m[0][3] = m1.m[0][3] - m2.m[0][3];
  m.m[1][0] = m1.m[1][0] - m2.m[1][0];
  m.m[1][1] = m1.m[1][1] - m2.m[1][1];
  m.m[1][2] = m1.m[1][2] - m2.m[1][2];
  m.m[1][3] = m1.m[1][3] - m2.m[1][3];
  m.m[2][0] = m1.m[2][0] - m2.m[2][0];
  m.m[2][1] = m1.m[2][1] - m2.m[2][1];
  m.m[2][2] = m1.m[2][2] - m2.m[2][2];
  m.m[2][3] = m1.m[2][3] - m2.m[2][3];
  m.m[3][0] = m1.m[3][0] - m2.m[3][0];
  m.m[3][1] = m1.m[3][1] - m2.m[3][1];
  m.m[3][2] = m1.m[3][2] - m2.m[3][2];
  m.m[3][3] = m1.m[3][3] - m2.m[3][3];
  return m;
}

inline Matrix4x4 operator*(const Matrix4x4& m1, const Matrix4x4& m2) {
  if (m1.flagBits == Matrix4x4::Identity) {
    return m2;
  }
  if (m2.flagBits == Matrix4x4::Identity) {
    return m1;
  }

  Matrix4x4 m(1);
  m.m[0][0] = m1.m[0][0] * m2.m[0][0] + m1.m[1][0] * m2.m[0][1] + m1.m[2][0] * m2.m[0][2] + m1.m[3][0] * m2.m[0][3];
  m.m[0][1] = m1.m[0][1] * m2.m[0][0] + m1.m[1][1] * m2.m[0][1] + m1.m[2][1] * m2.m[0][2] + m1.m[3][1] * m2.m[0][3];
  m.m[0][2] = m1.m[0][2] * m2.m[0][0] + m1.m[1][2] * m2.m[0][1] + m1.m[2][2] * m2.m[0][2] + m1.m[3][2] * m2.m[0][3];
  m.m[0][3] = m1.m[0][3] * m2.m[0][0] + m1.m[1][3] * m2.m[0][1] + m1.m[2][3] * m2.m[0][2] + m1.m[3][3] * m2.m[0][3];
  m.m[1][0] = m1.m[0][0] * m2.m[1][0] + m1.m[1][0] * m2.m[1][1] + m1.m[2][0] * m2.m[1][2] + m1.m[3][0] * m2.m[1][3];
  m.m[1][1] = m1.m[0][1] * m2.m[1][0] + m1.m[1][1] * m2.m[1][1] + m1.m[2][1] * m2.m[1][2] + m1.m[3][1] * m2.m[1][3];
  m.m[1][2] = m1.m[0][2] * m2.m[1][0] + m1.m[1][2] * m2.m[1][1] + m1.m[2][2] * m2.m[1][2] + m1.m[3][2] * m2.m[1][3];
  m.m[1][3] = m1.m[0][3] * m2.m[1][0] + m1.m[1][3] * m2.m[1][1] + m1.m[2][3] * m2.m[1][2] + m1.m[3][3] * m2.m[1][3];
  m.m[2][0] = m1.m[0][0] * m2.m[2][0] + m1.m[1][0] * m2.m[2][1] + m1.m[2][0] * m2.m[2][2] + m1.m[3][0] * m2.m[2][3];
  m.m[2][1] = m1.m[0][1] * m2.m[2][0] + m1.m[1][1] * m2.m[2][1] + m1.m[2][1] * m2.m[2][2] + m1.m[3][1] * m2.m[2][3];
  m.m[2][2] = m1.m[0][2] * m2.m[2][0] + m1.m[1][2] * m2.m[2][1] + m1.m[2][2] * m2.m[2][2] + m1.m[3][2] * m2.m[2][3];
  m.m[2][3] = m1.m[0][3] * m2.m[2][0] + m1.m[1][3] * m2.m[2][1] + m1.m[2][3] * m2.m[2][2] + m1.m[3][3] * m2.m[2][3];
  m.m[3][0] = m1.m[0][0] * m2.m[3][0] + m1.m[1][0] * m2.m[3][1] + m1.m[2][0] * m2.m[3][2] + m1.m[3][0] * m2.m[3][3];
  m.m[3][1] = m1.m[0][1] * m2.m[3][0] + m1.m[1][1] * m2.m[3][1] + m1.m[2][1] * m2.m[3][2] + m1.m[3][1] * m2.m[3][3];
  m.m[3][2] = m1.m[0][2] * m2.m[3][0] + m1.m[1][2] * m2.m[3][1] + m1.m[2][2] * m2.m[3][2] + m1.m[3][2] * m2.m[3][3];
  m.m[3][3] = m1.m[0][3] * m2.m[3][0] + m1.m[1][3] * m2.m[3][1] + m1.m[2][3] * m2.m[3][2] + m1.m[3][3] * m2.m[3][3];
  return m;
}

inline Vector3D operator*(const Vector3D& vector, const Matrix4x4& matrix) {
  const float x = vector.x() * matrix.m[0][0] + vector.y() * matrix.m[0][1] + vector.z() * matrix.m[0][2] + matrix.m[0][3];
  const float y = vector.x() * matrix.m[1][0] + vector.y() * matrix.m[1][1] + vector.z() * matrix.m[1][2] + matrix.m[1][3];
  const float z = vector.x() * matrix.m[2][0] + vector.y() * matrix.m[2][1] + vector.z() * matrix.m[2][2] + matrix.m[2][3];
  const float w = vector.x() * matrix.m[3][0] + vector.y() * matrix.m[3][1] + vector.z() * matrix.m[3][2] + matrix.m[3][3];
  if (w == 1.0f) {
    return {x, y, z};
  }
  return {x / w, y / w, z / w};
}

inline Vector3D operator*(const Matrix4x4& matrix, const Vector3D& vector) {
  if (matrix.flagBits == Matrix4x4::Identity) {
    return vector;
  }
  if (matrix.flagBits == Matrix4x4::Translation) {
    return {vector.x() + matrix.m[3][0], vector.y() + matrix.m[3][1], vector.z() + matrix.m[3][2]};
  }
  if (matrix.flagBits == (Matrix4x4::Translation | Matrix4x4::Scale)) {
    return {vector.x() * matrix.m[0][0] + matrix.m[3][0], vector.y() * matrix.m[1][1] + matrix.m[3][1], vector.z() * matrix.m[2][2] + matrix.m[3][2]};
  }
  if (matrix.flagBits == Matrix4x4::Scale) {
    return {vector.x() * matrix.m[0][0], vector.y() * matrix.m[1][1], vector.z() * matrix.m[2][2]};
  }
  const float x = vector.x() * matrix.m[0][0] + vector.y() * matrix.m[1][0] + vector.z() * matrix.m[2][0] + matrix.m[3][0];
  const float y = vector.x() * matrix.m[0][1] + vector.y() * matrix.m[1][1] + vector.z() * matrix.m[2][1] + matrix.m[3][1];
  const float z = vector.x() * matrix.m[0][2] + vector.y() * matrix.m[1][2] + vector.z() * matrix.m[2][2] + matrix.m[3][2];
  const float w = vector.x() * matrix.m[0][3] + vector.y() * matrix.m[1][3] + vector.z() * matrix.m[2][3] + matrix.m[3][3];
  if (w == 1.0f) {
    return {x, y, z};
  }
  return {x / w, y / w, z / w};
}

inline Vector4D operator*(const Vector4D& vector, const Matrix4x4& matrix) {
  const auto x = vector.x() * matrix.m[0][0] + vector.y() * matrix.m[0][1] + vector.z() * matrix.m[0][2] + vector.w() * matrix.m[0][3];
  const auto y = vector.x() * matrix.m[1][0] + vector.y() * matrix.m[1][1] + vector.z() * matrix.m[1][2] + vector.w() * matrix.m[1][3];
  const auto z = vector.x() * matrix.m[2][0] + vector.y() * matrix.m[2][1] + vector.z() * matrix.m[2][2] + vector.w() * matrix.m[2][3];
  const auto w = vector.x() * matrix.m[3][0] + vector.y() * matrix.m[3][1] + vector.z() * matrix.m[3][2] + vector.w() * matrix.m[3][3];
  return {x, y, z, w};
}

inline Vector4D operator*(const Matrix4x4& matrix, const Vector4D& vector) {
  const float x = vector.x() * matrix.m[0][0] + vector.y() * matrix.m[1][0] + vector.z() * matrix.m[2][0] + vector.w() * matrix.m[3][0];
  const float y = vector.x() * matrix.m[0][1] + vector.y() * matrix.m[1][1] + vector.z() * matrix.m[2][1] + vector.w() * matrix.m[3][1];
  const float z = vector.x() * matrix.m[0][2] + vector.y() * matrix.m[1][2] + vector.z() * matrix.m[2][2] + vector.w() * matrix.m[3][2];
  const float w = vector.x() * matrix.m[0][3] + vector.y() * matrix.m[1][3] + vector.z() * matrix.m[2][3] + vector.w() * matrix.m[3][3];
  return {x, y, z, w};
}

inline Point operator*(const Point& point, const Matrix4x4& matrix) {
  const auto xin = static_cast<float>(point.x());
  const auto yin = static_cast<float>(point.y());
  const auto x = xin * matrix.m[0][0] + yin * matrix.m[0][1] + matrix.m[0][3];
  const auto y = xin * matrix.m[1][0] + yin * matrix.m[1][1] + matrix.m[1][3];
  const auto w = xin * matrix.m[3][0] + yin * matrix.m[3][1] + matrix.m[3][3];
  if (w == 1.0f) {
    return {oRound(x), oRound(y)};
  }
  return {oRound(x / w), oRound(y / w)};
}

inline PointF operator*(const PointF& point, const Matrix4x4& matrix) {
  const auto xin = point.x();
  const auto yin = point.y();
  const auto x = xin * matrix.m[0][0] + yin * matrix.m[0][1] + matrix.m[0][3];
  const auto y = xin * matrix.m[1][0] + yin * matrix.m[1][1] + matrix.m[1][3];
  const auto w = xin * matrix.m[3][0] + yin * matrix.m[3][1] + matrix.m[3][3];
  if (w == 1.0f) {
    return {x, y};
  }

  return {x / w, y / w};
}

inline Point operator*(const Matrix4x4& matrix, const Point& point) {
  const auto xin = static_cast<float>(point.x());
  const auto yin = static_cast<float>(point.y());
  if (matrix.flagBits == Matrix4x4::Identity) {
    return point;
  }
  if (matrix.flagBits == Matrix4x4::Translation) {
    return {oRound(xin + matrix.m[3][0]), oRound(yin + matrix.m[3][1])};
  }
  if (matrix.flagBits == (Matrix4x4::Translation | Matrix4x4::Scale)) {
    return {oRound(xin * matrix.m[0][0] + matrix.m[3][0]), oRound(yin * matrix.m[1][1] + matrix.m[3][1])};
  }
  if (matrix.flagBits == Matrix4x4::Scale) {
    return {oRound(xin * matrix.m[0][0]), oRound(yin * matrix.m[1][1])};
  }

  const auto x = xin * matrix.m[0][0] + yin * matrix.m[1][0] + matrix.m[3][0];
  const auto y = xin * matrix.m[0][1] + yin * matrix.m[1][1] + matrix.m[3][1];
  const auto w = xin * matrix.m[0][3] + yin * matrix.m[1][3] + matrix.m[3][3];
  if (w == 1.0f) {
    return {oRound(x), oRound(y)};
  }
  return {oRound(x / w), oRound(y / w)};
}

inline PointF operator*(const Matrix4x4& matrix, const PointF& point) {
  const auto xin = point.x();
  const auto yin = point.y();
  if (matrix.flagBits == Matrix4x4::Identity) {
    return point;
  }
  if (matrix.flagBits == Matrix4x4::Translation) {
    return {xin + matrix.m[3][0], yin + matrix.m[3][1]};
  }
  if (matrix.flagBits == (Matrix4x4::Translation | Matrix4x4::Scale)) {
    return {xin * matrix.m[0][0] + matrix.m[3][0], yin * matrix.m[1][1] + matrix.m[3][1]};
  }
  if (matrix.flagBits == Matrix4x4::Scale) {
    return {xin * matrix.m[0][0], yin * matrix.m[1][1]};
  }
  const auto x = xin * matrix.m[0][0] + yin * matrix.m[1][0] + matrix.m[3][0];
  const auto y = xin * matrix.m[0][1] + yin * matrix.m[1][1] + matrix.m[3][1];
  const auto w = xin * matrix.m[0][3] + yin * matrix.m[1][3] + matrix.m[3][3];
  if (w == 1.0f) {
    return {x, y};
  }
  return {x / w, y / w};
}

inline Matrix4x4 operator-(const Matrix4x4& matrix) {
  Matrix4x4 m(1);
  m.m[0][0] = -matrix.m[0][0];
  m.m[0][1] = -matrix.m[0][1];
  m.m[0][2] = -matrix.m[0][2];
  m.m[0][3] = -matrix.m[0][3];
  m.m[1][0] = -matrix.m[1][0];
  m.m[1][1] = -matrix.m[1][1];
  m.m[1][2] = -matrix.m[1][2];
  m.m[1][3] = -matrix.m[1][3];
  m.m[2][0] = -matrix.m[2][0];
  m.m[2][1] = -matrix.m[2][1];
  m.m[2][2] = -matrix.m[2][2];
  m.m[2][3] = -matrix.m[2][3];
  m.m[3][0] = -matrix.m[3][0];
  m.m[3][1] = -matrix.m[3][1];
  m.m[3][2] = -matrix.m[3][2];
  m.m[3][3] = -matrix.m[3][3];
  return m;
}

inline Matrix4x4 operator*(const float factor, const Matrix4x4& matrix) {
  Matrix4x4 m(1);
  m.m[0][0] = matrix.m[0][0] * factor;
  m.m[0][1] = matrix.m[0][1] * factor;
  m.m[0][2] = matrix.m[0][2] * factor;
  m.m[0][3] = matrix.m[0][3] * factor;
  m.m[1][0] = matrix.m[1][0] * factor;
  m.m[1][1] = matrix.m[1][1] * factor;
  m.m[1][2] = matrix.m[1][2] * factor;
  m.m[1][3] = matrix.m[1][3] * factor;
  m.m[2][0] = matrix.m[2][0] * factor;
  m.m[2][1] = matrix.m[2][1] * factor;
  m.m[2][2] = matrix.m[2][2] * factor;
  m.m[2][3] = matrix.m[2][3] * factor;
  m.m[3][0] = matrix.m[3][0] * factor;
  m.m[3][1] = matrix.m[3][1] * factor;
  m.m[3][2] = matrix.m[3][2] * factor;
  m.m[3][3] = matrix.m[3][3] * factor;
  return m;
}

inline Matrix4x4 operator*(const Matrix4x4& matrix, const float factor) {
  Matrix4x4 m(1);
  m.m[0][0] = matrix.m[0][0] * factor;
  m.m[0][1] = matrix.m[0][1] * factor;
  m.m[0][2] = matrix.m[0][2] * factor;
  m.m[0][3] = matrix.m[0][3] * factor;
  m.m[1][0] = matrix.m[1][0] * factor;
  m.m[1][1] = matrix.m[1][1] * factor;
  m.m[1][2] = matrix.m[1][2] * factor;
  m.m[1][3] = matrix.m[1][3] * factor;
  m.m[2][0] = matrix.m[2][0] * factor;
  m.m[2][1] = matrix.m[2][1] * factor;
  m.m[2][2] = matrix.m[2][2] * factor;
  m.m[2][3] = matrix.m[2][3] * factor;
  m.m[3][0] = matrix.m[3][0] * factor;
  m.m[3][1] = matrix.m[3][1] * factor;
  m.m[3][2] = matrix.m[3][2] * factor;
  m.m[3][3] = matrix.m[3][3] * factor;
  return m;
}

inline bool fuzzyCompare(const Matrix4x4& m1, const Matrix4x4& m2) {
  return fuzzyCompare(m1.m[0][0], m2.m[0][0]) && fuzzyCompare(m1.m[0][1], m2.m[0][1]) && fuzzyCompare(m1.m[0][2], m2.m[0][2]) && fuzzyCompare(m1.m[0][3], m2.m[0][3]) &&
         fuzzyCompare(m1.m[1][0], m2.m[1][0]) && fuzzyCompare(m1.m[1][1], m2.m[1][1]) && fuzzyCompare(m1.m[1][2], m2.m[1][2]) && fuzzyCompare(m1.m[1][3], m2.m[1][3]) &&
         fuzzyCompare(m1.m[2][0], m2.m[2][0]) && fuzzyCompare(m1.m[2][1], m2.m[2][1]) && fuzzyCompare(m1.m[2][2], m2.m[2][2]) && fuzzyCompare(m1.m[2][3], m2.m[2][3]) &&
         fuzzyCompare(m1.m[3][0], m2.m[3][0]) && fuzzyCompare(m1.m[3][1], m2.m[3][1]) && fuzzyCompare(m1.m[3][2], m2.m[3][2]) && fuzzyCompare(m1.m[3][3], m2.m[3][3]);
}

inline Point Matrix4x4::map(const Point& point) const { return *this * point; }

inline PointF Matrix4x4::map(const PointF& point) const { return *this * point; }

inline Vector3D Matrix4x4::map(const Vector3D& point) const { return *this * point; }

inline Vector3D Matrix4x4::mapVector(const Vector3D& vector) const {
  if (flagBits == Identity || flagBits == Translation) {
    return vector;
  }
  if (flagBits == Scale || flagBits == (Translation | Scale)) {
    return {vector.x() * m[0][0], vector.y() * m[1][1], vector.z() * m[2][2]};
  }
  return {vector.x() * m[0][0] + vector.y() * m[1][0] + vector.z() * m[2][0], vector.x() * m[0][1] + vector.y() * m[1][1] + vector.z() * m[2][1],
          vector.x() * m[0][2] + vector.y() * m[1][2] + vector.z() * m[2][2]};
}

inline Vector4D Matrix4x4::map(const Vector4D& point) const { return *this * point; }

inline float* Matrix4x4::data() {
  flagBits = General;
  return m[0];
}


code: #include "Point.hpp"


code:
#pragma once
#include "imgui.h"
#include "MathGlobals.hpp"

#include <algorithm>
#include <cmath>

class Point {
public:
  Point() = default;
  Point(const int x, const int y)
  : mX(x)
  , mY(y) {}
  [[nodiscard]] bool isNull() const { return mX == 0 && mY == 0; }

#ifdef IMGUI_VERSION
  operator ImVec2() const { return ImVec2{static_cast<float>(mX), static_cast<float>(mY)}; }
#endif
  [[nodiscard]] int manhattanLength() const { return std::abs(mX) + std::abs(mY); }

  [[nodiscard]] int x() const { return mX; }
  [[nodiscard]] int& xr() { return mX; }
  [[nodiscard]] int y() const { return mY; }
  [[nodiscard]] int& yr() { return mY; }

  Point& operator+=(const Point& p) {
    mX += p.x();
    mY += p.y();
    return *this;
  }

  Point& operator-=(const Point& p) {
    mX -= p.x();
    mY -= p.y();
    return *this;
  }

  Point& operator*=(const float c) {
    mX = oRound(mX * c);
    mY = oRound(mY * c);
    return *this;
  }

  Point& operator/=(const float c) {
    mX = oRound(mX / c);
    mY = oRound(mY / c);
    return *this;
  }

  friend inline bool operator==(const Point&, const Point&);
  friend inline bool operator!=(const Point&, const Point&);
  friend inline Point operator+(const Point&, const Point&);
  friend inline Point operator-(const Point&, const Point&);
  friend inline Point operator*(const Point&, float);
  friend inline Point operator*(float, const Point&);
  friend inline Point operator-(const Point&);
  friend inline Point operator/(const Point&, float);

private:
  int mX{0};
  int mY{0};
};

inline bool operator==(const Point& lhs, const Point& rhs) { return lhs.mX == rhs.mX && lhs.mY == rhs.mY; }
inline bool operator!=(const Point& lhs, const Point& rhs) { return lhs.mX != rhs.mX || lhs.mY != rhs.mY; }

inline Point operator+(const Point& lhs, const Point& rhs) { return Point{lhs.mX + rhs.mX, lhs.mY + rhs.mY}; }
inline Point operator-(const Point& lhs, const Point& rhs) { return Point{lhs.mX - rhs.mX, lhs.mY - rhs.mY}; }
inline Point operator*(const Point& lhs, const float rhs) { return Point{oRound(lhs.mX * rhs), oRound(lhs.mY * rhs)}; }
inline Point operator*(const float lhs, const Point& rhs) { return Point{oRound(lhs * rhs.mX), oRound(lhs * rhs.mY)}; }

inline Point operator-(const Point& lhs) { return Point{-lhs.mX, -lhs.mY}; }

inline Point operator/(const Point& lhs, const float rhs) { return Point{(oRound(lhs.mX / rhs)), (oRound(lhs.mY / rhs))}; }

class PointF {
public:
  PointF() = default;
  PointF(const float x, const float y)
  : mX(x)
  , mY(y) {}

  PointF(const int x, const int y)
  : PointF(static_cast<float>(x), static_cast<float>(y)) {}

  explicit PointF(const Point& point)
  : PointF(point.x(), point.y()) {}

  [[nodiscard]] bool isNull() const { return mX == 0.f && mY == 0.f; }
  [[nodiscard]] float manhattanLength() const { return oAbs(mX) + oAbs(mY); }

#ifdef IMGUI_VERSION
  operator ImVec2() const { return ImVec2{mX, mY}; }
#endif

  [[nodiscard]] float x() const { return mX; }
  float& xr() { return mX; }
  [[nodiscard]] float y() const { return mY; }
  float& yr() { return mY; }

  PointF& operator+=(const PointF& p) {
    mX += p.x();
    mY += p.y();
    return *this;
  }

  PointF& operator-=(const PointF& p) {
    mX -= p.x();
    mY -= p.y();
    return *this;
  }

  PointF& operator*=(const float c) {
    mX *= c;
    mY *= c;
    return *this;
  }

  PointF& operator/=(const float c) {
    mX /= c;
    mY /= c;
    return *this;
  }

  friend inline bool operator==(const PointF&, const PointF&);
  friend inline bool operator!=(const PointF&, const PointF&);
  friend inline PointF operator+(const PointF&, const PointF&);
  friend inline PointF operator-(const PointF&, const PointF&);
  friend inline PointF operator*(const PointF&, float);
  friend inline PointF operator*(float, const PointF&);
  friend inline PointF operator-(const PointF&);
  friend inline PointF operator/(const PointF&, float);

  [[nodiscard]] Point toPoint() const { return Point{oRound(mX), oRound(mY)}; }

private:
  float mX{0.f};
  float mY{0.f};
};

inline bool operator==(const PointF& lhs, const PointF& rhs) { return fuzzyIsNull(lhs.mX - rhs.mX) && fuzzyIsNull(lhs.mY - rhs.mY); }
inline bool operator!=(const PointF& lhs, const PointF& rhs) { return !fuzzyIsNull(lhs.mX - rhs.mX) || !fuzzyIsNull(lhs.mY - rhs.mY); }

inline PointF operator+(const PointF& lhs, const PointF& rhs) { return PointF{lhs.mX + rhs.mX, lhs.mY + rhs.mY}; }
inline PointF operator-(const PointF& lhs, const PointF& rhs) { return PointF{lhs.mX - rhs.mX, lhs.mY - rhs.mY}; }
inline PointF operator*(const PointF& lhs, const float rhs) { return PointF{lhs.mX * rhs, lhs.mY * rhs}; }
inline PointF operator*(const float lhs, const PointF& rhs) { return PointF{lhs * rhs.mX, lhs * rhs.mY}; }
inline PointF operator-(const PointF& lhs) { return PointF{-lhs.mX, -lhs.mY}; }
inline PointF operator/(const PointF& lhs, const float rhs) { return PointF{lhs.mX / rhs, lhs.mY / rhs}; }


code:
#include "OREMath/Rect.hpp"
#include <algorithm>

Rect Rect::normalized() const {
  Rect r;
  if (mRight < mLeft - 1) {
    r.mLeft = mRight;
    r.mRight = mLeft;
  } else {
    r.mLeft = mLeft;
    r.mRight = mRight;
  }
  if (mBottom < mTop - 1) {
    r.mTop = mBottom;
    r.mBottom = mTop;
  } else {
    r.mTop = mTop;
    r.mBottom = mBottom;
  }
  return r;
}

bool Rect::contains(const Point& p, const bool proper) const {
  int l, r;
  if (mRight < mLeft - 1) {
    l = mRight;
    r = mLeft;
  } else {
    l = mLeft;
    r = mRight;
  }
  if (proper) {
    if (p.x() <= l || p.x() >= r)
      return false;
  } else {
    if (p.x() < l || p.x() > r)
      return false;
  }
  int t, b;
  if (mBottom < mTop - 1) {
    t = mBottom;
    b = mTop;
  } else {
    t = mTop;
    b = mBottom;
  }
  if (proper) {
    if (p.y() <= t || p.y() >= b)
      return false;
  } else {
    if (p.y() < t || p.y() > b)
      return false;
  }
  return true;
}

bool Rect::contains(const Rect& r, const bool proper) const {
  if (isNull() || r.isNull())
    return false;

  int l1 = mLeft;
  int r1 = mLeft;
  if (mRight - mLeft + 1 < 0)
    l1 = mRight;
  else
    r1 = mRight;

  int l2 = r.mLeft;
  int r2 = r.mLeft;
  if (r.mRight - r.mLeft + 1 < 0)
    l2 = r.mRight;
  else
    r2 = r.mRight;

  if (proper) {
    if (l2 <= l1 || r2 >= r1)
      return false;
  } else {
    if (l2 < l1 || r2 > r1)
      return false;
  }

  int t1 = mTop;
  int b1 = mTop;
  if (mBottom - mTop + 1 < 0)
    t1 = mBottom;
  else
    b1 = mBottom;

  int t2 = r.mTop;
  int b2 = r.mTop;
  if (r.mBottom - r.mTop + 1 < 0)
    t2 = r.mBottom;
  else
    b2 = r.mBottom;

  if (proper) {
    if (t2 <= t1 || b2 >= b1)
      return false;
  } else {
    if (t2 < t1 || b2 > b1)
      return false;
  }

  return true;
}

Rect Rect::operator|(const Rect& r) const {
  if (isNull()) {
    return r;
  }

  if (r.isNull()) {
    return *this;
  }

  int l1 = mLeft;
  int r1 = mLeft;
  if (mRight - mLeft + 1 < 0) {
    l1 = mRight;
  } else {
    r1 = mRight;
  }

  int l2 = r.mLeft;
  int r2 = r.mLeft;
  if (r.mRight - r.mLeft + 1 < 0) {
    l2 = r.mRight;
  } else {
    r2 = r.mRight;
  }

  int t1 = mTop;
  int b1 = mTop;
  if (mBottom - mTop + 1 < 0) {
    l1 = mBottom;
  } else {
    r1 = mBottom;
  }

  int t2 = r.mTop;
  int b2 = r.mTop;
  if (r.mBottom - r.mTop + 1 < 0) {
    t2 = r.mBottom;
  } else {
    b2 = r.mBottom;
  }

  Rect tmp;
  tmp.mLeft = std::min(l1, l2);
  tmp.mRight = std::max(r1, r2);
  tmp.mTop = std::min(t1, t2);
  tmp.mBottom = std::max(b1, b2);
  return tmp;
}

Rect Rect::operator&(const Rect& r) const {
  if (isNull() || r.isNull()) {
    return {};
  }

  int l1 = mLeft;
  int r1 = mLeft;
  if (mRight - mLeft + 1 < 0) {
    l1 = mRight;
  } else {
    r1 = mRight;
  }

  int l2 = r.mLeft;
  int r2 = r.mLeft;
  if (r.mRight - r.mLeft + 1 < 0) {
    l2 = r.mRight;
  } else {
    r2 = r.mRight;
  }

  if (l1 > r2 || l2 > r1) {
    return {};
  }

  int t1 = mTop;
  int b1 = mTop;
  if (mBottom - mTop + 1 < 0) {
    t1 = mBottom;
  } else {
    b1 = mBottom;
  }

  int t2 = r.mTop;
  int b2 = r.mTop;
  if (r.mBottom - r.mTop + 1 < 0) {
    t2 = r.mBottom;
  } else {
    b2 = r.mBottom;
  }

  if (t1 > b2 || t2 > b1) {
    return {};
  }

  Rect tmp;
  tmp.mLeft = std::max(l1, l2);
  tmp.mRight = std::min(r1, r2);
  tmp.mTop = std::max(t1, t2);
  tmp.mBottom = std::min(b1, b2);
  return tmp;
}

bool Rect::intersects(const Rect& r) const {
  if (isNull() || r.isNull()) {
    return false;
  }

  int l1 = mLeft;
  int r1 = mLeft;
  if (mRight - mLeft + 1 < 0) {
    l1 = mRight;
  } else {
    r1 = mRight;
  }

  int l2 = r.mLeft;
  int r2 = r.mLeft;
  if (r.mRight - r.mLeft + 1 < 0) {
    l2 = r.mRight;
  } else {
    r2 = r.mRight;
  }

  if (l1 > r2 || l2 > r1) {
    return false;
  }

  int t1 = mTop;
  int b1 = mTop;
  if (mBottom - mTop + 1 < 0) {
    t1 = mBottom;
  } else {
    b1 = mBottom;
  }

  int t2 = r.mTop;
  int b2 = r.mTop;
  if (r.mBottom - r.mTop + 1 < 0) {
    t2 = r.mBottom;
  } else {
    b2 = r.mBottom;
  }

  if (t1 > b2 || t2 > b1) {
    return false;
  }

  return true;
}

RectF RectF::normalized() const {
  RectF ret = *this;
  if (ret.mWidth < 0.f) {
    ret.mX += ret.mWidth;
    ret.mWidth = -ret.mWidth;
  }

  if (ret.mHeight < 0.f) {
    ret.mY += ret.mHeight;
    ret.mHeight = -ret.mHeight;
  }

  return ret;
}

bool RectF::contains(const PointF& p) const {
  float l = mX;
  float r = mX;
  if (mWidth < 0.f) {
    l += mWidth;
  } else {
    r += mWidth;
  }

  if (l == r) {
    return false;
  }

  if (p.x() < l || p.x() > r) {
    return false;
  }

  float t = mY;
  float b = mY;

  if (mHeight < 0.f) {
    t += mHeight;
  } else {
    b += mHeight;
  }

  if (t == b) {
    return false;
  }

  if (p.y() < t || p.y() > b) {
    return false;
  }

  return true;
}

bool RectF::contains(const RectF& r) const {
  float l1 = mX;
  float r1 = mX;
  if (mWidth < 0.f) {
    l1 += mWidth;
  } else {
    r1 += mWidth;
  }

  if (l1 == r1) {
    return false;
  }

  float l2 = r.mX;
  float r2 = r.mX;
  if (r.mWidth < 0.f) {
    l2 += r.mWidth;
  } else {
    r2 += r.mWidth;
  }

  if (l2 == r2) {
    return false;
  }

  float t1 = mY;
  float b1 = mY;
  if (mHeight < 0.f) {
    t1 += mHeight;
  } else {
    b1 += mHeight;
  }

  if (t1 == b1) {
    return false;
  }

  float t2 = r.mY;
  float b2 = r.mY;
  if (r.mHeight < 0.f) {
    t2 += r.mHeight;
  } else {
    b2 += r.mHeight;
  }

  if (t2 == b2) {
    return false;
  }

  return true;
}

RectF RectF::operator|(const RectF& other) const {
  if (isNull()) {
    return {};
  }

  if (other.isNull()) {
    return {};
  }

  float left = mX;
  float right = mX;
  if (mWidth < 0.f) {
    left += mWidth;
  } else {
    right += mWidth;
  }

  if (other.mWidth < 0.f) {
    left = std::min(left, other.mX + other.mWidth);
    right = std::max(right, other.mX);
  } else {
    left = std::min(left, other.mX);
    right = std::max(right, other.mX + other.mWidth);
  }

  float top = mY;
  float bottom = mY;
  if (mHeight < 0.f) {
    top += mHeight;
  } else {
    bottom += mHeight;
  }

  if (other.mHeight < 0.f) {
    top = std::min(top, other.mY + other.mHeight);
    bottom = std::max(bottom, other.mY);
  } else {
    top = std::min(top, other.mY);
    bottom = std::max(bottom, other.mY + other.mHeight);
  }

  return RectF{left, top, right - left, bottom - top};
}

RectF RectF::operator&(const RectF& other) const {
  float l1 = mX;
  float r1 = mX;

  if (mWidth < 0) {
    l1 += mWidth;
  } else {
    r1 += mWidth;
  }

  if (l1 == r1) {
    return {};
  }

  float l2 = other.mX;
  float r2 = other.mX;

  if (other.mWidth < 0) {
    l2 += other.mWidth;
  } else {
    r2 += other.mWidth;
  }

  if (l2 == r2) {
    return {};
  }
  if (l1 >= r2 || l2 >= r1) {
    return {};
  }

  float t1 = mY;
  float b1 = mY;

  if (mHeight < 0) {
    t1 += mHeight;
  } else {
    b1 += mHeight;
  }

  if (t1 == b1) {
    return {};
  }

  float t2 = other.mY;
  float b2 = other.mY;

  if (other.mHeight < 0) {
    t2 += other.mHeight;
  } else {
    b2 += other.mHeight;
  }

  if (t2 == b2) {
    return {};
  }

  if (t1 >= b2 || t2 >= b1) {
    return {};
  }

  RectF tmp;
  tmp.mX = std::max(l1, l2);
  tmp.mY = std::max(t1, t2);
  tmp.mWidth = std::min(r1, r2) - tmp.mX;
  tmp.mHeight = std::min(b1, b2) - tmp.mY;
  return tmp;
}

bool RectF::intersects(const RectF& r) const {
  float l1 = mX;
  float r1 = mX;

  if (mWidth < 0) {
    l1 += mWidth;
  } else {
    r1 += mWidth;
  }

  if (l1 == r1) {
    return false;
  }

  float l2 = r.mX;
  float r2 = r.mX;

  if (r.mWidth < 0) {
    l2 += r.mWidth;
  } else {
    r2 += r.mWidth;
  }

  if (l2 == r2) {
    return false;
  }

  if (l1 >= r2 || l2 >= r1) {
    return false;
  }

  float t1 = mY;
  float b1 = mY;

  if (mHeight < 0) {
    t1 += mHeight;
  } else {
    b1 += mHeight;
  }

  if (t1 == b1) {
    return false;
  }

  float t2 = r.mY;
  float b2 = r.mY;

  if (r.mHeight < 0) {
    t2 += r.mHeight;
  } else {
    b2 += r.mHeight;
  }

  if (t2 == b2) {
    return false;
  }

  if (t1 >= b2 || t2 >= b1) {
    return false;
  }

  return true;
}

Rect RectF::toAlignedRect() const {
  const auto xMin = static_cast<int>(std::floor(mX));
  const auto xMax = static_cast<int>(std::ceil(mX + mWidth));
  const auto yMin = static_cast<int>(std::floor(mY));
  const auto yMax = static_cast<int>(std::ceil(mY + mHeight));

  return Rect{xMin, yMin, xMax - xMin, yMax - yMin};
}


code:
#pragma once
#include "Point.hpp"
#include "Size.hpp"

class Rect {
public:
  Rect() = default;
  Rect(const Point& topLeft, const Point& bottomRight)
  : mLeft(topLeft.x())
  , mTop(topLeft.y())
  , mRight(bottomRight.x())
  , mBottom(bottomRight.y()) {}

  Rect(const Point& topLeft, const Size& size)
  : mLeft(topLeft.x())
  , mTop(topLeft.y())
  , mRight(mLeft + size.width() - 1)
  , mBottom(mTop + size.height() - 1) {}

  Rect(const int left, const int top, const int width, const int height)
  : mLeft(left)
  , mTop(top)
  , mRight(left + width - 1)
  , mBottom(top + height - 1) {}

  Rect normalized() const;

  bool isNull() const { return mRight == mLeft - 1 && mBottom == mTop - 1; }

  bool isEmpty() const { return mLeft > mRight || mTop > mBottom; }

  bool isValid() const { return mLeft <= mRight && mTop <= mBottom; }

  int left() const { return mLeft; }
  void setLeft(const int pos) { mLeft = pos; }

  int top() const { return mTop; }
  void setTop(const int pos) { mTop = pos; }

  int right() const { return mRight; }
  void setRight(const int pos) { mRight = pos; }

  int bottom() const { return mBottom; }
  void setBottom(const int pos) { mBottom = pos; }

  int x() const { return mLeft; }
  void setX(const int x) { mLeft = x; }

  int y() const { return mTop; }
  void setY(const int y) { mTop = y; }

  Point topLeft() const { return Point{mLeft, mTop}; }
  void setTopLeft(const Point& p) {
    mLeft = p.x();
    mTop = p.y();
  }

  Point bottomRight() const { return Point{mRight, mBottom}; }
  void setBottomRight(const Point& p) {
    mRight = p.x();
    mBottom = p.y();
  }

  Point topRight() const { return Point{mRight, mTop}; }
  void setTopRight(const Point& p) {
    mRight = p.x();
    mTop = p.y();
  }

  Point bottomLeft() const { return Point{mLeft, mBottom}; }
  void setBottomLeft(const Point& p) {
    mLeft = p.x();
    mBottom = p.y();
  }

  Point center() const { return Point{(mLeft + mRight) / 2, (mTop + mBottom) / 2}; }

  int width() const { return mRight - mLeft + 1; }
  int height() const { return mBottom - mTop + 1; }

  Size size() const { return Size{width(), height()}; }

  void translate(const int x, const int y) {
    mLeft += x;
    mTop += y;
    mRight += x;
    mBottom += y;
  }

  void translate(const Point& p) { translate(p.x(), p.y()); }

  Rect translated(const int x, const int y) const { return translated(Point(x, y)); }

  Rect translated(const Point& p) const { return Rect(Point(mLeft + p.x(), mTop + p.y()), Point(mRight + p.x(), mBottom + p.y())); }

  void moveTo(const int x, const int y) {
    mRight += x - mLeft;
    mBottom = y - mTop;
    mLeft = x;
    mTop = y;
  }

  void moveTo(const Point& p) { moveTo(p.x(), p.y()); }

  void moveLeft(const int pos) {
    mRight += (pos - mLeft);
    mLeft = pos;
  }

  void moveTop(const int pos) {
    mBottom += (pos - mTop);
    mTop = pos;
  }
  void moveRight(const int pos) {
    mLeft += (pos - mRight);
    mRight = pos;
  }

  void moveBottom(const int pos) {
    mTop += (pos - mBottom);
    mBottom = pos;
  }

  void moveTopLeft(const Point& pos) {
    moveLeft(pos.x());
    moveTop(pos.y());
  }

  void moveBottomRight(const Point& pos) {
    moveRight(pos.x());
    moveBottom(pos.y());
  }

  void moveTopRight(const Point& pos) {
    moveRight(pos.x());
    moveTop(pos.y());
  }

  void moveBottomLeft(const Point& pos) {
    moveLeft(pos.x());
    moveBottom(pos.y());
  }

  void moveCenter(const Point& pos) {
    const int w = mRight - mLeft;
    const int h = mBottom - mTop;
    mLeft = pos.x() - w / 2;
    mTop = pos.y() - w / 2;
    mRight = mLeft + w;
    mBottom = mTop + h;
  }

  void getRect(int* x, int* y, int* w, int* h) const {
    *x = mLeft;
    *y = mTop;
    *w = mRight - mLeft + 1;
    *h = mBottom - mTop + 1;
  }

  void setRect(const int x, const int y, const int width, const int height) {
    mLeft = x;
    mTop = y;
    mRight = x + width - 1;
    mBottom = y + height - 1;
  }

  void getCoords(int* left, int* top, int* right, int* bottom) const {
    *left = mLeft;
    *top = mTop;
    *right = mRight;
    *bottom = mBottom;
  }

  void setCoords(const int left, const int top, const int right, const int bottom) {
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
  }

  Rect adjusted(const int left, const int top, const int right, const int bottom) const { return Rect(Point{mLeft + left, mTop + top}, Point{mRight + right, mBottom + bottom}); }

  void adjust(const int left, const int top, const int right, const int bottom) {
    mLeft += left;
    mTop += top;
    mRight += right;
    mBottom += bottom;
  }

  void setWidth(const int w) { mRight = mLeft + w - 1; }
  void setHeight(const int h) { mBottom = mTop + h - 1; }

  void setSize(const Size& s) {
    mRight = s.width() + mLeft - 1;
    mBottom = s.height() + mTop - 1;
  }

  bool contains(const int x, const int y, const bool proper = false) const { return contains(Point(x, y), proper); }
  bool contains(const Point& p, bool proper = false) const;
  bool contains(const Rect& r, bool proper = false) const;
  bool intersects(const Rect& r) const;

  Rect united(const Rect& other) const { return *this | other; }

  Rect intersected(const Rect& other) const { return *this & other; }

  Rect operator|(const Rect& r) const;
  Rect& operator|=(const Rect& r) {
    *this = *this | r;
    return *this;
  }

  Rect operator&(const Rect& r) const;
  Rect& operator&=(const Rect& r) {
    *this = *this & r;
    return *this;
  }

  bool operator==(const Rect& other) const { return mLeft == other.mLeft && mTop == other.mTop && mRight == other.mRight && mBottom == other.mTop; }

  bool operator!=(const Rect& other) const { return mLeft != other.mLeft || mTop != other.mTop || mRight != other.mRight || mBottom != other.mTop; }

private:
  int mLeft{0};
  int mTop{0};
  int mRight{-1};
  int mBottom{-1};
};

class RectF {
public:
  RectF() = default;
  RectF(const PointF& topLeft, const PointF& bottomRight)
  : mX(topLeft.x())
  , mY(topLeft.y())
  , mWidth(topLeft.x() - bottomRight.x())
  , mHeight(topLeft.y() - bottomRight.y()) {}
  RectF(const PointF& topLeft, const SizeF& size)
  : mX(topLeft.x())
  , mY(topLeft.y())
  , mWidth(size.width())
  , mHeight(size.height()) {}
  RectF(const float left, const float top, const float width, const float height)
  : mX(left)
  , mY(top)
  , mWidth(width)
  , mHeight(height) {}

  bool isNull() const { return mWidth == 0.f && mHeight == 0.f; }
  bool isEmpty() const { return mWidth <= 0.f || mHeight <= 0.f; }
  bool isValid() const { return mWidth > 0.f && mHeight > 0.f; }

  RectF normalized() const;

  float left() const { return mX; }
  float top() const { return mY; }
  float right() const { return mX + mWidth; }
  float bottom() const { return mY + mHeight; }

  float x() const { return mX; }
  float y() const { return mY; }
  void setX(const float x) { setLeft(x); }
  void setY(const float y) { setTop(y); }

  void setLeft(const float pos) {
    const auto diff = pos - mX;
    mX += diff;
    mWidth += diff;
  }

  void setRight(const float pos) { mWidth = pos - mX; }

  void setTop(const float pos) {
    const auto diff = pos - mY;
    mY += diff;
    mHeight += diff;
  }

  void setBottom(const float pos) { mHeight = pos - mY; }

  void setTopLeft(const PointF& p) {
    setLeft(p.x());
    setTop(p.y());
  }

  void setTopRight(const PointF& p) {
    setRight(p.x());
    setTop(p.y());
  }

  void setBottomLeft(const Point& p) {
    setLeft(p.x());
    setBottom(p.y());
  }

  PointF topLeft() const { return {mX, mY}; }
  PointF bottomRight() const { return {mX + mWidth, mY + mHeight}; }
  PointF topRight() const { return {mX + mWidth, mY}; }
  PointF bottomLeft() const { return {mX, mY + mHeight}; }

  PointF center() const { return PointF{mX + mWidth / 2, mY + mHeight / 2}; }

  void moveLeft(const float pos) { mX = pos; }
  void moveTop(const float pos) { mY = pos; }
  void moveRight(const float pos) { mX = pos - mWidth; }
  void moveBottom(const float pos) { mY = pos - mHeight; }
  void moveTopLeft(const PointF& p) {
    moveLeft(p.x());
    moveTop(p.y());
  }

  void moveTopRight(const PointF& p) {
    moveRight(p.x());
    moveTop(p.y());
  }

  void moveBottomLeft(const PointF& p) {
    moveLeft(p.x());
    moveBottom(p.y());
  }

  void moveBottomRight(const PointF& p) {
    moveRight(p.x());
    moveBottom(p.y());
  }

  void moveCenter(const PointF& p) {
    mX = p.x() - mWidth / 2;
    mY = p.y() - mHeight / 2;
  }

  float width() const { return mWidth; }
  float height() const { return mHeight; }
  SizeF size() const { return SizeF{mWidth, mHeight}; }

  void translate(const float x, const float y) {
    mX += x;
    mY += y;
  }

  void translate(const PointF& p) { translate(p.x(), p.y()); }

  void moveTo(const float x, const float y) {
    mX = x;
    mY = y;
  }

  void moveTo(const Point& p) { moveTo(p.x(), p.y()); }

  RectF translated(const float x, const float y) const { return RectF{mX + x, mY + y, mWidth, mHeight}; }
  RectF translated(const PointF& p) const { return translated(p.x(), p.y()); }

  void getRect(float* x, float* y, float* width, float* height) const {
    *x = mX;
    *y = mY;
    *width = mWidth;
    *height = mHeight;
  }

  void setRect(const float x, const float y, const float width, const float height) {
    mX = x;
    mY = y;
    mWidth = width;
    mHeight = height;
  }

  void getCoords(float* left, float* top, float* right, float* bottom) const {
    *left = mX;
    *top = mY;
    *right = mX + mWidth;
    *bottom = mY + mHeight;
  }

  void adjust(const float left, const float top, const float right, const float bottom) {
    mX += left;
    mY += top;
    mWidth += right - left;
    mHeight += bottom - top;
  }
  RectF adjusted(const float left, const float top, const float right, const float bottom) const { return RectF(mX + left, mY + top, mWidth + right - left, mHeight + bottom - top); }

  void setWidth(const float w) { mWidth = w; }

  void setHeight(const float h) { mHeight = h; }

  bool contains(const float x, const float y) const { return contains(PointF(x, y)); }
  bool contains(const PointF& p) const;
  bool contains(const RectF& r) const;
  bool intersects(const RectF& r) const;

  RectF operator|(const RectF& other) const;
  RectF& operator|=(const RectF& other) {
    *this = *this | other;
    return *this;
  }

  RectF operator&(const RectF& other) const;
  RectF& operator&=(const RectF& other) {
    *this = *this & other;
    return *this;
  }

  RectF intersected(const RectF& r) const { return *this & r; }

  RectF united(const RectF& r) const { return *this | r; }

  bool operator==(const RectF& other) const { return fuzzyCompare(mX, other.mX) && fuzzyCompare(mY, other.mY) && fuzzyCompare(mWidth, other.mWidth) && fuzzyCompare(mHeight, other.mHeight); }

  bool operator!=(const RectF& other) const { return !fuzzyCompare(mX, other.mX) || !fuzzyCompare(mY, other.mY) || !fuzzyCompare(mWidth, other.mWidth) || !fuzzyCompare(mHeight, other.mHeight); }

  Rect toRect() const { return Rect{static_cast<int>(std::round(mX)), static_cast<int>(std::round(mY)), static_cast<int>(std::round(mWidth)), static_cast<int>(std::round(mHeight))}; }

  Rect toAlignedRect() const;

private:
  float mX{0.f};
  float mY{0.f};
  float mWidth{0.f};
  float mHeight{0.f};
};