uniform float u_progress; //{readonly: true, update: true, info: "The animation progress over time", decimals: 3}
uniform float u_flowDist; //{expose: true, update: true}

uniform vec2 u_globalFlowDir; //{expose: true}

uniform vec4 u_shallowColor; //{type: "COLOR", default: [0.5, 0.7, 0.8, 0.2], update: true}
uniform vec4 u_deepColor; //{type: "COLOR", default: [0.3, 0.5, 0.9, 0.6], update: true}
uniform vec3 u_highlightColor; //{type: "COLOR", default: [0.0, 0.0, 0.0], update: true}
uniform vec4 u_foamColor; //{type: "COLOR", update: true}
uniform float u_depthScale;
uniform float u_depthScaleAlpha;
uniform float u_distortion; //{type: "SLIDER", min: 0, max: 10, step: 0.1, default: 1.0}

uniform float u_foamFeathering; //{type: "SLIDER", min: 0, max: 5.0, default: 3.0, step: 1}

uniform float u_foamBorder; //{type: "SLIDER", min: 0, max: 10.0, default: 2.0, step: 1}
uniform float u_noiseScale; //{type: "SLIDER", min: 0.01, max: 50.0, default: 3.5}
uniform float u_foamScale; //{type: "SLIDER", min: 0.01, max: 50.0, default: 1.0, step: 0.05}
uniform float u_foamSpeed;//{type: "SLIDER", min: 0, max: 10, default: 1, step: 0.05}
uniform float u_foamWaveInfluence; //{type: "SLIDER", min: 0, max: 1, default: 1, step: 0.05}
uniform float u_wobbleStrength; //{type: "SLIDER", min: 0, max: 10, default: 8}

uniform float u_waveHeight; //{type: "SLIDER", min: 0, max: 16, default: 1}
uniform float u_waveScale; //{type: "SLIDER", min: 0, max: 10, default: 8.7}
uniform float u_waveShift; //{type: "SLIDER", min: 0, max: 2, step: 0.01, default: 0.5}
uniform float u_waveSpeed; //{type: "SLIDER", min: 0, max: 20, step: 0.1, default: 1}

uniform float u_streaksIntensity; //{type: "SLIDER", min: 0, max: 1, default: 0.08, step: 0.01}
uniform float u_sparkleIntensity; //{type: "SLIDER", min: 0, max: 1, default: 1, step: 0.01}

uniform float u_normalMix;//{type: "SLIDER", min: 0, max: 1, default: 0}

uniform bool u_debugView; //{expose: true}

//Waterfall
uniform float u_waterfallProgress; //{readonly: true, update: true, info: "The waterfall animation progress over time", decimals: 3}
uniform float u_waterfallHeight;
uniform float u_streakIntensity; //{type: "SLIDER", min: 0, max: 1, default: 0.08, step: 0.01}
uniform float u_streakStart;
uniform float u_streakRampUp;
uniform float u_stripeIntensity;
uniform float u_waterfallSpeed;
uniform float u_curveHighlightY;
uniform float u_curveHighlightHeight;

//#group Dithering
uniform vec3 u_ditherCenter; //{expose: true}
uniform float u_ditherScale; //{min: 1, max: 4, step: 1}
uniform vec2 u_ditherRadii; //{type: "SLIDER"}
//#endgroup
uniform vec2 u_screenSize;

const vec3 grid = vec3(24.0, 16.0, 16.0);
//const vec3 grid = vec3(12.0, 8.0, 8.0);
//const vec3 grid = vec3(2.0, 2.0, 2.0);
const vec3 gridStep = 1.0 / grid;

/*
FX_WALL_DIRECTION {
    NORTH_NE,
    NORTH,
    NORTH_NW,

    EAST_NE,
    EAST,
    EAST_SE,

    SOUTH_SE,
    SOUTH,
    SOUTH_SW,

    WEST_NW,
    WEST,
    WEST_SW,
}
*/

