#version 450 core

// inputs
in  vec2 texCoord;
in	vec3 rayOrigin;			// projected onto near plane
in	vec3 rayDirection;		// normalize(g_origin - origin)

// outputs
layout (location = 0) out vec4 gAlbedo;
layout (location = 1) out vec2 gNormal;
layout (location = 2) out vec4 gProps;
layout (location = 3) out vec4 gWorldPos;

// samplers
uniform sampler2D	t_albedo;
uniform sampler2D	t_mra;

// uniforms
uniform mat4  m_Perspective;
uniform vec3  g_origin;
uniform float g_znear;

uniform vec2  g_resolution;

/*
void main() {
	gAlbedo		= vec4(texCoord, texCoord.x*texCoord.y, 1.0);
	gProps		= vec4(texCoord.x*texCoord.y, texCoord, 1.0);
	gNormal		= rayDirection.xy;
	gWorldPos	= vec4(rayOrigin, 1.0);
}
*/



#define MAX_STEPS 128
#define MAX_DIST  64.
#define EPS_RAYMARCH .0001

float opRep(in vec3 p, in vec3 c)
{
    vec3 q = mod(p+0.5*c,c)-0.5*c;
    return length(q) - 1.0;
}

float map(vec3 p) {
    return min(opRep(p, vec3(3.0, 0.0, 3.0)), dot(p - vec3(0., -0.4, 0.), vec3(0.0, 1.0, 0.0)));
}


mat3 lookAt(vec3 origin, vec3 target, float roll) {
  vec3 rr = vec3(sin(roll), cos(roll), 0.0);
  vec3 ww = normalize(target - origin);
  vec3 uu = normalize(cross(ww, rr));
  vec3 vv = normalize(cross(uu, ww));

  return mat3(uu, vv, ww);
}

#define NORMAL_EPS .001
vec3 getNormal(vec3 p) {
    vec2 v = vec2(NORMAL_EPS, 0.);
    float mp = map(p);
    return normalize(vec3(
        map(p + v.xyy) - mp,
        map(p + v.yxy) - mp,
        map(p + v.yyx) - mp
        ));
}

float raymarch(vec3 o, vec3 d) {
    float t = 0.;
    
    for (int i = 0; i < MAX_STEPS; i++) {
        vec3 p = o + d*t;
        float ct = map(p);
        if (abs(ct) < EPS_RAYMARCH || t > MAX_DIST) break;
        t += ct;
    }
    
    return t;
}

float softshadow( in vec3 ro, in vec3 rd, float mint, float maxt, float k )
{
    float res = 1.0;
    for( float t=mint; t<maxt; )
    {
        float h = map(ro + rd*t);
        if( h<0.001 )
            return 0.0;
        res = min( res, k*h/t );
        t += h;
    }
    return res;
}

float blinnPhong(vec3 v, vec3 p, vec3 l, vec3 n) {
    float ka = 0.1, kd = 0.6, ks = 1. - ka - kd;
    float k = 200.0;
    
    vec3 lightDir   = normalize(l - p);
    vec3 viewDir    = normalize(v - p);
    vec3 halfwayDir = normalize(lightDir + viewDir);
    
    float amb = 1.;
    float dif = max(dot(lightDir, n), 0.0) * softshadow(p+0.0*n, l, 0.1, 40., 2.);
    float spec = max(pow(dot(n, halfwayDir), k), 0.0);
    
    float color =
        ka * amb +
        kd * dif +
        ks * spec;
    return color;
}


float calcAO( in vec3 pos, in vec3 nor )
{
	float occ = 0.0;
    float sca = 1.0;
    for( int i=0; i<5; i++ )
    {
        float h = 0.001 + 0.15*float(i)/4.0;
        float d = map( pos + h*nor );
        occ += (h-d)*sca;
        sca *= 0.95;
    }
    return clamp( 1.0 - 1.5*occ, 0.0, 1.0 );    
}


vec3 triplanar(sampler2D tex, vec3 p, vec3 n) {
    vec3 tex_xy = texture(tex, p.xy).rgb;
    vec3 tex_xz = texture(tex, p.xz).rgb;
    vec3 tex_yz = texture(tex, p.yz).rgb;
    
    vec3 an = pow(abs(n), vec3(1.0));
    vec3 w = (an/(an.x+an.y+an.z));
    
    return (tex_xy * w.z + tex_xz * w.y + tex_yz * w.x);
}

void main()
{
    vec2 uv = (2.*texCoord-1) * vec2(g_resolution.x / g_resolution.y, 1.); // uniform rendertarget coords
    vec3 ray = normalize(vec3(uv, 1.));

    vec3 origin = g_origin;
    vec3 target = vec3(0., 0., 0.);
    mat3 m = lookAt(origin, target, 0.);
    ray = m*ray;
    
    float t = raymarch(origin, ray);
    vec3 p = origin + t*ray;
    vec3 n = getNormal(p);
    vec3 l = vec3(0., 5.0, 1.5);
    
    vec3 color = vec3(0.0);
    if (t < MAX_DIST) {
        // albedo
        //gAlbedo = vec4(texture(t_albedo, p.xz*0.2).rgb, 1.0);
        gAlbedo = vec4(triplanar(t_albedo, p*0.2, n), 1.0);
        // normals
        gNormal = n.xy;
        // ao
        gProps = vec4(calcAO(p, n));
        // rough (kinda)
        //color = triplanar(t_mra, p*0.2, n).rgb;
        // light
        //color = vec4(triplanar(t_albedo, p*0.2, n) * blinnPhong(origin, p, l, n), 1.0);
    } else {
        gAlbedo = vec4(0.0);
        gNormal = vec2(0.0);
        gProps = vec4(0.0);
    }

    gWorldPos	= vec4(p - origin, 1.0);        // precision fixup
    gl_FragDepth = (m_Perspective[3][2] / (p-origin).z) + m_Perspective[2][2];
}

