Source: StereoEffect.js

/**
 * @module StereoEffect
 * @description Uses dual PerspectiveCameras for Parallax Barrier effects.
 * @see About {@link https://en.wikipedia.org/wiki/Parallax_barrier|Parallax barrier}.
 * 
 * @author [Andrej Hristoliubov]{@link https://github.com/anhr}
 * @author {@link http://alteredqualia.com/|alteredq}
 * @author {@link http://mrdoob.com/|mrdoob}
 * @author {@link http://aleksandarrodic.com/|arodic}
 * @author {@link http://fonserbc.github.io/|fonserbc}
 *
 * @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 three from '../three.js'

import PositionController from '../PositionController.js';
//import PositionController from 'https://raw.githack.com/anhr/commonNodeJS/master/PositionController.js';

import CreateFullScreenSettings from '../createFullScreenSettings.js';
import Options from '../Options.js'

class StereoEffect {

	/**
	 * @class
	 * Uses dual PerspectiveCameras for [Parallax Barrier]{@link https://en.wikipedia.org/wiki/Parallax_barrier} effects.
	 * @param {THREE.WebGLRenderer} renderer {@link https://threejs.org/docs/#api/en/renderers/WebGLRenderer|WebGL renderer}
	 * @param {Options} [options=new Options()] <a href="../../jsdoc/Options/Options.html" target="_blank">Options</a> instance.
	 * See <b>options</b> parameter of <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
	 * @param {Function} [options.getLanguageCode=language code of your browser] Your custom getLanguageCode() function.
	 * <pre>
	 * returns the "primary language" subtag of the language version of the browser.
	 * Examples: "en" - English language, "ru" Russian.
	 * See the {@link https://tools.ietf.org/html/rfc4646#section-2.1|Syntax} paragraph of RFC 4646 for details.
	 * </pre>
	 * @param {Object} [options.lang] Object with localized language values.
	 * @param {boolean} [options.dat.cookie] false - do not save to cookie the StereoEffects  settings
	 * @param {string} [options.dat.cookieName] Name of the cookie is "StereoEffect" + options.dat.cookieName.
	 * @param {boolean|Object} [options.stereoEffect] false - do not create a <b>StereoEffect</b> instance.
	 * <p>Or:</p>
	 * @param {number} [options.stereoEffect.spatialMultiplex=spatialMultiplexsIndexs.Mono] spatial multiplex
	 * <pre>
	 * See {@link https://en.wikipedia.org/wiki/DVB_3D-TV|DVB 3D-TV} for details
	 * 	Available values
	 *
	 * 		spatialMultiplexsIndexs.Mono - no stereo effacts
	 *
	 * 		spatialMultiplexsIndexs.SbS - 'Side by side' format just put the left and right images one next to the other.
	 * 			See https://en.wikipedia.org/wiki/DVB_3D-TV#Side_by_side for dretails
	 *
	 * 		spatialMultiplexsIndexs.TaB - 'Top and bottom' format put left and right images one above the other.
	 * 			See //https://en.wikipedia.org/wiki/DVB_3D-TV#Top_and_bottom for details
	 *
	 * 	Example - options.stereoEffect.spatialMultiplex: StereoEffect.spatialMultiplexsIndexs.Mono
	 * </pre>
	 * @param {THREE.PerspectiveCamera} [options.stereoEffect.camera] [PerspectiveCamera]{@link https://threejs.org/docs/index.html#api/en/cameras/PerspectiveCamera}. Use the <b>camera</b> key if you want control cameras focus.
	 * @param {Float} [options.stereoEffect.far=10] Camera frustum far plane. The <b>far</b> key uses for correct calculation default values of Eye separation.
	 * See <b>far</b> parameter of the [PerspectiveCamera]{@link https://threejs.org/docs/index.html?q=PerspectiveCamera#api/en/cameras/PerspectiveCamera.far}
	 * @param {Float} [options.stereoEffect.eyeSep=( new THREE.StereoCamera().eyeSep / 10 ) * options.stereoEffect.far] Eye separation.
	 * See [StereoCamera.eyeSep]{@link https://threejs.org/docs/index.html?q=StereoCamera#api/en/cameras/StereoCamera.eyeSep}.
	 * @param {Float} [options.stereoEffect.stereoAspect=1] [THREE.StereoCamera.aspect]{@link https://threejs.org/docs/index.html?q=StereoCamera#api/en/cameras/StereoCamera.aspect}. Camera frustum aspect ratio.
	 * @param {boolean} [options.stereoEffect.rememberSize] true - remember default size of the canvas. Resize of the canvas to full screen for stereo mode and restore to default size if no stereo effacts.
	 * @param {HTMLElement} [options.stereoEffect.elParent] parent of the canvas.
	 * Use only if you use {@link https://threejs.org/docs/index.html#api/en/core/Raycaster|THREE.Raycaster} (working out what objects in the 3d space the mouse is over)
	 * and your canvas is not full screen.
	 */
	constructor( renderer, options = new Options() ) {

		if ( !renderer ) {

			console.error( 'StereoEffect: renderer = ' + renderer );
			return;

		}

		if ( !options.boOptions ) {

			options = new Options( options );

		}
		if ( options.stereoEffect === false ) return;

		if( options.dat.gui ) options.dat.mouseenter = false;//true - мышка находится над gui или canvasMenu
				//В этом случае не надо обрабатывать событие elContainer 'pointerdown'
				//по которому выбирается точка на canvas.
				//В противном случае если пользователь щелкнет на gui, то он может случайно выбрать точку на canvas.
				//Тогда открывается папка Meshes и все органы управления сдвигаются вниз. Это неудобно.
				//И вообще нехорошо когда выбирается точка когда пользователь не хочет это делать.

		const THREE = three.THREE;
		assign();

		if ( !options.stereoEffect ) options.stereoEffect = {};

		const settings = options.stereoEffect;
		/**
		 * See <b>options.stereoEffect</b> parameter of the <b>StereoEffect</b> class above.
		 * */
		this.settings = settings;
		/**
		 * See <b>options</b> parameter of the <b>StereoEffect</b> class above.
		 * */
		this.options = options;

		options.stereoEffect = this;

		//Убрал spatialMultiplex: settings.spatialMultiplex !== undefined ? settings.spatialMultiplex : spatialMultiplexsIndexs.Mono,//SbS,
		//из const optionsDefault
		//Потому что будет ошибка 
		//THREE.StereoEffect.render: Invalid "Spatial  multiplex" parameter: NaN
		//если в приложении не используется StereoEffect.gui
		//и не определен settings.spatialMultiplex
		if ( settings.spatialMultiplex === undefined ) settings.spatialMultiplex = spatialMultiplexsIndexs.Mono;//SbS

		settings.stereo = new THREE.StereoCamera();
		settings.stereo.aspect = settings.stereoAspect || 1;//0.5;
		if ( settings.far === undefined )
			settings.far = new THREE.PerspectiveCamera().focus;
		settings.focus = settings.camera === undefined ? new THREE.PerspectiveCamera().focus :
			new THREE.Vector3().distanceTo( settings.camera.position );
		settings.zeroParallax = 0;
		settings.eyeSep = settings.eyeSep || ( new THREE.StereoCamera().eyeSep / 10 ) * settings.far;
		if ( settings.camera !== undefined )
			settings.camera.focus = settings.focus;

		/**
		 * Sets eye separation.
		 * @param {any} eyeSep See <b>options.stereoEffect.eyeSep</b> parameter of the <b>StereoEffect</b> class above.
		 */
		this.setEyeSeparation = function ( eyeSep ) {

			settings.stereo.eyeSep = eyeSep;

		};

		this.setEyeSeparation( settings.eyeSep );

		/**
		 * Convert mouse position to renderer coordinates.
		 * @returns <pre>
		 * {
		 *
		 *	getMousePosition:function
		 *
		 * }
		 * </pre>
		 */
		this.getRendererSize = function () {

			return Options.raycaster.EventListeners.getRendererSize( renderer, settings.elParent );

		};
		var fullScreenSettings;
		var spatialMultiplexCur;
		/**
		 * Render a scene or another type of object using a camera.
		 * @see [WebGLRenderer.render]{@link https://threejs.org/docs/index.html?q=WebGLRenderer#api/en/renderers/WebGLRenderer.render}
		 * @param {THREE.Scene} scene [Scene]{@link https://threejs.org/docs/index.html#api/en/scenes/Scene}.
		 * @param {THREE.Camera} camera [PerspectiveCamera]{@link https://threejs.org/docs/index.html?q=persp#api/en/cameras/PerspectiveCamera}.
		 */
		this.render = function ( scene, camera ) {

			const spatialMultiplex = parseInt( settings.spatialMultiplex );

			if ( settings.rememberSize && !fullScreenSettings ) {

				if ( _canvasMenu && _canvasMenu.getFullScreenSettings )
					fullScreenSettings = _canvasMenu.getFullScreenSettings( this );
				else fullScreenSettings = new CreateFullScreenSettings( THREE, renderer, camera,
					{
						canvasMenu: _canvasMenu,
						stereoEffect: this,

					} );

			}

			scene.updateMatrixWorld();

			if ( camera.parent === null ) camera.updateMatrixWorld();

			const size = new THREE.Vector2();
			renderer.getSize( size );

			if ( renderer.autoClear ) renderer.clear();
			renderer.setScissorTest( true );

			var xL, yL, widthL, heightL,
				xR, yR, widthR, heightR;
			const parallax = settings.zeroParallax;
			function setMultiplex( stereoEffect ) {

				if ( !fullScreenSettings || ( spatialMultiplexCur === spatialMultiplex ) )
					return false;
				spatialMultiplexCur = spatialMultiplex;
				if ( stereoEffect.setControllerSpatialMultiplex ) stereoEffect.setControllerSpatialMultiplex( spatialMultiplex );
				else if ( stereoEffect.setSpatialMultiplex ) stereoEffect.setSpatialMultiplex( spatialMultiplex );
				return true;

			}
			function setFullScreen( fullScreen, stereoEffect ) {

				if ( setMultiplex( stereoEffect ) )
					fullScreenSettings.setFullScreen( fullScreen );

			}

			switch ( spatialMultiplex ) {

				case spatialMultiplexsIndexs.Mono://Mono

					renderer.setScissor( 0, 0, size.width, size.height );
					renderer.setViewport( 0, 0, size.width, size.height );
					renderer.render( scene, camera );
					renderer.setScissorTest( false );

					//если оствить setFullScreen то canvas не перейдет в full screen
					//если в программе по умолчанию был задан full screen и используется canvasMenu
					//Для провероки
					//в файле "D:\My documents\MyProjects\webgl\three.js\GitHub\commonNodeJS\master\myThree\Examples\html\index.html"
					//	для new MyThree(...)
					//		установить canvasMenu: true,
					//		убрать canvas: { fullScreen: false, }. Тоесть по умолчанию canvas перейдет в full screen
					//открыть http://localhost/anhr/commonNodeJS/master/myThree/Examples/html/

					//setFullScreen( true, this );

					//если оставить setFullScreen то canvas не перейдет в full screen
					//если убрать setFullScreen то canvas перейдет в full screen
					//но тогда будет неправильно выбираться пункт меню stereo Effects в canvasMenu в случае, если 
					//Выбрать пункт "слева направо" меню stereo Effects в canvasMenu.
					//	включится стерео режим.
					//	canvas перейдет в full screen.
					//Выбрать пункт "Восстановить размеры экрана" меню stereo Effects в canvasMenu.
					//	canvas выйдет из full screen.
					//	стерео режим отключится
					//	НО! ОСТАНЕТСЯ ВЫБРАННЫМ пункт "слева направо" меню stereo Effects в canvasMenu.
					//Для решения проблемы вставил строку ниже

					if ( options.canvasMenu ) setMultiplex( this );
					else setFullScreen( true, this );

					//Теперь в режиме full screen при выборе пункта "Моно" меню stereo Effects в canvasMenu
					//canvas не выходит из full screen. Ну и фиг с ним.

					return;

				case spatialMultiplexsIndexs.SbS://'Side by side'

					const _width = size.width / 2;

					xL = 0 + parallax; yL = 0; widthL = _width; heightL = size.height;
					xR = _width - parallax; yR = 0; widthR = _width; heightR = size.height;

					setFullScreen( false, this );

					break;

				case spatialMultiplexsIndexs.TaB://'Top and bottom'

					xL = 0 + parallax; yL = 0; widthL = size.width; heightL = size.height / 2;
					xR = 0 - parallax; yR = size.height / 2; widthR = size.width; heightR = size.height / 2;

					setFullScreen( false, this );

					break;
				default: console.error( 'THREE.StereoEffect.render: Invalid "Spatial  multiplex" parameter: ' + spatialMultiplex );

			}

			settings.stereo.update( camera );

			renderer.setScissor( xL, yL, widthL, heightL );
			renderer.setViewport( xL, yL, widthL, heightL );
			renderer.render( scene, settings.stereo.cameraL );

			renderer.setScissor( xR, yR, widthR, heightR );
			renderer.setViewport( xR, yR, widthR, heightR );
			renderer.render( scene, settings.stereo.cameraR );

			renderer.setScissorTest( false );

		};

		//Localization
		function getLang( params ) {

			params = params || {};
			const _lang = {

				stereoEffects: 'Stereo effects',

				spatialMultiplexName: 'Spatial  multiplex',
				spatialMultiplexTitle: 'Choose a way to do spatial multiplex.',

				spatialMultiplexs: {
					'Mono': spatialMultiplexsIndexs.Mono,
					'Side by side': spatialMultiplexsIndexs.SbS, //https://en.wikipedia.org/wiki/DVB_3D-TV#Side_by_side
					'Top and bottom': spatialMultiplexsIndexs.TaB, //https://en.wikipedia.org/wiki/DVB_3D-TV#Top_and_bottom
				},

				eyeSeparationName: 'Eye separation',
				eyeSeparationTitle: 'The distance between left and right cameras.',

				focus: 'Focus',
				focusTitle: 'Object distance.',

				zeroParallaxName: 'Zero parallax',
				zeroParallaxTitle: 'Distance to objects with zero parallax.',

				defaultButton: 'Default',
				defaultTitle: 'Restore default stereo effects settings.',

			};

			const _languageCode = params.getLanguageCode === undefined ? 'en'//Default language is English
				: params.getLanguageCode();
			switch ( _languageCode ) {

				case 'ru'://Russian language

					_lang.stereoEffects = 'Стерео эффекты';//'Stereo effects'

					_lang.spatialMultiplexName = 'Мультиплекс';//'Spatial  multiplex'
					_lang.spatialMultiplexTitle = 'Выберите способ создания пространственного мультиплексирования.';

					_lang.spatialMultiplexs = {
						'Моно': spatialMultiplexsIndexs.Mono, //Mono
						'Слева направо': spatialMultiplexsIndexs.SbS, //https://en.wikipedia.org/wiki/DVB_3D-TV#Side_by_side
						'Сверху вниз': spatialMultiplexsIndexs.TaB, //https://en.wikipedia.org/wiki/DVB_3D-TV#Top_and_bottom
					};

					_lang.eyeSeparationName = 'Развод камер';
					_lang.eyeSeparationTitle = 'Расстояние между левой и правой камерами.';

					_lang.focus = 'Фокус';
					_lang.focusTitle = 'Расстояние до объекта.';

					_lang.zeroParallaxName = 'Параллакс 0';
					_lang.zeroParallaxTitle = 'Расстояние до объектов с нулевым параллаксом.';

					_lang.defaultButton = 'Восстановить';
					_lang.defaultTitle = 'Восстановить настройки стерео эффектов по умолчанию.';

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

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

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

					} );

			}
			return _lang;

		}

		/**
		 * Adds StereoEffects folder into dat.GUI.
		 * @see {@link https://github.com/anhr/dat.gui|dat.gui}.
		 * @param {Object} [guiParams={}] the following params are available.
		 * @param {GUI} [guiParams.dat] [dat.GUI()]{@link https://github.com/dataarts/dat.gui}.
		 * @param {GUI} [guiParams.folder] <b>StereoEffects</b> folder. See {@link https://github.com/anhr/dat.gui|dat.gui} for details
		 * @param {Function} [guiParams.onChangeMode] The event is fired if user has changed the stereo mode.
		 * <pre>
		 * parameter is new stereo mode.
		 * See the <b>settings.spatialMultiplex</b> parameter of the <b>StereoEffect</b> class for details.
		 * <pre>
		 * @param {number} [guiParams.scale=1] scale of allowed values.
		 */
		this.gui = function ( guiParams = {} ) {

			const gui = guiParams.folder || options.dat.gui;
			if ( !gui || options.dat.stereoEffectsGui === false )
				return;
			const dat = guiParams.dat || three.dat;//options.dat.dat;
			if ( guiParams === undefined ) guiParams = {};
			guiParams.scale = guiParams.scale || 1;
			const stereoEffect = options.dat.getCookieName( 'StereoEffect' ),
				_lang = getLang( { getLanguageCode: options.getLanguageCode, lang: options.lang } );
			
			const optionsDefault = {

				spatialMultiplex: settings.spatialMultiplex,
				eyeSep: ( new THREE.StereoCamera().eyeSep / 10 ) * settings.far,
				focus: settings.focus,
				zeroParallax: 0,

			}
			Object.freeze( optionsDefault );
			options.dat.cookie.getObject( stereoEffect, settings, optionsDefault );
			settings.spatialMultiplex = parseInt( settings.spatialMultiplex );

			if ( this.setSpatialMultiplex ) this.setSpatialMultiplex( settings.spatialMultiplex );

			//

			function displayControllers( value ) {

				const display = value == spatialMultiplexsIndexs.Mono ? 'none' : 'block';
				_fEyeSeparation.domElement.style.display = display;
				if ( _controllerCameraFocus !== undefined )
					_controllerCameraFocus.__li.style.display = display;
				_controllerDefaultF.__li.style.display = display;
				_controllerZeroParallax.__li.style.display = display;

			}

			const _fStereoEffects = gui.addFolder( _lang.stereoEffects );//Stero effects folder

			//Spatial multiplex
			const _controllerSpatialMultiplex = _fStereoEffects.add( settings, 'spatialMultiplex',
				_lang.spatialMultiplexs ).onChange( function ( value ) {

					value = parseInt( value );
					displayControllers( value );
					setObject( stereoEffect );
					if ( guiParams.onChangeMode )
						guiParams.onChangeMode( value );
					if ( menuItemStereoEffect )
						menuItemStereoEffect.select( value )

				} );
			dat.controllerNameAndTitle( _controllerSpatialMultiplex, _lang.spatialMultiplexName, _lang.spatialMultiplexTitle );
			this.setControllerSpatialMultiplex = function ( index ) {

				saveToCookie = false;
				_controllerSpatialMultiplex.setValue( index );
				saveToCookie = true;

			}

			//eyeSeparation
			//http://paulbourke.net/papers/vsmm2007/stereoscopy_workshop.pdf
			const _fEyeSeparation = _fStereoEffects.addFolder( _lang.eyeSeparationName );//Eye Separation
			dat.folderNameAndTitle( _fEyeSeparation, _lang.eyeSeparationName, _lang.eyeSeparationTitle );
			_fEyeSeparation.add( new PositionController( function ( shift ) {

				settings.eyeSep += shift;
				_controllerEyeSep.setValue( settings.eyeSep );

			}, { settings: { offset: 0.01 }, min: 0.0001, max: 0.01, step: 0.0001 }
			) );
			const _controllerEyeSep = dat.controllerZeroStep( _fEyeSeparation, settings.stereo, 'eyeSep', function ( value ) {

				settings.eyeSep = value;
				setObject( stereoEffect );

			} );
			dat.controllerNameAndTitle( _controllerEyeSep, _lang.eyeSeparationName, _lang.eyeSeparationTitle );

			if ( settings.camera !== undefined ) settings.camera.focus = settings.focus;
			
			var _controllerCameraFocus;
			if ( settings.camera ) {

				_controllerCameraFocus = _fStereoEffects.add( settings.camera,
					'focus', optionsDefault.focus / 10, optionsDefault.focus * 2, optionsDefault.focus / 1000 )
					.onChange( function ( value ) {

						settings.focus = value;
						setObject( stereoEffect );

					} );
				dat.controllerNameAndTitle( _controllerCameraFocus, _lang.focus, _lang.focusTitle );

			}

			//Zero parallax
			//http://paulbourke.net/papers/vsmm2007/stereoscopy_workshop.pdf
			const _minMax = ( 60 - ( 400 / 9 ) ) * guiParams.scale + 400 / 9;
			const _controllerZeroParallax = _fStereoEffects.add( settings, 'zeroParallax', - _minMax, _minMax )
				.onChange( function ( value ) {

					settings.zeroParallax = value;
					setObject( stereoEffect );

				} );
			dat.controllerNameAndTitle( _controllerZeroParallax, _lang.zeroParallaxName, _lang.zeroParallaxTitle );

			//default button
			const _controllerDefaultF = _fStereoEffects.add( {
				defaultF: function ( value ) {

					settings.stereo.eyeSep = optionsDefault.eyeSep;
					_controllerEyeSep.setValue( settings.stereo.eyeSep );

					if ( settings.camera ) {

						settings.camera.focus = optionsDefault.focus;
						_controllerCameraFocus.setValue( settings.camera.focus );

					}

					settings.zeroParallax = optionsDefault.zeroParallax;
					_controllerZeroParallax.setValue( settings.zeroParallax );

				},

			}, 'defaultF' );
			dat.controllerNameAndTitle( _controllerDefaultF, _lang.defaultButton, _lang.defaultTitle );

			displayControllers( settings.spatialMultiplex );

			var saveToCookie = true;
			/**
			 * sets an object into cookie.
			 * @param {string} name cookie name.
			 */
			function setObject( name ) {

				if ( !saveToCookie )
					return;
				const object = {};
				Object.keys( optionsDefault ).forEach( function ( key ) {

					object[key] = settings[key];

				} );
				options.dat.cookie.setObject( name, object );

			};

		};

		var _canvasMenu, menuItemStereoEffect;
		/**
		 * Adds a StereoEffect's menu item into [CanvasMenu]{@link https://github.com/anhr/commonNodeJS/tree/master/canvasMenu}.
		 * @param {CanvasMenu} [canvasMenu] [CanvasMenu]{@link https://github.com/anhr/commonNodeJS/tree/master/canvasMenu}.
		 * @param {Object} [params] the following params are available.
		 * @param {Function} [params.getLanguageCode=language code of your browser] Your custom getLanguageCode() function.
		 * <pre>
		   * returns the "primary language" subtag of the language version of the browser.
		 * Examples: "en" - English language, "ru" Russian.
		 * See the {@link https://tools.ietf.org/html/rfc4646#section-2.1|Syntax} paragraph of RFC 4646 for details.
		 * </pre>
		 */
		this.createCanvasMenuItem = function ( canvasMenu, params ) {

			_canvasMenu = canvasMenu;
			params = params || {};
			const _lang = getLang( { getLanguageCode: params.getLanguageCode, lang: params.lang } ),
				spatialMultiplexs = Object.keys( _lang.spatialMultiplexs );
			menuItemStereoEffect = {

				name: '⚭',
				title: _lang.stereoEffects,
				id: 'menuButtonStereoEffects',
				drop: 'up',
				items: [

					{
						name: spatialMultiplexs[spatialMultiplexsIndexs.Mono],//'Mono',
						id: 'menuButtonStereoEffectsMono',
						radio: true,
						checked: settings.spatialMultiplex === spatialMultiplexsIndexs.Mono,
						spatialMultiplex: spatialMultiplexsIndexs.Mono,
						onclick: function ( event ) {

							settings.spatialMultiplex = spatialMultiplexsIndexs.Mono;

						}
					},
					{
						name: spatialMultiplexs[spatialMultiplexsIndexs.SbS],//'Side by side',
						id: 'menuButtonStereoEffectsSbS',
						radio: true,
						checked: settings.spatialMultiplex === spatialMultiplexsIndexs.SbS,
						spatialMultiplex: spatialMultiplexsIndexs.SbS,
						onclick: function ( event ) {

							settings.spatialMultiplex = spatialMultiplexsIndexs.SbS;

						}
					},
					{
						name: spatialMultiplexs[spatialMultiplexsIndexs.TaB],//'Top and bottom',
						id: 'menuButtonStereoEffectsTaB',
						radio: true,
						checked: settings.spatialMultiplex === spatialMultiplexsIndexs.TaB,
						spatialMultiplex: spatialMultiplexsIndexs.TaB,
						onclick: function ( event ) {

							settings.spatialMultiplex = spatialMultiplexsIndexs.TaB;

						}
					},

				],

			}
			menuItemStereoEffect.select = function ( value ) {

				menuItemStereoEffect.items.forEach( function ( item ) {

					if ( item.spatialMultiplex === value ) {

						if ( !item.checked )
							item.elName.onclick( { target: item.elName } );

					}

				} );

			}
			this.setSpatialMultiplex = function ( index ) {

				menuItemStereoEffect.select( index );

			}
			canvasMenu.menu.push( menuItemStereoEffect );

		}

	}

};
/**
 * Enumeration of available stereo modes. Available as <b>StereoEffect.spatialMultiplexsIndexs</b>.
 * @see {@link https://en.wikipedia.org/wiki/DVB_3D-TV|DVB 3D-TV} for details
 * @readonly
 * @enum {number}
 */
