/*
 * Screen space rays by rimina
 */

THREE.DepthScreen = {

	uniforms: {
		"tDepth":   { type: "t", value: null },
		"tDiffuse":   { type: "t", value: null },
		"uNear": { type: "f", value: 0 },
		"uFar":   { type: "f", value: 0 },
		"uRo": { type: "v3", value: new THREE.Vector3(0, 0, 0)},
		"uRd": { type: "v3", value: new THREE.Vector3(0, 0, 0)},
		"uTime" : { type: "f", value: 0.0},
		"uResolution" : {type: "v2", value: new THREE.Vector2(0, 0)},
		"uFOV" : {type : "f", value: 50.0}
	},

	vertexShader: [

		"varying vec2 vUv;",
		"void main() {",
			"vUv = uv;",
			"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
		"}"

	].join("\n"),

	fragmentShader: [
		"#include <packing>",
        "uniform sampler2D tDepth;",
        "uniform sampler2D tDiffuse;",
        "uniform float uNear;",
		"uniform float uFar;",
		"uniform vec3 uRo;",
		"uniform vec3 uRd;",

		"uniform float uTime;",
		"uniform vec2 uResolution;",
		"uniform float uFOV;",

        "varying vec2 vUv;",

        "#define STEPS 28",
		"#define EPSILON 0.01",
		"#define FAR 200.0",
		"#define PI 3.14159265359",

		"#define BOUNCES 12",
		"#define SAMPLES 2.0",

		//Distance functions from Mercury's SDF library
		//http://mercury.sexy/hg_sdf/

		// Maximum/minumum elements of a vector
		"float vmax3(vec3 v) {",
			"return max(max(v.x, v.y), v.z);",
		"}",

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

		// Box: correct distance to corners
		"float fBox(vec3 p, vec3 b) {",
			"vec3 d = abs(p) - b;",
			"return length(max(d, vec3(0))) + vmax3(min(d, vec3(0)));",
		"}",

		// Plane with normal n (n is normalized) at some distance from the origin
		"float fPlane(vec3 p, vec3 n, float distanceFromOrigin) {",
			"return dot(p, n) + distanceFromOrigin;",
		"}",

		// Repeat in two dimensions
		"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;",
		"}",

		// 3D noise function (IQ)
		"float noise(vec3 p){",
			"vec3 ip = floor(p);",
		    "p -= ip;",
		    "vec3 s = vec3(7.0,157.0,113.0);",
		    "vec4 h = vec4(0.0, s.yz, s.y+s.z)+dot(ip, s);",
		    "p = p*p*(3.0-2.0*p);",
		    "h = mix(fract(sin(h)*43758.5), fract(sin(h+s.x)*43758.5), p.x);",
		    "h.xy = mix(h.xz, h.yw, p.y);",
		    "return mix(h.x, h.y, p.z);",
		"}",

		"vec2 dist(vec3 p){",
		    "float obj = fSphere(p, 10.0)-noise((p+uTime*10.0)*0.5)*2.0;",
			"return vec2(obj, 1.0);",
		"}",

		"vec2 march(vec3 ro, vec3 rd){",
		    "float t = 0.0;",
		    "vec3 p = ro;",
		    "for(int i = 0; i < STEPS; ++i){",
		        "float d = dist(p).x;",
		        "p += rd*d;",
		        "t += d;",
		        
		        "if(d < EPSILON || t >= FAR){",
		            "break;",
		        "}",
		    "}",
		    "p = ro+rd*t;",
    		"float id = dist(p).y;",
		    "return vec2(t, id);",
		"}",

		"float marchShadow(vec3 ro, vec3 rd){",
		    "float t = 0.0;",
		    "vec3 p = ro;",
		    "float s = 1.0;",
		    "for(int i = 0; i < STEPS; ++i){",
		        "float d = dist(p).x;",
		        "p += rd*d;",
		        "t += d;",
		        
		        "if(d < EPSILON){",
		            "s = 0.05;",
		            "break;",
		        "}",
		    "}",
		    "return s;",
		"}",

        "float depthScale(float depth){",
        	"return depth*uFar;",
        "}",

        "float depth(vec2 uv){",
        	"float z = texture2D( tDepth, uv).x;",
            "float view_z = perspectiveDepthToViewZ(z, uNear, uFar);",
            "float depth = viewZToPerspectiveDepth(view_z, uNear, uFar);",
            "return depthScale(depth);",
        "}",

        /*"vec3 normals2(){",
		    "vec2 eps = vec2(EPSILON, 0.0);",
		    "return normalize(vec3(",
		        "depth(vUv+eps.xy)-depth(vUv-eps.xy),",
		        "depth(vUv+eps.yx)-depth(vUv-eps.yx),",
		        "depth(vUv+eps.xy)-depth(vUv-eps.yx)",
		    "));",
		"}",*/

		"vec3 normals(vec3 p){",
		    "vec3 eps = vec3(EPSILON, 0.0, 0.0);",
		    "return normalize(vec3(",
		        "dist(p+eps.xyy).x-dist(p-eps.xyy).x,",
		        "dist(p+eps.yxy).x-dist(p-eps.yxy).x,",
		        "dist(p+eps.yyx).x-dist(p-eps.yyx).x",
		    "));",
		"}",


		"vec3 normals2(){",
			"float depth0 = depth(vUv);",
			"float depth1 = depth(vUv+vec2(1.0, 0.0));",
			"float depth2 = depth(vUv+vec2(0.0, 1.0));",

			"vec3 p0 = uRo + uRd*depth0;",
			"vec3 p1 = uRo + uRd*depth1;",
			"vec3 p2 = uRo + uRd*depth2;",

			"return normalize(cross(p2-p0, p1-p0));",
		"}",

        "vec3 shade(vec3 rd, vec3 ld, vec3 p, float id){",
        	"vec3 n = vec3(0.0);",
        	"vec3 color = vec3(0.0);",
        	"if( id < 0.0){",
        		//"n = smoothstep(vec3(0.0), vec3(1.0), normals2());",
        		"color = texture2D(tDiffuse, vUv).rgb;",
        	"}",
        	"else{",
        		"n = normals(p);",
        		"float lambertian = max(dot(n, ld), 0.0);",
			    "float angle = max(dot(reflect(ld, n), rd), 0.0);",
			    "float specular = pow(angle, 40.0);",
			    
			    "color = vec3(0.5)+",
			        "lambertian*vec3(0.2) +",
			        "specular*vec3(0.7, 0.7, 0.6);",

		        "float s = marchShadow(p+n*0.02, ld);",
		        "color *= s;",
        	"}",
		    
		    

		    "return color;",
		"}",

        "void main() {",

        	"vec2 q = -1.0+2.0*vUv;",
    		"q.x *= uResolution.x/uResolution.y;",

            "vec3 color = vec3(0.0);",
            "float d = depth(vUv);",

            "vec3 z = normalize(uRd - uRo);",
            "vec3 x = normalize(cross(z, vec3(0.0, 1.0, 0.0)));",
    		"vec3 y = normalize(cross(x, z));",
    		"vec3 rd = normalize(mat3(x, y, z)*vec3(q, radians(uFOV)));",

            "vec2 t = march(uRo, rd);",

            "if(d < t.x){",
            	"t.x = d;",
            	"t.y = -1.0;",
            "}",

            "vec3 p = uRo + rd*t.x;",
            "vec3 ld = normalize(uRo-p);",
            "if( t.x < uFar){",
    			"color = shade(rd, ld, p, t.y);",
    		"}",
            "gl_FragColor = vec4(color, 1.0);",
        "}"

	].join("\n")

};
