//
// (c)MX^Addict
//

#version 430

///////////////////////////////////////////////////////////////////////////////
// Definitions

#define sat(x) clamp(x, 0., 1.)

///////////////////////////////////////////////////////////////////////////////
// Uniforms

out     vec4 o;   // Output
uniform ivec4 tm; // x - # samples since start, y - Beat [0...200], z - Slow beat history [0...200], w - Sum of beats & 0xFFFF
layout (binding = 0) uniform sampler2D sm;
layout (binding = 1) uniform sampler2D sn;

///////////////////////////////////////////////////////////////////////////////
// Globals

vec2  i_Resolution = vec2(1280.0, 720.0);
float i_MPI		   = 3.14159265359; // acos(-1.0)
float i_HDRFactor  = 2.5;
float i_PBREps	   = 0.003;
float i_PBRNEps    = 0.02;
float i_PBRPMul    = 0.33;
float i_PBRFar     = 386.0;
int   i_PBRSteps   = 256;

vec3  g_Haze;
vec3  g_PBRMainLD;
vec3  g_RayOrg;
vec3  g_RayDir;
vec3  g_CamDir;

vec3  g_SrfPos;
vec3  g_SrfNrm;
vec3  g_SrfDiffuse;
vec3  g_SrfSpecular;
float g_SrfRoughness;

float g_CoCNear;
float g_CoCDst;
int   g_ScnCtrl;			  // Scene controll

float iTime = tm.x / 44100.0; // Time ctrl

bool isSceneCtrl(int v)
{
	return (g_ScnCtrl & v) != 0;
}

///////////////////////////////////////////////////////////////////////////////
// Common functions

float Pow2(float x)
{
	return x * x;
}

/*
vec2 Pow2(vec2 x)
{
	return x * x;
}
*/
/*
vec3 Pow2(vec3 x)
{
	return x * x;
}
*/
/*
vec4 Pow2(vec4 x)
{
	return x * x;
}
*/
/*
float Dot2(vec3 x)
{
	return dot(x, x);
}
*/
/*
float Pow4(float x)
{
    float  xx = x * x;
    return xx * xx;
}
*/

float Pow5(float x)
{
	float  xx = x * x;
	return xx * xx * x;
}

// 2D ramp
float Ramp(float y, float start, float end)
{
	float inside = step(start, y) - step(end, y);
	return (1.0 - ((y - start) / (end-start) * inside)) * inside;
}

// Angle between two unit length vectors (-PI - PI)
float AngleBetweenVectors(vec2 u, vec2 v)
{
	return atan(u.x*v.y - u.y*v.x, dot(u, v));
}

/*
float smin(float a, float b, float k) 
{
	float h = sat(0.5 + 0.5 * (b - a) / k);
	return mix(b, a, h) - k * h * (1.0 - h);
}
*/
/*
float sminexp(float a, float b, float k)
{
	k = 3.0 / k;
	float res = exp2(-k * a) + exp2(-k * b);
	return -log2(res) / k;
}
*/
/*
float smax(float a, float b, float k)
{
    float h = sat(0.5 + 0.5*(a - b) / k);
    return mix(b, a, h) + h * (1.0 - h) * k;
}
*/
/*
vec2 cmul(vec2 a, vec2 b)  
{ 
    return vec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x); 
}
*/
/*
vec2 csqr(vec2 a)  
{ 
    return vec2(a.x*a.x - a.y*a.y, 2.*a.x*a.y); 
}
*/
/*
float sabs(float x, float k) 
{
	return sqrt(x*x+k);
}
*/

///////////////////////////////////////////////////////////////////////////////
// Common SDF shapes

/*
float sdLine(vec3 p, vec3 a, vec3 b)
{
	vec3 pa = p-a, ba = b-a;
	float  h  = sat(dot(pa, ba) / dot(ba, ba));
	return length(pa - ba*h);
}
*/
/*
float sdTriangle(vec3 p, vec3 v1,  vec3 v2,  vec3 v3)
{
    vec3 v21 = v2 - v1; vec3 p1 = p - v1;
    vec3 v32 = v3 - v2; vec3 p2 = p - v2;
    vec3 v13 = v1 - v3; vec3 p3 = p - v3;
    vec3 nor = cross(v21, v13);

	return sqrt((sign(dot(cross(v21, nor), p1)) + sign(dot(cross(v32, nor), p2)) + sign(dot(cross(v13, nor), p3)) < 2.0) ? min(min(Dot2(v21 * clamp(dot(v21, p1) / Dot2(v21), 0.0, 1.0) - p1), Dot2(v32 * clamp(dot(v32, p2) / Dot2(v32), 0.0, 1.0) - p2)), Dot2(v13 * clamp(dot(v13, p3) / Dot2(v13), 0.0, 1.0) - p3)) : dot(nor, p1) * dot(nor, p1) / Dot2(nor));
}	
*/

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

float sdBox(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);
}

/*
// Plane from normal & distance
float sdPlane(vec3 p, vec3 n, float h)
{
	return dot(p, n) + h;
}
*/

// Plane from 3 points
float sdPlane(vec3 p, vec3 p0, vec3 p1, vec3 p2)
{
	return dot(p - p0, normalize(cross(p0 - p1, p0 - p2)));
}

// Quad on XY with height defined by 4 values & size defined by s
float sdVQuad(vec3 p, float h0, float h1, float h2, float h3, float s)
{
	float diag = sdPlane(p, vec3(0), vec3(s, s, 0), vec3(0, 0, s));
	return min(max(diag, sdPlane(p, vec3(0, 0, -h0), vec3(0, s, -h1), vec3(s, s, -h2))), max(-diag, sdPlane(p, vec3(0, 0, -h0), vec3(s, s, -h2), vec3(s, 0, -h3))));
}

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

float sdTorus(vec3 p, float r, float h)
{
	return length(vec2(length(p.yz) - h, p.x)) - r;
}

/*
float sdEllipsoid(vec3 p, vec3 r)
{
  return (length(p / r) - 1.0) * min(min(r.x, r.y), r.z);
}
*/

float sdDisc(vec3 p, vec2 rt)
{
	float  l = length(p.xz) - rt.x;
	return l < 0. ? abs(p.y) - rt.y : length(vec2(p.y, l)) - rt.y;
}


float sdCylinder(vec3 p, vec2 hr)
{
	vec2 d = abs(vec2(length(p.xz), p.y)) - hr;
	return min(max(d.x, d.y), 0.) + length(max(d, 0.));
}

/*
float sdCylinderSide(vec3 p, vec2 hr)
{
	vec2 d = vec2(length(p.xy) - hr.y, abs(p.z) - hr.x * 0.5);
	return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}
*/
/*
float sdHexPrism(vec3 p, vec2 hr)
{
	vec3 q = abs(p);
	return max(q.z - hr.y, max((q.x * 0.866025 + q.y * 0.5), q.y) - hr.x);
}
*/
/*
float sdOctahedron(vec3 p, float s)
{
	vec3 q = abs(p);
	return(q.x+q.y+q.z-s) * 0.57735027;
}
*/
/*
float sdAxisAlignedRect(vec2 uv, vec2 tl, vec2 br)
{
	vec2 d = max(tl - uv, uv - br);
	return length(max(vec2(0.0, 0.0), d)) + min(0.0, max(d.x, d.y));
}
*/
/*
float sdAxisAlignedRectManhattan(vec2 uv, vec2 tl, vec2 br)
{
	vec2 d = max(tl - uv, uv - br);
	vec2 dif = vec2(max(vec2(0.0, 0.0), d)) + min(0.0, max(d.x, d.y));
	return max(dif.x, dif.y);
}
*/
/*
float sdCircle(vec2 p, float s)
{
	return length(p) - s;
}
*/
/*
float sdRectangle(vec2 p, vec2 b)
{
	vec2 d = abs(p) - b;
	return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}
*/

float sdSemiArc(vec2 uv, vec2 p, float rma, float rmi, float dr)
{
    return sat(smoothstep(0.005, -0.005, max(abs(length(uv - p) - rma) - rmi, (uv.x - p.x) * dr)));
}

float sdECrc(vec2 uv, float px, float py, float r)
{
	vec3 p = vec3(uv.x+px, uv.y+py, 0);
    return sat(sat(smoothstep(0.005, -0.005, sdSphere(p, r+0.03)))-sat(smoothstep(0.005, -0.005, sdSphere(p, r-0.03))));
}

float sdQuad(vec2 uv, vec2 p0, vec2 p1, vec2 p2, vec2 p3)
{
    return sat(smoothstep(0.005, -0.005, max(max(dot(uv-p0,normalize((p1 - p0).yx * vec2(-1, 1))),dot(uv-p1,normalize((p2 - p1).yx * vec2(-1, 1)))), max(dot(uv-p2,normalize((p3 - p2).yx * vec2(-1, 1))),dot(uv-p3,normalize((p0 - p3).yx * vec2(-1, 1)))))));
}

/*
float sdSmpCapsule(vec3 p, float h, float r)
{
	p.x -= clamp(p.x, 0.0, h);
	return length(p) - r;
}
*/

float sdCapsule(vec3 p, vec3 a, vec3 b, float r)
{
	vec3 pa = p - a, ba = b - a;
	return length(pa - ba * (sat(dot(pa, ba) / dot(ba, ba)))) - r;
}

/*
float sdCone(vec3 p, vec3 c)
{
	vec2 q = vec2(length(p.xz), p.y);
	float d1 = -q.y - c.z;
	float d2 = max(dot(q, c.xy), q.y);
	return length(max(vec2(d1, d2), 0.0)) + min(max(d1, d2), 0.0);
}
*/
/*
float sdRoundCone(vec3 p, float r1, float r2, float h)
{
	vec2 q = vec2(length(p.xz), p.y);
    
	float b = (r1 - r2) / h;
	float a = sqrt(1.0 - b * b);
	float k = dot(q, vec2(-b, a));
    
	if (k < 0.0)
		return length(q) - r1;
	
	if (k > a * h)
		return length(q - vec2(0.0, h)) - r2;
        
	return dot(q, vec2(a, b)) - r1;
}
*/

///////////////////////////////////////////////////////////////////////////////
// Common SDF operations

// SDF smooth subtract
float sdSmoothSubtraction(float d1, float d2, float k)
{
	float h = sat(0.5 - 0.5 * (d2 + d1) / k);
	return mix(d2, -d1, h) + k * h * (1.0 - h);
}

/*
// SDF smooth intersection	
float sdSmoothIntersection(float d1, float d2, float k)
{
	float h = sat(0.5 - 0.5 * (d2 - d1) / k);
	return mix(d2, d1, h) + k * h * (1.0 - h);
}
*/

// SDF smooth union
float sdSmoothUnion(float d1, float d2, float k)
{
	float h = sat(0.5 + 0.5 * (d2 - d1) / k);
	return mix(d2, d1, h) - k * h * (1.0 - h);
}

/*
// SDF smooth union with material
vec2 sdSmoothUnion(vec2 d1, vec2 d2, float k)
{
	float h = sat(0.5 + 0.5 * (d2.x - d1.x) / k);
	return vec2(mix(d2.x, d1.x, h) - k * h * (1.0 - h), (d1.x < d2.x) ? d1.y : d2.y);
}
*/

/*
// SDF union with material
vec2 opUnion(vec2 r, float sdf, float m)
{
	return (r.x < sdf) ? r : vec2(sdf, m);
}
*/

// SDF union with material
vec2 opUnion(vec2 r1, vec2 r2)
{
	return (r1.x < r2.x) ? r1 : r2;
}

/*
// SDF subtract with material
vec2 opSubtract(vec2 r, float sdf, float m)
{
	return (-sdf > r.x) ? vec2(-sdf, m) : r;
}
*/

///////////////////////////////////////////////////////////////////////////////
// Hashes

float hash11(float x)
{
	return fract(sin(x) * 43758.5453);
}

/*
float hash12(vec2 x)
{
 	return fract(sin(dot(x, vec2(42.2347, 43.4271))) * 342.324234);   
}
*/

