Source: universe.js

/**
 * @module Universe
 * @description Base class for n dimensional hypersphere universe.
 *
 * @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
*/

//Получаю ошибку
//[!] (cleanup plugin) SyntaxError: Unexpected token (88:9)
//..\..\..\three.js\dev\src\Three.js(88: 9)
//При выполнении "npm run build"
//import * as THREE from '../../three.js/dev/src/Three.js';
//////////////////////////////////////////

import * as THREE from '../../three.js/dev/build/three.module.js';
//import * as THREE from 'https://threejs.org/build/three.module.js';
//import * as THREE from 'https://raw.githack.com/anhr/three.js/dev/build/three.module.js';

import MyThree from '../../commonNodeJS/master/myThree/myThree.js';
//import MyThree from '../../commonNodeJS/master/myThree/build/myThree.module.js';
//import MyThree from '../../commonNodeJS/master/myThree/build/myThree.module.min.js';
//import MyThree from 'https://raw.githack.com/anhr/commonNodeJS/master/myThree/myThree.js';
//import MyThree from 'https://raw.githack.com/anhr/commonNodeJS/master/myThree/build/myThree.module.js';
//import MyThree from 'https://raw.githack.com/anhr/commonNodeJS/master/myThree/build/myThree.module.min.js';
MyThree.three.THREE = THREE;

import { dat } from '../../commonNodeJS/master/dat/dat.module.js';
import ND from '../../commonNodeJS/master/nD/nD.js';
//import * as utils from '../../commonNodeJS/master/HyperSphere/utilsHSphere.js'
let utils;

const sUniverse = 'Universe';

