Source: sphere.js

/**
 * @module Sphere
 * @description 2 dimensional [hypersphere]{@link https://en.wikipedia.org/wiki/N-sphere}.
 * All the vertices form a sphere.
 *
 * @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 Circle from './circle.js';
import three from '../three.js'
import { RandomVerticeSphere as RandomVertice } from './RandomVertice/randomVerticeSphere.js';
//import RandomCloud from './RandomVertice/randomCloudSphere.js';
import * as utils from './utilsSphere.js'
import Position from './position.js'

const sSphere = 'Sphere',
	π = Math.PI;

/**
 * 2 dimensional [hypersphere]{@link https://en.wikipedia.org/wiki/N-sphere}.
 * All the vertices form a sphere.
 * @class
 * @extends Circle
 */
class Sphere extends Circle {

	/**
	 * @param {Options} options See <a href="../../../master/jsdoc/Options/Options.html" target="_blank">Options</a>.
	 * @param {object} [classSettings] <b>Circle</b> class settings. See <a href="./module-HyperSphere-HyperSphere.html" target="_blank">HyperSphere classSettings</a>.
	 **/
	constructor(options, classSettings) { super(options, classSettings); }

	//base methods

	planesGeometry(changedAngleId, aAngleControls, planeGeometry, longitudeId){

		const latitudeId = longitudeId - 1;
		switch(changedAngleId){

			case latitudeId: planeGeometry(longitudeId); break;
			case longitudeId: planeGeometry( latitudeId); break;
			default: console.error(sSphere + ': Update planes. Invalid changedAngleId = ' + changedAngleId);
				
		}
		
	}
	get axes() { return {

			//порядок размещения осей в декартовой системе координат
			//нужно что бы широта двигалась по оси y а долгота вращалась вокруг y
			indices: [1, 0, 2],//долгота вращается вокруг оси y. Широта двигается вдоль оси y. Вершины собираются по краям оси y
//			indices: [0, 1, 2],
			names: (getLanguageCode) => { return super.axes.names(getLanguageCode); }

		}

	}
	newHyperSphere(options, classSettings) { return new Sphere(options, classSettings); }
	get cookieName() { return 'Sphere' + (this.classSettings.cookieName ? '_' + this.classSettings.cookieName : ''); }
	get probabilityDensity() {

		return {

			sectorValueName: 'sectorSquare',
			sectorValue: (probabilityDensity, i) => {

				const sector = probabilityDensity[i], r = this.classSettings.r, hb = sector.hb, ht = sector.ht;
				
				//Площадь сегмента
				//https://allll.net/wiki/%D0%9F%D0%BB%D0%BE%D1%89%D0%B0%D0%B4%D1%8C_%D0%BF%D0%BE%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%BE%D1%81%D1%82%D0%B8_%D1%88%D0%B0%D1%80%D0%BE%D0%B2%D0%BE%D0%B3%D0%BE_%D1%81%D0%B5%D0%B3%D0%BC%D0%B5%D0%BD%D1%82%D0%B0
				sector[this.probabilityDensity.sectorValueName] = 2 * Math.PI * r * (ht - hb);
				return sector[this.probabilityDensity.sectorValueName];

			},

		}

	}
	defaultAngles() { return { count: 4, } }//random pyramid
	pushRandomLatitude(verticeAngles) {

		//добиваемся равномерного распределения вершин по поверхности сферы
		const f = this.rotateLatitude === 0 ? Math.acos : Math.asin;
		verticeAngles.push(f(Math.random() * (Math.random() > 0.5 ? 1: -1)));
		
	}
	
	pushRandomAngle(verticeAngles) {

		this.pushRandomLatitude(verticeAngles);
		this.pushRandomLongitude(verticeAngles);

	}
	name( getLanguageCode ) {

		//Localization
		
		const lang = {

			name: "Sphere",

		};

		const _languageCode = getLanguageCode();

		switch (_languageCode) {

			case 'ru'://Russian language

				lang.name = 'Сфера';

				break;

		}
		return lang.name;
		
	}
	logSphere() {

		if (!this.classSettings.debug) return;
		this.logHyperSphere();
		
	}

