#version 330 core

in vec2 vUV;
out vec4 FragColor;

uniform vec3 iResolution;
uniform float iTime;
uniform float uAlpha;
uniform int uCableCount;
uniform int uMaxSteps;
uniform float uTransitionDelay;
uniform float uTransitionPeriod;
uniform float uTransitionAmount;
uniform float uTwistSpeed;
uniform float uYRepeat;
uniform float uAngleMultiplier;
uniform float uCableRadius;
uniform float uCableThickness;
uniform float uRadiusBase;
uniform float uRadiusVariance;
uniform float uPulseAmount;
uniform float uMarchScale;
uniform float uMaxDistance;
uniform float uCameraDistance;
uniform float uFocalLength;
uniform vec3 uWarmColor;
uniform vec3 uCoolColor;
uniform float uCoolIntensity;
uniform vec3 uAccentColor;
uniform float uAccentPower;
uniform float uDiffusePower;
uniform float uSpecularPower;
uniform float uSpecularIntensity;
uniform float uAoStrength;
uniform float uAoIntensity;
uniform float uFogDensity;
uniform float uGamma;

const float PI = 3.1415926;
const float E = 0.003;
const int MAX_CABLES = 64;
const int MAX_TRACE_STEPS = 256;

mat2 rotate2D(float r)
{
    return mat2(cos(r), -sin(r), sin(r), cos(r));
}

vec2 de(vec3 p)
{
    vec2 o = vec2(100.0, 0.0);
    vec3 p_ = p;
    
    // trasition
    float transitionPeriod = max(uTransitionPeriod, 0.001);
    float trasition = smoothstep(0.0, 1.0, mod((iTime - uTransitionDelay), transitionPeriod)) + floor((iTime - uTransitionDelay) / transitionPeriod);
    trasition *= PI * 0.5 * uTransitionAmount;
    p.xy *= rotate2D(trasition * 2.0);
    p.yz *= rotate2D(trasition);
    p.xz *= rotate2D(iTime * uTwistSpeed);

    // y-axis repetition
    p.y += atan(p.z, p.x) * uYRepeat;
    p.y = mod(p.y, PI) - PI * 0.5;

    // many torus !
    float r = atan(p.x, p.z) * uAngleMultiplier;
    int cableCount = clamp(uCableCount, 1, MAX_CABLES);
    for (int i = 0; i < MAX_CABLES; i++)
    {
        if (i >= cableCount) break;
        r += 1.0 / float(cableCount) * PI * 2.0;
        float s = uRadiusBase + sin(float(i) * 1.618 * PI * 2.0) * uRadiusVariance;
        s += sin(iTime + float(i)) * uPulseAmount;

        vec2 q = vec2(length(p.xz) + cos(r) * s - uCableRadius, p.y + sin(r) * s);
        float d = length(q) - uCableThickness;
        
        if (d < o.x)
        {
            o.x = d;
            o.y = float(i);
        }
    }

    return o;
}

vec3 normal(vec3 p)
{
    float h = E;
    vec2 k = vec2(1.0, -1.0);
    return normalize(
            k.xyy * de(p + k.xyy * h).x + 
            k.yyx * de(p + k.yyx * h).x + 
            k.yxy * de(p + k.yxy * h).x + 
            k.xxx * de(p + k.xxx * h).x
        );
}

void trace(vec3 ro, vec3 rd, inout vec3 color)
{
    vec3 ro_ = ro;
    
    float ad = 0.0;
    int maxSteps = clamp(uMaxSteps, 1, MAX_TRACE_STEPS);
    for (int i = 0; i < MAX_TRACE_STEPS; i++)
    {
        if (i >= maxSteps) break;
        vec2 res = de(ro) * uMarchScale;
        ro += rd * res.x;
        ad += res.x;
        
        if (res.x < E)
        {
            // light direction
            vec3 ld = normalize(vec3(1.0, 1.0, 0.5));
            
            // normal
            vec3 n = normal(ro);
            
            // albedo
            color = mix(uWarmColor, uCoolColor * uCoolIntensity, fract(res.y * 1.618));
            color = mix(color, uAccentColor, pow(fract((res.y + 10.5) * 1.618), uAccentPower));
            
            // diffuse
            color *= pow(dot(n, ld) * 0.5 + 0.5, uDiffusePower);
            
            // specular
            vec3 h = normalize(ld + normalize(ro_ - ro));
            color += pow(max(dot(h, n), 0.0), uSpecularPower) * uSpecularIntensity;
            
            // ao
            float rim = float(i) / max(float(maxSteps - 1), 1.0);
            color *= exp(-rim * rim * uAoStrength) * uAoIntensity;
            
            // fog
            color *= exp(-ad * ad * uFogDensity);

            return;
        }
        else if (ad > uMaxDistance)
        {
            break;
        }
    }
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 p = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;
    vec3 color = vec3(0.0);

    // ray
    vec3 ro = vec3(0.0, 0.0, uCameraDistance);
    vec3 rd = normalize(vec3(p, -uFocalLength));

    // ray marching
    trace(ro, rd, color);
    
    // gamma correction
    color = pow(max(color, vec3(0.0)), vec3(uGamma));
    
    fragColor = vec4(color, 1.0);
}

void main()
{
    vec2 fragCoord = vUV * iResolution.xy;
    vec4 outCol;
    mainImage(outCol, fragCoord);
    outCol.a *= uAlpha;
    FragColor = outCol;
}
