#version 330 core

in vec2 vTexCoord;

uniform float uTime;
uniform vec2 uResolution;
uniform vec2 uMouse;

uniform sampler2D uTextureUnit;
uniform vec4 uColor;
uniform float uDepth;

out vec4 fragColor;
out float gl_FragDepth;

/*
 * Original shader from: https://www.shadertoy.com/view/4dVyzz
 */

#extension GL_OES_standard_derivatives : enable

#ifdef GL_ES
precision mediump float;
#endif

// glslsandbox uniforms
//uniform float time;
//uniform vec2 resolution;

// shadertoy emulation
#define iTime uTime
#define iResolution uResolution
const vec4 iMouse = vec4(0.);

// --------[ Original ShaderToy begins here ]---------- //
// Fork of "Raymarching - Primitives" by iq. https://shadertoy.com/view/Xds3zN
// 2018-03-20

struct Segment
{
    vec3 A;
    vec3 B;
};

float dist(Segment s, vec3 p)
{
	vec3 pa = p-s.A, ba = s.B-s.A;
	float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
	return length( pa - ba*h );
}

float dist(vec3 s, vec3 p)
{
	return length(p-s);
}

struct Bezier4
{
    vec3 A;
    vec3 B;
    vec3 C;
    vec3 D;
};

vec3 getPt(Bezier4 c, float t)
{
    return mix(mix(mix(c.A, c.B, t), mix(c.B, c.C, t), t), mix(mix(c.B, c.C, t), mix(c.C, c.D, t), t), t);
}

vec3 getDir(Bezier4 B, float t)
{
    return normalize(getPt(B, t+0.01)-getPt(B, t-0.01));
}

vec3 getCur(Bezier4 B, float t)
{
    return normalize(getPt(B, t+0.01)-2.*getPt(B, t)+getPt(B, t-0.01));
}

float closest(Bezier4 BB, vec3 p)
{
    vec3 A = BB.A, B = BB.D;

    float ppt;
    float At = .0;
    float Bt = 1.;

    vec3 pp = vec3(0.);

    float dis = dist(pp, p);

    for(int i=0; i<6; ++i)
    {
        ppt = (At+Bt)*.5;
        pp = getPt(BB, ppt);

        if(dist(Segment(A, pp), p) < dist(Segment(pp, B), p))
        {
            Bt = ppt;
        	B = getPt(BB, Bt);
        }
        else
        {
            At = ppt;
        	A = getPt(BB, At);
        }

    }

	vec3 pa = p-A, ba = B-A;
	float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
    return mix(At, Bt, h);
}

#define AA 0

#define PI	3.1415927


mat3 setCamera( in vec3 ro, in vec3 ta, float cr )
{
	vec3 cw = normalize(ta-ro);
	vec3 cp = vec3(sin(cr), cos(cr),0.0);
	vec3 cu = normalize( cross(cw,cp) );
	vec3 cv = normalize( cross(cu,cw) );
    return mat3( cu, cv, cw );
}

//------------------------------------------------------------------

float sdPlane( vec3 p )
{
	return p.y*.5;
}

float sdObj(vec3 p)
{
    vec3 p0 = vec3(0., 1., 0.)+vec3(1., 0., 0.);
    vec3 p1 = vec3(0., 1., 0.)+vec3(0.6, -0.35, 0.6);
    vec3 p2 = vec3(0., 1., 0.)+vec3(.2, -.2, 1.);
    vec3 p3 = vec3(0., 1., 0.)+vec3(0., 0., 1.);

    float inv = 1.;

    p.z = abs(p.z);
    if(p.x<0.)
    {
        p.x = -p.x;
        p.y = -p.y+2.;
        inv = -1.;
    }

    Bezier4 b = Bezier4(p0, p1, p2, p3);


    float t = closest(b, p);

    vec3 proj1 = getPt(b, t);



    p0 = vec3(0., 1., 0.)+vec3(1. , 1., 0. );
    p1 = vec3(0., 1., 0.)+vec3(0.6, 1., 0.6);
    p2 = vec3(0., 1., 0.)+vec3( .2, 1., 1. );
    p3 = vec3(0., 1., 0.)+vec3(0. , 1., 1. );

    b = Bezier4(p0, p1, p2, p3);


    t = closest(b, p);

    vec3 proj2 = getPt(b, t);


    vec3 proj = proj2;
    if(inv>0.)
    	proj.y = min(p.y, proj1.y);
    else
        proj.y = max(p.y, proj1.y);


    float d = length(p-proj);
    return d*.5-.01;
}

