#define AA

#define pi (acos(-1.))

const float phi = (1.+sqrt(5.))*.5;

vec2 rotate(vec2 a, float b)
{
    float c = cos(b);
    float s = sin(b);
    return vec2(
        a.x * c - a.y * s,
        a.x * s + a.y * c
    );
}

float sdIcosahedron(vec3 p, float r)
{
    // need to minify and tidy up

    const float q = (sqrt(5.)+3.)/2.;

    const vec3 n1 = normalize(vec3(q,1,0));
    const vec3 n2 = vec3(sqrt(3.)/3.);

    p = abs(p/r);
    float a = dot(p, n1.xyz);
    float b = dot(p, n1.zxy);
    float c = dot(p, n1.yzx);
    float d = dot(p, n2.xyz)-n1.x;
    return max(max(max(a,b),c)-n1.x,d)*r;
}

float sdDodecahedron(vec3 p, float r)
{
    p = abs(p);
    p += phi*p.zxy;
    return (max(max(p.x,p.y),p.z)-r*phi) / sqrt(phi*phi+1.);
}


float sdRhombicTriacontahedron(vec3 p, float r)
{
    const float l = phi*2.;
    p = abs(p);
    float a = max(max(p.x,p.y),p.z);
    p += (p+p.yzx)*phi+p.zxy;
    return max(a, max(p.x,max(p.y,p.z))/l)-r;
}

int mat;
float scene(vec3 p)
{
    vec3 r = vec3(1);
    float R = sin(iTime)*.1+.2;

    float t=cos(iTime)*.4+.4;
    t=.8;

    float cage = max(
        min(
			sdDodecahedron(p.zyx,1.03-t*.03),
			sdIcosahedron(p,1.02-t*.02)
        ),
		-sdRhombicTriacontahedron(p,1.)
    );

	float ball = length(p)-.8;
    float ball2 = min(
        sdIcosahedron(p,.8),
        sdDodecahedron(p.zyx,.8)
    );
    ball=mix(ball,ball2,cos(iTime)+1.)*.5;

    float best = min(cage,ball);
    mat=(best==cage?0:1);
    return best;
}

vec3 trace(vec3 cam, vec3 dir)
{
    vec3 sky = mix(
        vec3(.1,0,.5),
        vec3(.75,.03,0),
        gl_FragCoord.y/iResolution.y
    );

    vec3 accum = vec3(1);
    for(int bounce=0;bounce<3;++bounce)
    {
        float t;
        float k;
        for(int i=0;i<100;++i)
        {
            k = scene(cam+dir*t);
            t += k;
            if (k < .001 || k > 10.)
                break;
        }

        vec3 h = cam+dir*t;
        vec2 o = vec2(.001, 0);
        vec3 n = normalize(vec3(
            scene(h+o.xyy)-scene(h-o.xyy),
            scene(h+o.yxy)-scene(h-o.yxy),
            scene(h+o.yyx)-scene(h-o.yyx)
        ));

        if (k > 10.)
        {
            // sky
            return sky * accum;
        }
        else if (iMouse.z > 0.)
        {
            return n*.5+.5;
        }
        else if (mat==0)
        {
            float A = .1;
            float B = scene(h+n*A);
            float fakeAO = clamp(B/A,0.,1.);
            fakeAO = pow(fakeAO,.6)*.2+.8;

            float light = n.y*.5+.5;//+n.x*.2+n.z*.1;

            vec3 color = mix(vec3(1.5),sky*3.,.6);
            color = vec3(.1,1,.8)*.7;

            // floor
            return light * fakeAO * accum * color;
        }
        else
        {
            float fresnel = min(.7,pow(max(0.,1.-dot(-dir,n)),5.))/.7;
            fresnel=mix(.04,1.,fresnel);
            cam=h+n*.01;
            dir=reflect(dir,n);
            accum *= fresnel;
        }
    }
    return vec3(0);
}

void mainImage(out vec4 out_color, vec2 fragCoord)
{
    vec2 uv = fragCoord.xy / iResolution.xy-.5;
    uv.x *= iResolution.x / iResolution.y;

    out_color=vec4(0);

#ifdef AA
    for(int i=0;i<4;++i){
        vec2 offset=vec2(i/2,i%2)*.5/iResolution.y;
#else
        vec2 offset=vec2(0);
#endif

        vec3 cam = vec3((uv+offset)*3.,-5.);
        vec3 dir = vec3(0,0,1);

        cam.yz = rotate(cam.yz, atan(1.,sqrt(2.)));
        dir.yz = rotate(dir.yz, atan(1.,sqrt(2.)));

        cam.xz = rotate(cam.xz, pi/4.);
        dir.xz = rotate(dir.xz, pi/4.);

        cam.xz = rotate(cam.xz, iTime*.1);
        dir.xz = rotate(dir.xz, iTime*.1);

        out_color.rgb += trace(cam,dir);
#ifdef AA
    }
    out_color *= .25;
#endif
    out_color *= 2.; // exposure
    out_color.rgb *= 1.-dot(uv,uv)*.2; // vignette
	out_color.rgb = pow(out_color.rgb,vec3(.45)); // gamma
}