	intersection(color) {

		const THREE = three.THREE,
			classSettings = this.classSettings,
			r = classSettings.r,
			mesh = new THREE.GridHelper(2 * r, 10, color, color);
		mesh.rotation.x = Math.PI / 2;
		mesh.position.copy(new THREE.Vector3(0, 0, classSettings.intersection.position * r));
		return mesh;

	}

	//Overridden methods from base class

	middlePosition(points, boCloud, boCreateHypersphere) {

		const _this = this;
		
		//https://chat.deepseek.com/a/chat/s/24dea3aa-eccd-4e97-a3e3-3f3ff0631de6
		/*
		Заданы несколько точек на поверхности сферы в декартовой системе координат. Начало координат в центре сферы.
		Найти точку на поверхности сферы, равноудаленную от заданных точек.
		Написать код на javascript.
		*/
		/**
		 * Находит точку на поверхности сферы (с центром в начале координат),
		 * которая приблизительно равноудалена от заданных точек.
		 * @param {Array<Array<number>>} points - Массив точек [x, y, z]
		 * @returns {Array<number>} Точка на сфере [x, y, z] или null, если точки не заданы
		 */
		function findEquidistantPoint(points) {
			
			if (!points || points.length === 0) {
				console.error(sSphere + ': findEquidistantPoint. Должна быть задана хотя бы одна точка');
				return;
			}

			// Суммируем все векторы точек
			let sumVector = [0, 0, 0];
			for (const point of points) {
				sumVector[0] += point[0];
				sumVector[1] += point[1];
				sumVector[2] += point[2];
			}
			
			// Радиус сферы
			const sphereRadius = _this.r;

			// Нормализуем сумму, чтобы получить точку на сфере
			const [x, y, z] = sumVector;
			const length = Math.sqrt(x * x + y * y + z * z);
			_this.setArc(sphereRadius, 1 - length / points.length);
			
			let middleVertice;//, middleVerticeAngles;

			if (length < 7e-17) {

				// Все точки в начале координат или их сумма нулевая, то есть все три противоположные вершины образуют равнобедренный треугольник на плоскости, проходящей через центр сферы

				const THREE = three.THREE;
				const oppositeVertices = points;

				//http://localhost/anhr/commonNodeJS/master/HyperSphere/Examples/NormalSphere.html

				// Заданные три точки на сфере (в декартовых координатах)
				if (oppositeVertices.length != 3) console.error(sSphere + ': findEquidistantPoint. Invalid oppositeVertices.length = ' + oppositeVertices.length);
				const oppositeVerticeA = oppositeVertices[0];
				const pointA = new THREE.Vector3(oppositeVerticeA.x, oppositeVerticeA.y, oppositeVerticeA.z);//(3, 4, 0);
				const oppositeVerticeB = oppositeVertices[1];
				const pointB = new THREE.Vector3(oppositeVerticeB.x, oppositeVerticeB.y, oppositeVerticeB.z);//(0, 3, -4);
				const oppositeVerticeC = oppositeVertices[2];
				const pointC = new THREE.Vector3(oppositeVerticeC.x, oppositeVerticeC.y, oppositeVerticeC.z);//(-3, -4, 0);

				// Функция для построения плоскости по трем точкам
				function createPlaneFromPoints(p1, p2, p3) {

					// Вычисляем нормаль плоскости через векторное произведение
					const v1 = new THREE.Vector3().subVectors(p2, p1);
					const v2 = new THREE.Vector3().subVectors(p3, p1);
					const normal = new THREE.Vector3().crossVectors(v1, v2).normalize();
					return normal;

				}

				// Функция для вычисления точек пересечения нормали со сферой
				function findSphereNormalIntersections(normal, radius) {
					// Нормаль уже проходит через центр сферы (начало координат)
					// Уравнение пересечения: |t * normal| = radius
					// t = ±radius (так как normal - единичный вектор)

					return normal.clone().multiplyScalar(Math.random() > 0.5 ? radius : -radius);

				}
				const normal = createPlaneFromPoints(pointA, pointB, pointC);

				// Вычисление точек пересечения нормали со сферой
				middleVertice = findSphereNormalIntersections(normal, sphereRadius);
//				middleVerticeAngles = _this.vertice2angles(middleVertice);

			} else middleVertice = [x / length, y / length, z / length];

			_this.randomVertices(_this.vertice2angles(middleVertice), _this.object3D.parent, boCloud, boCreateHypersphere);
			return middleVertice;
			
		}

		/**
		 * Вычисляет угловое расстояние (в радианах) между двумя точками на сфере.
		 * @param {Array<number>} p1 - Точка [x, y, z]
		 * @param {Array<number>} p2 - Точка [x, y, z]
		 * @returns {number} Угловое расстояние в радианах
		 */
		/*
		function angularDistance(p1, p2) {
			const dot = p1[0] * p2[0] + p1[1] * p2[1] + p1[2] * p2[2];
			// Избегаем ошибок округления
			const clamped = Math.max(-1, Math.min(1, dot));
			return Math.acos(clamped);
		}
		*/

		// Пример использования
		/*
		const points = [
			[1, 0, 0],
			[0, 1, 0],
			[0, 0, 1]
		];
		*/

		const result = findEquidistantPoint(points);
		/*
		console.log("Приближенная равноудаленная точка:", result);

		// Проверяем расстояния
		if (result) {
			console.log("Угловые расстояния до каждой точки:");
			for (const point of points) {
				const dist = angularDistance(result, point);
				console.log(`Точка ${point}: ${dist.toFixed(6)} рад (${(dist * 180 / Math.PI).toFixed(2)}°)`);
			}
		}
		*/
		return result;

	}
	ZeroArray() { return [0, 0]; }
	Vertice(angles) { return utils.angles(angles); }
	/**
	 * Converts a vertice position to vertice angles.
	 * @param {array} vertice array of the vertice axes
	 * @returns Vertice angles.
	 */
	vertice2angles(vertice) { return utils.casterianToAngles(Position(vertice)); }
	a2v(angles, r = 1) { return utils.anglesToCartesian(angles, r); }
//	normalizeAngles(angles){

