text: import { FrontSide, BackSide, DoubleSide, RGBAFormat, NearestFilter, LinearFilter, PCFShadowMap, VSMShadowMap, RGBADepthPacking, NoBlending } from '../../constants.js';
  import { WebGLRenderTarget } from '../WebGLRenderTarget.js';
  import { MeshDepthMaterial } from '../../materials/MeshDepthMaterial.js';
  import { MeshDistanceMaterial } from '../../materials/MeshDistanceMaterial.js';
  import { ShaderMaterial } from '../../materials/ShaderMaterial.js';
  import { BufferAttribute } from '../../core/BufferAttribute.js';
  import { BufferGeometry } from '../../core/BufferGeometry.js';
  import { Mesh } from '../../objects/Mesh.js';
  import { Vector4 } from '../../math/Vector4.js';
  import { Vector2 } from '../../math/Vector2.js';
  import { Frustum } from '../../math/Frustum.js';
  
  import vsm_frag from '../shaders/ShaderLib/vsm_frag.glsl.js';
  import vsm_vert from '../shaders/ShaderLib/vsm_vert.glsl.js';
  
  function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
  
  	let _frustum = new Frustum();
  
  	const _shadowMapSize = new Vector2(),
  		_viewportSize = new Vector2(),
  
  		_viewport = new Vector4(),
  
  		_depthMaterials = [],
  		_distanceMaterials = [],
  
  		_materialCache = {};
  
  	const shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide };
  
  	const shadowMaterialVertical = new ShaderMaterial( {
  
  		defines: {
  			SAMPLE_RATE: 2.0 / 8.0,
  			HALF_SAMPLE_RATE: 1.0 / 8.0
  		},
  
  		uniforms: {
  			shadow_pass: { value: null },
  			resolution: { value: new Vector2() },
  			radius: { value: 4.0 }
  		},
  
  		vertexShader: vsm_vert,
  
  		fragmentShader: vsm_frag
  
  	} );
  
  	const shadowMaterialHorizontal = shadowMaterialVertical.clone();
  	shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1;
  
  	const fullScreenTri = new BufferGeometry();
  	fullScreenTri.setAttribute(
  		'position',
  		new BufferAttribute(
  			new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ),
  			3
  		)
  	);
  
  	const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical );
  
  	const scope = this;
  
  	this.enabled = false;
  
  	this.autoUpdate = true;
  	this.needsUpdate = false;
  
  	this.type = PCFShadowMap;
  
  	this.render = function ( lights, scene, camera ) {
  
  		if ( scope.enabled === false ) return;
  		if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;
  
  		if ( lights.length === 0 ) return;
  
  		const currentRenderTarget = _renderer.getRenderTarget();
  		const activeCubeFace = _renderer.getActiveCubeFace();
  		const activeMipmapLevel = _renderer.getActiveMipmapLevel();
  
  		const _state = _renderer.state;
  
  		// Set GL state for depth map.
  		_state.setBlending( NoBlending );
  		_state.buffers.color.setClear( 1, 1, 1, 1 );
  		_state.buffers.depth.setTest( true );
  		_state.setScissorTest( false );
  
  		// render depth map
  		for ( let i = 0, il = lights.length; i < il; i ++ ) {
  
  			const light = lights[ i ];
  			const shadow = light.shadow;
  
  			if ( shadow === undefined ) {
  
  				console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' );
  				continue;
  
  			}
  
  			if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue;
  
  			_shadowMapSize.copy( shadow.mapSize );
  
  			const shadowFrameExtents = shadow.getFrameExtents();
  
  			_shadowMapSize.multiply( shadowFrameExtents );
  
  			_viewportSize.copy( shadow.mapSize );
  
  			if ( _shadowMapSize.x > maxTextureSize || _shadowMapSize.y > maxTextureSize ) {
  
  				if ( _shadowMapSize.x > maxTextureSize ) {
  
  					_viewportSize.x = Math.floor( maxTextureSize / shadowFrameExtents.x );
  					_shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x;
  					shadow.mapSize.x = _viewportSize.x;
  
  				}
  
  				if ( _shadowMapSize.y > maxTextureSize ) {
  
  					_viewportSize.y = Math.floor( maxTextureSize / shadowFrameExtents.y );
  					_shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y;
  					shadow.mapSize.y = _viewportSize.y;
  
  				}
  
  			}
  
  			if ( shadow.map === null && ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
  
  				const pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat };
  
  				shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
  				shadow.map.texture.name = light.name + '.shadowMap';
  
  				shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
  
  				shadow.camera.updateProjectionMatrix();
  
  			}
  
  			if ( shadow.map === null ) {
  
  				const pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat };
  
  				shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
  				shadow.map.texture.name = light.name + '.shadowMap';
  
  				shadow.camera.updateProjectionMatrix();
  
  			}
  
  			_renderer.setRenderTarget( shadow.map );
  			_renderer.clear();
  
  			const viewportCount = shadow.getViewportCount();
  
  			for ( let vp = 0; vp < viewportCount; vp ++ ) {
  
  				const viewport = shadow.getViewport( vp );
  
  				_viewport.set(
  					_viewportSize.x * viewport.x,
  					_viewportSize.y * viewport.y,
  					_viewportSize.x * viewport.z,
  					_viewportSize.y * viewport.w
  				);
  
  				_state.viewport( _viewport );
  
  				shadow.updateMatrices( light, vp );
  
  				_frustum = shadow.getFrustum();
  				
  				renderObject( scene, camera, shadow.camera, light, this.type );
  
  			}
  
  			// do blur pass for VSM
  
  			if ( ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
  
  				VSMPass( shadow, camera );
  
  			}
  
  			shadow.needsUpdate = false;
  
  		}
  
  		scope.needsUpdate = false;
  
  		_renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel );
  
  	};
  
  	function VSMPass( shadow, camera ) {
  
  		const geometry = _objects.update( fullScreenMesh );
  
  		// vertical pass
  
  		shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture;
  		shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize;
  		shadowMaterialVertical.uniforms.radius.value = shadow.radius;
  		_renderer.setRenderTarget( shadow.mapPass );
  		_renderer.clear();
  		_renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null );
  
  		// horizontal pass
  
  		shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture;
  		shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize;
  		shadowMaterialHorizontal.uniforms.radius.value = shadow.radius;
  		_renderer.setRenderTarget( shadow.map );
  		_renderer.clear();
  		_renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null );
  
  	}
  
  	function getDepthMaterialVariant( useMorphing, useSkinning, useInstancing ) {
  
  		const index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;
  
  		let material = _depthMaterials[ index ];
  
  		if ( material === undefined ) {
  
  			material = new MeshDepthMaterial( {
  
  				depthPacking: RGBADepthPacking,
  
  				morphTargets: useMorphing,
  				skinning: useSkinning
  
  			} );
  
  			_depthMaterials[ index ] = material;
  
  		}
  
  		return material;
  
  	}
  
  	function getDistanceMaterialVariant( useMorphing, useSkinning, useInstancing ) {
  
  		const index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;
  
  		let material = _distanceMaterials[ index ];
  
  		if ( material === undefined ) {
  
  			material = new MeshDistanceMaterial( {
  
  				morphTargets: useMorphing,
  				skinning: useSkinning
  
  			} );
  
  			_distanceMaterials[ index ] = material;
  
  		}
  
  		return material;
  
  	}
  
  	function getDepthMaterial( object, geometry, material, light, shadowCameraNear, shadowCameraFar, type ) {
  
  		let result = null;
  
  		let getMaterialVariant = getDepthMaterialVariant;
  		let customMaterial = object.customDepthMaterial;
  
  		if ( light.isPointLight === true ) {
  
  			getMaterialVariant = getDistanceMaterialVariant;
  			customMaterial = object.customDistanceMaterial;
  
  		}
  
  		if ( customMaterial === undefined ) {
  
  			let useMorphing = false;
  
  			if ( material.morphTargets === true ) {
  
  				useMorphing = geometry.morphAttributes && geometry.morphAttributes.position && geometry.morphAttributes.position.length > 0;
  
  			}
  
  			let useSkinning = false;
  
  			if ( object.isSkinnedMesh === true ) {
  
  				if ( material.skinning === true ) {
  
  					useSkinning = true;
  
  				} else {
  
  					console.warn( 'THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:', object );
  
  				}
  
  			}
  
  			const useInstancing = object.isInstancedMesh === true;
  
  			result = getMaterialVariant( useMorphing, useSkinning, useInstancing );
  
  		} else {
  
  			result = customMaterial;
  
  		}
  
  		if ( _renderer.localClippingEnabled &&
  				material.clipShadows === true &&
  				material.clippingPlanes.length !== 0 ) {
  
  			// in this case we need a unique material instance reflecting the
  			// appropriate state
  
  			const keyA = result.uuid, keyB = material.uuid;
  
  			let materialsForVariant = _materialCache[ keyA ];
  
  			if ( materialsForVariant === undefined ) {
  
  				materialsForVariant = {};
  				_materialCache[ keyA ] = materialsForVariant;
  
  			}
  
  			let cachedMaterial = materialsForVariant[ keyB ];
  
  			if ( cachedMaterial === undefined ) {
  
  				cachedMaterial = result.clone();
  				materialsForVariant[ keyB ] = cachedMaterial;
  
  			}
  
  			result = cachedMaterial;
  
  		}
  
  		result.visible = material.visible;
  		result.wireframe = material.wireframe;
  
  		if ( type === VSMShadowMap ) {
  
  			result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side;
  
  		} else {
  
  			result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ];
  
  		}
  
  		result.clipShadows = material.clipShadows;
  		result.clippingPlanes = material.clippingPlanes;
  		result.clipIntersection = material.clipIntersection;
  
  		result.wireframeLinewidth = material.wireframeLinewidth;
  		result.linewidth = material.linewidth;
  
  		if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) {
  
  			result.referencePosition.setFromMatrixPosition( light.matrixWorld );
  			result.nearDistance = shadowCameraNear;
  			result.farDistance = shadowCameraFar;
  
  		}
  
  		return result;
  
  	}
  
  	function renderObject( object, camera, shadowCamera, light, type ) {
  
  		if ( object.visible === false ) return;
  
  		const visible = object.layers.test( camera.layers );
  
  		if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) {
  			
  			if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) {
  
  				object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
  	
  				if (object.isInstancedMesh) {
  					object.updateMatrixes( object.modelViewMatrix );
  				}
  
  				const geometry = _objects.update( object );
  				const material = object.material;
  
  				if ( Array.isArray( material ) ) {
  
  					const groups = geometry.groups;
  
  					for ( let k = 0, kl = groups.length; k < kl; k ++ ) {
  
  						const group = groups[ k ];
  						const groupMaterial = material[ group.materialIndex ];
  
  						if ( groupMaterial && groupMaterial.visible ) {
  
  							const depthMaterial = getDepthMaterial( object, geometry, groupMaterial, light, shadowCamera.near, shadowCamera.far, type );
  
  							_renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group );
  
  						}
  
  					}
  
  				} else if ( material.visible ) {
  
  					const depthMaterial = getDepthMaterial( object, geometry, material, light, shadowCamera.near, shadowCamera.far, type );
  
  					_renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null );
  
  				}
  
  			}
  
  		}
  
  		const children = object.children;
  
  		for ( let i = 0, l = children.length; i < l; i ++ ) {
  
  			renderObject( children[ i ], camera, shadowCamera, light, type );
  
  		}
  
  	}
  
  }
  
  
  export { WebGLShadowMap };
  