/* VertaLux - shared utility functions (included first by every program) */

// ============ TAAU - render scale (TAA upscaling) ============
// TAAU_S = linear fraction of the framebuffer covered by the 3D render, in the
// BOTTOM-LEFT corner. gbuffers/dh map gl_Position into that sub-rect, pre-TAA
// passes work in UV [0..TAAU_S], composite4 rebuilds full resolution, post-TAA
// passes sample scene buffers at uv * TAAU_S.
// Gated on TAA: without TAA composite4 is a passthrough and the scene would
// stay a postage stamp in the corner. At 1.0 every "x * TAAU_S" / "x / TAAU_S"
// folds at compile time, so the path is free when disabled.
// settings.glsl is always included BEFORE common.glsl; an undefined
// TAA_UPSCALING evaluates to 0 in the preprocessor -> TAAU_S = 1.0.
#if defined TAA && TAA_UPSCALING == 1
    #define TAAU_S TAAU_RENDER_SCALE
#else
    #define TAAU_S 1.0
#endif

float luma(vec3 c) {
    return dot(c, vec3(0.2126, 0.7152, 0.0722));
}

// Sub-pixel TAA jitter. Halton(2,3), period 16, indexed by frameCounter.
// Returns NDC offset (+/-0.5 screen pixel): the vsh multiplies by w*(2/res),
// composite4 converts back to UV to de-jitter the reprojection.
vec2 taaJitterNDC(int frame) {
    // Halton(2,3) mod 16 precomputed: this runs per vertex, so the table
    // replaces a per-vertex loop that only ever produced 16 constants.
    int m = frame - (frame / 16) * 16;
    vec2 h;
    if      (m == 0)  h = vec2(0.5,     0.33333334);
    else if (m == 1)  h = vec2(0.25,    0.66666669);
    else if (m == 2)  h = vec2(0.75,    0.11111111);
    else if (m == 3)  h = vec2(0.125,   0.44444445);
    else if (m == 4)  h = vec2(0.625,   0.77777779);
    else if (m == 5)  h = vec2(0.375,   0.22222222);
    else if (m == 6)  h = vec2(0.875,   0.55555556);
    else if (m == 7)  h = vec2(0.0625,  0.88888890);
    else if (m == 8)  h = vec2(0.5625,  0.03703704);
    else if (m == 9)  h = vec2(0.3125,  0.37037037);
    else if (m == 10) h = vec2(0.8125,  0.70370370);
    else if (m == 11) h = vec2(0.1875,  0.14814815);
    else if (m == 12) h = vec2(0.6875,  0.48148148);
    else if (m == 13) h = vec2(0.4375,  0.81481481);
    else if (m == 14) h = vec2(0.9375,  0.25925926);
    else              h = vec2(0.03125, 0.59259259);
    return h - 0.5;
}

vec3 toLinear(vec3 c) {
    return pow(max(c, 0.0), vec3(2.2)); // max: pow() of a negative component returns NaN
}

vec3 toSrgb(vec3 c) {
    return pow(c, vec3(1.0 / 2.2));
}

// Ordered dithering (Bayer matrix) to break up banding
float bayer2(vec2 a) {
    a = floor(a);
    return fract(a.x * 0.5 + a.y * a.y * 0.75);
}
#define bayer4(a)  (bayer2(0.5 * (a)) * 0.25 + bayer2(a))
#define bayer8(a)  (bayer4(0.5 * (a)) * 0.25 + bayer2(a))

