/* Refrax — lib/common.glsl */
#ifndef REFRAX_COMMON
#define REFRAX_COMMON

const float PI = 3.14159265359;
#define saturate(x) clamp(x, 0.0, 1.0)
#define HAND_DEPTH_LIMIT 0.56

float pow2(float x) { return x * x; }
float pow3(float x) { return x * x * x; }
float pow4(float x) { float y = x * x; return y * y; }
float pow5(float x) { float y = x * x; return y * y * x; }

float luminance(vec3 c) { return dot(c, vec3(0.2126, 0.7152, 0.0722)); }
vec3 srgbToLinear(vec3 c) { return pow(c, vec3(2.2)); }
vec3 linearToSrgb(vec3 c) { return pow(max(c, 0.0), vec3(1.0 / 2.2)); }


float ign(vec2 px) {
    return fract(52.9829189 * fract(0.06711056 * px.x + 0.00583715 * px.y));
}
float ignAnim(vec2 px, int frame) {
    return ign(px + 5.588238 * float(frame % 64));
}

vec3 screenToView(vec3 screenPos, mat4 projInv) {
    vec4 ndc = vec4(screenPos * 2.0 - 1.0, 1.0);
    vec4 v = projInv * ndc;
    return v.xyz / v.w;
}
vec3 viewToScreen(vec3 viewPos, mat4 proj) {
    vec4 c = proj * vec4(viewPos, 1.0);
    return c.xyz / c.w * 0.5 + 0.5;
}
float linearizeDepth(float d, float near, float far) {
    return (2.0 * near * far) / (far + near - (d * 2.0 - 1.0) * (far - near));
}


float packSurface(float emission, float dynamicFlag) {
    return dynamicFlag > 0.5 ? -1.0 - emission : emission;
}
float surfaceEmission(float surfaceTag) {
    return surfaceTag < 0.0 ? -1.0 - surfaceTag : (surfaceTag > 1.0 ? 0.0 : surfaceTag);
}
float surfaceDynamic(float surfaceTag) {
    return surfaceTag < 0.0 ? 1.0 : 0.0;
}

#define SURF_STATIC 0.0
#define SURF_DYNAMIC 1.0
#define SURF_WATER 2.0
#define SURF_PORTAL 3.0

#ifdef GBUFFER_PACK
vec2 octEncode(vec3 n) {
    n /= abs(n.x) + abs(n.y) + abs(n.z);
    vec2 e = n.z >= 0.0 ? n.xy
           : (1.0 - abs(n.yx)) * vec2(n.x >= 0.0 ? 1.0 : -1.0, n.y >= 0.0 ? 1.0 : -1.0);
    return (e * 511.0 + 511.0) * (1.0 / 1023.0);
}
vec3 octDecode(vec2 f) {
    vec2 e = (f * 1023.0 - 511.0) * (1.0 / 511.0);
    float z = 1.0 - abs(e.x) - abs(e.y);
    e += vec2(e.x >= 0.0 ? -1.0 : 1.0, e.y >= 0.0 ? -1.0 : 1.0) * max(-z, 0.0);
    return normalize(vec3(e, z));
}
vec4 packSurfaceData(vec3 n, float emission, float state) {
    return vec4(octEncode(normalize(n)), saturate(emission), state * (1.0 / 3.0));
}
vec3 unpackSurfaceNormal(vec4 c) { return octDecode(c.xy); }
float unpackSurfaceEmission(vec4 c) { return c.z; }
float unpackSurfaceDynamic(vec4 c) { return float(c.w > 0.1667 && c.w < 0.5); }
bool unpackSurfaceWater(vec4 c) { return c.w > 0.5 && c.w < 0.8333; }
bool unpackSurfacePortal(vec4 c) { return c.w > 0.8333; }
#else
vec4 packSurfaceData(vec3 n, float emission, float state) {
    return vec4(normalize(n), state > 1.5 ? state : packSurface(emission, state));
}
vec3 unpackSurfaceNormal(vec4 c) { return normalize(c.xyz); }
float unpackSurfaceEmission(vec4 c) { return surfaceEmission(c.w); }
float unpackSurfaceDynamic(vec4 c) { return surfaceDynamic(c.w); }
bool unpackSurfaceWater(vec4 c) { return c.w > 1.8 && c.w < 2.5; }
bool unpackSurfacePortal(vec4 c) { return c.w > 2.5; }
#endif