		/*https://chat.deepseek.com/a/chat/s/26327d17-c346-476d-9199-a0814f3a7a0a
		Есть точка на поверхности сферы в полярной системе координат. Начало полярной системы координат находится в центре сферы.
		Положение точки обозначить как
		point.latitude - широта в диапазоне от -π/2 до π/2,
		point.longitude - долгота в диапазоне от -π до π,
		Пусть положение точки point задано за пределами допустимого диапазона углов.
		Написать функцию на javascript с названием normalize, которая нормализует положение точки point в допустимый диапазон.

		Как работает функция:

		Долгота нормализуется в диапазон [-π, π] с помощью операции взятия по модулю

		Широта сначала также нормализуется по модулю, но если она выходит за пределы [-π/2, π/2], то:

		Широта отражается относительно полюсов

		Долгота сдвигается на π (антиподальная точка)

		Долгота снова нормализуется после сдвига

		Это обеспечивает корректное отображение любой точки на поверхности сферы в допустимый диапазон координат.
		*/
/*
		const normalize = (normalized) => {

			// Нормализация долготы в диапазон [-π, π]
			normalized.longitude = normalized.longitude % (2 * Math.PI);
			if (normalized.longitude > Math.PI) {
				normalized.longitude -= 2 * Math.PI;
			} else if (normalized.longitude < -Math.PI) {
				normalized.longitude += 2 * Math.PI;
			}

			// Нормализация широты в диапазон [-π/2, π/2]
			normalized.latitude = normalized.latitude % (2 * Math.PI);

			// Если широта выходит за пределы [-π/2, π/2], отражаем ее
			if (Math.abs(normalized.latitude) > Math.PI / 2) {
				if (normalized.latitude > 0) {
					normalized.latitude = Math.PI - normalized.latitude;
				} else {
					normalized.latitude = -Math.PI - normalized.latitude;
				}

				// После отражения, сдвигаем долготу на π
				normalized.longitude += Math.PI;

				// Снова нормализуем долготу после сдвига
				normalized.longitude = normalized.longitude % (2 * Math.PI);
				if (normalized.longitude > Math.PI) {
					normalized.longitude -= 2 * Math.PI;
				} else if (normalized.longitude < -Math.PI) {
					normalized.longitude += 2 * Math.PI;
				}
			}
		}

		// Тестовые случаи
		const point1DeepSeek = { latitude: 3, longitude: 5 }
		normalize(point1DeepSeek);
		console.log(point1DeepSeek);
		// {latitude: ~1.1416, longitude: ~-1.2832}

		const point2DeepSeek = { latitude: -2, longitude: 4 }
		normalize(point2DeepSeek);
		console.log(normalize(point2DeepSeek));
		// {latitude: ~-2, longitude: ~-2.2832}

		const point3DeepSeek = { latitude: Math.PI, longitude: 2 * Math.PI }
		normalize(point3DeepSeek);
		console.log(normalize(point3DeepSeek));
		// {latitude: ~0, longitude: ~0}

		const point4DeepSeek = { latitude: -3 * Math.PI / 2, longitude: -3 * Math.PI }
		normalize(point4DeepSeek);
		console.log(normalize(point4DeepSeek));
		// {latitude: ~0, longitude: ~0}

		//https://gemini.google.com/app/64aa493066e3b07e
*/
		/**
		 * Нормализует широту и долготу точки в допустимые диапазоны.
		 * point.latitude: широта, от -π/2 до π/2
		 * point.longitude: долгота, от -π до π
		 * @param {object} point Объект с полями latitude и longitude.
		 * @param {number} point.latitude Текущая широта.
		 * @param {number} point.longitude Текущая долгота.
		 * @returns {object} Новый объект с нормализованными latitude и longitude.
		 */
/*
		function normalizeGemini(point) {
			const PI = Math.PI;

			// 1. Нормализация долготы (longitude) в диапазон (-π, π]
			let normalizedLongitude = point.longitude % (2 * PI);
			if (normalizedLongitude > PI) {
				normalizedLongitude -= 2 * PI;
			} else if (normalizedLongitude <= -PI) {
				normalizedLongitude += 2 * PI;
			}
			// Примечание: Если нужно строго (-π, π), то случай -π обрабатывается как π,
			// но в геодезии обычно используют (-180, 180], что соответствует (-π, π].
			// Для строгого (-π, π) нужно поменять <= на < в последнем условии,
			// но при % (2 * PI) и последующих операциях крайние случаи обычно попадают в π или -π.
			// Если point.longitude изначально кратно 2*PI, то % даст 0, что корректно.

			// 2. Нормализация широты (latitude)
			let normalizedLatitude = point.latitude;

			// Сначала приводим широту к диапазону [-π/2, 3π/2] (или аналогичному),
			// чтобы учесть "переход через полюса".
			let revs = Math.floor((normalizedLatitude + PI / 2) / (2 * PI));
			let adjustedLatitude = normalizedLatitude - revs * 2 * PI;

			// Проверяем, в какой полуоборот попадает широта
			if (adjustedLatitude > PI / 2 && adjustedLatitude <= 3 * PI / 2) {
				// Переход через Северный полюс (π/2) или Южный полюс (-π/2 после приведения)

				// Меняем полушарие
				normalizedLatitude = PI - adjustedLatitude;

				// Также нужно сдвинуть долготу на π (180 градусов), т.к. мы перешли на противоположную сторону
				normalizedLongitude = normalizedLongitude + PI;

				// Повторная нормализация долготы после сдвига
				normalizedLongitude = normalizedLongitude % (2 * PI);
				if (normalizedLongitude > PI) {
					normalizedLongitude -= 2 * PI;
				} else if (normalizedLongitude <= -PI) { // Используем <= для включения -π в случай сдвига, если исходная была π
					normalizedLongitude += 2 * PI;
				}

			} else {
				// Широта уже в диапазоне [-π/2, π/2]
				normalizedLatitude = adjustedLatitude;
			}

			// Финальная проверка на крайние случаи (хотя должно быть уже корректно)
			// Ограничение широты:
			if (normalizedLatitude > PI / 2) {
				normalizedLatitude = PI / 2;
			} else if (normalizedLatitude < -PI / 2) {
				normalizedLatitude = -PI / 2;
			}

			return {
				latitude: normalizedLatitude,
				longitude: normalizedLongitude
			};
		}

		// Пример использования:
		const point1 = { latitude: 5 * Math.PI / 2 + 0.1, longitude: 3 * Math.PI }; // Широта больше π/2, Долгота больше π
		const normalized1 = normalizeGemini(point1);
		console.log(`Исходная: lat=${point1.latitude.toFixed(3)}, lon=${point1.longitude.toFixed(3)}`);
		console.log(`Нормализованная: lat=${normalized1.latitude.toFixed(3)} (~${Math.PI / 2 - 0.1}), lon=${normalized1.longitude.toFixed(3)} (~0)`);
		// Ожидаемый результат: lat: π/2 - 0.1 (полюс + обратный путь), lon: 0 (3π -> π -> -π -> 0)
		// После 2*π оборота: lat: π/2 + 0.1, lon: π.
		// Переход через полюс: new_lat = π - (π/2 + 0.1) = π/2 - 0.1. new_lon = π + π = 2π -> 0.

		const point2 = { latitude: -Math.PI, longitude: -5 * Math.PI / 2 }; // Широта меньше -π/2, Долгота меньше -π
		const normalized2 = normalizeGemini(point2);
		console.log(`\nИсходная: lat=${point2.latitude.toFixed(3)}, lon=${point2.longitude.toFixed(3)}`);
		console.log(`Нормализованная: lat=${normalized2.latitude.toFixed(3)} (~0), lon=${normalized2.longitude.toFixed(3)} (~-π/2)`);
		// Ожидаемый результат: lat: 0, lon: -π/2
		// После 2*π оборота: lat: -π, lon: -π/2.
		// Переход через полюс: lat: -π, adjusted = -π. revs = -1. adjustedLatitude = -π - (-1 * 2π) = π.
		// 0 < π/2 && π <= 3π/2. new_lat = π - π = 0. new_lon = -π/2 + π = π/2.

		const point3 = { latitude: 0, longitude: 2.5 * Math.PI };
		const normalized3 = normalizeGemini(point3);
		console.log(`\nИсходная: lat=${point3.latitude.toFixed(3)}, lon=${point3.longitude.toFixed(3)}`);
		console.log(`Нормализованная: lat=${normalized3.latitude.toFixed(3)} (~0), lon=${normalized3.longitude.toFixed(3)} (~π/2)`);
		// Ожидаемый результат: lat: 0, lon: 0.5π

		const point4 = { latitude: Math.PI / 2 + 0.1, longitude: 0 };
		const normalized4 = normalizeGemini(point4);
		console.log(`\nИсходная: lat=${point4.latitude.toFixed(3)}, lon=${point4.longitude.toFixed(3)}`);
		console.log(`Нормализованная: lat=${normalized4.latitude.toFixed(3)} (~π/2 - 0.1), lon=${normalized4.longitude.toFixed(3)} (~π)`);
		// Ожидаемый результат: lat: π/2 - 0.1, lon: π. (Переход через Северный полюс)

		const point5 = { latitude: -Math.PI / 2 - 0.1, longitude: Math.PI / 2 };
		const normalized5 = normalizeGemini(point5);
		console.log(`\nИсходная: lat=${point5.latitude.toFixed(3)}, lon=${point5.longitude.toFixed(3)}`);
		console.log(`Нормализованная: lat=${normalized5.latitude.toFixed(3)} (~-π/2 + 0.1), lon=${normalized5.longitude.toFixed(3)} (~-π/2)`);
		// Ожидаемый результат: lat: -π/2 + 0.1, lon: -π/2. (Переход через Южный полюс)

		angles.forEach((verticeAngles) => {

			
		});
		
	}
*/
	get verticeEdgesLengthMax() { return 3/*6*/; }//нельзя добавлть новое ребро если у вершины уже 6 ребра
	get dimension() { return 3; }//space dimension
	get verticesCountMin() { return 4; }
/*
	getRandomMiddleAngles(oppositeVertices) {

		const THREE = three.THREE;
		
		//http://localhost/anhr/commonNodeJS/master/HyperSphere/Examples/NormalSphere.html
		
		// Радиус сферы
		const settings = this.classSettings.settings;
		const sphereRadius = this.classSettings.overriddenProperties.r(settings.guiPoints ? settings.guiPoints.timeId : settings.options.player.getTimeId());//this.classSettings.r;

		// Заданные три точки на сфере (в декартовых координатах)
		if (oppositeVertices.length != 3) console.error(sSphere + ': getRandomMiddleAngles. Invalid oppositeVertices.length = ' + oppositeVertices.length);
		const oppositeVerticeA = oppositeVertices[0];
		const pointA = new THREE.Vector3(oppositeVerticeA.x, oppositeVerticeA.y, oppositeVerticeA.z);//(3, 4, 0);
		const oppositeVerticeB = oppositeVertices[1];
		const pointB = new THREE.Vector3(oppositeVerticeB.x, oppositeVerticeB.y, oppositeVerticeB.z);//(0, 3, -4);
		const oppositeVerticeC = oppositeVertices[2];
		const pointC = new THREE.Vector3(oppositeVerticeC.x, oppositeVerticeC.y, oppositeVerticeC.z);//(-3, -4, 0);

		//// Нормализуем точки, чтобы они точно лежали на сфере
		//pointA.normalize().multiplyScalar(sphereRadius);
		//pointB.normalize().multiplyScalar(sphereRadius);
		//pointC.normalize().multiplyScalar(sphereRadius);

		// Функция для построения плоскости по трем точкам
		function createPlaneFromPoints(p1, p2, p3) {
			// Вычисляем нормаль плоскости через векторное произведение
			const v1 = new THREE.Vector3().subVectors(p2, p1);
			const v2 = new THREE.Vector3().subVectors(p3, p1);
			const normal = new THREE.Vector3().crossVectors(v1, v2).normalize();
			return normal;
		}

		// Функция для вычисления точек пересечения нормали со сферой
		function findSphereNormalIntersections(normal, radius) {
			// Нормаль уже проходит через центр сферы (начало координат)
			// Уравнение пересечения: |t * normal| = radius
			// t = ±radius (так как normal - единичный вектор)

			return normal.clone().multiplyScalar(Math.random() > 0.5 ? radius: -radius);
		}
		const normal = createPlaneFromPoints(pointA, pointB, pointC);

		// Вычисление точек пересечения нормали со сферой
		const point = findSphereNormalIntersections(normal, sphereRadius);
		return this.vertice2angles(Position([point.x, point.y, point.z]));
		
	}
*/
	/**
	 * @param {THREE.Scene} scene [THREE.Scene]{@link https://threejs.org/docs/index.html?q=sce#api/en/scenes/Scene}
	 * @param {Options} options See <a href="../../jsdoc/Options/Options.html" target="_blank">Options</a>.
	 * @param {object} randomVerticesSettings See <b>randomVerticesSettings</b> of the <a href="./module-HyperSphere-RandomVertices.html" target="_blank">RandomVertices</a> class.
	 * @returns new RandomVertices child class.
	 */
	newRandomVertices(scene, options, randomVerticesSettings) { return new RandomVertices(scene, options, randomVerticesSettings); }
	get RandomVertice() { return RandomVertice; }
//	get RandomCloud() { return RandomCloud; }