StereoEffect.spatialMultiplexsIndexs = {
	/** No stereo effect */
	Mono: 0,
	/** {@link https://en.wikipedia.org/wiki/DVB_3D-TV#Side_by_side|Side by side} */
	SbS: 1, //
	/** {@link https://en.wikipedia.org/wiki/DVB_3D-TV#Top_and_bottom|Top and bottom} */
	TaB: 2, //
}
Object.freeze( StereoEffect.spatialMultiplexsIndexs );
const spatialMultiplexsIndexs = StereoEffect.spatialMultiplexsIndexs;

/* * @namespace
 * @description Assigh setStereoEffect to [THREE.Raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster}
 */
function assign() {

	const THREE = three.THREE;
	if ( new THREE.Raycaster().setStereoEffect )
		return;
		
	//Modifying of THREE.Raycaster for StereoEffect
	Object.assign( THREE.Raycaster.prototype, {

		/**
		 * @namespace
		 * @description Sets the <b>StereoEffect</b> settings to the {@link https://threejs.org/docs/#api/en/core/Raycaster|THREE.Raycaster}.
		 * Available as <b>THREE.Raycaster.setStereoEffect(...)</b>.
		 * @param {Object} [settings={}]
		 * @param {Options} [settings.options] See <a href="../../jsdoc/Options/Options.html" target="_blank">Options</a>.
		 * @param {THREE.PerspectiveCamera} settings.camera {@link https://threejs.org/docs/index.html#api/en/cameras/PerspectiveCamera|PerspectiveCamera}
		 * @param {THREE.Scene} [settings.scene] [Scene]{@link https://threejs.org/docs/index.html?q=Scene#api/en/scenes/Scene}
		 * @param {StereoEffect} [settings.stereoEffect=no stereo effects] stereoEffect.
		 * @param {THREE.WebGLRenderer} [settings.renderer=renderer parameter of THREE.StereoEffect] renderer. The {@link https://threejs.org/docs/#api/en/renderers/WebGLRenderer|WebGL renderer} displays your beautifully crafted scenes using WebGL.
		 * @param {boolean} [settings.raycasterEvents=true] true - add raycaster events: mousemove and pointerdown.
		 */
		setStereoEffect: function ( settings = {} ) {

			if ( settings.stereoEffect === false ) return;
			settings.raycasterEvents = settings.raycasterEvents === undefined ? true : settings.raycasterEvents;
			const camera = settings.camera, renderer = settings.renderer;

			if ( settings.raycasterEvents ){

				const mouse = new THREE.Vector2();
				window.addEventListener( 'mousemove', function ( event ) {

					// calculate mouse position in normalized device coordinates
					// (-1 to +1) for both components

					mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
					mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

					// update the picking ray with the camera and mouse position
					raycaster.setFromCamera( mouse, camera );

					raycaster.stereo.onDocumentMouseMove( event );

				}, false );
				window.addEventListener( 'pointerdown', function ( event ) {

					raycaster.stereo.onDocumentMouseDown( event );

				}, false );

			}

			const stereoEffect = settings.stereoEffect !== undefined ? settings.stereoEffect : typeof effect !== 'undefined' ? effect :
				new StereoEffect( renderer, settings.options ),
				raycaster = this,
				mouseL = new THREE.Vector2(),
				mouseR = new THREE.Vector2();
			var particles, //The object or array of objects to check for intersection with the ray. See THREE.Raycaster.intersectObject https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObject for details.
//				intersects, //An array of intersections is returned by THREE.Raycaster.intersectObject or THREE.Raycaster.intersectObjects.
				mouse; //Attention!!! Do not assign new THREE.Vector2() here
			//for prevention of invalid detection of intersection with zero point ( THREE.Vector3( 0, 0, 0 ) )
			//after opening of the web page and before user has moved mouse.

			function getMousePosition() {

				stereoEffect.getRendererSize().getMousePosition( mouse, event );

				function mousePosition( vectorName, b ) {

					mouseL.copy( mouse );
					mouseR.copy( mouse );
					const a = 0.5;

					mouseL[vectorName] += a;
					mouseL[vectorName] *= 2;

					mouseR[vectorName] -= a;
					mouseR[vectorName] *= 2;

					//zeroParallax
					const size = new THREE.Vector2();
					renderer.getSize( size );
					const zeroParallax = ( stereoEffect.settings.zeroParallax / size.x ) * b;
					mouseL.x -= zeroParallax;
					mouseR.x += zeroParallax;

				}
				switch ( parseInt( stereoEffect.settings.spatialMultiplex ) ) {

					case spatialMultiplexsIndexs.Mono:
						return;
					case spatialMultiplexsIndexs.SbS:
						mousePosition( 'x', 4 );
						break;
					case spatialMultiplexsIndexs.TaB:
						mousePosition( 'y', 2 );
						break;
					default: console.error( 'THREE.Raycaster.setStereoEffect.getMousePosition: Invalid effect.settings.spatialMultiplex = ' + effect.settings.spatialMultiplex );
						return;

				}

			}
			function intersection( optionsIntersection ) {

				if ( mouse === undefined )
					return;//User has not moved mouse

				optionsIntersection = optionsIntersection || settings;
				function isIntersection() {

//					intersects = Options.raycaster.intersectionsInOut( particles, raycaster, renderer, mouse, settings );
					Options.raycaster.intersectionsInOut( particles, raycaster, renderer, mouse, settings );

				}
				if ( parseInt( stereoEffect.settings.spatialMultiplex ) !== spatialMultiplexsIndexs.Mono ) {

					const mouseCur = mouse;
					mouse = mouseL;
					raycaster.setFromCamera( mouseL, camera );
					if ( !isIntersection() ) {

						mouse = mouseR;
						raycaster.setFromCamera( mouseR, camera );
						isIntersection();

					}
					mouse = mouseCur;
					return;

				}
				raycaster.setFromCamera( mouse, camera );
				isIntersection();

			}

			/**
			 * @namespace
			 * @description
			 * <pre>
			 * [Raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster} helper.
			 * Available as <b>raycaster.stereo</b>.
			 * <b>raycaster</b> is [Raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster} instance.
			 * </pre>
			 * */
			this.stereo = {

				/**
				 * [mousemove]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/mousemove_event} event.
				 * User is moving of mouse over canvas.
				 * @param {object} event
				 */
				onDocumentMouseMove: function ( event ) {

					if ( particles === undefined )
						return;//The object or array of objects to check for intersection with the ray is not defined. See THREE.Raycaster.intersectObject https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObject for details.
					event.preventDefault();
					if ( mouse === undefined )
						mouse = new THREE.Vector2();
					getMousePosition();
					intersection();

				},
				/**
				 * <pre>
				 * Available as <b>raycaster.stereo.isAddedToParticles( particle )</b>.
				 * <b>raycaster</b> is [Raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster} instance.
				 * </pre>
				 * @param {THREE.Mesh} particle The [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh} or derived class instance for check is added to <b>particles</b>.
				 * @returns true if <b>particle</b> is added to <b>particles</b>.
				 */
				isAddedToParticles: function ( particle ) {

					if ( !particles )
						return false;
					return particles.includes( particle );

				},
				/**
				 * <pre>
				 * Adds new particle into array of objects to check for intersection with the ray.
				 * Available as <b>raycaster.stereo.addParticle( particle )</b>.
				 * <b>raycaster</b> is [Raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster} instance.
				 * </pre>
				 * @see <b>objects</b> parameter of the [Raycaster.intersectObjects]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObjects} for details.
				 * @param {THREE.Mesh} particle The [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh} or derived class instance for check for intersection with the ray.
				 */
				addParticle: function ( particle ) {

					if ( particles === undefined )
						particles = [];
					if ( this.isAddedToParticles( particle ) ) {

						console.error( 'Duplicate particle "' + particle.name + '"' );
						return;

					}
					particles.push( particle );

				},
				/**
				 * <pre>
				 * New array of objects to check for intersection with the ray.
				 * Available as <b>raycaster.stereo.addParticles( particles )</b>.
				 * <b>raycaster</b> is [Raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster} instance.
				 * </pre>
				 * @see <b>objects</b> parameter of the [Raycaster.intersectObjects]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObjects} for details.
				 * @param {array} newParticles New array of objects to check for intersection with the ray.
				 */
				addParticles: function ( newParticles ) {

					if ( particles !== undefined ) {

						if ( !Array.isArray( particles ) ) {

							var particlesCur = particles;
							particles = [];
							particles.push( particlesCur );

						}
						particles.push( newParticles );
						return;

					}
					particles = newParticles;

				},
				/**
				 * <pre>
				 * Remove particle from array of objects to check for intersection with the ray.
				 * Available as <b>raycaster.stereo.removeParticle( particle )</b>.
				 * <b>raycaster</b> is [Raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster} instance.
				 * </pre>
				 * @see <b>objects</b> parameter of the [Raycaster.intersectObjects]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObjects} for details.
				 * @param {THREE.Mesh} particle The [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh} or derived class instance for removing from array of objects for check for intersection with the ray.
				 */
				removeParticle: function ( particle ) {

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

						if ( Object.is( particle, particles[i] ) ) {

							particles.splice( i, 1 );
							break;

						}

					}

				},
				/**
				 * <pre>
				 * remove array of objects to check for intersection with the ray.
				 * Available as <b>raycaster.stereo.removeParticles()</b>.
				 * <b>raycaster</b> is [Raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster} instance.
				 * </pre>
				 * @see <b>objects</b> parameter of the [Raycaster.intersectObjects]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObjects} for details.
				 */
				removeParticles: function () { particles = undefined; },
				/**
				 * <pre>
				 * get position of intersected object
				 * Available as <b>raycaster.stereo.getPosition( intersection )</b>.
				 * <b>raycaster</b> is [Raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster} instance.
				 * </pre>
				 * @param {object} intersection first item of array of intersections.
				 * @See [Raycaster.intersectObject]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObject} for details
				 */
				getPosition: function ( intersection ) {

					const attributesPosition = intersection.object.geometry.attributes.position;

					//Если опредедить как const, то при выполнении npm run build появится ошибка
					//(babel plugin) SyntaxError: D:/My documents/MyProjects/webgl/three.js/GitHub/commonNodeJS/master/StereoEffect/StereoEffect.js: "position" is read-only
					var position = attributesPosition.itemSize >= 4 ? new THREE.Vector4( 0, 0, 0, 0 ) : new THREE.Vector3();

					if ( intersection.index !== undefined ) {

						position.fromArray( attributesPosition.array, intersection.index * attributesPosition.itemSize );

						position.multiply( intersection.object.scale );

						position.add( intersection.object.position );

					} else position = intersection.object.position;
					return position;

				}

			};

		}

	} );

}
StereoEffect.assign = assign;

