// ===========================
// 🔧 Helper Macros
// ===========================

#define diagonal3(m) vec3((m)[0].x, (m)[1].y, m[2].z)
#define projMAD(m, v) (diagonal3(m) * (v) + (m)[3].xyz)
#define viewMAD(m, v) (mat3(m) * (v) + (m)[3].xyz)

#ifdef DISTANT_HORIZONS
    #define diagonal3_dh(m) vec3((m)[0].x, (m)[1].y, m[2].z)
    #define projMAD_dh(m, v) (diagonal3_dh(m) * (v) + (m)[3].xyz)
#endif

// ===========================
// 🌍 World Space Conversions
// ===========================

vec3 toWorldSpace(vec3 p3)
{
    p3 = mat3(gbufferModelViewInverse) * p3 + gbufferModelViewInverse[3].xyz;

#ifdef PIXEL_LOCK
    p3 = floor((p3 + cameraPosition) * 16 + 0.001) / 16 - cameraPosition + 0.5 / 16;
#endif

    return p3;
}

vec3 toWorldSpaceNoLock(vec3 p3)
{
    p3 = mat3(gbufferModelViewInverse) * p3 + gbufferModelViewInverse[3].xyz;
    return p3;
}

vec3 worldToView(vec3 worldPos)
{
    return (gbufferModelView * vec4(worldPos, 0.0)).xyz;
}

vec3 viewToWorld(vec3 viewPos)
{
    return (gbufferModelViewInverse * vec4(viewPos, 0.0)).xyz;
}

vec3 viewToSceneSpace(vec3 viewpos, mat4 mvInv)
{
    return projMAD(mvInv, viewpos);
}

vec3 viewToSceneSpace(vec3 viewpos)
{
    return viewToSceneSpace(viewpos, gbufferModelViewInverse);
}

// ===========================
// 🌑 Shadow Space Conversions
// ===========================

vec3 shadowSpace0()
{
    vec3 p3 = gbufferModelViewInverse[3].xyz;
    p3 = mat3(shadowModelView) * p3 + shadowModelView[3].xyz;
    p3 = diagonal3(shadowProjection) * p3 + shadowProjection[3].xyz;
    return p3;
}

vec3 toShadowSpaceProjected(vec3 p3)
{
    p3 = toWorldSpace(p3);
    p3 = mat3(shadowModelView) * p3 + shadowModelView[3].xyz;
    p3 = diagonal3(shadowProjection) * p3 + shadowProjection[3].xyz;
    return p3;
}

// ===========================
// 🎯 Clip Space Conversions
// ===========================
#ifdef VOXY

vec3 toClipSpace3_voxy(vec3 viewSpacePosition)
{
    return projMAD(vxProj, viewSpacePosition) / -viewSpacePosition.z * 0.5 + 0.5;
}
#endif
vec3 toClipSpace3(vec3 viewSpacePosition)
{
    return projMAD(gbProjection_proxy, viewSpacePosition) / -viewSpacePosition.z * 0.5 + 0.5;
}

vec4 toClipSpace3_shadow(vec3 viewSpacePosition)
{
    return vec4(projMAD(gl_ProjectionMatrix, viewSpacePosition), 1.0);
}

vec4 toClipSpace4(vec3 viewSpacePosition)
{
    return vec4(projMAD(gl_ProjectionMatrix, viewSpacePosition), -viewSpacePosition.z);
}

vec3 toClipSpace3Prev(vec3 viewSpacePosition)
{
    return projMAD(gbufferPreviousProjection, viewSpacePosition) / -viewSpacePosition.z * 0.5 + 0.5;
}

#ifdef DISTANT_HORIZONS
    #define diagonal3_dh(m) vec3((m)[0].x, (m)[1].y, m[2].z)
    #define projMAD_dh(m, v) (diagonal3_dh(m) * (v) + (m)[3].xyz)

vec3 toClipSpace3DH(vec3 viewSpacePosition)
{
    return projMAD_dh(dhProjection, viewSpacePosition) / -viewSpacePosition.z * 0.5 + 0.5;
}

vec3 toClipSpace3_org(vec3 viewSpacePosition)
{
    return projMAD_dh(gbProjection_proxy, viewSpacePosition) / -viewSpacePosition.z * 0.5 + 0.5;
}

vec3 toClipSpace3_alt(vec3 viewSpacePosition)
{
    return projMAD_dh(gbProjection_proxy, viewSpacePosition) / -viewSpacePosition.z * 0.5 + 0.5;
}
#endif

// ===========================
// 🖥️ Screen Space Conversions
// ===========================

