#ifndef RENDER_MATH_HPP
#define RENDER_MATH_HPP

// Header-only C++11 rendering math utilities.
// Convention:
//   - Row-major storage.
//   - Row vectors: transformed = vector * matrix.
//   - Col vectors: transformed = matrix * vector.
//   - Composition_RowVec order: world * view * projection.
//   - Composition_ColVec order: projection * view * world.
//   - Translation in Matrix_ColVec is stored in the last column.
//   - Translation in Matrix_RowVec is stored in the last row.
//   - Direct3D NDC depth range is [0, 1].
//   - Functions ending in _lh use a left-handed coordinate system.
//
// SIMD:
//   - SSE2 is enabled automatically on x86/x64.
//   - Define RENDER_MATH_USE_SIMD to 0 before including this file to disable it.

#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <type_traits>

#ifndef RENDER_MATH_USE_SIMD
#  if defined(_M_X64) || defined(__x86_64__) || defined(_M_IX86) || defined(__i386__)
#    define RENDER_MATH_USE_SIMD 1
#  else
#    define RENDER_MATH_USE_SIMD 0
#  endif
#endif

#if RENDER_MATH_USE_SIMD
#  include <xmmintrin.h>
#  include <emmintrin.h>
#endif

#if defined(_MSC_VER)
#  define RENDER_MATH_FORCE_INLINE __forceinline
#else
#  define RENDER_MATH_FORCE_INLINE inline __attribute__((always_inline))
#endif

namespace math {

static constexpr float pi = 3.14159265358979323846f;
static constexpr float two_pi = 6.28318530717958647692f;
static constexpr float half_pi = 1.57079632679489661923f;
static constexpr float epsilon = 1.0e-6f;
static constexpr bool simd_enabled = (RENDER_MATH_USE_SIMD != 0);

// -----------------------------------------------------------------------------
// Scalar helpers
// -----------------------------------------------------------------------------

template<typename T>
RENDER_MATH_FORCE_INLINE constexpr T minimum(T a, T b) noexcept {
    return (a < b) ? a : b;
}

template<typename T>
RENDER_MATH_FORCE_INLINE constexpr T maximum(T a, T b) noexcept {
    return (a > b) ? a : b;
}

template<typename T>
RENDER_MATH_FORCE_INLINE constexpr T clamp(T value, T low, T high) noexcept {
    return (value < low) ? low : ((value > high) ? high : value);
}

RENDER_MATH_FORCE_INLINE constexpr float radians(float degrees_value) noexcept {
    return degrees_value * (pi / 180.0f);
}

RENDER_MATH_FORCE_INLINE constexpr float degrees(float radians_value) noexcept {
    return radians_value * (180.0f / pi);
}

RENDER_MATH_FORCE_INLINE constexpr float saturate(float value) noexcept {
    return clamp(value, 0.0f, 1.0f);
}

RENDER_MATH_FORCE_INLINE constexpr float lerp(float a, float b, float t) noexcept {
    return a + (b - a) * t;
}

RENDER_MATH_FORCE_INLINE float inverse_lerp(float a, float b, float value) noexcept {
    const float d = b - a;
    return (std::fabs(d) > epsilon) ? ((value - a) / d) : 0.0f;
}

RENDER_MATH_FORCE_INLINE float smoothstep(float edge0, float edge1, float value) noexcept {
    const float t = saturate(inverse_lerp(edge0, edge1, value));
    return t * t * (3.0f - 2.0f * t);
}

RENDER_MATH_FORCE_INLINE bool nearly_equal(
    float a,
    float b,
    float absolute_epsilon = 1.0e-6f,
    float relative_epsilon = 1.0e-5f) noexcept {
    const float difference = std::fabs(a - b);
    if (difference <= absolute_epsilon) {
        return true;
    }
    const float largest = maximum(std::fabs(a), std::fabs(b));
    return difference <= largest * relative_epsilon;
}

RENDER_MATH_FORCE_INLINE float wrap_angle(float angle) noexcept {
    angle = std::fmod(angle + pi, two_pi);
    if (angle < 0.0f) {
        angle += two_pi;
    }
    return angle - pi;
}

// -----------------------------------------------------------------------------
// Integer vectors
// -----------------------------------------------------------------------------

struct int2 {
    int x;
    int y;

    constexpr int2() noexcept : x(0), y(0) {}
    constexpr explicit int2(int value) noexcept : x(value), y(value) {}
    constexpr int2(int x_value, int y_value) noexcept : x(x_value), y(y_value) {}

    RENDER_MATH_FORCE_INLINE int &operator[](std::size_t index) noexcept {
        return (index == 0) ? x : y;
    }
    RENDER_MATH_FORCE_INLINE const int &operator[](std::size_t index) const noexcept {
        return (index == 0) ? x : y;
    }
};

struct int3 {
    int x;
    int y;
    int z;

    constexpr int3() noexcept : x(0), y(0), z(0) {}
    constexpr explicit int3(int value) noexcept : x(value), y(value), z(value) {}
    constexpr int3(int x_value, int y_value, int z_value) noexcept
    : x(x_value), y(y_value), z(z_value) {}

    RENDER_MATH_FORCE_INLINE int &operator[](std::size_t index) noexcept {
        return (index == 0) ? x : ((index == 1) ? y : z);
    }
    RENDER_MATH_FORCE_INLINE const int &operator[](std::size_t index) const noexcept {
        return (index == 0) ? x : ((index == 1) ? y : z);
    }
};

struct int4 {
    int x;
    int y;
    int z;
    int w;

    constexpr int4() noexcept : x(0), y(0), z(0), w(0) {}
    constexpr explicit int4(int value) noexcept : x(value), y(value), z(value), w(value) {}
    constexpr int4(int x_value, int y_value, int z_value, int w_value) noexcept
    : x(x_value), y(y_value), z(z_value), w(w_value) {}

    RENDER_MATH_FORCE_INLINE int &operator[](std::size_t index) noexcept {
        return (index == 0) ? x : ((index == 1) ? y : ((index == 2) ? z : w));
    }
    RENDER_MATH_FORCE_INLINE const int &operator[](std::size_t index) const noexcept {
        return (index == 0) ? x : ((index == 1) ? y : ((index == 2) ? z : w));
    }
};

RENDER_MATH_FORCE_INLINE constexpr int2 operator+(int2 a, int2 b) noexcept { return int2(a.x + b.x, a.y + b.y); }
RENDER_MATH_FORCE_INLINE constexpr int2 operator-(int2 a, int2 b) noexcept { return int2(a.x - b.x, a.y - b.y); }
RENDER_MATH_FORCE_INLINE constexpr int2 operator-(int2 v) noexcept { return int2(-v.x, -v.y); }
RENDER_MATH_FORCE_INLINE constexpr int2 operator*(int2 a, int2 b) noexcept { return int2(a.x * b.x, a.y * b.y); }
RENDER_MATH_FORCE_INLINE constexpr int2 operator/(int2 a, int2 b) noexcept { return int2(a.x / b.x, a.y / b.y); }
RENDER_MATH_FORCE_INLINE constexpr int2 operator*(int2 v, int scalar) noexcept { return int2(v.x * scalar, v.y * scalar); }
RENDER_MATH_FORCE_INLINE constexpr int2 operator*(int scalar, int2 v) noexcept { return v * scalar; }
RENDER_MATH_FORCE_INLINE constexpr int2 operator/(int2 v, int scalar) noexcept { return int2(v.x / scalar, v.y / scalar); }
RENDER_MATH_FORCE_INLINE constexpr bool operator==(int2 a, int2 b) noexcept { return a.x == b.x && a.y == b.y; }
RENDER_MATH_FORCE_INLINE constexpr bool operator!=(int2 a, int2 b) noexcept { return !(a == b); }

RENDER_MATH_FORCE_INLINE constexpr int3 operator+(int3 a, int3 b) noexcept { return int3(a.x + b.x, a.y + b.y, a.z + b.z); }
RENDER_MATH_FORCE_INLINE constexpr int3 operator-(int3 a, int3 b) noexcept { return int3(a.x - b.x, a.y - b.y, a.z - b.z); }
RENDER_MATH_FORCE_INLINE constexpr int3 operator-(int3 v) noexcept { return int3(-v.x, -v.y, -v.z); }
RENDER_MATH_FORCE_INLINE constexpr int3 operator*(int3 a, int3 b) noexcept { return int3(a.x * b.x, a.y * b.y, a.z * b.z); }
RENDER_MATH_FORCE_INLINE constexpr int3 operator/(int3 a, int3 b) noexcept { return int3(a.x / b.x, a.y / b.y, a.z / b.z); }
RENDER_MATH_FORCE_INLINE constexpr int3 operator*(int3 v, int scalar) noexcept { return int3(v.x * scalar, v.y * scalar, v.z * scalar); }
RENDER_MATH_FORCE_INLINE constexpr int3 operator*(int scalar, int3 v) noexcept { return v * scalar; }
RENDER_MATH_FORCE_INLINE constexpr int3 operator/(int3 v, int scalar) noexcept { return int3(v.x / scalar, v.y / scalar, v.z / scalar); }
RENDER_MATH_FORCE_INLINE constexpr bool operator==(int3 a, int3 b) noexcept { return a.x == b.x && a.y == b.y && a.z == b.z; }
RENDER_MATH_FORCE_INLINE constexpr bool operator!=(int3 a, int3 b) noexcept { return !(a == b); }

RENDER_MATH_FORCE_INLINE constexpr int4 operator+(int4 a, int4 b) noexcept { return int4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); }
RENDER_MATH_FORCE_INLINE constexpr int4 operator-(int4 a, int4 b) noexcept { return int4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); }
RENDER_MATH_FORCE_INLINE constexpr int4 operator-(int4 v) noexcept { return int4(-v.x, -v.y, -v.z, -v.w); }
RENDER_MATH_FORCE_INLINE constexpr int4 operator*(int4 a, int4 b) noexcept { return int4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); }
RENDER_MATH_FORCE_INLINE constexpr int4 operator/(int4 a, int4 b) noexcept { return int4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); }
RENDER_MATH_FORCE_INLINE constexpr int4 operator*(int4 v, int scalar) noexcept { return int4(v.x * scalar, v.y * scalar, v.z * scalar, v.w * scalar); }
RENDER_MATH_FORCE_INLINE constexpr int4 operator*(int scalar, int4 v) noexcept { return v * scalar; }
RENDER_MATH_FORCE_INLINE constexpr int4 operator/(int4 v, int scalar) noexcept { return int4(v.x / scalar, v.y / scalar, v.z / scalar, v.w / scalar); }
RENDER_MATH_FORCE_INLINE constexpr bool operator==(int4 a, int4 b) noexcept { return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; }
RENDER_MATH_FORCE_INLINE constexpr bool operator!=(int4 a, int4 b) noexcept { return !(a == b); }

