float gbf(in vec3 eyePlayerPos, in float far)
{

    float eyeDist = max(length(eyePlayerPos.xz), abs(eyePlayerPos.y));

#ifdef DISTANT_HORIZONS
    float borderFogFactor = smoothstep(dhRenderDistance * 0.75, dhRenderDistance - 8.0, eyeDist);
    return clamp(borderFogFactor, 0.0, 1.0);
#else
    float borderFogFactor = smoothstep(far * 0.6, far, eyeDist);
    return borderFogFactor * borderFogFactor;
#endif
}

void applySkyBlend2(inout vec4 toblend, vec3 sky, vec3 p3, float far)
{
#ifdef SKYBLEND

    float blend_factor = gbf(p3, far);

    toblend = mix(toblend, vec4(sky, 0.0), blend_factor * blend_factor);

#endif
}

////////////////////////

// -------- 2-D value-noise octave -------------------------------------------
float n2d(vec2 p)
{
    vec2 i = floor(p);
    vec2 f = p - i;

    f *= f * (3.0 - 2.0 * f);

    // Hash function to generate pseudo-random values at the four corners
    vec4 hashInput = vec4(0.0, 1.0, 113.0, 114.0) + dot(i, vec2(1.0, 113.0));
    vec4 hashed = fract(sin(mod(hashInput, 6.2831853)) * 43758.5453);

    // Matrix of the four corner values
    mat2 values = mat2(hashed.xy, hashed.zw);

    // Bilinear interpolation using the smoothed local position
    return dot(values * vec2(1.0 - f.y, f.y), vec2(1.0 - f.x, f.x));
}
// -------- light-weight hashes ----------------------------------------------
float hash1(vec2 p)
{ // scalar in [0,1)
    return fract(sin(dot(p, vec2(15.32, 5.78))) * 43758.236237153);
}
vec2 hash2(vec2 p)
{
    return vec2(hash1(p * .754), hash1(1.5743 * p.yx + 4.5891)) - .5;
}

// -------- compact 3×3-cell Gabor noise -------------------------------------
float gaborNoise(vec2 p)
{
    const vec2 m = vec2(0.2, 0.5);
    const float f = 2.0 * PI; // frequency
    const float v = 0.8;      // cell variability <1
    const float dv = 0.4;     // direction variability <1

    vec2 ip = floor(p);
    vec2 fp = fract(p);
    vec2 dir = m;

    float va = 0.0;
    float wt = 0.0;

    for (int i = -1; i <= 1; ++i)
    {
        for (int j = -1; j <= 1; ++j)
        {
            vec2 o = vec2(i, j) - 0.5;
            vec2 h = hash2(ip - o);
            vec2 pp = fp + o - h;
            float d = dot(pp, pp);
            float w = exp(-4.0 * d);
            wt += w;
            va += cos(dot(pp, dv * h + dir) * f / v) * w;
        }
    }
    return va / wt;
}

// -------- main ocean-surface fBm + Gabor ------------------------------------
float seaSurf2(vec2 pxz)
{
    pxz *= 256.0;
    vec2 mv = frameTimeCounter * vec2(-0.6, -1.5) * 0.5;

    float n = 0.0;
    n += n2d(pxz * 0.10 + mv) * 0.75;
    n += n2d(pxz * 1.60 + mv) * 0.50;
    n += gaborNoise(pxz * 0.25 + mv * 0.5) * 0.25;

    return n * 0.5;
}
float seaSurf(vec2 pxz)
{
    pxz *= 128.0;
    vec2 mv = frameTimeCounter * vec2(-0.6, -1.5) * 0.5;

    float n = 0.0;
    n += n2d(pxz * 0.10 + mv);        // base swell
    n += n2d(pxz * 0.40 - mv) * 0.5;  // 1st detail
    n += n2d(pxz * 1.60 + mv) * 0.25; // 2nd detail
    n += n2d(pxz * 3.50 - mv) * 0.25; // fine ripples

    return n * 0.5;
}
float getWaterHeightmap(vec2 posxz)
{
    float scale = 16.0;
    posxz *= scale;

    vec2 pos = posxz;
    vec2 movement = vec2(-0.01 * frameTimeCounter * scale, 0.0);
    float caustic = 0.0;
    float weightSum = 0.0;
    const float radiance = 2.39996;
    const mat2 rotationMatrix = mat2(vec2(cos(radiance), -sin(radiance)), vec2(sin(radiance), cos(radiance)));
    for (int i = 0; i < 4; i++)
    {
        float wave = n2d((pos * vec2(3.0, 1.0) + movement) * exp(i * 1.0));
        caustic += wave * exp(-i * 1.0);
        weightSum += exp(-i * 1.0);
        pos = rotationMatrix * pos;
    }
    return caustic / weightSum;
}
vec2 HashVec(vec2 d)
{
    const vec2 k1 = vec2(123.4, 234.5);
    const vec2 k2 = vec2(234.5, 345.6);
    const float MULT = 58322.51;
    const float TIME_MULT = 2.0;

    vec2 s = sin(vec2(dot(d, k1), dot(d, k2)));
    return sin(s * MULT + frameTimeCounter * TIME_MULT);
}

