// /**
//  * Creates smooth shadows
//  *
//  *
//  */

// // https://github.com/mrdoob/three.js/blob/master/examples/webgl_shadow_contact.html
// import {
// 	WebGLRenderer,
// 	Object3D,
// 	Vector2,
// 	Group,
// 	Scene,
// 	Camera,
// 	BufferGeometry,
// 	Material,
// 	Mesh,
// 	MeshBasicMaterial,
// 	MeshDepthMaterial,
// 	OrthographicCamera,
// 	PlaneGeometry,
// 	WebGLRenderTarget,
// 	BufferAttribute,
// } from 'three';
// import {TypedObjNode} from './_Base';
// import {FlagsControllerD} from '../utils/FlagsController';
// import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
// import {BaseNodeType} from '../_Base';
// import {HierarchyController} from './utils/HierarchyController';
// import {RenderHook} from '../../../core/geometry/Material';
// import {TypeAssert} from '../../poly/Assert';
// import {CameraHelper} from '../../../core/helpers/CameraHelper';
// import {isBooleanTrue} from '../../../core/BooleanValue';
// import {TransformController, TransformedParamConfig} from './utils/TransformController';
// import {CoreRenderBlur} from '../../../core/render/Blur';

// enum ContactShadowUpdateMode {
// 	ON_RENDER = 'On Every Render',
// 	MANUAL = 'Manual',
// }
// const UPDATE_MODES: ContactShadowUpdateMode[] = [ContactShadowUpdateMode.ON_RENDER, ContactShadowUpdateMode.MANUAL];

// class ContactShadowObjParamConfig extends TransformedParamConfig(NodeParamsConfig) {
// 	shadow = ParamConfig.FOLDER();
// 	/** @param distance from the ground up to which shadows are visible */
// 	dist = ParamConfig.FLOAT(1, {
// 		range: [0, 10],
// 		rangeLocked: [true, false],
// 	});
// 	/** @param size of the plane on which shadows are rendered */
// 	planeSize = ParamConfig.VECTOR2([1, 1]);
// 	/** @param shadow resolution */
// 	shadowRes = ParamConfig.VECTOR2([256, 256]);
// 	/** @param blur amount */
// 	blur = ParamConfig.FLOAT(1, {
// 		range: [0, 10],
// 		rangeLocked: [true, false],
// 	});
// 	/** @param toggle on to add a secondary blur, which may be useful to get rid of artefacts */
// 	tblur2 = ParamConfig.BOOLEAN(1);
// 	/** @param secondary blur amount */
// 	blur2 = ParamConfig.FLOAT(1, {
// 		range: [0, 10],
// 		rangeLocked: [true, false],
// 		visibleIf: {tblur2: 1},
// 	});
// 	/** @param shadow darkness */
// 	darkness = ParamConfig.FLOAT(1);
// 	/** @param shadow opacity */
// 	opacity = ParamConfig.FLOAT(1);
// 	/** @param show helper */
// 	showHelper = ParamConfig.BOOLEAN(0);

// 	/** @param set update mode, which can be to update on every frame, or manually only */
// 	updateMode = ParamConfig.INTEGER(UPDATE_MODES.indexOf(ContactShadowUpdateMode.ON_RENDER), {
// 		callback: (node: BaseNodeType) => {
// 			ContactShadowObjNode.PARAM_CALLBACK_update_updateMode(node as ContactShadowObjNode);
// 		},
// 		menu: {
// 			entries: UPDATE_MODES.map((name, value) => {
// 				return {name, value};
// 			}),
// 		},
// 	});
// 	/** @param click to update shadow, when mode is manual */
// 	update = ParamConfig.BUTTON(null, {
// 		callback: (node: BaseNodeType) => {
// 			ContactShadowObjNode.PARAM_CALLBACK_updateManual(node as ContactShadowObjNode);
// 		},
// 		visibleIf: {updateMode: UPDATE_MODES.indexOf(ContactShadowUpdateMode.MANUAL)},
// 	});

