Source: scene/contact_shadow.js

import { Reporter } from '../reporter/reporter.js';
import { checkPropTypes } from '../lib.js';
import { BuildableComponent } from '../package/component/buildable_component.js';

import {
    Camera,
    Scene,
	Color,
	WebGLRenderer,
	MeshBasicMaterial,
	WebGLRenderTarget,
	MeshDepthMaterial,
	RGBAFormat,
	PlaneBufferGeometry,
	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';


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.35;
		this.shadowDarkness = 1;
		this.shadowOpacity = 1;
		this.blurAmount = 0.25;
		this.showWireframe = true;

		this._scene = settings.scene
		this._camera = settings.camera
		this._renderer = settings.renderer

	}


	async _build(){

		// PLANE GEO
		const shadowPlaneGeometry = new PlaneBufferGeometry( 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;

		//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

		// 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]
		
		// 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)

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

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

		const renderTargets = [ renderTarget, renderTargetBlur  ]

		this._renderTargets = renderTargets

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

		// 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
		this._renderer.autoClear = false;
		this._renderer.setRenderTarget( 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;
			};
		};

		//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._scene.background = initialBackground;

	};

};

export { ContactShadow }