vec2 hash22(vec2 x)
{
 	return fract(sin(x * mat2x2(23.4217, 24.4217, 25.3271, 27.2412)) * 342.3242);   
}

/*
float hash21(vec2 x)
{ 
    return fract(sin(dot(x, vec2(41.232, 289.453))) * 43758.5453); 
}
*/
/*
float hash31(vec3 x)
{ 
    return fract(sin(dot(x, vec3(157.345, 113.234, 7.132))) * 43758.5453); 
}
*/
/*
vec2 hash23(vec3 p3)
{
	p3  = fract(p3 * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yzx+33.33);
    return fract((p3.xx+p3.yz)*p3.zy);
}
*/

vec3 hash32(vec2 p)
{
	return fract(sin(vec3(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)), dot(p, vec2(419.2, 371.9)))) * 43758.5453);
}

/*
vec3 hash33(vec3 x)
{
 	return fract(sin(x * mat3x3(23.421, 24.4217, 25.3271, 27.2412, 32.21731, 21.27641, 20.421, 27.4217, 22.3271)) * 342.324234);   
}
*/
/*
vec4 hash44(vec4 p) 
{ 
	p = fract(p * 0.1031); 
    p*= p + 3.3456; 
	return fract(p * (p + p)); 
}
*/

///////////////////////////////////////////////////////////////////////////////
// Noises

/*
float noise3d(vec3 p) 
{
	vec3 s  = vec3(7, 157, 113);
	vec3 ip = floor(p);
		 p  = fract(p);
		 p  = p * p * (3. - 2. * p);
	vec4 h  = vec4(0, s.yz, s.y + s.z) + dot(ip, s);
	
	h    = mix(hash44(h), hash44(h + s.x), p.x);
	h.xy = mix(h.xz, h.yw, p.y);
	return mix(h.x, h.y, p.z);
}
*/

/*
float noise3d2(vec3 x) 
{
	vec3 p = floor(x);
	vec3 f = fract(x);
		 f = f * f * (3.0 - 2.0 * f);

	float n = p.x + p.y * 157.0 + 113.0 * p.z;
	return mix(mix(mix(hash11(n + 0.0), hash11(n + 1.0), f.x), mix(hash11(n + 157.0), hash11(n + 158.0), f.x), f.y), mix(mix(hash11(n + 113.0), hash11(n + 114.0), f.x), mix(hash11(n + 270.0), hash11(n + 271.0), f.x), f.y), f.z);
}
*/

float noise2d(vec2 p) 
{
	vec2 i = floor(p); 
         p-= i; 
         p*= p*(3.0 - p*2.0);
    
	return dot(mat2(fract(sin(vec4(0.0, 41.0, 289.0, 330.0) + dot(i, vec2(41.0, 289.0))) * 43758.5453)) * vec2(1.0 - p.y, p.y), vec2(1.0 - p.x, p.x));
}

/*
float iqNoise2d(vec2 P, float u, float v) // Range -1 ... 1
{
    vec2 p = floor(P);
    vec2 f = fract (P);
		
	float k = 1.0+63.0*pow(1.0-v,4.0);
	float va = 0.0;
	float wt = 0.0;
	
    for (int j=-2; j<=2; j++)
	{
		for (int i = -2; i <= 2; i++)
		{
			vec2 g = vec2(float(i), float(j));
			vec3 o = hash32(p + g) * vec3(u, u, 1.0);
			vec2 r = g - f + o.xy;
			float  d = dot(r, r);
			float ww = pow(1.0 - smoothstep(0.0, 1.414, sqrt(d)), k);
			va += o.z * ww;
			wt += ww;
		}
	}
	
    return (va/wt) * 2.0 - 1.0;
}
*/
/*
float voronoiNoise2d(vec2 UV, int pointCount, float seed, float squareWidth, float distanceBlend) // Range -1 ... 1
{
    float col     = 1.0;
    float minDist = 10.0;
	
    for (int i = 0; i < pointCount; i++)
    {
        float  f = float(i) * seed;
        vec4 r = vec4(hash11(f), hash11(f*1.2721), hash11(f*7.8273) * squareWidth, hash11(f*7.8273) * 0.9 + 0.1);        
        float  d = mix(sdAxisAlignedRect(UV, r.xy-r.zz, r.xy+r.zz), sdAxisAlignedRectManhattan(UV, r.xy-r.zz, r.xy+r.zz), distanceBlend);
        
        if (d < minDist)
        {
        	minDist = d;
            col     = r.w;
        }
    }        

    return col * 2.0 - 1.0;
}
*/
/*
vec3 triNoise(vec3 x)
{
	return abs(fract(x)-0.5);
}
*/
/*
// Worley pattern [0.0 - 1.0]
float worleyPattern(vec2 n, float s)
{
    float dis = 2.0;
    for (int x = -1; x <= 1; x++)
    {
        for (int y = -1;y<=1;y++)
        {
            vec2  p = floor(n/s)+vec2(x,y);
            float d = length(hash22(p)+vec2(x,y)-fract(n/s));
            if (dis > d)
             	dis = d;   
        }
    }
	
    return 1.0 - dis;
}
*/
/*
// Triangle wave
float TriangleWave(float t)
{
	t = fract(t);
	return t < 0.5 ? (4 * t - 1) : (-4 * t + 3);
}
*/

///////////////////////////////////////////////////////////////////////////////
// Geometry basics

// Make 2x2 rotation matrix
mat2 r2(float a)
{ 
    float c = cos(a), s = sin(a); 
    return mat2(c, s, -s, c); 
}

// LookAt matrix
mat3 cameraLookAt(vec3 forward, vec3 up)
{
	vec3 xaxis = normalize(cross(forward, up));
	vec3 yaxis = cross(xaxis, forward);
	return transpose(mat3(xaxis, yaxis, cross(yaxis, xaxis)));
}

// Two bones IK solver (calculate B position for [0,0]->B->C(p) and r1==len(B-[0,0]), r2==len(C-B))
vec2 IKSolve2Bones(vec2 p, float r1, float r2, float fct)
{
	vec2  q = p * (0.5 + 0.5 * (r1 * r1 - r2 * r2) / dot(p,p));
	float s = r1 * r1 / dot(q, q) - 1.0;
    return s < 0.0 ? vec2(1000.0*fct) : (q + fct * vec2(-q.y, q.x) * sqrt(s));
}

/*
bool raySphereIntersect(vec3 org, vec3 dir, float r, out vec2 nf)
{
	float b = dot(dir, org);
	float c = dot(org, org) - (r*r);
	float delta = b*b - c;
	if (delta < 0.0) 
		return false;

	float deltasqrt = sqrt(delta);
	nf.x = -b - deltasqrt;
	nf.y  = -b + deltasqrt;
	return nf.y > 0.0;
}
*/
/*
bool rayBoxIntersect(vec3 org, vec3 dir, vec3 boxMin, vec3 boxMax, out vec2 nf)
{
    vec3 tMin = (boxMin - org) / dir;
    vec3 tMax = (boxMax - org) / dir;
    vec3 t1   = min(tMin, tMax);
    vec3 t2   = max(tMin, tMax);
		   nf   = vec2(max(max(t1.x, t1.y), t1.z), min(min(t2.x, t2.y), t2.z));
	return nf.x < nf.y && nf.y > 0.0;
}
*/
/*
float rayBoxIntersectDst(vec3 org, vec3 dir, vec3 boxMin, vec3 boxMax)
{
    vec3 tMin = (boxMin - org) / dir;
    vec3 tMax = (boxMax - org) / dir;
    vec3 t1   = min(tMin, tMax);
    vec3 t2   = max(tMin, tMax);
    vec2 nf   = vec2(max(max(t1.x, t1.y), t1.z), min(min(t2.x, t2.y), t2.z));
	if (nf.x < nf.y && nf.y > 0.0)
		return nf.y;
	return 1000000.0;
}
*/
/*
float rayCapsuleIntersect(vec3 org, vec3 dir, vec3 pa, vec3 pb, float r)
{
    vec3  ba = pb  - pa;
    vec3  oa = org - pa;

    float baba = dot(ba,ba);
    float bard = dot(ba,dir);
    float baoa = dot(ba,oa);
    float rdoa = dot(dir,oa);
    float oaoa = dot(oa,oa);

    float a = baba      - bard*bard;
    float b = baba*rdoa - baoa*bard;
    float c = baba*oaoa - baoa*baoa - r*r*baba;
    float h = b*b - a*c;
	
    if (h >= 0.0)
    {
        float t = (-b-sqrt(h))/a;
        float y = baoa + t*bard;

        if (y > 0.0 && y < baba) 
			return t;
        
        vec3 oc = (y <= 0.0) ? oa : org - pb;
		
        b = dot(dir,oc);
        c = dot(oc,oc) - r*r;
        h = b*b - c;
		
        if (h > 0.0) 
			return -b - sqrt(h);
    }
	
    return -1.0;
}
*/

///////////////////////////////////////////////////////////////////////////////
// Color operations

/*
vec3 toneMapFilmicALU(vec3 color)
{
	color = max(vec3(0), color - vec3(0.001));
	color = (color * (6.2*color + vec3(0.5))) / (color * (6.2 * color + vec3(1.7)) + vec3(0.06));
	return color;
}
*/

/*
vec3 toneMapFilmicHejl2015(vec3 hdr, float whitePt) 
{
    vec4 vh = vec4(hdr, whitePt);
    vec4 va = 1.425 * vh + 0.05;
    vec4 vf = (vh * va + 0.004) / (vh * (va + 0.55) + 0.0491) - 0.0821;
    return (vf / vf.w).xyz;
}
*/

vec3 toneMapFilmicHejl2015W1(vec4 vh)
{
    vec4 va = 1.425 * vh + 0.05;
    vec4 vf = (vh * va + 0.004) / (vh * (va + 0.55) + 0.0491) - 0.0821;
    return sqrt((vf / vf.w).xyz);
}

///////////////////////////////////////////////////////////////////////////////

/*
// Christian Schuler, "Normal Mapping without Precomputed Tangents", ShaderX 5, Chapter 2.6, pp. 131-140
// See also follow-up blog post: http://www.thetenthplanet.de/archives/1180
mat3 calculateTBN(vec3 p, vec3 n, vec2 tex)
{
    vec3 dp1  = dFdx(p);
    vec3 dp2  = dFdy(p);
    vec2 duv1 = dFdx(tex);
    vec2 duv2 = dFdy(tex);

    mat3 M = mat3(dp1, dp2, cross(dp1, dp2));
    mat2x3 inverseM = mat2x3(cross(M[1], M[2]), cross(M[2], M[0]));

    vec3 t = normalize(inverseM * vec2(duv1.x, duv2.x));
    vec3 b = normalize(inverseM * vec2(duv1.y, duv2.y));

    return mat3(t, b, n);
}
*/
/*
vec3 peturbNormal(vec3 localNormal, vec3 position, vec3 normal, vec2 texCoord)
{
    return normalize(calculateTBN(position, normal, texCoord) * localNormal);
}
*/

///////////////////////////////////////////////////////////////////////////////
// Lighting functions

/*
// [GGX / Trowbridge-Reitz]
float fDGGX(float NoH)
{
	float  a2 = Pow2(Pow2(g_SrfRoughness));
	return a2 / (i_MPI * Pow2((NoH * a2 - NoH) * NoH + 1.0));
}
*/

/*
// For area lights (sphere, capsule)
float fTrowbridgeReitz(float HoN, float a, float aP)
{
	float a2  = Pow2(a);
	float aP2 = Pow2(aP);
	return (a2 * aP2) / Pow2(HoN * HoN * (a2 - 1.0) + 1.0);
}
*/

/*
// For area lights (rect)
float fTrowbridgeReitzRect(float HoN, float a, float aP)
{
    float a2  = Pow2(a);
    float a4  = Pow2(a2);
    float aP3 = aP * aP * aP;
    return (a2 * aP3) / Pow2(HoN * HoN * (a2 - 1.0) + 1.0);
}
*/

