#define AA 16.

#define FOCAL_LENGTH 0.6

//#define iTime 6.52

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

vec3 rotX(in vec3 p, float r)
{
    p.yz *= rotMat(r); return p;
}

vec3 rotY(in vec3 p, float r)
{
    p.xz *= rotMat(r); return p;
}

vec3 rotZ(in vec3 p, float r)
{
    p.xy *= rotMat(r); return p;
}

float hash11(float p)
{
    p = fract(p * .1031);
    p *= p + 33.33;
    p *= p + p;
    return fract(p);
}

vec2 hash12(float p)
{
    vec3 p3 = fract(vec3(p,p,p) * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.xx+p3.yz)*p3.zy);
}

vec2 hash22(vec2 p)
{
    vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yzx+33.33);
    return fract((p3.xx+p3.yz)*p3.zy);
}

float hash21(vec2 p)
{
    vec3 p3 = fract(vec3(p.xyx) * .1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}

float cosmooth(float x) { return 1.-cos(3.141593*x)*0.5-0.5; }

float shash(float x)
{
    float fx = floor(x);
    float s = mod(fx, 2.)*2.-1.;
    float a = hash11(fx)*s;
    float b = hash11(fx+1.)*-s;
    return mix(a, b, cosmooth(fract(x)))*0.5;
}

vec3 planeIntersect(in vec3 ro, vec3 rd, float y)
{
    ro.xz = ro.xz+rd.xz*(y-ro.y)/rd.y;
    return vec3(ro.x, y, ro.z);
}

vec3 getEnvironmentMap(vec3 rd, vec3 l)
{
    float ld = dot(rd, l);
    return (1./(max(1e-10, 1.-ld+0.003))*0.01
    +0.02/(abs(ld-0.95)+0.07) // ring
    )
    *vec3(0.733,0.302,0.769) // coloring
    +exp(-rd.y*3.)*vec3(0.125,0.075,0.110) // sky gradient
    ;
}

float getHeight(vec2 p)
{
    float r = 0.;
    for(float i = 0.; i < 3.; i++)
    {
        float e = exp2(-i);
        r += abs(sin(p.x/e+iTime))*e;
        p = rotZ(p.xyx, hash11(i*25.)*0.2).xy;
    }
    return r;
}

vec2 heightNormal(vec2 p, float dist)
{
    vec2 e = vec2(0.2, 0);
    return normalize(vec2(getHeight(p+e.xy)-getHeight(p-e.xy),
                          getHeight(p+e.yx)-getHeight(p-e.yx)));
}

vec3 gradToVec(vec2 d)
{
    vec3 a = normalize(vec3(1, -d.x, 0)),
         b = normalize(vec3(0, -d.y, 1));
    return cross(a, b);
}

vec3 halfVec(vec3 a, vec3 b)
{
    return normalize(a+b);
}

void _mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 nuv = fragCoord/iResolution.xy;
    vec2 uv = (fragCoord-iResolution.xy*0.5)/iResolution.y;
    vec2 m =  (iMouse.xy-iResolution.xy*0.5)/iResolution.y*step(0.01, iMouse.z);

    vec2 rot = vec2(3.14*-0.5, 0)-m.xy*4.-vec2(shash(iTime*0.25), shash(iTime*0.25+0.7071067812))*0.5;
    vec3 ro = vec3(0, 1, 0);
    vec3 rd = rotY(rotX(normalize(vec3(uv, FOCAL_LENGTH)), rot.y), rot.x);
    vec3 center = rotY(rotX(normalize(vec3(0, 0, FOCAL_LENGTH)), rot.y), rot.x);;
    vec3 lgt = rotZ(vec3(1, 0, 0), 0.2);

    vec3 pli = planeIntersect(ro, rd, 0.);
    float blend = smoothstep(-0.05, 0., rd.y);

    vec3 planeNormal = gradToVec(0.2*heightNormal(clamp(pli.xz, vec2(-1e4), vec2(1e4)), 1.)/max(1., distance(ro, pli)*0.5));

    vec3 env = getEnvironmentMap(mix(reflect(rd, planeNormal), rd, blend), lgt);
    float vignette = 4.*nuv.x*(1.-nuv.x)*4.*nuv.y*(1.-nuv.y)*0.6+0.4;
    float glow = pow(max(0., dot(rd, halfVec(center, lgt))), 12.)*0.3*mix(0.2, 1.5, pow(max(0., dot(center, lgt)), 25.));

    fragColor = vec4(sqrt((env+glow)*vignette), 1.0);
    //fragColor = vec4(glow);
}

vec4 simpleAA(vec2 fc, vec4 m)
{
    vec4 res = vec4(0);
    float st = 2.0/AA;
    for(vec2 o = vec2(-1.); o.x < 1.; o.x += st)
    for(o.y = -1.; o.y < 1.; o.y += st)
    {
        vec4 color;
        _mainImage(color, fc + o);
        res += color;
    }
    return res/(AA*AA);
}

vec4 stochasticAA(vec2 fc, vec4 m)
{
    vec4 res = vec4(0);
    float st = 1.0/(AA-1.);
    vec4 color;
    _mainImage(color, fc);
    res += color;
    if(AA > 1.)
    {
        for(float x = 0.; x < 1.; x += st)
        {
            vec4 color;
            _mainImage(color, fc + (hash22((x+iTime+fc)*25.)-0.5));
            res += color;
        }
    }
    return res/AA;
}

vec3 dither(vec3 color, vec2 coord, float steps)
{
    vec3 reduce = floor(color*steps)/steps;
    vec3 error = color-reduce;
    return reduce+step(vec3(hash21(coord)), error*steps)/steps;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    fragColor = vec4(dither(stochasticAA(fragCoord, iMouse).rgb, fragCoord, 256.), 1);
}