// 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 };