///////////////////////////////////////////////////////////////////////////////
// Visibility models
//

/*
// No visibility approximation
float fVisNone()
{
	return 0.25;
}
*/

/*
// Appoximation of joint Smith term for GGX
// [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
float fVisSmithJointApprox(float NoV, float NoL)
{
    return 0.5 * (1.0/(max(0.005, (NoL * (NoV * (1.0 - g_SrfRoughness) + g_SrfRoughness)) + (NoV * (NoL * (1.0 - g_SrfRoughness) + g_SrfRoughness)))));
}
*/

/*
// Tuned to match behavior of Vis_Smith
// [Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"]
float fVisSchlick(float NoV, float NoL)
{
	float k = g_SrfRoughness * 0.5;
	return 0.25 / ((NoV * (1.0 - k) + k) * (NoL * (1.0 - k) + k));
}
*/

/*
// Schlick-Beckmann vis
float fVisSchlickBeckmann(float NoV, float NoL)
{
 	float rp1 = g_SrfRoughness + 1.0;
    float k   = rp1 * rp1 / 8.0;
    
   return (NoV / max(NoV * (1.0 - k) + k, 0.000001)) * (NoL / max(NoL * (1.0 - k) + k, 0.000001));
}
*/
///////////////////////////////////////////////////////////////////////////////
// Reflection models

/*
// No reflections modification
vec3 fRefNone(vec3 SpecularColor)
{
	return g_SrfSpecular;
}
*/

/*
// [Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"]
vec3 fRefSchlick(float VoH)
{
	float Fc = Pow5(1.0 - VoH);
	return sat(50.0 * g_SrfSpecular) * Fc + (1.0 - Fc) * g_SrfSpecular;
}
*/

/*
// [Hecht 1982]
vec3 fRefFresnel(float VoH)
{
	vec3 SpecularColorSqrt = sqrt(clamp(g_SrfSpecular, vec3(0), vec3(0.99)));
	vec3 n = (1.0 + SpecularColorSqrt) / (1.0 - SpecularColorSqrt);
	vec3 g = sqrt(n*n + VoH*VoH - 1.0);
	return 0.5 * Pow2((g - VoH) / (g + VoH)) * (1.0 + Pow2(((g+VoH)*VoH - 1.0) / ((g-VoH)*VoH + 1.0)));
}
*/

///////////////////////////////////////////////////////////////////////////////
// Diffuse lighting models

/*
// [Lambert]
vec3 diffuseLambert()
{
	return g_SrfDiffuse * (1.0 / i_MPI);
}
*/

/*
// [Burley 2012, "Physically-Based Shading at Disney"]
vec3 diffuseBurley(float NoV, float NoL, float VoH)
{
	float FD90 = 0.5 + 2.0 * VoH * VoH * g_SrfRoughness;
	return g_SrfDiffuse * ((1.0 / i_MPI) * (1.0 + (FD90 - 1.0) * Pow5(1.0 - NoV)) * (1.0 + (FD90 - 1.0) * Pow5(1.0 - NoL)));
}
*/

/*
// [Gotanda 2012, "Beyond a Simple Physically Based Blinn-Phong Model in Real-Time"]
vec3 diffuseOrenNayar(float NoV, float NoL, float VoH)
{
	float s2    = Pow2(Pow2(g_SrfRoughness));
	float Cosri = (2.0 * VoH * VoH - 1.0) - NoV * NoL;
	return g_SrfDiffuse / i_MPI * ((1.0 - 0.5 * s2 / (s2 + 0.33)) + (0.45 * s2 / (s2 + 0.09) * Cosri * (Cosri >= 0.0 ? (1.0/max(NoL, NoV)) : 1.0))) * (1.0 + g_SrfRoughness * 0.5);
}
*/

// [Lazarov 2013, "Getting More Physical in Call of Duty: Black Ops II"]
vec3 envBRDFApprox(float NoV)
{
	vec4 i_c0 = vec4(-1, -0.0275, -0.572,  0.022);
	vec4 i_c1 = vec4( 1,  0.0425,  1.040, -0.040);

	vec4  r   = g_SrfRoughness * i_c0 + i_c1;
	vec2  AB  = vec2(-1.04, 1.04) * (min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y) + r.zw;

	return g_SrfSpecular * AB.x + (AB.y * sat(50.0 * g_SrfSpecular));
}

///////////////////////////////////////////////////////////////////////////////

/*
// PDF = D * NoH / (4 * VoH)
vec4 importanceSampleGGX(vec2 E, float a2)
{
    float  Phi	    = 2.0 * i_MPI * E.x;
    float  CosTheta = sqrt((1.0 - E.y) / (1.0 + (a2 - 1.0) * E.y));
    float  SinTheta = sqrt(1.0 - CosTheta * CosTheta);
	
    vec3   H   = vec3(SinTheta * cos(Phi), SinTheta * sin(Phi), CosTheta);
    float  d   = (CosTheta * a2 - CosTheta) * CosTheta + 1.0;
    float  D   = a2 / (i_MPI * d * d);
    float  PDF = D * CosTheta;

    return vec4(H, PDF);
}
*/

///////////////////////////////////////////////////////////////////////////////
// Radiance models

/*
// Calculates radial attenuation of light (physicall model only allows FalloffExponent = 2)
float radialAttenuation(vec3 WorldLightVector, float FalloffExponent)
{
	return pow(1.0 - sat(dot(WorldLightVector, WorldLightVector)), FalloffExponent); 
}
*/

///////////////////////////////////////////////////////////////////////////////
// Samples distribution

/*
// Returns a random direction for sampling a GGX distribution (in tangent space (u1, u2))
vec3 sampleDirectionGGX(vec3 v, vec3 n, float roughness, float u1, float u2)
{
    float theta = atan(roughness * sqrt(u1), sqrt(1.0 - u1));
    float phi   = 2.0 * i_MPI * u2;
    vec3 h      = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));

    return normalize(2.0 * dot(h, v) * h - v);
}
*/

// Returns a random direction on the unit sphere
vec3 sampleDirectionSphere(vec2 u)
{
    float z = u.x * 2.0 - 1.0;
    float r = sqrt(max(0.0f, 1.0 - z * z));
    float phi = 2.0 * i_MPI * u.y;
    return vec3(r * cos(phi), r * sin(phi), z);
}

/*
// Returns a random direction on the hemisphere around z = 1
vec3 sampleDirectionHemisphere(vec2 u)
{
    float z = u.x;
    float r = sqrt(max(0.0, 1.0 - z * z));
    float phi = 2.0 * i_MPI * u.y;
    float x = r * cos(phi);
    float y = r * sin(phi);

    return vec3(x, y, z);
}
*/

///////////////////////////////////////////////////////////////////////////////
// Shading models

/*
// Calculates simple labertian shading
vec3 simpleShading(vec3 L)
{
	vec3   H   = normalize(-g_RayDir + L);
	float  NoV = sat(abs(dot(g_SrfNrm, -g_RayDir)) + 1e-5);
	float  NoL = dot(g_SrfNrm, L);
	float  LoV = dot(L, -g_RayDir);
	float  InvLenH = inversesqrt(2.0 + 2.0 * LoV );
	float  NoH = sat(( NoL + NoV ) * InvLenH);
	float  VoH = sat(InvLenH + InvLenH * LoV);

	return diffuseLambert() + (fDGGX(NoH) * fVisSchlickBeckmann(NoV, NoL)) * fRefSchlick(VoH);
}
*/

/*
// Advanced lighting (OrenNayar)
vec3 orenNayarShading(vec3 L)
{
	vec3   H   = normalize(-g_RayDir + L);
	float  NoV = sat(abs(dot(g_SrfNrm, -g_RayDir)) + 1e-5);
	float  NoL = dot(g_SrfNrm, L);
	float  LoV = dot(L, -g_RayDir);
	float  InvLenH = inversesqrt(2.0 + 2.0 * LoV );
	float  NoH = sat(( NoL + NoV ) * InvLenH);
	float  VoH = sat(InvLenH + InvLenH * LoV);

	return diffuseOrenNayar(NoV, NoL, VoH) + (fDGGX(NoH) * fVisSchlickBeckmann(NoV, NoL)) * fRefSchlick(VoH);
}
*/

/*
// Advanced lighting (Burley)
vec3 burleyShading(vec3 L)
{
	vec3   H   = normalize(-g_RayDir + L);
	float  NoV = sat(abs(dot(g_SrfNrm, -g_RayDir)) + 1e-5);
	float  NoL = dot(g_SrfNrm, L);
	float  LoV = dot(L, -g_RayDir);
	float  InvLenH = inversesqrt(2.0 + 2.0 * LoV);
	float  NoH = sat((NoL + NoV) * InvLenH);
	float  VoH = sat(InvLenH + InvLenH * LoV);

	return diffuseBurley(NoV, NoL, VoH) + (fDGGX(NoH) * fVisSchlickBeckmann(NoV, NoL)) * fRefSchlick(VoH);
}
*/

// Advanced lighting (Burley) combined, more compact form, can support simple area lights
vec3 burleyShadingCombined(vec3 L, float aP2)
{
	vec3   H   = normalize(-g_RayDir + L);
	float  NoV = sat(abs(dot(g_SrfNrm, -g_RayDir)) + 1e-5);
	float  NoL = dot(g_SrfNrm, L);
	float  LoV = dot(L, -g_RayDir);
	float  InvLenH = inversesqrt(2.0 + 2.0 * LoV);
	float  NoH = sat((NoL + NoV) * InvLenH);
	float  VoH = sat(InvLenH + InvLenH * LoV);

	float FD90 = 0.5 + 2.0 * VoH * VoH * g_SrfRoughness;
	float a2   = g_SrfRoughness * g_SrfRoughness * g_SrfRoughness * g_SrfRoughness;
	float k    = ((g_SrfRoughness + 1.0) * (g_SrfRoughness + 1.0)) / 8.0;
	float Fc   = Pow5(1.0 - VoH);

	vec3 Res   = sat(50.0 * g_SrfSpecular) * Fc + (1.0 - Fc) * g_SrfSpecular;														// Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"
		 Res  *= (a2 * aP2) / Pow2(NoH * NoH * (a2 - 1.0) + 1.0);																	// Trowbridge Reitz (can be used with aP2 == 1/PI for non-area lights)
		 Res  *= (NoV / max(NoV * (1.0 - k) + k, 0.0001)) * (NoL / max(NoL * (1.0 - k) + k, 0.0001));								// Schlick-Beckmann vis
	return Res + (g_SrfDiffuse * ((1.0 / i_MPI) * (1.0 + (FD90 - 1.0) * Pow5(1.0 - NoV)) * (1.0 + (FD90 - 1.0) * Pow5(1.0 - NoL))));// Diffuse - Burley
}

///////////////////////////////////////////////////////////////////////////////
// Lighting helper

void setPBRMat(vec3 Albedo, float Roughness, float Metallic, float Specular)
{
	g_SrfDiffuse  = Albedo - Albedo * Metallic;
	g_SrfRoughness= Roughness;
	g_SrfSpecular = mix(vec3(Specular * 0.08), Albedo, Metallic);
	return;
}

vec3 lightDirectional(vec3 color, vec3 l, float shadow)
{
	return color * shadow * sat((dot(g_SrfNrm, l) + 0.25) / 1.25) * burleyShadingCombined(l, 1.0 / i_MPI);
}

/*
vec3 lightLocal(vec3 color, vec3 pos, float radius, float shadow)
{
	// Quadratic attenuation
	vec3 vle = (pos - g_SrfPos) / radius;
	vec3 l   = normalize(pos - g_SrfPos);

	color   *= Pow2(1.0 - sat(dot(vle, vle))) * shadow; 

	// Early ret
	if (dot(color, vec3(1.)) < 0.0001)
		return color;
		
	return color * sat(dot(g_SrfNrm, l)) * burleyShadingCombined(l, 1.0 / i_MPI);
}
*/

