Source: sg2d-math.js

"use strict";

/**
 * Математические функции
 * @class
 * @alias SG2D.Math
 * @returns {SG2D.Math}
 */
let SG2DMath = {};

(function() {

	let _uid = 0;
	
	/**
	 * Генератор уникального числового целого значения
	 * @alias SG2D.Math.uid
	 * @returns {Number}
	 */
	SG2DMath.uid = function() {
		return (++_uid);
	};
	
	/**
	 * Функция для работы с битовыми масками - добавить биты
	 * @param {Number} value - Исходное значение
	 * @param {Number} flag - Добавляемые биты
	 */
	SG2DMath.addFlag = (value, flag)=>(value | flag);
	
	/**
	 * Функция для работы с битовыми масками - обнулить биты
	 * @param {Number} value - Исходное значение
	 * @param {Number} flag - Обнуляемые биты
	 */
	SG2DMath.removeFlag = (value, flag)=>(value & ~flag);
	
	/**
	 * Функция для работы с битовыми масками - установить биты в значение state={0|1}
	 * @param {Number} value - Исходное значение
	 * @param {Number} flag - Изменяемые биты
	 * @param {Number} [state=true] - Устанавливаемое значение бита
	 */
	SG2DMath.setFlag = (value, flag, state = true)=>(state ? SG2DMath.addFlag(value, flag) : SG2DMath.removeFlag(value, flag));
	
	/**
	 * Функция для работы с битовыми масками - проверить наличие битов (что не нулевые)
	 * @param {Number} value - Исходное значение
	 * @param {Number} flag - Проверяемые биты
	 */
	SG2DMath.hasFlag = (value, flag)=>(value & flag);
	
	/**
	 * Функция для работы с битовыми масками - возвращает ! hasFlag(..)
	 * @param {Number} value - Исходное значение
	 * @param {Number} flag - Проверяемые биты
	 */
	SG2DMath.noFlag = (value, flag)=>(! (value & flag)); // TODO: used?
	
	let _ap = [];
	for (var dec = 0; dec <= 10; dec++) _ap[dec] = 10 ** dec;
	
	/**
	 * Округлить до десятичного знака. Поддерживает округление до 10-го знака после запятой.
	 * @param {Number} v
	 * @param {Number} dec
	 * @return {Number}
	 */
	SG2DMath.roundTo = function(v, dec) {
		var e = _ap[dec];
		return Math.round(v * e) / e;
	};
	
	/**
	 * Получить модуль разницы между числами
	 * @param {Number} v1
	 * @param {Number} v2
	 * @return {Number}
	 */
	SG2DMath.absDelta = function(v1, v2) {
		return Math.abs(v1 - v2);
	};

	/**
	 * Константа для перевода радиан в градусы и наоборот (значение 0.017453292519943295)
	 * @const
	 * @type {Number}
	 */
	SG2DMath.PI180 = Math.PI/180;
	
	SG2DMath.PI2 = Math.PI*2;
	SG2DMath.rad90 = 90 * SG2DMath.PI180;
	
	/**
	 * Преобразовать градусы в радианы
	 * @param {Number} a
	 * @return {Number}
	 */
	SG2DMath.toRad = function(a) {
		return a * this.PI180;
	};
	
	/**
	 * Преобразовать радианы в градусы
	 * @param {Number} a
	 * @return {Number}
	 */
	SG2DMath.toDeg = function(a) {
		return a / this.PI180;
	};
	
	/**
	 * Нормализировать угол с округлением до заданного десятичного знака. Возвращается угол между 0 и 360 градусов.
	 * @param {Number} a
	 * @param {Number} [precision=0] - Точность
	 * @return {Number}
	 */
	SG2DMath.normalize_a = function(a, precision = 0) {
		while (a >= 360) a = a - 360;
		while (a < 0) a = a + 360;
		return this.roundTo(a, precision);
	};

	let _aSin = [];
	let _aCos = [];
	for (var a = 0; a <= 360; a++) {
		_aSin[a] = Math.sin(a * SG2DMath.PI180);
		_aCos[a] = Math.cos(a * SG2DMath.PI180);
	}
	let _aSin1 = [];
	let _aCos1 = [];
	for (var a = 0; a <= 3600; a++) {
		var _a = a / 10;
		_aSin1[a] = Math.sin(_a * SG2DMath.PI180);
		_aCos1[a] = Math.cos(_a * SG2DMath.PI180);
	}
	
	/**
	 * Быстрая функция получения синуса угла. Поддерживается точность угла до одного знака после запятой.
	 * @param {Number} a - Угол в градусах
	 * @param {Number} [precision=0] - Точность. Может быть 0 или 1
	 * @return {Number}
	 */
	SG2DMath.sin = function(a, precision = 0) { // Точность до десятых долей градуса
		return (precision === 0 ? _aSin[this.normalize_a(a, precision)] : _aSin1[ Math.round(10 * this.normalize_a(a, 1)) ]);
	};
	
	/**
	 * Быстрая функция получения косинуса угла. Поддерживается точность угла до одного знака после запятой.
	 * @param {Number} a - Угол в градусах
	 * @param {Number} [precision=0] - Точность. Может быть 0 или 1
	 * @return {Number}
	 */
	SG2DMath.cos = function(a, precision = 0) {
		return (precision === 0 ? _aCos[this.normalize_a(a, precision)] : _aCos1[ Math.round(10 * this.normalize_a(a, 1)) ]);
	};
	
	/**
	 * Быстрая функция получения синуса угла.
	 * @param {Number} a - Угол в радианах
	 * @param {Number} [precision=0] - Точность угла в градусах. Может быть 0 или 1
	 * @return {Number}
	 */
	SG2DMath.sinrad = function() {
		arguments[0] = arguments[0] / this.PI180;
		return this.sin.apply(this, arguments);
	};
	
	/**
	 * Быстрая функция получения косинуса угла.
	 * @param {Number} a - Угол в радианах
	 * @param {Number} [precision=0] - Точность угла в градусах. Может быть 0 или 1
	 * @return {Number}
	 */
	SG2DMath.cosrad = function() {
		arguments[0] = arguments[0] / this.PI180;
		return this.cos.apply(this, arguments);
	};
	
	/*Math.angle_p1p2_rad = function(p1, p2) { //not used!
		return Math.atan2(p2.y - p1.y, p2.x - p1.x);
	};*/
	
	/**
	 * Угол между точками в градусах
	 * @param {object} p1
	 * @param {object} p2
	 * @param {Number} [precision=0] - Точность угла в градусах
	 * @return {Number}
	 */
	SG2DMath.angle_p1p2_deg = function(p1, p2, precision = 0) {
		var angle_rad = Math.atan2(p2.y - p1.y, p2.x - p1.x);
		var angle_deg = this.normalize_a(angle_rad / this.PI180, precision);
		return angle_deg;
	};
	
	/**
	 * Расстояние между точками
	 * @param {Number} dx - Разница координат по оси X
	 * @param {Number} dy - Разница координат по оси Y
	 * @return {Number}
	 */
	SG2DMath.distance_d = function(dx, dy) {
		return Math.sqrt(dx*dx + dy*dy);
	};
	
	/**
	 * Расстояние между точками
	 * @param {object} p1
	 * @param {object} p2
	 * @return {Number}
	 */
	SG2DMath.distance_p = function(p1, p2) {
		return Math.sqrt((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y));
	};
	
	/**
	 * Минимальный угол между a_start и a_end (градусы)
	 * @param {Number} a_start
	 * @param {Number} a_end
	 * @return {Number}
	 */
	SG2DMath.betweenAnglesDeg = function(a_start, a_end) {
		var da1 = a_end - a_start;
		var da2 = (a_end-360) - a_start;
		var da3 = a_end - (a_start-360);
		return (Math.abs(da1) < Math.abs(da2) ? (Math.abs(da3) < Math.abs(da1) ? da3 : da1) : (Math.abs(da3) < Math.abs(da2) ? da3 : da2));
	};

	/**
	 * Получить направление вращения (вправо / влево) при котором целевой угол будет достигнут быстрее
	 * @param {Number} rotate_current - Начальный угол
	 * @param {Number} rotate_target - Целевой угол
	 * @return {Number} Возвращает -1, 0 или 1
	 */
	SG2DMath.nearestDirRotate = function(rotate_current, rotate_target) {
		var a1 = this.normalize_a(rotate_target - rotate_current);
		var a2 = this.normalize_a(rotate_current - rotate_target);
		if (a1 === a2) return 0;
		return a1 > a2 ? -1 : 1;
	};
	
	/**
	 * 8 векторов. Против часовой стрелки, начиная с 0 градусов, шаг 45 градусов.
	 */
	SG2DMath.vectors45 = [{dx:1,dy:0},{dx:1,dy:1},{dx:0,dy:1},{dx:-1,dy:1},{dx:-1,dy:0},{dx:-1,dy:-1},{dx:0,dy:-1},{dx:1,dy:-1}];
	
	/**
	 * 4 вектора. Против часовой стрелки, начиная с 0 градусов, шаг 90 градусов.
	 */
	SG2DMath.vectors90 = [{dx:1,dy:0},{dx:0,dy:1},{dx:-1,dy:0},{dx:0,dy:-1}];

	/** @private */
	let _linePoints = [];
	/**
	 * Default point collection function
	 * @private
	 */
	SG2DMath._addLinePoint = function(x, y) {
		this.push({x: x, y: y});
	};
	
	/**
	* Формирует сплошную линию в виде массива точек
	* @param {object} oPointStart
	* @param {object} oPointEnd
	* @param {mixed} [dest] - Может быть функцией fAddLinePoint или массивом aDest
	* @return {array}
	*/
	SG2DMath.getLinePoints = function(oPointStart, oPointEnd, dest = void 0) {
		
		var fAddLinePoint = this._addLinePoint;
		if (typeof dest === "function") fAddLinePoint = dest;
		else if (Array.isArray(dest)) fAddLinePoint = fAddLinePoint.bind(dest);
		else (_linePoints.length = 0, fAddLinePoint = fAddLinePoint.bind(_linePoints));
		
		fAddLinePoint(Math.round(oPointStart.x), Math.round(oPointStart.y));

		var x1 = oPointStart.x, y1 = oPointStart.y;
		var x2 = oPointEnd.x, y2 = oPointEnd.y;
		var Dx = (x2 - x1), Dy = (y2 - y1);
		var d = Math.sqrt(Dx*Dx + Dy*Dy);
		var dx = Dx / d, dy = Dy / d;
		var cx = x1, cy = y1;
		var cxPrev = Math.round(x1), cyPrev = Math.round(y1);
		var cxIntPrev = cxPrev, cyIntPrev = cyPrev;

		while (true) {
			cx += dx;
			cy += dy;

			var cxInt = Math.round(cx);
			var cyInt = Math.round(cy);

			if ( (cxInt === cxIntPrev) && (cyInt === cyIntPrev) ) continue;
			if ( ( (x2 > x1) && (cx > x2) ) || ( (x2 < x1) && (cx < x2) ) ) break;
			if ( ( (y2 > y1) && (cy > y2) ) || ( (y2 < y1) && (cy < y2) ) ) break;
			if ( (cxInt != cxIntPrev) && (cyInt != cyIntPrev) ) {
				var cx1Int = cxInt;
				var cy1Int = cyIntPrev;
				var d1 = Math.sqrt( Math.pow(cx1Int - cx, 2) + Math.pow(cy1Int - cy, 2) ) + Math.sqrt( Math.pow(cx1Int - cxPrev, 2) + Math.pow(cy1Int - cyPrev, 2) );
				var cx2Int = cxIntPrev;
				var cy2Int = cyInt;
				var d2 = Math.sqrt( Math.pow(cx2Int - cx, 2) + Math.pow(cy2Int - cy, 2) ) + Math.sqrt( Math.pow(cx2Int - cxPrev, 2) + Math.pow(cy2Int - cyPrev, 2) );
				if (d1 > d2) {
					fAddLinePoint(cx2Int, cy2Int);
					cxIntPrev = cx2Int;
					cyIntPrev = cy2Int;
				} else {
					fAddLinePoint(cx1Int, cy1Int);
					cxIntPrev = cx1Int;
					cyIntPrev = cy1Int;
				}
			}

			fAddLinePoint(cxInt, cyInt);

			cxPrev = cx;
			cyPrev = cy;
			cxIntPrev = cxInt;
			cyIntPrev = cyInt;
		}
		
		return _linePoints;
	};
	
	let _avgVertext = {x: void 0, y: void 0};
	
	/**
	 * Получить среднюю точку
	 * @param {array} vertexes
	 * @param {object} point
	 * @return {object}
	 */
	SG2DMath.avgVertext = function(vertexes, point) {
		if (! vertexes.length) { debugger; throw "Error 773734";}
		point = point || _avgVertext;
		_avgVertext.x = vertexes[0].x;
		_avgVertext.y = vertexes[0].y;
		for (var i = 1; i < vertexes.length; i++) {
			_avgVertext.x += vertexes[i].x;
			_avgVertext.y += vertexes[i].y;
		}
		_avgVertext.x = _avgVertext.x / vertexes.length;
		_avgVertext.y = _avgVertext.y / vertexes.length;
		return _avgVertext;
	};
})();

export default SG2DMath;