#version 330 core
out vec4 FragColor;
in vec2 vUV;

uniform vec3 iResolution;
uniform float iTime;
uniform float uAlpha;
uniform float uIterations;
uniform float uNoiseFreq1;
uniform float uNoiseFreq2;
uniform float uNoiseFreq3;
uniform float uNoiseFreq4;
uniform float uNoiseTimeScale;
uniform float uTunnelRadius;
uniform float uTunnelMin;
uniform float uTimeDepthSpeed;
uniform float uRotationSpeed;
uniform float uRotationPhaseY;
uniform float uRotationPhaseZ;
uniform float uBubbleTimeScale;
uniform float uBubbleRadius;
uniform float uBubbleMin;
uniform float uOctaveStart;
uniform float uOctaveEnd;
uniform float uOctaveGrowth;
uniform float uWarpAmount;
uniform float uRadialRadius;
uniform float uRadialNegativeWeight;
uniform float uStepBase;
uniform float uStepDivisor;
uniform float uSafeStepMin;
uniform float uColorBandScale;
uniform vec4 uColorPhase;
uniform float uColorPhaseScale;
uniform vec4 uHighlightColor;
uniform float uTunnelGlow;
uniform float uBubbleGlow;
uniform float uExposure;

float NoiseField(float p, float t)
{
    return sin(p * uNoiseFreq1 + sin(p * uNoiseFreq2) + t) * cos(p * uNoiseFreq3 + cos(p * uNoiseFreq4) + t);
}

void mainImage(out vec4 color, in vec2 fragCoord)
{
    float t = iTime;
    float z = 0.0;
    color = vec4(0.0);

    float iterations = clamp(uIterations, 1.0, 200.0);
    float octaveStart = max(abs(uOctaveStart), 0.0001);
    float octaveEnd = max(uOctaveEnd, octaveStart + 0.0001);
    float octaveGrowth = max(uOctaveGrowth, 1.0001);

    for (float i = 0.0; i < iterations; i += 1.0)
    {
        vec3 p = z * normalize(vec3(fragCoord + fragCoord, 0.0) - iResolution.xyy);
        float l = max(length(p.xy) - uTunnelRadius, uTunnelMin);

        p.z -= t * uTimeDepthSpeed;

        vec4 rotTerms = cos(p.z * uRotationSpeed + vec4(0.0, uRotationPhaseY, uRotationPhaseZ, 0.0));
        p.xy *= mat2(rotTerms.x, rotTerms.y, rotTerms.z, rotTerms.w);

        float r = max(length(sin(p.xz + uBubbleTimeScale * t)) - uBubbleRadius, uBubbleMin);

        float octave = octaveStart;
        for (; octave < octaveEnd; octave *= octaveGrowth)
        {
            p += uWarpAmount * vec3(
                NoiseField(p.y, uNoiseTimeScale * t),
                NoiseField(p.z, uNoiseTimeScale * t),
                NoiseField(p.x, uNoiseTimeScale * t)) / octave;
        }

        float radial = length(p.xy) - uRadialRadius;
        float stepSize = uStepBase + max(radial, -radial * uRadialNegativeWeight) / uStepDivisor;
        z += stepSize;

        float safeStep = max(abs(stepSize), uSafeStepMin);
        color += cos((uColorBandScale - fract(length(p.xy))) + uColorPhase * uColorPhaseScale) / safeStep;
        color += uHighlightColor * (uTunnelGlow / l + uBubbleGlow / r);
    }

    color.rgb = tanh(color.rgb * color.rgb / uExposure);
    color.a = 1.0;
}

void main()
{
    vec4 color;
    mainImage(color, gl_FragCoord.xy);
    color.a *= uAlpha;
    FragColor = color;
}
