Source: randomVerticeSphere.js

/**
 * @module RandomVerticeSphere
 * @description Generates a random vertice near the opposite vertice in 2D hypersphere.
 *
 * @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 RandomVertice from './randomVertice.js';
import * as utils from '../utilsSphere.js'
import HyperSphere from '../sphere.js';


const sRandomVerticesSphere = 'RandomVerticesSphere',
	π = Math.PI, round = Math.round,
	cos = Math.cos;
let random = Math.random;
		
export const anglesIdMax = 50;//Количество точек на окружности, расположенной на экваторе

/**
 * Generates a random vertice near the opposite vertice in 2D hypersphere.
 * @class
 * @extends RandomVertice
 */
export class RandomVerticeSphere extends RandomVertice {

	/**
	 * Generates a random vertice near the opposite vertice in 2D hypersphere.
	 * @param {object} [params={}] See the <b>params</b> of the <a href="./module-RandomVertice-RandomVertice.html" target="_blank"><b>RandomVertice</b></a> constructor for details.
	 * @param {object} [params.circlesCount] Count of the circles in the sphere.
	 * @param {number} verticesCount count of vertices in the random vertices cloud.
	 */
	constructor(params={}, verticesCount) {

		if (params.classSettings.debug && params.classSettings.debug.random) random = params.classSettings.debug.random;
		
		super(params, verticesCount);

		/*
Есть точка на поверхности сферы в полярной системе координат.
R - радиус сферы. 
Положение точки определено ее углами:
latitude - широта в диапазоне от -π/2 до π/2.
longitude - долгота в диапазоне от -π до π.
Начало координат находится в центре сферы. Вычислить координаты другой точки на сфере, которая находится на определенном растоянии и под определенным углом от заданной точки.
		*/
		//https://chat.deepseek.com/a/chat/s/e304455b-5714-4923-88f5-6ac9c6adee40
		/**
		 * Вычисляет точку на сфере, находящуюся на заданном расстоянии и азимуте от исходной точки
		 * @param {number} latA - Широта исходной точки в радианах [-π/2, π/2]
		 * @param {number} lonA - Долгота исходной точки в радианах [-π, π]
		 * @param {number} R - Радиус сферы
		 * @param {number} distance - Расстояние по поверхности сферы (в тех же единицах, что и R)
		 * @param {number} azimuth - Азимут (направление) в радианах, отсчитывается от севера по часовой стрелке [0, 2π]
		 * @returns {Object} Объект с координатами {lat, lon, x, y, z}
		 */
		const calculatePointOnSphere = (latA, lonA, R, distance, azimuth) => {
			if (distance > (2 * π * R)) console.error(sRandomVerticesSphere + '.calculatePointOnSphere. Invalid distance = ' + distance);
			// Обработка нулевого расстояния
			if (Math.abs(distance) < 1e-12) {
				return {
					lat: latA,
					lon: lonA,
				};
			}

			// Центральный угол
			const gamma = distance / R;

			// Обработка антиподной точки
			if (Math.abs(gamma - Math.PI) < 1e-12) {
				const latB = -latA;
				const lonB = lonA + Math.PI;
				// Нормализация долготы
				const normalizedLon = ((lonB + Math.PI) % (2 * Math.PI) - Math.PI);

				return {
					lat: latB,
					lon: normalizedLon,
				};
			}

			// Проверка на полюса
			const isNorthPole = Math.abs(latA - Math.PI / 2) < 1e-12;
			const isSouthPole = Math.abs(latA + Math.PI / 2) < 1e-12;

			// Если исходная точка на полюсе
			if (isNorthPole || isSouthPole) {
				
				return {
					
					lat: Math.asin(random() * 2 - 1),
					lon: random() * π * 2,//lonB,

				}

			}

			// Общий случай (не полюс)

			// Единичный вектор точки A
			const cosLatA = Math.cos(latA);
			const sinLatA = Math.sin(latA);
			const cosLonA = Math.cos(lonA);
			const sinLonA = Math.sin(lonA);

			const a_x = cosLatA * cosLonA;
			const a_y = cosLatA * sinLonA;
			const a_z = sinLatA;

			// Векторы севера и востока
			const N_x = -sinLatA * cosLonA;
			const N_y = -sinLatA * sinLonA;
			const N_z = cosLatA;

			const E_x = -sinLonA;
			const E_y = cosLonA;
			const E_z = 0;

			// Вектор направления D
			const cosAz = Math.cos(azimuth);
			const sinAz = Math.sin(azimuth);

			const D_x = cosAz * N_x + sinAz * E_x;
			const D_y = cosAz * N_y + sinAz * E_y;
			const D_z = cosAz * N_z + sinAz * E_z;

			// Единичный вектор точки B
			const cosGamma = Math.cos(gamma);
			const sinGamma = Math.sin(gamma);

			const b_x = cosGamma * a_x + sinGamma * D_x;
			const b_y = cosGamma * a_y + sinGamma * D_y;
			const b_z = cosGamma * a_z + sinGamma * D_z;

			// Декартовы координаты
			const xB = R * b_x;
			const yB = R * b_y;
			const zB = R * b_z;

			// Сферические координаты
			// Используем atan2 для численной устойчивости при малых значениях
			let latB, lonB;

			// Вычисление широты
			latB = Math.asin(b_z);

			// Вычисление долготы с обработкой сингулярностей
			const xyNorm = Math.sqrt(b_x * b_x + b_y * b_y);
			if (xyNorm < 1e-12) {
				// Точка близка к полюсу
				lonB = 0; // Произвольно выбираем 0
			} else {
				lonB = Math.atan2(b_y, b_x);
			}

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

			return {
				lat: latB,
				lon: lonB,
			};
		}


		//overridden methods

		this.getRandomAngles = () => {

			/*
Написать на javascript код, который будет использовать функцию calculatePointOnSphere для вычисления случайных точек на поверхности сферы, таким образом,
что бы эти случайные точки были равномерно распределены по поверхности сферы.
			*/
			//https://chat.deepseek.com/a/chat/s/e304455b-5714-4923-88f5-6ac9c6adee40

			/**
			* Метод 5: Использование calculatePointOnSphere для равномерного покрытия
			* с движением от полюсов и экватора
			*/

			const R = 1, TWO_PI = 2 * π, oppositeVertice = params.oppositeVertice;
			//strategy 3
			const distance = this.distance(Math.acos(1 - 2 * random()) * R, R);
			const result = calculatePointOnSphere(
				oppositeVertice.latitude,//Math.asin(2 * Math.random() - 1),//lat
				oppositeVertice.longitude,//Math.random() * TWO_PI - π,//lon
				R,
				distance,
				random() * TWO_PI//azimuth
			);
			const angles = utils.angles([result.lat, result.lon]);
			this.paramsVerticesAngles(angles);
			return this.angles;
			
		}
		
		/////////////////////////////overridden methods

		params.pointsCount = 0;
		this.verticesAngles(false);

	}
	
	//overridden methods
	
	ZeroArray() { return [0, 0]; }
	Center(params) { utils.angles(params.oppositeVertice); }
	get HyperSphere() { return HyperSphere; }
	
	/////////////////////////////overridden methods

}