vec3 toScreenSpace(vec3 p)
{
    vec4 iProjDiag = vec4(gbProjectionInverse_proxy[0].x, gbProjectionInverse_proxy[1].y, gbProjectionInverse_proxy[2].zw);
    vec3 p3 = p * 2. - 1.;
    vec4 fragposition = iProjDiag * p3.xyzz + gbProjectionInverse_proxy[3];
    return fragposition.xyz / fragposition.w;
}
vec3 toScreenSpace_og(vec3 p)
{
    vec4 iProjDiag = vec4(gbufferProjectionInverse[0].x, gbufferProjectionInverse[1].y, gbufferProjectionInverse[2].zw);
    vec3 p3 = p * 2. - 1.;
    vec4 fragposition = iProjDiag * p3.xyzz + gbufferProjectionInverse[3];
    return fragposition.xyz / fragposition.w;
}

#ifdef DISTANT_HORIZONS
vec3 toScreenSpaceDH(vec3 position)
{
    position = position * 2.0 - 1.0;
    position.xy = vec2(dhProjectionInverse[0].x, dhProjectionInverse[1].y) * position.xy + dhProjectionInverse[3].xy;
    return vec3(position.xy, dhProjectionInverse[3].z) /
           (dhProjectionInverse[2].w * position.z + dhProjectionInverse[3].w);
}

vec3 toScreenSpace_org(vec3 p)
{
    vec4 iProjDiag = vec4(gbProjectionInverse_proxy[0].x, gbProjectionInverse_proxy[1].y, gbProjectionInverse_proxy[2].zw);
    vec3 p3 = p * 2. - 1.;
    vec4 fragposition = iProjDiag * p3.xyzz + gbProjectionInverse_proxy[3];
    return fragposition.xyz / fragposition.w;
}

vec3 toScreenSpace_alt(vec2 texcoord, float depthA, float depthB, bool hand)
{
    #ifdef DISTANT_HORIZONS
    if (depthA >= 1.0)
    {
        return toScreenSpaceDH(vec3(texcoord, depthB));
    }
    else
    {
        return toScreenSpace_org(vec3(texcoord, depthA));
    }
    #else
    return toScreenSpace_org(vec3(texcoord, depthA));
    #endif
}
#endif
#ifdef VOXY
vec3 toScreenSpaceDH(vec3 position)
{
    position = position * 2.0 - 1.0;
    position.xy = vec2(vxProjInv[0].x, vxProjInv[1].y) * position.xy + vxProjInv[3].xy;
    return vec3(position.xy, vxProjInv[3].z) /
           (vxProjInv[2].w * position.z + vxProjInv[3].w);
}

vec3 toScreenSpace2(vec3 p)
{
    vec4 iProjDiag = vec4(gbProjectionInverse_proxy[0].x, gbProjectionInverse_proxy[1].y, gbProjectionInverse_proxy[2].zw);
    vec3 p3 = p * 2. - 1.;
    vec4 fragposition = iProjDiag * p3.xyzz + gbProjectionInverse_proxy[3];
    return fragposition.xyz / fragposition.w;
}
float ld4(const float dist)
{
    return (2.0 * nearorg) / (farorg + nearorg - dist * (far - nearorg));
}
// return vec3(ld4(depthA) < (1 / far) * farorg);
vec3 toScreenSpace_og3(vec3 p)
{
    vec4 iProjDiag = vec4(gbufferProjectionInverse[0].x, gbufferProjectionInverse[1].y, gbufferProjectionInverse[2].zw);
    vec3 p3 = p * 2. - 1.;
    vec4 fragposition = iProjDiag * p3.xyzz + gbufferProjectionInverse[3];
    return fragposition.xyz / fragposition.w;
}

vec3 toScreenSpace_alt(vec2 texcoord, float depthA, float depthB, bool hand)
{
    vec3 A = toScreenSpace_og(vec3(texcoord, depthA));
    vec3 B = toScreenSpaceDH(vec3(texcoord, depthB));
    vec3 outv = B;
    vec3 p3 = toWorldSpace(A);

    float eyeDist = max(length(p3.xz), abs(p3.y)) / far;

    bool check2 = (eyeDist < (1 / far) * (farorg - 16));
    if (depthB >= 1 || hand || check2)
        outv = A;

    return outv;
}

#endif

vec2 WorldToScreenPos(vec3 worldPos)
{
    vec4 clipSpacePos = gbProjection_proxy * vec4(worldPos, 1.0);
    vec3 ndc = clipSpacePos.xyz / clipSpacePos.w; // Perspective divide
    return ndc.xy * 0.5 + 0.5;                    // NDC [-1,1] -> UV [0,1]
}

// ===========================
// 🔁 Screen <-> View Space
// ===========================

vec3 ViewSpaceFromScreenSpace(vec3 pos, mat4 inverseProjection)
{
    pos = pos * 2.0 - 1.0;
    vec3 viewPosition = vec3(vec2(inverseProjection[0].x, inverseProjection[1].y) * pos.xy + inverseProjection[3].xy, inverseProjection[3].z);
    viewPosition /= inverseProjection[2].w * pos.z + inverseProjection[3].w;
    return viewPosition;
}

