

layout(location = 0) out vec4 color16;
layout(location = 1) out vec4 color17;
layout(location = 2) out vec4 color18;

vec2 voxy_OctWrap(vec2 v) {
    return (1.0 - abs(v.yx)) * (step(vec2(0.0), v.xy) * 2.0 - 1.0);
}
vec2 voxy_normalEncode(vec3 n) {
    n /= (abs(n.x) + abs(n.y) + abs(n.z));
    n.xy = n.z >= 0.0 ? n.xy : voxy_OctWrap(n.xy);
    n.xy = n.xy * 0.5 + 0.5;
    return n.xy;
}

float voxy_hashInt(ivec2 p) {
    int n = p.x * 3 + p.y * 113;
    n = (n << 13) ^ n;
    n = n * (n * n * 15731 + 789221) + 1376312589;
    return -1.0 + 2.0 * float(n & 0x0fffffff) / float(0x0fffffff);
}
float voxy_noise(vec2 p) {
    ivec2 i = ivec2(floor(p));
    vec2 f = fract(p);
    vec2 u = f * f * (3.0 - 2.0 * f);
    vec4 h = vec4(
        voxy_hashInt(i + ivec2(0, 0)),
        voxy_hashInt(i + ivec2(0, 1)),
        voxy_hashInt(i + ivec2(1, 0)),
        voxy_hashInt(i + ivec2(1, 1))
    );
    vec2 n = mix(h.xy, h.zw, u.x);
    return mix(n.x, n.y, u.y);
}

const float VOXY_GOLDEN_ANGLE = 2.39996323;
float voxy_getWaterWave2(vec2 pos, int quality) {
    float frequency = 0.15;
    float weight = 1.0;
    float time = 0.75 * frameTimeCounter;
    float sinA = sin(VOXY_GOLDEN_ANGLE);
    float cosA = cos(VOXY_GOLDEN_ANGLE);
    mat2 goldenRot = mat2(cosA, -sinA, sinA, cosA);

    float waterHeight = 0.0;
    float c = 0.0;
    for (int i = 0; i < quality; i++) {
        vec2 p = pos * frequency + time;
        float wave = (1.0 - abs(voxy_noise(p))) * weight;
        waterHeight += wave;
        c += weight;

        pos -= (1.0 - wave) * 0.7;
        pos = goldenRot * pos;

        frequency *= 1.4;
        weight *= 0.7;
        time *= 1.2;
    }
    waterHeight /= c;
    return mix(1.0, waterHeight, 0.3);
}
float voxy_getWaterHeightLod(vec2 pos) {
    return voxy_getWaterWave2(pos, 4);
}
vec3 voxy_getWaterNormal(vec2 uv) {
    float e = 1e-3;
    float hC  = voxy_getWaterHeightLod(uv);
    float hX1 = voxy_getWaterHeightLod(uv + vec2(e, 0.0));
    float hX0 = voxy_getWaterHeightLod(uv - vec2(e, 0.0));
    float hY1 = voxy_getWaterHeightLod(uv + vec2(0.0, e));
    float hY0 = voxy_getWaterHeightLod(uv - vec2(0.0, e));
    return normalize(vec3(-(hX1 - hX0), -(hY1 - hY0), 2.0 * e));
}

vec3 voxy_reconstructWorldPos(vec2 fragCoord, float depth) {
    vec3 ndc = vec3(fragCoord / viewSize, depth) * 2.0 - 1.0;
    vec4 viewPos = vxProjInv * vec4(ndc, 1.0);
    viewPos /= viewPos.w;
    vec4 worldPos = vxModelViewInv * viewPos;
    return worldPos.xyz + cameraPosition;
}

void voxy_emitFragment(VoxyFragmentParameters parameters) {
    vec3 faceNormal = vec3(
        uint((parameters.face>>1)==2),
        uint((parameters.face>>1)==0),
        uint((parameters.face>>1)==1)
    ) * (float(int(parameters.face)&1)*2-1);

    bool isWater = abs(parameters.customId - 8.0) < 0.5 || abs(parameters.customId - 9.0) < 0.5;

    vec3 col = parameters.sampledColour.rgb * parameters.tinting.rgb;
    float coverage = parameters.sampledColour.a * parameters.tinting.a;
    vec3 normal = faceNormal;

    if (isWater) {
        vec3 worldPos = voxy_reconstructWorldPos(gl_FragCoord.xy, gl_FragCoord.z);
        vec3 waveNormal = voxy_getWaterNormal(worldPos.xz);
        normal = normalize(mix(faceNormal, waveNormal, 0.6));
    }

    color16 = vec4(col, coverage);
    color17 = vec4(voxy_normalEncode(normal), 0.0, 0.0);
    color18 = vec4(clamp(parameters.lightMap, 0.5/16.0, 15.5/16.0), 0.0, 0.0);
}