vec3 lightLocalTube(vec3 color, vec3 poss, vec3 pose, float radius, float sourcer, float shadow)
{
	// Quadratic attenuation
	vec3 vle = (((poss + pose) * 0.5) - g_SrfPos) / radius;
	color *= Pow2(1.0 - sat(dot(vle, vle))) * shadow; 

	// Early ret
	if (dot(color, vec3(1.)) < 0.0001)
		return color;
		
	// Area light (tube)
    vec3  L0     = poss - g_SrfPos;
    vec3  L1     = pose - g_SrfPos;
    float distL0 = length(L0);
    float distL1 = length(L1);
    float NoL    = (2.0 * sat((dot(L0, g_SrfNrm) / (2.0 * distL0)) + (dot(L1, g_SrfNrm) / (2.0 * distL1)))) / (distL0 * distL1 + dot(L0, L1) + 2.0);
    float NoV    = sat(dot(g_SrfNrm, -g_RayDir));
    vec3  r      = reflect(g_RayDir, g_SrfNrm);
    vec3  Ld     = L1 - L0;
    float RoL0   = dot(r, L0);
    float RoLd   = dot(r, Ld);
    float L0oLd  = dot(L0, Ld);
    float distLd = length(Ld);

    vec3  closestPoint = L0 + Ld * sat((RoL0 * RoLd - L0oLd) / (distLd * distLd - RoLd * RoLd));
    vec3  centerToRay  = dot(closestPoint, r) * r - closestPoint;
		  closestPoint+= centerToRay * sat(sourcer / length(centerToRay));
    vec3  l            = normalize(closestPoint);

	return color * sat(dot(g_SrfNrm, l)) * burleyShadingCombined(l, Pow2(sat(sourcer / (length(closestPoint) * 2.0) + g_SrfRoughness * g_SrfRoughness)));
}	

///////////////////////////////////////////////////////////////////////////////
// Irradiance

/*
struct SHCoefficients 
{
    vec3 l00, l1m1, l10, l11, l2m2, l2m1, l20, l21, l22;
};

const SHCoefficients PBREnv = SHCoefficients
(
    vec3(...), ...
);

vec3 calcIrradiance(vec3 nor, SHCoefficients c)
{
    const float i_c1 = 0.429043;
    const float i_c2 = 0.511664;
    const float i_c3 = 0.743125;
    const float i_c4 = 0.886227;
    const float i_c5 = 0.247708;

    return (i_c1 * c.l22 * (nor.x * nor.x - nor.y * nor.y) + i_c3 * c.l20 * nor.z * nor.z + i_c4 * c.l00 - i_c5 * c.l20 + 2.0 * i_c1 * c.l2m2 * nor.x * nor.y + 2.0 * i_c1 * c.l21  * nor.x * nor.z + 2.0 * i_c1 * c.l2m1 * nor.y * nor.z + 2.0 * i_c2 * c.l11  * nor.x + 2.0 * i_c2 * c.l1m1 * nor.y + 2.0 * i_c2 * c.l10 * nor.z);
}

vec3 calcIrradiance(vec3 nor)
{
	return calcIrradiance(nor, PBREnv);
}
*/

vec3 calcIrradiance(vec3 nor)
{
    float i_c1 = 0.4290;
    float i_c2 = 0.5117;
    float i_c3 = 0.7431;
    float i_c4 = 0.8862;
    float i_c5 = 0.2477;

	// Grace (lowQ coefs)
    vec3 i_l00  = vec3( 0.7953,  0.4405,  0.5459);
    vec3 i_l1m1 = vec3( 0.3981,  0.3526,  0.6097);
    vec3 i_l10  = vec3(-0.3424, -0.1838, -0.2715);
    vec3 i_l11  = vec3(-0.2944, -0.0560,  0.0095);
    vec3 i_l2m2 = vec3(-0.1123, -0.0513, -0.1232);
    vec3 i_l2m1 = vec3(-0.2645, -0.2257, -0.4785);
    vec3 i_l20  = vec3(-0.1569, -0.0954, -0.1485);
    vec3 i_l21  = vec3( 0.5646,  0.2161,  0.1402);
    vec3 i_l22  = vec3( 0.2137, -0.0547, -0.3061);

	/*
	// Grace
    vec3 i_l00  = vec3( 0.7953949,  0.4405923,  0.5459412);
    vec3 i_l1m1 = vec3( 0.3981450,  0.3526911,  0.6097158);
    vec3 i_l10  = vec3(-0.3424573, -0.1838151, -0.2715583);
    vec3 i_l11  = vec3(-0.2944621, -0.0560606,  0.0095193);
    vec3 i_l2m2 = vec3(-0.1123051, -0.0513088, -0.1232869);
    vec3 i_l2m1 = vec3(-0.2645007, -0.2257996, -0.4785847);
    vec3 i_l20  = vec3(-0.1569444, -0.0954703, -0.1485053);
    vec3 i_l21  = vec3( 0.5646247,  0.2161586,  0.1402643);
    vec3 i_l22  = vec3( 0.2137442, -0.0547578, -0.3061700);
	*/

	/*
	// St. Peter
    vec3 i_l00  = vec3( 1.143142,  0.828385,  0.734243);
    vec3 i_l1m1 = vec3(-0.555274, -0.453089, -0.397034);
    vec3 i_l10  = vec3(-0.076708, -0.031024, -0.002636);
    vec3 i_l11  = vec3( 0.109453,  0.070540,  0.032073);
    vec3 i_l2m2 = vec3(-0.062244, -0.045340, -0.013917);
    vec3 i_l2m1 = vec3( 0.147010,  0.079701,  0.036927);
    vec3 i_l20  = vec3(-0.283950, -0.239996, -0.233201);
    vec3 i_l21  = vec3( 0.015775,  0.012168,  0.000236);
    vec3 i_l22  = vec3(-0.258810, -0.101581,  0.010649);
	*/
	
	/*
	// Forest
    vec3 i_l00  = vec3( 1.194736,  1.346145,  1.423871);
    vec3 i_l1m1 = vec3(-0.908735, -1.129492, -1.306158);
    vec3 i_l10  = vec3( 0.124528,  0.099208,  0.038059);
    vec3 i_l11  = vec3(-0.325896, -0.325415, -0.279034);
    vec3 i_l2m2 = vec3( 0.192515,  0.170983,  0.121381);
    vec3 i_l2m1 = vec3(-0.026309,  0.043711,  0.144063);
    vec3 i_l20  = vec3(-0.295198, -0.396629, -0.483597);
    vec3 i_l21  = vec3(-0.182287, -0.159872, -0.115756);
    vec3 i_l22  = vec3( 0.068653, -0.009192, -0.138252);
	*/

    vec3 v = (i_c1 * i_l22 * (nor.x * nor.x - nor.y * nor.y) + i_c3 * i_l20 * nor.z * nor.z + i_c4 * i_l00 - i_c5 * i_l20 + 2.0 * i_c1 * i_l2m2 * nor.x * nor.y + 2.0 * i_c1 * i_l21  * nor.x * nor.z + 2.0 * i_c1 * i_l2m1 * nor.y * nor.z + 2.0 * i_c2 * i_l11  * nor.x + 2.0 * i_c2 * i_l1m1 * nor.y + 2.0 * i_c2 * i_l10 * nor.z);

	return (v + dot(v, vec3(0.33))) * (isSceneCtrl(256) ? vec3(0.2) : vec3(0.5, 0.4, 0.45));
}

///////////////////////////////////////////////////////////////////////////////
// Effect

float i_MtlFloor	= 0.0;
float i_MtlRocks	= 1.0;
float i_MtlStructs	= 2.0;
float i_MtlBoxes	= 3.0;
float i_MtlMLFS		= 4.0;
float i_MtlMLFM		= 5.0;
float i_MtlMLFEyes	= 6.0;
float i_MtlNeonA	= 7.0;
float i_MtlNeonB	= 8.0;

// Tri-looking terrain height function
float Height(vec2 p)
{
	return Pow2(smoothstep(0.0, 1.0, noise2d(p*0.15)) * noise2d(p));
}

// Floor (grid of round boxes)
vec2 Floor(vec3 p)
{
	return vec2(sdRoundBox(vec3(abs(mod(p.xy, 3.0)), p.z) - vec3(1.5, 1.5, 0.95), vec3(1.4, 1.4, 1.0), 0.03), i_MtlFloor);
}
	
// Tri-looking terrain
vec2 TriTerra(vec3 p)
{
	// Bounding box to optimize things a little
	float v = sdBox(p, vec3(25.0, 150.0, 4.0));
	if (v > 2.0)
		return vec2(v, i_MtlRocks);

	// Triangulated terrain quads
	float i_QuadSize = 0.25;
	vec2  uv = floor(p.xy / i_QuadSize);
	return vec2(sdVQuad(vec3(mod(p.xy, vec2(i_QuadSize)), mix(0.5, 2.0, sat(abs(p.x)/15.0))-p.z), Height(uv), Height(uv + vec2(0, 1)), Height(uv + vec2(1, 1)), Height(uv + vec2(1, 0)), i_QuadSize), i_MtlRocks);
}

vec2 RecursiveBoxes(vec3 p) 
{
	float i_BoxHH  = 40.0;
	float i_BoxHZ  = 1.5;
	vec2  i_RSize  = vec2(50.0, 23.0);
	float i_BSize  = 3.0;
	float i_BExtra = 15.0;
  
	float d    = i_PBRFar, sc = 1.0;
		  p.x += 25.0;
		  p.xy = mod(p.xy + i_RSize * 0.5, i_RSize) - i_RSize * 0.5;
		  p.z -= 2.5;

	for (float i = 0; i < 7; i++, sc /= i_BoxHZ) 
	{
		d    = min(d, sdRoundBox(p, vec3(i_BSize, i_BSize, i_BoxHH + sat(1-i) * i_BExtra), 0.05) * sc);
		p.xy = abs(p.xy);
		p    = (p - vec3(i_BSize, i_BSize, i_BoxHH * 0.125)) * i_BoxHZ;
	}
  
	return vec2(d, i_MtlStructs);
}

// Wall of boxes
vec2 WallOfBoxes(vec3 p)
{
	vec2 r = vec2(i_PBRFar, i_MtlBoxes);

	// Repeat
	vec2 i_r  = vec2(15.25, 10.25);
		 p.xz = abs(mod(p.xz, i_r)) - 0.5 * i_r;

	// BBox
	r.x = sdBox(p + vec3(-1.0, 0.0,-1.0), vec3(11.6, 2.5, 9.7));
	if (r.x > 5.0)
		return r;

	r.x = i_PBRFar;
	vec2 rp = floor((p.xz)/(i_r/vec2(1.3, 1.1)));
	
	// Box rnd grid
	for (float x = -4.0; x < 4.0; x+=2.0)
		for (float y = -3.0; y < 3.0; y+=2.0)
			r.x = min(r.x, sdRoundBox(p + vec3(x*2.25, 0.0, y*2.25), vec3(2.75, 1.0, 2.75) + 1.5*abs(hash32(vec2(x, y)+rp)), 0.05));

	return r;
}

// Neon lights
vec2 NeonLights(vec3 p)
{
	float i_rx = 4.0;

	if (p.x < -17.0)
		return vec2(i_PBRFar, i_MtlNeonA);

	p.x = abs(mod(p.x, i_rx));
	p.y+= 17.0;

	return opUnion(vec2(sdCapsule(p, vec3( 1.0, 0.0, 5.0), vec3( 1.0, 0.0, 9.0), 0.125), i_MtlNeonA), vec2(sdCapsule(p, vec3( 3.0, 0.0, 3.0), vec3( 3.0, 0.0, 7.0), 0.125), i_MtlNeonB));
}

