Source: sg2d-camera.js

"use strict";

import SGModel from './libs/sg-model/sg-model.js';
import SG2DConsts from './sg2d-consts.js';
import SG2DMath from './sg2d-math.js';
import SG2DDebugging from './sg2d-debugging.js';

/**
 * Камера. Базовый класс: {@link SGModel}
 * @alias SG2D.Camera
 */
class SG2DCamera extends SGModel {
	
	/**
	 * Конструктор
	 * @param {object} [properties=void 0] Параметры конфигурации камеры 
	 * @param {boolean}	[properties.scale_wheel=true] Разрешить масштабирование камеры
	 * @param {number}		[properties.scale=SG2D.Camera.SCALE_NORMAL] Стартовый масштаб. Значения: 16 -> 200%, ..., 9 -> 112%,  8 -> 100%, ..., 4 -> 50%, 3 -> 37.5%, 2 -> 25%, 1-> 12.5%
	 * @param {number}		[properties.scale_min=SG2D.Camera.SCALE_MIN] Минимально допустимый масштаб
	 * @param {number}		[properties.scale_max=SG2D.Camera.SCALE_MAX] Максимально допустимый масштаб
	 * @param {boolean}	[properties.rotation=true] Разрешить вращение камеры
	 * @param {number}		[properties.rotate=0] Стартовый угол поворота камеры в градусах
	 * @param {object}		[properties.start_position={ x: 0, y: 0}] Стартовые координаты камеры
	 * @param {number}		[properties.rotate_adjustment=0] Базовое смещение угла камеры в градусах (например, значение -90 для вида сверху)
	 * @param {boolean}	[properties.movement_by_pointer=0] Разрешить свободное движение камеры правой кнопкой мыши
	 * @param {object} [thisProps=void 0] - Свойства, которые можно записать в this экземпляра камеры, например, ссылка на внешний объект
	 * @param {object} [options=void 0] - Дополнительные данные (настройки)
	 * @returns {SG2D.Camera}
	 */
	constructor(properties, thisProps, options) {
		super(...arguments);
	}
	
	/** @protected */
	defaults() {
		return {
			scale: SG2DCamera.SCALE_NORMAL,
			rotation: false,
			rotate: 0, // grad
			position: {x: 0, y: 0}, // current camera position
			target: {x: 0, y: 0}, // camera movement target
			offset: {x: 0, y: 0},
			wh: {w: 0, h: 0},
			wh05: {w: 0, h: 0},
			boundsPX: { left: 0, top: 0, right: 0, bottom: 0 },
			boundsCluster: { left: 0, top: 0, right: 0, bottom: 0 },
			frameCVC: 0, // frameChangeVisibleClusters - Changing this property means that some new clusters have appeared in the camera view, or some have disappeared
			scale_wheel: true, // scale with scrolling
			scale_min: SG2DCamera.SCALE_MIN,
			scale_max: SG2DCamera.SCALE_MAX,
			pointer_over_canvas: true,
			movement_by_pointer: 0,
			movement_state: 0
		};
	}
	