//------------------------------------------------------------------

vec2 opU( vec2 d1, vec2 d2 )
{
	return (d1.x<d2.x) ? d1 : d2;
}

float opS( float d1, float d2 )
{
    //return max(-d2,d1);
    return max(d2, d1);
}

//------------------------------------------------------------------

vec2 map( in vec3 pos )
{
    vec2 res = vec2( sdPlane(   pos), 1.0 );
    res = opU( res, vec2( sdObj(pos), 10.5 ) );


    return res;
}

vec2 castRay( in vec3 ro, in vec3 rd )
{
    float tmin = 1.;
    float tmax = 10.0;


    float t = tmin;
    float m = -1.0;
    for( int i=0; i<64; i++ )
    {
	    float precis = 0.0005*t;
	    vec2 res = map( ro+rd*t );
        if( res.x<precis || t>tmax ) break;
        t += res.x;
	    m = res.y;
    }

    if( t>tmax ) m=-1.0;
    return vec2( t, m );
}


float calcSoftshadow( in vec3 ro, in vec3 rd, in float mint, in float tmax )
{
	float res = 1.;
    float t = mint;
    for( int i=0; i<16; i++ )
    {
		float h = map( ro + rd*t ).x;
        res = min( res, 10.0*h/t );
        t += clamp( h, 0.01, 0.10 );
        if( h<0.0001 || t>tmax ) break;
    }
    return clamp( res, 0.0, 1.0 );
}

vec3 calcNormal( in vec3 pos )
{
    vec2 e = vec2(1.0,-1.0)*0.5773*0.0005;
    return normalize( e.xyy*map( pos + e.xyy ).x +
					  e.yyx*map( pos + e.yyx ).x +
					  e.yxy*map( pos + e.yxy ).x +
					  e.xxx*map( pos + e.xxx ).x );
}

float calcAO( in vec3 pos, in vec3 nor )
{
	float occ = 0.0;
    float sca = 1.0;
    for( int i=0; i<5; i++ )
    {
        float hr = 0.01 + 0.12*float(i)/4.0;
        vec3 aopos =  nor * hr + pos;
        float dd = map( aopos ).x;
        occ += -(dd-hr)*sca;
        sca *= 0.95;
    }
    return clamp( 1.0 - 3.0*occ, 0.0, 1.0 );
}

// http://iquilezles.org/www/articles/checkerfiltering/checkerfiltering.htm
float checkersGradBox( in vec2 p )
{
    // filter kernel
    vec2 w = fwidth(p) + 0.001;
    // analytical integral (box filter)
    vec2 i = 2.0*(abs(fract((p-0.5*w)*0.5)-0.5)-abs(fract((p+0.5*w)*0.5)-0.5))/w;
    // xor pattern
    return 0.5 - 0.5*i.x*i.y;
}

