#define CAMERA_DISTANCE 2.4
#define CAMERA_SPIN_SPEED 0.13
#define FOV_MULTIPLIER 0.8
#define SUN_DIR normalize(vec3(1,0.8,0))
#define SUN_COLOR vec3(2,1.6,0.9)*2.3
#define SCATTERING_MULTIPLIER 0.0
#define CLOUD_SIZE vec3(2.6,1.1,2.6)

vec3 aces_tonemap(vec3 color){	
	mat3 m1 = mat3(
        0.59719, 0.07600, 0.02840,
        0.35458, 0.90834, 0.13383,
        0.04823, 0.01566, 0.83777
	);
	mat3 m2 = mat3(
        1.60475, -0.10208, -0.00327,
        -0.53108,  1.10813, -0.07276,
        -0.07367, -0.00605,  1.07602
	);
	vec3 v = m1 * color;    
	vec3 a = v * (v + 0.0245786) - 0.000090537;
	vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;
	return pow(clamp(m2 * (a / b), 0.0, 1.0), vec3(1.0 / 2.2));	
}

float rayPlaneIntersect(vec3 ro, vec3 rd, vec3 center, vec3 normal) {
    float denom = dot(normal, rd);
    if (abs(denom) > 0.0001f) // your favorite epsilon
    {
        float t = dot(center - ro, normal) / denom;
        return t; // you might want to allow an epsilon here too
    }
    return -1.;
}


float opSmoothUnion( float d1, float d2, float k ) {
    float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
    return mix( d2, d1, h ) - k*h*(1.0-h); }

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

vec3 skyColor(vec3 viewDir, vec3 camPos) {
    float horizonNoise = smoothstep(-0.2, 0.9, noise(viewDir * vec3(8,36,8))+noise(viewDir * vec3(28,56,28))*0.5);
    float skyExtinction1 = exp(-max(0., abs(viewDir.y)*28. - horizonNoise*1.8));
    float skyExtinction2 = exp(-max(0., abs(viewDir.y)*12. - horizonNoise*0.5));
    vec3 sky =  vec3(7.5, 7.2, 7.2) * 
                mix(vec3(0.12, 0.85, 1.0) * 1.0, vec3(1), skyExtinction1) * 
                mix(vec3(0.5, 0.25, 0.52) * 0.5, vec3(1), skyExtinction2);
    sky += vec3(3, 2, 0.5) * pow(clamp(dot(viewDir, SUN_DIR), 0., 1.), 4.);
    sky += vec3(50, 40, 2) * smoothstep(0.991, 0.997, dot(viewDir, SUN_DIR)) * 0.3;
    float groundD = rayPlaneIntersect(camPos, viewDir, vec3(0,-50,0), vec3(0,1,0));
    vec3 groundPoint = camPos + viewDir * groundD;
    float groundNoise = smoothstep(-0.5, 0.5, noise(groundPoint*0.07)*0.99 + noise(groundPoint*0.69)*0.3);
    vec3 groundColPre = mix(vec3(0.3, 0.4, 0.6), vec3(0.45, 0.62, 0.68), groundNoise);
    vec3 groundCol = mix(groundColPre, vec3(0.79, 1.28, 1.85), 1.-exp(-max(groundD,0.)*0.003));
    vec3 ground = groundCol * ((1.0-pow(abs(viewDir.y),0.5))*0.8+0.2);
    return mix(ground, sky, smoothstep(-0.04, 0.1, viewDir.y))*0.65;
}

vec3 ambientContribution(vec3 normal) {
    vec3 ground = vec3(0.36, 0.6, 0.65)*0.4;
    //ground *= smoothstep(-1., 0., normal.y)*0.5+0.5;
    return mix(ground, vec3(0.4,0.8,1.1), pow(smoothstep(-1., 1., normal.y),1.0));
}

float density(vec3 pos) {
    vec3 normDist = pos / (CLOUD_SIZE*0.5);
    float topClip = smoothstep(-0.0, 1., normDist.y);
    float sideClipX = smoothstep(0.60-topClip*0.6, 1., abs(normDist.x));
    float sideClipZ = smoothstep(0.60-topClip*0.6, 1., abs(normDist.z));
    float bottomClip = smoothstep(-0.5, -1.0, normDist.y);
    float freq = 3.9;
    float sum = 0.;
    float amp = 0.5;
    float d1 = distance(normDist * vec3(1.0,0.9,1.0), vec3(0, -0.65, 0) * vec3(1.0,0.8,1.0));
    float d2 = distance(normDist * vec3(1.6,1.0,1.6), vec3(-0.3, 0.2, 0.3));
    float dens = 1.-opSmoothUnion(
        pow(d1*1.1, 1.2),
        pow(d2*1.1, 1.4),
        0.5
    );
    pos += vec3(1,0,2);
    for (int i = 1; i<4; i++) {
        float timeOffs = iTime * (1.+float(i)*1.0)*0.4;
        if (i==1) timeOffs = 0.;
        vec3 sineCoeff = vec3(
            pos.z*2.4*freq,
            pos.x*2.6*freq + timeOffs,
            pos.y*2.8*freq
        );
        vec3 randOffs = sin(sineCoeff);
        dens += (abs(noise(pos * freq + vec3(i)*167. + randOffs*0.022*freq))-0.48) * amp;
        freq *= 2.1;
        amp *= 0.5;
    }
    return  max(0., 
                0.0 + dens*0.8 - 
                pow(sideClipX,10.5)*0.4 - 
                pow(sideClipZ, 10.5)*0.4 - 
                pow(topClip,13.5)*0.3 - 
                pow(bottomClip,25.)*0.5
            );
}