// Machine Life Form
vec2 MachineLifeFormInner(vec3 pp, vec3 HeadAngles, vec4 HandAngles, vec3 LeftLegAngles, vec3 RightLegAngles, float BodyMtl)
{
	// Offset
	pp.y -= HeadAngles.z;

	// Bounding sphere optimalization
	vec2 r = vec2(i_PBRFar, BodyMtl);
	r.x = length(pp);
	if (r.x > 5.0)
		return r;

	vec3 ep;

	// Main body

	r.x = sdCylinder(pp, vec2(1.0, 1.0));
	r.x = sdSmoothSubtraction(sdTorus (pp.yzx - vec3(-0.97, 0.0, 0.0), 0.3, 1.25), r.x, 0.01);
	r.x = sdSmoothSubtraction(sdTorus (pp.yzx - vec3( 0.33, 0.0, 0.0), 0.1, 1.05), r.x, 0.05);
	r.x = sdSmoothUnion		 (sdSphere(pp - vec3( 0.0, 1.0, 0.0), 1.0), r.x, 0.0);
	r.x = sdSmoothSubtraction(sdSphere(pp - vec3( 0.8, 1.9, 0.0), 1.0), r.x, 0.02);

	// Hand slots

	r.x = sdSmoothUnion(sdDisc(pp.yzx - vec3( 1.0, 1.0, 0.0), vec2(0.1, 0.2)), r.x, 0.1);
	r.x = sdSmoothUnion(sdDisc(pp.yzx - vec3( 1.0,-1.0, 0.0), vec2(0.1, 0.2)), r.x, 0.1);

	// Legs slots

	r.x = sdSmoothUnion(sdDisc(pp - vec3( 0.0,-1.0, 0.4), vec2(0.2, 0.25)), r.x, 0.1);
	r.x = sdSmoothUnion(sdDisc(pp - vec3( 0.0,-1.0,-0.4), vec2(0.2, 0.25)), r.x, 0.1);

	// Right leg
	{
		ep = pp - vec3(-0.1, -1.0, 0.4);
		ep.xy *= r2(i_MPI+RightLegAngles.x); // Leg rotation
		r.x = sdSmoothUnion(sdRoundBox(ep - vec3(-0.1, 0.4, 0.0), vec3(0.2, 0.5, 0.1), 0.02), r.x, 0.05);
		r.x = sdSmoothUnion(sdDisc((ep - vec3(-0.1, 0.9, 0.00)).xzy, vec2(0.18, 0.05)), r.x, 0.05);
		ep = ep - vec3(-0.06, 1.0, 0.0);
		ep.xy *= r2(RightLegAngles.y); // Knee rotation
		r.x = sdSmoothUnion(sdRoundBox(ep - vec3(-0.06, 0.3, 0.0), vec3(0.12, 0.4, 0.07), 0.02), r.x, 0.05);
		ep = ep - vec3(0.0, 0.71, 0.0);
		ep.xy *= r2(RightLegAngles.z); // Feet rotation
		r.x = sdSmoothUnion(sdRoundBox(ep - vec3(-0.3, 0.01, 0.1), vec3(0.4, 0.02, 0.2), 0.02), r.x, 0.1);
	}

	// Left leg
	{
		ep = pp - vec3(-0.1, -1.0, -0.4);
		ep.xy *= r2(i_MPI+LeftLegAngles.x); // Leg rotation
		r.x = sdSmoothUnion(sdRoundBox(ep - vec3(-0.1, 0.4, 0.0), vec3(0.2, 0.5, 0.1), 0.02), r.x, 0.05);
		r.x = sdSmoothUnion(sdDisc((ep - vec3(-0.1, 0.9, 0.00)).xzy, vec2(0.18, 0.05)), r.x, 0.05);
		ep = ep - vec3(-0.06, 1.0, 0.0);
		ep.xy *= r2(LeftLegAngles.y); // Knee rotation
		r.x = sdSmoothUnion(sdRoundBox(ep - vec3(-0.06, 0.3, 0.0), vec3(0.12, 0.4, 0.07), 0.02), r.x, 0.05);
		ep = ep - vec3(0.0, 0.71, 0.0);
		ep.xy *= r2(LeftLegAngles.z); // Feet rotation
		r.x = sdSmoothUnion(sdRoundBox(ep - vec3(-0.3, 0.01,-0.1), vec3(0.4, 0.02, 0.2), 0.02), r.x, 0.1);
	}

	// Right hand
	{
		ep = pp - vec3(0.0, 1.0, 1.2);
		ep.xy *= r2(HandAngles.z); // Arm rotation
		r.x = sdSmoothUnion(sdSphere(ep, 0.2), r.x, 0.05);
		r.x = sdSmoothUnion(sdRoundBox(ep - vec3(0.0, 0.4, 0.1), vec3(0.1, 0.4, 0.05), 0.02), r.x, 0.02);
		ep = ep - vec3(0.0, 0.8, 0.1);
		ep.xy *= r2(HandAngles.w); // Elbow rotation
		r.x = sdSmoothUnion(sdSphere(ep, 0.125), r.x, 0.03);
		r.x = sdSmoothUnion(sdRoundBox(ep - vec3(0.0, 0.5, 0.0), vec3(0.05, 0.45, 0.04), 0.02), r.x, 0.02);
		r.x = sdSmoothUnion(sdDisc((ep - vec3(-0.05, 1.0, 0.03)).xzy, vec2(0.18, 0.05)), r.x, 0.05);
	}

	// Left hand
	{
		ep = pp - vec3(0.0, 1.0,-1.2);
		ep.xy *= r2(HandAngles.x); // Arm rotation
		r.x = sdSmoothUnion(sdSphere(ep, 0.2), r.x, 0.05);
		r.x = sdSmoothUnion(sdRoundBox(ep - vec3(0.0, 0.4,-0.1), vec3(0.1, 0.4, 0.05), 0.02), r.x, 0.02);
		ep = ep - vec3(0.0, 0.8,-0.1);
		ep.xy *= r2(HandAngles.y); // Elbow rotation
		r.x = sdSmoothUnion(sdSphere(ep, 0.125), r.x, 0.03);
		r.x = sdSmoothUnion(sdRoundBox(ep - vec3(0.0, 0.5, 0.0), vec3(0.05, 0.45, 0.04), 0.02), r.x, 0.02);
		r.x = sdSmoothUnion(sdDisc((ep - vec3(-0.05, 1.0,-0.03)).xzy, vec2(0.18, 0.05)), r.x, 0.05);
	}

	// Head & eyes (at end, eyes have different material)
	{
		ep = pp - vec3(0.6, 1.7, 0.0);
		ep.xz *= r2(HeadAngles.x); // Left-Right look-at
		ep.xy *= r2(HeadAngles.y); // Up-Down look-at
		r.x = sdSmoothUnion(sdSphere(ep, 0.75), r.x, 0.0);
		r.x = sdSmoothSubtraction(sdSphere(ep - vec3(0.6, 0.25, 0.4), 0.15), r.x, 0.03);
		r.x = sdSmoothSubtraction(sdSphere(ep - vec3(0.6, 0.25,-0.4), 0.15), r.x, 0.03);
		r = opUnion(r, vec2(sdSphere(ep - vec3(0.56, 0.24, 0.38), 0.1), i_MtlMLFEyes));
		r = opUnion(r, vec2(sdSphere(ep - vec3(0.56, 0.24,-0.38), 0.1), i_MtlMLFEyes));
    }
	
	return r;
}

vec3 MachineLifeFormLegIK(float HOffset, float XOffset)
{
	float i_ThighLen  = 1.1;
	float i_CalfLen   = 0.8;

	vec2  AnklePos    = vec2(0.0, i_ThighLen+i_CalfLen+min(0.0, HOffset - abs(XOffset)));
	vec2  KneePos     = IKSolve2Bones(AnklePos, i_ThighLen, i_CalfLen, 1.0);
	vec2  ThighVector = normalize(KneePos);
	vec2  CalfVector  = normalize(AnklePos - KneePos);

	return vec3(clamp(AngleBetweenVectors(normalize(vec2(XOffset, 1.0)), ThighVector), 0.0, 0.7), clamp(AngleBetweenVectors(ThighVector, CalfVector), -1.25, 0.0), clamp(-AngleBetweenVectors(CalfVector, vec2(1.0, 0.0)), 0.0, mix(0.0, 0.7, sat(-HOffset/0.2))) - mix(0.0, 0.4, sat(HOffset*2.0)));
}

vec2 MachineLifeFormArmIK(vec3 WristPos)
{
	float i_ArmLen     = 0.8;
	float i_ForearmLen = 0.9;

	// Bring effector into range
	if (length(WristPos) > (i_ArmLen+i_ForearmLen))
	{
		WristPos.xy = normalize(WristPos.xy) * ((i_ArmLen+i_ForearmLen)-0.001);
		WristPos.x *= 0.6 + abs(sin(WristPos.y*2.5)) * 0.4;
	}

	vec2  ElbowPos  = IKSolve2Bones(WristPos.xy, i_ArmLen, i_ForearmLen, -1.0);
	vec2  ArmVector = normalize(ElbowPos);

	return vec2(clamp(mod(AngleBetweenVectors(vec2(0.0, 1.0), ArmVector) + i_MPI*2.0, i_MPI*2.0), 3.0, 6.1), clamp(mod(-AngleBetweenVectors(normalize(WristPos.xy - ElbowPos), ArmVector) + i_MPI*2.0, i_MPI*2.0), 0.0, 2.0));
}

vec2 MachinesLifeForms(vec3 pp)
{
	float i_rx    = 5.0;
	float i_rz    = 5.0;

	pp.xyz = pp.yzx; // Need to flip as MachineLifeForm was in Y-Up
	pp.y  -= 4.75;	 // Floor offset

	float BodyMtl;
	int   OffID = 0;
	vec3  p     = pp;

	// Multi instance
	if (isSceneCtrl(32))
	{
		// Reject
		if ((((abs(pp.x) > 20.5 || abs(pp.z) > 15.5) && !isSceneCtrl(512)) || ((pp.x < -20.5 || pp.z < -15.5) && isSceneCtrl(512))) || (!isSceneCtrl(16) && pp.x < -15.5) || (isSceneCtrl(128) && (abs(pp.x) > 5.5 || abs(pp.z) > 5.5)))
			return vec2(i_PBRFar, 0.0);

		BodyMtl = i_MtlMLFM;

		if (isSceneCtrl(16) && pp.x < -15.5)
		{
			BodyMtl = i_MtlMLFS;
			if (pp.z > 10.5 || pp.z < 4.5)
				return vec2(i_PBRFar, 0.0);
		}

		p = vec3(abs(mod(pp.x, i_rx)) - 0.5 * i_rx, pp.y, abs(mod(pp.z, i_rz)) - 0.5 * i_rz);
	
		vec2 rnd = hash22(floor(pp.xz/5.0));

		OffID = int(rnd.x * 4.0) * 4;
		p.xz *= r2((rnd.y * 2.0 - 1.0) * (isSceneCtrl(512) ? 2.0 : 0.5));
		if (isSceneCtrl(512))
			p.xz += rnd;
	}
	// Single instance
	else
	{
		BodyMtl = isSceneCtrl(64) ? i_MtlMLFM : i_MtlMLFS;
	}

	return MachineLifeFormInner(p, texelFetch(sm, ivec2(OffID+4, 0), 0).xyz, texelFetch(sm, ivec2(OffID+5, 0), 0), texelFetch(sm, ivec2(OffID+6, 0), 0).xyz, texelFetch(sm, ivec2(OffID+7, 0), 0).xyz, BodyMtl);
}