RENDER_MATH_FORCE_INLINE constexpr std::int64_t dot(int2 a, int2 b) noexcept {
    return static_cast<std::int64_t>(a.x) * b.x + static_cast<std::int64_t>(a.y) * b.y;
}
RENDER_MATH_FORCE_INLINE constexpr std::int64_t dot(int3 a, int3 b) noexcept {
    return static_cast<std::int64_t>(a.x) * b.x + static_cast<std::int64_t>(a.y) * b.y + static_cast<std::int64_t>(a.z) * b.z;
}
RENDER_MATH_FORCE_INLINE constexpr std::int64_t dot(int4 a, int4 b) noexcept {
    return static_cast<std::int64_t>(a.x) * b.x + static_cast<std::int64_t>(a.y) * b.y +
           static_cast<std::int64_t>(a.z) * b.z + static_cast<std::int64_t>(a.w) * b.w;
}

RENDER_MATH_FORCE_INLINE constexpr int2 component_min(int2 a, int2 b) noexcept { return int2(minimum(a.x, b.x), minimum(a.y, b.y)); }
RENDER_MATH_FORCE_INLINE constexpr int2 component_max(int2 a, int2 b) noexcept { return int2(maximum(a.x, b.x), maximum(a.y, b.y)); }
RENDER_MATH_FORCE_INLINE constexpr int3 component_min(int3 a, int3 b) noexcept { return int3(minimum(a.x, b.x), minimum(a.y, b.y), minimum(a.z, b.z)); }
RENDER_MATH_FORCE_INLINE constexpr int3 component_max(int3 a, int3 b) noexcept { return int3(maximum(a.x, b.x), maximum(a.y, b.y), maximum(a.z, b.z)); }
RENDER_MATH_FORCE_INLINE constexpr int4 component_min(int4 a, int4 b) noexcept { return int4(minimum(a.x, b.x), minimum(a.y, b.y), minimum(a.z, b.z), minimum(a.w, b.w)); }
RENDER_MATH_FORCE_INLINE constexpr int4 component_max(int4 a, int4 b) noexcept { return int4(maximum(a.x, b.x), maximum(a.y, b.y), maximum(a.z, b.z), maximum(a.w, b.w)); }

RENDER_MATH_FORCE_INLINE constexpr int2 clamp(int2 value, int2 low, int2 high) noexcept {
    return component_min(component_max(value, low), high);
}
RENDER_MATH_FORCE_INLINE constexpr int3 clamp(int3 value, int3 low, int3 high) noexcept {
    return component_min(component_max(value, low), high);
}
RENDER_MATH_FORCE_INLINE constexpr int4 clamp(int4 value, int4 low, int4 high) noexcept {
    return component_min(component_max(value, low), high);
}

// -----------------------------------------------------------------------------
// Floating-point vectors
// -----------------------------------------------------------------------------

struct float2 {
    float x;
    float y;

    constexpr float2() noexcept : x(0.0f), y(0.0f) {}
    constexpr explicit float2(float value) noexcept : x(value), y(value) {}
    constexpr float2(float x_value, float y_value) noexcept : x(x_value), y(y_value) {}

    RENDER_MATH_FORCE_INLINE float &operator[](std::size_t index) noexcept {
        return (index == 0) ? x : y;
    }
    RENDER_MATH_FORCE_INLINE const float &operator[](std::size_t index) const noexcept {
        return (index == 0) ? x : y;
    }
};

struct float3 {
    float x;
    float y;
    float z;

    constexpr float3() noexcept : x(0.0f), y(0.0f), z(0.0f) {}
    constexpr explicit float3(float value) noexcept : x(value), y(value), z(value) {}
    constexpr float3(float x_value, float y_value, float z_value) noexcept
    : x(x_value), y(y_value), z(z_value) {}
    constexpr float3(float2 xy, float z_value) noexcept : x(xy.x), y(xy.y), z(z_value) {}

    RENDER_MATH_FORCE_INLINE float &operator[](std::size_t index) noexcept {
        return (index == 0) ? x : ((index == 1) ? y : z);
    }
    RENDER_MATH_FORCE_INLINE const float &operator[](std::size_t index) const noexcept {
        return (index == 0) ? x : ((index == 1) ? y : z);
    }
};

struct alignas(16) float4 {
    float x;
    float y;
    float z;
    float w;

    constexpr float4() noexcept : x(0.0f), y(0.0f), z(0.0f), w(0.0f) {}
    constexpr explicit float4(float value) noexcept : x(value), y(value), z(value), w(value) {}
    constexpr float4(float x_value, float y_value, float z_value, float w_value) noexcept
    : x(x_value), y(y_value), z(z_value), w(w_value) {}
    constexpr float4(float3 xyz, float w_value) noexcept : x(xyz.x), y(xyz.y), z(xyz.z), w(w_value) {}
    constexpr float4(float2 xy, float z_value, float w_value) noexcept : x(xy.x), y(xy.y), z(z_value), w(w_value) {}

    RENDER_MATH_FORCE_INLINE float &operator[](std::size_t index) noexcept {
        return (index == 0) ? x : ((index == 1) ? y : ((index == 2) ? z : w));
    }
    RENDER_MATH_FORCE_INLINE const float &operator[](std::size_t index) const noexcept {
        return (index == 0) ? x : ((index == 1) ? y : ((index == 2) ? z : w));
    }
};

namespace detail {
#if RENDER_MATH_USE_SIMD
RENDER_MATH_FORCE_INLINE __m128 load(float4 value) noexcept {
    return _mm_loadu_ps(&value.x);
}

RENDER_MATH_FORCE_INLINE float4 store_float4(__m128 value) noexcept {
    float4 result;
    _mm_storeu_ps(&result.x, value);
    return result;
}

RENDER_MATH_FORCE_INLINE float lane(__m128 value, int index) noexcept {
    switch (index) {
    case 0: return _mm_cvtss_f32(value);
    case 1: return _mm_cvtss_f32(_mm_shuffle_ps(value, value, _MM_SHUFFLE(1, 1, 1, 1)));
    case 2: return _mm_cvtss_f32(_mm_shuffle_ps(value, value, _MM_SHUFFLE(2, 2, 2, 2)));
    default: return _mm_cvtss_f32(_mm_shuffle_ps(value, value, _MM_SHUFFLE(3, 3, 3, 3)));
    }
}
#endif
} // namespace detail

RENDER_MATH_FORCE_INLINE constexpr float2 operator+(float2 a, float2 b) noexcept { return float2(a.x + b.x, a.y + b.y); }
RENDER_MATH_FORCE_INLINE constexpr float2 operator-(float2 a, float2 b) noexcept { return float2(a.x - b.x, a.y - b.y); }
RENDER_MATH_FORCE_INLINE constexpr float2 operator-(float2 v) noexcept { return float2(-v.x, -v.y); }
RENDER_MATH_FORCE_INLINE constexpr float2 operator*(float2 a, float2 b) noexcept { return float2(a.x * b.x, a.y * b.y); }
RENDER_MATH_FORCE_INLINE constexpr float2 operator/(float2 a, float2 b) noexcept { return float2(a.x / b.x, a.y / b.y); }
RENDER_MATH_FORCE_INLINE constexpr float2 operator*(float2 v, float scalar) noexcept { return float2(v.x * scalar, v.y * scalar); }
RENDER_MATH_FORCE_INLINE constexpr float2 operator*(float scalar, float2 v) noexcept { return v * scalar; }
RENDER_MATH_FORCE_INLINE constexpr float2 operator/(float2 v, float scalar) noexcept { return float2(v.x / scalar, v.y / scalar); }
RENDER_MATH_FORCE_INLINE constexpr bool operator==(float2 a, float2 b) noexcept { return a.x == b.x && a.y == b.y; }
RENDER_MATH_FORCE_INLINE constexpr bool operator!=(float2 a, float2 b) noexcept { return !(a == b); }

RENDER_MATH_FORCE_INLINE constexpr float3 operator+(float3 a, float3 b) noexcept { return float3(a.x + b.x, a.y + b.y, a.z + b.z); }
RENDER_MATH_FORCE_INLINE constexpr float3 operator-(float3 a, float3 b) noexcept { return float3(a.x - b.x, a.y - b.y, a.z - b.z); }
RENDER_MATH_FORCE_INLINE constexpr float3 operator-(float3 v) noexcept { return float3(-v.x, -v.y, -v.z); }
RENDER_MATH_FORCE_INLINE constexpr float3 operator*(float3 a, float3 b) noexcept { return float3(a.x * b.x, a.y * b.y, a.z * b.z); }
RENDER_MATH_FORCE_INLINE constexpr float3 operator/(float3 a, float3 b) noexcept { return float3(a.x / b.x, a.y / b.y, a.z / b.z); }
RENDER_MATH_FORCE_INLINE constexpr float3 operator*(float3 v, float scalar) noexcept { return float3(v.x * scalar, v.y * scalar, v.z * scalar); }
RENDER_MATH_FORCE_INLINE constexpr float3 operator*(float scalar, float3 v) noexcept { return v * scalar; }
RENDER_MATH_FORCE_INLINE constexpr float3 operator/(float3 v, float scalar) noexcept { return float3(v.x / scalar, v.y / scalar, v.z / scalar); }
RENDER_MATH_FORCE_INLINE constexpr bool operator==(float3 a, float3 b) noexcept { return a.x == b.x && a.y == b.y && a.z == b.z; }
RENDER_MATH_FORCE_INLINE constexpr bool operator!=(float3 a, float3 b) noexcept { return !(a == b); }

RENDER_MATH_FORCE_INLINE float4 operator+(float4 a, float4 b) noexcept {
#if RENDER_MATH_USE_SIMD
    return detail::store_float4(_mm_add_ps(detail::load(a), detail::load(b)));
#else
    return float4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
#endif
}
RENDER_MATH_FORCE_INLINE float4 operator-(float4 a, float4 b) noexcept {
#if RENDER_MATH_USE_SIMD
    return detail::store_float4(_mm_sub_ps(detail::load(a), detail::load(b)));
#else
    return float4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
#endif
}
RENDER_MATH_FORCE_INLINE float4 operator-(float4 v) noexcept {
#if RENDER_MATH_USE_SIMD
    return detail::store_float4(_mm_sub_ps(_mm_setzero_ps(), detail::load(v)));
#else
    return float4(-v.x, -v.y, -v.z, -v.w);
#endif
}
RENDER_MATH_FORCE_INLINE float4 operator*(float4 a, float4 b) noexcept {
#if RENDER_MATH_USE_SIMD
    return detail::store_float4(_mm_mul_ps(detail::load(a), detail::load(b)));
#else
    return float4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w);