//Localization

import { getLanguageCode } from '../lang.js';
//import { getLanguageCode } from 'https://raw.githack.com/anhr/commonNodeJS/master/lang.js';

const lang = {

	mesh: 'Mesh',
	pointName: 'Point Name',
	color: 'Сolor',
	opacity: 'Opacity',

};

switch ( getLanguageCode() ) {

	case 'ru'://Russian language
		lang.mesh = '3D объект';
		lang.pointName = 'Имя точки';
		lang.color = 'Цвет';
		lang.opacity = 'Непрозрачность';
		break;

}

import { SpriteText } from '../SpriteText/SpriteText.js';
//import { SpriteText } from 'https://raw.githack.com/anhr/commonNodeJS/master/SpriteText/SpriteText.js';

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

/** @namespace
 * @description Creates the <a href="../../SpriteText/jsdoc" target="_blank">SpriteText</a> instance with information about point, intersected with mouse cursor.
 * @param {THREE.Raycaster.intersectObject} intersection See [intersection]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObject} for details.
 * @param {object} options The following options are available
 * @param {object} [options.scales] axes scales.
 * See <b>options.scales</b> parameter of the <a href="../../AxesHelper/jsdoc/module-AxesHelper-AxesHelper.html" target="_blank">AxesHelper</a> class for details.
 * @param {object} options.spriteOptions Options of the <b>SpriteText</b>.
 * See [SpriteText]{@link https://raw.githack.com/anhr/commonNodeJS/master/SpriteText/jsdoc/module-SpriteText..html} for details.
 * @returns <b>new SpriteText</b> with information about point, intersected with mouse cursor.
 */