float Octave(vec2 uv, float gridSize)
{
    // Precompute the scaled coordinate to avoid redundant multiplication.
    vec2 scaledUV = uv * gridSize;
    vec2 gridUV = fract(scaledUV);
    vec2 cellId = floor(scaledUV);
    vec2 smoothUV = smoothstep(0.0, 1.0, gridUV);

    // Precompute wrapped cell IDs for the four corners.
    vec2 cell00 = mod(cellId, gridSize);
    vec2 cell10 = mod(cellId + vec2(1.0, 0.0), gridSize);
    vec2 cell01 = mod(cellId + vec2(0.0, 1.0), gridSize);
    vec2 cell11 = mod(cellId + vec2(1.0, 1.0), gridSize);

    // Precompute the UV offsets for interpolation.
    vec2 uv10 = gridUV - vec2(1.0, 0.0);
    vec2 uv01 = gridUV - vec2(0.0, 1.0);
    vec2 uv11 = gridUV - vec2(1.0, 1.0);

    // Compute dot products with hashed vectors.
    float n00 = dot(HashVec(cell00), gridUV);
    float n10 = dot(HashVec(cell10), uv10);
    float n01 = dot(HashVec(cell01), uv01);
    float n11 = dot(HashVec(cell11), uv11);

    // Perform bilinear interpolation.
    float nx0 = mix(n00, n10, smoothUV.x);
    float nx1 = mix(n01, n11, smoothUV.x);
    return mix(nx0, nx1, smoothUV.y);
}

float WaterMapFast(vec2 pos)
{

    float gradient = 0.0;
    float frequency = 8.0;
    float amplitude = 6.0;

    // Use fewer iterations for a faster approximation.
    for (int i = 0; i < 4; i++)
    {
        gradient += (Octave(pos, frequency) + 0.5) / amplitude;
        frequency *= 3.0;
        amplitude *= 1.75;
    }

    return gradient * 6.0;
}

////////////////////////
float interm(vec2 pxz)
{
    return texture(colortex7, pxz).r;

    // return WaterMapFast(pxz);
}
vec3 waterNormal(vec2 rawUv2, vec3 viewDir)
{
    vec2 uv = fract(rawUv2 / 128.0);

    const float MAP_RES = 512.0;
    const float TILE_SIZE_M = 128.0;                  // meters per tile
    const float INV_MAP_RES = 1.0 / MAP_RES;          // texel size in UV
    const float TEXEL_WS = TILE_SIZE_M * INV_MAP_RES; // world‐space meters per texel

    vec2 o = vec2(INV_MAP_RES, INV_MAP_RES);

    float hL = interm(uv - vec2(o.x, 0.0));
    float hR = interm(uv + vec2(o.x, 0.0));
    float hD = interm(uv - vec2(0.0, o.y));
    float hU = interm(uv + vec2(0.0, o.y));

    vec2 d = vec2(hR - hL, hU - hD) / (1.5 * TEXEL_WS);
    d = clamp(d, -1, +1) * 1.0;

    float f0 = max(0.0, 1.0 - abs(dot(viewDir, normal0)));
    float fade = f0 * (1.0 - float(isEyeInWater));

    vec3 v = vec3(-d * (1.0 - fade), 1.0);
    vec3 n = normalize(v);
    vec3 outval = n * 0.5 + 0.5;
    return outval;

    // return mix(outval, vec3(0.5, 0.5, 1.0), pow(f0, 2.0));
}