#endif
}
RENDER_MATH_FORCE_INLINE float4 operator/(float4 a, float4 b) noexcept {
#if RENDER_MATH_USE_SIMD
    return detail::store_float4(_mm_div_ps(detail::load(a), detail::load(b)));
#else
    return float4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w);
#endif
}
RENDER_MATH_FORCE_INLINE float4 operator*(float4 v, float scalar) noexcept {
#if RENDER_MATH_USE_SIMD
    return detail::store_float4(_mm_mul_ps(detail::load(v), _mm_set1_ps(scalar)));
#else
    return float4(v.x * scalar, v.y * scalar, v.z * scalar, v.w * scalar);
#endif
}
RENDER_MATH_FORCE_INLINE float4 operator*(float scalar, float4 v) noexcept { return v * scalar; }
RENDER_MATH_FORCE_INLINE float4 operator/(float4 v, float scalar) noexcept {
#if RENDER_MATH_USE_SIMD
    return detail::store_float4(_mm_div_ps(detail::load(v), _mm_set1_ps(scalar)));
#else
    return float4(v.x / scalar, v.y / scalar, v.z / scalar, v.w / scalar);
#endif
}
RENDER_MATH_FORCE_INLINE constexpr bool operator==(float4 a, float4 b) noexcept { return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; }
RENDER_MATH_FORCE_INLINE constexpr bool operator!=(float4 a, float4 b) noexcept { return !(a == b); }

RENDER_MATH_FORCE_INLINE float2 &operator+=(float2 &a, float2 b) noexcept {
    a = a + b;
    return a;
}
RENDER_MATH_FORCE_INLINE float2 &operator-=(float2 &a, float2 b) noexcept {
    a = a - b;
    return a;
}
RENDER_MATH_FORCE_INLINE float2 &operator*=(float2 &a, float scalar) noexcept {
    a = a * scalar;
    return a;
}
RENDER_MATH_FORCE_INLINE float2 &operator/=(float2 &a, float scalar) noexcept {
    a = a / scalar;
    return a;
}
RENDER_MATH_FORCE_INLINE float3 &operator+=(float3 &a, float3 b) noexcept {
    a = a + b;
    return a;
}
RENDER_MATH_FORCE_INLINE float3 &operator-=(float3 &a, float3 b) noexcept {
    a = a - b;
    return a;
}
RENDER_MATH_FORCE_INLINE float3 &operator*=(float3 &a, float scalar) noexcept {
    a = a * scalar;
    return a;
}
RENDER_MATH_FORCE_INLINE float3 &operator/=(float3 &a, float scalar) noexcept {
    a = a / scalar;
    return a;
}
RENDER_MATH_FORCE_INLINE float4 &operator+=(float4 &a, float4 b) noexcept {
    a = a + b;
    return a;
}
RENDER_MATH_FORCE_INLINE float4 &operator-=(float4 &a, float4 b) noexcept {
    a = a - b;
    return a;
}
RENDER_MATH_FORCE_INLINE float4 &operator*=(float4 &a, float scalar) noexcept {
    a = a * scalar;
    return a;
}
RENDER_MATH_FORCE_INLINE float4 &operator/=(float4 &a, float scalar) noexcept {
    a = a / scalar;
    return a;
}

RENDER_MATH_FORCE_INLINE constexpr float dot(float2 a, float2 b) noexcept { return a.x * b.x + a.y * b.y; }
RENDER_MATH_FORCE_INLINE constexpr float dot(float3 a, float3 b) noexcept { return a.x * b.x + a.y * b.y + a.z * b.z; }
RENDER_MATH_FORCE_INLINE constexpr float dot(float4 a, float4 b) noexcept { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; }
RENDER_MATH_FORCE_INLINE constexpr float cross(float2 a, float2 b) noexcept { return a.x * b.y - a.y * b.x; }
RENDER_MATH_FORCE_INLINE constexpr float3 cross(float3 a, float3 b) noexcept {
    return float3(a.y * b.z - a.z * b.y,
                  a.z * b.x - a.x * b.z,
                  a.x * b.y - a.y * b.x);
}

RENDER_MATH_FORCE_INLINE constexpr float length_squared(float2 v) noexcept { return dot(v, v); }
RENDER_MATH_FORCE_INLINE constexpr float length_squared(float3 v) noexcept { return dot(v, v); }
RENDER_MATH_FORCE_INLINE constexpr float length_squared(float4 v) noexcept { return dot(v, v); }
RENDER_MATH_FORCE_INLINE float length(float2 v) noexcept { return std::sqrt(length_squared(v)); }
RENDER_MATH_FORCE_INLINE float length(float3 v) noexcept { return std::sqrt(length_squared(v)); }
RENDER_MATH_FORCE_INLINE float length(float4 v) noexcept { return std::sqrt(length_squared(v)); }

RENDER_MATH_FORCE_INLINE float2 normalize(float2 v, float tolerance = epsilon) noexcept {
    const float length_sq = length_squared(v);
    return (length_sq > tolerance * tolerance) ? (v / std::sqrt(length_sq)) : float2(0.0f);
}
RENDER_MATH_FORCE_INLINE float3 normalize(float3 v, float tolerance = epsilon) noexcept {
    const float length_sq = length_squared(v);
    return (length_sq > tolerance * tolerance) ? (v / std::sqrt(length_sq)) : float3(0.0f);
}
RENDER_MATH_FORCE_INLINE float4 normalize(float4 v, float tolerance = epsilon) noexcept {
    const float length_sq = length_squared(v);
    return (length_sq > tolerance * tolerance) ? (v / std::sqrt(length_sq)) : float4(0.0f);
}

RENDER_MATH_FORCE_INLINE float2 normalize_or(float2 v, float2 fallback, float tolerance = epsilon) noexcept {
    const float length_sq = length_squared(v);
    return (length_sq > tolerance * tolerance) ? (v / std::sqrt(length_sq)) : fallback;
}
RENDER_MATH_FORCE_INLINE float3 normalize_or(float3 v, float3 fallback, float tolerance = epsilon) noexcept {
    const float length_sq = length_squared(v);
    return (length_sq > tolerance * tolerance) ? (v / std::sqrt(length_sq)) : fallback;
}
RENDER_MATH_FORCE_INLINE float4 normalize_or(float4 v, float4 fallback, float tolerance = epsilon) noexcept {
    const float length_sq = length_squared(v);
    return (length_sq > tolerance * tolerance) ? (v / std::sqrt(length_sq)) : fallback;
}

RENDER_MATH_FORCE_INLINE float distance(float2 a, float2 b) noexcept { return length(a - b); }
RENDER_MATH_FORCE_INLINE float distance(float3 a, float3 b) noexcept { return length(a - b); }
RENDER_MATH_FORCE_INLINE float distance(float4 a, float4 b) noexcept { return length(a - b); }

RENDER_MATH_FORCE_INLINE constexpr float2 component_min(float2 a, float2 b) noexcept { return float2(minimum(a.x, b.x), minimum(a.y, b.y)); }
RENDER_MATH_FORCE_INLINE constexpr float2 component_max(float2 a, float2 b) noexcept { return float2(maximum(a.x, b.x), maximum(a.y, b.y)); }
RENDER_MATH_FORCE_INLINE constexpr float3 component_min(float3 a, float3 b) noexcept { return float3(minimum(a.x, b.x), minimum(a.y, b.y), minimum(a.z, b.z)); }
RENDER_MATH_FORCE_INLINE constexpr float3 component_max(float3 a, float3 b) noexcept { return float3(maximum(a.x, b.x), maximum(a.y, b.y), maximum(a.z, b.z)); }
RENDER_MATH_FORCE_INLINE constexpr float4 component_min(float4 a, float4 b) noexcept { return float4(minimum(a.x, b.x), minimum(a.y, b.y), minimum(a.z, b.z), minimum(a.w, b.w)); }
RENDER_MATH_FORCE_INLINE constexpr float4 component_max(float4 a, float4 b) noexcept { return float4(maximum(a.x, b.x), maximum(a.y, b.y), maximum(a.z, b.z), maximum(a.w, b.w)); }

RENDER_MATH_FORCE_INLINE constexpr float2 clamp(float2 value, float2 low, float2 high) noexcept { return component_min(component_max(value, low), high); }
RENDER_MATH_FORCE_INLINE constexpr float3 clamp(float3 value, float3 low, float3 high) noexcept { return component_min(component_max(value, low), high); }
RENDER_MATH_FORCE_INLINE constexpr float4 clamp(float4 value, float4 low, float4 high) noexcept { return component_min(component_max(value, low), high); }
RENDER_MATH_FORCE_INLINE constexpr float2 saturate(float2 value) noexcept { return clamp(value, float2(0.0f), float2(1.0f)); }
RENDER_MATH_FORCE_INLINE constexpr float3 saturate(float3 value) noexcept { return clamp(value, float3(0.0f), float3(1.0f)); }
RENDER_MATH_FORCE_INLINE constexpr float4 saturate(float4 value) noexcept { return clamp(value, float4(0.0f), float4(1.0f)); }

RENDER_MATH_FORCE_INLINE constexpr float2 lerp(float2 a, float2 b, float t) noexcept { return a + (b - a) * t; }
RENDER_MATH_FORCE_INLINE constexpr float3 lerp(float3 a, float3 b, float t) noexcept { return a + (b - a) * t; }
RENDER_MATH_FORCE_INLINE float4 lerp(float4 a, float4 b, float t) noexcept { return a + (b - a) * t; }

RENDER_MATH_FORCE_INLINE constexpr float2 perpendicular(float2 v) noexcept { return float2(-v.y, v.x); }
RENDER_MATH_FORCE_INLINE constexpr float3 reflect(float3 incident, float3 normal) noexcept {
    return incident - normal * (2.0f * dot(incident, normal));
}
RENDER_MATH_FORCE_INLINE float3 refract(float3 incident, float3 normal, float eta) noexcept {
    const float d = dot(normal, incident);
    const float k = 1.0f - eta * eta * (1.0f - d * d);
    return (k < 0.0f) ? float3(0.0f) : (incident * eta - normal * (eta * d + std::sqrt(k)));
}

