Source: scene/contact_shadow.js

import {
    Reporter,
	checkPropTypes,
	BuildableComponent
} from '../../src/main.js';

import {
    Camera,
    Scene,
	Color,
	WebGLRenderer,
	MeshBasicMaterial,
	WebGLRenderTarget,
	MeshDepthMaterial,
	RGBAFormat,
	PlaneGeometry,
	ShaderMaterial,
	Mesh,
	OrthographicCamera,
	Group,
	WireframeGeometry,
	LineSegments
} from '../../node_modules/three/build/three.module.js';

//THREE SHADERS
import { HorizontalBlurShader } from '../../node_modules/three/examples/jsm/shaders/HorizontalBlurShader.js';
import { VerticalBlurShader } from '../../node_modules/three/examples/jsm/shaders/VerticalBlurShader.js';

import { Project } from '../project.js';

class ContactShadow extends BuildableComponent {

	/**
    * @param {Reporter} reporter
    * @param {Object} settings
	* @param {Object} [settings.contactShadow]
    * @param {UUID} [settings.id] 
    * @param {Scene} [settings.scene] 
    * @param {Camera} [settings.camera] 
	* @param {WebGLRenderer} [settings.renderer] 
    */

	constructor( reporter, settings ){

		super(reporter, settings);

		checkPropTypes(
            settings,
            {
                
            },    
            {
				contactShadow: Object,
				scene: Scene,
				camera: Camera,
				renderer: WebGLRenderer
            }
		);

		
		//console.log( settings )

		//SETTINGS
		this.name = "ContactShadow";
		this.planeWidth = 30;
		this.planeHeight = 30;
		this.planeColor = "#ffffff";
		this.planeOpacity = 1, 
		this.cameraHeight = 0.5;
		this.shadowDarkness = 0.85;
		this.shadowOpacity = 1;
		this.blurAmount = 0.15;
		this.showWireframe = true;

		this._scene = settings.scene
		this._camera = settings.camera
		this._renderer = settings.renderer
		this.renderTarget = null;
		this.renderTargetBlur = null;
		this.renderTargets = null;

	}


	async _build(){

		// PLANE GEO
		const shadowPlaneGeometry = new PlaneGeometry( this.planeWidth, this.planeHeight ).rotateX( Math.PI / 2 ); //settings.plane.width, settings.plane.height
		shadowPlaneGeometry.name = "ContactShadow Plane";

		
		//PLANE MATERIAL
		const shadowPlaneMaterial = new MeshBasicMaterial( {
			name: "ContactShadow Material",
			//map: renderTarget.texture,
			opacity: this.shadowOpacity,
			transparent: true,
			envMap: null
		} );


		// PLANE MESH 
		const shadowPlaneMesh = new Mesh( shadowPlaneGeometry, shadowPlaneMaterial ); 
		shadowPlaneMesh.name = "ContactShadow Plane"
		shadowPlaneMesh.scale.y = - 1; // the y from the texture is flipped!
		shadowPlaneMesh.receiveShadow = true;

		//set the shadowPlane to its own layer
		shadowPlaneMesh.layers.set( Project.layerMap.visibleActors )

		//shadowPlaneMesh.showWireframe = this.showWireframe


		// BLUR PLANE - onto which to blur the texture
		const shadowBlurPlane = new Mesh( shadowPlaneGeometry );
		shadowBlurPlane.name = "ContactShadow Blur Plane";
		shadowBlurPlane.visible = false;

		// CAMERA  - to render the depth material from
		const shadowCamera = new OrthographicCamera( - this.planeWidth / 2, this.planeHeight / 2, this.planeHeight / 2, - this.planeHeight / 2, 0, this.cameraHeight );
		shadowCamera.rotation.x = Math.PI / 2; // get the camera to look up

		//only get the visible actors 
		shadowCamera.layers.enable(Project.layerMap.visibleActors);
        
		// DEPTHMATERIAL -- like MeshDepthMaterial, but goes from black to transparent
		const depthMaterial = new MeshDepthMaterial(); 
		depthMaterial.userData.darkness = { value: this.shadowDarkness };
		depthMaterial.onBeforeCompile = function ( shader ) {
			shader.uniforms.darkness = depthMaterial.userData.darkness;
			shader.fragmentShader = `
					uniform float darkness;
					${shader.fragmentShader.replace(
						'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );',
						'gl_FragColor = vec4( vec3( 0.0 ), ( 1.0 - fragCoordZ ) * darkness );'
					)}
				`;
		};
		depthMaterial.depthTest = false;
		depthMaterial.depthWrite = false;

		//BLUR SHADERS
		const horizontalBlurMaterial = new ShaderMaterial( HorizontalBlurShader );
		horizontalBlurMaterial.depthTest = false;
	
		const verticalBlurMaterial = new ShaderMaterial( VerticalBlurShader );
		verticalBlurMaterial.depthTest = false;

		//GROUP
		const shadowGroup = new Group();
		shadowGroup.name = "ContactShadow Group"
		shadowGroup.position.y = - 0;
		shadowGroup.children = [ shadowPlaneMesh, shadowBlurPlane, shadowCamera  ];  //scene.add( line ); line
		shadowGroup.layers.set( 1 );
		shadowGroup.receiveShadow = true;


		this._shadowGroup = shadowGroup;
		//this.layers = 1
		this._shadowBlurPlane = shadowBlurPlane
		this._horizontalBlurMaterial = horizontalBlurMaterial
		this._verticalBlurMaterial = verticalBlurMaterial
		//this._renderTargets = renderTargets
		//this._renderTargetBlur = renderTargetBlur
		this._shadowCamera = shadowCamera
		this._depthMaterial = depthMaterial
		this._shadowPlaneMaterial = shadowPlaneMaterial

		return this;

	}