vec3 reprojectScene(vec3 scenePos, mat4 prevMV, mat4 prevProj, vec3 camPos, vec3 prevCamPos) {
    vec3 prevScene = scenePos + camPos - prevCamPos;
    vec4 clip = prevProj * (prevMV * vec4(prevScene, 1.0));
    return clip.xyz / clip.w * 0.5 + 0.5;
}

#ifndef UPSCALE_SCALE
#define UPSCALE_SCALE 1.0
#endif
#ifndef FSR_SCALE
#define FSR_SCALE UPSCALE_SCALE
#endif
vec2 fsrRegionUV(vec2 sceneUV, vec2 texel) {
    return clamp(sceneUV * FSR_SCALE, vec2(0.0), vec2(FSR_SCALE) - 0.5 * texel);
}

vec2 cloudRegionUV(vec2 sceneUV, vec2 texel) {
    return clamp(sceneUV * CLOUD_REGION_SCALE, vec2(0.0), vec2(CLOUD_REGION_SCALE) - texel);
}

vec2 aoRegionUV(vec2 sceneUV, vec2 texel) {
    return clamp(sceneUV * AO_REGION_SCALE, vec2(0.0), vec2(AO_REGION_SCALE) - texel);
}

vec2 historyUV(vec2 sceneUV, vec2 texel) {
#ifdef TAAU
    return sceneUV;
#else
    return fsrRegionUV(sceneUV, texel);
#endif
}

vec2 taaOffset(int frame) {
    const vec2 halton[8] = vec2[8](
        vec2(0.5000, 0.3333), vec2(0.2500, 0.6667), vec2(0.7500, 0.1111), vec2(0.1250, 0.4444),
        vec2(0.6250, 0.7778), vec2(0.3750, 0.2222), vec2(0.8750, 0.5556), vec2(0.0625, 0.8889));
    return halton[frame % 8] - 0.5;
}
vec2 taauOffset(int frame) {
    const vec2 r2 = vec2(0.7548776662466927, 0.5698402909980532);
    return fract(r2 * float(frame % 64 + 1)) - 0.5;
}
vec4 taaJitterPos(vec4 clipPos, vec2 viewSize, int frame) {
#ifdef TAAU
    clipPos.xy += taauOffset(frame) * 2.0 * clipPos.w / (viewSize * UPSCALE_SCALE);
#elif defined TEMPORAL_AA
    clipPos.xy += taaOffset(frame) * 2.0 * clipPos.w / viewSize;
#endif
    return clipPos;
}

vec2 taaJitterUV(vec2 viewSize, int frame) {
#if defined TEMPORAL_AA && !defined TAAU
    return taaOffset(frame) / viewSize;
#else
    return vec2(0.0);
#endif
}

vec3 discLightSpecular(vec3 N, vec3 V, vec3 L, float sinRadius, float roughness, vec3 f0) {
    vec3 R = reflect(-V, N);
    vec3 toRay = R * dot(L, R) - L;
    vec3 Lr = normalize(L + toRay * saturate(sinRadius / max(length(toRay), 1e-5)));
    vec3 H = normalize(V + Lr);
    float NoH = saturate(dot(N, H)), NoV = max(dot(N, V), 1e-4), NoL = saturate(dot(N, Lr));
    float a = max(roughness * roughness, 2e-3);
    float ap = min(a + 0.5 * sinRadius, 1.0);
    float norm = a / ap; norm *= norm;
    float ap2 = ap * ap;
    float d = (NoH * ap2 - NoH) * NoH + 1.0;
    float D = ap2 / (PI * d * d) * norm;
    vec3 F = f0 + (1.0 - f0) * pow(1.0 - saturate(dot(V, H)), 5.0);
    float G = 0.5 / mix(2.0 * NoL * NoV, NoL + NoV, ap);
    return min(D * G * NoL, 32.0) * F;
}
vec3 fresnelSchlick(float cosT, vec3 f0) {
    return f0 + (1.0 - f0) * pow5(1.0 - saturate(cosT));
}

vec3 waterDiscLightSpecular(vec3 N, vec3 V, vec3 L, float sinRadius, float roughness, vec3 f0) {
    return min(discLightSpecular(N, V, L, sinRadius, roughness, f0), vec3(4.0));
}

#endif