// 	scene = ParamConfig.FOLDER();
// 	renderAllObjects = ParamConfig.BOOLEAN(true);
// 	include = ParamConfig.STRING('', {
// 		visibleIf: {renderAllObjects: 0},
// 		objectMask: true,
// 	});
// 	exclude = ParamConfig.STRING('', {
// 		visibleIf: {renderAllObjects: 0},
// 		objectMask: true,
// 	});
// 	updateObjectsList = ParamConfig.BUTTON(null, {
// 		visibleIf: {renderAllObjects: 0},
// 		callback: (node: BaseNodeType) => {
// 			ContactShadowObjNode.PARAM_CALLBACK_updateObjectsList(node as ContactShadowObjNode);
// 		},
// 	});
// 	printResolveObjectsList = ParamConfig.BUTTON(null, {
// 		visibleIf: {renderAllObjects: 0},
// 		callback: (node: BaseNodeType) => {
// 			ContactShadowObjNode.PARAM_CALLBACK_printResolveObjectsList(node as ContactShadowObjNode);
// 		},
// 	});
// }
// const ParamsConfig = new ContactShadowObjParamConfig();

// const PLANE_WIDTH = 1;
// const PLANE_HEIGHT = 1;
// const CAMERA_HEIGHT = 1;
// const DARKNESS = 1;
// const DEFAULT_RENDER_TARGET_RES = new Vector2(256, 256);

// export class ContactShadowObjNode extends TypedObjNode<Group, ContactShadowObjParamConfig> {
// 	override paramsConfig = ParamsConfig;
// 	static override type(): Readonly<'contactShadow'> {
// 		return 'contactShadow';
// 	}
// 	override readonly hierarchyController: HierarchyController = new HierarchyController(this);
// 	public override readonly flags: FlagsControllerD = new FlagsControllerD(this);
// 	private _helper: CameraHelper | undefined;

// 	private _renderTarget = this._createRenderTarget(DEFAULT_RENDER_TARGET_RES);
// 	private _coreRenderBlur: CoreRenderBlur = this._createCoreRenderBlur(DEFAULT_RENDER_TARGET_RES);
// 	private _createRenderTarget(res: Vector2) {
// 		const renderTarget = new WebGLRenderTarget(res.x, res.y);
// 		renderTarget.texture.generateMipmaps = false;
// 		return renderTarget;
// 	}
// 	private _createCoreRenderBlur(res: Vector2) {
// 		return new CoreRenderBlur(res);
// 	}

// 	private _shadowGroup: Group | undefined;
// 	private _plane: Mesh | undefined;
// 	private _planeMaterial: MeshBasicMaterial | undefined;
// 	private _includedObjects: Object3D[] = [];
// 	private _includedAncestors: Object3D[] = [];
// 	private _excludedObjects: Object3D[] = [];

// 	override createObject() {
// 		const group = new Group();
// 		this._shadowGroup = new Group();
// 		group.add(this._shadowGroup);
// 		this._shadowGroup.name = 'shadowGroup';

// 		const planeGeometry = new PlaneGeometry(PLANE_WIDTH, PLANE_HEIGHT).rotateX(-Math.PI / 2);
// 		// update uvs
// 		const uvArray = (planeGeometry.getAttribute('uv') as BufferAttribute).array as number[];
// 		for (let index of [1, 3, 5, 7]) {
// 			uvArray[index] = 1 - uvArray[index];
// 		}
// 		this._planeMaterial = new MeshBasicMaterial({
// 			map: this._renderTarget.texture,
// 			opacity: 1,
// 			transparent: true,
// 			depthWrite: false,
// 		});
// 		// this._renderTarget.texture.flipY = true;
// 		this._plane = new Mesh(planeGeometry, this._planeMaterial);
// 		// make sure it's rendered after the fillPlane
// 		this._plane.renderOrder = 1;
// 		// the y from the texture is flipped!
// 		// this._plane.scale.y = -1;

// 		this._plane.matrixAutoUpdate = false;
// 		this._shadowGroup.add(this._plane);

// 		// the plane with the color of the ground
// 		// this._fillPlaneMaterial = new MeshBasicMaterial({
// 		// 	color: 0xffffff,
// 		// 	opacity: OPACITY,
// 		// 	transparent: true,
// 		// 	depthWrite: false,
// 		// });
// 		// const fillPlane = new Mesh(planeGeometry, this._fillPlaneMaterial);
// 		// fillPlane.rotateX(Math.PI);
// 		// this._shadowGroup.add(fillPlane);