	_blur( blurAmount ){

		//console.log( this._shadowBlurPlane )
		this._shadowBlurPlane.visible = true;


		this._shadowPlaneMaterial.map = this._renderTargets[0].texture;
		
		// RENDERTARGET A - blur vertically and draw in the main renderTarget
		this._shadowBlurPlane.material = this._verticalBlurMaterial;
		this._shadowBlurPlane.material.uniforms.tDiffuse.value = this._renderTargets[0].texture //this._renderTargetBlur.texture;
		this._verticalBlurMaterial.uniforms.v.value = blurAmount * 1 / 256;
	
		this._renderer.setRenderTarget( this._renderTargets[1] );
		this._renderer.render( this._shadowBlurPlane, this._shadowCamera );

		// RENDERTARGET B - blur horizontally and draw in the renderTargetBlur
		this._shadowBlurPlane.material = this._horizontalBlurMaterial;
		this._shadowBlurPlane.material.uniforms.tDiffuse.value = this._renderTargets[1].texture //this._renderTarget.texture;
		this._horizontalBlurMaterial.uniforms.h.value = blurAmount * 1 / 256;

		this._renderer.setRenderTarget( this._renderTargets[0] ); //this._renderTargetBlur
		this._renderer.render( this._shadowBlurPlane, this._shadowCamera );
	
		this._shadowBlurPlane.visible = false;

		return this._renderTargets

	}

	_update( exclude ){

		//console.log( exclude)
		//First dispose the old renderTargets when doing an update
		this._renderTargets = null;
		if( this.renderTarget ){ this.renderTarget.dispose() }
		if( this.renderTargetBlur ){ this.renderTargetBlur.dispose() }
		

		// RENDERTARGET - the render target that will show the shadows in the plane texture
		this.renderTarget = new WebGLRenderTarget( 512, 512 );
		this.renderTarget.texture.format = RGBAFormat;
		this.renderTarget.texture.generateMipmaps = false;

		// RENDERTARGET BLUR - the render target that we will use to blur the first render target
		this.renderTargetBlur = new WebGLRenderTarget( 512, 512 );
		this.renderTargetBlur.texture.generateMipmaps = false;

		this._renderTargets = [ this.renderTarget, this.renderTargetBlur  ] //renderTargets

		// REMOVE BACKGROUND
		const initialBackground = this._scene.background;
		this._scene.background = null;

		const crossHelper = this._scene.getObjectByName( "crossHelper" );
		if( crossHelper ){
			crossHelper.visible = false;
		}

		const frontHelper = this._scene.getObjectByName( "frontHelper" );
		if( frontHelper ){
			frontHelper.visible = false;
		}

		// REMOVE/EXCLUDE OBJECTS FROM SCENE/CONTACTSHADOW HERE
		if ( exclude instanceof Array ){
			for ( var i = 0; i < exclude.length; i++ ){
				exclude[i].visible = false;
			}
		};

		// force the depthMaterial to everything
		this._scene.overrideMaterial = this._depthMaterial;

		// render to the rendertarget to get the depths
        const initialClearAlpha = this._renderer.getClearAlpha();
        this._renderer.setClearAlpha( 0 );
		// this._renderer.autoClear = false;
		this._renderer.setRenderTarget( this.renderTarget );
		this._renderer.render( this._scene, this._shadowCamera );

		// reset the override material
		this._scene.overrideMaterial = null;								

		//RE INCLUDE OBJECTS FROM SCENE/SHADOW HERE
		if ( exclude instanceof Array){
			for ( var i = 0; i < exclude.length; i++ ){
				exclude[i].visible = true;
			};
		};

		if( crossHelper ){
			crossHelper.visible = true;
		}

		if( frontHelper ){
			frontHelper.visible = true;
		}

		//BLUR PASS 1
		this._blur( this.blurAmount );

		//BLUR PASS 2
		// a second pass to reduce the artifacts (0.4 is the minimum blur amout so that the artifacts are gone)
		this._blur( this.blurAmount * 0.4 );

		// reset and render the normal scene
		this._renderer.setRenderTarget( null );
        this._renderer.setClearAlpha( initialClearAlpha );
		this._scene.background = initialBackground;

	};

};

export { ContactShadow }