// ===========================
// 🔧 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
// ===========================

vec3 toClipSpace3(vec3 viewSpacePosition)
{
    return projMAD(gbufferProjection, 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(gbufferProjection, viewSpacePosition) / -viewSpacePosition.z * 0.5 + 0.5;
}

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

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

vec3 toScreenSpace(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(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)
{
    #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
vec2 WorldToScreenPos(vec3 worldPos)
{
    vec4 clipSpacePos = gbufferProjection * 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, gbufferProjectionInverse);

    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);
}

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