float hash12(vec2 p) {
    vec3 p3 = fract(vec3(p.xyx) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}

float hash13(vec3 p) {
    p = fract(p * 0.1031);
    p += dot(p, p.zyx + 31.32);
    return fract((p.x + p.y) * p.z);
}

float vnoise(vec2 p) {
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash12(i);
    float b = hash12(i + vec2(1.0, 0.0));
    float c = hash12(i + vec2(0.0, 1.0));
    float d = hash12(i + vec2(1.0, 1.0));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

// 3D value noise (volumetric clouds)
float vnoise3(vec3 p) {
    vec3 i = floor(p);
    vec3 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float n000 = hash13(i);
    float n100 = hash13(i + vec3(1.0, 0.0, 0.0));
    float n010 = hash13(i + vec3(0.0, 1.0, 0.0));
    float n110 = hash13(i + vec3(1.0, 1.0, 0.0));
    float n001 = hash13(i + vec3(0.0, 0.0, 1.0));
    float n101 = hash13(i + vec3(1.0, 0.0, 1.0));
    float n011 = hash13(i + vec3(0.0, 1.0, 1.0));
    float n111 = hash13(i + vec3(1.0, 1.0, 1.0));
    return mix(mix(mix(n000, n100, f.x), mix(n010, n110, f.x), f.y),
               mix(mix(n001, n101, f.x), mix(n011, n111, f.x), f.y), f.z);
}

float fbm(vec2 p) {
    float v = 0.0;
    float a = 0.5;
    for (int i = 0; i < 5; i++) {
        v += a * vnoise(p);
        p = p * 2.13 + vec2(7.7);
        a *= 0.5;
    }
    return v;
}

// 3-octave variant (cheap proxy: cloud shadows on the ground)
float fbm3(vec2 p) {
    float v = 0.0;
    float a = 0.5;
    for (int i = 0; i < 3; i++) {
        v += a * vnoise(p);
        p = p * 2.13 + vec2(7.7);
        a *= 0.5;
    }
    return v;
}

// ============ BAKED NOISE TEXTURE (NOISE_TEX) ============
// 256^2 RGBA8 home-baked noise (customTexture vlNoiseTex, bilinear + REPEAT):
// R/G = two smoothed periodic value noises (ONE lattice unit = ONE texel, so
// vnoiseTex keeps the EXACT spatial scale of vnoise - critical for 1:1 swaps),
// B = periodic Worley F1 (dark at feature points, ~8-texel cells), A = uniform
// white noise. vnoiseTex samples p/256: one bilinear tap replaces 4 hash12 +
// 3 mixes per octave; the field simply tiles every 256 noise units (call sites
// are decorative fog/cloud/sky fields far below that scale). The pattern is
// ANOTHER noise instance than vnoise: NEVER swap a site that must stay in sync
// with an arithmetic copy (cloudShadow mirrors in lib/lighting.glsl, water
// waves/caustics) - those keep vnoise.
// NOISE_TEX=0 -> arithmetic fallbacks, bit-identical to the original pack.
#if NOISE_TEX == 1
#ifndef VL_NOISE_TEX_DECL
#define VL_NOISE_TEX_DECL
uniform sampler2D vlNoiseTex;
#endif
float vnoiseTex(vec2 p) {
    return texture2D(vlNoiseTex, p / 256.0).r;
}
#else
float vnoiseTex(vec2 p) {
    return vnoise(p);
}
#endif

// fbm/fbm3 twins on the baked texture (same lacunarity, gain and offsets, so
// NOISE_TEX=0 makes them bit-identical to fbm/fbm3).
float fbmTex(vec2 p) {
    float v = 0.0;
    float a = 0.5;
    for (int i = 0; i < 5; i++) {
        v += a * vnoiseTex(p);
        p = p * 2.13 + vec2(7.7);
        a *= 0.5;
    }
    return v;
}

float fbm3Tex(vec2 p) {
    float v = 0.0;
    float a = 0.5;
    for (int i = 0; i < 3; i++) {
        v += a * vnoiseTex(p);
        p = p * 2.13 + vec2(7.7);
        a *= 0.5;
    }
    return v;
}

// ============ CLOUDS - WORLD TIME ============
// Cloud advection follows WORLD time (Iris worldTime/worldDay), not the real
// clock: /time set, sleeping or a changed tick speed move the clouds WITH the
// world. In normal play worldTime advances 20 ticks/s, so cloudWorldTime()
// grows by 1.0/s, matching frameTimeCounter - wind speeds need no rescaling.
// Declared here because common.glsl is included by ALL programs (v120 and
// v430); unused Iris uniforms are harmless in the programs that ignore them.
// mod 128 days caps float precision loss on old worlds (~153600 s max, step
// ~1/64 s); the trade-off is one cloud jump every 128 in-game days.
// v120 has no integer % operator, hence float mod().
uniform int worldTime;
uniform int worldDay;
float cloudWorldTime() {
    return mod(float(worldDay), 128.0) * 1200.0 + float(worldTime) * 0.05;
}

// ============ BLUE-NOISE DITHER (volumetric marches only) ============
// 64^3 tileable blue noise (customTexture vlBlueNoise, NEAREST + REPEAT).
// Screen xy wraps every 64 px through REPEAT, so gl_FragCoord passes unscaled;
// a new z-slice each frame decorrelates successive frames and TAA averages the
// march jitter into smooth gradients (the static 8x8 Bayer left banding).
// v120-safe: float mod(), no bitwise ops. Callers pass the frameCounter
// uniform; the slice freezes without TAA (no accumulation -> would shimmer).
#if BLUE_NOISE == 1
#ifndef VL_BLUE_NOISE_DECL
#define VL_BLUE_NOISE_DECL
uniform sampler3D vlBlueNoise;
#endif
float blueDither(vec2 fragCoord, int frame) {
    #ifdef TAA
    float slice = (mod(float(frame), 64.0) + 0.5) * (1.0 / 64.0);
    #else
    float slice = 0.5 * (1.0 / 64.0); // static slice: no flicker without TAA
    #endif
    return texture3D(vlBlueNoise, vec3(fragCoord * (1.0 / 64.0), slice)).r;
}
#endif