RENDER_MATH_FORCE_INLINE bool nearly_equal(float2 a, float2 b, float abs_eps = 1.0e-6f, float rel_eps = 1.0e-5f) noexcept {
    return nearly_equal(a.x, b.x, abs_eps, rel_eps) && nearly_equal(a.y, b.y, abs_eps, rel_eps);
}
RENDER_MATH_FORCE_INLINE bool nearly_equal(float3 a, float3 b, float abs_eps = 1.0e-6f, float rel_eps = 1.0e-5f) noexcept {
    return nearly_equal(a.x, b.x, abs_eps, rel_eps) && nearly_equal(a.y, b.y, abs_eps, rel_eps) && nearly_equal(a.z, b.z, abs_eps, rel_eps);
}
RENDER_MATH_FORCE_INLINE bool nearly_equal(float4 a, float4 b, float abs_eps = 1.0e-6f, float rel_eps = 1.0e-5f) noexcept {
    return nearly_equal(a.x, b.x, abs_eps, rel_eps) && nearly_equal(a.y, b.y, abs_eps, rel_eps) &&
           nearly_equal(a.z, b.z, abs_eps, rel_eps) && nearly_equal(a.w, b.w, abs_eps, rel_eps);
}

RENDER_MATH_FORCE_INLINE constexpr float2 to_float2(int2 value) noexcept { return float2(static_cast<float>(value.x), static_cast<float>(value.y)); }
RENDER_MATH_FORCE_INLINE constexpr float3 to_float3(int3 value) noexcept { return float3(static_cast<float>(value.x), static_cast<float>(value.y), static_cast<float>(value.z)); }
RENDER_MATH_FORCE_INLINE constexpr float4 to_float4(int4 value) noexcept { return float4(static_cast<float>(value.x), static_cast<float>(value.y), static_cast<float>(value.z), static_cast<float>(value.w)); }
RENDER_MATH_FORCE_INLINE int2 to_int2_trunc(float2 value) noexcept { return int2(static_cast<int>(value.x), static_cast<int>(value.y)); }
RENDER_MATH_FORCE_INLINE int3 to_int3_trunc(float3 value) noexcept { return int3(static_cast<int>(value.x), static_cast<int>(value.y), static_cast<int>(value.z)); }
RENDER_MATH_FORCE_INLINE int4 to_int4_trunc(float4 value) noexcept { return int4(static_cast<int>(value.x), static_cast<int>(value.y), static_cast<int>(value.z), static_cast<int>(value.w)); }
RENDER_MATH_FORCE_INLINE int2 to_int2_round(float2 value) noexcept { return int2(static_cast<int>(std::lround(value.x)), static_cast<int>(std::lround(value.y))); }
RENDER_MATH_FORCE_INLINE int3 to_int3_round(float3 value) noexcept { return int3(static_cast<int>(std::lround(value.x)), static_cast<int>(std::lround(value.y)), static_cast<int>(std::lround(value.z))); }
RENDER_MATH_FORCE_INLINE int4 to_int4_round(float4 value) noexcept { return int4(static_cast<int>(std::lround(value.x)), static_cast<int>(std::lround(value.y)), static_cast<int>(std::lround(value.z)), static_cast<int>(std::lround(value.w))); }

// -----------------------------------------------------------------------------
// Quaternion
// -----------------------------------------------------------------------------

struct alignas(16) quat {
    float x;
    float y;
    float z;
    float w;

    constexpr quat() noexcept : x(0.0f), y(0.0f), z(0.0f), w(1.0f) {}
    constexpr quat(float x_value, float y_value, float z_value, float w_value) noexcept
    : x(x_value), y(y_value), z(z_value), w(w_value) {}
    constexpr quat(float3 vector_part, float scalar_part) noexcept
    : x(vector_part.x), y(vector_part.y), z(vector_part.z), w(scalar_part) {}

    RENDER_MATH_FORCE_INLINE float &operator[](std::size_t index) noexcept {
        return (index == 0) ? x : ((index == 1) ? y : ((index == 2) ? z : w));
    }
    RENDER_MATH_FORCE_INLINE const float &operator[](std::size_t index) const noexcept {
        return (index == 0) ? x : ((index == 1) ? y : ((index == 2) ? z : w));
    }
};

RENDER_MATH_FORCE_INLINE constexpr quat quat_identity() noexcept { return quat(0.0f, 0.0f, 0.0f, 1.0f); }
RENDER_MATH_FORCE_INLINE constexpr quat operator+(quat a, quat b) noexcept { return quat(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); }
RENDER_MATH_FORCE_INLINE constexpr quat operator-(quat a, quat b) noexcept { return quat(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); }
RENDER_MATH_FORCE_INLINE constexpr quat operator-(quat q) noexcept { return quat(-q.x, -q.y, -q.z, -q.w); }
RENDER_MATH_FORCE_INLINE constexpr quat operator*(quat q, float scalar) noexcept { return quat(q.x * scalar, q.y * scalar, q.z * scalar, q.w * scalar); }
RENDER_MATH_FORCE_INLINE constexpr quat operator*(float scalar, quat q) noexcept { return q * scalar; }
RENDER_MATH_FORCE_INLINE constexpr quat operator/(quat q, float scalar) noexcept { return quat(q.x / scalar, q.y / scalar, q.z / scalar, q.w / scalar); }
RENDER_MATH_FORCE_INLINE constexpr bool operator==(quat a, quat b) noexcept { return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; }
RENDER_MATH_FORCE_INLINE constexpr bool operator!=(quat a, quat b) noexcept { return !(a == b); }
RENDER_MATH_FORCE_INLINE constexpr float dot(quat a, quat b) noexcept { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; }
RENDER_MATH_FORCE_INLINE constexpr float length_squared(quat q) noexcept { return dot(q, q); }
RENDER_MATH_FORCE_INLINE float length(quat q) noexcept { return std::sqrt(length_squared(q)); }

RENDER_MATH_FORCE_INLINE quat normalize(quat q, float tolerance = epsilon) noexcept {
    const float length_sq = length_squared(q);
    return (length_sq > tolerance * tolerance) ? (q / std::sqrt(length_sq)) : quat_identity();
}

// 返回共轭四元数
RENDER_MATH_FORCE_INLINE constexpr quat conjugate(quat q) noexcept {
    return quat(-q.x, -q.y, -q.z, q.w);
}

RENDER_MATH_FORCE_INLINE quat inverse(quat q, float tolerance = epsilon) noexcept {
    const float length_sq = length_squared(q);
    return (length_sq > tolerance * tolerance) ? (conjugate(q) / length_sq) : quat_identity();
}

// Algebraic Hamilton product: a * b.
// 两个四元数的代数汉密尔顿积
RENDER_MATH_FORCE_INLINE constexpr quat hamilton_product(quat a, quat b) noexcept {
    return quat(
        a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
        a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
        a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
        a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z);
}

// Row-vector composition: apply first, then second.
// 行向量组合,second在先,first在后乘
RENDER_MATH_FORCE_INLINE constexpr quat concatenate(quat first, quat second) noexcept {
    return hamilton_product(second, first);
}

RENDER_MATH_FORCE_INLINE quat quaternion_axis_angle(float3 axis, float angle) noexcept {
    const float3 n = normalize(axis);
    if (length_squared(n) <= epsilon * epsilon) {
        return quat_identity();
    }
    const float half = angle * 0.5f;
    const float s = std::sin(half);
    return quat(n.x * s, n.y * s, n.z * s, std::cos(half));
}

RENDER_MATH_FORCE_INLINE float3 rotate(float3 vector, quat rotation) noexcept {
    const quat q = normalize(rotation);
    const float3 qv(q.x, q.y, q.z);
    const float3 t = 2.0f * cross(qv, vector);
    return vector + q.w * t + cross(qv, t);
}

RENDER_MATH_FORCE_INLINE quat nlerp(quat a, quat b, float t) noexcept {
    if (dot(a, b) < 0.0f) {
        b = -b;
    }
    return normalize(a + (b - a) * t);
}

RENDER_MATH_FORCE_INLINE quat slerp(quat a, quat b, float t) noexcept {
    a = normalize(a);
    b = normalize(b);
    float cosine = dot(a, b);
    if (cosine < 0.0f) {
        b = -b;
        cosine = -cosine;
    }
    cosine = clamp(cosine, -1.0f, 1.0f);
    if (cosine > 0.9995f) {
        return nlerp(a, b, t);
    }
    const float angle = std::acos(cosine);
    const float sine = std::sin(angle);
    if (std::fabs(sine) <= epsilon) {
        return a;
    }
    const float weight_a = std::sin((1.0f - t) * angle) / sine;
    const float weight_b = std::sin(t * angle) / sine;
    return normalize(a * weight_a + b * weight_b);
}

// -----------------------------------------------------------------------------
// Matrix: row-major storage, row-vector convention
// -----------------------------------------------------------------------------

struct alignas(16) float4x4 {
    float m[4][4];

    float4x4() noexcept
    : m{{0.0f, 0.0f, 0.0f, 0.0f},
        {0.0f, 0.0f, 0.0f, 0.0f},
        {0.0f, 0.0f, 0.0f, 0.0f},
        {0.0f, 0.0f, 0.0f, 0.0f}} {}

    constexpr float4x4(
        float m00, float m01, float m02, float m03,
        float m10, float m11, float m12, float m13,
        float m20, float m21, float m22, float m23,
        float m30, float m31, float m32, float m33) noexcept
    : m{{m00, m01, m02, m03},
        {m10, m11, m12, m13},
        {m20, m21, m22, m23},
        {m30, m31, m32, m33}} {}

    constexpr float4x4(float4 row0, float4 row1, float4 row2, float4 row3) noexcept
    : m{{row0.x, row0.y, row0.z, row0.w},
        {row1.x, row1.y, row1.z, row1.w},
        {row2.x, row2.y, row2.z, row2.w},
        {row3.x, row3.y, row3.z, row3.w}} {}

    RENDER_MATH_FORCE_INLINE float *operator[](std::size_t row_index) noexcept { return m[row_index]; }
    RENDER_MATH_FORCE_INLINE const float *operator[](std::size_t row_index) const noexcept { return m[row_index]; }
    RENDER_MATH_FORCE_INLINE float &operator()(std::size_t row_index, std::size_t column_index) noexcept { return m[row_index][column_index]; }
    RENDER_MATH_FORCE_INLINE const float &operator()(std::size_t row_index, std::size_t column_index) const noexcept { return m[row_index][column_index]; }
    RENDER_MATH_FORCE_INLINE float *data() noexcept { return &m[0][0]; }
    [[nodiscard]]RENDER_MATH_FORCE_INLINE const float *data() const noexcept { return &m[0][0]; }
};

RENDER_MATH_FORCE_INLINE constexpr float4x4 zero_matrix() noexcept {
    return float4x4(
        0.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 0.0f);
}

RENDER_MATH_FORCE_INLINE constexpr float4x4 identity() noexcept {
    return float4x4(
        1.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f);
}

RENDER_MATH_FORCE_INLINE constexpr float4 row(const float4x4 &matrix, std::size_t index) noexcept {
    return float4(matrix.m[index][0], matrix.m[index][1], matrix.m[index][2], matrix.m[index][3]);
}

