text: /**
   * This class generates custom mipmaps for a roughness map by encoding the lost variation in the
   * normal map mip levels as increased roughness in the corresponding roughness mip levels. This
   * helps with rendering accuracy for MeshStandardMaterial, and also helps with anti-aliasing when
   * using PMREM. If the normal map is larger than the roughness map, the roughness map will be
   * enlarged to match the dimensions of the normal map.
   */
  
  import {
  	MathUtils,
  	Mesh,
  	NoBlending,
  	OrthographicCamera,
  	PlaneGeometry,
  	RawShaderMaterial,
  	Vector2,
  	WebGLRenderTarget
  } from 'three';
  
  const _mipmapMaterial = _getMipmapMaterial();
  
  const _mesh = new Mesh( new PlaneGeometry( 2, 2 ), _mipmapMaterial );
  
  const _flatCamera = new OrthographicCamera( 0, 1, 0, 1, 0, 1 );
  
  let _tempTarget = null;
  
  let _renderer = null;
  
  class RoughnessMipmapper {
  
  	constructor( renderer ) {
  
  		_renderer = renderer;
  
  		_renderer.compile( _mesh, _flatCamera );
  
  	}
  
  	generateMipmaps( material ) {
  
  		if ( 'roughnessMap' in material === false ) return;
  
  		const { roughnessMap, normalMap } = material;
  
  		if ( roughnessMap === null || normalMap === null || ! roughnessMap.generateMipmaps || material.userData.roughnessUpdated ) return;
  
  		material.userData.roughnessUpdated = true;
  
  		let width = Math.max( roughnessMap.image.width, normalMap.image.width );
  		let height = Math.max( roughnessMap.image.height, normalMap.image.height );
  
  		if ( ! MathUtils.isPowerOfTwo( width ) || ! MathUtils.isPowerOfTwo( height ) ) return;
  
  		const oldTarget = _renderer.getRenderTarget();
  
  		const autoClear = _renderer.autoClear;
  
  		_renderer.autoClear = false;
  
  		if ( _tempTarget === null || _tempTarget.width !== width || _tempTarget.height !== height ) {
  
  			if ( _tempTarget !== null ) _tempTarget.dispose();
  
  			_tempTarget = new WebGLRenderTarget( width, height, { depthBuffer: false } );
  
  			_tempTarget.scissorTest = true;
  
  		}
  
  		if ( width !== roughnessMap.image.width || height !== roughnessMap.image.height ) {
  
  			const params = {
  				wrapS: roughnessMap.wrapS,
  				wrapT: roughnessMap.wrapT,
  				magFilter: roughnessMap.magFilter,
  				minFilter: roughnessMap.minFilter,
  				depthBuffer: false
  			};
  
  			const newRoughnessTarget = new WebGLRenderTarget( width, height, params );
  
  			newRoughnessTarget.texture.generateMipmaps = true;
  
  			// Setting the render target causes the memory to be allocated.
  
  			_renderer.setRenderTarget( newRoughnessTarget );
  
  			material.roughnessMap = newRoughnessTarget.texture;
  
  			if ( material.metalnessMap == roughnessMap ) material.metalnessMap = material.roughnessMap;
  
  			if ( material.aoMap == roughnessMap ) material.aoMap = material.roughnessMap;
  
  			// Copy UV transform parameters
  
  			material.roughnessMap.offset.copy( roughnessMap.offset );
  			material.roughnessMap.repeat.copy( roughnessMap.repeat );
  			material.roughnessMap.center.copy( roughnessMap.center );
  			material.roughnessMap.rotation = roughnessMap.rotation;
  
  			material.roughnessMap.matrixAutoUpdate = roughnessMap.matrixAutoUpdate;
  			material.roughnessMap.matrix.copy( roughnessMap.matrix );
  
  		}
  
  		_mipmapMaterial.uniforms.roughnessMap.value = roughnessMap;
  
  		_mipmapMaterial.uniforms.normalMap.value = normalMap;
  
  		const position = new Vector2( 0, 0 );
  
  		const texelSize = _mipmapMaterial.uniforms.texelSize.value;
  
  		for ( let mip = 0; width >= 1 && height >= 1; ++ mip, width /= 2, height /= 2 ) {
  
  			// Rendering to a mip level is not allowed in webGL1. Instead we must set
  			// up a secondary texture to write the result to, then copy it back to the
  			// proper mipmap level.
  
  			texelSize.set( 1.0 / width, 1.0 / height );
  
  			if ( mip == 0 ) texelSize.set( 0.0, 0.0 );
  
  			_tempTarget.viewport.set( position.x, position.y, width, height );
  
  			_tempTarget.scissor.set( position.x, position.y, width, height );
  
  			_renderer.setRenderTarget( _tempTarget );
  
  			_renderer.render( _mesh, _flatCamera );
  
  			_renderer.copyFramebufferToTexture( position, material.roughnessMap, mip );
  
  			_mipmapMaterial.uniforms.roughnessMap.value = material.roughnessMap;
  
  		}
  
  		if ( roughnessMap !== material.roughnessMap ) roughnessMap.dispose();
  
  		_renderer.setRenderTarget( oldTarget );
  
  		_renderer.autoClear = autoClear;
  
  	}
  
  	dispose() {
  
  		_mipmapMaterial.dispose();
  
  		_mesh.geometry.dispose();
  
  		if ( _tempTarget != null ) _tempTarget.dispose();
  
  	}
  
  }
  
  function _getMipmapMaterial() {
  
  	const shaderMaterial = new RawShaderMaterial( {
  
  		uniforms: {
  			roughnessMap: { value: null },
  			normalMap: { value: null },
  			texelSize: { value: new Vector2( 1, 1 ) }
  		},
  
  		vertexShader: /* glsl */`
  			precision mediump float;
  			precision mediump int;
  
  			attribute vec3 position;
  			attribute vec2 uv;
  
  			varying vec2 vUv;
  
  			void main() {
  
  				vUv = uv;
  
  				gl_Position = vec4( position, 1.0 );
  
  			}
  		`,
  
  		fragmentShader: /* glsl */`
  			precision mediump float;
  			precision mediump int;
  
  			varying vec2 vUv;
  
  			uniform sampler2D roughnessMap;
  			uniform sampler2D normalMap;
  			uniform vec2 texelSize;
  
  			#define ENVMAP_TYPE_CUBE_UV
  
  			vec4 envMapTexelToLinear( vec4 a ) { return a; }
  
  			#include <cube_uv_reflection_fragment>
  
  			float roughnessToVariance( float roughness ) {
  
  				float variance = 0.0;
  
  				if ( roughness >= r1 ) {
  
  					variance = ( r0 - roughness ) * ( v1 - v0 ) / ( r0 - r1 ) + v0;
  
  				} else if ( roughness >= r4 ) {
  
  					variance = ( r1 - roughness ) * ( v4 - v1 ) / ( r1 - r4 ) + v1;
  
  				} else if ( roughness >= r5 ) {
  
  					variance = ( r4 - roughness ) * ( v5 - v4 ) / ( r4 - r5 ) + v4;
  
  				} else {
  
  					float roughness2 = roughness * roughness;
  
  					variance = 1.79 * roughness2 * roughness2;
  
  				}
  
  				return variance;
  
  			}
  
  			float varianceToRoughness( float variance ) {
  
  				float roughness = 0.0;
  
  				if ( variance >= v1 ) {
  
  					roughness = ( v0 - variance ) * ( r1 - r0 ) / ( v0 - v1 ) + r0;
  
  				} else if ( variance >= v4 ) {
  
  					roughness = ( v1 - variance ) * ( r4 - r1 ) / ( v1 - v4 ) + r1;
  
  				} else if ( variance >= v5 ) {
  
  					roughness = ( v4 - variance ) * ( r5 - r4 ) / ( v4 - v5 ) + r4;
  
  				} else {
  
  					roughness = pow( 0.559 * variance, 0.25 ); // 0.559 = 1.0 / 1.79
  
  				}
  
  				return roughness;
  
  			}
  
  			void main() {
  
  				gl_FragColor = texture2D( roughnessMap, vUv, - 1.0 );
  
  				if ( texelSize.x == 0.0 ) return;
  
  				float roughness = gl_FragColor.g;
  
  				float variance = roughnessToVariance( roughness );
  
  				vec3 avgNormal;
  
  				for ( float x = - 1.0; x < 2.0; x += 2.0 ) {
  
  					for ( float y = - 1.0; y < 2.0; y += 2.0 ) {
  
  						vec2 uv = vUv + vec2( x, y ) * 0.25 * texelSize;
  
  						avgNormal += normalize( texture2D( normalMap, uv, - 1.0 ).xyz - 0.5 );
  
  					}
  
  				}
  
  				variance += 1.0 - 0.25 * length( avgNormal );
  
  				gl_FragColor.g = varianceToRoughness( variance );
  
  			}
  		`,
  
  		blending: NoBlending,
  		depthTest: false,
  		depthWrite: false
  
  	} );
  
  	shaderMaterial.type = 'RoughnessMipmapper';
  
  	return shaderMaterial;
  
  }
  
  export { RoughnessMipmapper };
  