vec2 PBRScene(vec3 p) // Distance, MaterialID
{
	//   1 - Grid
	//   2 - TriTerra
	//   4 - Geo forms
	//   8 - Box walls
	//  16 - Machine life form (single red)
	//  32 - Machine life form (multi all)
	//  64 - Machine life form (single brown)
	// 128 - Machine life form (5 brown)
	// 256 - Neon lights (on box walls)
	// 512 - Machine life form (left-bottom unlimited)

	vec2 Res = vec2(i_PBRFar, 0.0);

	if (isSceneCtrl(1))
	{
		Res = opUnion(Res, Floor(p));
	}
	
	if (isSceneCtrl(2))
	{
		vec2 Ter = TriTerra(p);
		Res = vec2(sdSmoothUnion(Res.x, Ter.x, 0.75), (Res.x < Ter.x) ? Res.y : Ter.y);
	}

	if (isSceneCtrl(4))
	{
		vec2 Rbx = RecursiveBoxes(p);
		Res = vec2(sdSmoothUnion(Res.x, Rbx.x, 1.0), (Res.x < Rbx.x) ? Res.y : Rbx.y);
	}

	if (isSceneCtrl(8))
	{
		Res = opUnion(Res, opUnion(WallOfBoxes(p + vec3(0.0, 20.0, 0.0)), WallOfBoxes(p.yxz + vec3(5.0, 20.0, 0.0))));
	}

	if (isSceneCtrl(256))
	{
		Res = opUnion(Res, opUnion(NeonLights(p.xyz), NeonLights(p.yxz)));
	}

	if (isSceneCtrl(16+32))
	{
		Res = opUnion(Res, MachinesLifeForms(p));
	}

	return Res;
}

vec4 LGHScene(vec3 p)
{
	vec3 Lgh = vec3(0.0);
	vec2 Res = opUnion(NeonLights(p.xyz), NeonLights(p.yxz));

	if (Res.x < i_PBRFar)
		Lgh += (vec3(1.0, 0.5, 1.0) * 1.0 * sat(((Res.y == i_MtlNeonA ? tm.y : tm.z) / 100.0) - 0.25)) / max(1.0, Res.x);

	if (iTime > 116.0)
	{
		vec2 Mlf = MachinesLifeForms(p);
		if (Mlf.x < Res.x && Mlf.y == i_MtlMLFEyes)
		{
			Lgh  += vec3(0.1, 0.0, 0.0) / max(1.0, Mlf.x);
			Res.x = min(Res.x, Mlf.x);
		}
	}

	return vec4(Lgh, Res.x);
}

// Helper
vec3 CameraHardCutA(vec3 c, float t)
{
	c.y  += sin(t / 5.0) * 7.0;
	c.xy *= r2(sin(t / 2.0) + 0.5);
	c.z  += abs(cos(t)) * 3.0;

	return c;
}

// Helper
vec3 CameraHardCutB(vec3 c, float t)
{
	c.y  += iTime < 60.0 ? (cos(t / 16.0) * 7.0) : 0.0;
	c.xy *= r2(cos(t / 3.0) + 0.6);
	c.z  += (abs(sin(t * 1.25)) * 4.0) + cos(t * 2.0) * 2.5;

	return c;
}

///////////////////////////////////////////////////////////////////////////////
// Rendering (scene invatiant)

vec3 PBRNormal(vec3 p)
{
	vec2   eps = vec2(i_PBRNEps, 0.0);
	return normalize(vec3(PBRScene(p + eps.xyy).x - PBRScene(p - eps.xyy).x, PBRScene(p + eps.yxy).x - PBRScene(p - eps.yxy).x, PBRScene(p + eps.yyx).x - PBRScene(p - eps.yyx).x));
}

vec2 PBRCast() // Distance, MaterialID
{
	vec2  h = vec2(i_PBRFar, 0.0);
	float t = 0.0;
   
	for (int i = 0; i < i_PBRSteps && abs(h.x) > i_PBREps && t < i_PBRFar; ++i)
	{
		h    = PBRScene(g_RayOrg + g_RayDir * t);
		h.x *= i_PBRPMul; // Additional precision needed
		t   += h.x;
	}

	return vec2(t, h.y);
}

vec2 PBRCastHaze() // Distance, MaterialID
{
	vec2  h  = vec2(i_PBRFar, 0.0);
	float t  = 0.0;
	float hw = 0.0;

	g_Haze  = vec3(0.0);
	
	for (int i = 0; i < i_PBRSteps && abs(h.x) > i_PBREps && t < i_PBRFar; ++i)
	{
			 h  = PBRScene(g_RayOrg + g_RayDir * t);
		vec4 ls = LGHScene(g_RayOrg + g_RayDir * t);

		// Step limit
		h.x = min((i_PBRFar * 0.01) + t / i_PBRFar, h.x);
        
		// Haze (fake it with 2D, to not handle another noise type ...)
		float MediumHaze = noise2d(((g_RayOrg + g_RayDir * t) * 0.25).xy); // noise3d2((g_RayOrg + g_RayDir * t) * 0.25);

		float HazeFactor = sat(h.x / max(0.001, ls.w));
		g_Haze.xyz += HazeFactor * ls.xyz * MediumHaze;
		hw		   += HazeFactor;
           
		// Advance
		h.x *= i_PBRPMul; // Additional precision needed
		t   += h.x;
	}		
		
	// Haze processing
	g_Haze = (g_Haze / max(1.0, hw)) * smoothstep(0.0, 1.0, t);

	return vec2(t, h.y);
}

/*
// Calc soft shadows
float CalcSoftshadow(vec3 ro, vec3 rd, float mint, float tmax)
{
	float res = 1.0, h = i_PBRFar;

    for (int i = 0; i < 32 && res > i_PBREps && mint < tmax && h > i_PBREps; i++)
    {
		h   = PBRScene(ro + rd * mint).x;
        res = min(res, 10.0 * h / mint);
		mint+= h;
    }
        
    res = sat(res);
    return res*res*(3.0-2.0*res);
}
*/

// Calc directional light shadows
float CalcLDSoftshadow()
{
	float res = 1.0, h = i_PBRFar, mint = 0.1;

    for (int i = 0; i < 32 && res > i_PBREps && mint < 10.0 && h > i_PBREps; i++)
    {
		h   = PBRScene(g_SrfPos + g_PBRMainLD * mint).x;
        res = min(res, 10.0 * h / mint);
		mint+= h;
    }
        
    res = sat(res);
    return sat((res*res*(3.0-2.0*res)) * 0.7 + 0.3);
}

// AO hemispherical dithered by pixel position	
float CalcAODithered() 
{
	int   i_aoit	= 24;
	float i_dst		= 2.5;
	float i_falloff = 2.0;
		
	float ao  = 0.0;
	vec3  pos = g_SrfPos + g_SrfNrm * i_PBREps * 2.0;
		
	for (int i = 0; i < i_aoit; i++)
	{
		vec2  hsu = hash22(vec2(i+1.0, i+2.0));
		float l   = (hash11(i) * i_dst);
		vec3  v   = sampleDirectionSphere(hsu);
        
		ao += (l - max(PBRScene(pos + (normalize(g_SrfNrm + (v * sign(dot(v, g_SrfNrm))) * (1.0 - (1.0 / i_aoit))) * l)).x, 0.0)) / i_dst * i_falloff;
	}
			
	return clamp(1.0 - (ao * (1.0 / i_aoit)), 0.25, 1.0);
}

/*
// Calc fake reflection from tubes
vec3 FakeTubesReflection(float TOff)
{
	vec3  RayDir = reflect(g_RayDir, g_SrfNrm);
	vec3  RayOrg = g_SrfPos + sampleDirectionSphere(hash22(gl_FragCoord.xy + TOff)) * 0.2;
	float q		 = 0.0;
   
	for (int i = 0; i < 13; i++)
	{
		vec3  p = RayOrg + RayDir * q;
			  q+= opUnion(NeonLights(p.xyz), NeonLights(p.yxz)).x;
	}

	return q < i_PBRFar ? g_SrfSpecular * vec3(3.0, 2.0, 3.0) * (0.1 + sat(tm.y/100.0)) * Pow2(1.0 - sat((RayOrg + RayDir * q).z / 8.0)) : vec3(0);
}
*/

vec4 TracePBR()
{
	// Scene cast
	vec4 Color = vec4(calcIrradiance(g_RayDir.yzx), 1.0);
	vec2 t     = isSceneCtrl(256) ? PBRCastHaze() : PBRCast();
	
	if (t.x < i_PBRFar)
	{
		// Basic data
		g_SrfPos = g_RayOrg + t.x * g_RayDir;
		g_SrfNrm = PBRNormal(g_SrfPos);

		// Material resolve (any emission should be added directly to Result)
		vec3 Result = vec3(0.0);

		if (t.y == i_MtlStructs || t.y == i_MtlBoxes)
		{
			setPBRMat(vec3(0.5), 0.66, 0.33, 0.3);
		}
		else
		if (t.y == i_MtlFloor)
		{
			if (isSceneCtrl(256))
				setPBRMat(vec3(0.1), 0.15, 1.0, 1.0);
			else
				setPBRMat(vec3(0.53, 0.4, 0.46), 0.6, 0.2, 0.3);
		}
		else
		if (t.y == i_MtlRocks)
		{
			setPBRMat(vec3(0.3, 0.25, 0.4), sat(0.3+sat(t.x/500.0)), 0.2, 0.3);
		}
		else
		if (t.y == i_MtlMLFM)
		{
			setPBRMat(vec3(0.5, 0.4, 0.25), 0.2, 0.0, 0.5);
		}
		else
		if (t.y == i_MtlMLFS)
		{
			setPBRMat(vec3(0.4, 0.05, 0.1), 0.1, 0.8, 0.9);
		}
		else
		if (t.y == i_MtlMLFEyes)
		{
			setPBRMat(vec3(0.4), 0.8, 0.8, 0.7);
			Result.xyz += vec3(1.0, 0.1, 0.1) * max(0.0, iTime-116.0) * 2.0;
		}
		else
		if (t.y == i_MtlNeonA || t.y == i_MtlNeonB)
		{
			setPBRMat(vec3(0.5), 0.5, 0.5, 0.5);
			Result.xyz += vec3(1.0, 0.5, 1.0) * (0.25 + (t.y == i_MtlNeonA ? tm.y : tm.z) / 100.0);
		}
		else
		{
			setPBRMat(vec3(0.5), 1.0, 0.0, 0.5);
		}

		// AO
		float AO = CalcAODithered();

		// Global shadow
		float Shadow = CalcLDSoftshadow() * AO;

		// IBL
		Result += ((calcIrradiance(reflect(g_RayDir, g_SrfNrm).yzx).zyx * envBRDFApprox(sat(dot(-g_RayDir, g_SrfNrm)))) * AO);
		 
		// Light
		if (isSceneCtrl(256))
		{
			// Note - Shadow here is a fake, but looks OK!

			/*
			// Note - Super fake roughness hack ...
			g_SrfRoughness = sat(g_SrfRoughness * 3.7);
			*/

			for (float x = -16.0, z = 0.0; x < 37.0; x += 2.0, z++)
			{
				float ZOff = (1.0 - mod(z, 2.0)) * 2.0;
				
				Result += lightLocalTube(vec3(1.0, 0.5, 1.0) * (0.1 + sat(tm.z/100.0)) * 0.33, vec3( 1.0 + x, -15.0, 3.0+ZOff), vec3( 1.0 + x, -15.0, 7.0+ZOff), 30.0, 0.1, Shadow);
				Result += lightLocalTube(vec3(1.0, 0.5, 1.0) * (0.1 + sat(tm.y/100.0)) * 0.33, vec3(-15.0, 1.0 + x, 3.0+ZOff),  vec3(-15.0, 1.0 + x, 7.0+ZOff), 30.0, 0.1, Shadow);
				
				/*
				Result += lightLocal(vec3(1.0, 0.5, 1.0) * (0.1 + sat(tm.z/100.0)) * 0.33, vec3( 1.0 + x, -15.0, 6.0+ZOff), 30.0, Shadow);
				Result += lightLocal(vec3(1.0, 0.5, 1.0) * (0.1 + sat(tm.y/100.0)) * 0.33, vec3(-15.0, 1.0 + x, 6.0+ZOff), 30.0, Shadow);
				*/
			}

			/*
			if (t.y == i_MtlFloor)
			{
				for (float x = 0; x < 4; x++)
					Result += FakeTubesReflection(x * 100.0) * AO * Shadow;
			}
			*/
		}
		else
		{
			Result += lightDirectional(vec3(i_MPI) * vec3(1.0, 0.86, 0.98), g_PBRMainLD, Shadow);
		}

		// Fake sky-blend
		Color.xyz = mix(max(vec3(0), Result), Color.xyz, pow(sat((t.x-(i_PBRFar/2.0)) / (i_PBRFar/2.0)), 1.0));

		// CoC
		Color.w = sat((t.x - g_CoCNear) / g_CoCDst);
	}

	if (isSceneCtrl(256))
		Color.xyz += g_Haze;
	
	return Color;
}