RENDER_MATH_FORCE_INLINE constexpr float4 column(const float4x4 &matrix, std::size_t index) noexcept {
    return float4(matrix.m[0][index], matrix.m[1][index], matrix.m[2][index], matrix.m[3][index]);
}

RENDER_MATH_FORCE_INLINE void set_row(float4x4 &matrix, std::size_t index, float4 value) noexcept {
    matrix.m[index][0] = value.x;
    matrix.m[index][1] = value.y;
    matrix.m[index][2] = value.z;
    matrix.m[index][3] = value.w;
}

RENDER_MATH_FORCE_INLINE void set_column(float4x4 &matrix, std::size_t index, float4 value) noexcept {
    matrix.m[0][index] = value.x;
    matrix.m[1][index] = value.y;
    matrix.m[2][index] = value.z;
    matrix.m[3][index] = value.w;
}

RENDER_MATH_FORCE_INLINE float4x4 operator+(const float4x4 &a, const float4x4 &b) noexcept {
    float4x4 result;
#if RENDER_MATH_USE_SIMD
    for (int i = 0; i < 4; ++i) {
        _mm_storeu_ps(result.m[i], _mm_add_ps(_mm_loadu_ps(a.m[i]), _mm_loadu_ps(b.m[i])));
    }
#else
    for (int r = 0; r < 4; ++r) {
        for (int c = 0; c < 4; ++c) {
            result.m[r][c] = a.m[r][c] + b.m[r][c];
        }
    }
#endif
    return result;
}

RENDER_MATH_FORCE_INLINE float4x4 operator-(const float4x4 &a, const float4x4 &b) noexcept {
    float4x4 result;
#if RENDER_MATH_USE_SIMD
    for (int i = 0; i < 4; ++i) {
        _mm_storeu_ps(result.m[i], _mm_sub_ps(_mm_loadu_ps(a.m[i]), _mm_loadu_ps(b.m[i])));
    }
#else
    for (int r = 0; r < 4; ++r) {
        for (int c = 0; c < 4; ++c) {
            result.m[r][c] = a.m[r][c] - b.m[r][c];
        }
    }
#endif
    return result;
}

RENDER_MATH_FORCE_INLINE float4x4 operator*(const float4x4 &matrix, float scalar) noexcept {
    float4x4 result;
#if RENDER_MATH_USE_SIMD
    const __m128 s = _mm_set1_ps(scalar);
    for (int i = 0; i < 4; ++i) {
        _mm_storeu_ps(result.m[i], _mm_mul_ps(_mm_loadu_ps(matrix.m[i]), s));
    }
#else
    for (int r = 0; r < 4; ++r) {
        for (int c = 0; c < 4; ++c) {
            result.m[r][c] = matrix.m[r][c] * scalar;
        }
    }
#endif
    return result;
}

RENDER_MATH_FORCE_INLINE float4x4 operator*(float scalar, const float4x4 &matrix) noexcept { return matrix * scalar; }

RENDER_MATH_FORCE_INLINE float4x4 operator/(const float4x4 &matrix, float scalar) noexcept {
    return matrix * (1.0f / scalar);
}

RENDER_MATH_FORCE_INLINE float4x4 multiply(const float4x4 &a, const float4x4 &b) noexcept {
    float4x4 result;
#if RENDER_MATH_USE_SIMD
    const __m128 b0 = _mm_loadu_ps(b.m[0]);
    const __m128 b1 = _mm_loadu_ps(b.m[1]);
    const __m128 b2 = _mm_loadu_ps(b.m[2]);
    const __m128 b3 = _mm_loadu_ps(b.m[3]);

    for (int i = 0; i < 4; ++i) {
        const __m128 ar = _mm_loadu_ps(a.m[i]);
        const __m128 xxxx = _mm_shuffle_ps(ar, ar, _MM_SHUFFLE(0, 0, 0, 0));
        const __m128 yyyy = _mm_shuffle_ps(ar, ar, _MM_SHUFFLE(1, 1, 1, 1));
        const __m128 zzzz = _mm_shuffle_ps(ar, ar, _MM_SHUFFLE(2, 2, 2, 2));
        const __m128 wwww = _mm_shuffle_ps(ar, ar, _MM_SHUFFLE(3, 3, 3, 3));
        const __m128 r01 = _mm_add_ps(_mm_mul_ps(xxxx, b0), _mm_mul_ps(yyyy, b1));
        const __m128 r23 = _mm_add_ps(_mm_mul_ps(zzzz, b2), _mm_mul_ps(wwww, b3));
        _mm_storeu_ps(result.m[i], _mm_add_ps(r01, r23));
    }
#else
    for (int r = 0; r < 4; ++r) {
        for (int c = 0; c < 4; ++c) {
            result.m[r][c] =
                    a.m[r][0] * b.m[0][c] +
                    a.m[r][1] * b.m[1][c] +
                    a.m[r][2] * b.m[2][c] +
                    a.m[r][3] * b.m[3][c];
        }
    }
#endif
    return result;
}

RENDER_MATH_FORCE_INLINE float4x4 operator*(const float4x4 &a, const float4x4 &b) noexcept {
    return multiply(a, b);
}

RENDER_MATH_FORCE_INLINE float4x4 &operator+=(float4x4 &a, const float4x4 &b) noexcept {
    a = a + b;
    return a;
}
RENDER_MATH_FORCE_INLINE float4x4 &operator-=(float4x4 &a, const float4x4 &b) noexcept {
    a = a - b;
    return a;
}
RENDER_MATH_FORCE_INLINE float4x4 &operator*=(float4x4 &a, const float4x4 &b) noexcept {
    a = a * b;
    return a;
}
RENDER_MATH_FORCE_INLINE float4x4 &operator*=(float4x4 &a, float scalar) noexcept {
    a = a * scalar;
    return a;
}
RENDER_MATH_FORCE_INLINE float4x4 &operator/=(float4x4 &a, float scalar) noexcept {
    a = a / scalar;
    return a;
}

RENDER_MATH_FORCE_INLINE float4x4 transpose(const float4x4 &matrix) noexcept {
    float4x4 result;
#if RENDER_MATH_USE_SIMD
    __m128 r0 = _mm_loadu_ps(matrix.m[0]);
    __m128 r1 = _mm_loadu_ps(matrix.m[1]);
    __m128 r2 = _mm_loadu_ps(matrix.m[2]);
    __m128 r3 = _mm_loadu_ps(matrix.m[3]);
    _MM_TRANSPOSE4_PS(r0, r1, r2, r3);
    _mm_storeu_ps(result.m[0], r0);
    _mm_storeu_ps(result.m[1], r1);
    _mm_storeu_ps(result.m[2], r2);
    _mm_storeu_ps(result.m[3], r3);
#else
    for (int r = 0; r < 4; ++r) {
        for (int c = 0; c < 4; ++c) {
            result.m[r][c] = matrix.m[c][r];
        }
    }
#endif
    return result;
}

RENDER_MATH_FORCE_INLINE bool nearly_equal(
    const float4x4 &a,
    const float4x4 &b,
    float abs_eps = 1.0e-5f,
    float rel_eps = 1.0e-5f) noexcept {
    for (int r = 0; r < 4; ++r) {
        for (int c = 0; c < 4; ++c) {
            if (!nearly_equal(a.m[r][c], b.m[r][c], abs_eps, rel_eps)) {
                return false;
            }
        }
    }
    return true;
}

RENDER_MATH_FORCE_INLINE float determinant(const float4x4 &matrix) noexcept {
    double a[4][4];
    for (int r = 0; r < 4; ++r) {
        for (int c = 0; c < 4; ++c) {
            a[r][c] = static_cast<double>(matrix.m[r][c]);
        }
    }

    double det = 1.0;
    int sign = 1;
    for (int col = 0; col < 4; ++col) {
        int pivot_row = col;
        double pivot_abs = std::fabs(a[col][col]);
        for (int row_index = col + 1; row_index < 4; ++row_index) {
            const double candidate = std::fabs(a[row_index][col]);
            if (candidate > pivot_abs) {
                pivot_abs = candidate;
                pivot_row = row_index;
            }
        }
        if (pivot_abs == 0.0) {
            return 0.0f;
        }
        if (pivot_row != col) {
            for (int c = 0; c < 4; ++c) {
                const double temp = a[col][c];
                a[col][c] = a[pivot_row][c];
                a[pivot_row][c] = temp;
            }
            sign = -sign;
        }
        const double pivot = a[col][col];
        det *= pivot;
        for (int row_index = col + 1; row_index < 4; ++row_index) {
            const double factor = a[row_index][col] / pivot;
            for (int c = col + 1; c < 4; ++c) {
                a[row_index][c] -= factor * a[col][c];
            }
        }
    }
    return static_cast<float>(det * static_cast<double>(sign));
}

