const float PI = 3.1415926535;
const float SPEED = 10.0;
const float ARMS = 3.0;
const vec2 EPSILON = vec2(0, .005);

const vec3 sunDir = vec3(-0.363696,0.581914,0.727393);//normalize(vec3(-.5,.8,1));
const vec3 sunColor = vec3(3,2,1);

float time;
float z_offset;
float tunnelShake;

vec2 rotate(vec2 p, float a)
{
    return vec2(cos(a)*p.x + sin(a)*p.y, -sin(a)*p.x + cos(a)*p.y);
}

float Noise( in vec3 x )
{
    vec3 p = floor(x);
    vec3 f = fract(x);
    f = f*f*(3.0-2.0*f);
    vec2 uv = (p.xy+vec2(37.0,17.0)*p.z) + f.xy;
    vec2 rg = textureLod( iChannel0, (uv+ 0.5)/256.0, 0.0 ).yx;
    return -1.0+2.0*mix( rg.x, rg.y, f.z );
}

float Map3( in vec3 p )
{
    vec3 q = p;
    float f;
    f  = 0.50000*Noise( q ); q = q*2.02;
    f += 0.25000*Noise( q ); q = q*2.03;
    f += 0.12500*Noise( q ); q = q*2.01;
    return f;
}

float Map5( in vec3 p )
{
    vec3 q = p;
    float f;
    f  = 0.50000*Noise( q ); q = q*2.02;
    f += 0.25000*Noise( q ); q = q*2.03;
    f += 0.12500*Noise( q ); q = q*2.01;
    f += 0.06250*Noise( q ); q = q*2.02;
    f += 0.03125*Noise( q );
    return f;
}

mat4 LookAt(vec3 pos, vec3 target, vec3 up)
{
    vec3 dir = normalize(target - pos);
    vec3 x = normalize(cross(dir, up));
    vec3 y = cross(x, dir);
    return mat4(vec4(x, 0), vec4(y, 0), vec4(dir, 0), vec4(pos, 1));
}

vec2 TunnelCenter(float z)
{
    return vec2(sin(z*.17)*.4,sin(z*.1+4.))*3. * tunnelShake;
}

float GetAngle(vec3 pos)
{
    return atan(pos.y,pos.x) - pos.z*.25 + time*3.7 + sin(time)*.2;
}

vec3 Fresnel(vec3 R0, vec3 normal, vec3 viewDir)
{
    float NdotV = max(0., dot(normal, viewDir));
    return R0 + (vec3(1.0) - R0) * pow(1.0 - NdotV, 5.0);
}

float Map(vec3 pos)
{
    pos.z -= z_offset;
    pos.xy -= TunnelCenter(pos.z);
    float fft = textureLod( iChannel3, vec2(0.5,0.15), 0.0).x;
    float angle = GetAngle(pos);
    float r = sin(pos.z*.1)*0.5 + 3. + Map5(pos)*.3 + fft * 2.0 + sin(angle*ARMS)*.3;
    //r += texture( iChannel3, vec2(fract(pos.z*.1),0.0) ).x;
    return length(pos.xy) - r;
}

vec3 Normal(vec3 pos)
{
    vec2 e = vec2(0, .05);
    return normalize(vec3(Map(pos + e.yxx), Map(pos + e.xyx), Map(pos + e.xxy)) - Map(pos));
}

float IntersectPlanets(vec3 pos, vec3 dir, out vec3 normal, out float max_d, out int type)
{
    const float PLANET_CYCLE = 25.0;
    const int PLANET_PASSES = 3;
    float best_dist = 1e10;
    bool hit = false;
    max_d = -1e10;
    for(int i = 0; i < PLANET_PASSES; i++)
    {
        int tp = i;
        if(tp >= 3) tp-=3;
        float time2 = time + 15.5*float(i);
        float planetRound = floor(time2 / PLANET_CYCLE);
        float planetPos = time2 - planetRound * PLANET_CYCLE;
        float planetAngle = planetRound * 23.1;
        float planetDistance =  (tp==0) ? 20. :
        						(tp==1) ? 13. :
        						13.;
        vec3 sphereCenter = vec3(cos(planetAngle)*planetDistance,sin(planetAngle)*planetDistance,(PLANET_CYCLE- planetPos)*10.);
        vec3 delta = pos - sphereCenter;
        float sphereRadius = 	(tp==0) ? 13. :
        						(tp==1) ? 7. :
        						7.;
    
        float B = dot(dir, delta);
        float C = dot(delta, delta) - sphereRadius * sphereRadius;
        float D = B*B - C;
        
        if(D >= 0.0)
        {
            float t = -B - sqrt(D);
            if(t >= 0. && t < best_dist)
            {
                vec3 spherePos = pos + t * dir;
                normal = normalize(spherePos - sphereCenter);
                best_dist = t;
                type = tp;
                hit = true;
            }
        }
        max_d = max(max_d, D);
    }
    return hit ? best_dist : -1.;
}

