// Parallax Occlusion shaders from
//    http://sunandblackcat.com/tipFullView.php?topicid=28
// No tangent-space transforms logic based on
//   http://mmikkelsen3d.blogspot.sk/2012/02/parallaxpoc-mapping-and-no-tangent.html

var ParallaxShader = {
	// Ordered from fastest to best quality.
	modes: {
		none: "NO_PARALLAX",
		basic: "USE_BASIC_PARALLAX",
		steep: "USE_STEEP_PARALLAX",
		occlusion: "USE_OCLUSION_PARALLAX", // a.k.a. POM
		relief: "USE_RELIEF_PARALLAX"
	},

	uniforms: {
		"bumpMap": { value: null },
		"map": { value: null },
		"parallaxScale": { value: null },
		"parallaxMinLayers": { value: null },
		"parallaxMaxLayers": { value: null }
	},

	vertexShader: [
		"varying vec2 vUv;",
		"varying vec3 vViewPosition;",
		"varying vec3 vNormal;",

		"void main() {",

		"	vUv = uv;",
		"	vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
		"	vViewPosition = -mvPosition.xyz;",
		"	vNormal = normalize( normalMatrix * normal );",
		"	gl_Position = projectionMatrix * mvPosition;",

		"}"

	].join( "\n" ),

	fragmentShader: [
		"uniform sampler2D bumpMap;",
		"uniform sampler2D map;",

		"uniform float parallaxScale;",
		"uniform float parallaxMinLayers;",
		"uniform float parallaxMaxLayers;",

		"varying vec2 vUv;",
		"varying vec3 vViewPosition;",
		"varying vec3 vNormal;",

		"#ifdef USE_BASIC_PARALLAX",

		"	vec2 parallaxMap( in vec3 V ) {",

		"		float initialHeight = texture2D( bumpMap, vUv ).r;",

		// No Offset Limitting: messy, floating output at grazing angles.
		//"vec2 texCoordOffset = parallaxScale * V.xy / V.z * initialHeight;",

		// Offset Limiting
		"		vec2 texCoordOffset = parallaxScale * V.xy * initialHeight;",
		"		return vUv - texCoordOffset;",

		"	}",

		"#else",

		"	vec2 parallaxMap( in vec3 V ) {",

		// Determine number of layers from angle between V and N
		"		float numLayers = mix( parallaxMaxLayers, parallaxMinLayers, abs( dot( vec3( 0.0, 0.0, 1.0 ), V ) ) );",

		"		float layerHeight = 1.0 / numLayers;",
		"		float currentLayerHeight = 0.0;",
		// Shift of texture coordinates for each iteration
		"		vec2 dtex = parallaxScale * V.xy / V.z / numLayers;",

		"		vec2 currentTextureCoords = vUv;",

		"		float heightFromTexture = texture2D( bumpMap, currentTextureCoords ).r;",

		// while ( heightFromTexture > currentLayerHeight )
		// Infinite loops are not well supported. Do a "large" finite
		// loop, but not too large, as it slows down some compilers.
		"		for ( int i = 0; i < 30; i += 1 ) {",
		"			if ( heightFromTexture <= currentLayerHeight ) {",
		"				break;",
		"			}",
		"			currentLayerHeight += layerHeight;",
		// Shift texture coordinates along vector V
		"			currentTextureCoords -= dtex;",
		"			heightFromTexture = texture2D( bumpMap, currentTextureCoords ).r;",
		"		}",

		"		#ifdef USE_STEEP_PARALLAX",

		"			return currentTextureCoords;",

		"		#elif defined( USE_RELIEF_PARALLAX )",

		"			vec2 deltaTexCoord = dtex / 2.0;",
		"			float deltaHeight = layerHeight / 2.0;",

		// Return to the mid point of previous layer
		"			currentTextureCoords += deltaTexCoord;",
		"			currentLayerHeight -= deltaHeight;",

		// Binary search to increase precision of Steep Parallax Mapping
		"			const int numSearches = 5;",
		"			for ( int i = 0; i < numSearches; i += 1 ) {",

		"				deltaTexCoord /= 2.0;",
		"				deltaHeight /= 2.0;",
		"				heightFromTexture = texture2D( bumpMap, currentTextureCoords ).r;",
		// Shift along or against vector V
		"				if( heightFromTexture > currentLayerHeight ) {", // Below the surface

		"					currentTextureCoords -= deltaTexCoord;",
		"					currentLayerHeight += deltaHeight;",

		"				} else {", // above the surface

		"					currentTextureCoords += deltaTexCoord;",
		"					currentLayerHeight -= deltaHeight;",

		"				}",

		"			}",
		"			return currentTextureCoords;",

		"		#elif defined( USE_OCLUSION_PARALLAX )",

		"			vec2 prevTCoords = currentTextureCoords + dtex;",

		// Heights for linear interpolation
		"			float nextH = heightFromTexture - currentLayerHeight;",
		"			float prevH = texture2D( bumpMap, prevTCoords ).r - currentLayerHeight + layerHeight;",

		// Proportions for linear interpolation
		"			float weight = nextH / ( nextH - prevH );",

		// Interpolation of texture coordinates
		"			return prevTCoords * weight + currentTextureCoords * ( 1.0 - weight );",

		"		#else", // NO_PARALLAX

		"			return vUv;",

		"		#endif",

		"	}",
		"#endif",

		"vec2 perturbUv( vec3 surfPosition, vec3 surfNormal, vec3 viewPosition ) {",

 		"	vec2 texDx = dFdx( vUv );",
		"	vec2 texDy = dFdy( vUv );",

		"	vec3 vSigmaX = dFdx( surfPosition );",
		"	vec3 vSigmaY = dFdy( surfPosition );",
		"	vec3 vR1 = cross( vSigmaY, surfNormal );",
		"	vec3 vR2 = cross( surfNormal, vSigmaX );",
		"	float fDet = dot( vSigmaX, vR1 );",

		"	vec2 vProjVscr = ( 1.0 / fDet ) * vec2( dot( vR1, viewPosition ), dot( vR2, viewPosition ) );",
		"	vec3 vProjVtex;",
		"	vProjVtex.xy = texDx * vProjVscr.x + texDy * vProjVscr.y;",
		"	vProjVtex.z = dot( surfNormal, viewPosition );",

		"	return parallaxMap( vProjVtex );",
		"}",

		"void main() {",

		"	vec2 mapUv = perturbUv( -vViewPosition, normalize( vNormal ), normalize( vViewPosition ) );",
		"	gl_FragColor = texture2D( map, mapUv );",

		"}"

	].join( "\n" )

};

export { ParallaxShader };