vec3 ScreenSpaceFromViewSpace(vec3 viewPosition, mat4 projection)
{
    vec3 screenPosition = vec3(projection[0].x, projection[1].y, projection[2].z) * viewPosition + projection[3].xyz;
    return screenPosition * (0.5 / -viewPosition.z) + 0.5;
}

float ViewSpaceFromScreenSpace(float positionScreen, mat4 projectionInverse)
{
    return projectionInverse[3].z / (projectionInverse[2].w * (positionScreen * 2.0 - 1.0) + projectionInverse[3].w);
}

// ===========================
// ⏮️ Reprojection Functions
// ===========================

vec3 Reproject(vec3 position)
{
    bool isHand = position.z < 0.6;
    position = ViewSpaceFromScreenSpace(position, gbProjectionInverse_proxy);

    vec3 worldPosition = toWorldSpaceNoLock(position);
    worldPosition += (!isHand && position.z < 1.0) ? (cameraPosition - previousCameraPosition) : vec3(0.0);

    vec3 previousViewPos = mat3(gbufferPreviousModelView) * worldPosition + gbufferPreviousModelView[3].xyz;
    return ScreenSpaceFromViewSpace(previousViewPos, gbufferPreviousProjection);
}
#ifdef VOXY

vec3 Reprojectv(vec3 position)
{
    bool isHand = position.z < 0.6;
    position = ViewSpaceFromScreenSpace(position, vxProjInv);

    vec3 worldPosition = toWorldSpaceNoLock(position);
    worldPosition += (!isHand && position.z < 1.0) ? (cameraPosition - previousCameraPosition) : vec3(0.0);

    vec3 previousViewPos = mat3(gbufferPreviousModelView) * worldPosition + gbufferPreviousModelView[3].xyz;
    return ScreenSpaceFromViewSpace(previousViewPos, vxProjPrev);
}

#endif
vec3 ReprojectSimple(vec3 position)
{
    position = ViewSpaceFromScreenSpace(position, gbProjectionInverse_proxy);

    vec3 worldPosition = toWorldSpaceNoLock(position);
    worldPosition += (cameraPosition - previousCameraPosition);

    vec3 previousViewPos = mat3(gbufferPreviousModelView) * worldPosition + gbufferPreviousModelView[3].xyz;
    return ScreenSpaceFromViewSpace(previousViewPos, gbufferPreviousProjection);
}
vec3 ReprojectWorld(vec3 worldPosition)
{
    vec3 previousViewPos = mat3(gbufferPreviousModelView) * worldPosition + gbufferPreviousModelView[3].xyz;
    return ScreenSpaceFromViewSpace(previousViewPos, gbufferPreviousProjection);
}

#ifdef DISTANT_HORIZONS
vec3 ReprojectDH(vec3 position)
{
    bool isHand = position.z < 0.6;
    position = ViewSpaceFromScreenSpace(position, dhProjectionInverse);

    vec3 worldPosition = toWorldSpaceNoLock(position);
    //  worldPosition += (!isHand && position.z < 1.0) ? (cameraPosition - previousCameraPosition) : vec3(0.0);

    vec3 previousViewPos = mat3(gbufferPreviousModelView) * worldPosition + gbufferPreviousModelView[3].xyz;
    return ScreenSpaceFromViewSpace(previousViewPos, dhPreviousProjection);
}

vec3 Reproject2DH(vec3 position)
{
    position = ViewSpaceFromScreenSpace(position, dhProjectionInverse);

    vec3 worldPosition = toWorldSpaceNoLock(position);
    worldPosition += (cameraPosition - previousCameraPosition);

    vec3 previousViewPos = mat3(gbufferPreviousModelView) * worldPosition + gbufferPreviousModelView[3].xyz;
    return ScreenSpaceFromViewSpace(previousViewPos, dhPreviousProjection);
}
vec3 ReprojectWorldDH(vec3 worldPosition)
{
    vec3 previousViewPos = mat3(gbufferPreviousModelView) * worldPosition + gbufferPreviousModelView[3].xyz;
    return ScreenSpaceFromViewSpace(previousViewPos, dhPreviousProjection);
}
vec3 reprojectDH(vec3 sceneSpace, bool hand)
{
    vec3 prevScreenPos = hand ? vec3(0.0) : cameraPosition - previousCameraPosition;
    prevScreenPos = sceneSpace + prevScreenPos;
    prevScreenPos = viewMAD(gbufferPreviousModelView, prevScreenPos);
    prevScreenPos = viewMAD(dhPreviousProjection, prevScreenPos) * (0.5 / -prevScreenPos.z) + 0.5;
    return prevScreenPos;
}

#endif