float EarthHeight(vec3 pos)
{
    vec2 coord = vec2(acos(pos.y)/(2.0*PI), atan(pos.x, pos.z)/(2.0*PI));
    vec3 te = texture( iChannel2, coord ).rgb + texture( iChannel2, coord*3.0 ).rgb * .3;
    float landLerp = smoothstep( 0.45, 0.5, te.x);
    vec3 albedo = mix( vec3(0.1, 0.2, 0.45), (vec3(0.055, 0.275, 0.0275) + 0.45*te + te*te*0.5*texture( iChannel2, 2.0*coord.xy ).xyz)*0.4, landLerp );
    return length(pos) - albedo.x*.015;
}

vec3 BackgroundInner(vec3 pos, vec3 dir, bool enableSun, out bool sphereHit, out vec3 spherePos, out vec3 sphereNormal, out vec3 reflectivity)
{
    vec3 nebulaPos = dir.yxz;
    float v = Map5(nebulaPos*1.3 + Map5(nebulaPos*0.5)*3.0) + .1;
    v = max(v, 0.);
    
    vec3 color = v*mix(vec3(1.,.2,2), vec3(2,.2,10), clamp(Map3(dir*1.3),0.,1.)) + 0.1;
    
    vec2 uv = vec2(acos(dir.x), atan(dir.y,dir.z));
    
    vec3 a = texture( iChannel1, uv*1.5).rgb;
    
    vec3 b = texture( iChannel1, uv*0.4).rgb;
    
    color *= (a * b) * 4.;
    color += pow(texture(iChannel1, uv*1.0).rgb, vec3(4.0)) * 0.5;
    if(enableSun)
    {
        float sunDot = max(0., dot(dir, sunDir));
        color += (pow(sunDot, 8.0)*.03 + pow(sunDot, 512.0)) * 5. * sunColor;
    }

    sphereHit = false;
    reflectivity = vec3(0);
    float max_d;
    int type;
    float t = IntersectPlanets(pos, dir, sphereNormal, max_d, type);
    if(t >= 0.0)
    {
        spherePos = pos + t * dir;
        vec2 coord = vec2(acos(sphereNormal.y)/(2.0*PI), atan(sphereNormal.x, sphereNormal.z)/(2.0*PI));
        float time_offset = time*.04;
        coord.y += time_offset;

        if(type == 0)
        {
            float offset = texture( iChannel2, coord ).r * .005;
            vec3 lookup = sphereNormal;
            lookup.xy = rotate(lookup.xy, time_offset*2.0*PI);
            float height = Map5(lookup*4.)*.5+.8;//texture( iChannel2, coord + vec2(offset)).r;
            height = pow(min(height, 1.),8.);
            vec3 fire = (texture( iChannel2, coord*5. + time*.02).rgb +
                         texture( iChannel2, coord*1. + time*.006).rgb

                        ) * vec3(3,1,1) * .5;

            vec3 albedo = texture( iChannel2, coord + vec2(offset)).rgb * .25 -
                          texture( iChannel2, coord*7.0).rgb * .1;
            color = albedo * max(0., dot(sphereNormal, sunDir)) * sunColor + fire * pow(1.0-height,16.);
        }
        else if(type == 2)
        {
            vec3 te = texture( iChannel2, coord ).rgb + texture( iChannel2, coord*3.0 ).rgb * .3;
        
            float offset = 0.0 + texture( iChannel2, coord).x*.003;
            vec3 albedo = (texture( iChannel2, coord*vec2(.4,0)+vec2(offset,0) ).rgb-.5)*.7 + .4;
            albedo += texture( iChannel2, coord*1.0).rgb * .2;
            albedo += texture( iChannel2, coord*16.0).rgb * .075;
            color = albedo * max(0., dot(sphereNormal, sunDir)) * sunColor;
        }
        else if(type == 1)
        {
            vec3 te = texture( iChannel2, coord ).rgb + texture( iChannel2, coord*3.0 ).rgb * .3;

            vec3 bumpedNormal = normalize(vec3(EarthHeight(sphereNormal + EPSILON.yxx), EarthHeight(sphereNormal + EPSILON.xyx), EarthHeight(sphereNormal + EPSILON.xxy)) - EarthHeight(sphereNormal));
            sphereNormal = bumpedNormal;
            float landLerp = smoothstep( 0.45, 0.5, te.x);
            vec3 albedo = mix( vec3(0.1, 0.2, 0.45), (vec3(0.055, 0.275, 0.0275) + 0.45*te + te*te*0.5*texture( iChannel2, 2.0*coord.xy ).xyz)*0.4, landLerp );
            float specPower = mix(2048., 32., landLerp);
            float q = (  texture( iChannel2, coord+vec2(0,time*.02) ).x +
                            texture( iChannel2, coord*2.0+vec2(0,time*.013) ).x) * .5;

            float skyLerp = smoothstep( 0.4, 0.8, q);
            reflectivity = mix(vec3(0.1), vec3(0.0), skyLerp);

            float NdotL = max(0., dot(sphereNormal, sunDir));
            vec3 opaque = albedo * NdotL * sunColor;
            color = opaque + pow(max(0., dot(bumpedNormal, normalize(-dir + sunDir))), specPower) * (specPower + 8.0) / (8.0 * PI) * sunColor * reflectivity;

            vec3 sky = vec3(0.9) * NdotL * sunColor;
            color = mix( color, sky, skyLerp);        
        }

        sphereHit = true;
    }
    
    return color;
}
    