const vec2[12] DIRECTIONS = vec2[](
//NORTH: NE N NW
vec2(0.0, -1.0),
vec2(0.0, -1.0),
vec2(0.0, -1.0),
//EAST: NE E SE
vec2(1.0, 0.0),
vec2(1.0, 0.0),
vec2(1.0, 0.0),
//SOUTH: SE, S, SW
vec2(0.0, 1.0),
vec2(0.0, 1.0),
vec2(0.0, 1.0),
//WEST: NW, W, SW
vec2(-1.0, 0.0),
vec2(-1.0, 0.0),
vec2(-1.0, 0.0)
);

const vec2[12] DIRECTIONS_DIAG = vec2[](
//NORTH: NE N NW
vec2(0.0, -1.0),
vec2(0.0, 0.0),
vec2(0.0, 1.0),

//EAST: NE E SE
vec2(-1.0, 0.0),
vec2(0.0, 0.0),
vec2(1.0, 0.0),

//SOUTH: SE, S, SW
vec2(0.0, 1.0),
vec2(0.0, 0.0),
vec2(0.0, -1.0),

//WEST: NW, W, SW
vec2(1.0, 0.0),
vec2(0.0, 0.0),
vec2(-1.0, 0.0)
);

vec3 snap(vec3 p) {
    return floor(p * grid) * gridStep;
}

vec2 snap2(vec2 p) {
    return floor(p * grid.xy) * gridStep.xy;
}

float snapf(float v, float grid) {
    return floor(v * grid) / grid;
}

float quantize(float intensity, float steps) {
    return (floor(intensity * steps) + smoothstep(0.9, 1.0, fract(intensity * steps))) / steps;
}

vec2 bilinear(vec2[4] v, float t0, float t1) {
    vec2 v0 = mix(v[0], v[1], t0);
    vec2 v1 = mix(v[2], v[3], t0);
    return mix(v0, v1, t1);
}

vec3 gerstnerWave(vec3 p, vec2 direction, float waveLength, float steepness, float amplitude, float phase, inout vec3 tangent, inout vec3 binormal, float progress) {

    float lambda = 2.0 * PI / waveLength;
    float speed = sqrt(50.0 / lambda);
    float wavePhase = lambda * dot(direction, p.xy) - progress * speed + phase;
    float c = cos(wavePhase);
    float s = sin(wavePhase);

    float QA = steepness * amplitude;
    float QAC = QA * c;
    float QAS = QA * s;

    tangent += vec3(
    -direction.x * direction.x * QAS,
    -direction.x * direction.y * QAS,
    direction.x * QAC
    );

    binormal += vec3(
    -direction.x * direction.y * QAS,
    -direction.y * direction.y * QAS,
    direction.y * QAC
    );

    return vec3(direction.x * QAC, direction.y * QAC, amplitude * s * 0.5);
}

void computeProgress(in float progress, out float progress1, out float progress2, out float progress3, out float progress4,
                     out float phase1, out float phase2) {
    progress1 = fract(progress);
    progress2 = fract(progress + 0.5);
    progress3 = floor(progress);
    progress4 = floor(progress + 0.5);

    //phase1 = upDownRamp(progress1);
    //phase2 = upDownRamp(progress2);

    phase1 = 1.0 - (cos(progress1 * PI * 2.0) * 0.5 + 0.5);
    phase2 = 1.0 - phase1;
}

vec4[4] computeWaveFactors(out vec2 globalFlow, vec2 flowDir) {
    globalFlow = u_globalFlowDir;
    //lobalFlow += flowDir;
    if(dot(globalFlow, globalFlow) > 0.0)
        globalFlow = normalize(globalFlow);
    return vec4[](
        vec4(globalFlow, 1.0, 0.5),
        vec4(rotateV2(globalFlow, 0.5), 0.5, 0.25),
        vec4(rotateV2(globalFlow, -0.5), 0.25, 0.125),
        vec4(rotateV2(globalFlow, 2.0), 0.125, 0.125)
    );
}