	///////////////////////////////Overridden methods from base class

}

class RandomVertices extends Circle.RandomVertices {

	constructor(scene, options, randomVerticesSettings){

		if (randomVerticesSettings.np === undefined) randomVerticesSettings.np = 36;
		super(scene, options, randomVerticesSettings);
		this.class = Sphere;
		
	}

	//overridden methods

	getHyperSphere(options, classSettings) {

		let circlesSphere;
		circlesSphere = new Sphere(options, classSettings);
		return circlesSphere;

	}
	getArcAngle(vertice, oppositeVertice)
	{
		
		//DeepSeek. вычислить угол между двумя точками на поверхности шара
		//векторы
		//A=(R,ϕ1,λ1 ) - vertice
		const ϕ1 = vertice[0], λ1 = vertice[1];
		//B=(R,ϕ2,λ2 ) - oppositeVertice
		const ϕ2 = oppositeVertice[0], λ2 = oppositeVertice[1];
		//где
		//ϕ — широта (от −90° до 90°),
		//λ — долгота (от −180° до 180°),
		const arccos = Math.acos, sin = Math.sin, cos = Math.cos;
		const θ = arccos(sin(ϕ1) * sin(ϕ2) + cos(ϕ1) * cos(ϕ2) * cos(λ1 - λ2));
		if (isNaN(θ)) console.error(sSphere + ': getArcAngle. Invalid θ = ' + θ);
		return θ;
		
	}
	oppositeVertice0(params, inaccurateLatitude) {

		let latitude = params.oppositeVertice.latitude;
		Object.defineProperty(params.oppositeVertice, '0', {

			get: () => { return latitude; },
			set: (latitudeNew) => {
	
				latitude = inaccurateLatitude(latitudeNew);
				return true;
	
			},
	
		});
		
	}
//	antipodeCenter(params, antipodeLatitude) { return [antipodeLatitude(params.oppositeVertice.latitude), params.oppositeVertice.longitude - π]; }
	zeroArray() { return utils.angles([0, 0]); }
	onePointArea(d, np) {
		
		//Площадь сферы на которой в среднем будет находиться одна случайная точка.
		//Площадь сферы вычисляем из площади боковой поверхности цилиндра, поделенной на количество точек на окружности np
		//Цилиндр расположен на экваторе сферы так, чтобы его середина касалась экватора
		//Высота цилиндра в радианах равна d.
		const h = 2 * Math.tan(d / 2),//Высота цилиндра радиусом 1. См. https://en.wikipedia.org/wiki/Trigonometric_functions
			S = 2 * π * h;//Площадь боковой поверхности цилиндра радиусом 1
		return S / np;//Площадь сферы на которой в среднем будет находиться одна случайная точка.
		
	}
	numPoints(d, s, circleId, x) {
		
		//Для вычисления количества случайных точек numPoints около окружности, расположенной на расстоянии circleDistance радиан
		//я вычисляю площадь шарового пояса между параллелями S и делю ее на s площадь сферы на которой в среднем будет находиться одна случайная точка.
		const cos = Math.cos,
			h1 = cos(x),//расстояние от текущей окружности до центра шара
			hprev = cos((circleId - 1) * d),//расстояние от предыдущей окружности до центра шара
			h = h1 - hprev,//высота шарового пояса
			S = Math.abs(2 * π * h);//DeepSeek. площадь шарового пояса между параллелями
		return Math.round(S / s);//количество случайных точек около окружности, расположенной на расстоянии circleDistance радиан
		
	}
/*	
	center(params) {
		
		//center is antipode of the opposite vertice
		//Центр окружностей случайных точек center находится с противоположной от params.oppositeVertice стороны гиперсферы
		const antipodeLatitude = (latitude) => { return -latitude; },
//			center = params.randomVertices.antipodeCenter(params, antipodeLatitude);
			center = [antipodeLatitude(params.oppositeVertice.latitude), params.oppositeVertice.longitude - π];
		
		Object.defineProperty(center, 'lat', {
			
			get: () => { return center[0]; },
			set: (lat) => {
	
				params.oppositeVertice.latitude = antipodeLatitude(lat);
				return true;
	
			},
		
		});
		Object.defineProperty(center, 'lng', { get: () => { return center[1]; }, });
		return center;
		
	}
*/	
	getCirclePoint2D(circleDistance, params, options) {
		
		let newLat, newLng;
		const center = params.center, angle = 2 * π * (params.random ? Math.random() : options.i / options.numPoints), // Текущий угол в радианах
			lat = center.lat, lng = center.lng;
			
		if (circleDistance === 0) {

			//длинна дуги равна нулю. Координаты точки окружности противоположны координатам центра окружности
			newLat = - lat;
			newLng = lng + π;

		} else {

			// Формулы сферической тригонометрии
			newLat = Math.asin(
				Math.sin(lat) * Math.cos(circleDistance) +
				Math.cos(lat) * Math.sin(circleDistance) * Math.cos(angle)
			);

			newLng = lng + Math.atan2(
				Math.sin(angle) * Math.sin(circleDistance) * Math.cos(lat),
				Math.cos(circleDistance) - Math.sin(lat) * Math.sin(newLat)
			);

		}

		//Normalise angles
		if (newLng > π) newLng -= 2 * π;
		else if (newLng < -π) newLng += 2 * π;

		return [newLat, newLng];
	
	}
	getCirclePoint(circleDistance, params, options) { return utils.angles(this.getCirclePoint2D(circleDistance, params, options)); }
	circlesCount(np) { return np; }//если количество окружностей равно количеству точек на окружности, то точки будут равномерно располагаться на гиперсфере
	getNumPoints(circleDistance, R, dCircleDistance) {
		
		return parseInt(
			2 * π * Math.sin(circleDistance / R)//длинна окружности для гиперсферы радиусом 1
			/ dCircleDistance
		);
		
	}
	
