text: // 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 }
  