RENDER_MATH_FORCE_INLINE bool try_inverse(
    const float4x4 &matrix,
    float4x4 &result,
    float *determinant_out = nullptr,
    float relative_tolerance = 1.0e-8f) noexcept {
    float maximum_element = 0.0f;
    for (int r = 0; r < 4; ++r) {
        for (int c = 0; c < 4; ++c) {
            maximum_element = maximum(maximum_element, std::fabs(matrix.m[r][c]));
        }
    }
    if (maximum_element == 0.0f) {
        result = zero_matrix();
        if (determinant_out) {
            *determinant_out = 0.0f;
        }
        return false;
    }
    const float pivot_tolerance = maximum_element * relative_tolerance;

#if RENDER_MATH_USE_SIMD
    __m128 left[4] = {
        _mm_loadu_ps(matrix.m[0]),
        _mm_loadu_ps(matrix.m[1]),
        _mm_loadu_ps(matrix.m[2]),
        _mm_loadu_ps(matrix.m[3])
    };
    __m128 right[4] = {
        _mm_setr_ps(1.0f, 0.0f, 0.0f, 0.0f),
        _mm_setr_ps(0.0f, 1.0f, 0.0f, 0.0f),
        _mm_setr_ps(0.0f, 0.0f, 1.0f, 0.0f),
        _mm_setr_ps(0.0f, 0.0f, 0.0f, 1.0f)
    };

    float det = 1.0f;
    int sign = 1;
    for (int col = 0; col < 4; ++col) {
        int pivot_row = col;
        float pivot_abs = std::fabs(detail::lane(left[col], col));
        for (int row_index = col + 1; row_index < 4; ++row_index) {
            const float candidate = std::fabs(detail::lane(left[row_index], col));
            if (candidate > pivot_abs) {
                pivot_abs = candidate;
                pivot_row = row_index;
            }
        }
        if (pivot_abs <= pivot_tolerance) {
            result = zero_matrix();
            if (determinant_out) {
                *determinant_out = 0.0f;
            }
            return false;
        }
        if (pivot_row != col) {
            const __m128 left_temp = left[col];
            const __m128 right_temp = right[col];
            left[col] = left[pivot_row];
            right[col] = right[pivot_row];
            left[pivot_row] = left_temp;
            right[pivot_row] = right_temp;
            sign = -sign;
        }

        const float pivot = detail::lane(left[col], col);
        det *= pivot;
        const __m128 inverse_pivot = _mm_set1_ps(1.0f / pivot);
        left[col] = _mm_mul_ps(left[col], inverse_pivot);
        right[col] = _mm_mul_ps(right[col], inverse_pivot);

        for (int row_index = 0; row_index < 4; ++row_index) {
            if (row_index == col) {
                continue;
            }
            const float factor_value = detail::lane(left[row_index], col);
            const __m128 factor = _mm_set1_ps(factor_value);
            left[row_index] = _mm_sub_ps(left[row_index], _mm_mul_ps(factor, left[col]));
            right[row_index] = _mm_sub_ps(right[row_index], _mm_mul_ps(factor, right[col]));
        }
    }

    for (int r = 0; r < 4; ++r) {
        _mm_storeu_ps(result.m[r], right[r]);
    }
    if (determinant_out) {
        *determinant_out = det * static_cast<float>(sign);
    }
    return true;
#else
    float augmented[4][8];
    for (int r = 0; r < 4; ++r) {
        for (int c = 0; c < 4; ++c) {
            augmented[r][c] = matrix.m[r][c];
            augmented[r][c + 4] = (r == c) ? 1.0f : 0.0f;
        }
    }

    float det = 1.0f;
    int sign = 1;
    for (int col = 0; col < 4; ++col) {
        int pivot_row = col;
        float pivot_abs = std::fabs(augmented[col][col]);
        for (int row_index = col + 1; row_index < 4; ++row_index) {
            const float candidate = std::fabs(augmented[row_index][col]);
            if (candidate > pivot_abs) {
                pivot_abs = candidate;
                pivot_row = row_index;
            }
        }
        if (pivot_abs <= pivot_tolerance) {
            result = zero_matrix();
            if (determinant_out) {
                *determinant_out = 0.0f;
            }
            return false;
        }
        if (pivot_row != col) {
            for (int c = 0; c < 8; ++c) {
                const float temp = augmented[col][c];
                augmented[col][c] = augmented[pivot_row][c];
                augmented[pivot_row][c] = temp;
            }
            sign = -sign;
        }

        const float pivot = augmented[col][col];
        det *= pivot;
        const float inv_pivot = 1.0f / pivot;
        for (int c = 0; c < 8; ++c) {
            augmented[col][c] *= inv_pivot;
        }
        for (int row_index = 0; row_index < 4; ++row_index) {
            if (row_index == col) {
                continue;
            }
            const float factor = augmented[row_index][col];
            for (int c = 0; c < 8; ++c) {
                augmented[row_index][c] -= factor * augmented[col][c];
            }
        }
    }

    for (int r = 0; r < 4; ++r) {
        for (int c = 0; c < 4; ++c) {
            result.m[r][c] = augmented[r][c + 4];
        }
    }
    if (determinant_out) {
        *determinant_out = det * static_cast<float>(sign);
    }
    return true;
#endif
}

RENDER_MATH_FORCE_INLINE float4x4 inverse(
    const float4x4 &matrix,
    bool *success = nullptr,
    float *determinant_out = nullptr,
    float relative_tolerance = 1.0e-8f) noexcept {
    float4x4 result;
    const bool ok = try_inverse(matrix, result, determinant_out, relative_tolerance);
    if (success) {
        *success = ok;
    }
    return result;
}

RENDER_MATH_FORCE_INLINE float4 transform(float4 vector, const float4x4 &matrix) noexcept {
#if RENDER_MATH_USE_SIMD
    const __m128 r0 = _mm_loadu_ps(matrix.m[0]);
    const __m128 r1 = _mm_loadu_ps(matrix.m[1]);
    const __m128 r2 = _mm_loadu_ps(matrix.m[2]);
    const __m128 r3 = _mm_loadu_ps(matrix.m[3]);
    const __m128 x = _mm_mul_ps(_mm_set1_ps(vector.x), r0);
    const __m128 y = _mm_mul_ps(_mm_set1_ps(vector.y), r1);
    const __m128 z = _mm_mul_ps(_mm_set1_ps(vector.z), r2);
    const __m128 w = _mm_mul_ps(_mm_set1_ps(vector.w), r3);
    return detail::store_float4(_mm_add_ps(_mm_add_ps(x, y), _mm_add_ps(z, w)));
#else
    return float4(
        vector.x * matrix.m[0][0] + vector.y * matrix.m[1][0] + vector.z * matrix.m[2][0] + vector.w * matrix.m[3][0],
        vector.x * matrix.m[0][1] + vector.y * matrix.m[1][1] + vector.z * matrix.m[2][1] + vector.w * matrix.m[3][1],
        vector.x * matrix.m[0][2] + vector.y * matrix.m[1][2] + vector.z * matrix.m[2][2] + vector.w * matrix.m[3][2],
        vector.x * matrix.m[0][3] + vector.y * matrix.m[1][3] + vector.z * matrix.m[2][3] + vector.w * matrix.m[3][3]);
#endif
}

RENDER_MATH_FORCE_INLINE float4 operator*(float4 vector, const float4x4 &matrix) noexcept {
    return transform(vector, matrix);
}

RENDER_MATH_FORCE_INLINE float3 transform_point(float3 point, const float4x4 &matrix) noexcept {
    const float4 result = transform(float4(point, 1.0f), matrix);
    return float3(result.x, result.y, result.z);
}

RENDER_MATH_FORCE_INLINE float3 transform_vector(float3 vector, const float4x4 &matrix) noexcept {
    const float4 result = transform(float4(vector, 0.0f), matrix);
    return float3(result.x, result.y, result.z);
}

RENDER_MATH_FORCE_INLINE bool transform_coord(
    float3 point,
    const float4x4 &matrix,
    float3 &result,
    float w_tolerance = 1.0e-8f) noexcept {
    const float4 homogeneous = transform(float4(point, 1.0f), matrix);
    if (std::fabs(homogeneous.w) <= w_tolerance) {
        result = float3(0.0f);
        return false;
    }
    const float inverse_w = 1.0f / homogeneous.w;
    result = float3(homogeneous.x * inverse_w, homogeneous.y * inverse_w, homogeneous.z * inverse_w);
    return true;
}

RENDER_MATH_FORCE_INLINE float3 transform_coord(float3 point, const float4x4 &matrix) noexcept {
    float3 result;
    transform_coord(point, matrix, result);
    return result;
}

// -----------------------------------------------------------------------------
// Matrix construction and conversion
// -----------------------------------------------------------------------------

RENDER_MATH_FORCE_INLINE constexpr float4x4 translation_ColVec(float x, float y, float z) noexcept {
    return float4x4(
        1.0f, 0.0f, 0.0f, x,
        0.0f, 1.0f, 0.0f, y,
        0.0f, 0.0f, 1.0f, z,
        0.0f, 0.0f, 0.0f, 1.0f);
}
RENDER_MATH_FORCE_INLINE constexpr float4x4 translation_RowVec(float x, float y, float z) noexcept {
    return float4x4(
        1.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 1.0f, 0.0f,
        x, y, z, 1.0f);
}

RENDER_MATH_FORCE_INLINE constexpr float4x4 translation_ColVec(float3 value) noexcept {
    return translation_ColVec(value.x, value.y, value.z);
}
RENDER_MATH_FORCE_INLINE constexpr float4x4 translation_RowVec(float3 value) noexcept {
    return translation_RowVec(value.x, value.y, value.z);
}

RENDER_MATH_FORCE_INLINE constexpr float4x4 scaling(float x, float y, float z) noexcept {
    return float4x4(
        x, 0.0f, 0.0f, 0.0f,
        0.0f, y, 0.0f, 0.0f,
        0.0f, 0.0f, z, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f);
}

RENDER_MATH_FORCE_INLINE constexpr float4x4 scaling(float3 value) noexcept {
    return scaling(value.x, value.y, value.z);
}

RENDER_MATH_FORCE_INLINE constexpr float4x4 scaling(float uniform_scale) noexcept {
    return scaling(uniform_scale, uniform_scale, uniform_scale);
}

RENDER_MATH_FORCE_INLINE float4x4 rotation_x(float angle) noexcept {
    const float c = std::cos(angle);
    const float s = std::sin(angle);
    return float4x4(
        1.0f, 0.0f, 0.0f, 0.0f,
        0.0f, c, s, 0.0f,
        0.0f, -s, c, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f);
}

RENDER_MATH_FORCE_INLINE float4x4 rotation_y(float angle) noexcept {
    const float c = std::cos(angle);
    const float s = std::sin(angle);
    return float4x4(
        c, 0.0f, -s, 0.0f,
        0.0f, 1.0f, 0.0f, 0.0f,
        s, 0.0f, c, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f);
}

RENDER_MATH_FORCE_INLINE float4x4 rotation_z(float angle) noexcept {
    const float c = std::cos(angle);
    const float s = std::sin(angle);
    return float4x4(
        c, s, 0.0f, 0.0f,
        -s, c, 0.0f, 0.0f,
        0.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f);
}

RENDER_MATH_FORCE_INLINE float4x4 rotation_quaternion(quat rotation) noexcept {
    const quat q = normalize(rotation);
    const float xx = q.x * q.x;
    const float yy = q.y * q.y;
    const float zz = q.z * q.z;
    const float xy = q.x * q.y;
    const float xz = q.x * q.z;
    const float yz = q.y * q.z;
    const float wx = q.w * q.x;
    const float wy = q.w * q.y;
    const float wz = q.w * q.z;

    return float4x4(
        1.0f - 2.0f * (yy + zz), 2.0f * (xy + wz), 2.0f * (xz - wy), 0.0f,
        2.0f * (xy - wz), 1.0f - 2.0f * (xx + zz), 2.0f * (yz + wx), 0.0f,
        2.0f * (xz + wy), 2.0f * (yz - wx), 1.0f - 2.0f * (xx + yy), 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f);
}

RENDER_MATH_FORCE_INLINE float4x4 rotation_axis(float3 axis, float angle) noexcept {
    return rotation_quaternion(quaternion_axis_angle(axis, angle));
}

// Apply roll (Z), then pitch (X), then yaw (Y), matching row-vector composition.
RENDER_MATH_FORCE_INLINE float4x4 rotation_yaw_pitch_roll(float yaw, float pitch, float roll) noexcept {
    return rotation_z(roll) * rotation_x(pitch) * rotation_y(yaw);
}