// 		this._createDepthCamera(this._shadowGroup);
// 		this._createMaterials();

// 		return group;
// 	}
// 	override readonly transformController: TransformController = new TransformController(this);
// 	override initializeNode() {
// 		this.hierarchyController.initializeNode();
// 		this.transformController.initializeNode();

// 		this._updateShadowGroupVisibility();
// 		this._updateHelperVisibility();
// 		this.flags.display.onUpdate(() => {
// 			this._updateShadowGroupVisibility();
// 			this._updateHelperVisibility();
// 		});
// 	}
// 	override async cook() {
// 		this.transformController.update();
// 		this._updateRenderHook();
// 		this._updateHelperVisibility();
// 		this._updateObjectsList();

// 		if (this._planeMaterial) {
// 			this._planeMaterial.opacity = this.pv.opacity;
// 		}
// 		this._darknessUniform.value = this.pv.darkness;

// 		// update planes size
// 		if (this._plane && this._shadowCamera && this._helper) {
// 			this._plane.scale.x = this.pv.planeSize.x;
// 			this._plane.scale.z = this.pv.planeSize.y;
// 			this._plane.updateMatrix();

// 			this._shadowCamera.left = -this.pv.planeSize.x / 2;
// 			this._shadowCamera.right = this.pv.planeSize.x / 2;
// 			this._shadowCamera.bottom = -this.pv.planeSize.y / 2;
// 			this._shadowCamera.top = this.pv.planeSize.y / 2;
// 			this._shadowCamera.far = this.pv.dist;
// 			this._shadowCamera.updateProjectionMatrix();
// 			this._helper.update();
// 		}
// 		// update renderTargets if needed
// 		if (this._renderTarget.width != this.pv.shadowRes.x || this._renderTarget.height != this.pv.shadowRes.y) {
// 			if (this._planeMaterial) {
// 				this._renderTarget = this._createRenderTarget(this.pv.shadowRes);
// 				this._coreRenderBlur = this._createCoreRenderBlur(this.pv.shadowRes);
// 				this._planeMaterial.map = this._renderTarget.texture;
// 			}
// 		}

// 		this.cookController.endCook();
// 	}

// 	private _shadowCamera: OrthographicCamera | undefined;
// 	private _createDepthCamera(group: Group) {
// 		// the camera to render the depth material from
// 		this._shadowCamera = new OrthographicCamera(
// 			-PLANE_WIDTH / 2,
// 			PLANE_WIDTH / 2,
// 			PLANE_HEIGHT / 2,
// 			-PLANE_HEIGHT / 2,
// 			0,
// 			CAMERA_HEIGHT
// 		);
// 		// this._shadowCamera.rotation.z = Math.PI / 2;
// 		this._shadowCamera.rotation.x = Math.PI / 2; // get the camera to look up
// 		// this._shadowCamera.position.y = 0.1;
// 		group.add(this._shadowCamera);

// 		this._helper = new CameraHelper(this._shadowCamera);
// 		this._helper.visible = false;
// 		this._shadowCamera.add(this._helper);
// 	}

// 	private _depthMaterial: MeshDepthMaterial | undefined;
// 	private _darknessUniform = {value: DARKNESS};
// 	private _createMaterials() {
// 		// like MeshDepthMaterial, but goes from black to transparent
// 		this._depthMaterial = new MeshDepthMaterial();
// 		this._depthMaterial.onBeforeCompile = (shader) => {
// 			shader.uniforms.darkness = this._darknessUniform;
// 			shader.fragmentShader = /* glsl */ `
// 			uniform float darkness;
// 			${shader.fragmentShader.replace(
// 				'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );',
// 				'gl_FragColor = vec4( vec3( 0.0 ), ( 1.0 - fragCoordZ ) * darkness );'
// 			)}
// 		`;
// 		};
// 		this._depthMaterial.depthTest = false;
// 		this._depthMaterial.depthWrite = false;
// 	}

// 	private _emptyOnBeforeRender = () => {};
// 	private _renderShadow(renderer: WebGLRenderer, scene: Scene) {
// 		if (!(this._helper && this._depthMaterial && this._shadowCamera && this._helper && this._plane)) {
// 			return;
// 		}

