#version 130

#define MAX_DIST 100.
#define MIN_DIST 0.001
#define MAX_STEPS 1000
#define TWO_PI 6.28318530718
#define BALLS 9

uniform float iTime;

uniform vec3 ballPos1, ballPos2, ballPos3;
uniform sampler2D ballTex1, ballTex2, ballTex3;
uniform sampler2D creditsTex;
uniform float camMix;
uniform sampler2D wallTex;
uniform vec3 creditPos = vec3(-10, -10, -10);
/*uniform*/float meltFactor;
/*uniform*/ bool mixer;


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;
}

vec2 minX(vec2 a, vec2 b)
{
    if (a.x < b.x) return a;
    return b;
}

vec4 minX(vec4 a, vec4 b)
{
    if (a.x < b.x) return a;
    return b;
}

vec2 smin(vec2 a, vec2 b, float k)
{
    float h = max(k - abs(a.x - b.x), 0.0);
    vec2 c = minX(a, b);
    
    return vec2(c.x - h * h * 0.25 / k, c.y);
}

float smin(float a, float b, float k)
{
    float h = max(k - abs(a - b), 0.0);
    return min(a, b) - h * h * 0.25 / k;
}

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

float box(vec3 p, vec3 b)
{
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
}

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

float[2] smallest(float[BALLS] a)
{
    float[2] smallest = float[](a[0], a[1]);
    
    if (smallest[0] > smallest[1])
    {
        smallest[0] = smallest[1];
        smallest[1] = a[0];
    }
    
    for (int i = 2; i < 9; i++)
    {
        if (smallest[0] > a[i])
        {
            smallest[1] = smallest[0];
            smallest[0] = a[i];
        }
        else
        {
            smallest[1] = min(smallest[1], a[i]);
        }
    }

    return smallest;
}

vec4[2] smallest(vec4[BALLS] a)
{
    vec4[2] smallest = vec4[](a[0], a[1]);
    
    if (smallest[0].x > smallest[1].x)
    {
        smallest[0] = smallest[1];
        smallest[1] = a[0];
    }
    
    for (int i = 2; i < BALLS; i++)
    {
        if (smallest[0].x > a[i].x)
        {
            smallest[1] = smallest[0];
            smallest[0] = a[i];
        }
        else
        {
            smallest[1] = minX(smallest[1], a[i]);
        }
    }

    return smallest;
}

float map(vec3 p)
{
    float planeY = plane(p, vec3(0., 1., -0.0), 5.55);
    float planeY2 = plane(p, vec3(0., -1., -0.0), 6.5);
    float planeX = plane(p, vec3(1., 0., -0.0), 8.5);
    float planeX2 = plane(p, vec3(-1., 0., -0.0), 8.5);
    float planeZ = plane(p, vec3(0., 0., -1.), 15.5);
    
    float ball1 = sphere(p + ballPos1, 1.);
    float ball2 = sphere(p + ballPos2, 1.);
    float ball3 = sphere(p + ballPos3, 1.);
    
    float credits = box((p + creditPos) * rotate(iTime, 0., iTime), vec3(2.0)); // was 1.0
           
    float[] things = float[](planeY, planeY2, planeX, planeX2, planeZ, ball1, ball2, ball3, credits);
    
    float[2] small = smallest(things);
    
    float o = 0.;
    
    if (mixer) o = smin(small[0], small[1], meltFactor);
    else o = small[0];
    
    return o;
}

vec3 colorMap(vec3 p, vec3 n)
{
    vec4 planeY = vec4(plane(p, vec3(0., 1., -0.0), 5.55), texture(wallTex, p.xz).xyz);
    vec4 planeY2 = vec4(plane(p, vec3(0., -1., -0.0), 6.5), texture(wallTex, p.xz).xyz);
    vec4 planeX = vec4(plane(p, vec3(1., 0., -0.0), 8.5), texture(wallTex, p.yz).xyz);
    vec4 planeX2 = vec4(plane(p, vec3(-1., 0., -0.0), 8.5), texture(wallTex, p.yz).xyz);
    vec4 planeZ = vec4(plane(p, vec3(0., 0., -1.), 15.5), texture(wallTex, p.xy).xyz);
    
    vec3 q = (p + ballPos1) * rotate(iTime, 0., 0.);
    
    vec3 xy = texture(ballTex1, q.xy / 2. + 0.5).xyz;
    vec3 xz = texture(ballTex1, q.xz / 2. + 0.5).xyz;
    vec3 yz = texture(ballTex1, q.yz / 2. + 0.5).xyz;

    vec3 blend = pow(abs(n * rotate(iTime, 0., 0.)), vec3(8.));
    vec3 ball1Col = (xy * blend.z + xz * blend.y + yz * blend.x) / (blend.x + blend.y + blend.z);

    vec4 ball1 = vec4(sphere(p + ballPos1, 1.), ball1Col);

    q = (p + ballPos2) * rotate(0., iTime, 0.);
    
    xy = texture(ballTex2, q.xy / 2. + 0.5).xyz;
    xz = texture(ballTex2, q.xz / 2. + 0.5).xyz;
    yz = texture(ballTex2, q.yz / 2. + 0.5).xyz;

    blend = pow(abs(n * rotate(0., iTime, 0.)), vec3(8.));
    vec3 ball2Col = (xy * blend.z + xz * blend.y + yz * blend.x) / (blend.x + blend.y + blend.z);

    vec4 ball2 = vec4(sphere(p + ballPos2, 1.), ball2Col);
    
    q = (p + ballPos3) * rotate(0., 0., iTime);
    
    xy = texture(ballTex3, q.xy / 2. + 0.5).xyz;
    xz = texture(ballTex3, q.xz / 2. + 0.5).xyz;
    yz = texture(ballTex3, q.yz / 2. + 0.5).xyz;

    blend = pow(abs(n * rotate(0., 0., iTime)), vec3(8.));
    vec3 ball3Col = (xy * blend.z + xz * blend.y + yz * blend.x) / (blend.x + blend.y + blend.z);

    vec4 ball3 = vec4(sphere(p + ballPos3, 1.), ball3Col);
    
    q = (p + creditPos) * rotate(iTime, 0., iTime);
    
    xy = texture(creditsTex, q.xy / 4. + 0.5).xyz;
    xz = texture(creditsTex, q.xz / 4. + 0.5).xyz;
    yz = texture(creditsTex, q.yz / 4. + 0.5).xyz;

    blend = pow(abs(n * rotate(iTime, 0., iTime)), vec3(8.));

    vec3 boxCol = (xy * blend.z + xz * blend.y + yz * blend.x) / (blend.x + blend.y + blend.z);
    
    //vec4 credits = vec4(box(q, vec3(1.)), boxCol);
    vec4 credits = vec4(box(q, vec3(2.)), boxCol); 

    vec4[] things = vec4[](planeY, planeY2, planeX, planeX2, planeZ, ball1, ball2, ball3, credits);
    vec4[] small = smallest(things);
    
    float diff = clamp(0., 1., small[1].x);
    
    vec3 o = vec3(0.);
    
    if (mixer) o = mix(small[1], small[0], diff).yzw;
    else o = small[0].yzw;
    
    return o;

}

