#define WATER_WAVE_SCALE 1.0 // [0.05 0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.85 0.9 0.95 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.2 2.4 2.6 2.8 3.0 3.2 3.4 3.6 3.8 4.0 4.2 4.4 4.6 4.8 5.0 5.5 6.0 6.5 7.0 7.5 8.0 9.5 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0]
#define WATER_WAVE_HEIGHT 0.4 // [0.0 0.05 0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.85 0.9 0.95 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.2 2.4 2.6 2.8 3.0 3.2 3.4 3.6 3.8 4.0 4.2 4.4 4.6 4.8 5.0 5.5 6.0 6.5 7.0 7.5 8.0 9.5 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0]
#define WATER_WAVE_SPIKE 1.5 // [0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.5 1.6 1.8 2.0 2.2 2.4 2.6 2.8 3.0 3.5 4.0 4.5 5.0]

const mat2 rotation1 = mat2(cos(-0.4), sin(-0.4), -sin(-0.4), cos(-0.4));
const vec2 scale1 = vec2(0.6, 0.9) * 64.0;

const mat2 rotation2 = mat2(cos(0.35), sin(0.35), -sin(0.35), cos(0.35));
const vec2 scale2 = vec2(1.0, 0.5) * 64.0;

const vec2 scale3 = vec2(1.8, 1.2) * 64.0;

const mat2 rotation4 = mat2(cos(1.2), sin(1.2), -sin(1.2), cos(1.2));
const vec2 scale4 = vec2(3.0, 2.5) * 64.0;

const mat2 rotation5 = mat2(cos(-0.8), sin(-0.8), -sin(-0.8), cos(-0.8));
const vec2 scale5 = vec2(5.0, 4.0) * 64.0;

const mat2 rotation6 = mat2(cos(0.6), sin(0.6), -sin(0.6), cos(0.6));
const vec2 scale6 = vec2(8.0, 7.0) * 64.0;

const mat2 rotation7 = mat2(cos(-1.5), sin(-1.5), -sin(-1.5), cos(-1.5));
const vec2 scale7 = vec2(12.0, 10.0) * 64.0;

float spikyNoise(vec2 coord) {
    float n = smooth2DNoise(coord);
    float s = n * 2.0 - 1.0;
    s = sign(s) * pow(abs(s), 1.0 / (1.0 + WATER_WAVE_SPIKE));
    return s * 0.5 + 0.5;
}

float waterWaveHeight(vec4 coord12, vec4 coord34, vec4 coord56, vec3 coord7z) {
    float totalHeight = 0.0;
    totalHeight += spikyNoise(coord12.xy);
    totalHeight += spikyNoise(coord12.zw) * 0.5;
    totalHeight += spikyNoise(coord34.xy) * 0.25;
    totalHeight += spikyNoise(coord34.zw) * 0.125;
    totalHeight += spikyNoise(coord56.xy) * 0.0625;
    totalHeight += spikyNoise(coord56.zw) * 0.03125;
    totalHeight += spikyNoise(coord7z.xy) * 0.015625;
    totalHeight /= 1.0 + 0.5 + 0.25 + 0.125 + 0.0625 + 0.03125 + 0.015625;
    return totalHeight;
}

vec2 sampleWaterNormal(vec2 coord, vec2 scale) {
    vec2 fpc = floor(coord);
    vec4 sh = textureGather(noisetex, fpc / 64.0, 0);

    fpc = fract(coord);
    vec2 weight = fpc * fpc * (3.0 - 2.0 * fpc);

    sh.y = sh.w + sh.y - sh.x - sh.z;
    vec2 normal = (sh.yy * weight.yx + (sh.zx - sh.ww)) * fpc * (6.0 * fpc - 6.0) * scale;
    return normal;
}

vec2 waterWaveNormal(vec4 coord12, vec4 coord34, vec4 coord56, vec3 coord7z) {
    vec2 totalNormal = vec2(0.0);
    totalNormal += sampleWaterNormal(coord12.xy, rotation1 * scale1) * spikyNoise(coord12.xy + 0.5);
    totalNormal += sampleWaterNormal(coord12.zw, rotation2 * scale2) * spikyNoise(coord12.zw + 0.5) * 0.5;
    totalNormal += sampleWaterNormal(coord34.xy, scale3) * spikyNoise(coord34.xy + 0.5) * 0.25;
    totalNormal += sampleWaterNormal(coord34.zw, rotation4 * scale4) * spikyNoise(coord34.zw + 0.5) * 0.125;
    totalNormal += sampleWaterNormal(coord56.xy, rotation5 * scale5) * spikyNoise(coord56.xy + 0.5) * 0.0625;
    totalNormal += sampleWaterNormal(coord56.zw, rotation6 * scale6) * spikyNoise(coord56.zw + 0.5) * 0.03125;
    totalNormal += sampleWaterNormal(coord7z.xy, rotation7 * scale7) * spikyNoise(coord7z.xy + 0.5) * 0.015625;
    totalNormal /= 1.0 + 0.5 + 0.25 + 0.125 + 0.0625 + 0.03125 + 0.015625;
    return totalNormal;
}