class Universe
{
	/**
	 * Base class for n dimensional hypersphere universe.
	 * @param {object} [classSettings={}] See <a href="../../../commonNodeJS/master/HyperSphere/jsdoc/module-HyperSphere-HyperSphere.html" target="_blank">HyperSphere classSettings</a> parameter.
	 * @param {Array} [classSettings.settings.object.geometry.timesAngles] Array of vertices angles for different time of the <a href="../../../commonNodeJS/master/player/jsdoc/" target="_blank">player</a>.
	 * Every item of the <b>timeAngles</b> array is array of the vertices angles. See <b>classSettings.settings.object.geometry.angles</b> of the <a href="../../../commonNodeJS/master/HyperSphere/jsdoc/module-HyperSphere-HyperSphere.html" target="_blank">HyperSphere</a> for details
	 * @param {object} [myThreeOptions={}] See <a href="../../../commonNodeJS/master/myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree options</a> parameter.
	 */
	constructor(classSettings = {}, myThreeOptions = {}) {

		utils = classSettings.utils;
		const _this = this;
		//myThreeOptions.playerOptions ||= {};Not compatible with buid of hyperSphericalUniverse.module.min.js
		if (!myThreeOptions.playerOptions) myThreeOptions.playerOptions = {};
		myThreeOptions.playerOptions.interval = myThreeOptions.playerOptions.interval != undefined ? myThreeOptions.playerOptions.interval : Infinity;
		myThreeOptions.scales = myThreeOptions.scales || {};
		myThreeOptions.scales.x = myThreeOptions.scales.x || {};
		myThreeOptions.scales.y = myThreeOptions.scales.y || {};
		myThreeOptions.scales.w = myThreeOptions.scales.w || {};
		myThreeOptions.scales.text = myThreeOptions.scales.text || {};
		myThreeOptions.scales.text.precision = myThreeOptions.scales.text.precision != undefined ? myThreeOptions.scales.text.precision : 1;
		myThreeOptions.scales.text.rect = myThreeOptions.scales.text.rect || {};
		myThreeOptions.scales.text.rect.displayRect = myThreeOptions.scales.text.rect.displayRect != undefined ? myThreeOptions.scales.text.rect.displayRect : false;

		myThreeOptions.playerOptions = myThreeOptions.playerOptions || {};
		if (myThreeOptions.playerOptions.min === undefined) myThreeOptions.playerOptions.min = 0.1;

		//w
		myThreeOptions.scales.w = new Proxy(myThreeOptions.scales.w, {

			get: (rRange, name) => {
				
				switch (name) {

					//цвета вершин зависит от текущего времени в проигрывателе
					case 'min': return rRange.min != undefined ? rRange.min : myThreeOptions.playerOptions.min;
					case 'max': return rRange.max != undefined ? rRange.max : myThreeOptions.playerOptions.max;
					case 'isColor': return false;

				}
				return rRange[name];
				
			},

		});
		classSettings.rRange = classSettings.rRange || myThreeOptions.scales.w;
		myThreeOptions.orbitControls = myThreeOptions.orbitControls || {};
		myThreeOptions.orbitControls.enableRotate = myThreeOptions.orbitControls.enableRotate != undefined ? myThreeOptions.orbitControls.enableRotate : false;

		myThreeOptions.camera = myThreeOptions.camera || {};
		if (myThreeOptions.camera.position) {

			if (myThreeOptions.camera.position instanceof Array === true)
				myThreeOptions.camera.position = new THREE.Vector3(myThreeOptions.camera.position[0], myThreeOptions.camera.position[1], myThreeOptions.camera.position[2]);
			
		}
		else myThreeOptions.camera.position = new THREE.Vector3(0, 0, 2);

		myThreeOptions.canvas = myThreeOptions.canvas || {};
		myThreeOptions.canvas.noButtonFullScreen = myThreeOptions.canvas.noButtonFullScreen != undefined ? myThreeOptions.canvas.noButtonFullScreen : true;

		if (myThreeOptions.palette === undefined) myThreeOptions.palette = MyThree.ColorPicker.paletteIndexes.rainbow;
		
		new MyThree((scene, options) => {

			let traces, traceVerticeId;

			//classSettings.projectParams ||= {};//Эта строка выдает ошибку "[!] (cleanup plugin) SyntaxError: Unexpected token (63:36)" при выполнении команды "npm run build"
			classSettings.projectParams = classSettings.projectParams || {};
			classSettings.projectParams.scene = scene;

			classSettings.settings = classSettings.settings || {};
			classSettings.settings.object = classSettings.settings.object || {};
			classSettings.settings.object.name = classSettings.settings.object.name || this.name(options.getLanguageCode);

			if (classSettings.settings.object.color === undefined) classSettings.settings.object.color = (timeId) => { return options.player.getTime(timeId); }

			classSettings.projectParams.scene.userData.endSelect = () => {}

			classSettings.settings.isSetPosition = true;//при выполнении шага в Player не надо вычислять позицию вершин в самом Player
			
			if (classSettings.settings.object.geometry.opacity === undefined) classSettings.settings.object.geometry.opacity = 1;//Непрозрачность нужна для выделения трека одной вершины
			
			classSettings.settings.object.geometry.rCount = options.playerOptions.marks;//количество возможных радиусов вселенной
			{//hide times

				const times = classSettings.settings.object.geometry.times;
				if (times) {
					
					const timesLength = times.length;
					if (timesLength > 0 ) classSettings.settings.object.geometry.timesLength = timesLength;

				}

			}

			{//hide geometryAngles

				const geometryAngles = classSettings.settings.object.geometry.angles;
				classSettings.settings.object.geometry.times = new Proxy(classSettings.settings.object.geometry.times || [], {

					get: (times, name) => {

						const timeId = parseInt(name);
						if (!isNaN(timeId)) {

							if (timeId === 0) return times[timeId] || geometryAngles;
							if (!times[timeId]) {

								if (times.length != timeId) console.error(sUniverse + ': get times[' + timeId + '] failed. Invalid timeId = ' + timeId);
								let verticesCount = 0;
								times[timeId] = new Proxy([], {

									get: (timeAngles, name) => {

										const verticeId = parseInt(name);
//const v = classSettings.settings.object.geometry.angles[0];
/*										
const p = classSettings.settings.object.geometry.position[0];
const x = p.x;
console.error('Under constraction')
*/
										if (!isNaN(verticeId)) {
											const positionData = this.hyperSphere.getPositionData(verticeId, timeId);
											const position = classSettings.settings.bufferGeometry.attributes.position;
											const index = positionData.positionId / position.itemSize;
											let verticePosition;
											switch (position.itemSize) {
												case 4:
													verticePosition = new THREE.Vector4().fromBufferAttribute(position, index);
													break;
												case 3:
													verticePosition = new THREE.Vector3().fromBufferAttribute(position, index);
													break;
												default: console.error(sUniverse + ': get angles. Invalid position.itemSize = ' + position.itemSize);
											}
											return new Proxy(utils.cartesianToPolar(verticePosition)/*timeAngles[verticeId]*/, {

												get: (verticeAngles, name) => {

													switch (name) {

														case 'edges': return times[0][verticeId].edges;
															
													}
													return verticeAngles[name];
													
												}
												
											});
										}
										switch (name) {

											case 'player': return this.hyperSphere.anglesPlayer(timeId);
											case 'length': return verticesCount;

										}
										return timeAngles[name];

									},
									set: (timeAngles, name, value) => {

//										timeAngles[name] = value;
										const timeAnglesId = parseInt(name);
										if (!isNaN(timeAnglesId)) {
											verticesCount++;
											this.hyperSphere.setPositionAttributeFromPoint(timeAnglesId, utils.polarToCartesian(value, this.hyperSphere.r), timeId);
										} else switch(name) {
/*												
											case 'increaseVerticesCount':
												verticesCount += value;
												break;
*/												
											case 'color':
												this.hyperSphere.setColorAttributeFromPoint(verticesCount, value, timeId);
												verticesCount++;
												break;
										}
										return true;

									},

								});

							}
							return times[timeId];

						}
						switch (name) {

							case 'length': if (times.length === 0) return 1;
								break;

						}
						return times[name];

					},
					set: (times, name, value) => {

						const timeId = parseInt(name);
						if (!isNaN(timeId)) {

							const timeAngles0 = times[0];
							times[timeId] = new Proxy(value, {

								get: (timeAngles, name) => {

									switch (name) {

										case 'isTimeAnglesProxy': return true;
										case 'player':
											if (!this.hyperSphere) {

												const t = classSettings.settings.options.player.getTime(timeId);
												return {

													id: timeId,
													t: t,
													r: t * classSettings.r,

												}

											};
											return this.hyperSphere.anglesPlayer(timeId);
										case 'forEach': return (item) => {

											for (let verticeId = 0; verticeId < timeAngles0.length; verticeId++) {

												const verticeAngles = timeAngles[verticeId];
												item(verticeAngles ? verticeAngles : timeAngles0[verticeId], verticeId);

											}

										}
										case 'length':
											if (timeId > 0) return timeAngles0.length;
											return timeAngles.length != undefined ? timeAngles.length : 0;

									}
									const verticeId = parseInt(name);
									if (!isNaN(verticeId)) {

										let verticeAngles = timeAngles[verticeId];
										if (!verticeAngles || ((timeId > 0) && (verticeAngles[verticeId] === undefined)))
										{

											if (timeId === 0) {

												console.error(sUniverse + ': get vertice angles failed. Invalid timeId = ' + timeId);
												return;

											}
											//похоже если вершины для данного timeId нет, то берется вершина из предыдущего timeId
											verticeAngles = times[timeId - 1][verticeId];
										}
										return new Proxy(verticeAngles, {

											get: (verticeAngles, name) => {

												const angleId = parseInt(name);
												if (!isNaN(angleId)) {

													const angle = verticeAngles[angleId];
													if (angle === undefined) {

														if (timeId === 0) return 0;
														else return times[timeId - 1][verticeId][angleId];
														
													}
													return angle;
													
												}
												switch (name) {

													case 'length': 
														const maxAngles = classSettings.dimension - 1;
														if (classSettings.debug && (verticeAngles.length > maxAngles)) console.error(sUniverse + ': Invalid classSettings.settings.object.geometry.times[' + timeId + '][' + verticeId + '].length = ' + verticeAngles.length + '. Every vertice is limited to ' + maxAngles + ' angles.');
														return maxAngles;
														
												}
												return verticeAngles[name];
												
											},
											
										});

									}
									return timeAngles[name];

								},

							});

						}
						else times[name] = value;
						return true;

					},
					
				});

			}
			classSettings.settings.object.geometry = new Proxy(classSettings.settings.object.geometry, {

				get: (geometry, name) => {

					switch (name) {

						case 'angles': 
							const times = geometry.times;
							if (times.length === 0) return;
							else if (times.length === 1) return times[0];
							const timeId = classSettings.settings.guiPoints.timeId, angles0 = times[0];
							let angles = times[timeId === undefined ? 0 : timeId];
							
							if (!(angles instanceof Array)) {
								
								const array = [];
								Object.keys(angles).forEach((key) => array[key] = angles[key]);
								angles = array;

							}
							
							return new Proxy(angles, {
								
								get: (angles, name) => {

									switch (name) {

										case 'ranges': return angles0[name];
											
									}
									return angles[name];
									
								},
								set: (angles, name, value) => {

									switch (name) {

										case 'ranges': angles0[name] = value; break;
										default: angles[name] = value;
											
									}
									
									return true;
									
								},
								
							});
						case 'position': 
							if (!geometry.playerPosition) {
								
								geometry.playerPosition = new Proxy([], {
	
									get: (playerPosition, name) => {
	
										const timeId = parseInt(name);
										if (!isNaN(timeId)) {
	
											const playerPositionItem0 = playerPosition[0];
											if ((timeId != 0) && !playerPosition[timeId]) return new Proxy([], {
	
												get: (playerPositionItem, name) => {
	
													switch (name) {
	
														case 'length': {

															const length = playerPositionItem0.length;
															if (classSettings.debug && (length != classSettings.settings.object.geometry.angles.length))
																console.error(sUniverse + ': get player position item failed! Invalid length = ' + length);
															return length;
	
														}
														case 'angles': return classSettings.settings.object.geometry.times[timeId];
															
													}
													const playerIndexItemId = parseInt(name);
													if (!isNaN(playerIndexItemId)) {
	
														const userData = classSettings.settings.bufferGeometry.userData, playerIndexOld = userData.timeId;
														userData.timeId = userData.selectedTimeId === undefined ? timeId : userData.selectedTimeId;
														const vertice = userData.position[playerIndexItemId];
														userData.timeId = playerIndexOld;
														vertice.timeId = timeId;
														return vertice;
														
													}
													return playerPositionItem[name];
													
												},
												
											});
											return playerPosition[timeId];
					
										}
										return playerPosition[name];
										
									},
									
								});

								//иммитация наследования классов
								//classSettings.overriddenProperties ||= {};Is not compatible with build of hyperSphericalUniverse.module.min.js
								if (!classSettings.overriddenProperties) classSettings.overriddenProperties = {};
								const overriddenProperties = classSettings.overriddenProperties, settings = classSettings.settings;
								//overriddenProperties.oppositeVertice ||= (oppositeAngleId, timeId) => { return geometry.playerPosition[timeId - 1][oppositeAngleId]; }Is not compatible with build of hyperSphericalUniverse.module.min.js
								if (!overriddenProperties.oppositeVertice) overriddenProperties.oppositeVertice = (oppositeAngleId, timeId) => { return geometry.playerPosition[timeId - 1][oppositeAngleId]; }
								if (!overriddenProperties.position) Object.defineProperty(overriddenProperties, 'position', { get: () => { return geometry.playerPosition[settings.bufferGeometry.userData.timeId]; }, });
								if (!overriddenProperties.position0) Object.defineProperty(overriddenProperties, 'position0', { get: () => { return geometry.playerPosition[0]; }, });
								if (!overriddenProperties.updateVertices) overriddenProperties.updateVertices = (vertices) => {

									if (traces) traces.bufferGeometry.userData.setDrawRange(classSettings.settings.options.player.getTimeId());
									this.hyperSphere.bufferGeometry.attributes.position.needsUpdate = true;
								
								}
								if (!overriddenProperties.vertices) overriddenProperties.vertices = () => {}
								if (!overriddenProperties.r) overriddenProperties.r = (timeId) => { return settings.object.geometry.times[timeId != undefined ? timeId : 0].player.r; }
								if (!overriddenProperties.pushMiddleVertice) overriddenProperties.pushMiddleVertice = (timeId, middleVertice) => {
									
									geometry.times[timeId].push(middleVertice);
									return true;
							
								}
								if (!overriddenProperties.angles) overriddenProperties.angles = (anglesId, timeId = 0) => { return settings.object.geometry.times[timeId][anglesId]; }
								if (!overriddenProperties.verticeAngles) overriddenProperties.verticeAngles = (anglesCur, verticeId) => {

									const guiPoints = settings.guiPoints;
									return guiPoints.timeAngles[guiPoints.verticeId != undefined ? guiPoints.verticeId : verticeId];
									
								}
								if (!overriddenProperties.verticeText) overriddenProperties.verticeText = (intersection, text) => {
									
									const times = settings.object.geometry.times;
									let verticeId = intersection.index, tRes = '';
									if (classSettings.edges.project) {

										//Localization
							
										const getLanguageCode = settings.options.getLanguageCode;
							
										const lang = {
							
											pointId: "point Id",
											edgeId: "edge Id",
							
										};
							
										const _languageCode = getLanguageCode();
							
										switch (_languageCode) {
							
											case 'ru'://Russian language
							
												lang.pointId = 'Индекс вершины';
												lang.edgeId = 'Индекс ребра';
												break;
							
										}

										verticeId = this.hyperSphere.searchNearestEdgeVerticeId(verticeId, intersection);

										//find edge Id
										let edgeId = intersection.index;
										const timeEdgesCount = settings.object.geometry.indices.edges.timeEdgesCount * 2;//в intersection.object.geometry.attributes.position на каждое ребро приходится по два индекса. Поэтому приходится умножать на 2
										for (let i = 0; i < timeEdgesCount; i++) {

											if ((edgeId - timeEdgesCount) < 0) break;
											edgeId -= timeEdgesCount;
											
										}
										edgeId = edgeId / 2;
										tRes = lang.edgeId + ' ' + edgeId;
										
									}
									let index = 0;
									for (let i = 0; i < times.length; i++) {

										const timeAngles = times[i];
										index += timeAngles.length;
										if (index > verticeId) {

											index = verticeId - index + timeAngles.length;
											return (tRes === '' ? '' : '\n' + tRes) + text(timeAngles, index);
											
										}
										
									}
									
								}
								if (!overriddenProperties.text) overriddenProperties.text = (tab, timeAngles, lang) => {
									
									return '\n' + tab + 'time Id: ' + timeAngles.player.id
										+ '\n' + tab + 't: ' + timeAngles.player.t
										+ '\n' + tab + lang.radius + ': ' + timeAngles.player.r;
								
								}
								if (!overriddenProperties.onSelectSceneEndSetDrawRange) overriddenProperties.onSelectSceneEndSetDrawRange = (timeId) => {

									const appendTimesChild = classSettings.settings.guiPoints.appendTimesChild;
									if (appendTimesChild) appendTimesChild(undefined, timeId);
									
									if ((classSettings.edges === false) || (classSettings.edges.project === false)) return;//Ребра не отбражаются на холсте. Не нужно устанавливать bufferGeometry.drawRange в зависимость от индекса ребер.
									this.hyperSphere.setEdgesRange();
									
								}
								overriddenProperties.isEdgesOnly = () => {


									//Localization

									const lang = {

										isEdgesOnly: 'Only vertices with edges are available in the universe.',

									};

									switch (classSettings.settings.options.getLanguageCode()) {

										case 'ru'://Russian language
											lang.isEdgesOnly = 'Во вселенной доступны только вершины с ребрами.';
											break;

									}

									alert(lang.isEdgesOnly)
									return true;

								}
								if (!overriddenProperties.project) overriddenProperties.project = () => {

									const scene = classSettings.projectParams.scene;
									if (!classSettings.boTraces) {

										if (traces) {
											
											scene.remove(traces.object3D);
											traces = undefined;

										}
										return;

									}

									class Traces extends ND {

										constructor() {

											const settings = classSettings.settings;
											const angles = settings.object.geometry.angles, timeVerticesLength = angles.length, lines = [], playerMarks = settings.options.playerOptions.marks;
											let verticeId = timeVerticesLength, timeIndexCount;
											for (let timeId = 1; timeId < playerMarks; timeId++) {
												
												angles.forEach((angle, angleId) => {

													if ((traceVerticeId === undefined) || (traceVerticeId === angleId)) {
														
														const line = [verticeId - timeVerticesLength, verticeId];
														lines.push(line);

													}
													verticeId++;
													
												});
												if (timeIndexCount === undefined) timeIndexCount = lines.length * 2;
												
											}
											super(_this.hyperSphere.dimension, {
										
												options: settings.options,
												bufferAttributes: settings.bufferGeometry.attributes,
												scene: scene,
												options: settings.options,
												isRaycaster: false,
												object: {

													name: 'Traces',
													geometry: {

														position: settings.object.geometry.position,
														//indices: [[ [0,1], [1,2], [2,3], [3,0], ]],//Debug. Edges
														indices: [lines],
														opacity: 1,//задать этот параметр, чтобы можно было делать треки прозрачными
											
													}
												},
												overriddenProperties: { setTracesIndices: (bufferGeometry) => {

													bufferGeometry.userData = { setDrawRange: (timeId = classSettings.settings.options.player.getTimeId()) => { bufferGeometry.setDrawRange(0, timeIndexCount * timeId); } }
													bufferGeometry.userData.setDrawRange();
											
												} },
									
											});
											
										}
										
									}
									
									traces = new Traces();
										
								}
								if (!overriddenProperties.addSettingsFolder) overriddenProperties.addSettingsFolder = (fParent, getLanguageCode) => {

									//Localization
									
									const lang = {
							
										name: "Universe",
										traces: 'Traces',
										tracesTitle: 'Display tracks of universe vertices',
							
									};
							
									const _languageCode = getLanguageCode();
							
									switch (_languageCode) {
							
										case 'ru'://Russian language
							
											lang.name = 'Вселенная';
											
											lang.traces = 'Треки';
											lang.tracesTitle = 'Показать перемещения всех вершин вселенной';
							
											break;
							
									}
									
									const fUniverse = fParent.addFolder(lang.name);

									const cookieName = 'Traces', boTracesDefault = false;
									//let boTraces = classSettings.boTraces ||= options.dat ? options.dat.cookie.get(cookieName, boTracesDefault) : boTracesDefault,Is not compatible with build of hyperSphericalUniverse.module.min.js
									let boTraces = classSettings.boTraces === true ? classSettings.boTraces : options.dat ?  options.dat.cookie.get(cookieName, boTracesDefault): boTracesDefault,
										cTraces;
									Object.defineProperty(classSettings, 'boTraces', {

										get: () => { return boTraces; },
										set: (newValue) => {

											if (cTraces.getValue() === newValue) return;
											boTraces = newValue;
											classSettings.overriddenProperties.project();
											cTraces.setValue(newValue);//Add/remove traces
										
										},
					
									});
									
									cTraces = fUniverse.add(classSettings, 'boTraces').onChange((boTraces) => { if (options.dat)  options.dat.cookie.set(cookieName, boTraces); });
									dat.controllerNameAndTitle(cTraces, lang.traces, lang.tracesTitle);
									
								}

								settings.overriddenProperties.setDrawRange = (start, count) => { settings.bufferGeometry.setDrawRange(start, count); }
								settings.overriddenProperties.getPlayerTimesLength = () => { return settings.object.geometry.times.length; }
								settings.overriddenProperties.position = (position, i, userData) => {

									userData.timeId--;
									const pos = position[i];
									userData.timeId++;
									return pos;
									
								}
								settings.overriddenProperties.editVertice = (timeId, vertice) => {
									
									classSettings.overriddenProperties.pushMiddleVertice(timeId, vertice);//добавляем новый item в classSettings.settings.object.geometry.times[data.timeId]. Это нужно что бы после выполнения шага проигрывателя при наедении мыши на вершину отображалась полная информачия о вершине
									
								}

							}
							return geometry.playerPosition[classSettings.settings.options.player.getTimeId()];

					}
					return geometry[name];
					
				},
				set: (geometry, name, value) => {

					switch (name) {

						case 'angles':
							geometry.times[0] = value;
							return true;
						case 'position':
							geometry.playerPosition[0] = value;
							return true;

					}
					geometry[name] = value;
					return true;
					
				},
				
			});
			const block = 'block', none = 'none';
			let cTimes;
			classSettings.settings.guiPoints = {

				get timeAngles() { return classSettings.settings.object.geometry.times[this.timeId != undefined ? this.timeId : 0]; },
				//for debug
				//set timeAngles(value) { console.warn(sUniverse + ': set classSettings.settings.guiPoints.timeAngles is deprecated'); },
				isSetIntersectionIndex: false,
				seletedIndex: (guiIndexStr) => {

					let guiIndex = parseInt(guiIndexStr);
					if (isNaN(guiIndex)) return guiIndexStr;
					const anglesLength = classSettings.settings.object.geometry.angles.length;
					while (guiIndex > anglesLength) guiIndex -= anglesLength;
					return guiIndex;

				},
				setControllers: (index) => {

					const guiPoints = classSettings.settings.guiPoints;
					guiPoints.getVerticeId(index);
					guiPoints.changeControllers();

				},
				searchNearestEdgeVerticeId: (index, intersection) => { return this.hyperSphere.searchNearestEdgeVerticeId(index, intersection); },
				resetControllers: () => {
					
					if(!cTimes) return;

					//сбросить выбранное время
					cTimes.__onChange( -1 );
					cTimes.__select[0].selected = true;
					delete classSettings.settings.guiPoints.timeId;
				
				},
				getVerticeId: (index, timesItemCallBack) => {

					if (
						!timesItemCallBack && (
							(index === undefined) ||
							(classSettings.settings.guiPoints.verticeId === undefined)
						)
					) return index;

					//User has mouse clicked a vertice

					let anglesCount = 0;//, timeIdSelected;
					const guiPoints = classSettings.settings.guiPoints;
					classSettings.settings.object.geometry.times.forEach((timeAngles, timeId) => {

						if (timesItemCallBack) timesItemCallBack(timeAngles, timeId);
						const anglesCountOld = anglesCount;
						anglesCount += timeAngles.length;
						if ((index >= anglesCountOld) && (index < anglesCount)) {

							const verticeId = index - anglesCountOld;
							if ((guiPoints.verticeId != verticeId) || (guiPoints.timeId != timeId)) {

								guiPoints.timeId = timeId;
								guiPoints.verticeId = verticeId;

							}

						}

					});
					return guiPoints.verticeId;

				},
				pointsStyleDisplay: none,
				create: (fPoints, cPoints, cTrace, cTraceAll, count, intersectionSelected) => {

					//Localization

					const lang = {

						notSelected: 'Not selected',
						time: 'Time',
						timeTitle: 'Position of vertices at selected time',

					};

					const options = classSettings.settings.options;
					switch (options.getLanguageCode()) {

						case 'ru'://Russian language
							lang.notSelected = 'Не выбран';
							lang.time = 'Время';
							lang.timeTitle = 'Выбрать список вершин в выбранное время';
							break;

					}

					const sTimes = 'Times', guiPoints = classSettings.settings.guiPoints;

					//find duplicate cTimes
					fPoints.__controllers.forEach((controller) => {

						if (controller.property === sTimes) {

							cTimes = controller;
							for (let i = cTimes.__select.length - 1; i > 0; i--) cTimes.__select.remove(i);
							return;
							
						}
							
					});
					if (!cTimes) {
						
						cTimes = fPoints.add({ Times: lang.notSelected }, 'Times', { [lang.notSelected]: -1 });
						dat.controllerNameAndTitle(cTimes, lang.time, lang.timeTitle);

					}

					//Переместить список Time вверх
					const elBefore = fPoints.__ul.children[1], elLast = fPoints.__ul.children[fPoints.__ul.children.length - 1];
					fPoints.__ul.removeChild(elLast);
					fPoints.__ul.insertBefore(elLast, elBefore);

					const cPointsStyle = cPoints.domElement.parentElement.parentElement.style;
					if (!cTraceAll.userData) cTraceAll.userData = {}
					classSettings.settings.options.trace = {

						onChange: (boTrace, verticeId) => {

							traceVerticeId = verticeId;
							classSettings.boTraces = boTrace;//Add/remove traces
							traceVerticeId = undefined;
						
						}

					}
					cTimes.onChange((timeId) => {

						const selectPoints = cPoints.__select;
						options.guiSelectPoint.selectPoint(-1);
						while (selectPoints.length > 1) selectPoints.removeChild(selectPoints.lastChild);
						timeId = parseInt(timeId);
						if (isNaN(timeId)) {

							console.error(sUniverse + ': cTimes.onChange. Invalid timeId = ' + timeId);
							return;

						}
						const anglesLength = classSettings.settings.object.geometry.angles.length, hyperSphereObject = this.hyperSphere.object3D;
						let display, start, end;
						if (timeId != -1) {
							
							display = block;
							start = timeId;
							end = timeId + 1;
							this.selectTime = (newStart, newEnd) => {

								if (newStart != undefined) start = newStart;
								if (newEnd != undefined) end = newEnd;
								
								if (classSettings.edges.project)
									this.hyperSphere.setEdgesRange(start, end);
								else this.hyperSphere.setVerticesRange(anglesLength * start, anglesLength * (end - start));
								
							}
								
							guiPoints.timeId = timeId;
							guiPoints.timeAngles.forEach((verticeAngles, verticeId) => {
	
								const opt = document.createElement('option');
								opt.innerHTML = verticeId;
								opt.setAttribute('value', verticeId);
								selectPoints.appendChild(opt);
	
							});
							hyperSphereObject.userData.myObject.guiPoints.setTimeId = (newTimeId) => { timeId = newTimeId }
							hyperSphereObject.userData.myObject.guiPoints.getPositionId = (timeAnglesId) => {
	
								if (timeAnglesId >= anglesLength) {

									//не выводить сообщение об ошибке если пользователь проводит мышкой над вершиной.
									if ((timeId === 0) && !hyperSphereObject.userData.myObject.guiPoints.boMouseOver) console.error(sUniverse + ': guiPoints.getPositionId. timeAnglesId = ' + timeAnglesId + ' >= anglesLength = ' + anglesLength);
									return timeAnglesId;
	
								}
								return timeId * anglesLength + timeAnglesId;
							
							}

						} else {
							
							display = none;
							start = 0;
							end = classSettings.settings.options.player.getTimeId() + 1;
							hyperSphereObject.userData.myObject.guiPoints.getPositionId = (timeAnglesId) => { return timeAnglesId; }
							
						}
						if (classSettings.edges.project)
							this.hyperSphere.setEdgesRange(start, end);
						else this.hyperSphere.setVerticesRange(anglesLength * start, anglesLength * (end - start));
						cPointsStyle.display = display;
						cTraceAll.userData.display = none;
						guiPoints.pointsStyleDisplay = cPointsStyle.display;

					});
					guiPoints.appendTimesChild = (time, timeId) => {

						const opt = document.createElement('option');

						if (time === undefined) time = classSettings.settings.options.player.getTime(timeId);
						opt.innerHTML = '(' + timeId + ') ' + time;

						if (timeId != undefined) opt.setAttribute('value', timeId);
						cTimes.__select.appendChild(opt);
						return opt;

					}
					guiPoints.getVerticeId(intersectionSelected ? intersectionSelected.index : undefined, (timeAngles, timeId) => { guiPoints.appendTimesChild(timeAngles.player.t, timeId); });
					guiPoints.setIntersection = (intersection) => { intersectionSelected = intersection; }
					guiPoints.changeControllers = () => {

						let end;
						if (classSettings.edges.project) {

							//edges

							if (intersectionSelected) {
								
								intersectionSelected.index = this.hyperSphere.searchNearestEdgeVerticeId(intersectionSelected.index, intersectionSelected);
								guiPoints.getVerticeId(intersectionSelected.index);//, (timeAngles, timeId) => { guiPoints.appendTimesChild(timeAngles.player.t, timeId); });

							}
							if (guiPoints.timeId === undefined) return;
							const timeId = guiPoints.timeId, start = timeId;
							end = timeId + 1;
							this.hyperSphere.setEdgesRange(start, end);

 						} else {

							if (guiPoints.timeId === undefined) return;
							end = guiPoints.timeId + 1;
							if (cTimes.__select.selectedIndex === end) {

								guiPoints.getVerticeId(intersectionSelected.index, () => {});//Get guiPoints.timeId
								this.selectTime(guiPoints.timeId, guiPoints.timeId + 1);
								guiPoints.setTimeId(guiPoints.timeId);
								return;
								
							}

						}

						const option = cTimes.__select[end];

						//для устранения бесконечного цикла.
						//Для проверки
						//запустить http://localhost/anhr/universe/main/hyperSphere/Examples/
						//Сделать ребра видимыми
						//Мышью выбать ребро
						if (option.selected === true) return;
						
						cTimes.__onChange(guiPoints.timeId);
						option.selected = true;
							
						if (guiPoints.verticeId === undefined) return;
						options.guiSelectPoint.selectPoint(guiPoints.verticeId);

					}
					guiPoints.changeControllers();

				},
				getValue: (cPoints) => { return cPoints.__select.options.selectedIndex - 1; },

			}

			classSettings.anglesObject2Array = () => {

				const settings = classSettings.settings, times = settings.object.geometry.times;
				if (times.length > settings.options.playerOptions.marks) console.warn(sUniverse + ': anglesObject2Array. Invalid classSettings.settings.object.geometry.times.length = ' + times.length);
				times.forEach((timeAngles, timeId) => {

					if (timeAngles instanceof Array) {

						if (timeId > 0) times[timeId] = times[timeId];
						return;

					}
					if ((timeId > 0) && (timeAngles.count != undefined)) console.warn(sUniverse + ': anglesObject2Array. classSettings.settings.object.geometry.timesAngles[' + timeId + '].count = ' + timeAngles.count + ' is ignore.');
					const angles = [];
					Object.keys(timeAngles).forEach((key) => angles[key] = timeAngles[key]);
					times[timeId] = angles;

				});
				
			}
			if (!classSettings.overriddenProperties) classSettings.overriddenProperties = {};
			if (!classSettings.overriddenProperties.edges) classSettings.overriddenProperties.edges = () => {

				//во вселенной ребра должны быть обязательно.
				console.error(sUniverse + ': classSettings.edges = false is impossible in the Universe');
				return {};
			
			};
			this.hyperSphere = this.getHyperSphere(options, classSettings);
			if (classSettings.edges) classSettings.edges = new Proxy(classSettings.edges, {

				set: (edges, name, value) => {

					switch(name) {

						case 'project':
							if (value) 
								this.hyperSphere.setEdgesRange();
							else {
								
								//display of vertices
								//Время не выбрано. Показать все вершины до времени проигрывателя
								this.hyperSphere.setVerticesRange(0, classSettings.overriddenProperties.position0.length * (classSettings.settings.options.player.getTimeId() + 1));
								
							}
							break;
							
					}
					edges[name] = value;
					return true;
					
				}
					
			});
			{//hide geometry

				const geometry = classSettings.settings.object.geometry;
				geometry.position = new Proxy(geometry.position, {
		
					get: (positions, name) => {
	
						switch (name) {
		
							case 'intersection': return (i) => {

								const times = geometry.times;
								if (times) {
			
									let timeAnglesId = 0, positionId = times[timeAnglesId].length;
									while(i >= positionId) {
			
										timeAnglesId++;
										positionId += times[timeAnglesId].length;
										
									}
									const guiPoints = classSettings.settings.guiPoints, timeIdOld = guiPoints.timeId;
									guiPoints.timeId = timeAnglesId;
									i -= positionId - geometry.angles.length;
									const position = positions[i];
									guiPoints.timeId = timeIdOld;
									return position;
									
								}
								return positions[i];
							
							}
		
						}
						return positions[name];
						
					},
					
				});
	
			}
			{//hide timeId

				let timeId;
				Object.defineProperty(classSettings.settings.bufferGeometry.userData, 'timeId', {

					get: () => {

						if (timeId === undefined) {

							//Кажется мне сюда на должно попадать
							console.error(sUniverse + ': classSettings.settings.bufferGeometry.userData get timeId. Invalid timeId = ' + timeId);
							timeId = classSettings.settings.options.playerOptions.selectSceneIndex;
							
						}
						return timeId;
					
					},
					set: (playerIndexNew) => { timeId = playerIndexNew; },

				});
				
			}

			classSettings.projectParams.scene.userData = new Proxy(classSettings.projectParams.scene.userData, {
	
				set: (userData, name, value) => {

					//сначала надо установить время
					userData[name] = value;

					//а потом выполнять какие то действия
					switch (name) {
	
						case 't':
							this.hyperSphere.oldR = userData.t;
							break;
							
					}
					return true;
	
				}
				
			});
			options.onSelectScene = (index, t) => {

				const times = classSettings.settings.object.geometry.times, length = times.length;
				if ((length > 1) && (length > index)) {

					//пользователь передвинул проигрыватель назад
					const settings = classSettings.settings, bufferGeometry = settings.bufferGeometry, drawRange = bufferGeometry.drawRange;
					if (classSettings.edges.project) {

						//Видны ребра
						const timeEdgesCount = settings.object.geometry.indices[0].timeEdgesCount;
						if (timeEdgesCount)
							this.hyperSphere.setEdgesRange();
						
					} else {
						
						this.hyperSphere.setVerticesRange(drawRange.start, times[0].length * (index + 1) - drawRange.start);
						classSettings.overriddenProperties.updateVertices();

					}
					return false;//Сдедующий шаг проигрывателя выполняется немедленно
					
				}
					
				if (classSettings.onSelectScene) { return classSettings.onSelectScene(this.hyperSphere, index, t); }
				/*до сюда не доходит потому что сейчас по умолчанию classSettings.onSelectScene создается в констркуторе HyperSphere.
				Иначе на странице http://localhost/anhr/commonNodeJS/master/HyperSphere/Examples/hyperSphere.html
				не будет выполняться шаг проигрывателя при нажатии →
				при условии что в hyperSphere.html не будет задана classSettings.onSelectScene
				else return this.onSelectScene.copyAngles(index, t);
				return true;//Сдедующий шаг проигрывателя выполняется только после посторения всех вершин без временной задержки
				*/
			
			}
			this.hyperSphere.child = this;

		}, myThreeOptions);
		this.onSelectScene = {

			copyAngles: (timeId, t) => {

				if (timeId === 0) return;
				const geometry = classSettings.settings.object.geometry, times = geometry.times,
					timeAnglesSrc  = times[timeId - 1],
					timeAnglesDest = times[timeId],
					boLog = classSettings.debug && (classSettings.debug != false);
				if (boLog) console.log('timeId = ' + classSettings.settings.bufferGeometry.userData.timeId + ' t = ' + t);
				timeAnglesSrc.forEach((timeAngles, i) => {
					
					timeAnglesDest[i] = timeAngles;
					if (boLog){

						let vertice = classSettings.settings.bufferGeometry.userData.position[i];
						const timeAnglesDestItem = timeAnglesDest[i];
						console.log(' timeVertices[' + i + '] = ' + JSON.stringify(vertice) +
									' angles = ' + JSON.stringify(timeAnglesDestItem) +
									' edges = ' + JSON.stringify(timeAnglesDestItem.edges))
						
					}
				
				});
				const bufferGeometry = this.hyperSphere.bufferGeometry, drawRange = bufferGeometry.drawRange;
				bufferGeometry.attributes.position.needsUpdate = true;
				bufferGeometry.setDrawRange(drawRange.start, geometry.indices[0].timeEdgesCount * (timeId + 1) * 2);
				classSettings.overriddenProperties.updateVertices();
				this.hyperSphere.onSelectSceneEnd(timeId);
				return false;//Немедленно выпоняется следующий шаг проигрывателя потому что copyAngles выполняется синхронно
				
			},
			
		}

	}
	name(options) {

		//Localization

		const lang = {

			name: 'Universe',

		};

		switch (options.getLanguageCode()) {

			case 'ru'://Russian language

				lang.name = 'Вселенная';

				break;

		}
		return lang.name;

	}
	arc(aAngleControls, lang, arcAngles) {

		const arcEdges = [];
		for (let i = 0; i < (aAngleControls.MAX_POINTS - 1); i++) arcEdges.push([i, i + 1]);
		aAngleControls.arc = this.hyperSphere.line({

			cookieName: 'arc',//если не задать cookieName, то настройки дуги будут браться из настроек вселенной
			object: {

				name: lang.arc,
				geometry: {

					MAX_POINTS: aAngleControls.MAX_POINTS,
					angles: arcAngles,
					//opacity: 0.3,
					indices: {

						edges: arcEdges,

					}

				}

			},

		});

	}

}

Universe.release = 'v1.5';
if (Universe.release != MyThree.release) console.error(sUniverse + ': Incompatible Universe.release = ' + Universe.release + ' version with MyThree.release = ' + MyThree.release + ' version.')

export default Universe;