vec3 computeGerstnerOffset(vec3 pos, vec4[4] waves, out vec3 tangent, out vec3 binormal, float zScale, float wShift, float speed, float[4] amps, float[4] phases) {
    vec3 offset = vec3(0.0);
    tangent = vec3(1.0, 0.0, 0.0);
    binormal = vec3(0.0, 0.0, 1.0);
    float prog = u_progress * speed / u_waveScale * 1.5;
    offset += gerstnerWave(pos, waves[0].xy, waves[0].z * zScale, waves[0].w, amps[0], phases[0], tangent, binormal, prog);
    offset += gerstnerWave(pos, waves[1].xy, waves[1].z * zScale, waves[1].w, amps[1], phases[1], tangent, binormal, prog);
    offset += gerstnerWave(pos, waves[2].xy, waves[2].z * zScale, waves[2].w, amps[2], phases[2], tangent, binormal, prog);
    offset += gerstnerWave(pos, waves[3].xy, waves[3].z * zScale, waves[3].w, amps[3], phases[3], tangent, binormal, prog);
    return offset;
}

void computeScreenCoords(vec4 pos, vec4 origPos,
                      out vec2 screenCoord, out vec2 origScreenCoord, out vec2 pureScreenCoord) {
    vec4 origScreenPos = u_cameraProjM * origPos;
    vec4 screenPos = u_cameraProjM * pos;

    origScreenPos.xyz = origScreenPos.xyz / origScreenPos.w * 0.5 + 0.5;
    screenPos.xyz = screenPos.xyz / screenPos.w * 0.5 + 0.5;

    vec3 pureScreenPos = v_glPos.xyz / v_glPos.w * 0.5 + 0.5;

    screenCoord = screenPos.xy * u_textureRatio;
    origScreenCoord = origScreenPos.xy * u_textureRatio;
    pureScreenCoord = pureScreenPos.xy * u_textureRatio;
}

float gradFunc(float x) {
    return 1.0 - exp(-x);
}

float deriveFunc(float x) {
    return exp(-x);
}

float lerpSlope(float x, float m, float strength) {
    float c = log(strength / m);

    float f0 = gradFunc(x);
    float f1 = deriveFunc(c) * (x - c) + gradFunc(c);
    return x > c ? f1 : f0;
}

#ifdef WATERFALL_SHADER
float makeStripes(vec3 rootPos, vec2 uv, vec2 direction, float fallSpeed, float curvature) {

    float circumference = v_radius * PI * 0.25;
    vec2 p = uv;

    vec2 dirFactor = abs(direction);
    vec2 dirInv = 1.0 - dirFactor;

    vec2 fallSpeedV = dirInv + dirFactor / fallSpeed;

    vec2 scaledP = p * fallSpeedV * u_noisePixel * u_noiseScale * u_noiseScale;
    scaledP += rootPos.xy * dirInv * u_noisePixel * u_noiseScale * u_noiseScale;
    p += rootPos.xy;

    float scaledCurvature = (curvature - circumference - u_streakStart * u_waterfallHeight) / (u_waterfallHeight * (1.0 - u_streakStart ));


    vec4 noise = texture(u_noise,  scaledP * vec2(1.0, 1.0) - direction * u_waterfallProgress);
    vec4 noise2 = texture(u_noise, vec2(0.59056, 0.767845) + scaledP * (dirFactor * 0.255423 + dirInv * 3.1252) - direction * u_waterfallProgress);

    vec4 noiseCombo = clamp(noise * (noise2 + 0.1), 0.0, 1.0);

    float stripeX = abs(cos(dot(p.xy, dirInv) * 8.0 + noise.x * 2.0)) + cos(dot(p.xy, dirInv) * 4.0 + noise.z * 2.0);
    float stripeY = uv.y / fallSpeed * 20.0 + noise.y * clamp(curvature * 0.3, 0.0, 0.5) ;

    float stripes = cos( stripeX - stripeY + u_waterfallProgress * 80.0) + noise.z * 0.1;


    stripes = clamp(stripes - 0.7, 0.0, 1.0) / 0.7;
    stripes *= clamp(curvature - 1.0, 0.0, 1.0);
    stripes = smoothstep(0.4, 0.5, stripes);
    stripes += curvature * noise.x * 0.05;
    stripes *= clamp(curvature * 0.1 * noise.w, 0.0, 1.0);
    //stripes = smoothstep(0.1 + curvature * 0.02, 0.1 + scaledCurvature, stripes);

    vec2 transformedP = vec2(dot(p, dirInv), dot(p, dirFactor));

    float streakStart = u_streakStart;
    float streakRampUp = u_streakRampUp;

    float streakStripes = smoothstep(-1.0, 1.0, cos( transformedP.x * 40.0 + noiseCombo.y * 10.0) + scaledCurvature * 0.5 - noiseCombo.x);
    streakStripes *= smoothstep(-1.0, 1.0, cos((scaledP.y - u_waterfallProgress + 10.0) * 10.0 + 200.0* cos(transformedP.x + u_waterfallProgress * 0.1)) );
    streakStripes = clamp(streakStripes * noise.z + pow(scaledCurvature, 0.25) * (noiseCombo.w + streakRampUp * 0.5), 0.0, 1.0);
    //streakStripes *= clamp( 3.0 - noise2.y , 0.0, 1.0);
    float streaks = smoothstep(0.7, 1.0, streakStripes) * clamp( (curvature - circumference  - 1.0)/ u_waterfallHeight , 0.0, 1.0);

    float curveHighlightOffset = 1.0 + v_radius + u_curveHighlightY;

    float curveValue = curvature * (1.0 + noise2.z * u_curveHighlightHeight) * (1.0 + smoothstep(-1.0, 1.0, cos(transformedP.x * 20.0 + noise2.y * 10.0) * (1.0 - noiseCombo.w) * u_curveHighlightHeight));
    float curveHighlight = bandPassSmooth(curveValue, curveHighlightOffset - noiseCombo.x * 2.0 * u_curveHighlightHeight, curveHighlightOffset + noiseCombo.y * 2.0 * u_curveHighlightHeight, 0.2) * clamp(noiseCombo.x + 0.4, 0.0, 1.0);
    curveHighlight = max(0.0, curveHighlight);

    return clamp(curveHighlight + u_stripeIntensity * stripes + u_streakIntensity * streaks, 0.0, 1.0);
}
#endif

