Source: frustumPoints.js

/**
 * frustumPoints
 * 
 * Array of points, statically fixed in front of the camera.
 * I use frustumPoints for displaying of the clouds around points.
 *
 * @author [Andrej Hristoliubov]{@link https://github.com/anhr}
 *
 * @copyright 2011 Data Arts Team, Google Creative Lab
 *
 * @license under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
*/

//import MyThree from '../myThree/myThree.js';
import MyPoints from '../myPoints/myPoints.js';

import clearThree from '../clearThree.js';
//import { dat } from '../dat/dat.module.js';

import { getWorldPosition } from '../getPosition.js';

import three from '../three.js'
import FolderPoint from '../folderPoint.js'

//memory limit
//import roughSizeOfObject from '../../commonNodeJS/master/SizeOfObject.js';

import Options from '../Options.js'

var debug = {

	notHiddingFrustumPoints: true, //Точки не скрываются когда пересчитываются их координаты когда пользователь поворачивает сцену
	//notMoveFrustumPoints: true,//Точки двигаются относительно камеры вместе с остальными 3D объектами когда пользователь поворачивает сцену
	//linesiInMono: true,//Возможность показать линии в отсутсвии стерео режима

};
//Standard normal distributionю. Нормальное распределение
//https://en.wikipedia.org/wiki/Normal_distribution
function getStandardNormalDistribution( x ) {

	const standardDeviation = 0.1;//чем больше среднеквадратическое отклонение, тем шире пик нормального распределения
	const res = Math.exp( -0.5 * x * x / ( standardDeviation * standardDeviation ) );// / Math.sqrt( 2 * Math.PI );
	//console.warn( 'x = ' + x + ' y = ' + res );
	return res;

}