float marchShadow(vec3 ro, vec3 lightP)
{
    vec3 p = ro;
    vec3 q = vec3(0.);
    float travel = 0.1;
    float hit = 0.;
    
    vec3 pToLight = lightP - ro;
    float maxDist = length(pToLight);
    vec3 rd = normalize(pToLight);
    float o = 1.0;
    
    for (int i = 0; i < MAX_STEPS; i++)
    {
        p = ro + rd * travel;
        hit = map(p);
        travel += hit;
        
        o = min(o, 50. * hit / travel);
        
        if (abs(hit) <= MIN_DIST)
        {
            return 0.;
        }
        if (travel > maxDist) { break; }
    }
    
    return o;
}

vec3 phong(vec3 p, vec3 n, vec3 cam, vec3 light, vec3 diffuse, float spec, float shine)
{
    vec3 L = normalize(light - p);
    vec3 C = normalize(cam - p);
    
    float LN = max(0., dot(L, n));
    
    vec3 d = diffuse * LN;
    float s = 0.;
    
    if (LN > 0.)
    {
        vec3 R = -normalize(reflect(L, n));
        s = spec * pow(max(0., dot(R, C)), shine);
    }

    return vec3(d + s);
}

vec3 calcNormal(vec3 p)
{
    vec3 q = vec3(0.);
    vec3 smallStep = vec3(0.00001, 0.0, 0.0);

    float x = map(p + smallStep.xyy) - map(p - smallStep.xyy);
    float y = map(p + smallStep.yxy) - map(p - smallStep.yxy);
    float z = map(p + smallStep.yyx) - map(p - smallStep.yyx);

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

void main()
{
    vec2 iResolution = vec2(1920., 1080.);

    float fov = 1080. / 1920.;

    meltFactor = 1.25;
    mixer = false;
    
    vec2 uv = gl_TexCoord[0].xy  * 2. - 1.;
    uv.x *= 1920. / 1080.;
    
    vec2 uvOrth = gl_TexCoord[0].xy  * 8. - 4.;
    uvOrth.x *= 1920. / 1080.;
    
    vec3 camPos = vec3(0., 0., -15.);
    vec3 lookAt = vec3(0., 0., 1.);//-ballPos1;
    
    vec3 forward = normalize(lookAt - camPos);
    vec3 right = normalize(vec3(forward.z, 0., -forward.x));
    vec3 up = normalize(cross(forward, right));
        
    vec3 rayOriginPersp = camPos;
    vec3 rayOriginOrtho = camPos + vec3(uvOrth, 0.);
    vec3 rayDirPersp = normalize(forward + fov * uv.x * right + fov * uv.y * up);
    vec3 rayDirOrtho = vec3(0., 0., 1.);
    
    vec3 rayOrigin = mix(rayOriginPersp, rayOriginOrtho, camMix);
    vec3 rayDir = mix(rayDirPersp, rayDirOrtho, camMix);
    
    vec3 lightPos = camPos;
    int id = -100;
    
    float traveled = 0.;
    vec3 p = vec3(0.);

    vec3 col = vec3(0.2, 0.4, 0.8);
    
    float hit = 0.;
    
    for (int i = 0; i < MAX_STEPS; i++)
    {
        p = rayOrigin + rayDir * traveled;
        
        hit = map(p);
                
        traveled += hit;
        
        if (abs(hit) <= MIN_DIST)
        {
            break;
        }
        if (abs(hit) > MAX_DIST)
        {
            break;
        }
        
    }
    
    vec3 n = vec3(0.);
    
    n = calcNormal(p);

    col = colorMap(p, n);
    
    col = phong(p, n, camPos, lightPos, col, 0.1, 512.);

    //float shadow = marchShadow(p, lightPos);
    //col *= shadow;
        
    gl_FragColor = vec4(col,1.0);
}