float blueNoise(vec2 pixelPos) {
    const float c_goldenRatioConjugate = 0.61803398875f; // also just fract(goldenRatio)
    float val = textureLod(iChannel0, pixelPos / 1024.0f, 0.).r;
    int frame = iFrame % 64;
    val = fract(val + float(frame) * c_goldenRatioConjugate);
    return val;
}

vec2 intersectAABB(vec3 rayOrigin, vec3 rayDir, vec3 boxMin, vec3 boxMax) {
    vec3 tMin = (boxMin - rayOrigin) / rayDir;
    vec3 tMax = (boxMax - rayOrigin) / rayDir;
    vec3 t1 = min(tMin, tMax);
    vec3 t2 = max(tMin, tMax);
    float tNear = max(max(t1.x, t1.y), t1.z);
    float tFar = min(min(t2.x, t2.y), t2.z);
    return vec2(tNear, tFar);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from -1 to 1)
    vec2 uv = (fragCoord - 0.5*iResolution.xy) / iResolution.y;
    
    float camX = cos(iTime*CAMERA_SPIN_SPEED+2.0); 
    float camZ = sin(iTime*CAMERA_SPIN_SPEED+2.0);

    float camYOffs = sin(iTime*0.3)*0.28;
    vec3 cameraPos = normalize(vec3(camX,camYOffs-0.03,camZ))*CAMERA_DISTANCE;
    vec3 cameraDir = -normalize(cameraPos+vec3(0.,cameraPos.y*0.30+0.11,0.));
    vec3 cameraRight = normalize(cross(vec3(0,1,0), cameraDir));
    vec3 cameraUp = normalize(cross(cameraDir, cameraRight));
    
    vec3 viewDir = normalize(cameraDir + cameraUp*uv.y*FOV_MULTIPLIER + cameraRight*uv.x*FOV_MULTIPLIER);
    
    float ao = 1.0;
    
    vec3 final = skyColor(viewDir, cameraPos);
    
    vec3 sunUp = normalize(cross(SUN_DIR, vec3(0,1,0)));
    vec3 sunRight = normalize(cross(SUN_DIR, sunUp));
    sunUp = normalize(cross(SUN_DIR, sunRight));
    
    vec2 box = intersectAABB(cameraPos, viewDir, -CLOUD_SIZE*0.5, CLOUD_SIZE*0.5); 
    vec3 hitPoint = cameraPos + viewDir*box.x;
    
    if (box.x<=box.y) {
        float lightDirMult = 0.9 + blueNoise(fragCoord+50.)*0.2;
        hitPoint += viewDir * blueNoise(fragCoord+123.) * 0.2;
        //lightDirMult = 1.;
        vec4 volume = vec4(0);
        const float marchStep = 0.07;
        for(int i = 0; i<32; i++) {
            vec3 p = hitPoint + viewDir*float(i)*marchStep;
            if (distance(p, cameraPos)>box.y) break;
            float dens = density(p);
            float light = 0.0;
            vec3 lightP = p;
            float lightStepInc = 2.0;
            float lightStep = 0.002;
            for (int n = 0; n<8; n++) {
                float lightDens = density(lightP);
                light += lightDens * lightStep * 20.;
                float randUp = (blueNoise(fragCoord + vec2(n)*50.)*2.-1.) * SCATTERING_MULTIPLIER;
                float randRight = (blueNoise(fragCoord + vec2(n)*88.)*2.-1.) * SCATTERING_MULTIPLIER;
                vec3 scatterVec = normalize(SUN_DIR + sunUp*randUp + sunRight*randRight);
                lightP += scatterVec * lightStep * lightDirMult;
                if  (
                        abs(lightP.y)>CLOUD_SIZE.y*0.5 ||
                        abs(lightP.x)>CLOUD_SIZE.x*0.5 ||
                        abs(lightP.z)>CLOUD_SIZE.z*0.5 
                    ) break;
                lightStep *= lightStepInc;
            }
            
            light = exp(-light*light*12.0);
            
            //Ambient
            /**/
            float amb = 0.;
            for (int a = 1; a<2; a++) {
                vec3 ambP = p + 
                    normalize(vec3(
                        blueNoise(fragCoord + vec2(a) * 120.)*2.0-1.0,
                        blueNoise(fragCoord + vec2(a) * 151.)*2.0-1.0,
                        blueNoise(fragCoord + vec2(a) * 199.)*2.0-1.0
                    ))*0.10*(1.+float(a)*0.4);
                amb += density(ambP);
            }
            float ambSoft = exp(-amb*5.0)*0.9+0.1;
            vec3 ambient = mix(vec3(0.15, 0.25,0.55), vec3(0.58,0.85,1.02), ambSoft);
            
            amb = 0.;
            for (int a = 1; a<2; a++) {
                vec3 ambP = p + 
                    normalize(vec3(
                        blueNoise(fragCoord + vec2(a) * 150.)*2.0-1.0,
                        blueNoise(fragCoord + vec2(a) * 81.)*2.0-1.0,
                        blueNoise(fragCoord + vec2(a) * 149.)*2.0-1.0
                    ))*0.05*(1.+float(a)*0.4);
                amb += density(ambP);
            }
            ambient *= exp(-amb*3.0)*0.8+0.2;
            
            vec3 newColor = vec3(0.7, 0.85, 1) * (light * SUN_COLOR + ambient*1.6);
            volume.rgb += (1.0 - volume.a) * dens * newColor;
            volume.a += (1.0 - volume.a) * dens;
        }
        if (volume.a > 0.001) volume.rgb /= volume.a;
        final = mix(final, volume.rgb, clamp(volume.a*1.3, 0., 1.));
    }

    // Output to screen
    uv.y *= 1.6;
    float vignette = exp2(-length(uv*uv*uv*uv*0.6));
    fragColor = vec4(aces_tonemap(final) * vignette,1.0);
}