	/////////////////////////////overridden methods
	
}
/*
RandomVertices.Vertice = (vertice) => {

	if (vertice.longitude != undefined) return;
	Object.defineProperty(vertice, 'latitude', {
		
		get: () => { return vertice[0]; },
		set: (latitude) => {

			vertice[0] = latitude;
			return true;

		},
	
	});
	Object.defineProperty(vertice, 'longitude', { get: () => { return vertice[1]; }, });

}
*/
RandomVertices.ZeroArray = () => { return [0, 0]; }
RandomVertices.getCenter = (params) => {
	
	//center is antipode of the opposite vertice
	//Центр окружностей случайных точек center находится с противоположной от params.oppositeVertice стороны гиперсферы
	const antipodeLatitude = (latitude) => { return -latitude; },
//			center = params.randomVertices.antipodeCenter(params, antipodeLatitude);
		center = [antipodeLatitude(params.oppositeVertice.latitude), params.oppositeVertice.longitude - π];
	
	Object.defineProperty(center, 'lat', {
		
		get: () => { return center[0]; },
		set: (lat) => {

			params.oppositeVertice.latitude = antipodeLatitude(lat);
			return true;

		},
	
	});
	Object.defineProperty(center, 'lng', { get: () => { return center[1]; }, });
	return center;
	
}
RandomVertices.Center = (params, inaccurateLatitude) => {

	const center = params.center;
	if (center.length < 1) center.push(0);
	if (center.length < 2) center.push(0);
//	params.randomVertices.defineCenterCoordinates(params);
	if (center.lat === undefined)
		Object.defineProperty(center, 'lat', {
	
			get: () => { return center[0]; },
			set: (lat) => {

				if (center[0] === lat) return true;
				center[0] = lat;
				if (params.randomVertices) params.randomVertices.changeCirclesPoints();
				return true;
	
			},
	
		});
	if (center.lng === undefined)
		Object.defineProperty(center, 'lng', {
	
			get: () => { return center[1]; },
			set: (lng) => {
	
				if (center[1] === lng) return true;
				center[1] = lng;
				if (params.randomVertices) params.randomVertices.changeCirclesPoints();
				return true;
	
			},

		});
	center.lat = inaccurateLatitude(center.lat);
	
	const Vertice = (vertice) => {
	
		if (vertice.longitude != undefined) return;
		Object.defineProperty(vertice, 'latitude', {
			
			get: () => { return vertice[0]; },
			set: (latitude) => {
	
				if (vertice[0] === latitude) return true;
				vertice[0] = latitude;
				if (params.randomVertices) params.randomVertices.changeCirclesPoints();
				return true;
	
			},
		
		});
		Object.defineProperty(vertice, 'longitude', {
			
			get: () => { return vertice[1]; },
			set: (longitude) => {
	
				if (vertice[1] === longitude) return true;
				vertice[1] = longitude;
				if (params.randomVertices) params.randomVertices.changeCirclesPoints();
				return true;
	
			},
		
		});
	
	}
	utils.angles(params.vertice);
	utils.angles(params.oppositeVertice);
	
}
Sphere.RandomVertices = RandomVertices;

export default Sphere;