vec3 render( in vec3 ro, in vec3 rd )
{
    vec3 col = vec3(0.7, 0.9, 1.0) +rd.y*0.8;
    vec2 res = castRay(ro,rd);
    float t = res.x;
	float m = res.y;
    if( m>-0.5 )
    {
        vec3 pos = ro + t*rd;
        vec3 nor = calcNormal( pos );
        vec3 ref = reflect( rd, nor );

        // material
		col = 0.45 + 0.35*sin( vec3(0.05,0.08,0.10)*(m-1.0) );
        if( m<1.5 )
        {

            float f = checkersGradBox( 5.0*pos.xz );
            col = 0.3 + f*vec3(0.1);
        }

        // lighitng
        float occ = calcAO( pos, nor );
		vec3  lig = normalize( vec3(-0.4, 1., -0.6) );
        vec3  hal = normalize( lig-rd );
		float amb = clamp( 0.5+0.5*nor.y, 0.0, 1.0 );
        float dif = clamp( dot( nor, lig ), 0.0, 1.0 );
        float bac = clamp( dot( nor, normalize(vec3(-lig.x,0.0,-lig.z))), 0.0, 1.0 )*clamp( 1.0-pos.y,0.0,1.0);
        float dom = smoothstep( -0.1, 0.1, ref.y );
        float fre = pow( clamp(1.0+dot(nor,rd),0.0,1.0), 2.0 );

        dif *= calcSoftshadow( pos, lig, 0.02, 2.5 );
        dom *= calcSoftshadow( pos, ref, 0.02, 2.5 );

		float spe = pow( clamp( dot( nor, hal ), 0.0, 1.0 ),16.0)*
                    dif *
                    (0.04 + 0.96*pow( clamp(1.0+dot(hal,rd),0.0,1.0), 5.0 ));

		vec3 lin = vec3(0.0);
        lin += 1.30*dif*vec3(1.00,0.80,0.55);
        lin += 0.40*amb*vec3(0.40,0.60,1.00)*occ;
        lin += 0.50*dom*vec3(0.40,0.60,1.00)*occ;
        lin += 0.50*bac*vec3(0.25,0.25,0.25)*occ;
        lin += 0.25*fre*vec3(1.00,1.00,1.00)*occ;
		col = col*lin;
		col += 10.00*spe*vec3(1.00,0.90,0.70);

    	col = mix( col, vec3(0.8,0.9,1.0), 1.0-exp( -0.0002*t*t*t ) );
    }

	return vec3( clamp(col,0.0,1.0) );
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 mo = iMouse.xy/iResolution.xy;
	float time = 15.0 + iTime;


    vec3 tot = vec3(0.0);
#if AA>1
    for( int m=0; m<AA; m++ )
    for( int n=0; n<AA; n++ )
    {
        // pixel coordinates
        vec2 o = vec2(float(m),float(n)) / float(AA) - 0.5;
        vec2 p = (-iResolution.xy + 2.0*(fragCoord+o))/iResolution.y;
#else
        vec2 p = (-iResolution.xy + 2.0*fragCoord)/iResolution.y;
#endif

		// camera
        float alpha = time*PI*2.*.1;
        alpha = smoothstep(0.,.5, fract(time*.2))*PI + smoothstep(.5,1., fract(time*.2))*PI;
        vec3 ro = vec3(0.,1.,0.) + 2.*vec3(cos(alpha), 1., sin(alpha));
        if(iMouse.z>0.)
        	ro = vec3(0.,1.,0.) + 2.*vec3(cos(mo.x*PI*2.)*cos((1.-mo.y)*.5*PI), sin((1.-mo.y)*.5*PI), sin(mo.x*PI*2.)*cos((1.-mo.y)*.5*PI));
        vec3 ta = vec3(0., 1., 0. );
        // camera-to-world transformation
        mat3 ca = setCamera( ro, ta, 0.0 );
        // ray direction
        vec3 rd = ca * normalize( vec3(p.xy,2.0) );

        rd = ca[2];
        ro += 1.5*(ca[0]*p.x+ca[1]*p.y);

        // render	
        vec3 col = render( ro, rd );

		// gamma
        col = pow( col, vec3(0.4545) );

        tot += col;
#if AA>1
    }
    tot /= float(AA*AA);
#endif

    fragColor = vec4( tot, 1.0 );
}
// --------[ Original ShaderToy ends here ]---------- //

void main(void)
{
    mainImage(fragColor, vec2(vTexCoord.x, 1.0 - vTexCoord.y) * uResolution);

    fragColor *= uColor;
    gl_FragDepth = uDepth;
}