///////////////////////////////////////////////////////////////////////////////
// 2D stuff

// Letter A
float drawA(vec2 uv)
{
    return sdQuad(uv, vec2(0.385, 0.010), vec2(0.000, 0.790), vec2(0.220, 0.790), vec2(0.525, 0.010)) + sdQuad(uv, vec2(0.775, 1.000), vec2(1.000, 1.000), vec2(0.525, 0.010), vec2(0.360, 0.140)) + sdQuad(uv, vec2(0.100, 0.790), vec2(0.520, 0.790), vec2(0.425, 0.610), vec2(0.100, 0.610)) + sdQuad(uv, vec2(0.220, 0.610), vec2(0.310, 0.610), vec2(0.495, 0.230), vec2(0.310, 0.220));
}

// Letter I
float drawI(vec2 uv)
{
    return sdQuad(uv, vec2(0.000, 0.790), vec2(0.145, 0.790), vec2(0.210, 0.610), vec2(0.210, 0.010)) + sdQuad(uv, vec2(0.000, 0.790), vec2(0.210, 0.010), vec2(0.070, 0.010), vec2(0.000, 0.200));
}

// Letter T
float drawT(vec2 uv)
{
    return drawI(uv + vec2(-0.290, 0.000)) + sdQuad(uv, vec2(0.000, 0.200), vec2(0.730, 0.200), vec2(0.800, 0.010), vec2(0.080, 0.010));
}

// Letter D
float drawD(vec2 uv)
{
    return drawI(uv) + sdQuad(uv, vec2(0.180, 0.200), vec2(0.520, 0.200), vec2(0.430, 0.010), vec2(0.180, 0.010)) + sdQuad(uv, vec2(0.180, 0.200), vec2(0.520, 0.200), vec2(0.430, 0.010), vec2(0.180, 0.010)) + sdSemiArc(uv * vec2(1.0, 1.04), vec2(0.385, 0.415), 0.31, 0.095, -1.0) + sdQuad(uv, vec2(0.300, 0.790), vec2(0.400, 0.790), vec2(0.400, 0.610), vec2(0.380, 0.610));
}

// Letter C
float drawC(vec2 uv)
{
    return sdSemiArc(uv * vec2(1.0, 1.04), vec2(0.485, 0.415), 0.31, 0.095, 1.0) + sdQuad(uv, vec2(0.450, 0.790), vec2(0.785, 0.790), vec2(0.865, 0.610), vec2(0.430, 0.605)) + sdQuad(uv, vec2(0.300, 0.200), vec2(0.595, 0.200), vec2(0.665, 0.010), vec2(0.480, 0.010));
}

// Letter O
float drawO(vec2 uv)
{
    return sdSemiArc(uv * vec2(1.0, 1.04), vec2(0.335, 0.415), 0.31, 0.095, 1.0) + sdSemiArc(uv * vec2(1.0, 1.04), vec2(0.44, 0.415), 0.31, 0.095, -1.0);
}

// Letter R
float drawR(vec2 uv)
{
	return drawI(uv) + sdQuad(uv, vec2(0.270, 0.520), vec2(0.450, 0.520), vec2(0.720, 0.350), vec2(0.340, 0.350)) + sdQuad(uv, vec2(0.100, 0.190), vec2(0.600, 0.190), vec2(0.480, 0.010), vec2(0.100, 0.010)) + sdQuad(uv, vec2(0.600, 0.790), vec2(0.850, 0.790), vec2(0.580, 0.470), vec2(0.310, 0.470)) + sdSemiArc(uv * vec2(1.0, 1.25), vec2(0.450, 0.354), 0.25, 0.095, -1.0);
}

// Letter S
float drawS(vec2 uv)
{
    return sat(sdSemiArc(uv * vec2(1.5, 2.5), vec2(0.485, 0.75), 0.55, 0.15, 1.0) - sdQuad(uv, vec2(0.2, 0.60), vec2(0.35, 0.60), vec2(0.35, 0.2), vec2(0.2, 0.2))) +
		   sat(sdSemiArc(uv * vec2(1.5, 2.2), vec2(0.15, 1.45), 0.5, 0.15, -1.0) - sdQuad(uv, vec2(0.1, 0.60), vec2(0.3, 0.60), vec2(0.3, 0.2), vec2(0.1, 0.2))) +
		   sdQuad(uv, vec2(-4.18, 0.96), vec2(0.05, 0.96), vec2(0.05, 0.83), vec2(-4.18, 0.83));
}

// Helper
float SinFlash(float x, float y, float z)
{
	return (iTime > x && iTime < y) ? sat(sin((iTime-x) * z)) : 0;
}

/*
// Addict logo
vec3 AddictLogo(vec2 uv)
{
	float sbc = (sdECrc(uv, -4.2, -3.45, 3.5) + sdECrc(uv, -2.5, -4.0, 4.5) + sdECrc(uv, -1.8,  2.05, 2.4) + sdECrc(uv, 1.1, -3.33, 3.2)) * 2.0;

    float res = drawA(uv); uv.x -= 1.07;
		  res+= drawD(uv); uv.x -= 0.97;
		  res+= drawD(uv); uv.x -= 0.97;
		  res+= drawI(uv); uv.x -= 0.30;
		  res+= drawC(uv); uv.x -= 0.74;
		  res+= drawT(uv);
		  res = sat(res - sbc);

		  uv += vec2(4.0,-1.2);

		  res+= drawD(uv); uv.x -= 0.97;
		  res+= drawR(uv); uv.x -= 0.97;
	float red = drawO(uv); uv.x -= 0.97;
		  res+= red;
		  res+= drawI(uv); uv.x -= 0.30;
		  res+= drawD(uv); uv.x -= 0.97;
		  res+= drawS(uv); 

	res = sat(res);

	return mix(vec3(res), vec3(res, 0.0, 0.0), red);
}
*/

///////////////////////////////////////////////////////////////////////////////
// Main
/*
void CameraSetup(vec2 uv)
{
	// Camera setup load
    vec4  CameraPosition = texelFetch(sm, ivec2(0, 0), 0); // Position & FOV
    vec4  TargetPosition = texelFetch(sm, ivec2(1, 0), 0); // Target & CoC Near
    vec4  UpVector       = texelFetch(sm, ivec2(2, 0), 0); // Up & CoC Dst

	g_CoCNear = TargetPosition.w;
	g_CoCDst  = UpVector.w;
    
    // Ray from camera
	g_CamDir = normalize(TargetPosition.xyz - CameraPosition.xyz);

	// Prepare ray
    g_RayOrg = CameraPosition.xyz;
	g_RayDir = normalize(vec3(uv.x, uv.y, CameraPosition.w)) * cameraLookAt(g_CamDir, UpVector.xyz);

	return;
}
*/
///////////////////////////////////////////////////////////////////////////////
// Main pixel shader
//

void amain()
{
	vec2 fragCoord		= gl_FragCoord.xy;
    vec2 sfc			= (2.0*fragCoord-i_Resolution.xy)/i_Resolution.y;
    vec2 Ctrl			= texelFetch(sm, ivec2(0, 0), 0).xy;// Scene ctrl & flashes
	vec4 CameraPosition = texelFetch(sm, ivec2(1, 0), 0);	// Position & FOV
    vec4 TargetPosition = texelFetch(sm, ivec2(2, 0), 0);	// Target & CoC Near
    vec4 UpVector       = texelFetch(sm, ivec2(3, 0), 0);	// Up & CoC Dst

	// Scene ctrl over time
	g_ScnCtrl	= int(Ctrl.y);
	g_CoCNear   = 50.0;
	g_CoCDst    = 333.0;
	g_PBRMainLD	= normalize(vec3(0.3, 0.3, 1.0));

	// Camera setup
	g_CoCNear = TargetPosition.w;
	g_CoCDst  = UpVector.w;
    
    // Ray from camera
	g_CamDir = normalize(TargetPosition.xyz - CameraPosition.xyz);

	// Prepare ray
    g_RayOrg = CameraPosition.xyz;
	g_RayDir = normalize(vec3(sfc.x, sfc.y, CameraPosition.w)) * cameraLookAt(g_CamDir, UpVector.xyz);

	// Rendering
    vec4 Final = TracePBR();

	// Dithering (to cover low precission due to fake HDR)
	Final.xyz+= fract(52.9829189 * fract(dot(fragCoord + iTime, vec2(0.06711056, 0.00583715)))) * 0.005;

	// Flash
	Final.xyz = mix(max(vec3(0.0), Final.xyz), vec3(sat(Ctrl.x * 1e3)), sat(abs(Ctrl.x)));

	// 2D 
	float Fctr = SinFlash(2, 5, 1.25);
	if (Fctr > 0.0)
	{
		vec2  luv = sfc * vec2(3.0, -3.0) + vec2(2.45, 1.1);
		float sbc = sdECrc(luv, -4.2, -3.45, 3.5) + sdECrc(luv, -2.5, -4.0, 4.5) + sdECrc(luv, -1.8,  2.05, 2.4) + sdECrc(luv, 1.1, -3.33, 3.2);

		float res = drawA(luv); luv.x -= 1.07;
			  res+= drawD(luv); luv.x -= 0.97;
			  res+= drawD(luv); luv.x -= 0.97;
			  res+= drawI(luv); luv.x -= 0.30;
			  res+= drawC(luv); luv.x -= 0.74;
			  res+= drawT(luv);
			  res = sat(res - sbc * 4.0);
			  luv+= vec2(4.0,-1.2);
			  res+= drawD(luv); luv.x -= 0.97;
			  res+= drawR(luv); luv.x -= 0.97;
		float red = drawO(luv); luv.x -= 0.97;
			  res+= red;
			  res+= drawI(luv); luv.x -= 0.30;
			  res+= drawD(luv); luv.x -= 0.97;
			  res+= drawS(luv); 
			  res = sat(res);

	    Fctr	 *= res;
		Final.xyz = mix(Final.xyz, mix(vec3(res), vec3(res, 0.0, 0.0), red), Fctr);
		Final.w   = mix(Final.w, 0.01, sat(Fctr * 4.0));
	}

	// CoC fade at end
	Final.w += sat(iTime - 118.5);

	// Fake HDR
	Final.xyz /= i_HDRFactor;

	// Out
	o = Final;

	return;
}

///////////////////////////////////////////////////////////////////////////////
// Postprocessing - hexagonal blur
//

void bmain()
{
	vec2  FragCoord = gl_FragCoord.xy;
	vec2  TexelSize = 1.0 / i_Resolution;
	vec2  UV        = FragCoord*TexelSize;
    vec4  Color		= vec4(0.0);
    float CoC		= textureLod(sm, UV, 0).a;

	for (float t = 0.0; t < 50.0; t += 0.2) 
	{
		float r = 0.87 / cos(mod(t, 1.05) - 0.5);
        
        // Tap filter once for coc
        vec2 Offset = vec2(sin(t), cos(t)) * r * t * TexelSize; 
        vec4 Samp   = textureLod(sm, UV + Offset * CoC, 0);
        
        // Tap filter with coc from texture
        Samp   = textureLod(sm, UV + Offset * Samp.a, 0) + vec4(0.001);
        
        // Weigh and save
        Color  += vec4(Samp.rgb * i_HDRFactor * Samp.a * t, Samp.a * t);
    }

	// Tonemapping, Vignetting and Output
    o = vec4(toneMapFilmicHejl2015W1(Color / Color.w) * min(1.0, pow(512.0*UV.x*UV.y*(1.0-UV.x)*(1.0-UV.y),0.3)), 1.0);

	// Gretz
	UV.x += 0.01 * mix(0.0, hash11(iTime+UV.y) - 0.5, Ramp(mod(UV.y * 4.0 + iTime / 2.0 + sin(iTime + sin(iTime * 0.65)), 1.0), 0.5, 0.6));
	o = mix(sat(o), vec4(1.0), sat((0.7 + fract((FragCoord.x+FragCoord.y) / 2.0)) * sat(iTime-116) * (1.0 - sat(iTime-120)) * textureLod(sn, UV, 0).x));

	return;
}