class FrustumPoints
{
	/**
	 * Create a `FrustumPoints` instance.
	 * @param {THREE.PerspectiveCamera} camera [PerspectiveCamera]{@link https://threejs.org/docs/index.html#api/en/cameras/PerspectiveCamera}
	 * @param {THREE.Group} group [group]{@link https://threejs.org/docs/index.html?q=Gro#api/en/objects/Group} of objects to which a new FrustumPoints will be added
	 * @param {DOM} canvas The Graphics [Canvas]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas} element to draw graphics and animations.
	 * @param {object} [settings={}] the following settings are available
	 * @param {Options} [settings.options=new Options()] <a href="../../jsdoc/Options/Options.html" target="_blank">Options</a> instance. The following options are available.
	 * See <b>options</b> parameter of <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
	 * @param {boolean|OrbitControls} [settings.options.orbitControls] <pre>false - do not add the [OrbitControls]{@link https://threejs.org/docs/index.html#examples/en/controls/OrbitControls}. Allow the camera to orbit around a target.
	 * Or <b>OrbitControls</b> instance.
	 * </pre>
	 * @param {object} [settings.options.dat] use dat-gui JavaScript Controller Library. [dat.gui]{@link https://github.com/dataarts/dat.gui}.
	 * See <b>options.dat</b> parameter of <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
	 * @param {object} [settings.options.scales] axes scales.
	 * See <b>options.scales</b> parameter of <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
	 * @param {boolean|GuiSelectPoint} [settings.options.guiSelectPoint] <pre>false - do not displays the <a href="../../guiSelectPoint/jsdoc/module-GuiSelectPoint.html" target="_blank">Select Point</a>. [dat.gui]{@link https://github.com/dataarts/dat.gui} based graphical user interface for select a point from the mesh.
	 * Or <b>GuiSelectPoint</b> instance.
	 * </pre>
	 * @param {object} [settings.options.raycaster] for [Raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster}.
	 * @param {MyThree.ColorPicker.palette|boolean|number} [settings.options.palette] Points сolor.
	 * See <b>options.palette</b> parameter of <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
	 * @param {Function|string} [settings.options.getLanguageCode=language code of your browser] Your custom <b>getLanguageCode()</b> function or language code string.
	 * See <b>options.getLanguageCode</b> parameter of <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
	 * @param {boolean|AxesHelper} [settings.options.axesHelper] <pre>false - do not add the <a href="../../AxesHelper/jsdoc/index.html" target="_blank">AxesHelper</a>.
	 * Or <b>AxesHelper</b> instance.
	 * </pre>
	 * @param {object} [settings.options.frustumPoints] <b>FrustumPoints</b> options. undefined - do not create a <b>FrustumPoints</b> instance.
	 * @param {object} [settings.options.frustumPoints.point={}] points options.
	 * @param {number} [settings.options.frustumPoints.point.size=0] Size of each frustum point.
	 * @param {boolean} [settings.options.frustumPoints.display=true] true - display frustum points.
	 * @param {boolean} [settings.options.frustumPoints.info=false] true - display information about frustum point if user move mouse over or click this point.
	 *
	 * @param {object} [settings.options.frustumPoints.stereo] stereo mode options
	 * @param {number} [settings.options.frustumPoints.stereo.hide=0] Hide the nearby to the camera points in percentage to all points for more comfortable visualisation.
	 * @param {number} [settings.options.frustumPoints.stereo.opacity=0.3] Float in the range of 0.0 - 1.0 indicating how transparent the lines is. A value of 0.0 indicates fully transparent, 1.0 is fully opaque.
	 *
	 * @param {number} [settings.options.frustumPoints.zCount=50] The count of layers of the frustum of the camera's field of view.
	 * @param {number} [settings.options.frustumPoints.yCount=30] The count of vertical points for each z level of the  frustum of the camera's field of view.
	 *
	 * @param {number} [settings.options.frustumPoints.near=0] Shift of the frustum layer near to the camera in percents.
	 * <pre>
	 * 0 percents - no shift.
	 * 100 percents - ближний к камере слой усеченной пирамиды приблизился к дальнему от камеры слою усеченной пирамиды.
	 * </pre>
	 * @param {number} [settings.options.frustumPoints.far=0] Shift of the frustum layer far to the camera in percents.
	 * <pre>
	 * 0 percents - no shift.
	 * 100 percents - дальний от камеры слоем усеченной пирамиды приблизился к ближнему к камере слою усеченной пирамиды.
	 * </pre>
	 * @param {number} [settings.options.frustumPoints.base=100] Scale of the base of the frustum points in percents.
	 * <pre>
	 * 0 base is null
	 * 100 no scale
	 * </pre>
	 * @param {boolean} [settings.options.frustumPoints.square=false] true - Square base of the frustum points.
	 */
	constructor( camera, group, canvas, settings = {} ) {

		const THREE = three.THREE;
		settings.options = settings.options || new Options();
		const options = settings.options;
		if ( !options.boOptions ) {

			console.error( 'FrustumPoints: call options = new Options( options ) first' );
			return;

		}
		if ( !options.frustumPoints ) return;

		//Непонятно почему frustumPoints не видны если не выполнить эту команду
		//https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/webglcontextlost_event
		this.gl = canvas.getContext('webgl');

		this.getOptions = function () { return options; }
		const optionsShaderMaterial = options.frustumPoints;
		options.frustumPoints = this;

		const _arrayCloud = []//Массив координат точек, имеющих облако вокруг себя
							//координаты точек сгруппированы в группы отдельно для каждого THREE.Points
		var _guiSelectPoint, _names, _points;
		_arrayCloud.getCloudsCount = function () {

			var count = 0;
			for ( var i = 0; i < _arrayCloud.length; i++ ) {

				var arrayVectors = _arrayCloud[i];
				count += arrayVectors.length;

			}
			return count;

		}

		/**
		 * Pushes to clouds array all points from <b>geometry.attributes.position</b>
		 * @param {THREE.BufferGeometry|THREE.Points} geometry [THREE.BufferGeometry]{@link https://threejs.org/docs/index.html?q=BufferGeometry#api/en/core/BufferGeometry}
		 * or [THREE.Points]{@link https://threejs.org/docs/index.html?q=Poin#api/en/objects/Points}
		 * @returns index of the new array item
		 */
		this.pushArrayCloud = function ( geometry ) {

			var points;
			if ( geometry.geometry ) {

				points = geometry;
				geometry = geometry.geometry;

			}
			if ( geometry.attributes.position.itemSize !== 4 ) {

				console.error( 'FrustumPoints.pushArrayCloud: Invalid geometry.attributes.position.itemSize = ' + geometry.attributes.position.itemSize );
				return;
				
			}

			//Массив точек, имеющих облако _arrayCloud, разбил на группы points
			//В каждой группе points содержатся все точки, из одного mesh
			//Это сделал потому что если одновременно имеются точки с 
			// shaderMaterial и без shaderMaterial, то порядок добавления точек в _arrayCloud
			// Не совпадает с порядком расположения mesh в group
			// потому что точки без shaderMaterial добавляются сразу после создания
			// а точки с shaderMaterial добаляются только после вызова loadShaderText в function getShaderMaterialPoints
			const index = _arrayCloud.getCloudsCount(),
				arrayPoints = [];
			_arrayCloud.push( arrayPoints );
			for ( var i = 0; i < geometry.attributes.position.count; i++ ) {

				const point = new THREE.Vector4().fromArray( geometry.attributes.position.array, i * geometry.attributes.position.itemSize );

				//Здесь коррекитровка point.w не имеет эффекта. Она коррекитруется в FrustumPoints.cloud.updateMesh
//				point.w *= options.scales.w.max;

				arrayPoints.push( point );

			}
			if ( points ) points.userData.cloud = { indexArray: index, }
			return index;

		}

		/** create points
		 * @param {THREE.WebGLRenderer} renderer [THREE.WebGLRenderer]{@link https://threejs.org/docs/index.html#api/en/renderers/WebGLRenderer}.
		 */
		this.create = function ( renderer ) {

			//если нет точек с облаком, то облако нужно создавать что бы его было видно в guiSelectPoint
			//однако в этом случае в консоли появятся сообщения:
			//
			//warning X3557: loop only executes for 0 iteration(s), consider removing [loop]
			//warning X3557: loop doesn't seem to do anything, consider removing [loop]
			//WebGL: INVALID_OPERATION: texImage2D: ArrayBufferView not big enough for request
			//
			//Это означает что из D:\My documents\MyProjects\webgl\three.js\GitHub\commonNodeJS\master\frustumPoints\vertex.c
			//нужно удалить цикл
			//for ( float i = 0.; i < cloudPointsWidth; i++ )
			//
			if ( _arrayCloud.length === 0 )
				return undefined;//нет точек с облаком. Поэтому нет смысла создавать frustumPoints

			const shaderMaterial = {}, zeroPoint = new THREE.Vector3(), cameraDistanceDefault = camera.position.distanceTo( zeroPoint ), _this = this,// lines = []
				groupFrustumPoints = new THREE.Group();

			settings.optionsShaderMaterial = settings.optionsShaderMaterial || {};

			optionsShaderMaterial.point = optionsShaderMaterial.point || {};
			optionsShaderMaterial.point.size = optionsShaderMaterial.point.size || 0.01;//Size of each frustum point

			optionsShaderMaterial.display = optionsShaderMaterial.display === undefined ? true : optionsShaderMaterial.display;//true - display frustum points
			optionsShaderMaterial.info = optionsShaderMaterial.info !== undefined ? optionsShaderMaterial.info : false;//true - display information about frustum point if user move mouse over or click this point.

			//Stereo
			optionsShaderMaterial.stereo = optionsShaderMaterial.stereo || {};
			optionsShaderMaterial.stereo.hide = optionsShaderMaterial.stereo.hide || 0;//Hide the nearby to the camera points in percentage to all points for more comfortable visualisation.
			optionsShaderMaterial.stereo.opacity = optionsShaderMaterial.stereo.opacity || 0.3;//Float in the range of 0.0 - 1.0 indicating how transparent the lines is. A value of 0.0 indicates fully transparent, 1.0 is fully opaque.'

			optionsShaderMaterial.zCount = optionsShaderMaterial.zCount || 50;//The count of layers of the frustum of the camera's field of view.
			optionsShaderMaterial.yCount = optionsShaderMaterial.yCount || 30;//The count of vertical points for each z level of the  frustum of the camera's field of view.

			//изменение размеров усеченной пирамиды FrustumPoints

			optionsShaderMaterial.near = optionsShaderMaterial.near || 0;//Shift of the frustum layer near to the camera in percents.
			//0 percents - no shift.
			//100 percents - ближний к камере слой усеченной пирамиды приблизился к дальнему от камеры слою усеченной пирамиды
			optionsShaderMaterial.far = optionsShaderMaterial.far || 0;//Shift of the frustum layer far to the camera in percents.
			//0 percents - no shift.
			//100 percents - дальний от камеры слоем усеченной пирамиды приблизился к ближнему к камере слою усеченной пирамиды
			optionsShaderMaterial.base = optionsShaderMaterial.base || 100;//Scale of the base of the frustum points in percents.
			//0 base is null
			//100 no scale
			optionsShaderMaterial.square = optionsShaderMaterial.square !== undefined ? optionsShaderMaterial.square : false; //true - Square base of the frustum points.

			const cookie = options.dat.cookie,
				cookieName = options.dat ? options.dat.getCookieName( 'FrustumPoints' ) : 'FrustumPoints';

			Object.freeze( optionsShaderMaterial );
			if ( cookie ) cookie.getObject( cookieName, shaderMaterial, optionsShaderMaterial );

			//оставить shaderMaterial.stereo по умолчанию потому что сейчас lines не использую
			//и возможно в cookie сохранились зачения shaderMaterial.stereo от старых версий этой программы
			//		shaderMaterial.stereo.lines = optionsShaderMaterial.stereo.lines;
			if ( shaderMaterial.stereo ) {

				shaderMaterial.stereo.hide = optionsShaderMaterial.stereo.hide;
				shaderMaterial.stereo.opacity = optionsShaderMaterial.stereo.opacity;

			}

			var cloud = function () {

				var uniforms;
				var distanceTableWidth;//distanceTable points count
				this.create = function ( _uniforms ) {

					uniforms = _uniforms;

					//индекс палитры надо пересчитывать в зависимости от min and max key of the options.scales.w
					//В D:\My documents\MyProjects\webgl\three.js\GitHub\commonNodeJS\master\frustumPoints\vertex.c
					//сначала вычисляется paletteIndex,
					//который равен сумме индексов цветов всех точек cloudPoint.w 
					//помноженное на растояние fDistance от точки имеющей облако до текущей точки frustumPoint.
					//Индексы cloudPoint.w находятся в диапазоне min and max key of the options.scales.w
					//paletteIndex это координата x в палитре palette в D:\My documents\MyProjects\webgl\three.js\GitHub\commonNodeJS\master\frustumPoints\vertex.c
					//которая имеет тип sampler2D https://www.khronos.org/opengl/wiki/Sampler_(GLSL)#Sampler_types
					//которая имеет текстуру 2D texture.
					//Диапазон paletteIndex должен быть в предплах от 0 до 1 https://www.khronos.org/opengl/wiki/Sampler_(GLSL)#Texture_coordinates
					//Получаем пропорции:
					//Если w = options.scales.w.min то paletteIndex = 0.
					//Если w = options.scales.w.max то paletteIndex = 1.
					if ( !options.scales.w ) options.scales.setW();
					const max = options.scales.w.max, min = options.scales.w.min;
					//w = ( w - min ) / ( max - min ) = w / ( max - min ) - min / ( max - min ) = w / (1+1) + 1 / (1+1) = w * 0.5 + 0.5 ;
					uniforms.paletteA = { value: 1 / ( max - min ) };//0.5 };
					uniforms.paletteB = { value: - min / ( max - min ) };//0.5 };

					//array of all points with cloud
					this.cloudPoints = new this.addUniforms( THREE.RGBAFormat, _arrayCloud.getCloudsCount(), 'cloudPoints' );

					//function of distance between points. Use for creating of the cloud around point
					//distanceTable is THREE.DataTexture
					//	width = distanceTableWidth( distanceTable points count )
					//	height = 2
					//THREE.DataTexture contains two lines:
					//	Every line have x from 0 to width - 1
					//	First line (y = 0) is function of distance
					//	Second line (y = 1) is distance from cloud point to frustum point
					//Такая структура distanceTable позволяет неравномерно распределять точки по дистанции
					//Если function of distance меняется быстро, то надо наставить побольше точек
					//Если function of distance почти не меняется, точек можно поставить поменьше
					//Это позволит поставить последнюю точку на достаточно большой дистанции
					//и таким образом можно учитывать малое влияние облака на большом расстоянии.
					distanceTableWidth = 256;//distanceTable points count
					const pointLength = 2;//Every point contains two coordinates
					new this.addUniforms( THREE.LuminanceFormat,//RGFormat,//RGBFormat,
						distanceTableWidth, 'distanceTable', {

						height: pointLength,
						onReady: function ( data, itemSize, updateItem ) {

							//debug
							//var linePoints = [];
							////////////////////////////
							var fDistancePrev, x = 0;
							const dDistanceMax = 0.035;
							var dx = 0.5 / ( distanceTableWidth - 1 ); const ddx = 1.001;
							//dx = 0.00196078431372549
							//ddx	xmax
							//1.001	0.5686435272529023 y = 9.515363374066325e-8
							//var dx = 1.5 / ( distanceTableWidth - 1 ); const ddx = 1.001;
							//dx = 0.0058823529411764705
							//ddx	xmax
							//1.001	1.686899158126089 y = 1.614196454247848e-62
							//1.01	5.688013140820184
							//1.05	11783.008823594038
							//1.1	285390429.9744771
							//var dx = 2/ ( distanceTableWidth - 1 ); const ddx = 1.001;
							//dx = 0.00784313725490196
							//ddx	xmax
							//1.001	2.2149519380160148 y = 2.932926014021469e-107
							//1.01	7.0082200110085715
							//1.05	12925.219146819716
							//1.1	314479812.6420277
							//var dx = 20/ ( distanceTableWidth - 1 ); const ddx = 1.1;
							//dx = 0.0784313725490196
							//ddx	xmax
							//1.001	22.56677751344284 y = 0
							//1.1	11942365647.747343 y = 0
							for ( var i = 0; i < distanceTableWidth; i++ ) {

								var fDistance = getStandardNormalDistribution( x );
								//console.warn( 'dx = ' + dx );
								x += dx;
								if ( fDistancePrev !== undefined ) {

									if ( Math.abs( fDistancePrev - fDistance ) > dDistanceMax )
										dx /= ddx;
									else dx *= ddx;

								}
								fDistancePrev = fDistance;

								updateItem( i, fDistance );//function of distance
								updateItem( i + distanceTableWidth, x );//distance from cloud point to frustum point

								//debug
								//if ( linePoints !== undefined )
								//	linePoints.push( new THREE.Vector3( x, fDistance, 0 ) );
								////////////////////////////

							}

							//debug
							/*
							if ( linePoints !== undefined ) {

								//group.add( new THREE.Line( new THREE.BufferGeometry().setFromPoints( linePoints ), new THREE.LineBasicMaterial( {
								//	color: 0x0000ff
								//} ) ) );
								group.add( new THREE.Points( new THREE.BufferGeometry().setFromPoints( linePoints ),
									new THREE.PointsMaterial( { color: 0xffffff, alphaTest: 0.5 } ) ) );

							}
							*/
							////////////////////////////

						}

					} );

				}
				this.addUniforms = function ( format, width, key, optionsAddUniforms ) {

					optionsAddUniforms = optionsAddUniforms || {};
					//format = RGBAFormat,//LuminanceFormat,//Available formats https://threejs.org/docs/index.html#api/en/constants/Textures
					//D:\My documents\MyProjects\webgl\three.js\GitHub\three.js\dev\src\constants.js
					const itemSize = format === THREE.RGBAFormat ? 4 : format === THREE.RGBFormat ? 3 : format === THREE.LuminanceFormat ? 1 : NaN;
					const height = optionsAddUniforms.height || 1,//format === THREE.LuminanceFormat ? 1 : 2,
						size = width * height,
						type = THREE.FloatType,
						data = type === THREE.FloatType ? new Float32Array( itemSize * size ) : new Uint8Array( itemSize * size );
					/*Uncaught TypeError: Right-hand side of 'instanceof' is not callable
					if( !this instanceof cloud )
						console.error('');
					*/
					if ( this.addUniforms !== undefined )
						console.error( 'Please use "new this.addUniforms(...)"' );
					this.updateItem = function ( i, vector ) {

						var x, y, z, w;
						if ( typeof vector === "number" )
							x = vector;
						else if ( vector.x === undefined ) {

							x = vector.r;
							y = vector.g;
							z = vector.b;
							
							w = 1//remove warning : THREE.WebGLRenderer: THREE.RGBFormat has been removed. Use THREE.RGBAFormat instead. https://github.com/mrdoob/three.js/pull/23228

						} else {

							x = vector.x;
							y = vector.y;
							z = vector.z;
							if ( isNaN( vector.w ) )
								console.error( 'frustumPoints.create.cloud.addUniforms.updateItem: vector.w = ' + vector.w + '. Probably you use THREE.Color for w coordinate of the point with cloud.' );

							/*
							//w это координата x в палитре palette в D:\My documents\MyProjects\webgl\three.js\GitHub\commonNodeJS\master\frustumPoints\vertex.c
							//которая имеет тип sampler2D https://www.khronos.org/opengl/wiki/Sampler_(GLSL)#Sampler_types
							//которая имеет текстуру 2D texture.
							//Диапазон w должен быть в предплах от 0 до 1 https://www.khronos.org/opengl/wiki/Sampler_(GLSL)#Texture_coordinates
							//Получаем пропорции:
							//Если vector.w = options.scales.w.min то w = 0.
							//Если vector.w = options.scales.w.max то w = 1.
							const max = options.scales.w.max, min = options.scales.w.min;
							w = ( vector.w - min ) / ( max - min );
							*/
							w = vector.w;

						}
						const vectorSize = y === undefined ? 1 : z === undefined ? 2 : w === undefined ? 3 : 4,
							stride = i * itemSize;
						if ( vectorSize !== itemSize )
							console.error( 'frustumPoints.create.cloud.addUniforms.updateItem: vectorSize = ' + vectorSize + ' !== itemSize = ' + itemSize );
						data[stride] = x;
						if ( itemSize > 1 ) {

							data[stride + 1] = y;
							if ( itemSize > 2 ) {

								data[stride + 2] = z;
								if ( itemSize > 3 )
									data[stride + 3] = w;

							}

						}

					}

					if ( optionsAddUniforms.onReady !== undefined )
						optionsAddUniforms.onReady( data, itemSize, this.updateItem );

					uniforms[key] = {

						value: new THREE.DataTexture( data,
							width, height, format, type )

					};
					uniforms[key].value.needsUpdate = true;

					return itemSize;

				}
				this.editShaderText = function ( shaderText ) {

					var scloudPointsWidth = 0;
					for ( var i = 0; i < _arrayCloud.length; i++ ) {

						var arrayVectors = _arrayCloud[i];
						scloudPointsWidth += arrayVectors.length;

					}
					shaderText.vertex = shaderText.vertex.replace( '%scloudPointsWidth', scloudPointsWidth + '.' );
					shaderText.vertex = shaderText.vertex.replace( '%distanceTableWidth', distanceTableWidth + '.' );

				}
				this.updateMesh = function ( mesh ) {

					if (
						( mesh.userData.cloud === undefined )
						|| !this.cloudPoints//Пользователь убрал галочку в 'Display' - 'Display or hide Frustum Points.'
					)
						return;
					for ( var i = 0; i < mesh.geometry.attributes.position.count; i++ ) {

						const point = new THREE.Vector4().fromArray( mesh.geometry.attributes.position.array, i * mesh.geometry.attributes.position.itemSize );
						this.cloudPoints.updateItem( mesh.userData.cloud.indexArray + i, getWorldPosition( mesh, point ) );

					}

				}

			};
			cloud = new cloud();
			group.add( groupFrustumPoints );

			function setPointsParams() {

				function set() {

					if ( !_points )
						return;
					_points.position.copy( camera.position );
					_points.rotation.set( camera.rotation.x, camera.rotation.y, camera.rotation.z );
					var scale = camera.position.distanceTo( zeroPoint ) / cameraDistanceDefault;
					_points.scale.x = scale;
					_points.scale.y = scale;
					_points.scale.z = scale;

				}
				set();
				/*
				console.warn( '_points.position: ' + _points.position.x + ' ' + _points.position.y + ' ' + _points.position.z +
				' _points.scale: ' + _points.scale.x + ' ' + _points.scale.y + ' ' + _points.scale.z +
				' _points.rotation: ' + _points.rotation.x + ' ' + _points.rotation.y + ' ' + _points.rotation.z );
				*/
				if ( options.guiSelectPoint ) options.guiSelectPoint.setMesh();

			}
			function update( onReady ) {

				if ( _points === undefined ) {

					progress( onReady );

				}

			}
			/**
			 * The user has moved the camera
			 * @event
			 * */
			this.onChangeControls = function () {

				if ( !debug.notHiddingFrustumPoints ) {

					//Updating of the canvas is too slow if FrustumPoints count is very big (about 'Z Count' = 50 and 'Y Count' = 30).
					//I am hidding all points during  changing of the contdrol and show it again after 500 msec for resolving of the problem.
					if ( timeoutControls === undefined ) {

						group.remove( _points );
						group.remove( groupFrustumPoints );
						options.raycaster.removeParticle( _points );

					}
					clearTimeout( timeoutControls );
					timeoutControls = setTimeout( function () {

						group.add( groupFrustumPoints );
						if ( shaderMaterial.info )
							options.raycaster.addParticle( _points );

						clearTimeout( timeoutControls );
						timeoutControls = undefined;

						if ( !debug.notMoveFrustumPoints ) {

							_this.update();

						}

					}, 500 );

				} else if ( !debug.notMoveFrustumPoints ) {

					_this.update();

				}

			}
			function progress( onReady ) {

				if ( !shaderMaterial.display )
					return;

				const cameraPerspectiveHelper = new THREE.CameraHelper( camera );

				var array, indexArray = 0;

				function getPoint( pointName ) {

					var points = cameraPerspectiveHelper.pointMap[pointName],
						position = cameraPerspectiveHelper.geometry.attributes.position;
					return new THREE.Vector3().fromArray( position.array, points[0] * position.itemSize )

				}

				//near точки ближней к камере плоскости усеченной пирамиды
				const point_n1 = getPoint( 'n1' ),
					point_n2 = getPoint( 'n2' ),
					point_n3 = getPoint( 'n3' );

				//far точки основания пирамиды

				const point_f1 = getPoint( 'f1' ),
					point_f2 = getPoint( 'f2' ),
					point_f3 = getPoint( 'f3' );

				//изменение размеров усеченной пирамиды FrustumPoints

				//Scale of the base of the frustum points.
				point_n1.x = ( point_n1.x * shaderMaterial.base ) / 100;
				point_n2.x = ( point_n2.x * shaderMaterial.base ) / 100;
				point_n3.x = ( point_n3.x * shaderMaterial.base ) / 100;
				point_n1.y = ( point_n1.y * shaderMaterial.base ) / 100;
				point_n2.y = ( point_n2.y * shaderMaterial.base ) / 100;
				point_n3.y = ( point_n3.y * shaderMaterial.base ) / 100;
				point_f1.x = ( point_f1.x * shaderMaterial.base ) / 100;
				point_f2.x = ( point_f2.x * shaderMaterial.base ) / 100;
				point_f3.x = ( point_f3.x * shaderMaterial.base ) / 100;
				point_f1.y = ( point_f1.y * shaderMaterial.base ) / 100;
				point_f2.y = ( point_f2.y * shaderMaterial.base ) / 100;
				point_f3.y = ( point_f3.y * shaderMaterial.base ) / 100;

				//Square base of the frustum points.
				if ( shaderMaterial.square ) {

					point_n1.x /= camera.aspect;
					point_n2.x /= camera.aspect;
					point_n3.x /= camera.aspect;
					point_f1.x /= camera.aspect;
					point_f2.x /= camera.aspect;
					point_f3.x /= camera.aspect;

				}

				const pointn1x = point_n1.x, pointn2x = point_n2.x, pointn3x = point_n3.x,
					pointn1y = point_n1.y, pointn2y = point_n2.y, pointn3y = point_n3.y,
					pointn1z = point_n1.z;
				//Shift of the frustum layer near to the camera
				point_n1.x = point_n1.x + ( ( point_f1.x - point_n1.x ) * shaderMaterial.near ) / 100;
				point_n2.x = point_n2.x + ( ( point_f2.x - point_n2.x ) * shaderMaterial.near ) / 100;
				point_n3.x = point_n3.x + ( ( point_f3.x - point_n3.x ) * shaderMaterial.near ) / 100;
				point_n1.y = point_n1.y + ( ( point_f1.y - point_n1.y ) * shaderMaterial.near ) / 100;
				point_n2.y = point_n2.y + ( ( point_f2.y - point_n2.y ) * shaderMaterial.near ) / 100;
				point_n3.y = point_n3.y + ( ( point_f3.y - point_n3.y ) * shaderMaterial.near ) / 100;
				point_n1.z = point_n2.z = point_n3.z = point_n1.z + ( ( point_f1.z - point_n1.z ) * shaderMaterial.near ) / 100;
				//Shift of the frustum layer far to the camera
				point_f1.x = point_f1.x + ( ( pointn1x - point_f1.x ) * shaderMaterial.far ) / 100;
				point_f2.x = point_f2.x + ( ( pointn2x - point_f2.x ) * shaderMaterial.far ) / 100;
				point_f3.x = point_f3.x + ( ( pointn3x - point_f3.x ) * shaderMaterial.far ) / 100;
				point_f1.y = point_f1.y + ( ( pointn1y - point_f1.y ) * shaderMaterial.far ) / 100;
				point_f2.y = point_f2.y + ( ( pointn2y - point_f2.y ) * shaderMaterial.far ) / 100;
				point_f3.y = point_f3.y + ( ( pointn3y - point_f3.y ) * shaderMaterial.far ) / 100;
				point_f1.z = point_f2.z = point_f3.z = point_f1.z + ( ( pointn1z - point_f1.z ) * shaderMaterial.far ) / 100;

				const pointStart = new THREE.Vector3().copy( point_n1 );

				function sqrtInt( value ) {

					const a = parseInt( Math.sqrt( zCount - 1 ) );
					return parseInt( value / a ) * a;

				}
				const zCount = shaderMaterial.zCount,
					zStep = ( point_f1.z - point_n1.z ) / ( ( zCount - 1 ) * ( zCount - 1 ) );

				//смещение по оси x
				var zx = 0;
				const yCount = shaderMaterial.yCount,
					xCount = yCount * ( shaderMaterial.square ? 1 : parseInt( camera.aspect ) );
				var zy = 0;//смещение по оси y

				//You can see the Chrome memory crash if you has set very big shaderMaterial.zCount or  shaderMaterial.yCount. (about 900000).
				//Unfortunately you cannot to catch memory crash. https://stackoverflow.com/questions/44531357/how-to-catch-and-handle-chrome-memory-crash
				//Instead I temporary set shaderMaterial.zCount to default value and restore it after creating of all z levels.
				//Now you can see default shaderMaterial.zCount after memory crash and reloading of the wab page.
				shaderMaterial.zCount = optionsShaderMaterial.zCount;
				shaderMaterial.yCount = optionsShaderMaterial.yCount;

				const zStart = parseInt( ( zCount * shaderMaterial.stereo.hide ) / 100 ),
					zEnd = zStart + zCount - 1;
				function Z( z ) {

					//console.warn( 'z ' + z );
					const ynStep = ( point_n3.y - point_n1.y ) / ( yCount - 1 ),
						yfStep = ( point_f3.y - point_f1.y ) / ( yCount - 1 ),
						yStep = ( ( yfStep - ynStep ) / ( ( zCount - 1 ) * ( zCount - 1 ) ) ) * z * z + ynStep,
						sqrtZCount = parseInt( Math.sqrt( zCount ) ),
						yzStep = yStep / ( sqrtZCount + parseInt( Math.sqrt( zCount - ( sqrtZCount * sqrtZCount ) ) ) ),//координату точки надо немного сдвинуть в зависимости от z что бы точки не накладывались друг на друга

						xnStep = ( point_n2.x - point_n1.x ) / ( xCount - 1 ),
						xfStep = ( point_f2.x - point_f1.x ) / ( xCount - 1 ),
						xStep = ( ( xfStep - xnStep ) / ( ( zCount - 1 ) * ( zCount - 1 ) ) ) * z * z + xnStep,
						xzStep = xStep / parseInt( Math.sqrt( zCount ) );//координату точки надо немного сдвинуть в зависимости от z что бы точки не накладывались друг на друга
					pointStart.y = - yStep * ( yCount - 1 ) / 2;
					pointStart.x = - xStep * ( xCount - 1 ) / 2;
					for ( var y = 0; y < yCount; y++ ) {

						for ( var x = 0; x < xCount; x++ )
							if ( z >= zStart ) {

								function addPoint( point ) {

									_names.push(

										x === 0 ?
											y === 0 ? { y: y, z: z } : { y: y } :
											x

									);
									array[indexArray] = point.x;
									indexArray++;
									array[indexArray] = point.y;
									indexArray++;
									array[indexArray] = point.z;
									indexArray++;

								}
								addPoint( new THREE.Vector3(

									pointStart.x + xStep * x + xzStep * zx,
									pointStart.y + yStep * y + yzStep * zy,
									pointStart.z + zStep * z * z

								) );

							}

					}
					zx++;
					if ( zx >= parseInt( Math.sqrt( zCount ) ) ) {

						zx = 0;
						zy++;

					}

				}
				function eachZ( zStart, zEnd ) {

					if ( zStart > zEnd )
						return;
					Z( zStart );
					if ( zStart >= zEnd )
						return;
					Z( zEnd );
					var zMid = parseInt( ( zStart + zEnd ) / 2 );
					if ( zMid === zStart )
						return;//for testing set 'Z Count' = 6
					Z( zMid );
					eachZ( zStart + 1, zMid - 1 );
					eachZ( zMid + 1, zEnd - 1 );

				}

				//For Chrome memory crash see above.
				shaderMaterial.zCount = zCount;
				shaderMaterial.yCount = yCount;

				//если оставить эту строку то когда произойдет переполнение памяти, веб страница вечно будет не открываться
				//for testing set 'Z Count' = 100 and 'Y count' = 1000
				//saveSettings();

				removePoints( true );

				const itemSize = 3;
				_this.pointIndexes = function ( pointIndex ) {

					if ( _names === undefined ) {

						console.error( '_names = ' + _names );//сюда попадает в отладочной версии
						return undefined;

					}
					var name = _names[pointIndex], x, y, z, index = pointIndex;
					function getObject() {

						index--;
						while ( ( index >= 0 ) && ( typeof _names[index] !== "object" ) )
							index--;
						name = _names[index];

					}
					function getZ() {

						while ( ( index >= 0 ) && ( name.z === undefined ) )
							getObject();

					}
					if ( typeof name === "object" ) {

						x = 0;
						y = name.y;
						getZ();
						z = name.z;

					} else {

						x = name;
						getObject();
						y = name.y;
						getZ();
						z = name.z;

					}
					return { x: x, y: y, z: z };

				}

				//Thanks to https://stackoverflow.com/a/27369985/5175935
				//Такая же функция есть в myPoints.js но если ее использовать то она будет возвращать путь на myPoints.js
				const getCurrentScript = function () {

					if ( document.currentScript && ( document.currentScript.src !== '' ) )
						return document.currentScript.src;
					const scripts = document.getElementsByTagName( 'script' ),
						str = scripts[scripts.length - 1].src;
					if ( str !== '' )
						return src;
					//Thanks to https://stackoverflow.com/a/42594856/5175935
					return new Error().stack.match( /(https?:[^:]*)/ )[0];

				};
				//Thanks to https://stackoverflow.com/a/27369985/5175935
				const getCurrentScriptPath = function () {
					const script = getCurrentScript(),
						path = script.substring( 0, script.lastIndexOf( '/' ) );
					return path;
				};
				//console.warn( 'getCurrentScriptPath = ' + getCurrentScriptPath() );
				var path = getCurrentScriptPath();
				var cameraPositionDefault = new THREE.Vector3( camera.position.x, camera.position.y, camera.position.z );
				var cameraQuaternionDefault = new THREE.Vector4( camera.quaternion.x, camera.quaternion.y, camera.quaternion.z, camera.quaternion.w );

				//сделал это приравниванеие что бы избежать двойного создания массива точек frustumPoints MyThree.MyPoints(...)
				//Если это произойдет, то непонятно почему для каждой точки будет создано два облака
				//Одно облако верное
				//Второе находится строго между точкой и камерой
				//Второе облако можно увидеть если повернуть камеру с помощью orbitControl
				//Сюда попадает по второму разу если вызвать stereoEffect.gui(...)
				_points = false;

				new MyPoints( function () {

					var geometry = new THREE.BufferGeometry(),
						geometryLength = ( zEnd - zStart + 1 ) * xCount * yCount;

					array = new Float32Array( geometryLength * itemSize );
					indexArray = 0;
					_names = null;
					_names = [];

					eachZ( zStart, zEnd );

					geometry.setAttribute( 'position', new THREE.BufferAttribute( array, itemSize ) );

					return geometry;

				}, group, {

					options: options,
					pointsOptions: {

						name: 'frustum points',
						shaderMaterial: shaderMaterial,
						boFrustumPoints: true,
						position: camera.position,
						scale: camera.scale,
						rotation: camera.rotation,
						opacity: true,
						pointIndexes: function ( pointIndex ) { return _this.pointIndexes( pointIndex ); },
						path: {

							vertex: path + '/frustumPoints/vertex.c',

						},
						pointName: function ( pointIndex ) {

							var indexes = _this.pointIndexes( pointIndex );
							if ( indexes === undefined )
								return indexes;
							return 'x = ' + indexes.x + ', y = ' + indexes.y + ', z = ' + ( indexes.z + zStart ) + ', i = ' + pointIndex;

						},
						controllers: function () {

							if ( _guiSelectPoint ) _guiSelectPoint.appendChild( { xCount: xCount, yCount: yCount, zCount: zCount, } );

						},
						uniforms: function ( uniforms ) {

							cloud.create( uniforms );

							//rotate the quaternion vector to 180 degrees
							cameraQuaternionDefault.x = - cameraQuaternionDefault.x;
							cameraQuaternionDefault.y = - cameraQuaternionDefault.y;
							cameraQuaternionDefault.z = - cameraQuaternionDefault.z;

							cameraPositionDefault.applyQuaternion( cameraQuaternionDefault );

							uniforms.cameraPositionDefault = { value: cameraPositionDefault };
							uniforms.cameraQuaternion = { value: camera.quaternion };

							//palette
							//ВНИМАНИЕ!!! Для того, что бы палитра передалась в vertex надо добавить
							//points.material.uniforms.palette.value.needsUpdate = true;
							//в getShaderMaterialPoints.loadShaderText

							
							new cloud.addUniforms(THREE.RGBAFormat, 256, 'palette', {

								onReady: function ( data, itemSize, updateItem ) {

									options.scales.setW();
									const min = options.scales.w.min, max = options.scales.w.max;
									const size = data.length / itemSize;
									for ( var i = 0; i < size; i++ )
										updateItem( i, options.palette.toColor( ( max - min ) * i / ( size - 1 ) + min, min, max ) );

								}

							} );
							return cloud;

						},
						onReady: function ( points ) {

							_points = points;
							_points.userData.isInfo = function () { return shaderMaterial.info; }

							if ( shaderMaterial.info && options.raycaster )
								options.raycaster.addParticle( _points );

							if ( !shaderMaterial.display )
								removePoints();
							pointOpacity = _points === undefined ?
								1.0 :
								_points.userData.shaderMaterial === undefined ? shaderMaterial.point.opacity : _points.userData.shaderMaterial.point.opacity;

							if ( onReady !== undefined )
								onReady();//Пользователь изменил настройки frustumPoints

							//когда задан параметр cameraTarget у MyThree то нужно передвинуть frustumPoints после того как созданы points
							_this.update();

							if ( options.guiSelectPoint ) options.guiSelectPoint.addMesh( _points );

						},

					}

				} );

			}
			update();
			var pointOpacity;
			/** Moves frustum points in front of the camera. */
			this.update = function ( onReady ) {

				update( onReady );

				//Эта команда нужна в случае изменения размера окна браузера когда canvas на весь экран
				setPointsParams();

				var cameraQuaternionDefault = new THREE.Vector4( camera.quaternion.x, camera.quaternion.y, camera.quaternion.z, camera.quaternion.w );

				if ( !_points )
					return;//User has changed 'Z Count' of the frustumPoints

				_points.material.uniforms.cameraPositionDefault.value.copy( camera.position );

				//rotate the quaternion vector to 180 degrees
				cameraQuaternionDefault.x = - cameraQuaternionDefault.x;
				cameraQuaternionDefault.y = - cameraQuaternionDefault.y;
				cameraQuaternionDefault.z = - cameraQuaternionDefault.z;

				_points.material.uniforms.cameraPositionDefault.value.applyQuaternion( cameraQuaternionDefault );

			}
			/** 
			 *  @returns true - frustum points is created and visible.
			 *  <p>false - frustum points have been removed.</p>
			 * */
			this.isDisplay = function () { return shaderMaterial.display; }
			/** Updates the cloud's points according new position of the all points of the all meshes
			 * of the group of objects to which a new FrustumPoints has been added. */
			this.updateCloudPoints = function () {

				group.children.forEach( function ( mesh ) {

					if ( !mesh.userData.cloud )
						return;
					if ( mesh.geometry.attributes.position.itemSize !== 4 ) {

						console.error( 'mesh.geometry.attributes.position.itemSize = ' + mesh.geometry.attributes.position.itemSize );
						return;

					}
					cloud.updateMesh( mesh );

				} );
				needsUpdate();

			}
			function needsUpdate() {

				if ( _points )
					_points.material.uniforms.cloudPoints.value.needsUpdate = true;

			}
			/**
			 * 
			 * @param {THREE.Mesh} mesh [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh}
			 */
			this.updateCloudPoint = function ( mesh ) {

				cloud.updateMesh( mesh );
				needsUpdate();

			}
			/**
			 * The user has changed selected point.
			 * @event
			 * @param {THREE.Points} points [Points]{@link https://threejs.org/docs/index.html?q=Poin#api/en/objects/Points}
			 * @param {number} i index of the point from <b>points</b> for udating
			 */
			this.updateCloudPointItem = function ( points, i ) {

				if ( points.userData.cloud === undefined )
					return;
				if ( points.geometry.attributes.position.itemSize !== 4 )
					console.error( 'points.geometry.attributes.position.itemSize = ' + points.geometry.attributes.position.itemSize );
				cloud.cloudPoints.updateItem( points.userData.cloud.indexArray + i,
					getWorldPosition( points,
						new THREE.Vector4().fromArray( points.geometry.attributes.position.array, i * points.geometry.attributes.position.itemSize ) ),
					true );
				needsUpdate();

			}

			//Convert all points with cloud, but not shaderMaterial from local to world positions
			// i.e. calculate scales, positions and rotation of the points.
			//Converting of all points with cloud and shaderMaterial see getShaderMaterialPoints function in the myPoints.js file
			//		this.updateCloudPoints();

			function removePoints( notRemoveMesh ) {

				if ( _points === undefined )
					return;
				if ( !notRemoveMesh && options.guiSelectPoint )
					options.guiSelectPoint.removeMesh( _points );//не удаляю frustumPoints из списка Meshes потому что сюда попадает только если пользователь изменил число точек frustumPoints.
				//В этом случае создается новый frustumPoints, который надо присоеденить к старому frustumPoints из списка Meshes.
				//Если я удалю frustumPoints из списка Meshes а потом добавлю туда новый frustumPoints,
				//то изменится индекс frustumPoints в списке Meshes
				//и тогда неверно будет выполняться function update() в frustumPoints и как результат буде неверный список Ponts списке Meshes
				//for testing
				//Select in the canvas any point, but not frustum point.
				//Now you can see your selected point in the in the Meshes/Points/Select list in the gui.
				//Change Settings/Frustum Points/Z count in the gui.
				//Now your selected point is deselected.
				//Select in the canvas your point again.
				//Now yiou can see "Cannot read property 'selected' of undefined" error message in the console.
				//Try to select your point in the gui. You can not to do it because your point is not exists in the Meshes/Points/Select list. Instead you see all Frustum Points in the Meshs/Points/Select list.

				group.remove( _points );
				renderer.renderLists.dispose();
				clearThree( _points );
				_points = undefined;

			}
			/** Called from animate loop for rendering.
			 * @see {@link https://threejs.org/docs/index.html?q=animate#manual/en/introduction/Creating-a-scene|Rendering the scene}*/
			this.animate = function () {

				if (
					!_points ||
					( _points.userData.shaderMaterial === undefined ) ||
					(
						( pointOpacity === _points.userData.shaderMaterial.point.opacity ) )
				) {

					return false;

				}
				pointOpacity = _points.userData.shaderMaterial.point.opacity;
				_points.material.uniforms.opacity.value = _points.userData.shaderMaterial.point.opacity;
				return true;

			}

			/** update "frustum points" item in the <a href="../../guiSelectPoint/jsdoc/index.html" target="_blank">GuiSelectPoint</a>.*/
			this.updateGuiSelectPoint = function () {


				//Не помню почему не удаляю старый points из списка cMeshs, но если так делать, то будут какие то запутанные косяки.
				//Поэтому не удаляю points из списка cMeshs а только получаю индекс points в этом списке.
				const index = options.guiSelectPoint ? options.guiSelectPoint.getMeshIndex( _points ) : undefined;

				update();

				//затем заменяю указатель на старый points в списке cMeshs на новый.
				if ( index ) options.guiSelectPoint.setIndexMesh( index, _points );

			}

			/**
			* @callback FolderPoint
			* @param {object} folder parent folder
			* @param {function} setSettings save points setting to the cookie
			*/

			/**
			 * Adds FrustumPoints folder into dat.GUI.
			 * See [dat.GUI API]{@link https://github.com/dataarts/dat.gui/blob/master/API.md}.
			 * @param {object} [folder] parent folder
			 */
			this.gui = function ( folder ) {

				const dat = three.dat;//options.dat.dat;

				folder = folder || options.dat.gui;
				if ( !folder || options.dat.guiFrustumPoints === false )
					return;

				//Localization

				const lang = {

					frustumPoints: 'Frustum Points',
					frustumPointsTitle: 'A cloud of the fixed points in front of the camera for describe the properties of space.',

					display: 'Display',
					displayTitle: 'Display or hide Frustum Points.',

					info: 'Information',
					infoTitle: 'Display information about frustum point if user move mouse over or click this point.',

					stereo: 'Stereo',
					stereoTitle: 'Frustum Points setting for stereo mode of the canvas',

					hide: 'Hide Nearby Points',
					hideTitle: 'Hide the nearby to the camera points in percentage to all points for more comfortable visualisation.',

					opacity: 'Opacity',
					opacityTitle: 'Float in the range of 0.0 - 1.0 indicating how transparent the lines is. A value of 0.0 indicates fully transparent, 1.0 is fully opaque.',

					near: 'Near Layer',
					nearTitle: 'Shift of the frustum layer near to the camera in percents.',

					far: 'Far Layer',
					farTitle: 'Shift of the frustum layer far to the camera in percents.',

					base: 'Scale',
					baseTitle: 'Scale of the base of the frustum points in percents.',

					square: 'Square Base',
					squareTitle: 'Square base of the frustum points.',

					defaultButton: 'Default',
					defaultTitle: 'Restore default Frustum Points settings.',

					zCount: 'Z Count',
					zCountTitle: "The count of layers of the frustum of the camera's field of view.",

					yCount: 'Y Count',
					yCountTitle: "The count of vertical points for each z level of the  frustum of the camera's field of view.",

				};
				switch ( options.getLanguageCode() ) {

					case 'ru'://Russian language

						lang.frustumPoints = 'Неподвижные точки';
						lang.frustumPointsTitle = 'Облако точек перед камерой для описания свойств пространства';

						lang.display = 'Показать';
						lang.displayTitle = 'Показать или скрыть неподвижные точки.';

						lang.info = 'Информация';
						lang.infoTitle = 'Отображать информацию о неподвижной точке, если пользователь наведет курсор мыши или щелкнет эту точку.';

						lang.stereo = 'Стерео';
						lang.stereoTitle = 'Настройки неподвижных точек для стерео режима холста';

						lang.hide = 'Близкие точки';
						lang.hideTitle = 'Скрыть близкие к камере точки в процентах ко всем точкам для более удобной визуализации.';

						lang.opacity = 'Непрозрачность';
						lang.opacityTitle = 'Число в диапазоне 0.0 - 1.0, указывающее, насколько прозрачены линии. Значение 0.0 означает полностью прозрачный, 1.0 - полностью непрозрачный.';

						lang.near = 'Ближний слой';
						lang.nearTitle = 'Смещение ближайшего к камере слоя точек в процентах.';

						lang.far = 'Дальний слой';
						lang.farTitle = 'Смещение дальнего от камеры слоя точек в процентах.';

						lang.base = 'Масштаб';
						lang.baseTitle = 'Масштаб неподвижных точек в процентах.';

						lang.square = 'Квадратное основание';
						lang.squareTitle = 'Неподвиждые точки образуют пирамиду с квадратным основанием.';

						lang.defaultButton = 'Восстановить';
						lang.defaultTitle = 'Восстановить настройки неподвижных точек.';

						lang.zCount = 'Z cлои';
						lang.zCountTitle = 'Количество слоев усеченной пирамиды, образующей поле зрения камеры';

						lang.yCount = 'Y точки';
						lang.yCountTitle = "Количество вертикальных точек в каждом слое усеченной пирамиды, образующей поле зрения камеры.";

						break;
					default://Custom language
						if ( ( options.lang === undefined ) || ( options.lang.languageCode != languageCode ) )
							break;

						Object.keys( options.lang ).forEach( function ( key ) {

							if ( lang[key] === undefined )
								return;
							lang[key] = options.lang[key];

						} );

				}

				function saveSettings() {

					cookie.setObject( cookieName, shaderMaterial );

				}
				const fFrustumPoints = folder.addFolder( lang.frustumPoints );
				dat.folderNameAndTitle( fFrustumPoints, lang.frustumPoints, lang.frustumPointsTitle );

				function displayControllers( value ) {

					var display = value ? 'block' : 'none';
					folderPoint.display( display );
					cZCount.__li.style.display = display;
					cYCount.__li.style.display = display;

				}
				//Display frustumPoints
				const cDisplay = fFrustumPoints.add( shaderMaterial, 'display' ).onChange( function ( value ) {

					if ( shaderMaterial.display ) {

						update();

					} else {

						if ( options.raycaster ) options.raycaster.removeParticle( _points );
						removePoints();

					}
					displayControllers( shaderMaterial.display );
					saveSettings();

				} );
				dat.controllerNameAndTitle( cDisplay, lang.display, lang.displayTitle );

				//FrustumPoints info.
				//Display information about frustum point if user move mouse over or click this point.
				const cInfo = !options.raycaster ? undefined : fFrustumPoints.add( shaderMaterial, 'info' ).onChange( function ( value ) {

					if ( _points === undefined ) {

						saveSettings();
						return;

					}
					if ( shaderMaterial.info ) {

						if ( options.raycaster ) options.raycaster.addParticle( _points );
						
					} else {

						if ( options.guiSelectPoint ) options.guiSelectPoint.selectPoint( -1 );
						if ( options.raycaster ) options.raycaster.removeParticle( _points );

					}
					saveSettings();

				} );
				if ( cInfo ) dat.controllerNameAndTitle( cInfo, lang.info, lang.infoTitle );

				//Shift of the frustum layer near to the camera in percents
				const cNear = fFrustumPoints.add( shaderMaterial, 'near', 0, 100, 1 ).onChange( function ( value ) { update(); } );
				dat.controllerNameAndTitle( cNear, lang.near, lang.nearTitle );

				//Shift of the frustum layer far to the camera in percents
				const cFar = fFrustumPoints.add( shaderMaterial, 'far', 0, 100, 1 ).onChange( function ( value ) { update(); } );
				dat.controllerNameAndTitle( cFar, lang.far, lang.farTitle );

				//Scale of the base of the frustum points in percents.
				const cBase = fFrustumPoints.add( shaderMaterial, 'base', 0, 100, 1 ).onChange( function ( value ) { update(); } );
				dat.controllerNameAndTitle( cBase, lang.base, lang.baseTitle );

				//Square base of the frustum points.
				const cSquare = fFrustumPoints.add( shaderMaterial, 'square' ).onChange( function ( value ) { update(); } );
				dat.controllerNameAndTitle( cSquare, lang.square, lang.squareTitle );

				const folderPoint = new FolderPoint( shaderMaterial.point, function ( value ) {

					//Не помню зачем это написал
					if ( value === undefined ) {

						console.warn( 'under constraction' );

					}
					if ( value < 0 )
						value = 0;

					_points.material.uniforms.pointSize.value = value;

					folderPoint.size.setValue( value );
					shaderMaterial.point.size = value;
					saveSettings();

				}, new Options( { dat: {gui: options.dat.gui } } ), {

					folder: fFrustumPoints,
					defaultPoint: { size: 0.01 },
					PCOptions: {

						settings: { offset: 0.1 },
						max: 0.1,

					},

				} );

				var toUpdate = true,//Когда пользователь нажимает кнопку Default надо установить toUpdate = false
					//что бы несколько раз не вызывался _this.update(); чтобы быстрее работало
					canUpdate = true,
					_this = this;

				function update() {

					if ( !toUpdate || !canUpdate )
						return;
					canUpdate = false;

					//Не помню почему не удаляю старый points из списка cMeshs, но если так делать, то будут какие то запутанные косяки.
					//Поэтому не удаляю points из списка cMeshs а только получаю индекс points в этом списке.
					const index = options.guiSelectPoint ? options.guiSelectPoint.getMeshIndex( _points ) : undefined;

					if ( options.raycaster ) options.raycaster.removeParticle( _points );
					removePoints( true );

					_this.update( function () {

						//затем заменяю указатель на старый points в списке cMeshs на новый.
						if ( options.guiSelectPoint ) options.guiSelectPoint.setIndexMesh( index, _points );

						saveSettings();
						canUpdate = true;

						_points.userData.controllers();

					} );

				}

				//zCount
				const cZCount = fFrustumPoints.add( shaderMaterial, 'zCount' ).min( 3 ).step( 1 ).onChange( function ( value ) { update(); } );
				dat.controllerNameAndTitle( cZCount, lang.zCount, lang.zCountTitle );

				//yCount
				const cYCount = fFrustumPoints.add( shaderMaterial, 'yCount' ).min( 3 ).step( 1 ).onChange( function ( value ) { update(); } );
				dat.controllerNameAndTitle( cYCount, lang.yCount, lang.yCountTitle );

				//Default button
				dat.controllerNameAndTitle( fFrustumPoints.add( {

					defaultF: function ( value ) {

						toUpdate = false;

						cDisplay.setValue( optionsShaderMaterial.display );
						if ( cInfo ) cInfo.setValue( optionsShaderMaterial.info );

						cNear.setValue( optionsShaderMaterial.near );
						cFar.setValue( optionsShaderMaterial.far );
						cBase.setValue( optionsShaderMaterial.base );
						cSquare.setValue( optionsShaderMaterial.square );

						folderPoint.size.setValue( optionsShaderMaterial.point.size );

						cZCount.setValue( optionsShaderMaterial.zCount );
						cYCount.setValue( optionsShaderMaterial.yCount );

						toUpdate = true;
						update();
						saveSettings();

					},

				}, 'defaultF' ), lang.defaultButton, lang.defaultTitle );
				displayControllers( shaderMaterial.display );

			}
			return this;

		}

		/** @class [GUI]{@link https://github.com/anhr/dat.gui} for select a frustum point.
		 *  Uses in the <a href="../../guiSelectPoint/jsdoc/index.html" target="_blank">guiSelectPoint</a>.
		 */
		this.guiSelectPoint = function () {

			var cFrustumPointsX = null, cFrustumPointsY = null, cFrustumPointsZ = null;
			_guiSelectPoint = this;
			/**
			 * create controls
			 * @param {GUI} fPoints parent folder. See [GUI]{@link https://github.com/anhr/dat.gui}.
			 * @param {string} languageCode Localization. The "primary language" subtag of the language version of the browser.
			 * <pre>
			 * Examples: "en" - English language, "ru" Russian.
			 * See the {@link https://tools.ietf.org/html/rfc4646#section-2.1|rfc4646 2.1 Syntax} for details.
			 * </pre>
			 */
			this.create = function ( fPoints, languageCode ) {

				const dat = three.dat;//options.dat.dat;
				function frustumPointsControl( name ) {


					//Localization

					const lang = {

						notSelected: 'Not selected',

					};

					switch ( languageCode ) {

						case 'ru'://Russian language
							lang.notSelected = 'Не выбран';

							break;

					}

					const controller = fPoints.add( { Points: lang.notSelected }, 'Points', { [lang.notSelected]: -1 } ).onChange( function ( value ) {

						const index = _guiSelectPoint.getSelectedIndex();
						if ( index === null ) {

							if ( options.axesHelper ) options.axesHelper.exposePosition();
							return;

						}
						options.guiSelectPoint.select( { object: _points, index: index } );

					} );
					controller.__select[0].selected = true;
					//Не стоит переименовывать шкалы потому что шкала frustumPoints не совпадает со общей шкалой
					//			dat.controllerNameAndTitle( controller, scales[name].name ? scales[name].name : name );
					dat.controllerNameAndTitle( controller, name );
					return controller;

				}

				cFrustumPointsX = frustumPointsControl( 'x' );
				cFrustumPointsY = frustumPointsControl( 'y' );
				cFrustumPointsZ = frustumPointsControl( 'z' );

			}

			/**
			 * Append new item into controller
			 * @param {object} count Count of points in the FrustumPoints
			 * @param {number} count.xCount Count of rows of the points in the x axis
			 * @param {number} count.yCount Count of rows of the points in the y axis
			 * @param {number} count.zCount Count of layers of the points in the z axis
			 */
			this.appendChild = function ( count ) {

				function appendChild( cFrustumPoint, count ) {

					//thanks to https://stackoverflow.com/a/48780352/5175935
					cFrustumPoint.domElement.querySelectorAll( 'select option' ).forEach( option => { if ( option.value != '-1' ) option.remove() } );

					for ( var i = 0; i < count; i++ ) {

						var opt = document.createElement( 'option' );
						opt.innerHTML = i;
						cFrustumPoint.__select.appendChild( opt );

					}
					cFrustumPoint.setValue( -1 );//если не выбрать ни одной точки,
						//то при повторном выборе frustumPoints в списке cMeshs выберется точка, которая была выбрана прошлый раз.
						//Но индекс этой выбранной точки почему то не будет выбран в cFrustumPoints
						//Для проверки
						//выбрать точку frustumPoints в guiSelectPoint. Если есть оси кооддинат, то от выбранной точки появятся пунктирные линии.
						//Выбрать точку не frustumPoints в guiSelectPoint.
						//Опять выбрать точку frustumPoints в guiSelectPoint. Раньше появлялись пунктирные линии, но в огранах управления почемуто это точка не выбиралась.

				}
				appendChild( cFrustumPointsX, count.xCount );
				appendChild( cFrustumPointsY, count.yCount );
				appendChild( cFrustumPointsZ, count.zCount );

			}
			/**
			 * Sets to the controls the indexes of the selected frustum point.
			 * @param {object} index
			 * @param {object} index.x index of the x row of selected frustum point
			 * @param {object} index.y index of the y row of selected frustum point
			 * @param {object} index.z index of the z layer of selected frustum point
			 */
			this.pointIndexes = function ( index ) {

				if ( index === undefined )
					return;//Сюда попадает в отладочной версии когда не заданы имена точек
				if ( parseInt( cFrustumPointsX.getValue() ) !== index.x )
					cFrustumPointsX.setValue( index.x );
				if ( parseInt( cFrustumPointsY.getValue() ) !== index.y )
					cFrustumPointsY.setValue( index.y );
				if ( parseInt( cFrustumPointsZ.getValue() ) !== index.z )
					cFrustumPointsZ.setValue( index.z );

			};
			/**
			 * @returns frustum point index selected by user from controls.
			 * <p>null if user have not selected any point.</p>
			 */
			this.getSelectedIndex = function () {

				if ( _names === undefined ) {

					console.warn( 'Сюда попадает во время отладки когда не задаю имени каждой точки или когда the cDisplay checkbox of the frustumPoints is not checked' );
					return null;

				}
				const x = parseInt( cFrustumPointsX.getValue() ),
					y = parseInt( cFrustumPointsY.getValue() ),
					z = parseInt( cFrustumPointsZ.getValue() );
				if ( isNaN( x ) || ( x === -1 ) || isNaN( y ) || ( y === -1 ) || isNaN( z ) || ( z === -1 ) )
					return null;
				for ( var i = 0; i < _names.length; i++ ) {

					var name = _names[i];
					if ( ( typeof name !== "object" ) || ( name.z === undefined ) || ( name.z !== z ) )
						continue;
					for ( ; i < _names.length; i++ ) {

						name = _names[i];
						if ( ( typeof name !== "object" ) || ( name.y !== y ) )
							continue;
						for ( ; i < _names.length; i++ ) {

							name = _names[i];
							if ( typeof name === "object" ) {

								if ( ( x === 0 ) && ( name.y === y ) ) {

									//Сюда попадает когда в guiSelectPoint пользователь выбрал frustum Point с коодинатами 0,0,0,
									return i;

								}

							}
							if ( name === x ) {

								//console.warn( 'x = ' + x + ', y = ' + y + ', z = ' + z + ', i = ' + i );
								return i;

							}

						}

					}

				}
				console.error( 'FrustumPoints.selectPoint: not selected' );
				return null;

			}
			/**
			 * Display or hide the <b>FrustumPoints</b> controls.
			 * @param {string} display 'block' - display
			 * <p>'none' - hide</p>
			 */
			this.display = function ( display ) {

				cFrustumPointsX.domElement.parentElement.parentElement.style.display = display;
				cFrustumPointsY.domElement.parentElement.parentElement.style.display = display;
				cFrustumPointsZ.domElement.parentElement.parentElement.style.display = display;

			}
			/**
			 * Is FrustumPoints controls visible?
			 * @returns true - FrustumPoints controls is visible.
			 * */
			this.isDisplay = function () {

				if (
					( cFrustumPointsX.domElement.parentElement.parentElement.style.display !== cFrustumPointsY.domElement.parentElement.parentElement.style.display )
					|| ( cFrustumPointsX.domElement.parentElement.parentElement.style.display !== cFrustumPointsZ.domElement.parentElement.parentElement.style.display )
				)
					console.error( 'cFrustumPointsF.isDisplay failed!' );
				return cFrustumPointsX.domElement.parentElement.parentElement.style.display !== 'none';

			}

		}

		//делаю затычки на случай пустого _arrayCloud = [] массива координат точек, имеющих облако вокруг себя.
		//Другими словми если в программе нет ни одной точки с облаком вокруг себя.
		//В этом случае точки FrustumPoints не создаются
		this.gui = function () { console.warn( 'FrustumPoints.gui(): First, call FrustumPoints.pushArrayCloud(...) for push a points to the clouds array and call FrustumPoints.create(...).' ); }
		this.animate = function () { }
		this.updateGuiSelectPoint = function () { }
		this.isDisplay = function () { }
		this.onChangeControls = function () { }
		this.updateCloudPoints = function () { }
		this.updateCloudPoint = function () { }
		this.updateCloudPointItem = function () { }

	}

 }

export default FrustumPoints;