#define FAR 80.0
#define FAR_R 80.0
#define STEPS 80
#define GAMMA vec3(2.2)

float sgn(float x) {
	return (x<0.)?-1.:1.;
}

float vmax3(vec3 v) {
	return max(max(v.x, v.y), v.z);
}


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

float fBox(vec3 p, vec3 b) {
	vec3 d = abs(p) - b;
	return length(max(d, vec3(0))) + vmax3(min(d, vec3(0)));
}

vec2 pMod2(inout vec2 p, vec2 size) {
	vec2 c = floor((p + size*0.5)/size);
	p = mod(p + size*0.5,size) - size*0.5;
	return c;
}

vec3 pMod3(inout vec3 p, vec3 size) {
    vec3 c = floor((p + size*0.5)/size);
    p = mod(p + size*0.5, size) - size*0.5;
    return c;
}

float sdf(vec3 p){

    //some performance tests...
    //pMod2(p.xz, vec2(10.));
    //pMod3(p, vec3(10.));

    return min(fSphere(p-vec3(-1.0, 0. ,0.), 0.5),min(fBox(p, vec3(0.025, 1.0, 1.5)), fBox(p-vec3(1.0, 0. , 0.), vec3(0.5))));
}

vec3 normal( vec3 p ){
	vec3 eps = vec3(0.001, 0.0, 0.0);
	return normalize( vec3(
		sdf(p+eps.xyy)-sdf(p-eps.xyy),
		sdf(p+eps.yxy)-sdf(p-eps.yxy),
		sdf(p+eps.yyx)-sdf(p-eps.yyx)
	));
}

//Ambien occlusion & shadow coeffience function modified from
//las's (Mercury) and dechipher's (YUP) methods from pouet raymarching thread
//PRAMETERS:
//  p = position,
//  n = normals (ao) or unit vector of light direction (shadow)
//  k = constant

float ambientOcclusion(vec3 p, vec3 n, float k){
    float s = sgn(k);
    float o = s*.5+.5;
    for(float i = 0.; i < 6.; ++i){
        o -= (i*k - sdf(p+n*i*k*s))/exp2(i);
    }
    return max(min(o, 1.), 0.);
}

vec3 march(in vec3 ro, in vec3 rd, inout float t){

    vec3 p = ro;
    for(int i = 0; i < STEPS; ++i){
        float d = sdf(p);
        t += d;
        p += rd*d;

        if(d < 0.01 || t > FAR){
            break;
        }

    }
    return p;
}


mat4 marchRF(in vec3 ro, in vec3 rd, in vec3 ro2, in vec3 rd2, inout vec2 t){
    vec3 n = normal(ro);
    vec3 n2 = normal(ro2);

    vec3 rdRefract= refract(rd2, n2, 1.31);

    vec3 rdReflect = reflect(rd, n);

    vec3 pRefract = ro2;
    vec3 pReflect = (ro+(0.01*n));

    bool refractDone = false;
    bool reflectDone = false;

    for(int i = 0; i < STEPS; ++i){
        float d1 = sdf(pRefract);
        float d2 = sdf(pReflect);

        if(!refractDone){
             t.x += d1;
             pRefract += rdRefract*d1;
        }

        if(!reflectDone){
        	t.y += d2;
        	pReflect += rdReflect*d2;
        }

        if(d1 < 0.0001 || t.x > FAR_R){
            refractDone = true;
        }

        if(d2 < 0.0001 || t.y > FAR_R){
            reflectDone = true;
        }

        if(refractDone && reflectDone){
            break;
        }

    }

    return mat4(
        vec4(pRefract, 0.),
        vec4(pReflect, 0.),
        vec4(rdRefract, 0.),
    	vec4(rdReflect, 0.));

}

vec3 shade(in vec3 p, in vec3 ld, in vec3 rd){
    vec3 n = normal(p);

    float lambertian = max(dot(-ld, n), 0.0);
    float angle = max(dot(reflect(-ld, n), rd), 0.0);
    float specular = pow(angle, 80.0);

    float ao = ambientOcclusion(p, n, 0.5);


    vec3 c = ao*vec3(0.5)+lambertian*vec3(0.5, 0.5, 0.6)*0.5+specular*vec3(0.6)*0.6;
    return c;
}




// Functio to set camera
// o = camera origin
// target = camera target (lookAt)
// cr = right angle
mat3 setCamera(vec3 o, vec3 target, float cr ){
	vec3 cw = normalize(target-o);//z
	vec3 cp = vec3(sin(cr), cos(cr),0.0);//up
	vec3 cu = normalize( cross(cw,cp) );//x
	vec3 cv = normalize( cross(cu,cw) );//y

    return mat3( cu, cv, cw );
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
	vec2 uv = fragCoord.xy / iResolution.xy;
    vec2 v = -1.0+2.0*uv;
    v.x *= iResolution.x/iResolution.y;

    vec3 ro = vec3(cos(iTime*0.5)*3.5, 0.0, sin(iTime*0.5)*3.5);
    vec3 rt = vec3(0., 0., 0.);

    mat3 cam = setCamera(ro, rt, 0.);
    vec3 rd = normalize(cam*vec3(v.xy, radians(90.)));

    float t = 0.01;
    vec3 p = march(ro, rd, t);

    vec3 ld = (rt-ro)/distance(rt, ro);
	fragColor.rgb = shade(p, ld, rd);
    fragColor.a = 1.0;

    if (t < FAR){

        vec3 col = vec3(1.);
        vec3 p2 = p;
        vec3 rd2 = rd;
        for(int i = 0; i < 8; ++i){

            vec2 td = vec2(0.);
            mat4 pr = marchRF(p, rd, p2, rd2, td);

            if(td.x < FAR_R){
               col = shade(pr[0].xyz, ld, pr[2].xyz);
               fragColor.rgb = mix(fragColor.rgb, col, 0.25);

                p2 = pr[0].xyz;
                rd2 = pr[2].xyz;
            }
            if(td.y < FAR_R){
                col = shade(pr[1].xyz, ld, pr[3].xyz);
                fragColor.rgb = mix(fragColor.rgb, col, 0.25);

                p = pr[1].xyz;
                rd = pr[3].xyz;
            }
            else{
                fragColor.rgb = mix(fragColor.rgb, vec3(1.), 0.042);
                break;
            }
        }
    }
    else{
        fragColor = vec4(1.);
    }


    fragColor.rgb = pow(fragColor.rgb, 1.0/GAMMA);
}