vec3 Background(vec3 pos, vec3 dir)
{
    dir = normalize(dir);
    
    bool sphereHit;
    vec3 spherePos;
    vec3 sphereNormal;
    vec3 reflectivity;
    vec3 color = BackgroundInner(pos, dir, true, sphereHit, spherePos, sphereNormal, reflectivity);
    if(sphereHit)
    {
        vec3 R = Fresnel(reflectivity, sphereNormal, -dir);

        vec3 reflectionDir = reflect(dir,sphereNormal);
        bool dummyHit;
        vec3 dummyPos;
        vec3 dummyNormal;
        color += (BackgroundInner(spherePos + sphereNormal*.01, reflectionDir, false, dummyHit, dummyPos, dummyNormal, reflectivity)*(1.0-R)+vec3(1,2,3)*.075)*R*sunColor;
    }
    
    return color;
}

vec3 LensFlare(vec2 x)
{
    x = abs(x);
    float e = 1.5;
    float d = pow(pow(x.x*.5, e) + pow(x.y*3., e), 1./e);
    
    vec3 c = vec3(exp(-2.5*d))*sunColor*(.3+sin(x.y*iResolution.y*2.)*.01) * .5;
    c += vec3(exp(-dot(d,d)))*sunColor*.05;
    
    return c;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord / iResolution.xy;
    
    time = iTime;
    vec3 camPos = vec3(0);
    z_offset = time*SPEED;
 
    float introFade = min(time*.05, 1.0);
    
    float camZoom = 3.0 - introFade*2.0;
    
    tunnelShake = introFade;
    camPos.xy += TunnelCenter(camPos.z-z_offset)*.5;
    vec3 camTarget = vec3(0,0,5);
    camTarget.xy += TunnelCenter(camTarget.z-z_offset)*.5;
    //camTarget = vec3(3,0,5);
    camTarget = mix(vec3(3,0,5), camTarget, introFade);
    
    float camAngle = sin(time*.3) + time*.1;
    vec3 camUp = vec3(sin(camAngle),cos(camAngle),0);

    mat4 viewToWorld = LookAt(camPos, camTarget, camUp);
    vec2 uv2 = (fragCoord - .5*iResolution.xy) / (iResolution.y * camZoom);
    vec3 eyeDir = (viewToWorld * normalize(vec4(uv2, 1, 0))).xyz;
    
    float t = 0.0;
    vec3 p = camPos;
    float iterationCount = 0.0;
    for(int i = 0; i < 70; i++)
    {
        float dist = Map(p);
        
        t += dist;
        p += dist*eyeDir;
        iterationCount++;
        if(abs(dist) < .001) break;
    }
    
    vec3 normal = Normal(p);
    vec3 refraction = refract(normalize(eyeDir), normal, 1.012);
    vec3 reflection = reflect(-normalize(eyeDir), normal);
    vec3 halfDir = normalize(sunDir - eyeDir);
    vec2 circlePos = p.xy - TunnelCenter(p.z - z_offset);
    float angle = atan(circlePos.y,circlePos.x);
    
    float z = p.z - z_offset;
    
    vec3 R = Fresnel(vec3(0.0), normal, -eyeDir);
    //vec3 c = mix(vec3(2,1,1),vec3(1,1,2),sin(p.z*.1)*.5+.5);
    vec3 c = vec3(1);
    vec3 outColor = Background(p, refraction)*(vec3(1.0) - R)*c + Background(p, -reflection)*R;
    
    float fft = texture( iChannel3, vec2(0.2,0.25) ).x;
    fft = max(.0, fft - .5);
    
    float tunnelDist = length(p - camPos);
    outColor = outColor * exp(-tunnelDist*.05) + (1.0-exp(-tunnelDist*.05))*vec3(2,1,3)*(.1+fft*.6);
    
    outColor += sqrt(iterationCount)*.005;
    
    vec3 sunPos = (vec4(sunDir, 0) * viewToWorld).xyz;
    vec2 sunUV = sunPos.xy / sunPos.z;

    float vignette = uv.x * (1.0-uv.x) * uv.y * (1.0-uv.y) * 32. * 0.75 + 0.25;
    outColor *= vignette;
    
    vec3 sphereNormal;
    float max_d;
    int type;
    float planet_t = IntersectPlanets(camPos, sunDir, sphereNormal, max_d, type);
    float lensIntensity = clamp(1.0 - max_d*.02, 0.0, 1.0);
    
    outColor += LensFlare(uv2 - sunUV) * lensIntensity;
    outColor += LensFlare(uv2 + sunUV) * .4 * lensIntensity;

    outColor = clamp(outColor, 0.0, 1.0);
    outColor *= vec3(sqrt(min(time*.2, 1.0)));
    fragColor = vec4( outColor, 1.0 );
}