// 		// save current state
// 		const previousOnBeforeRender = this._plane.onBeforeRender;
// 		const initialBackground = scene.background;
// 		const helperVisible = this._helper.visible;

// 		// prepare scene
// 		scene.background = null;
// 		this._plane.onBeforeRender = this._emptyOnBeforeRender;
// 		this._helper.visible = false;
// 		scene.overrideMaterial = this._depthMaterial;
// 		this._initVisibility(scene);

// 		// set renderer clear alpha
// 		const initialClearAlpha = renderer.getClearAlpha();
// 		renderer.setClearAlpha(0);

// 		// render to the render target to get the depths
// 		renderer.setRenderTarget(this._renderTarget);
// 		renderer.render(scene, this._shadowCamera);

// 		this._coreRenderBlur.applyBlur(this._renderTarget, renderer, this.pv.blur, this.pv.blur);

// 		if (isBooleanTrue(this.pv.tblur2)) {
// 			this._coreRenderBlur.applyBlur(this._renderTarget, renderer, this.pv.blur2, this.pv.blur2);
// 		}

// 		// reset and render the normal scene
// 		this._restoreVisibility(scene);
// 		scene.overrideMaterial = null;
// 		this._helper.visible = helperVisible;
// 		renderer.setRenderTarget(null);
// 		renderer.setClearAlpha(initialClearAlpha);
// 		scene.background = initialBackground;
// 		this._plane.onBeforeRender = previousOnBeforeRender;
// 	}

// 	private _updateShadowGroupVisibility() {
// 		if (!this._shadowGroup) {
// 			return;
// 		}
// 		if (this.flags.display.active()) {
// 			this._shadowGroup.visible = true;
// 		} else {
// 			this._shadowGroup.visible = false;
// 		}
// 	}

// 	private _updateHelperVisibility() {
// 		if (!this._helper) {
// 			return;
// 		}
// 		if (this.flags.display.active() && isBooleanTrue(this.pv.showHelper)) {
// 			this._helper.visible = true;
// 		} else {
// 			this._helper.visible = false;
// 		}
// 	}

// 	private _updateRenderHook() {
// 		const mode = UPDATE_MODES[this.pv.updateMode];
// 		switch (mode) {
// 			case ContactShadowUpdateMode.ON_RENDER: {
// 				return this._addRenderHook();
// 			}
// 			case ContactShadowUpdateMode.MANUAL: {
// 				return this._removeRenderHook();
// 			}
// 		}
// 		TypeAssert.unreachable(mode);
// 	}

// 	private _addRenderHook() {
// 		if (!this._plane) {
// 			return;
// 		}
// 		if (this._plane.onBeforeRender != this._on_object_before_render_bound) {
// 			this._plane.onBeforeRender = this._on_object_before_render_bound;
// 		}
// 	}
// 	private _emptyRenderHook = () => {};
// 	private _removeRenderHook() {
// 		if (!this._plane) {
// 			return;
// 		}
// 		if (this._plane.onBeforeRender != this._emptyRenderHook) {
// 			this._plane.onBeforeRender = this._emptyRenderHook;
// 		}
// 	}
// 	private _on_object_before_render_bound: RenderHook = this._update.bind(this);
// 	// private _previous_on_before_render: RenderHook | undefined;
// 	private _update(
// 		renderer?: WebGLRenderer,
// 		scene?: Scene,
// 		camera?: Camera,
// 		geometry?: BufferGeometry,
// 		material?: Material,
// 		group?: Group | null
// 	) {
// 		if (!renderer || !scene) {
// 			console.log('no renderer or scene');
// 			return;
// 		}
// 		this._renderShadow(renderer, scene);
// 	}
// 	private _updateManual() {
// 		const renderer = this.scene().renderersRegister.lastRegisteredRenderer();
// 		if (!renderer) {
// 			console.log('no renderer found');
// 			return;
// 		}
// 		if (!(renderer instanceof WebGLRenderer)) {
// 			console.log('renderer found is not WebGLRenderer');
// 			return;
// 		}
// 		const scene = this.scene().threejsScene();
// 		this._renderShadow(renderer, scene);
// 	}

// 	//
// 	//
// 	// ACTIVE
// 	//
// 	//
// 	static PARAM_CALLBACK_update_updateMode(node: ContactShadowObjNode) {
// 		node._updateRenderHook();
// 	}