vec3 waterWave(vec3 position, vec3 tangentDir) {
    position /= (WATER_WAVE_SCALE);
    vec2 coord = position.xz + vec2(position.y);

    const vec3 stepScale = vec3(vec2(0.2 * WATER_WAVE_HEIGHT / (WATER_WAVE_SCALE * 32.0)), 1.0);
    float tangentDirZ = abs(tangentDir.z * inversesqrt(dot(tangentDir, tangentDir)));
    vec3 stepSize = tangentDir * stepScale;
    stepSize *= 0.2 / abs(stepSize.z);
    tangentDirZ /= 0.2;

    vec2 sampleCoord1 = rotation1 * (coord + vec2(0.02, -0.02) * frameTimeCounter * WATER_WAVE_SPEED) * scale1;
    vec2 sampleCoord2 = rotation2 * (coord - 0.6 * vec2(-0.02, 0.03) * frameTimeCounter * WATER_WAVE_SPEED) * scale2;
    vec2 sampleCoord3 = (coord + 0.4 * vec2(0.025, 0.01) * frameTimeCounter * WATER_WAVE_SPEED) * scale3;
    vec2 sampleCoord4 = rotation4 * (coord + 0.3 * vec2(-0.015, 0.02) * frameTimeCounter * WATER_WAVE_SPEED) * scale4;
    vec2 sampleCoord5 = rotation5 * (coord - 0.5 * vec2(0.01, -0.025) * frameTimeCounter * WATER_WAVE_SPEED) * scale5;
    vec2 sampleCoord6 = rotation6 * (coord + 0.7 * vec2(0.018, 0.012) * frameTimeCounter * WATER_WAVE_SPEED) * scale6;
    vec2 sampleCoord7 = rotation7 * (coord - 0.4 * vec2(-0.012, -0.018) * frameTimeCounter * WATER_WAVE_SPEED) * scale7;
    float samplePosZ = 1.0;

    vec4 sampleCoord12 = vec4(sampleCoord1, sampleCoord2);
    vec4 sampleCoord34 = vec4(sampleCoord3, sampleCoord4);
    vec4 sampleCoord56 = vec4(sampleCoord5, sampleCoord6);
    vec3 sampleCoord7z = vec3(sampleCoord7, samplePosZ);

    vec2 stepSize1 = rotation1 * stepSize.xy * scale1;
    vec2 stepSize2 = rotation2 * stepSize.xy * scale2;
    vec2 stepSize3 = stepSize.xy * scale3;
    vec2 stepSize4 = rotation4 * stepSize.xy * scale4;
    vec2 stepSize5 = rotation5 * stepSize.xy * scale5;
    vec2 stepSize6 = rotation6 * stepSize.xy * scale6;
    vec2 stepSize7 = rotation7 * stepSize.xy * scale7;
    float stepSizeZ = stepSize.z;

    vec4 stepSize12 = vec4(stepSize1, stepSize2);
    vec4 stepSize34 = vec4(stepSize3, stepSize4);
    vec4 stepSize56 = vec4(stepSize5, stepSize6);
    vec3 stepSize7z = vec3(stepSize7, stepSizeZ);

    for (int i = 0; i < 5; i++) {
        sampleCoord12 += stepSize12;
        sampleCoord34 += stepSize34;
        sampleCoord56 += stepSize56;
        sampleCoord7z += stepSize7z;
        float sampleHeight = waterWaveHeight(sampleCoord12, sampleCoord34, sampleCoord56, sampleCoord7z);
        if (sampleCoord7z.z - sampleHeight < 0.0) {
            break;
        }
    }
    sampleCoord12 -= stepSize12;
    sampleCoord34 -= stepSize34;
    sampleCoord56 -= stepSize56;
    sampleCoord7z -= stepSize7z;
    for (int i = 0; i < 5; i++) {
        float sampleHeight = waterWaveHeight(sampleCoord12, sampleCoord34, sampleCoord56, sampleCoord7z);
        float nextLength = sampleCoord7z.z - sampleHeight;
        float nextScale = nextLength * tangentDirZ;
        sampleCoord12 += stepSize12 * nextScale;
        sampleCoord34 += stepSize34 * nextScale;
        sampleCoord56 += stepSize56 * nextScale;
        sampleCoord7z += stepSize7z * nextScale;
    }
    vec2 sampleNormal = waterWaveNormal(sampleCoord12, sampleCoord34, sampleCoord56, sampleCoord7z);

    return normalize(vec3(sampleNormal * stepScale.xy, 1.0));
}