StereoEffect.getTextIntersection = function ( intersection, options ) {

	const spriteText = Options.findSpriteTextIntersection( options.spriteOptions.group );
	if ( spriteText )
		return spriteText;
		
	const THREE = three.THREE;
	const position = getObjectPosition( intersection.object, intersection.index ),
		scales = options.scales || {},
		isArrayFuncs = (
			( intersection.index !== undefined ) &&
			( intersection.object.userData.player !== undefined ) &&
			( intersection.object.userData.player.arrayFuncs !== undefined ) 
		),
		funcs = !isArrayFuncs ? undefined : intersection.object.userData.player.arrayFuncs,
		func = ( funcs === undefined ) || ( typeof funcs === "function" ) ? undefined : funcs[intersection.index],
		pointName = isArrayFuncs && func ? func.name : undefined,
		color = !isArrayFuncs || ( func === undefined ) ?
			undefined :
			Array.isArray( func.w ) ?
				Player.execFunc( func, 'w', group.userData.t, options ) ://.a, options.b ) :
				func.w;

	if ( intersection.object.userData.onIntersection ) intersection.object.userData.onIntersection();
	const boXYZ = !scales.x &&  !scales.y &&  !scales.z;
	options.spriteOptions.name = Options.findSpriteTextIntersection.spriteTextIntersectionName;
	options.spriteOptions.name = Options.findSpriteTextIntersection.spriteTextIntersectionName;
	return new SpriteText(

		//text
		lang.mesh + ': ' + ( intersection.object.name === '' ? intersection.object.type : intersection.object.name ) +
		( pointName === undefined ? '' : '\n'+ lang.pointName + ': ' + pointName ) +
		( ( !boXYZ && !scales.x ) || ( scales.x && !scales.x.isAxis() ) ? '' :
			'\n' + ( ( scales.x && scales.x.name ) || ( scales.x.name === 0 ) ? scales.x.name : 'X' ) + ': ' + position.x ) +
		( ( !boXYZ && !scales.y ) || ( scales.y && !scales.y.isAxis() ) ? '' :
			'\n' + ( ( scales.y && scales.y.name ) || ( scales.y.name === 0 ) ? scales.y.name : 'Y' ) + ': ' + position.y ) +
		( ( !boXYZ && !scales.z ) || ( scales.z && !scales.z.isAxis() ) ? '' :
			'\n' + ( ( scales.z && scales.z.name ) || ( scales.z.name === 0 ) ? scales.z.name : 'Z' ) + ': ' + position.z ) + 
		(//w
			!isArrayFuncs ?
				'' :
				funcs[intersection.index] instanceof THREE.Vector4 ||
					funcs[intersection.index] instanceof THREE.Vector3 ||
					typeof funcs === "function" ?
					color instanceof THREE.Color ?
						'\n' + lang.color + ': ' + new THREE.Color( color.r, color.g, color.b ).getHexString() :
						position.w !== undefined ? '\n' + ( scales.w && scales.w.name ? scales.w.name : 'W' ) + ': ' + position.w : '' :
					''

		) +
		(//opacity
			( intersection.object.geometry.attributes.ca === undefined ) ||
				( intersection.object.geometry.attributes.ca.itemSize < 4 ) ?
				'' :
				'\n' + lang.opacity + ': ' + new THREE.Vector4().fromArray(

					intersection.object.geometry.attributes.ca.array,
					intersection.index * intersection.object.geometry.attributes.ca.itemSize

				).w
		) +
		(//Custom text
			intersection.object.userData.raycaster && intersection.object.userData.raycaster.text ? intersection.object.userData.raycaster.text( intersection/*, intersection.object.userData.raycaster.points*/ ) : ''
		),
		intersection.pointSpriteText ? intersection.pointSpriteText : intersection.point,//position,
		options.spriteOptions
	);

}

export default StereoEffect;