// 	//
// 	//
// 	// UPDATE
// 	//
// 	//
// 	static PARAM_CALLBACK_updateManual(node: ContactShadowObjNode) {
// 		node._updateManual();
// 	}

// 	//
// 	//
// 	// UPDATE OBJECTS LIST
// 	//
// 	//
// 	static PARAM_CALLBACK_updateObjectsList(node: ContactShadowObjNode) {
// 		node._updateObjectsList();
// 	}
// 	private _updateObjectsList() {
// 		if (this.pv.include != '') {
// 			this._includedObjects = this.scene().objectsByMask(this.pv.include)  as Object3D[];
// 		} else {
// 			this._includedObjects = [];
// 		}
// 		// we also need to add the parents of this._includedObjects,
// 		// as otherwise those will be hidden, indirectly hidding this._includedObjects
// 		const parentsMap: Map<string, Object3D> = new Map();
// 		for (let object of this._includedObjects) {
// 			object.traverseAncestors((parent) => {
// 				parentsMap.set(parent.uuid, parent);
// 			});
// 		}
// 		this._includedAncestors = [];
// 		parentsMap.forEach((parent, uuid) => {
// 			this._includedAncestors.push(parent);
// 		});

// 		// add excluded
// 		if (this.pv.exclude != '') {
// 			this._excludedObjects = this.scene().objectsByMask(this.pv.exclude)  as Object3D[];
// 		} else {
// 			this._excludedObjects = [];
// 		}
// 	}

// 	static PARAM_CALLBACK_printResolveObjectsList(node: ContactShadowObjNode) {
// 		node._printResolveObjectsList();
// 	}
// 	private _printResolveObjectsList() {
// 		console.log('included objects:');
// 		console.log(this._includedObjects);
// 		console.log('included parents:');
// 		console.log(this._includedAncestors);
// 		console.log('excluded objects:');
// 		console.log(this._excludedObjects);
// 	}

// 	private _initialVisibilityState: WeakMap<Object3D, boolean> = new WeakMap();
// 	private _initVisibility(scene: Scene) {
// 		if (isBooleanTrue(this.pv.renderAllObjects)) {
// 			return;
// 		}
// 		// if at least one object is included,
// 		// this means those which are not are to be hidden.
// 		// Therefore, we then have to traverse the whole scene.
// 		// If none is specified in included, we do not need to traverse
// 		if (this._includedObjects.length > 0) {
// 			scene.traverse((object) => {
// 				this._initialVisibilityState.set(object, object.visible);
// 				object.visible = false;
// 			});
// 		} else {
// 			this._storeObjectsVisibility(this._includedObjects);
// 			this._storeObjectsVisibility(this._includedAncestors);
// 			this._storeObjectsVisibility(this._excludedObjects);
// 		}

// 		this._setObjectsVisibility(this._includedObjects, true);
// 		this._setObjectsVisibility(this._includedAncestors, true);
// 		this._setObjectsVisibility(this._excludedObjects, false);
// 	}
// 	private _storeObjectsVisibility(objects: Object3D[]) {
// 		for (let object of objects) {
// 			this._initialVisibilityState.set(object, object.visible);
// 		}
// 	}
// 	private _setObjectsVisibility(objects: Object3D[], visible: boolean) {
// 		for (let object of objects) {
// 			object.visible = visible;
// 		}
// 	}

// 	private _restoreVisibility(scene: Scene) {
// 		if (isBooleanTrue(this.pv.renderAllObjects)) {
// 			return;
// 		}
// 		if (this._includedObjects.length > 0) {
// 			scene.traverse((object) => {
// 				const state = this._initialVisibilityState.get(object);
// 				if (state != null) object.visible = state;
// 			});
// 		} else {
// 			this._restoreObjectsVisibility(this._includedObjects);
// 			this._restoreObjectsVisibility(this._includedAncestors);
// 			this._restoreObjectsVisibility(this._excludedObjects);
// 		}
// 	}
// 	private _restoreObjectsVisibility(objects: Object3D[]) {
// 		for (let object of objects) {
// 			const state = this._initialVisibilityState.get(object);
// 			if (state) {
// 				object.visible = state;
// 			}
// 		}
// 	}
// }