///////////////////////////////////////////////////////////////////////////////
// Calculations
//

void cmain()
{
	// Selector 

	int  PixelIndex    = int(gl_FragCoord.x);	
	bool i_DebugCamera = false;

	if (PixelIndex == 0)
	{
		//   1 - Grid
		//   2 - TriTerra
		//   4 - Geo forms
		//   8 - Box walls
		//  16 - Machine life form (single red)
		//  32 - Machine life form (multi all)
		//  64 - Machine life form (single brown)
		// 128 - Machine life form (5 brown)
		// 256 - Neon lights (on box walls)
		// 512 - Machine life form (left-bottom unlimited)

		float Flash = 0.0; // -1 - Black, 0 - None, 1 - White
		int   SCtrl = 1 + 8 + 32 + 256 + 512;

		// Scene configs
		if (iTime < 23.0)
			SCtrl = 1 + 8 + 16 + 256;
		else
		if (iTime < 31.0)
			SCtrl = 1 + 2 + 4 + 16 + 64;
		else
		if (iTime < 39.5)
			SCtrl = 1 + 2 + 4 + ((abs(iTime-31.5) < 0.15 || abs(iTime-31.85) < 0.15 || iTime > 33.0) ? 32 + 128 : 16 + 64);
		else
		if (iTime < 43.5)
			SCtrl = 1 + 2 + 4 + 16 + 64;
		else
		if (iTime < 60.0)
			SCtrl = 1 + 2 + 4 + 32;
		else
		if (iTime < 62.0)
			SCtrl = 1 + 8 + 16 + 256;
		else
		if (iTime < 83.5)
			SCtrl = 1 + 2 + 4 + 32;
		else
		if (iTime < 99.0)
			SCtrl = 1 + 2 + 4 + 16 + 32;

		// Flash configs
		if (iTime < 1.0)
			Flash = iTime - 1.0;
		else
		if (iTime > 22.0 && iTime < 23.0)
			Flash = 22.0 - iTime;
		else
		if (iTime >= 23.0 && iTime < 25.0)
			Flash = min(0.0, iTime - 25.0) * ((abs(iTime-23.5) < 0.1 || abs(iTime-23.75) < 0.1) ? -0.5 : 1.0);
		else
		if (iTime < 41.0)
			Flash = ((abs(iTime-31.5) < 0.05 || abs(iTime-31.85) < 0.05 || abs(iTime-33.0) < 0.05 || abs(iTime-39.5) < 0.15) ? 1.0 : 0.0);
		else
		if (iTime > 60.0 && iTime < 62.0)
			Flash = ((abs(iTime-60.0) < 0.2 || abs(iTime-62.0) < 0.2) ? 1.0 : 0.0);
		else
		if (iTime > 115.0)
			Flash = abs(iTime-116.0) < 0.1 ? 1.0 : min(0.0, (116.0 - iTime) * 0.3);

		Flash += SinFlash(4, 5, 2) + SinFlash(6, 7, 5) + SinFlash(43, 44, 3) + SinFlash(83, 84, 3);

		if (i_DebugCamera)
		{
			Flash = 0.0;
			SCtrl = 1 + 8 + 16;
		}

		o = vec4(Flash, float(SCtrl)+0.5, 0.0, 0.0);
	}
	else
	if (PixelIndex < 4)
	{
		float LocalTime;
		float BeatSum		 = floor(hash11(floor(tm.w * 0.0015)) * 2.0) * 3.0;
		vec3  CameraPosition = vec3(0.0, 15.0, 5.0);
		vec3  TargetPosition = vec3(0.0,  0.0, 5.0);
		vec3  UpVector       = vec3(0.0,  0.0, 1.0);
		vec3  FovAndCoC      = vec3(2.25, 75.0, 500.0);
    
		// Camera movement
		if (iTime < 23.0 || iTime > 60.0 && iTime < 62.0)
		{
			FovAndCoC.y        = 20.0;
			LocalTime          = sat(iTime/6.0);
			
			CameraPosition.y  -= LocalTime*7.0;
			CameraPosition.xy *= r2((1.0-LocalTime) * 2.0);
			CameraPosition.z  += 10.0 - (sin(LocalTime*i_MPI*0.5) * 10.0);

			if (iTime > 6.25)
			{
				LocalTime = iTime - 5.0;

				if (hash11(floor(iTime*1.7)) < 0.5 && iTime < 60.0)
				{
					CameraPosition.y += LocalTime * 1.5;
					FovAndCoC.y      += LocalTime * 1.5;
					CameraPosition	  = CameraHardCutA(CameraPosition, LocalTime);
					FovAndCoC.x		  = 5.0;
				}
				else
				{
					CameraPosition = CameraHardCutB(CameraPosition, LocalTime);
				}
			}

			FovAndCoC.z  = FovAndCoC.y + 30.0;
			FovAndCoC.x += tm.z/1200.0;
		}
		else
		if (iTime < 43.5)
		{
			FovAndCoC.yz = vec2(40.0, 150.0);
			LocalTime    = sat(iTime/6.0);

			if (iTime > 33.0)
			{
				CameraPosition.xy *= r2((sin(iTime/6.5)+cos(iTime/4.3)));
				CameraPosition.z  += abs(sin(iTime/9.9)+cos(iTime/5.3))*10.0;
			}
			else
			{
				CameraPosition.xy *= r2((sin(iTime/7.5)+cos(iTime/3.3)));
				CameraPosition.z  += abs(sin(iTime/9.25)+cos(iTime/4.1))*10.0;
			}
		}
		else
		if (iTime < 60.0)
		{
			CameraPosition = vec3(0.0, 40.0, 15.0);
			TargetPosition = vec3(0.0, 0.0, 8.0);
			
			CameraPosition.xy *= r2((noise2d(vec2(iTime * 0.25, BeatSum)) * 2.0 - 1.0) * 0.5);
			CameraPosition.z  += (noise2d(vec2(iTime * 0.35, BeatSum)) * 2.0 - 1.0) * 12.0;
		}
		else
		if (iTime < 83.5)
		{
			FovAndCoC.x	  += tm.z/1000.0;
			CameraPosition = vec3(0.0, 50.0, 15.0);
			TargetPosition = vec3(0.0, 10.0, 0.0);

			CameraPosition.y  *= hash11(floor(tm.w * 0.0005)) < 0.5 ? -1.0 : 1.0;
			CameraPosition.xy *= r2((noise2d(vec2(iTime * 0.3, BeatSum)) * 2.0 - 1.0) * 0.25);
			CameraPosition.z  -= (noise2d(vec2(iTime * 0.35, BeatSum)) ) * 8.0;
		}
		else
		{
			FovAndCoC.yz   = vec2(50.0, 200.0);
			FovAndCoC.x	  += tm.z/666.0;
			CameraPosition = vec3(10.0, -45.0, 40.0);
			TargetPosition = vec3(10.0, -20.0, 5.0);

			if (iTime < 99.0)
			{
				CameraPosition.y*= hash11(floor(tm.w * 0.0005)) < 0.5 ? -0.3 : 1.0;
			}
			else
			{
				CameraPosition.y = 50.0;
				TargetPosition.x = -10.0;
				BeatSum          = 0.5;
			}

			CameraPosition.xy *= r2((noise2d(vec2(iTime * 0.3, BeatSum)) * 2.0 - 1.0) * 0.2);
			CameraPosition.z  -= (noise2d(vec2(iTime * 0.35, BeatSum))) * 20.0;
			UpVector.xz       *= r2((BeatSum-1.5) / 5.0);
		}

		if (i_DebugCamera)
		{
			FovAndCoC      = vec3(2.25, 50.0, 333.0);
			CameraPosition = vec3(0.0, 15.0, 5.0);
			TargetPosition = vec3(0.0, 0.0, 5.0);
			UpVector       = vec3(0.0, 0.0, 1.0);

			CameraPosition.xy *= r2(sin(iTime));
		}

		// Write camera setup results
		if (PixelIndex == 1)
			o = vec4(CameraPosition, FovAndCoC.x);

		if (PixelIndex == 2)
			o = vec4(TargetPosition, FovAndCoC.y);

		if (PixelIndex == 3)
			o = vec4(UpVector, FovAndCoC.z);
	}
	else
	if (PixelIndex < 20) // [4, 5, 6, 7][8, 9, 10, 11][12, 13, 14, 15][16, 17, 18, 19]
	{
		// Animations

		int   LOff       = ((PixelIndex-4)/4)*4;
		float LTime0     = iTime + (hash11(float(LOff)*37.0)*2.0-1.0) * 0.1;
		float LTime1     = iTime + (hash11(float(LOff)*41.0)*2.0-1.0) * (iTime < 99.0 ? 0.05 : 0.15);
		vec4  HeadAngles = vec4(sin(tm.z/120.0)*0.5, clamp(0.4-tm.z/150.0, -0.5, 0.5), 0, 0);
		vec4  LeftLegAngles, RightLegAngles, HandAngles;
		
		if (iTime < 6.25 || (iTime > 23.0 && iTime < 43.5) || ((iTime > 60.0 && iTime < 62.0) || iTime > 116.0))
		{
			// Calm animation

			HeadAngles.z = clamp(cos(LTime0*7.2)*0.3, -0.25, -0.1); // Slight Up-Down movement

			// Animation of legs

			LeftLegAngles = RightLegAngles = vec4(MachineLifeFormLegIK(HeadAngles.z, 0.0).xy, mix(0.3, 0.55, sat(HeadAngles.z / -0.25)), 0);

			// Animation of hands

			HandAngles = vec4(MachineLifeFormArmIK(vec3(abs(sin(LTime0*4.0))*0.66, -1.6 + (iTime > 31.5 ? ((sin(sat((LTime0-31.5))*i_MPI) + sin(sat((LTime0-39.5))*i_MPI)) * 4.0) : 0.0), 0.0)), MachineLifeFormArmIK(vec3(abs(sin(LTime0*3.95))*0.66, -1.4, 0.0)));
		}
		else
		{
			// Full jump animation
			// Jump in sync with music beat ...

			float JOff = 1.0;

			if (iTime > 43.0)
				JOff = 0.7;

			if (iTime > 60.0)
				JOff = 0.4;

			if (iTime > 83.5)
				JOff = 0.1;

			if (iTime > 99.0)
				JOff = -0.1;

			HeadAngles.z = clamp(pow(abs(sin(JOff + LTime1 * 6.3)), 1.25) * 1.5 - 0.5, -0.35, 1.25);

			// Animation of legs

			LeftLegAngles  = vec4(MachineLifeFormLegIK(HeadAngles.z, sin(LTime0*4.16)*0.125), 0);
			RightLegAngles = vec4(MachineLifeFormLegIK(HeadAngles.z, cos(LTime0*3.45)*0.125), 0);

			// Animation of hands

			HandAngles = vec4(MachineLifeFormArmIK(vec3(abs(sin(LTime0*5.9))*1.25, cos(LTime0*3.71)*1.5, 100.0)), MachineLifeFormArmIK(vec3(abs(sin(LTime0*5.8))*1.25, cos(LTime0*3.8)*1.5, 100.0)));
		}

		// Output result

		HeadAngles.z += 0.05;

		if (PixelIndex == 4+LOff)
			o = HeadAngles;

		if (PixelIndex == 5+LOff)
			o = HandAngles;

		if (PixelIndex == 6+LOff)
			o = LeftLegAngles;

		if (PixelIndex == 7+LOff)
			o = RightLegAngles;
	}
	else
	{
		o = vec4(0.0);
	}

	return;
}

///////////////////////////////////////////////////////////////////////////////