void waterMain() {
    vec3 localPos = v_localPos;
    vec3 localPosSnapped = snap(localPos);
    vec2 flowDir = vec2(0.0);
#ifdef WATERFALL_SHADER

    vec2 direction = DIRECTIONS[v_direction];
    vec2 directionDiag = DIRECTIONS_DIAG[v_direction];
    vec2 directionInv = 1.0 - direction;


    //snapped waterfall position
    vec2 snappedWFPos = localPos.xy - direction * localPos.z;
    snappedWFPos = snap2(snappedWFPos);
    snappedWFPos += snappedWFPos.yx * directionDiag;



    float curvature2 = snapf(dot(snappedWFPos, direction), dot(direction, grid.xy));
    curvature2 = max(0.0, curvature2);
    float curvePercentUnclamped = (curvature2) / (2.0 * v_radius);
    float curvePercent = clamp(curvePercentUnclamped, 0.0, 1.0);

    vec4 origPos = (vec4(localPos + v_rootPos, 1.0));
    vec4 pos = (vec4(localPos + v_rootPos, 1.0));
    flowDir = direction;
#else
    vec4 origPos = (u_transform * vec4(localPos, 1.0));
    vec4 pos = (u_transform * vec4(localPos, 1.0));
    vec2 tilePos = snap2(fract(v_localPos.xy));
    flowDir = bilinear(v_flowDirs, tilePos.x, tilePos.y);
    //flowDir = v_flowDir;
    //if(dot(flowDir, flowDir) > 0.0)
    //    flowDir = normalize(flowDir);
#endif
    pos.xyz = snap(pos.xyz);

    float progressShift = texture(u_noise, pos.xy * 0.01).r;

    float progress = u_progress * 0.1;

    progress += progressShift * 0.1;

    float progress1, progress2, progress3, progress4, phase1, phase2;
    computeProgress(progress, progress1, progress2, progress3, progress4, phase1, phase2);

    vec2 globalFlow;
    vec4[4] waves = computeWaveFactors(globalFlow, flowDir);
    globalFlow += flowDir;

    vec3 tangent, binormal;
    vec3 offset = computeGerstnerOffset(pos.xyz, waves, tangent, binormal, u_waveScale, u_waveShift, 1.0, float[](1.0, 0.3, 0.1, 0.0), float[](0.6, 1.1, 2.3, 3.7));
    vec3 finalTangent = tangent;
    vec3 finalBinormal = binormal;

    vec3 offsetFoam = computeGerstnerOffset(origPos.xyz, waves, tangent, binormal, u_foamScale, u_waveShift, u_foamSpeed / u_foamScale * 10.0, float[](0.5, 0.25, 0.125, 0.125), float[](0.0, 1.1, 2.5, 3.9));
    vec3 offsetOrigFoam = offsetFoam;
    offsetFoam *= u_wobbleStrength;
    offsetFoam.z -= u_wobbleStrength*0.5 - gridStep.z;

    offsetOrigFoam *= u_wobbleStrength;
    offsetOrigFoam.z -= u_wobbleStrength*0.5 - gridStep.z;

    finalTangent.y *= u_waveHeight;
    finalBinormal.y *= u_waveHeight;

    vec3 waveNormal = normalize(cross(finalTangent, finalBinormal)).xzy;

    offset.y *= u_waveHeight * gridStep.y;
    offset.z *= u_waveHeight;
    offset.z -= u_waveHeight * 0.5;
    offset.y -= u_waveHeight * gridStep.y;
    offset.z -= gridStep.z;

#ifdef WATERFALL_SHADER
    vec4 posShifted =(vec4(localPosSnapped + v_rootPos + offset * (1.0 - curvePercent), 1.0));
#else
    vec4 posShifted =(u_transform * vec4(localPosSnapped + offset, 1.0));
#endif

    vec2 screenCoord, origScreenCoord, pureScreenCoord;
    computeScreenCoords(pos, origPos, screenCoord, origScreenCoord, pureScreenCoord);

    int flags = int(texture(u_data, origScreenCoord).g * 255.0);
    if(getFlag(flags, FLAG_DARKEST_COLOR)) discard;

    vec4 ditherPS = u_cameraProjM * vec4(u_ditherCenter, 1.0);
    ditherPS.xy /= ditherPS.w;
    ditherPS.xy = (ditherPS.xy * 0.5 + vec2(0.5)) * u_screenSize;
    float distFactor = abs(ditherPS.w) * 1280.0 / (3200.0 * u_screenSize.x);
    vec2 ditherDeltaVec =  ditherPS.xy - gl_FragCoord.xy;
    ditherDeltaVec.y *= 1.3;
    float ditherDelta = length(ditherDeltaVec) * distFactor;
    float outerRadius = u_ditherRadii.y;
    float innerRadius = u_ditherRadii.x;
    float ditherDist = 1.0 - clamp((ditherDelta - innerRadius) / outerRadius, 0.0, 1.0);
    ditherDist = 1.0 - ditherDist;
    //ditherDist = 0.0;
    if( ditherDist < 1.0){
        if(dither(ditherDist, gl_FragCoord.xy, 0u) < -1.0/17.0)
            discard;
    }

    float origWorldDepth = texture(u_depth, origScreenCoord).r;

    vec3 origWorldPos = sampleDepthToWorld(u_depth, u_depthRatio, origScreenCoord / u_depthRatio, u_cameraProjInvM);
    origWorldPos.z += sin((snapf(origWorldPos.x, 24.0) * 2.0) * PI_DOUBLE) * 0.02 + sin((snapf(origWorldPos.y, 16.0) * 3.0) * PI_DOUBLE) * 0.02;
    vec3 worldPos = sampleDepthToWorld(u_depth, u_depthRatio, screenCoord / u_depthRatio, u_cameraProjInvM);

    vec4 origWorldScreenPos = u_cameraProjM * vec4(origWorldPos, 1.0);
    origWorldScreenPos.xyz = origWorldScreenPos.xyz / origWorldScreenPos.w * 0.5 + 0.5;

    vec2 noiseShift = texelFetch(u_noise2, ivec2(mod(progress3, u_noise2Size.x), 0), 0).rg;
    vec2 noiseShift2 = texelFetch(u_noise2, ivec2(mod(progress4, u_noise2Size.x), 32), 0).rg;

    vec2 wobbleNoiseCoord1 = posShifted.xy * u_foamScale * u_foamScale - globalFlow * progress1 * u_flowDist * 0.1 * u_foamSpeed;
    vec2 wobbleNoiseCoord2 = posShifted.xy * u_foamScale * u_foamScale - globalFlow * progress2 * u_flowDist * 0.1 * u_foamSpeed;
    vec4 wobbleNoise1 = texture(u_noise, wobbleNoiseCoord1);
    vec4 wobbleNoise2 = texture(u_noise, wobbleNoiseCoord2);

    vec4 wobbleNoise = phase1 * wobbleNoise1 + phase2 * wobbleNoise2;

    vec3 wobbleVec = vec3(0.0, -offsetFoam.z - offset.z * u_foamWaveInfluence, -offsetFoam.z -offset.z * u_foamWaveInfluence);
    vec3 wobbleVecBorder = vec3(0.0, -offsetOrigFoam.z - offset.z * u_foamWaveInfluence, -offsetOrigFoam.z -offset.z * u_foamWaveInfluence);
#ifdef WATERFALL_SHADER
        wobbleVecBorder *= vec3( max(0.0, 1.0 - curvature2));
#endif
    vec3 posDelta = origPos.xyz - worldPos - wobbleVec;
    vec3 posDeltaNoWobble = pos.xyz - worldPos;
    vec3 posOrigDelta = origPos.xyz - origWorldPos - wobbleVec;
    vec3 posOrigDeltaBorder = origPos.xyz - origWorldPos - wobbleVecBorder;
    posOrigDeltaBorder.z -= (sin((snapf(origWorldPos.x, 24.0) * 2.0) * PI_DOUBLE) * 0.02 + sin((snapf(origWorldPos.y, 16.0) * 3.0) * PI_DOUBLE) * 0.02);

    float posDeltaLength = length(posDelta);

    float posOrigDeltaLength = length(posOrigDelta);
    float deltaFix = posOrigDeltaLength - posDeltaLength;

    vec3 posDeltaPixel = (posDelta - gridStep) * grid;
    vec3 posOrigDeltaPixel = (posOrigDelta - gridStep) * grid;
    vec3 posOrigDeltaPixelBorder = (posOrigDeltaBorder - gridStep) * grid;

    if(posOrigDeltaPixelBorder.y + posOrigDeltaPixelBorder.z - 1.0 <= 0.0 && posOrigDeltaPixelBorder.z <= 0.0) discard;

    float borderTop = 0.0;
    float borderBottom = 1.0;

    if(posDelta.z - gridStep.z <= 0.0 && posOrigDelta.z > (4.0 * gridStep.z)) {
        borderTop = 1.0;
        borderBottom = 0.0;
    }else {
        borderTop = smoothstep(0.0, u_foamBorder * 0.25, posOrigDeltaPixelBorder.z);
        borderBottom = u_foamFeathering > 0.0 ? smoothstep(u_foamBorder * 0.25 + u_foamFeathering, u_foamBorder * 0.25, snapf(posOrigDeltaPixel.z, 2.0)) : 0.0;
    }

    if(borderTop <= 0.0) discard;
    float minFlowDist = max(0.0, u_flowDist);
#ifdef WATERFALL_SHADER
    vec2 noiseCoord = ( snap2(origPos.xy) + offset.xz * 0.0) * u_noiseScale * u_noiseScale + flowDir * u_flowDist * 10.0 * 0.5;
    //oiseCoord.x = snapf(noiseCoord.x, 24.0);
    //noiseCoord.y = snapf(noiseCoord.y, 24.0);

    //noiseCoord *= u_noiseScale * u_noiseScale;
#else
    vec2 noiseCoord = ( snap2(origPos.xy) + offset.xz * 0.0) * u_noiseScale * u_noiseScale + flowDir * u_flowDist * 10.0 * 0.5;
#endif

    vec2 offset1 = floor(noiseCoord - globalFlow * progress1 * u_flowDist * 10.0);
    vec2 offset2 = floor(noiseCoord - globalFlow * progress2 * u_flowDist * 10.0);

    vec4 noise1 = texture(u_noise, offset1 * u_noisePixel + noiseShift);
    vec4 noise2 = texture(u_noise, offset2 * u_noisePixel + noiseShift2);

    vec4 combined1 = noise1 * phase1 + noise2 * phase2;
    vec2 fullWave = combined1.xy;

#ifdef WATERFALL_SHADER
    vec4 noise3 = texture(u_noise, (noiseCoord - floor(globalFlow * u_waterfallProgress * minFlowDist * 10.0)) * u_noisePixel);
    vec4 noise4 = texture(u_noise, (noiseCoord + floor(globalFlow * u_waterfallProgress * minFlowDist * 10.0)) * u_noisePixel);
    vec4 combined2 = (noise3 + noise4) * 0.5;
    fullWave = mix(combined1.xy, combined2.xy, curvePercent);
    fullWave = combined1.xy;
#endif
    float depthPixel = posOrigDelta.z * grid.z;
    waveNormal.z *= -1.0;
    vec3 waterNormal = normalize(mix(vec3((fullWave - 0.5) * 2.0, 0.1), waveNormal, u_normalMix));

    vec2 distortedCoord = round((waterNormal.xy + waveNormal.xy) * 0.5 * 0.1 * depthPixel * u_distortion);
#ifdef WATERFALL_SHADER
    distortedCoord *= (1.0 + 2.0 * min(1.0, curvePercent));
#endif
    distortedCoord = pureScreenCoord / u_textureRatio + distortedCoord * u_depthPixel;

    vec3 refractedPos = sampleDepthToWorld(u_depth, u_depthRatio, distortedCoord, u_cameraProjInvM);

    distortedCoord = distortedCoord * u_textureRatio;

    vec2 sampleCoord = pureScreenCoord;

    bool refracting = refractedPos.z <= posShifted.z;

    if(refracting) {
        sampleCoord = distortedCoord;
        depthPixel = (posShifted.z - refractedPos.z) * grid.z;
    }

    float depthPixelScaled = depthPixel / u_depthScale;
    float depthPixelScaledAlpha = depthPixel / u_depthScaleAlpha;

    vec4 albedo = texture(u_texture, sampleCoord);
    vec3 dataShifted = texture(u_data, sampleCoord).xyz;

    vec4 waterColor = vec4(0.0);
    waterColor.rgb = mix(u_shallowColor.rgb, u_deepColor.rgb, clamp(depthPixelScaled, 0.0, 1.0));
    waterColor.a = mix(u_shallowColor.a, u_deepColor.a, clamp(depthPixelScaledAlpha, 0.0, 1.0));

    float d = -dot(waterNormal, u_cameraEye);


    vec3 edge = vec3(0.25, 0.27, 0.01);


    float highlights = 0.0;
    vec4 highlightNoise = combined1 * (1.0 - abs(d));

    highlights = bandPassSmooth(highlightNoise.x, edge.x, edge.y, edge.z) / edge.y;
    highlights += bandPassSmooth(highlightNoise.y, edge.x, edge.y, edge.z) / edge.y;
    highlights += bandPassSmooth(highlightNoise.z, edge.x, edge.y, edge.z) / edge.y;
highlights /= 2.0;
    highlights *= wobbleNoise1.x;

    //highlights *= highlights;

    highlights = smoothstep(0.4, 0.9, highlights);

    //highlights = bandPassSmooth(highlights, edge.x, edge.y, edgeSize);

    float highlightThreshold = 0.99;
    //highlights = bandPassSmooth(fullWave.x * abs(d), edge.x, edge.y, edgeSize);
    //highlights = smoothstep(edge.x, edge.x + edgeSize, highlights) * smoothstep(edge.y, edge.y - edgeSize, highlights);

    vec3 lightDir = normalize(vec3(0.5, -1, -2));
    float streaks = bandPassSmooth(fullWave.x, 0.5, 0.5 + 0.01, 0.01);
    float sparkles = pow(max(0.0, dot(reflect(-lightDir, waveNormal), u_cameraEye)), u_sparkleIntensity);
    float shapedWave = bandReject(abs(d) * fullWave.x, 0.2, 0.6);
    float fresnel = max(0.0, 1.0 - dot(vec3(0.0, 0.0, 1.0), u_cameraEye));

    float alpha = shapedWave * 0.5;

#ifdef WATERFALL_SHADER

    highlights *= (1.0 - curvePercent);

    streaks = bandPass(mix(fullWave.x, combined2.y, curvePercent), 0.5, 0.5 + 0.02);
    streaks *= clamp(curvePercentUnclamped * 0.5, 0.0, 1.0);

    float fallSpeed = max(0.0, curvature2 - 1.0);
    fallSpeed = min(curvature2, 1.0) + max(0.0, lerpSlope(fallSpeed, 0.25, 1.0));



    float stripesAndStreaks = makeStripes(v_rootPos.xyz, snappedWFPos, direction, fallSpeed, curvature2);

    float foamStreaks = bandPassSmooth(combined1.x, 0.5, 0.7, 0.05);

    foamStreaks *= smoothstep(1.3 - combined2.z * 0.5, 1.5 + combined2.a * 3.0, min(1.0, curvature2));
    foamStreaks *= contrastPow(foamStreaks, 3.0);

    foamStreaks += clamp(0.0, 1.0, curvature2 * 0.2 - combined2.x * 0.3);
    foamStreaks = max(0.0, foamStreaks);

    foamStreaks *= 1.0;

    waterColor.rgb += mix(vec3(0.0), u_foamColor.rgb * u_foamColor.a, clamp(borderBottom + foamStreaks, 0.0, 1.0));
    waterColor.a *= borderTop;
    waterColor.a = min(1.0, waterColor.a + curvePercent * 0.25);

    alpha *= 1.0 - curvePercent;
    streaks *= 1.0 - curvePercent;
#else
    waterColor.rgb += mix(vec3(0.0), u_foamColor.rgb * u_foamColor.a, clamp(borderBottom, 0.0, 1.0));
    waterColor.a *= borderTop;
#endif

    albedo.rgb = albedo.rgb * (albedo.a - waterColor.a);


    vec3 water = mix(waterColor.rgb, u_highlightColor, alpha)
    + vec3(streaks * fresnel * u_streaksIntensity)
    + highlights * u_sparkleIntensity;
#ifdef WATERFALL_SHADER
    water += vec3(min(1.0, stripesAndStreaks));
#endif
    water = min(vec3(1.0), water) * waterColor.a;

    fragColor.rgb = albedo.rgb + water;

    normal = packNormal(vec3(0.0, 0.0, 1.0));
    //fragColor.rgb = vec3(depthPixelScaled);

    vec4 surfacePos = pos;
    vec3 shadowOffset = vec3(0.0);

    #ifdef WATERFALL_SHADER
        vec3 surfaceNormal = mix(vec3(0.0, 0.0, 1.0), vec3(direction, 0.0), curvature2);
        shadowOffset = surfaceNormal * fullWave.x * u_waveHeight * (1.0 - curvePercent);
    #else
        shadowOffset.z += fullWave.x * u_waveHeight + wobbleVec.z;
    #endif


    //shadowOffset = snap(shadowOffset);
    surfacePos.xyz -= shadowOffset;
    surfacePos = u_cameraProjM * surfacePos;
    //surface movement for shadows
    gl_FragDepth = (surfacePos.z / surfacePos.w + 1.0) * 0.5;

    //fragColor.rgb = mod(vec3(refractedPos), 1.0);

    //fragColor.rgb = mod(vec3(origWorldPos), 1.0);

    //fragColor.rgb = vec3(progress1, progress2, 0.0);

    //fragColor.rgb = mod(vec3(origPos.xy, 0.0), 2.0);

    //if(u_debugView)
    #ifdef WATERFALL_SHADER
        //fragColor.rgb = mod(vec3(flowDir * 0.5 + 0.5, 0.), vec3(1.000001));
    #else
        //fragColor.rgb = mod(vec3(flowDir* 0.5 + 0.5, 0.), vec3(1.000001));
    #endif
        //fragColor.rgb = vec3(v_flowDir * 0.5 + 0.5, 0.);

    fragColor.a = 1.0;
    data = dataShifted;
    data.g = 0.0;


}