#version 130

#define MAX_STEPS 300
#define MAX_DIST 300.
#define MIN_DIST 0.01

vec2 iResolution = vec2(1920, 1080);
uniform float iTime;
uniform sampler2D logo;
uniform vec3 camPos;
uniform vec3 lookAt;
float robot = 0;

struct surf {
    float dist;
    int id; 
};

mat3 rotate(float x, float y, float z)
{
    mat3 rotZ = mat3(vec3(cos(z), 	sin(z), 	0.),
                    vec3(-sin(z), 	cos(z), 	0.),
                    vec3(0., 		0., 		1.));
    mat3 rotY = mat3(vec3(cos(y), 0., -sin(y)),
                     vec3(0., 	 1., 0.), 
                     vec3(sin(y), 0., cos(y)));
    mat3 rotX = mat3(vec3(1., 0., 		0.), 
                     vec3(0., cos(x), 	sin(x)),
                     vec3(0., -sin(x), 	cos(x)));
    

    return rotX * rotY * rotZ;
}

surf smoothUnion(surf a, surf b, float k)
{
    float h = clamp(0.5 + 0.5 * (b.dist - a.dist) / k, 0., 1.);
    float dist = mix(b.dist, a.dist, h) - k * h * (1.0 - h);
        
    return surf(dist, a.id);
}

surf minSurf(surf a, surf b)
{
    if (a.dist < b.dist) return a;
    return b;
}

float sdfPlane(vec3 p, vec3 n, float h)
{
    return dot(p, n) + h;
}

float sdfSphere(vec3 p, float r)
{
    return length(p) - r;
}

float sdfCapsule(vec3 p, vec3 a, vec3 b, float r)
{
    float t = clamp(dot(p - a, b - a) / dot(b - a, b - a), 0., 1.);
    vec3 c = a + t * (b - a);
    
    return length(p - c) - r;
}

float smoothUnion(float a, float b, float k)
{
    float h = clamp(0.5 + 0.5 * (b - a) / k, 0., 1.);
    return mix(b, a, h) - k*h*(1.0-h);
}

surf sdfDiff(surf a, surf b)
{
    float dist = max(a.dist, -b.dist);
    if (dist > a.dist) return surf(dist, b.id);
    
    return a;
}

float rand(vec2 co){
    return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
}

surf map(vec3 p)
{
    vec3 groundP = p;
    vec2 f = vec2(9., 4.5);
    vec2 h = trunc(p.xz / f); //Joker index
    vec2 g = vec2(rand(h.xy), rand(h.yx));
    
    p.xz = mod(p.xz, f) - 0.5 * f;
    p.xz += clamp(g, -0.5, 0.5);
    p.y -= clamp(sin(iTime * 4. + g.y) * 1.5, 0., 1.5);
    
    surf flooring = surf(sdfPlane(groundP, vec3(0., 1., 0.), 3.2), 0);
    surf head = surf(sdfSphere(p, 1.5), 1);
    surf limb_1 = surf(sdfCapsule(p, vec3(-1., -0.2, 0.), vec3(-1.5, -1.0, 0.), 0.4), 1);
    surf limb_2 = surf(sdfCapsule(p, vec3(-0.4, -0.5, 0.), vec3(-0.4, -2.8, 0.), 0.4), 1);
    surf limb_3 = surf(sdfCapsule(p, vec3(0.2, -0.5, 0.), vec3(0.2, -2.8, 0.), 0.4), 1);
    surf limb_4 = surf(sdfCapsule(p, vec3(1., -0.2, 0.), vec3(1.5, -1.0, 0.), 0.4), 1);
    surf limb_5 = surf(sdfCapsule(p, vec3(-1.5, -1.0, 0.), vec3(-2.5 + sin(iTime * 8.) / 2., 0.0 + sin(iTime * 8.) / 2., 0.), 0.4), 1);
    surf limb_6 = surf(sdfCapsule(p, vec3(1.5, -1.0, 0.), vec3(2.5 - sin(iTime * 8. + 0.7) / 2., 0.0 + sin(iTime * 8. + 0.7) / 2., 0.), 0.4), 1);
    
    surf leftEye = surf( sdfSphere(p - vec3(-0.5, .7, -1.1), 0.3), 2 );
    surf leftPupil = surf( sdfSphere(p - vec3(-0.55, 0.8, -1.35), 0.1), 3);
    surf rightEye = surf( sdfSphere(p - vec3(0.5, .7, -1.1), 0.3), 2 );
    surf rightPupil = surf( sdfSphere(p - vec3(0.55, 0.8, -1.35), 0.1), 3);
    surf mouth = surf( sdfCapsule(p, vec3(-0.5, .1, -1.5), vec3(0.5, .1, -1.5), 0.285 - (sin(iTime * 4.) / 32.) ), 4 );
    
    head = smoothUnion(head, limb_1, 0.1);
    head = smoothUnion(head, limb_2, 0.1);
    head = smoothUnion(head, limb_3, 0.1);
    head = smoothUnion(head, limb_4, 0.1);
    head = smoothUnion(head, limb_5, 0.1);
    head = smoothUnion(head, limb_6, 0.1);
    head = sdfDiff(head, mouth);
    
    return minSurf(rightPupil, minSurf(leftPupil, minSurf(rightEye, minSurf(leftEye, minSurf(head, flooring)))));

}