RENDER_MATH_FORCE_INLINE quat quaternion_rotation_matrix(const float4x4 &matrix) noexcept {
    // Convert the transposed 3x3 to the common column-vector formula.
    const float r00 = matrix.m[0][0];
    const float r01 = matrix.m[1][0];
    const float r02 = matrix.m[2][0];
    const float r10 = matrix.m[0][1];
    const float r11 = matrix.m[1][1];
    const float r12 = matrix.m[2][1];
    const float r20 = matrix.m[0][2];
    const float r21 = matrix.m[1][2];
    const float r22 = matrix.m[2][2];

    quat result;
    const float trace = r00 + r11 + r22;
    if (trace > 0.0f) {
        const float s = std::sqrt(trace + 1.0f) * 2.0f;
        result.w = 0.25f * s;
        result.x = (r21 - r12) / s;
        result.y = (r02 - r20) / s;
        result.z = (r10 - r01) / s;
    } else if (r00 > r11 && r00 > r22) {
        const float s = std::sqrt(1.0f + r00 - r11 - r22) * 2.0f;
        result.w = (r21 - r12) / s;
        result.x = 0.25f * s;
        result.y = (r01 + r10) / s;
        result.z = (r02 + r20) / s;
    } else if (r11 > r22) {
        const float s = std::sqrt(1.0f + r11 - r00 - r22) * 2.0f;
        result.w = (r02 - r20) / s;
        result.x = (r01 + r10) / s;
        result.y = 0.25f * s;
        result.z = (r12 + r21) / s;
    } else {
        const float s = std::sqrt(1.0f + r22 - r00 - r11) * 2.0f;
        result.w = (r10 - r01) / s;
        result.x = (r02 + r20) / s;
        result.y = (r12 + r21) / s;
        result.z = 0.25f * s;
    }
    return normalize(result);
}

RENDER_MATH_FORCE_INLINE quat quaternion_yaw_pitch_roll(float yaw, float pitch, float roll) noexcept {
    return quaternion_rotation_matrix(rotation_yaw_pitch_roll(yaw, pitch, roll));
}

RENDER_MATH_FORCE_INLINE float4x4 compose_ColVec(float3 position, quat rotation, float3 scale) noexcept {
    return translation_ColVec(position) * rotation_quaternion(rotation) * scaling(scale);
}
RENDER_MATH_FORCE_INLINE float4x4 compose_RowVec(float3 position, quat rotation, float3 scale) noexcept {
    return scaling(scale) * rotation_quaternion(rotation) * translation_RowVec(position);
}

RENDER_MATH_FORCE_INLINE bool decompose_RowVec(
    const float4x4 &matrix,
    float3 &scale_out,
    quat &rotation_out,
    float3 &translation_out,
    float tolerance = 1.0e-6f) noexcept {
    if (std::fabs(matrix.m[0][3]) > tolerance ||
        std::fabs(matrix.m[1][3]) > tolerance ||
        std::fabs(matrix.m[2][3]) > tolerance ||
        std::fabs(matrix.m[3][3] - 1.0f) > tolerance) {
        return false;
    }

    translation_out = float3(matrix.m[3][0], matrix.m[3][1], matrix.m[3][2]);
    float3 basis0(matrix.m[0][0], matrix.m[0][1], matrix.m[0][2]);
    float3 basis1(matrix.m[1][0], matrix.m[1][1], matrix.m[1][2]);
    float3 basis2(matrix.m[2][0], matrix.m[2][1], matrix.m[2][2]);

    float sx = length(basis0);
    float sy = length(basis1);
    float sz = length(basis2);
    if (sx <= tolerance || sy <= tolerance || sz <= tolerance) {
        return false;
    }

    basis0 /= sx;
    basis1 /= sy;
    basis2 /= sz;

    const float orthogonality_tolerance = maximum(1.0e-5f, tolerance * 10.0f);
    if (std::fabs(dot(basis0, basis1)) > orthogonality_tolerance ||
        std::fabs(dot(basis0, basis2)) > orthogonality_tolerance ||
        std::fabs(dot(basis1, basis2)) > orthogonality_tolerance) {
        return false; // Shear cannot be represented by scale/rotation/translation.
    }

    if (dot(basis0, cross(basis1, basis2)) < 0.0f) {
        sx = -sx;
        basis0 = -basis0;
    }

    const float4x4 rotation_matrix(
        basis0.x, basis0.y, basis0.z, 0.0f,
        basis1.x, basis1.y, basis1.z, 0.0f,
        basis2.x, basis2.y, basis2.z, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f);

    scale_out = float3(sx, sy, sz);
    rotation_out = quaternion_rotation_matrix(rotation_matrix);
    return true;
}

RENDER_MATH_FORCE_INLINE bool make_normal_matrix(
    const float4x4 &world_matrix,
    float4x4 &result,
    float relative_tolerance = 1.0e-8f) noexcept {
    float4x4 inverse_world;
    if (!try_inverse(world_matrix, inverse_world, nullptr, relative_tolerance)) {
        result = zero_matrix();
        return false;
    }
    result = transpose(inverse_world);
    result.m[0][3] = 0.0f;
    result.m[1][3] = 0.0f;
    result.m[2][3] = 0.0f;
    result.m[3][0] = 0.0f;
    result.m[3][1] = 0.0f;
    result.m[3][2] = 0.0f;
    result.m[3][3] = 1.0f;
    return true;
}

// -----------------------------------------------------------------------------
// Camera and Direct3D projection matrices
// -----------------------------------------------------------------------------

RENDER_MATH_FORCE_INLINE float4x4 look_to_lh(float3 eye, float3 direction, float3 up) noexcept {
    const float3 z_axis = normalize(direction);
    const float3 x_axis = normalize(cross(up, z_axis));
    const float3 y_axis = cross(z_axis, x_axis);

    return float4x4(
        x_axis.x, y_axis.x, z_axis.x, 0.0f,
        x_axis.y, y_axis.y, z_axis.y, 0.0f,
        x_axis.z, y_axis.z, z_axis.z, 0.0f,
        -dot(x_axis, eye), -dot(y_axis, eye), -dot(z_axis, eye), 1.0f);
}

RENDER_MATH_FORCE_INLINE float4x4 look_at_lh(float3 eye, float3 target, float3 up) noexcept {
    return look_to_lh(eye, target - eye, up);
}

RENDER_MATH_FORCE_INLINE float4x4 look_to_rh(float3 eye, float3 direction, float3 up) noexcept {
    return look_to_lh(eye, -direction, up);
}

RENDER_MATH_FORCE_INLINE float4x4 look_at_rh(float3 eye, float3 target, float3 up) noexcept {
    return look_to_rh(eye, target - eye, up);
}

RENDER_MATH_FORCE_INLINE float4x4 perspective_fov_lh(
    float vertical_fov,
    float aspect_ratio,
    float near_plane,
    float far_plane) noexcept {
    if (!(vertical_fov > 0.0f && vertical_fov < pi) || aspect_ratio <= 0.0f ||
        near_plane <= 0.0f || far_plane <= near_plane) {
        return zero_matrix();
    }
    const float y_scale = 1.0f / std::tan(vertical_fov * 0.5f);
    const float x_scale = y_scale / aspect_ratio;
    const float z_scale = far_plane / (far_plane - near_plane);
    return float4x4(
        x_scale, 0.0f, 0.0f, 0.0f,
        0.0f, y_scale, 0.0f, 0.0f,
        0.0f, 0.0f, z_scale, 1.0f,
        0.0f, 0.0f, -near_plane * z_scale, 0.0f);
}

RENDER_MATH_FORCE_INLINE float4x4 perspective_fov_rh(
    float vertical_fov,
    float aspect_ratio,
    float near_plane,
    float far_plane) noexcept {
    if (!(vertical_fov > 0.0f && vertical_fov < pi) || aspect_ratio <= 0.0f ||
        near_plane <= 0.0f || far_plane <= near_plane) {
        return zero_matrix();
    }
    const float y_scale = 1.0f / std::tan(vertical_fov * 0.5f);
    const float x_scale = y_scale / aspect_ratio;
    const float z_scale = far_plane / (near_plane - far_plane);
    return float4x4(
        x_scale, 0.0f, 0.0f, 0.0f,
        0.0f, y_scale, 0.0f, 0.0f,
        0.0f, 0.0f, z_scale, -1.0f,
        0.0f, 0.0f, near_plane * z_scale, 0.0f);
}

RENDER_MATH_FORCE_INLINE float4x4 orthographic_lh(
    float width,
    float height,
    float near_plane,
    float far_plane) noexcept {
    if (std::fabs(width) <= epsilon || std::fabs(height) <= epsilon || far_plane <= near_plane) {
        return zero_matrix();
    }
    const float inverse_depth = 1.0f / (far_plane - near_plane);
    return float4x4(
        2.0f / width, 0.0f, 0.0f, 0.0f,
        0.0f, 2.0f / height, 0.0f, 0.0f,
        0.0f, 0.0f, inverse_depth, 0.0f,
        0.0f, 0.0f, -near_plane * inverse_depth, 1.0f);
}

RENDER_MATH_FORCE_INLINE float4x4 orthographic_rh(
    float width,
    float height,
    float near_plane,
    float far_plane) noexcept {
    if (std::fabs(width) <= epsilon || std::fabs(height) <= epsilon || far_plane <= near_plane) {
        return zero_matrix();
    }
    const float inverse_depth = 1.0f / (near_plane - far_plane);
    return float4x4(
        2.0f / width, 0.0f, 0.0f, 0.0f,
        0.0f, 2.0f / height, 0.0f, 0.0f,
        0.0f, 0.0f, inverse_depth, 0.0f,
        0.0f, 0.0f, near_plane * inverse_depth, 1.0f);
}

RENDER_MATH_FORCE_INLINE float4x4 orthographic_off_center_lh(
    float left,
    float right,
    float bottom,
    float top,
    float near_plane,
    float far_plane) noexcept {
    const float width = right - left;
    const float height = top - bottom;
    const float depth = far_plane - near_plane;
    if (std::fabs(width) <= epsilon || std::fabs(height) <= epsilon || depth <= 0.0f) {
        return zero_matrix();
    }
    return float4x4(
        2.0f / width, 0.0f, 0.0f, 0.0f,
        0.0f, 2.0f / height, 0.0f, 0.0f,
        0.0f, 0.0f, 1.0f / depth, 0.0f,
        -(left + right) / width,
        -(top + bottom) / height,
        -near_plane / depth,
        1.0f);
}

