#define MAX_STEPS 64
#define MAX_DIST 20.
#define EPS 0.001
const float PI = acos(-1.0);

mat3 rotX(float angle)
{
    return mat3(1,	0,			0,
               0,	cos(angle), -sin(angle),
               0,	sin(angle), cos(angle));
}

mat3 rotY(float angle)
{
	return mat3(cos(angle), 0, sin(angle),
               0,			1, 0,
               -sin(angle), 0, cos(angle));
}

mat3 rotZ(float angle)
{
	return mat3(cos(angle), -sin(angle), 0,
               sin(angle), 	cos(angle),	 0,
               0,		 	0,			 1);
}

vec2 opRepLim( in vec2 p, in float s, in vec2 lima, in vec2 limb )
{
    return p-s*clamp(round(p/s),lima,limb);
}


float sdCube(vec2 pos, float r)
{
	vec2 q = abs(pos) - r;
	return length(max(q,0.0)) + min(max(q.x,q.y),0.0);
}

float sdCube(vec3 pos, vec3 r)
{
	vec3 q = abs(pos) - r;
	return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
}

float plane(vec3 pos)
{
    pos.z = mod((pos.z + iTime*0.2), 0.5);
    pos.xz = opRepLim(pos.xz, 0.5, vec2(-1., -1), vec2(1., 1.0));

    return sdCube(pos, vec3(0.125,0.02,0.125));
}

float map(vec3 pos)
{
    vec3 p = vec3(0);
    vec2 rot = vec2(PI/4., PI/2.);

    p = pos;
    float d1 = max(p.z-0.65, plane(p));

    p = pos;
    p *= rotY(rot.x);
    p *= rotZ(rot.y);
    p *= rotY(rot.x);
    float d2 = max(p.z-0.65, plane(p));

    p = pos;
    p *= rotY(-rot.x);
    p *= rotZ(-rot.y);
    p *= rotY(-rot.x);
    float d3 = max(p.z-0.65, plane(p));

    float res = MAX_DIST;
    res = min(res, d1);
    res = min(res, d2);
    res = min(res, d3);
    return res;
}

vec3 normals(vec3 pos)
{
    vec2 e = vec2(0, 0.01);
    return normalize(vec3(
    	map(pos+e.yxx)-map(pos-e.yxx),
        map(pos+e.xyx)-map(pos-e.xyx),
        map(pos+e.xxy)-map(pos-e.xxy)
    ));
}

const vec4 miss = vec4(MAX_DIST, vec3(0));
vec4 raymarch(vec3 ro, vec3 rd)
{
    float t = 0.0;
    for(int i = 0; i<MAX_STEPS; i++)
    {
        vec3 pos = ro + rd*t;
        float d = map(pos);

        if(abs(d)<=EPS) return vec4(length(pos-ro), normals(pos));
        if(d >= MAX_DIST) return miss;

        t+=d;
    }


    return miss;
}

const vec3 BACK_GROUND = vec3(0.2);
vec3 GetScene(vec3 ro, vec3 rd, vec3 lightDir)
{

    vec4 a = raymarch(ro, rd);
    float d = a.x;
    vec3 nor = a.yzw;

    vec3 col = BACK_GROUND;

    if(d < MAX_DIST)
    {
        float li = dot(lightDir, nor);

        vec3 pos = ro+rd*d;
        pos *= rotX(0.9);

        float lines = sin(pos.y * 300.) * 0.5+0.5;
        lines = smoothstep(li+0.25, li-0.25, lines);
        lines = lines*0.7+0.3;

        col = vec3(1, 0.94, 0.8) * lines;
        col = mix(col, BACK_GROUND, smoothstep(1., 10., d)); // fog
    }

    return col;

}

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 =          ( cross(cu,cw) );
    return mat3( cu, cv, cw );
}

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

    vec2 mouse = iMouse.xy / iResolution.xy;
    float camAngle = PI/2.;

    vec2 p = 1.3 * vec2(cos(camAngle), sin(camAngle));


    vec3 ro = vec3(p.x,0.915,p.y);
    mat3 ca = setCamera(ro, vec3(0,0.2,0), 0.0);
    vec3 rd = ca*normalize(vec3(uv,0.9));

    vec3 ld = normalize(vec3(1, 0.5, -0.5));

    vec3 col = GetScene(ro, rd, ld);

    float v = dot(uv,uv);
    col = mix(col, vec3(0.4), pow(v,2.));

    // Output to screen
    fragColor = vec4(col,1.0);
}