	/** @protected */
	initialize(properties) {
		
		let config = properties || {};
		config.scale_wheel = typeof config.scale_wheel !== "undefined" ? config.scale_wheel : true;
		config.scale = +config.scale || SG2DCamera.SCALE_NORMAL;
		config.rotation = !!config.rotation || false;
		config.rotate = +config.rotate || 0;
		config.position = config.position || null;
		config.rotate_adjustment = +config.rotate_adjustment || SG2DCamera.ROTATE_ADJUSTMENT;
		config.movement_by_pointer = +config.movement_by_pointer;
		
		this.rotate_adjustment = config.rotate_adjustment;
		this.browser_scale_start = window.devicePixelRatio ? window.devicePixelRatio : window.outerWidth/window.innerWidth;
		this.set("scale_min", +config.scale_min || SG2DCamera.SCALE_MIN);
		this.set("scale_max", +config.scale_max || SG2DCamera.SCALE_MAX);
		
		this.scales_k = [];
		this.scales_per = [];
		var k = 1, p = 100, k_step = 1.00;
		for (var i = SG2DCamera.SCALE_NORMAL; i <= this.properties.scale_max; i++) {
			this.scales_k[i] = k;
			this.scales_per[i] = ~~p;
			k += 0.125 * k_step;
			p += 12.5 * k_step;
		}
		var k = 1, p = 100;
		for (var i = SG2DCamera.SCALE_NORMAL; i >= this.properties.scale_min; i--) {
			this.scales_k[i] = k;
			this.scales_per[i] = ~~p;
			k -= 0.125 * k_step;
			p -= 12.5 * k_step;
		}
		
		this.positionPrev = {
			x: this.properties.position.x,
			y: this.properties.position.y
		};
		this.frame_lighting = 1;
		this.boundsClusterPrev = null;
		this.clustersInCamera = new Set();
		
		// Visible part of the area in PX units
		this.boundsPXTops = { // The vertices of the rectangle are calculated with camera rotation and scaling
			leftTop: {x:0,y:0},
			rightTop: {x:0,y:0},
			rightBottom: {x:0,y:0},
			leftBottom: {x:0,y:0}
		};
		// Visible part of the area in Cells units
		this.boundsClusterTops = { // The vertices of the rectangle are calculated with camera rotation and scaling
			leftTop: {x:0,y:0},
			rightTop: {x:0,y:0},
			rightBottom: {x:0,y:0},
			leftBottom: {x:0,y:0}
		};
		
		this.clustersOut = []; // Clusters that disappeared from the camera view
		this.clustersIn = []; // Clusters that appeared in the camera view
		
		this._addLinePoint = this._addLinePoint.bind(this);
		
		this.onPointerEnter = this.onPointerEnter.bind(this);
		this.onPointerLeave = this.onPointerLeave.bind(this);
		
		this.onWheelScale = this.onWheelScale.bind(this);
		if (this.properties.scale_wheel) {
			this._onwheel(this.onWheelScale);
		}
		
		// Boundaries of the visible part in Cells units (all points of 4 solid lines formed by boundsCluster vertices)
		this.boundsPoint = [];
		this.on("wh", (wh)=>{
			var q = ~~((wh.w + wh.h) / SG2DConsts.CELLSIZEPIX * 4);
			for (var i = 0; i < q; i++) this.boundsPoint[i] = {x: 0, y: 0}; // so as not to spawn objects every time!
		});
		
		this.set("scale_wheel", config.scale_wheel);
		this.set("scale", config.scale);
		this.set("rotation", config.rotation);
		
		this.on("frameCVC", SG2DDebugging.drawDebug2);
		
		this.set("movement_by_pointer", config.movement_by_pointer);
		
		this._followToTile = null;
	}
	
	_sg2dconnect(sg2d) {
		this.sg2d = sg2d;
		this.onResize();
		this.sg2d.pixi.view.addEventListener("pointerenter", this.onPointerEnter);
		this.sg2d.pixi.view.addEventListener("pointerleave", this.onPointerLeave);
		this.set("rotate", this.properties.rotate);
		this.sg2d.viewport.angle = -this.properties.rotate + this.rotate_adjustment;
		this.startPosition(this.properties.position || null);
		this._calc();
	}
	