RENDER_MATH_FORCE_INLINE float4x4 orthographic_off_center_rh(
    float left,
    float right,
    float bottom,
    float top,
    float near_plane,
    float far_plane) noexcept {
    const float width = right - left;
    const float height = top - bottom;
    const float depth = near_plane - far_plane;
    if (std::fabs(width) <= epsilon || std::fabs(height) <= epsilon || far_plane <= near_plane) {
        return zero_matrix();
    }
    return float4x4(
        2.0f / width, 0.0f, 0.0f, 0.0f,
        0.0f, 2.0f / height, 0.0f, 0.0f,
        0.0f, 0.0f, 1.0f / depth, 0.0f,
        -(left + right) / width,
        -(top + bottom) / height,
        near_plane / depth,
        1.0f);
}

// -----------------------------------------------------------------------------
// Viewport projection/unprojection
// -----------------------------------------------------------------------------

struct viewport {
    float x;
    float y;
    float width;
    float height;
    float min_depth;
    float max_depth;

    constexpr viewport() noexcept
    : x(0.0f), y(0.0f), width(0.0f), height(0.0f), min_depth(0.0f), max_depth(1.0f) {}
    constexpr viewport(float x_value, float y_value, float width_value, float height_value,
                       float min_depth_value = 0.0f, float max_depth_value = 1.0f) noexcept
    : x(x_value), y(y_value), width(width_value), height(height_value),
      min_depth(min_depth_value), max_depth(max_depth_value) {}
};

RENDER_MATH_FORCE_INLINE bool project(
    float3 point,
    const viewport &vp,
    const float4x4 &world_view_projection,
    float3 &result,
    float w_tolerance = 1.0e-8f) noexcept {
    if (std::fabs(vp.width) <= epsilon || std::fabs(vp.height) <= epsilon ||
        std::fabs(vp.max_depth - vp.min_depth) <= epsilon) {
        result = float3(0.0f);
        return false;
    }
    const float4 clip = transform(float4(point, 1.0f), world_view_projection);
    if (std::fabs(clip.w) <= w_tolerance) {
        result = float3(0.0f);
        return false;
    }
    const float inverse_w = 1.0f / clip.w;
    const float ndc_x = clip.x * inverse_w;
    const float ndc_y = clip.y * inverse_w;
    const float ndc_z = clip.z * inverse_w;
    result.x = vp.x + (ndc_x + 1.0f) * 0.5f * vp.width;
    result.y = vp.y + (1.0f - ndc_y) * 0.5f * vp.height;
    result.z = vp.min_depth + ndc_z * (vp.max_depth - vp.min_depth);
    return true;
}

RENDER_MATH_FORCE_INLINE bool project(
    float3 point,
    const viewport &vp,
    const float4x4 &world,
    const float4x4 &view,
    const float4x4 &projection,
    float3 &result,
    float w_tolerance = 1.0e-8f) noexcept {
    return project(point, vp, world * view * projection, result, w_tolerance);
}

RENDER_MATH_FORCE_INLINE bool unproject(
    float3 screen_point,
    const viewport &vp,
    const float4x4 &world_view_projection,
    float3 &result,
    float relative_tolerance = 1.0e-8f) noexcept {
    if (std::fabs(vp.width) <= epsilon || std::fabs(vp.height) <= epsilon ||
        std::fabs(vp.max_depth - vp.min_depth) <= epsilon) {
        result = float3(0.0f);
        return false;
    }

    float4x4 inverse_wvp;
    if (!try_inverse(world_view_projection, inverse_wvp, nullptr, relative_tolerance)) {
        result = float3(0.0f);
        return false;
    }

    const float ndc_x = ((screen_point.x - vp.x) / vp.width) * 2.0f - 1.0f;
    const float ndc_y = 1.0f - ((screen_point.y - vp.y) / vp.height) * 2.0f;
    const float ndc_z = (screen_point.z - vp.min_depth) / (vp.max_depth - vp.min_depth);
    const float4 world_h = transform(float4(ndc_x, ndc_y, ndc_z, 1.0f), inverse_wvp);
    if (std::fabs(world_h.w) <= epsilon) {
        result = float3(0.0f);
        return false;
    }
    const float inverse_w = 1.0f / world_h.w;
    result = float3(world_h.x * inverse_w, world_h.y * inverse_w, world_h.z * inverse_w);
    return true;
}

RENDER_MATH_FORCE_INLINE bool unproject(
    float3 screen_point,
    const viewport &vp,
    const float4x4 &world,
    const float4x4 &view,
    const float4x4 &projection,
    float3 &result,
    float relative_tolerance = 1.0e-8f) noexcept {
    return unproject(screen_point, vp, world * view * projection, result, relative_tolerance);
}

// -----------------------------------------------------------------------------
// Rendering conversions: UNORM8 and IEEE-754 binary16
// -----------------------------------------------------------------------------

namespace detail {
RENDER_MATH_FORCE_INLINE std::uint32_t unorm8(float value) noexcept {
    if (!(value > 0.0f)) {
        return 0u;
    }
    if (value >= 1.0f) {
        return 255u;
    }
    return static_cast<std::uint32_t>(value * 255.0f + 0.5f);
}
} // namespace detail

// Integer layout: 0xAABBGGRR. In little-endian memory this is R, G, B, A.
RENDER_MATH_FORCE_INLINE std::uint32_t pack_rgba8_unorm(float4 color) noexcept {
    const std::uint32_t r = detail::unorm8(color.x);
    const std::uint32_t g = detail::unorm8(color.y);
    const std::uint32_t b = detail::unorm8(color.z);
    const std::uint32_t a = detail::unorm8(color.w);
    return r | (g << 8u) | (b << 16u) | (a << 24u);
}

RENDER_MATH_FORCE_INLINE constexpr float4 unpack_rgba8_unorm(std::uint32_t packed) noexcept {
    return float4(
        static_cast<float>(packed & 0xffu) / 255.0f,
        static_cast<float>((packed >> 8u) & 0xffu) / 255.0f,
        static_cast<float>((packed >> 16u) & 0xffu) / 255.0f,
        static_cast<float>((packed >> 24u) & 0xffu) / 255.0f);
}

RENDER_MATH_FORCE_INLINE std::uint16_t float_to_half(float value) noexcept {
    std::uint32_t bits = 0u;
    std::memcpy(&bits, &value, sizeof(bits));

    const std::uint32_t sign = (bits >> 16u) & 0x8000u;
    const std::uint32_t exponent = (bits >> 23u) & 0xffu;
    std::uint32_t mantissa = bits & 0x7fffffu;

    if (exponent == 0xffu) {
        if (mantissa == 0u) {
            return static_cast<std::uint16_t>(sign | 0x7c00u);
        }
        const std::uint32_t payload = maximum(1u, mantissa >> 13u);
        return static_cast<std::uint16_t>(sign | 0x7c00u | payload | 0x0200u);
    }

    const int half_exponent = static_cast<int>(exponent) - 127 + 15;
    if (half_exponent >= 31) {
        return static_cast<std::uint16_t>(sign | 0x7c00u);
    }

    if (half_exponent <= 0) {
        if (half_exponent < -10) {
            return static_cast<std::uint16_t>(sign);
        }
        mantissa |= 0x800000u;
        const int shift = 14 - half_exponent;
        std::uint32_t half_mantissa = mantissa >> shift;
        const std::uint32_t remainder_mask = (1u << shift) - 1u;
        const std::uint32_t remainder = mantissa & remainder_mask;
        const std::uint32_t halfway = 1u << (shift - 1);
        if (remainder > halfway || (remainder == halfway && (half_mantissa & 1u) != 0u)) {
            ++half_mantissa;
        }
        return static_cast<std::uint16_t>(sign | half_mantissa);
    }

    std::uint32_t result = sign | (static_cast<std::uint32_t>(half_exponent) << 10u) | (mantissa >> 13u);
    const std::uint32_t remainder = mantissa & 0x1fffu;
    if (remainder > 0x1000u || (remainder == 0x1000u && (result & 1u) != 0u)) {
        ++result;
    }
    return static_cast<std::uint16_t>(result);
}

RENDER_MATH_FORCE_INLINE float half_to_float(std::uint16_t value) noexcept {
    const std::uint32_t sign = (static_cast<std::uint32_t>(value & 0x8000u)) << 16u;
    const std::uint32_t exponent = (value >> 10u) & 0x1fu;
    std::uint32_t mantissa = value & 0x03ffu;
    std::uint32_t bits = 0u;

    if (exponent == 0u) {
        if (mantissa == 0u) {
            bits = sign;
        } else {
            int e = -14;
            while ((mantissa & 0x0400u) == 0u) {
                mantissa <<= 1u;
                --e;
            }
            mantissa &= 0x03ffu;
            const std::uint32_t exponent32 = static_cast<std::uint32_t>(e + 127);
            bits = sign | (exponent32 << 23u) | (mantissa << 13u);
        }
    } else if (exponent == 0x1fu) {
        bits = sign | 0x7f800000u | (mantissa << 13u);
        if (mantissa != 0u) {
            bits |= 0x00400000u;
        }
    } else {
        const std::uint32_t exponent32 = exponent + static_cast<std::uint32_t>(127 - 15);
        bits = sign | (exponent32 << 23u) | (mantissa << 13u);
    }

    float result = 0.0f;
    std::memcpy(&result, &bits, sizeof(result));
    return result;
}

RENDER_MATH_FORCE_INLINE std::uint32_t pack_half2(float2 value) noexcept {
    return static_cast<std::uint32_t>(float_to_half(value.x)) |
           (static_cast<std::uint32_t>(float_to_half(value.y)) << 16u);
}

RENDER_MATH_FORCE_INLINE float2 unpack_half2(std::uint32_t packed) noexcept {
    return float2(
        half_to_float(static_cast<std::uint16_t>(packed & 0xffffu)),
        half_to_float(static_cast<std::uint16_t>(packed >> 16u)));
}

static_assert(sizeof(float2) == 8, "float2 must contain two floats");
static_assert(sizeof(float3) == 12, "float3 must contain three tightly packed floats");
static_assert(sizeof(float4) == 16, "float4 must contain four floats");
static_assert(sizeof(quat) == 16, "quat must contain four floats");
static_assert(sizeof(float4x4) == 64, "float4x4 must contain sixteen floats");
static_assert(alignof(float4) == 16, "float4 must be 16-byte aligned");
static_assert(alignof(quat) == 16, "quat must be 16-byte aligned");
static_assert(alignof(float4x4) == 16, "float4x4 must be 16-byte aligned");
static_assert(std::is_standard_layout<float4x4>::value, "float4x4 must be standard-layout");

} // namespace math

#undef RENDER_MATH_FORCE_INLINE
#endif //RENDER_MATH_HPP