surf march(vec3 ro, vec3 rd)
{
    vec3 p = vec3(0);
    float travel = 0.;
    surf hit = surf(0., -1);

    for (int i = 0; i < MAX_STEPS; i++)
    {
        p = ro + rd * travel;
        hit = map(p);
        travel += (hit.dist / 2.);
        
        if (abs(hit.dist) <= MIN_DIST)
        {
            break;
        }
        if (travel > MAX_DIST) {hit.id = -1; break;}
    }
    
    return surf(travel, hit.id);
}

vec3 getNormal(vec3 p)
{
    vec2 smol = vec2(0.00001, 0);
    float x = map(p + smol.xyy).dist - map(p - smol.xyy).dist;
    float y = map(p + smol.yxy).dist - map(p - smol.yxy).dist;
    float z = map(p + smol.yyx).dist - map(p - smol.yyx).dist;

    return normalize(vec3(x, y, z));
}

vec3 phong(vec3 p, vec3 n, vec3 cam, vec3 light, vec3 diffuse, vec3 spec, float shine)
{
    vec3 L = normalize(light - p);
    vec3 C = normalize(cam - p);
    vec3 R = normalize(reflect(-L, n));
    
    float NL = dot(L, n);
    float CR = dot(R, C);
    
    if (NL < 0.) return vec3(0.); //No light reaches surface
    if (CR < 0.) return diffuse * NL; //Light is reflected away from camera

    return diffuse * NL + spec * pow(CR, shine);
}

void main( /* out vec4 fragColor, in vec2 fragCoord */ )
{
    //vec2 uv = fragCoord/iResolution.xy * 2. - 1.;    
    //vec2 fragCoord = gl_TexCoord[0].xy;
    vec2 uv = gl_TexCoord[0].xy; // / iResolution.xy * 2. - 1.;
    uv.x *= iResolution.x / iResolution.y;

    //vec2 uv = gl_TexCoord[0].xy / iResolution.xy * 2. - 1.;
    

    vec3 col = vec3(0.);
    vec3 mov = vec3(0., 0., iTime);
    
    //vec3 lookAt = vec3(0., 0., -1.);
    //vec3 camPos = vec3(0., 2.5, -5.);
    
    vec3 forward = normalize(lookAt - camPos);
    vec3 right = normalize(vec3(forward.z, 0., -forward.x));
    vec3 up = normalize(cross(forward, right));
    
    float fov = iResolution.y / iResolution.x;
    
    vec3 rayOrigin = camPos;
    vec3 rayDir = normalize(forward + fov * uv.x * right + fov * uv.y * up);
    
    vec3 lightPos = vec3(1., 28., 0.);
    
    surf hit = march(rayOrigin, rayDir);
    vec3 p = rayOrigin + rayDir * hit.dist;
    
    vec3 n = getNormal(p);

    switch(hit.id)
    {
        case 0://floor
            vec2 texOffset = vec2(0, 0);
            float texScale = 64.;
            col = texture(logo, (p.xz + texOffset) / texScale).xyz * abs(n.y);            
            break;

        case 1://skin
            col = phong(p, n, camPos, lightPos, mix(vec3(0.5, 1., 0.), vec3(.6, .6, .6), robot), vec3(0.1), 4.);
            break;

        case 2://eye
            col = phong(p, n, camPos, lightPos, vec3(0.75), vec3(1.), 128.);
            break;

        case 3://pupil
            col = vec3(0.);
            break;

        case 4://mooth
            col = phong(p, n, camPos, lightPos, vec3(.7, .4, .4), vec3(0.2), 1.);
            break;

        default:
            col = vec3(0);
            break;
    }
    
    if (march(p + n * MIN_DIST * 4., normalize(lightPos)).dist < length(lightPos - p)) col *= 0.8;
    
    // Output to screen
    gl_FragColor = vec4(col,1.0);
}