	/** @protected */
	setRotate(newRotate, options, flags = 0) {
		if (! this.properties.rotation && ! (flags & SGModel.FLAG_FORCE_CALLBACKS)) return; //?
		newRotate = SG2DMath.normalize_a(newRotate, 1);
		let prevRotate = this.properties.rotate;
		if (this.set("rotate", newRotate, options, flags | SGModel.FLAG_IGNORE_OWN_SETTER)) {
			this.sg2d.viewport.angle = -this.properties.rotate + this.rotate_adjustment;
			let da = SG2DMath.normalize_a(newRotate - prevRotate);
			let dx = this.properties.offset.x;
			let dy = this.properties.offset.y;
			SG2DCamera._point.x = dx * SG2DMath.cos(da, 1) - dy * SG2DMath.sin(da, 1);
			SG2DCamera._point.y = dy * SG2DMath.cos(da, 1) + dx * SG2DMath.sin(da, 1);
			this.set("offset", SG2DCamera._point);
			this._calc();
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Установить начальные координаты камеры
	 * @param {object} position
	 */
	startPosition(position) {
		this.moveTo(position, true);
	}
	
	/** @protected */
	setPosition(value, options = SGModel.OBJECT_EMPTY, flags = 0) {
		value = value || this.positionPrev;
		options = ! options ||  options === SGModel.OBJECT_EMPTY ? SGModel.OPTIONS_PRECISION_5 : options;
		options.precision = options.precision || 5;
		
		if (! this.set("position", value, options, flags | SGModel.FLAG_IGNORE_OWN_SETTER)) return false;
		
		this._calc();
		
		return true;
	}
	
	_calc() {
		
		this.sg2d.viewport.x = (this.sg2d.pixi.screen.width  || 100) / 2;
		this.sg2d.viewport.y = (this.sg2d.pixi.screen.height || 100) / 2;
		this.sg2d.viewport.pivot.x = this.properties.position.x;
		this.sg2d.viewport.pivot.y = this.properties.position.y;
		
		var x = this.properties.position.x
		var y = this.properties.position.y
		var acos = SG2DMath.cos(this.properties.rotate, 1), asin = SG2DMath.sin(this.properties.rotate, 1);
		var w05 = this.properties.wh05.w * SG2DConsts.CAMERA.WIDTH_HEIGHT_K;
		var h05 = this.properties.wh05.h * SG2DConsts.CAMERA.WIDTH_HEIGHT_K;
		var bpx = this.boundsPXTops;
		var bc = this.boundsClusterTops;
		
		// boundsPXTops calc
		bpx.leftTop.x = Math.round(x - w05 * acos + h05 * asin);
		bpx.leftTop.y = Math.round(y - w05 * asin - h05 * acos);
		bpx.rightTop.x = Math.round(x + w05 * acos + h05 * asin);
		bpx.rightTop.y = Math.round(y + w05 * asin - h05 * acos);
		bpx.rightBottom.x = Math.round(x + w05 * acos - h05 * asin);
		bpx.rightBottom.y = Math.round(y + w05 * asin + h05 * acos);
		bpx.leftBottom.x = Math.round(x - w05 * acos - h05 * asin);
		bpx.leftBottom.y = Math.round(y - w05 * asin + h05 * acos);
		
		// boundsPX calc
		var boundsPX = SG2DCamera._boundsPX;
		boundsPX.left = boundsPX.top = boundsPX.right = boundsPX.bottom = void 0;
		for (var p in bpx) {
			var o = bpx[p];
			if (boundsPX.left === void 0) boundsPX.left = o.x; else if (boundsPX.left > o.x) boundsPX.left = o.x;
			if (boundsPX.top === void 0) boundsPX.top = o.y; else if (boundsPX.top > o.y) boundsPX.top = o.y;
			if (boundsPX.right === void 0) boundsPX.right = o.x; else if (boundsPX.right < o.x) boundsPX.right = o.x;
			if (boundsPX.bottom === void 0) boundsPX.bottom = o.y; else if (boundsPX.bottom < o.y) boundsPX.bottom = o.y;
		}
		var bChangeBoundsPX = false;
		for (var p in this.properties.boundsPX) {
			if (boundsPX[p] !== this.properties.boundsPX[p]) bChangeBoundsPX = true;
			this.properties.boundsPX[p] = boundsPX[p];
		}
		
		// boundsCluster calc
		var boundsCluster = SG2DCamera._boundsCluster;
		boundsCluster.left = Math.floor( 1 + boundsPX.left / SG2DConsts.CELLSIZEPIX );
		boundsCluster.top = Math.floor( 1 + boundsPX.top / SG2DConsts.CELLSIZEPIX );
		boundsCluster.right = Math.floor( 1 + boundsPX.right / SG2DConsts.CELLSIZEPIX );
		boundsCluster.bottom = Math.floor( 1 + boundsPX.bottom / SG2DConsts.CELLSIZEPIX );
		var bChangeBoundsCluster = false;
		for (var p in boundsCluster) {
			if (boundsCluster[p] !== this.properties.boundsCluster[p]) bChangeBoundsCluster = true;
			this.properties.boundsCluster[p] = boundsCluster[p];
		}
		
		if (! this.boundsClusterPrev) {
			this.boundsClusterPrev = {
				left: this.properties.boundsCluster.left,
				top: this.properties.boundsCluster.top,
				right: this.properties.boundsCluster.right,
				bottom: this.properties.boundsCluster.bottom
			}
		}
		
		// boundsClusterTops calc - step 1
		bc.leftTop.x = 0.5 + bpx.leftTop.x / SG2DConsts.CELLSIZEPIX;
		bc.leftTop.y = 0.5 + bpx.leftTop.y / SG2DConsts.CELLSIZEPIX;
		bc.rightTop.x = 0.5 + bpx.rightTop.x / SG2DConsts.CELLSIZEPIX;
		bc.rightTop.y = 0.5 + bpx.rightTop.y / SG2DConsts.CELLSIZEPIX;
		bc.rightBottom.x = 0.5 + bpx.rightBottom.x / SG2DConsts.CELLSIZEPIX;
		bc.rightBottom.y = 0.5 + bpx.rightBottom.y / SG2DConsts.CELLSIZEPIX;
		bc.leftBottom.x = 0.5 + bpx.leftBottom.x / SG2DConsts.CELLSIZEPIX;
		bc.leftBottom.y = 0.5 + bpx.leftBottom.y / SG2DConsts.CELLSIZEPIX;
		
		// Calc line points
		this.boundsPoint._length = -1;
		SG2DMath.getLinePoints(bc.leftTop, bc.rightTop, this._addLinePoint);
		SG2DMath.getLinePoints(bc.rightTop, bc.rightBottom, this._addLinePoint);
		SG2DMath.getLinePoints(bc.rightBottom, bc.leftBottom, this._addLinePoint);
		SG2DMath.getLinePoints(bc.leftBottom, bc.leftTop, this._addLinePoint);
		this.boundsPoint._length++;
		
		// calculate visible clusters
		var cluster, x ,y, x1, x2, state;
		var frame_lighting_prev = this.frame_lighting++;
		var frame_lighting = this.frame_lighting;
		this.clustersOut.length = 0;
		this.clustersIn.length = 0;

		for (var i = 0; i < this.boundsPoint._length; i++) {
			var p = this.boundsPoint[i];
			if (cluster = this.sg2d.clusters.getCluster(p.x, p.y)) {
				if (cluster.lighting_frame === 0) {
					this.clustersIn.push(cluster);
					this.clustersInCamera.add(cluster);
				}
				cluster.lighting_frame = frame_lighting;
			}
		}
		
		var x_min = Math.min(this.boundsClusterPrev.left, this.properties.boundsCluster.left);
		var y_min = Math.min(this.boundsClusterPrev.top, this.properties.boundsCluster.top);
		var x_max = Math.max(this.boundsClusterPrev.right, this.properties.boundsCluster.right);
		var y_max = Math.max(this.boundsClusterPrev.bottom, this.properties.boundsCluster.bottom);
		
		for (y = y_min; y <= y_max; y++) {
			state = 0;
			x1 = 0;
			x2 = 0;
			for (x = x_min; x <= x_max; x++) {
				if (cluster = this.sg2d.clusters.getCluster(x, y)) {
					switch (state) {
						case 0: { // Finding the first cluster with frame_lighting
							switch (cluster.lighting_frame) {
								case 0: break;
								case frame_lighting_prev: {
									this.clustersOut.push(cluster);
									this.clustersInCamera.delete(cluster);
									cluster.lighting_frame = 0;
									break;
								}
								case frame_lighting: {
									state = 1;
									x1 = x;
									x2 = x1;
									break;
								}
								default:
									debugger; throw "Error!";
							}
							break;
						}
						case 1: { // Finding the last cluster with frame lighting
							switch (cluster.lighting_frame) {
								case 0: case frame_lighting_prev: {
									break;
								}
								case frame_lighting: {
									x2 = x;
									break;
								}
								default: 
									console.log("cluster.lighting_frame=" + cluster.lighting_frame);
									debugger; throw "Error!";
							}
							break;
						}
					}
				}
			}
			if (x1 && x2) {
				for (var x = x1; x <= x2; x++) {
					if (cluster = this.sg2d.clusters.getCluster(x, y)) {
						if (cluster.lighting_frame === 0) {
							this.clustersIn.push(cluster);
							this.clustersInCamera.add(cluster);
						}
						cluster.lighting_frame = frame_lighting;
					}
				}
				for (var x = x2 + 1; x <= x_max; x++) {
					if (cluster = this.sg2d.clusters.getCluster(x, y)) {
						if (cluster.lighting_frame === frame_lighting_prev) {
							this.clustersOut.push(cluster);
							this.clustersInCamera.delete(cluster);
						} //else break; // if left, sometimes the error in state=1 => cluster.lighting_frame is older than it could be!
						cluster.lighting_frame = 0;
					}
				}
			}
		}
		if (this.clustersOut.length || this.clustersIn.length) {
			this.set("frameCVC", this.properties.frameCVC+1);
			for (var i = 0; i < this.clustersOut.length; i++) {
				this.clustersOut[i].outCamera();
			}
			for (var i = 0; i < this.clustersIn.length; i++) {
				this.clustersIn[i].inCamera();
			}
		}
		
		// boundsClusterTops calc - step 2
		for (var p in bc) {
			bc[p].x = Math.floor(0.5 + bc[p].x);
			bc[p].y = Math.floor(0.5 + bc[p].y);
		}
		
		SG2DDebugging.drawDebug();
		
		this.positionPrev.x = this.properties.position.x;
		this.positionPrev.y = this.properties.position.y;
		
		this.boundsClusterPrev.left = this.properties.boundsCluster.left;
		this.boundsClusterPrev.top = this.properties.boundsCluster.top;
		this.boundsClusterPrev.right = this.properties.boundsCluster.right;
		this.boundsClusterPrev.bottom = this.properties.boundsCluster.bottom;
		
		if (bChangeBoundsPX) this.trigger("boundsPX");
		if (bChangeBoundsCluster) this.trigger("boundsCluster");
	}
	
	/**
	 * Тайл, за которым будет следовать камера
	 * @param {SG2DTile} tile
	 */
	followTo(tile) {
		this._followToTile = tile;
	}
	
	/**
	 * Прекратить следование за тайлом
	 */
	stopFollow() {
		this._followToTile = null;
	}
	
	/**
	 * Получить тайл за которым следует камера
	 */
	getFollow() {
		return this._followToTile;
	}
	
	/**
	 * Назначить точку притяжения камеры. Камера начнёт плавно к ней смещаться, если не задан флаг true
	 * @param {object} point
	 * @param {boolean} [flag=false] Переместить мгновенно (true) или плавно (false)
	 */
	moveTo() {
		if (typeof arguments[0] === "object") {
			this.set("target", arguments[0], SGModel.OPTIONS_PRECISION_5);
			if (arguments[1] === true) {
				this.set("position", arguments[0], SGModel.OPTIONS_PRECISION_5);
			}
		} else {
			SG2DCamera._moveToTarget.x = arguments[0];
			SG2DCamera._moveToTarget.y = arguments[1];
			this.set("target", SG2DCamera._moveToTarget, SGModel.OPTIONS_PRECISION_5);
			if (arguments[2] === true) {
				this.set("position", SG2DCamera._moveToTarget, SGModel.OPTIONS_PRECISION_5);
			}
		}
	}
	
	/**
	 * Сдвинуть камеру в заданном направлении на указанное расстояние
	 * @param {number} [step=SG2D.Consts.CELLSIZEPIX] - Расстояние в пикселях
	 * @param {number} [rotate=0] Направление (угол в градусах)
	 */
	moveOffset(step = SG2DConsts.CELLSIZEPIX, rotate = 0) {
		let k = this.scales_k[this.properties.scale];
		let d = step / k;
		rotate = rotate - this.properties.rotate + this.rotate_adjustment;
		SG2DCamera._point.x = this.properties.offset.x + d * SG2DMath.cos(rotate, 1);
		SG2DCamera._point.y = this.properties.offset.y - d * SG2DMath.sin(rotate, 1);
		this.set("offset", SG2DCamera._point);
	}
	
	/** @protected */
	onResize() {
		var browser_scale = window.devicePixelRatio ? window.devicePixelRatio : window.outerWidth/window.innerWidth; // ignoring browser scaling
		var k = this.scales_k[this.properties.scale] * this.browser_scale_start / browser_scale;
		this.sg2d.viewport.scale.set(k);
		if (this.rotate_adjustment === 0 || this.rotate_adjustment === 180 || this.rotate_adjustment === -180) {
			this.set("wh", {w: (this.sg2d.pixi.screen.width || 100) / k, h: (this.sg2d.pixi.screen.height || 100) / k});
		} else {
			this.set("wh", {w: (this.sg2d.pixi.screen.height || 100) / k, h: (this.sg2d.pixi.screen.width || 100) / k});
		}
		this.set("wh05", {w: this.properties.wh.w>>1, h: this.properties.wh.h>>1});
		
		this.set("position", void 0, void 0, SG2DCamera.FLAG_FORCE_CALLBACKS);
	}
	
	/** @private */
	_iterate() {
		if (this._followToTile) {
			SG2DCamera._point.x = this._followToTile.properties.position.x + this.properties.offset.x;
			SG2DCamera._point.y = this._followToTile.properties.position.y + this.properties.offset.y;
			this.set("target", SG2DCamera._point);
		}	
		if (this.properties.target.x !== this.properties.position.x || this.properties.target.y !== this.properties.position.y) {
			SG2DCamera._point.x = this.properties.position.x + SG2DCamera.SMOOTHNESS_FACTOR * (this.properties.target.x - this.properties.position.x);
			SG2DCamera._point.y = this.properties.position.y + SG2DCamera.SMOOTHNESS_FACTOR * (this.properties.target.y - this.properties.position.y);
			//SG2DCamera._point.x = this.properties.target.x; SG2DCamera._point.y = this.properties.target.y; // TODO DEL DEBUG
			this.set("position", SG2DCamera._point);
		}
	}
	
	/** @private */
	onPointerEnter(e) {
		this.set("pointer_over_canvas", true);
	}
	
	/** @private */
	onPointerLeave(e) {
		this.set("pointer_over_canvas", false);
	}
	
	/** @private */
	onWheelScale(e) {
		if (! this.properties.pointer_over_canvas) return;
		var delta = e.deltaY || e.detail || e.wheelDelta;
		this.zoomInc(delta < 0 ? 1 : -1);
	}
	
	/**
	 * Установить масштабирование камеры
	 * @param {number} [scale=SG2D.Camera.SCALE_NORMAL]
	 */
	zoom(scale = SG2DCamera.SCALE_NORMAL) {
		scale = Math.max(this.properties.scale_min, scale);
		scale = Math.min(this.properties.scale_max, scale);
		this.set("scale", scale);
		this.onResize();
	}
	
	/**
	 * Приблизить/отдалить камеру
	 * @param {number} [scaleIncrement=1]
	 */
	zoomInc(scaleIncrement = 1) {
		var scale_next = this.properties.scale + scaleIncrement;
		scale_next = Math.max(this.properties.scale_min, scale_next);
		scale_next = Math.min(this.properties.scale_max, scale_next);
		this.set("scale", scale_next);
		this.onResize();
	}
	
	/** Получить текущее значения масштабирования */
	getScale() {
		return {
			value: this.properties.scale,
			percent: this.scales_per[this.properties.scale],
			k: this.scales_k[this.properties.scale]
		};
	}
	
	_addLinePoint(x, y) { // Used in Math.GetLinePoints (..) as a callback
		var p = this.boundsPoint[++this.boundsPoint._length];
		p.x = (x < 1 ? 1 : (x > SG2DConsts.AREASIZE ? SG2DConsts.AREASIZE : x));
		p.y = (y < 1 ? 1 : (y > SG2DConsts.AREASIZE ? SG2DConsts.AREASIZE : y));
	}
	
	_onwheel(fCallback, e) {
		if (! e) e = window;
		if ("onwheel" in document) {
			e.addEventListener("wheel", fCallback); // IE9+, FF17+, Ch31+
		} else if ("onmousewheel" in document) {
			e.addEventListener("mousewheel", fCallback); // obsolete version of the event
		} else {
			e.addEventListener("MozMousePixelScroll", fCallback); // Firefox < 17
		}
	}
	_offwheel(fCallback, e) {
		if (! e) e = window;
		e.removeEventListener("wheel", fCallback);
		e.removeEventListener("onmousewheel", fCallback);
		e.removeEventListener("mousewheel", fCallback);
		e.removeEventListener("MozMousePixelScroll", fCallback);
	}
	
	/** @protected */
	destroy() {
		this.sg2d.pixi.view.removeEventListener("pointerenter", this.onPointerEnter);
		this.sg2d.pixi.view.removeEventListener("pointerleave", this.onPointerLeave);
		this._offwheel(this.onWheelScale);
		super.destroy();
	}
}

SG2DCamera.singleInstance = true;

SG2DCamera.ROTATE_ADJUSTMENT = 0; // default

SG2DCamera.SCALE_MIN = 2;
SG2DCamera.SCALE_NORMAL = 8;
SG2DCamera.SCALE_MAX = 10;

SG2DCamera.MOVEMENT_BY_POINTER = 0b00000001;
SG2DCamera.MOVEMENT_BY_POINTER_LEFT = 0b00000001;
SG2DCamera.MOVEMENT_BY_POINTER_RIGHT = 0b00000010;
SG2DCamera.MOVEMENT_BY_POINTER_MIDDLE = 0b00000100;

SG2DCamera.STATE_NO_MOVEMENT = 0;
SG2DCamera.STATE_MOVEMENT_WAITING_SHIFT = 1;
SG2DCamera.STATE_MOVING = 2;

/**
 * @property {number} [SMOOTHNESS_FACTOR=0.25] Плавность движения камеры к точке притяжения
 */
SG2DCamera.SMOOTHNESS_FACTOR = 0.25;

SG2DCamera.typeProperties = {
	rotate: SGModel.TYPE_NUMBER,
	rotation: SGModel.TYPE_BOOLEAN,
	position: SGModel.TYPE_OBJECT_NUMBERS,
	target: SGModel.TYPE_OBJECT_NUMBERS,
	offset: SGModel.TYPE_OBJECT_NUMBERS,
	wh: SGModel.TYPE_OBJECT_NUMBERS,
	wh05: SGModel.TYPE_OBJECT_NUMBERS,
	boundsPX: SGModel.TYPE_OBJECT_NUMBERS,
	boundsCluster: SGModel.TYPE_OBJECT_NUMBERS
};

SG2DCamera.ownSetters = {
	rotate: true,
	position: true
};

/** @public */
SG2DCamera.moveTo = function(...args) {
	SG2DCamera.getInstance().moveTo.apply(SG2DCamera.getInstance(), ...args);
}

SG2DCamera.scaling = function(params) {
	if (SG2DCamera.getInstance().properties.scale_wheel) {
		SG2DCamera.getInstance().onWheelScale({deltaY: params.action === "-" ? 1 : -1});
	}
}

SG2DCamera._point = {x: void 0, y: void 0};

SG2DCamera._boundsPX = {};

SG2DCamera._boundsCluster = {};

SG2DCamera._moveToTarget = {x: 0, y: 0};

export default SG2DCamera;