( function () {

	/**
 * Generate a texture that represents the luminosity of the current scene, adapted over time
 * to simulate the optic nerve responding to the amount of light it is receiving.
 * Based on a GDC2007 presentation by Wolfgang Engel titled "Post-Processing Pipeline"
 *
 * Full-screen tone-mapping shader based on http://www.graphics.cornell.edu/~jaf/publications/sig02_paper.pdf
 */

	class AdaptiveToneMappingPass extends THREE.Pass {

		constructor( adaptive, resolution ) {

			super();
			this.resolution = resolution !== undefined ? resolution : 256;
			this.needsInit = true;
			this.adaptive = adaptive !== undefined ? !! adaptive : true;
			this.luminanceRT = null;
			this.previousLuminanceRT = null;
			this.currentLuminanceRT = null;
			if ( THREE.CopyShader === undefined ) console.error( 'THREE.AdaptiveToneMappingPass relies on THREE.CopyShader' );
			const copyShader = THREE.CopyShader;
			this.copyUniforms = THREE.UniformsUtils.clone( copyShader.uniforms );
			this.materialCopy = new THREE.ShaderMaterial( {
				uniforms: this.copyUniforms,
				vertexShader: copyShader.vertexShader,
				fragmentShader: copyShader.fragmentShader,
				blending: THREE.NoBlending,
				depthTest: false
			} );
			if ( THREE.LuminosityShader === undefined ) console.error( 'THREE.AdaptiveToneMappingPass relies on THREE.LuminosityShader' );
			this.materialLuminance = new THREE.ShaderMaterial( {
				uniforms: THREE.UniformsUtils.clone( THREE.LuminosityShader.uniforms ),
				vertexShader: THREE.LuminosityShader.vertexShader,
				fragmentShader: THREE.LuminosityShader.fragmentShader,
				blending: THREE.NoBlending
			} );
			this.adaptLuminanceShader = {
				defines: {
					'MIP_LEVEL_1X1': ( Math.log( this.resolution ) / Math.log( 2.0 ) ).toFixed( 1 )
				},
				uniforms: {
					'lastLum': {
						value: null
					},
					'currentLum': {
						value: null
					},
					'minLuminance': {
						value: 0.01
					},
					'delta': {
						value: 0.016
					},
					'tau': {
						value: 1.0
					}
				},
				vertexShader: `varying vec2 vUv;

				void main() {

					vUv = uv;
					gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

				}`,
				fragmentShader: `varying vec2 vUv;

				uniform sampler2D lastLum;
				uniform sampler2D currentLum;
				uniform float minLuminance;
				uniform float delta;
				uniform float tau;

				void main() {

					vec4 lastLum = texture2D( lastLum, vUv, MIP_LEVEL_1X1 );
					vec4 currentLum = texture2D( currentLum, vUv, MIP_LEVEL_1X1 );

					float fLastLum = max( minLuminance, lastLum.r );
					float fCurrentLum = max( minLuminance, currentLum.r );

					//The adaption seems to work better in extreme lighting differences
					//if the input luminance is squared.
					fCurrentLum *= fCurrentLum;

					// Adapt the luminance using Pattanaik's technique
					float fAdaptedLum = fLastLum + (fCurrentLum - fLastLum) * (1.0 - exp(-delta * tau));
					// "fAdaptedLum = sqrt(fAdaptedLum);
					gl_FragColor.r = fAdaptedLum;
				}`
			};
			this.materialAdaptiveLum = new THREE.ShaderMaterial( {
				uniforms: THREE.UniformsUtils.clone( this.adaptLuminanceShader.uniforms ),
				vertexShader: this.adaptLuminanceShader.vertexShader,
				fragmentShader: this.adaptLuminanceShader.fragmentShader,
				defines: Object.assign( {}, this.adaptLuminanceShader.defines ),
				blending: THREE.NoBlending
			} );
			if ( THREE.ToneMapShader === undefined ) console.error( 'THREE.AdaptiveToneMappingPass relies on THREE.ToneMapShader' );
			this.materialToneMap = new THREE.ShaderMaterial( {
				uniforms: THREE.UniformsUtils.clone( THREE.ToneMapShader.uniforms ),
				vertexShader: THREE.ToneMapShader.vertexShader,
				fragmentShader: THREE.ToneMapShader.fragmentShader,
				blending: THREE.NoBlending
			} );
			this.fsQuad = new THREE.FullScreenQuad( null );

		}

		render( renderer, writeBuffer, readBuffer, deltaTime
			/*, maskActive*/
		) {

			if ( this.needsInit ) {

				this.reset( renderer );
				this.luminanceRT.texture.type = readBuffer.texture.type;
				this.previousLuminanceRT.texture.type = readBuffer.texture.type;
				this.currentLuminanceRT.texture.type = readBuffer.texture.type;
				this.needsInit = false;

			}

			if ( this.adaptive ) {

				//Render the luminance of the current scene into a render target with mipmapping enabled
				this.fsQuad.material = this.materialLuminance;
				this.materialLuminance.uniforms.tDiffuse.value = readBuffer.texture;
				renderer.setRenderTarget( this.currentLuminanceRT );
				this.fsQuad.render( renderer ); //Use the new luminance values, the previous luminance and the frame delta to
				//adapt the luminance over time.

				this.fsQuad.material = this.materialAdaptiveLum;
				this.materialAdaptiveLum.uniforms.delta.value = deltaTime;
				this.materialAdaptiveLum.uniforms.lastLum.value = this.previousLuminanceRT.texture;
				this.materialAdaptiveLum.uniforms.currentLum.value = this.currentLuminanceRT.texture;
				renderer.setRenderTarget( this.luminanceRT );
				this.fsQuad.render( renderer ); //Copy the new adapted luminance value so that it can be used by the next frame.

				this.fsQuad.material = this.materialCopy;
				this.copyUniforms.tDiffuse.value = this.luminanceRT.texture;
				renderer.setRenderTarget( this.previousLuminanceRT );
				this.fsQuad.render( renderer );

			}

			this.fsQuad.material = this.materialToneMap;
			this.materialToneMap.uniforms.tDiffuse.value = readBuffer.texture;

			if ( this.renderToScreen ) {

				renderer.setRenderTarget( null );
				this.fsQuad.render( renderer );

			} else {

				renderer.setRenderTarget( writeBuffer );
				if ( this.clear ) renderer.clear();
				this.fsQuad.render( renderer );

			}

		}

		reset() {

			// render targets
			if ( this.luminanceRT ) {

				this.luminanceRT.dispose();

			}

			if ( this.currentLuminanceRT ) {

				this.currentLuminanceRT.dispose();

			}

			if ( this.previousLuminanceRT ) {

				this.previousLuminanceRT.dispose();

			}

			const pars = {
				minFilter: THREE.LinearFilter,
				magFilter: THREE.LinearFilter,
				format: THREE.RGBAFormat
			}; // was RGB format. changed to RGBA format. see discussion in #8415 / #8450

			this.luminanceRT = new THREE.WebGLRenderTarget( this.resolution, this.resolution, pars );
			this.luminanceRT.texture.name = 'AdaptiveToneMappingPass.l';
			this.luminanceRT.texture.generateMipmaps = false;
			this.previousLuminanceRT = new THREE.WebGLRenderTarget( this.resolution, this.resolution, pars );
			this.previousLuminanceRT.texture.name = 'AdaptiveToneMappingPass.pl';
			this.previousLuminanceRT.texture.generateMipmaps = false; // We only need mipmapping for the current luminosity because we want a down-sampled version to sample in our adaptive shader

			pars.minFilter = THREE.LinearMipmapLinearFilter;
			pars.generateMipmaps = true;
			this.currentLuminanceRT = new THREE.WebGLRenderTarget( this.resolution, this.resolution, pars );
			this.currentLuminanceRT.texture.name = 'AdaptiveToneMappingPass.cl';

			if ( this.adaptive ) {

				this.materialToneMap.defines[ 'ADAPTED_LUMINANCE' ] = '';
				this.materialToneMap.uniforms.luminanceMap.value = this.luminanceRT.texture;

			} //Put something in the adaptive luminance texture so that the scene can render initially


			this.fsQuad.material = new THREE.MeshBasicMaterial( {
				color: 0x777777
			} );
			this.materialLuminance.needsUpdate = true;
			this.materialAdaptiveLum.needsUpdate = true;
			this.materialToneMap.needsUpdate = true; // renderer.render( this.scene, this.camera, this.luminanceRT );
			// renderer.render( this.scene, this.camera, this.previousLuminanceRT );
			// renderer.render( this.scene, this.camera, this.currentLuminanceRT );

		}

		setAdaptive( adaptive ) {

			if ( adaptive ) {

				this.adaptive = true;
				this.materialToneMap.defines[ 'ADAPTED_LUMINANCE' ] = '';
				this.materialToneMap.uniforms.luminanceMap.value = this.luminanceRT.texture;

			} else {

				this.adaptive = false;
				delete this.materialToneMap.defines[ 'ADAPTED_LUMINANCE' ];
				this.materialToneMap.uniforms.luminanceMap.value = null;

			}

			this.materialToneMap.needsUpdate = true;

		}

		setAdaptionRate( rate ) {

			if ( rate ) {

				this.materialAdaptiveLum.uniforms.tau.value = Math.abs( rate );

			}

		}

		setMinLuminance( minLum ) {

			if ( minLum ) {

				this.materialToneMap.uniforms.minLuminance.value = minLum;
				this.materialAdaptiveLum.uniforms.minLuminance.value = minLum;

			}

		}

		setMaxLuminance( maxLum ) {

			if ( maxLum ) {

				this.materialToneMap.uniforms.maxLuminance.value = maxLum;

			}

		}

		setAverageLuminance( avgLum ) {

			if ( avgLum ) {

				this.materialToneMap.uniforms.averageLuminance.value = avgLum;

			}

		}

		setMiddleGrey( middleGrey ) {

			if ( middleGrey ) {

				this.materialToneMap.uniforms.middleGrey.value = middleGrey;

			}

		}

		dispose() {

			if ( this.luminanceRT ) {

				this.luminanceRT.dispose();

			}

			if ( this.previousLuminanceRT ) {

				this.previousLuminanceRT.dispose();

			}

			if ( this.currentLuminanceRT ) {

				this.currentLuminanceRT.dispose();

			}

			if ( this.materialLuminance ) {

				this.materialLuminance.dispose();

			}

			if ( this.materialAdaptiveLum ) {

				this.materialAdaptiveLum.dispose();

			}

			if ( this.materialCopy ) {

				this.materialCopy.dispose();

			}

			if ( this.materialToneMap ) {

				this.materialToneMap.dispose();

			}

		}

	}

	THREE.AdaptiveToneMappingPass = AdaptiveToneMappingPass;

} )();
