<commit_before>define([
	'goo/loaders/Loader',
	'goo/renderer/Texture',
	'goo/loaders/dds/DdsLoader',
	'goo/util/SimpleResourceUtil',
	'goo/renderer/Util',
	'goo/util/Latch'
],
/** @lends */
function (
	Loader,
	Texture,
	DdsLoader,
	SimpleResourceUtil,
	Util,
	Latch
) {
	"use strict";

	/**
	 * @class Takes away the pain of creating textures of various sorts.
	 * @param {Settings} settings Texturing settings
	 */
	function TextureCreator(settings) {
		settings = settings || {};

		this.verticalFlip = settings.verticalFlip !== undefined ? settings.verticalFlip : true;
		this._loader = settings.loader !== undefined ? settings.loader : new Loader();

		this.textureLoaders = {
			'.dds': new DdsLoader()
		};
	}

	TextureCreator.cache = {};
	TextureCreator.UNSUPPORTED_FALLBACK = '.png';

	function endsWith(str, suffix) {
		return str.indexOf(suffix, str.length - suffix.length) !== -1;
	}

	TextureCreator.clearCache = function () {
		TextureCreator.cache = {};
	};

	TextureCreator.prototype.loadTexture2D = function (imageURL, settings) {
		if (TextureCreator.cache[imageURL] !== undefined) {
			return TextureCreator.cache[imageURL];
		}

		var simpleResourceUtilCallback = {
			onSuccess: function (/* ArrayBuffer */response) {
				loader.load(response, rVal, creator.verticalFlip, 0, response.byteLength);
//				console.info("Loaded image: " + imageURL);
				TextureCreator._finishedLoading();
				// callLoadCallback(url);
			},
			onError: function (t) {
				console.warn("Error loading texture: " + imageURL + " | " + t);
			}
		};

		var creator = this;
		for (var extension in this.textureLoaders) {
			if (endsWith(imageURL.toLowerCase(), extension)) {
				var loader = this.textureLoaders[extension];
//				console.log(extension + ' - ' + loader);

				if (!loader || !loader.isSupported()) {
					imageURL = imageURL.substring(0, imageURL.length - extension.length);
					imageURL += TextureCreator.UNSUPPORTED_FALLBACK;
					settings = settings || {};
					settings.flipY = false;
					break;
				}

				// make a dummy texture to fill on load = similar to normal
				// path, but using arraybuffer instead
				var rVal = new Texture(Util.clone(TextureCreator.DEFAULT_TEXTURE_2D.image), settings);
				rVal.image.dataReady = false;
				rVal.a = imageURL;

				TextureCreator.cache[imageURL] = rVal;

				// from URL
				this._loader.load(imageURL, null, Loader.ARRAY_BUFFER).then(simpleResourceUtilCallback.onSuccess, simpleResourceUtilCallback.onError);

				// return standin while we wait for texture to load.
				return rVal;
			}
		}

		if (TextureCreator.cache[imageURL] !== undefined) {
			return TextureCreator.cache[imageURL];
		}

		// Create a texture
		var texture = new Texture(null, settings);
		TextureCreator.cache[imageURL] = texture;

		// Load the actual image
		this._loader.loadImage(imageURL).then(function (data) {
			texture.setImage(data);
			TextureCreator._finishedLoading(data);
		});

//		console.info("Loaded image: " + imageURL);

		return texture;
	};

	TextureCreator.prototype.loadTextureVideo = function (videoURL, loop) {
		if (TextureCreator.cache[videoURL] !== undefined) {
			return TextureCreator.cache[videoURL];
		}

		var video = document.createElement('video');
		video.loop = (typeof (loop) === 'boolean') ? loop : true;

		video.addEventListener('error', function () {
			console.warn('Couldn\'t load video URL [' + videoURL + ']');
		}, false);

		var texture = new Texture(video, {
			wrapS: 'EdgeClamp',
			wrapT: 'EdgeClamp'
		});

		texture.readyCallback = function () {
			if (video.readyState >= 3) {
				console.log('Video ready: ' + video.videoWidth + ', ' + video.videoHeight);
				video.width = video.videoWidth;
				video.height = video.videoHeight;

				// set minification filter based on pow2
				if (Util.isPowerOfTwo(video.width) === false || Util.isPowerOfTwo(video.height) === false) {
					texture.generateMipmaps = false;
					texture.minFilter = 'BilinearNoMipMaps';
				}

				video.play();

				video.dataReady = true;
				return true;
			}
			return false;
		};
		texture.updateCallback = function () {
			return !video.paused;
		};

		video.crossOrigin = 'anonymous';

		video.src = videoURL;

		TextureCreator.cache[videoURL] = texture;

		return texture;
	};

	TextureCreator.prototype.loadTextureWebCam = function () {
		var video = document.createElement('video');
		video.autoplay = true;
		video.loop = true;

		var texture = new Texture(video, {
			wrapS: 'EdgeClamp',
			wrapT: 'EdgeClamp'
		});

		texture.readyCallback = function () {
			if (video.readyState >= 3) {
				console.log('WebCam video ready: ' + video.videoWidth + ', ' + video.videoHeight);
				video.width = video.videoWidth;
				video.height = video.videoHeight;

				// set minification filter based on pow2
				if (Util.isPowerOfTwo(video.width) === false || Util.isPowerOfTwo(video.height) === false) {
					texture.generateMipmaps = false;
					texture.minFilter = 'BilinearNoMipMaps';
				}

				video.dataReady = true;
				return true;
			}
			return false;
		};
		texture.updateCallback = function () {
			return !video.paused;
		};

		// Webcam video
		window.URL = window.URL || window.webkitURL;
		navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
		if (navigator.getUserMedia) {
			navigator.getUserMedia({
				video: true
			}, function (stream) {
				video.src = window.URL.createObjectURL(stream);
			}, function () {
				console.warn('Unable to capture WebCam. Please reload the page.');
			});
		} else {
			console.warn('No support for WebCam getUserMedia found!');
		}

		return texture;
	};

	/**
	 *
	 * @param {Array} imageDataArray Array containing images, image elements or image urls. [left, right, bottom, top, back, front]
	 * @returns {Texture} cubemap
	 */
	TextureCreator.prototype.loadTextureCube = function (imageDataArray, settings) {
		var texture = new Texture(null, settings);
		texture.variant = 'CUBE';
		var images = [];

		var latch = new Latch(6, function () {
			var w = images[0].width;
			var h = images[0].height;
			for (var i = 0; i < 6; i++) {
				var img = images[i];
				if (w !== img.width || h !== img.height) {
					texture.generateMipmaps = false;
					texture.minFilter = 'BilinearNoMipMaps';
					console.error('Images not all the same size!');
				}
			}

			texture.setImage(images);
			texture.image.dataReady = true;
			texture.image.width = w;
			texture.image.height = h;
		});

		var that = this;
		for (var i = 0; i < imageDataArray.length; i++) {
			/*jshint loopfunc: true */
			(function (index) {
				var queryImage = imageDataArray[index];
				if (typeof queryImage === 'string') {
					that._loader.loadImage(queryImage).then(function (image) {
						images[index] = image;
						latch.countDown();
					});
				} else {
					images[index] = queryImage;
					latch.countDown();
				}
			})(i);
		}

		return texture;
	};

	TextureCreator._globalCallback = null;
	TextureCreator._finishedLoading = function (image) {
		if (TextureCreator._globalCallback) {
			try {
				TextureCreator._globalCallback(image);
			} catch (e) {
				console.error('Error in texture callback:', e);
			}
		}
	};

	// Add Object.freeze when fast enough in browsers
	var colorInfo = new Uint8Array([255, 255, 255, 255]);
	TextureCreator.DEFAULT_TEXTURE_2D = new Texture(colorInfo, null, 1, 1);
	TextureCreator.DEFAULT_TEXTURE_CUBE = new Texture([colorInfo, colorInfo, colorInfo, colorInfo, colorInfo, colorInfo], null, 1, 1);
	TextureCreator.DEFAULT_TEXTURE_CUBE.variant = 'CUBE';

	return TextureCreator;
});
<commit_msg>Added more verbal error message when WebCam can't be captured.<commit_after>define([
	'goo/loaders/Loader',
	'goo/renderer/Texture',
	'goo/loaders/dds/DdsLoader',
	'goo/util/SimpleResourceUtil',
	'goo/renderer/Util',
	'goo/util/Latch'
],
/** @lends */
function (
	Loader,
	Texture,
	DdsLoader,
	SimpleResourceUtil,
	Util,
	Latch
) {
	"use strict";

	/**
	 * @class Takes away the pain of creating textures of various sorts.
	 * @param {Settings} settings Texturing settings
	 */
	function TextureCreator(settings) {
		settings = settings || {};

		this.verticalFlip = settings.verticalFlip !== undefined ? settings.verticalFlip : true;
		this._loader = settings.loader !== undefined ? settings.loader : new Loader();

		this.textureLoaders = {
			'.dds': new DdsLoader()
		};
	}

	TextureCreator.cache = {};
	TextureCreator.UNSUPPORTED_FALLBACK = '.png';

	function endsWith(str, suffix) {
		return str.indexOf(suffix, str.length - suffix.length) !== -1;
	}

	TextureCreator.clearCache = function () {
		TextureCreator.cache = {};
	};

	TextureCreator.prototype.loadTexture2D = function (imageURL, settings) {
		if (TextureCreator.cache[imageURL] !== undefined) {
			return TextureCreator.cache[imageURL];
		}

		var simpleResourceUtilCallback = {
			onSuccess: function (/* ArrayBuffer */response) {
				loader.load(response, rVal, creator.verticalFlip, 0, response.byteLength);
//				console.info("Loaded image: " + imageURL);
				TextureCreator._finishedLoading();
				// callLoadCallback(url);
			},
			onError: function (t) {
				console.warn("Error loading texture: " + imageURL + " | " + t);
			}
		};

		var creator = this;
		for (var extension in this.textureLoaders) {
			if (endsWith(imageURL.toLowerCase(), extension)) {
				var loader = this.textureLoaders[extension];
//				console.log(extension + ' - ' + loader);

				if (!loader || !loader.isSupported()) {
					imageURL = imageURL.substring(0, imageURL.length - extension.length);
					imageURL += TextureCreator.UNSUPPORTED_FALLBACK;
					settings = settings || {};
					settings.flipY = false;
					break;
				}

				// make a dummy texture to fill on load = similar to normal
				// path, but using arraybuffer instead
				var rVal = new Texture(Util.clone(TextureCreator.DEFAULT_TEXTURE_2D.image), settings);
				rVal.image.dataReady = false;
				rVal.a = imageURL;

				TextureCreator.cache[imageURL] = rVal;

				// from URL
				this._loader.load(imageURL, null, Loader.ARRAY_BUFFER).then(simpleResourceUtilCallback.onSuccess, simpleResourceUtilCallback.onError);

				// return standin while we wait for texture to load.
				return rVal;
			}
		}

		if (TextureCreator.cache[imageURL] !== undefined) {
			return TextureCreator.cache[imageURL];
		}

		// Create a texture
		var texture = new Texture(null, settings);
		TextureCreator.cache[imageURL] = texture;

		// Load the actual image
		this._loader.loadImage(imageURL).then(function (data) {
			texture.setImage(data);
			TextureCreator._finishedLoading(data);
		});

//		console.info("Loaded image: " + imageURL);

		return texture;
	};

	TextureCreator.prototype.loadTextureVideo = function (videoURL, loop) {
		if (TextureCreator.cache[videoURL] !== undefined) {
			return TextureCreator.cache[videoURL];
		}

		var video = document.createElement('video');
		video.loop = (typeof (loop) === 'boolean') ? loop : true;

		video.addEventListener('error', function () {
			console.warn('Couldn\'t load video URL [' + videoURL + ']');
		}, false);

		var texture = new Texture(video, {
			wrapS: 'EdgeClamp',
			wrapT: 'EdgeClamp'
		});

		texture.readyCallback = function () {
			if (video.readyState >= 3) {
				console.log('Video ready: ' + video.videoWidth + ', ' + video.videoHeight);
				video.width = video.videoWidth;
				video.height = video.videoHeight;

				// set minification filter based on pow2
				if (Util.isPowerOfTwo(video.width) === false || Util.isPowerOfTwo(video.height) === false) {
					texture.generateMipmaps = false;
					texture.minFilter = 'BilinearNoMipMaps';
				}

				video.play();

				video.dataReady = true;
				return true;
			}
			return false;
		};
		texture.updateCallback = function () {
			return !video.paused;
		};

		video.crossOrigin = 'anonymous';

		video.src = videoURL;

		TextureCreator.cache[videoURL] = texture;

		return texture;
	};

	TextureCreator.prototype.loadTextureWebCam = function () {
		var video = document.createElement('video');
		video.autoplay = true;
		video.loop = true;

		var texture = new Texture(video, {
			wrapS: 'EdgeClamp',
			wrapT: 'EdgeClamp'
		});

		texture.readyCallback = function () {
			if (video.readyState >= 3) {
				console.log('WebCam video ready: ' + video.videoWidth + ', ' + video.videoHeight);
				video.width = video.videoWidth;
				video.height = video.videoHeight;

				// set minification filter based on pow2
				if (Util.isPowerOfTwo(video.width) === false || Util.isPowerOfTwo(video.height) === false) {
					texture.generateMipmaps = false;
					texture.minFilter = 'BilinearNoMipMaps';
				}

				video.dataReady = true;
				return true;
			}
			return false;
		};
		texture.updateCallback = function () {
			return !video.paused;
		};

		// Webcam video
		window.URL = window.URL || window.webkitURL;
		navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
		if (navigator.getUserMedia) {
			navigator.getUserMedia({
				video: true
			}, function (stream) {
				video.src = window.URL.createObjectURL(stream);
			}, function (e) {
				console.warn('Unable to capture WebCam. Please reload the page.', e);
			});
		} else {
			console.warn('No support for WebCam getUserMedia found!');
		}

		return texture;
	};

	/**
	 *
	 * @param {Array} imageDataArray Array containing images, image elements or image urls. [left, right, bottom, top, back, front]
	 * @returns {Texture} cubemap
	 */
	TextureCreator.prototype.loadTextureCube = function (imageDataArray, settings) {
		var texture = new Texture(null, settings);
		texture.variant = 'CUBE';
		var images = [];

		var latch = new Latch(6, function () {
			var w = images[0].width;
			var h = images[0].height;
			for (var i = 0; i < 6; i++) {
				var img = images[i];
				if (w !== img.width || h !== img.height) {
					texture.generateMipmaps = false;
					texture.minFilter = 'BilinearNoMipMaps';
					console.error('Images not all the same size!');
				}
			}

			texture.setImage(images);
			texture.image.dataReady = true;
			texture.image.width = w;
			texture.image.height = h;
		});

		var that = this;
		for (var i = 0; i < imageDataArray.length; i++) {
			/*jshint loopfunc: true */
			(function (index) {
				var queryImage = imageDataArray[index];
				if (typeof queryImage === 'string') {
					that._loader.loadImage(queryImage).then(function (image) {
						images[index] = image;
						latch.countDown();
					});
				} else {
					images[index] = queryImage;
					latch.countDown();
				}
			})(i);
		}

		return texture;
	};

	TextureCreator._globalCallback = null;
	TextureCreator._finishedLoading = function (image) {
		if (TextureCreator._globalCallback) {
			try {
				TextureCreator._globalCallback(image);
			} catch (e) {
				console.error('Error in texture callback:', e);
			}
		}
	};

	// Add Object.freeze when fast enough in browsers
	var colorInfo = new Uint8Array([255, 255, 255, 255]);
	TextureCreator.DEFAULT_TEXTURE_2D = new Texture(colorInfo, null, 1, 1);
	TextureCreator.DEFAULT_TEXTURE_CUBE = new Texture([colorInfo, colorInfo, colorInfo, colorInfo, colorInfo, colorInfo], null, 1, 1);
	TextureCreator.DEFAULT_TEXTURE_CUBE.variant = 'CUBE';

	return TextureCreator;
});
<|endoftext|>


<commit_before>define([
	'goo/math/Vector3',
	'goo/scripts/Scripts',
	'goo/scripts/ScriptUtils',
	'goo/renderer/Renderer',
	'goo/entities/SystemBus'
], function (
	Vector3,
	Scripts,
	ScriptUtils,
	Renderer,
	SystemBus
) {
	'use strict';

	function PanCamScript() {
		var fwdVector, leftVector, moveVector, calcVector, calcVector2;
		var panButton;
		var lookAtPoint;
		var mouseState;
		var devicePixelRatio;
		var listeners;

		function getTouchCenter(touches) {
			var x1 = touches[0].clientX;
			var y1 = touches[0].clientY;
			var x2 = touches[1].clientX;
			var y2 = touches[1].clientY;
			var cx = (x1 + x2) / 2;
			var cy = (y1 + y2) / 2;
			return [cx, cy];
		}

		function setup(parameters, environment) {
			panButton = ['Any', 'Left', 'Middle', 'Right'].indexOf(parameters.panButton) - 1;
			if (panButton < -1) {
				panButton = -1;
			}
			lookAtPoint = environment.goingToLookAt;
			fwdVector = new Vector3(Vector3.UNIT_Y);
			leftVector = new Vector3(Vector3.UNIT_X).invert();
			moveVector = new Vector3();
			calcVector = new Vector3();
			calcVector2 = new Vector3();

			var renderer = environment.world.gooRunner.renderer;
			devicePixelRatio = renderer._useDevicePixelRatio && window.devicePixelRatio ?
				window.devicePixelRatio / renderer.svg.currentScale : 1;

			mouseState = {
				x: 0,
				y: 0,
				ox: 0,
				oy: 0,
				dx: 0,
				dy: 0,
				down: false
			};
			listeners = {
				mousedown: function (event) {
					if (!parameters.whenUsed || environment.entity === environment.activeCameraEntity) {
						var button = event.button;
						if (button === 0) {
							if (event.altKey) {
								button = 2;
							} else if (event.shiftKey) {
								button = 1;
							}
						}
						if (button === panButton || panButton === -1) {
							mouseState.down = true;
							var x = (event.offsetX !== undefined) ? event.offsetX : event.layerX;
							var y = (event.offsetY !== undefined) ? event.offsetY : event.layerY;
							mouseState.ox = mouseState.x = x;
							mouseState.oy = mouseState.y = y;
						}
					}
				},
				mouseup: function (event) {
					var button = event.button;
					if (button === 0) {
						if (event.altKey) {
							button = 2;
						} else if (event.shiftKey) {
							button = 1;
						}
					}
					mouseState.down = false;
					mouseState.dx = mouseState.dy = 0;
				},
				mousemove: function (event) {
					if (!parameters.whenUsed || environment.entity === environment.activeCameraEntity) {
						if (mouseState.down) {
							var x = (event.offsetX !== undefined) ? event.offsetX : event.layerX;
							var y = (event.offsetY !== undefined) ? event.offsetY : event.layerY;
							mouseState.x = x;
							mouseState.y = y;
							environment.dirty = true;
						}
					}
				},
				mouseleave: function (/*event*/) {
					mouseState.down = false;
					mouseState.ox = mouseState.x;
					mouseState.oy = mouseState.y;
				},
				touchstart: function (event) {
					if (!parameters.whenUsed || environment.entity === environment.activeCameraEntity) {
						mouseState.down = (event.targetTouches.length === 2);
						if (!mouseState.down) { return; }

						var center = getTouchCenter(event.targetTouches);
						mouseState.ox = mouseState.x = center[0];
						mouseState.oy = mouseState.y = center[1];
					}
				},
				touchmove: function (event) {
					if (!parameters.whenUsed || environment.entity === environment.activeCameraEntity) {
						if (!mouseState.down) { return; }

						var center = getTouchCenter(event.targetTouches);
						mouseState.x = center[0];
						mouseState.y = center[1];
					}
				},
				touchend: function (/*event*/) {
					mouseState.down = false;
					mouseState.ox = mouseState.x;
					mouseState.oy = mouseState.y;
				}
			};
			for (var event in listeners) {
				environment.domElement.addEventListener(event, listeners[event]);
			}
			environment.dirty = true;
		}

		function update(parameters, environment) {
			if (!environment.dirty) {
				return;
			}
			mouseState.dx = mouseState.x - mouseState.ox;
			mouseState.dy = mouseState.y - mouseState.oy;
			if (mouseState.dx === 0 && mouseState.dy === 0) {
				return;
			}

			if (parameters.invertX) {
				mouseState.dx = -mouseState.dx;
			}
			if (parameters.invertY) {
				mouseState.dy = -mouseState.dy;
			}

			mouseState.ox = mouseState.x;
			mouseState.oy = mouseState.y;

			var mainCam = Renderer.mainCamera;

			var entity = environment.entity;
			var transform = entity.transformComponent.transform;

			if (lookAtPoint && mainCam) {
				if (lookAtPoint.equals(mainCam.translation)) {
					return;
				}
				var camera = entity.cameraComponent.camera;
				var width = environment.domElement.offsetWidth;
				var height = environment.domElement.offsetHeight;
				mainCam.getScreenCoordinates(lookAtPoint, width, height, calcVector);
				calcVector.sub_d(
					mouseState.dx,/// (environment.viewportWidth/devicePixelRatio),
					mouseState.dy,/// (environment.viewportHeight/devicePixelRatio),
					0
				);
				mainCam.getWorldCoordinates(
					calcVector.x,
					calcVector.y,
					width,
					height,
					calcVector.z,
					calcVector
				);
				lookAtPoint.setv(calcVector);

			} else {
				calcVector.setv(fwdVector).scale(mouseState.dy);
				calcVector2.setv(leftVector).scale(mouseState.dx);

				//! schteppe: use world coordinates for both by default?
				//if (parameters.screenMove){
					// In the case of screenMove, we normalize the camera movement
					// to the near plane instead of using pixels. This makes the parallel
					// camera map mouse world movement to camera movement 1-1
					if (entity.cameraComponent && entity.cameraComponent.camera) {
						var camera = entity.cameraComponent.camera;
						calcVector.scale((camera._frustumTop - camera._frustumBottom) / environment.viewportHeight);
						calcVector2.scale((camera._frustumRight - camera._frustumLeft) / environment.viewportWidth);
					}
				//}
				calcVector.addv(calcVector2);
				transform.rotation.applyPost(calcVector);
				//if (!parameters.screenMove){
					// panSpeed should be 1 in the screenMove case, to make movement sync properly
					calcVector.scale(parameters.panSpeed);
				//}
				entity.transformComponent.transform.translation.addv(calcVector);
				entity.transformComponent.setUpdated();
				//environment.dirty = false;
			}
			SystemBus.emit('goo.cameraPositionChanged', {
				translation: transform.translation.data,
				lookAtPoint: lookAtPoint ? lookAtPoint.data : null,
				id: entity.id
			});
		}

		function cleanup(parameters, environment) {
			for (var event in listeners) {
				environment.domElement.removeEventListener(event, listeners[event]);
			}
		}

		return {
			setup: setup,
			update: update,
			cleanup: cleanup
		};
	}

	PanCamScript.externals = {
		key: 'PanCamControlScript',
		name: 'PanCamera Control',
		description: 'Enables camera to pan around a point in 3D space using the mouse',
		parameters: [{
			key: 'whenUsed',
			type: 'boolean',
			'default': true
		}, {
			key: 'panButton',
			name: 'Pan button',
			description: 'Only pan with this button',
			type: 'string',
			control: 'select',
			'default': 'Any',
			options: ['Any', 'Left', 'Middle', 'Right']
		}, {
			key: 'panSpeed',
			type: 'float',
			'default': 0.005,
			scale: 0.001,
			decimals: 3
		}, {
			key: 'screenMove',
			type: 'boolean',
			'default': false,
			description: 'Syncs camera movement with mouse world position 1-1, needed for parallel camera.'
		}]
	};

	return PanCamScript;
});<commit_msg>Bugfix on Pancamscript #1408<commit_after>define([
	'goo/math/Vector3',
	'goo/scripts/Scripts',
	'goo/scripts/ScriptUtils',
	'goo/renderer/Renderer',
	'goo/entities/SystemBus',
	'goo/renderer/Camera'
], function (
	Vector3,
	Scripts,
	ScriptUtils,
	Renderer,
	SystemBus,
	Camera
) {
	'use strict';

	function PanCamScript() {
		var fwdVector, leftVector, moveVector, calcVector, calcVector2;
		var panButton;
		var lookAtPoint;
		var mouseState;
		var devicePixelRatio;
		var listeners;

		function getTouchCenter(touches) {
			var x1 = touches[0].clientX;
			var y1 = touches[0].clientY;
			var x2 = touches[1].clientX;
			var y2 = touches[1].clientY;
			var cx = (x1 + x2) / 2;
			var cy = (y1 + y2) / 2;
			return [cx, cy];
		}

		function setup(parameters, environment) {
			panButton = ['Any', 'Left', 'Middle', 'Right'].indexOf(parameters.panButton) - 1;
			if (panButton < -1) {
				panButton = -1;
			}
			lookAtPoint = environment.goingToLookAt;
			fwdVector = new Vector3(Vector3.UNIT_Y);
			leftVector = new Vector3(Vector3.UNIT_X).invert();
			moveVector = new Vector3();
			calcVector = new Vector3();
			calcVector2 = new Vector3();

			var renderer = environment.world.gooRunner.renderer;
			devicePixelRatio = renderer._useDevicePixelRatio && window.devicePixelRatio ?
				window.devicePixelRatio / renderer.svg.currentScale : 1;

			mouseState = {
				x: 0,
				y: 0,
				ox: 0,
				oy: 0,
				dx: 0,
				dy: 0,
				down: false
			};
			listeners = {
				mousedown: function (event) {
					if (!parameters.whenUsed || environment.entity === environment.activeCameraEntity) {
						var button = event.button;
						if (button === 0) {
							if (event.altKey) {
								button = 2;
							} else if (event.shiftKey) {
								button = 1;
							}
						}
						if (button === panButton || panButton === -1) {
							mouseState.down = true;
							var x = (event.offsetX !== undefined) ? event.offsetX : event.layerX;
							var y = (event.offsetY !== undefined) ? event.offsetY : event.layerY;
							mouseState.ox = mouseState.x = x;
							mouseState.oy = mouseState.y = y;
						}
					}
				},
				mouseup: function (event) {
					var button = event.button;
					if (button === 0) {
						if (event.altKey) {
							button = 2;
						} else if (event.shiftKey) {
							button = 1;
						}
					}
					mouseState.down = false;
					mouseState.dx = mouseState.dy = 0;
				},
				mousemove: function (event) {
					if (!parameters.whenUsed || environment.entity === environment.activeCameraEntity) {
						if (mouseState.down) {
							var x = (event.offsetX !== undefined) ? event.offsetX : event.layerX;
							var y = (event.offsetY !== undefined) ? event.offsetY : event.layerY;
							mouseState.x = x;
							mouseState.y = y;
							environment.dirty = true;
						}
					}
				},
				mouseleave: function (/*event*/) {
					mouseState.down = false;
					mouseState.ox = mouseState.x;
					mouseState.oy = mouseState.y;
				},
				touchstart: function (event) {
					if (!parameters.whenUsed || environment.entity === environment.activeCameraEntity) {
						mouseState.down = (event.targetTouches.length === 2);
						if (!mouseState.down) { return; }

						var center = getTouchCenter(event.targetTouches);
						mouseState.ox = mouseState.x = center[0];
						mouseState.oy = mouseState.y = center[1];
					}
				},
				touchmove: function (event) {
					if (!parameters.whenUsed || environment.entity === environment.activeCameraEntity) {
						if (!mouseState.down) { return; }

						var center = getTouchCenter(event.targetTouches);
						mouseState.x = center[0];
						mouseState.y = center[1];
					}
				},
				touchend: function (/*event*/) {
					mouseState.down = false;
					mouseState.ox = mouseState.x;
					mouseState.oy = mouseState.y;
				}
			};
			for (var event in listeners) {
				environment.domElement.addEventListener(event, listeners[event]);
			}
			environment.dirty = true;
		}

		function update(parameters, environment) {
			if (!environment.dirty) {
				return;
			}
			mouseState.dx = mouseState.x - mouseState.ox;
			mouseState.dy = mouseState.y - mouseState.oy;
			if (mouseState.dx === 0 && mouseState.dy === 0) {
				environment.dirty = !!environment.lookAtPoint;
				return;
			}

			if (parameters.invertX) {
				mouseState.dx = -mouseState.dx;
			}
			if (parameters.invertY) {
				mouseState.dy = -mouseState.dy;
			}

			mouseState.ox = mouseState.x;
			mouseState.oy = mouseState.y;

			var mainCam = Renderer.mainCamera;

			var entity = environment.entity;
			var transform = entity.transformComponent.transform;

			var camera = entity.cameraComponent.camera;
			if (lookAtPoint && mainCam) {
				if (lookAtPoint.equals(mainCam.translation)) {
					return;
				}
				var width = environment.domElement.offsetWidth;
				var height = environment.domElement.offsetHeight;
				mainCam.getScreenCoordinates(lookAtPoint, width, height, calcVector);
				calcVector.sub_d(
					mouseState.dx,/// (environment.viewportWidth/devicePixelRatio),
					mouseState.dy,/// (environment.viewportHeight/devicePixelRatio),
					0
				);
				mainCam.getWorldCoordinates(
					calcVector.x,
					calcVector.y,
					width,
					height,
					calcVector.z,
					calcVector
				);
				lookAtPoint.setv(calcVector);

			} else {
				calcVector.setv(fwdVector).scale(mouseState.dy);
				calcVector2.setv(leftVector).scale(mouseState.dx);

				//! schteppe: use world coordinates for both by default?
				//if (parameters.screenMove){
					// In the case of screenMove, we normalize the camera movement
					// to the near plane instead of using pixels. This makes the parallel
					// camera map mouse world movement to camera movement 1-1
				if (entity.cameraComponent && entity.cameraComponent.camera) {
					var camera = entity.cameraComponent.camera;
					calcVector.scale((camera._frustumTop - camera._frustumBottom) / environment.viewportHeight);
					calcVector2.scale((camera._frustumRight - camera._frustumLeft) / environment.viewportWidth);
				}
				//}
				calcVector.addv(calcVector2);
				transform.rotation.applyPost(calcVector);
				//if (!parameters.screenMove){
					// panSpeed should be 1 in the screenMove case, to make movement sync properly
				if (camera.projectionMode === Camera.Perspective) {
					// RB: I know, very arbitrary but looks ok
					calcVector.scale(parameters.panSpeed * 60);
				} else {
					calcVector.scale(parameters.panSpeed);
				}
				entity.transformComponent.transform.translation.addv(calcVector);
				entity.transformComponent.setUpdated();
				environment.dirty = false;
			}
			SystemBus.emit('goo.cameraPositionChanged', {
				translation: transform.translation.data,
				lookAtPoint: lookAtPoint ? lookAtPoint.data : null,
				id: entity.id
			});
		}

		function cleanup(parameters, environment) {
			for (var event in listeners) {
				environment.domElement.removeEventListener(event, listeners[event]);
			}
		}

		return {
			setup: setup,
			update: update,
			cleanup: cleanup
		};
	}

	PanCamScript.externals = {
		key: 'PanCamControlScript',
		name: 'PanCamera Control',
		description: 'Enables camera to pan around a point in 3D space using the mouse',
		parameters: [{
			key: 'whenUsed',
			type: 'boolean',
			'default': true
		}, {
			key: 'panButton',
			name: 'Pan button',
			description: 'Only pan with this button',
			type: 'string',
			control: 'select',
			'default': 'Any',
			options: ['Any', 'Left', 'Middle', 'Right']
		}, {
			key: 'panSpeed',
			type: 'float',
			'default': 0.005,
			scale: 0.001,
			decimals: 3
		}/*, {
			key: 'screenMove',
			type: 'boolean',
			'default': false,
			description: 'Syncs camera movement with mouse world position 1-1, needed for parallel camera.'
		}*/]
	};

	return PanCamScript;
});<|endoftext|>


<commit_before>(function (root, factory) {
  var name = 'mag$';
  if (typeof define === 'function' && define.amd) {
    define(['mag',' jquery'], function (mag, $) {
        return (root[name] = factory(mag, $));
    });
  } else if (typeof exports === 'object') {
    module.exports = factory(require('./mag'), require('jquery'));
  } else {
    root[name] = factory(Mag, $);
  }
}(this, function (Mag, $) {

  var normalizeOffsets = function (e) {
    var offset = $(e.target).offset();
    return {
      x: e.pageX - offset.left,
      y: e.pageY - offset.top
    };
  };

  var ratioOffsets = function (e) {
    var normOff = normalizeOffsets(e);
    var $target = $(e.target);
    return {
      x: normOff.x / $target.width(),
      y: normOff.y / $target.height()
    }
  };

  var ratioOffsetsFor = function ($target, x, y) {
    return {
      x: x / $target.width(),
      y: y / $target.height()
    }
  };

  var cssPerc = function (frac) {
    return (frac * 100) + '%';
  };

  var toCSS = function (pt) {
    var css = {};
    if (pt.x !== undefined) css.left = cssPerc(pt.x);
    if (pt.y !== undefined) css.top = cssPerc(pt.y);
    if (pt.w !== undefined) css.width = cssPerc(pt.w);
    if (pt.h !== undefined) css.height = cssPerc(pt.h);
    return css;
  };

  var mag$ = function (options) {

    return this.each(function() {

      var $el = $(this);

      var $lens;

      var defaultToggle = function (enter) {
        if (enter) {
          this.$zoomedContainer.fadeIn();
        }
        else {
          this.$zoomedContainer.fadeOut();
        }
      };

      options = $.extend({
        mode: 'inner',
        position: 'mirror',
        positionEvent: 'move',
        constrainLens: true,
        constrainZoomed: false,
        theme: 'default',
        initialShow: 'thumb',
        transclude: true,
        zoomMin: 1,
        zoomMax: 10,
        zoomRate: 0.2,
        toggle: defaultToggle
      }, options);

      if (options.mode === 'outer' && options.showLens == null) {
        options.showLens = true;
      }

      if (options.transclude) {
        if (! options.content) {
          options.content = $el.find('[mag-zoomed], [data-mag-zoomed]').html();
        }
        if (! options.contentThumb) {
          options.contentThumb = $el.find('[mag-thumb], [data-mag-thumb]').html();
        }
      }

      if (! options.content) {
        options.content = $el.html();
      }

      if (! options.contentThumb) {
        options.contentThumb = options.content;
      }

      var mag = new Mag({
        zoomMin: options.zoomMin,
        zoomMax: options.zoomMax,
        constrainLens: options.constrainLens,
        constrainZoomed: options.constrainZoomed
      });

      var model = {
        mode: 'overflow',
        focus: {
          x: 0.5,
          y: 0.5
        },
        zoom: 2,
        lens: {
          w: 0,
          h: 0
        }
      };

      var render = function () {
        var lens, zoomed;
        if ($lens) {
          lens = model.lazyLens;
          var lensCSS = toCSS(lens);
          $lens.css(lensCSS);
        }
        zoomed = model.lazyZoomed;
        var zoomedCSS = toCSS(zoomed);
        $zoomed.css(zoomedCSS);
      };

      $el.empty();
      $el.addClass('mag-host');

      if (options.mode) {
        $el.attr('mag-mode', options.mode);
      }

      if (options.theme) {
        $el.attr('mag-theme', 'default');
      }

      if (options.positionEvent) {
        $el.attr('mag-position-event', options.positionEvent);
      }

      if (options.showLens) {
        $lens = $('

');
        $el.append($lens);
      }

      var $noflow = $('

');
      $el.append($noflow);

      var $zone = $('

');
      $noflow.append($zone);

      var $zoomedContainer;
      if (options.mode === 'inner') {
        $zoomedContainer = $noflow;
        $noflow.addClass('mag-zoomed-bg');
      }
      else if (options.mode === 'outer') {
        if (! options.zoomedContainer) {
          throw new Error("Required 'zoomedContainer' option.");
        }
        $zoomedContainer = $(options.zoomedContainer);
        $zoomedContainer.addClass('mag-zoomed-container');
        $zoomedContainer.addClass('mag-zoomed-bg');
        $zoomedContainer.attr('mag-theme', options.theme);
      }
      else {
        throw new Error("Invalid 'mode' option.");
      }

      var $thumb = $('

');
      $thumb.html(options.contentThumb);
      $el.append($thumb);

      var $zoomed = $('

');
      $zoomed.html(options.content);
      $zoomedContainer.append($zoomed);

      this.$el = $el;
      this.$zone = $zone;
      this.$noflow = $noflow;
      this.$thumb = $thumb;
      this.$zoomed = $zoomed;
      this.$zoomedContainer = $zoomedContainer;

      if (options.toggle) {
        if (options.initialShow === 'thumb') {
          $zoomedContainer.hide();
        }
        else if (options.initialShow === 'zoomed') {
          //
        }
        else {
          throw new Error("Invalid 'initialShow' option.");
        }

        $el.on('mouseenter', function () {
          if ($.isFunction(options.toggle)) {
            options.toggle.call(this, true);
          }
        });

        $el.on('mouseleave', function () {
          if ($.isFunction(options.toggle)) {
            options.toggle.call(this, false);
          }
        });
      }

      mag.compute(model);

      model.lazyFocus = {
        x: model.focus.x,
        y: model.focus.y
      };
      model.lazyLens = {
        x: model.lens.x,
        y: model.lens.y,
        w: model.lens.w,
        h: model.lens.h
      };
      model.lazyZoomed = {
        x: model.zoomed.x,
        y: model.zoomed.y,
        w: model.zoomed.w,
        h: model.zoomed.h
      };
      model.lazyZoom = model.zoom;

      render();

      var lazyRate = 1;
      var renderLoopIntervalTime = 100;
      var dragRate = 0.2;
      var zoomRate = options.zoomRate;

      var approach = function (rate, dest, src, props, srcProps) {
        srcProps = srcProps ? srcProps : props;
        if (! $.isArray(props)) {
          props = [props];
          srcProps = [srcProps];
        }
        for (var i = 0, m = props.length; i < m; ++i) {
          var prop = props[i];
          var srcProp = srcProps[i];
          var diff = src[srcProp] - dest[prop];
          dest[prop] += diff * rate;
        }
      };

      var renderLoop = function () {
        approach(lazyRate, model.lazyFocus, model.focus, 'x');
        approach(lazyRate, model.lazyFocus, model.focus, 'y');
        approach(lazyRate, model.lazyZoomed, model.zoomed, ['x', 'y', 'w', 'h']);
        approach(lazyRate, model.lazyLens, model.lens, ['x', 'y', 'w', 'h']);
        approach(lazyRate, model, model, 'lazyZoom', 'zoom');

        render();
      };

      var adjustForMirror = function (focus) {
        model.focus.x = focus.x;
        model.focus.y = focus.y;
        mag.compute(model);
      };

      if (options.position === 'mirror') {
        if (options.positionEvent === 'move') {
          lazyRate = 0.2;
          renderLoopIntervalTime = 20;

          $zone.on('mousemove', function(e){
            var ratios = ratioOffsets(e);
            adjustForMirror(ratios);
          });
        }
        else if (options.positionEvent === 'hold') {
          lazyRate = 0.2;
          renderLoopIntervalTime = 20;

          $zone.drag('start', function () {
            dragging = true;
            $el.addClass('mag--dragging');
          });

          $zone.drag('end', function () {
            dragging = false;
            $el.removeClass('mag--dragging');
          });

          $zone.drag(function( e, dd ){
            var offset = $zone.offset();
            var ratios = ratioOffsetsFor($zone, e.pageX - offset.left, e.pageY - offset.top);
            adjustForMirror(ratios);
          });
        }
        else {
          throw new Error("Invalid 'positionEvent' option.");
        }
      }
      else if (options.position === 'joystick') {

        var joystickIntervalTime = 50;

        var dragging = false;

        var ratios = {
          x: model.focus.x,
          y: model.focus.y
        };

        if (options.positionEvent === 'move') {
          dragging = true;
          lazyRate = 0.5;
          renderLoopIntervalTime = 20;

          $zone.on('mousemove', function(e){
            ratios = ratioOffsets(e);
          });
        }
        else if (options.positionEvent === 'hold') {
          lazyRate = 0.5;
          renderLoopIntervalTime = 20;

          $zone.drag('start', function () {
            dragging = true;
            $el.addClass('mag--dragging');
          });

          $zone.drag('end', function () {
            dragging = false;
            $el.removeClass('mag--dragging');
          });

          $zone.drag(function( e, dd ){

            var offset = $zone.offset();
            ratios = ratioOffsetsFor($zone, e.pageX - offset.left, e.pageY - offset.top);
          });
        }
        else {
          throw new Error("Invalid 'positionEvent' option.");
        }

        var joystickInterval = setInterval(function () {
          if (! dragging) return;

          var focus = model.focus;

          var adjustedDragRate = dragRate;
          focus.x += (ratios.x - 0.5) * adjustedDragRate;
          focus.y += (ratios.y - 0.5) * adjustedDragRate;
          mag.compute(model);
        }, joystickIntervalTime);

      }
      else {
        throw new Error("Invalid 'position' option.");
      }

      $zone.on('mousewheel', function (e) {
        e.preventDefault();

        var zoom = model.zoom;
        var delta = e.deltaY;
        delta = delta > 0 ? delta + zoomRate : Math.abs(delta) - zoomRate;
        zoom *= delta;
        model.zoom = zoom;
        mag.compute(model);
      });

      var renderLoopInterval = setInterval(renderLoop, renderLoopIntervalTime);

    });
  };

  $.fn.mag = mag$;

  return mag$;
}));
<commit_msg>lazy has own mag model, approach only focus & zoom<commit_after>(function (root, factory) {
  var name = 'mag$';
  if (typeof define === 'function' && define.amd) {
    define(['mag',' jquery'], function (mag, $) {
        return (root[name] = factory(mag, $));
    });
  } else if (typeof exports === 'object') {
    module.exports = factory(require('./mag'), require('jquery'));
  } else {
    root[name] = factory(Mag, $);
  }
}(this, function (Mag, $) {

  var normalizeOffsets = function (e) {
    var offset = $(e.target).offset();
    return {
      x: e.pageX - offset.left,
      y: e.pageY - offset.top
    };
  };

  var ratioOffsets = function (e) {
    var normOff = normalizeOffsets(e);
    var $target = $(e.target);
    return {
      x: normOff.x / $target.width(),
      y: normOff.y / $target.height()
    }
  };

  var ratioOffsetsFor = function ($target, x, y) {
    return {
      x: x / $target.width(),
      y: y / $target.height()
    }
  };

  var cssPerc = function (frac) {
    return (frac * 100) + '%';
  };

  var toCSS = function (pt) {
    var css = {};
    if (pt.x !== undefined) css.left = cssPerc(pt.x);
    if (pt.y !== undefined) css.top = cssPerc(pt.y);
    if (pt.w !== undefined) css.width = cssPerc(pt.w);
    if (pt.h !== undefined) css.height = cssPerc(pt.h);
    return css;
  };

  var mag$ = function (options) {

    return this.each(function() {

      var $el = $(this);

      var $lens;

      var defaultToggle = function (enter) {
        if (enter) {
          this.$zoomedContainer.fadeIn();
        }
        else {
          this.$zoomedContainer.fadeOut();
        }
      };

      options = $.extend({
        mode: 'inner',
        position: 'mirror',
        positionEvent: 'move',
        constrainLens: true,
        constrainZoomed: false,
        theme: 'default',
        initialShow: 'thumb',
        transclude: true,
        zoomMin: 1,
        zoomMax: 10,
        zoomRate: 0.2,
        toggle: defaultToggle
      }, options);

      if (options.mode === 'outer' && options.showLens == null) {
        options.showLens = true;
      }

      if (options.transclude) {
        if (! options.content) {
          options.content = $el.find('[mag-zoomed], [data-mag-zoomed]').html();
        }
        if (! options.contentThumb) {
          options.contentThumb = $el.find('[mag-thumb], [data-mag-thumb]').html();
        }
      }

      if (! options.content) {
        options.content = $el.html();
      }

      if (! options.contentThumb) {
        options.contentThumb = options.content;
      }

      var mag = new Mag({
        zoomMin: options.zoomMin,
        zoomMax: options.zoomMax,
        constrainLens: options.constrainLens,
        constrainZoomed: options.constrainZoomed
      });

      var magLazy = new Mag({
        zoomMin: options.zoomMin,
        zoomMax: options.zoomMax,
        constrainLens: options.constrainLens,
        constrainZoomed: options.constrainZoomed
      });

      var model = {
        focus: {
          x: 0.5,
          y: 0.5
        },
        zoom: 2,
        lens: {
          w: 0,
          h: 0
        }
      };
      mag.compute(model);

      var modelLazy = {
        focus: {
          x: 0.5,
          y: 0.5
        },
        zoom: 2,
        lens: {
          w: 0,
          h: 0
        }
      };
      magLazy.compute(modelLazy);

      var render = function () {
        var lens, zoomed;
        if ($lens) {
          lens = modelLazy.lens;
          var lensCSS = toCSS(lens);
          $lens.css(lensCSS);
        }
        zoomed = modelLazy.zoomed;
        var zoomedCSS = toCSS(zoomed);
        $zoomed.css(zoomedCSS);
      };

      $el.empty();
      $el.addClass('mag-host');

      if (options.mode) {
        $el.attr('mag-mode', options.mode);
      }

      if (options.theme) {
        $el.attr('mag-theme', 'default');
      }

      if (options.positionEvent) {
        $el.attr('mag-position-event', options.positionEvent);
      }

      if (options.showLens) {
        $lens = $('

');
        $el.append($lens);
      }

      var $noflow = $('

');
      $el.append($noflow);

      var $zone = $('

');
      $noflow.append($zone);

      var $zoomedContainer;
      if (options.mode === 'inner') {
        $zoomedContainer = $noflow;
        $noflow.addClass('mag-zoomed-bg');
      }
      else if (options.mode === 'outer') {
        if (! options.zoomedContainer) {
          throw new Error("Required 'zoomedContainer' option.");
        }
        $zoomedContainer = $(options.zoomedContainer);
        $zoomedContainer.addClass('mag-zoomed-container');
        $zoomedContainer.addClass('mag-zoomed-bg');
        $zoomedContainer.attr('mag-theme', options.theme);
      }
      else {
        throw new Error("Invalid 'mode' option.");
      }

      var $thumb = $('

');
      $thumb.html(options.contentThumb);
      $el.append($thumb);

      var $zoomed = $('

');
      $zoomed.html(options.content);
      $zoomedContainer.append($zoomed);

      this.$el = $el;
      this.$zone = $zone;
      this.$noflow = $noflow;
      this.$thumb = $thumb;
      this.$zoomed = $zoomed;
      this.$zoomedContainer = $zoomedContainer;

      if (options.toggle) {
        if (options.initialShow === 'thumb') {
          $zoomedContainer.hide();
        }
        else if (options.initialShow === 'zoomed') {
          //
        }
        else {
          throw new Error("Invalid 'initialShow' option.");
        }

        $el.on('mouseenter', function () {
          if ($.isFunction(options.toggle)) {
            options.toggle.call(this, true);
          }
        });

        $el.on('mouseleave', function () {
          if ($.isFunction(options.toggle)) {
            options.toggle.call(this, false);
          }
        });
      }

      render();

      var lazyRate = 0.5;
      var renderLoopIntervalTime = 20;
      var dragRate = 0.2;
      var zoomRate = options.zoomRate;

      var approach = function (rate, dest, src, props, srcProps) {
        srcProps = srcProps ? srcProps : props;
        if (! $.isArray(props)) {
          props = [props];
          srcProps = [srcProps];
        }
        for (var i = 0, m = props.length; i < m; ++i) {
          var prop = props[i];
          var srcProp = srcProps[i];
          var diff = src[srcProp] - dest[prop];
          dest[prop] += diff * rate;
        }
      };

      var renderLoop = function () {
        approach(lazyRate, modelLazy.focus, model.focus, 'x');
        approach(lazyRate, modelLazy.focus, model.focus, 'y');
        approach(lazyRate, modelLazy, model, 'zoom');

        magLazy.compute(modelLazy);

        render();
      };

      var adjustForMirror = function (focus) {
        model.focus.x = focus.x;
        model.focus.y = focus.y;
        mag.compute(model);
      };

      if (options.position === 'mirror') {
        if (options.positionEvent === 'move') {
          lazyRate = 0.2;

          $zone.on('mousemove', function(e){
            var ratios = ratioOffsets(e);
            adjustForMirror(ratios);
          });
        }
        else if (options.positionEvent === 'hold') {
          lazyRate = 0.2;

          $zone.drag('start', function () {
            dragging = true;
            $el.addClass('mag--dragging');
          });

          $zone.drag('end', function () {
            dragging = false;
            $el.removeClass('mag--dragging');
          });

          $zone.drag(function( e, dd ){
            var offset = $zone.offset();
            var ratios = ratioOffsetsFor($zone, e.pageX - offset.left, e.pageY - offset.top);
            adjustForMirror(ratios);
          });
        }
        else {
          throw new Error("Invalid 'positionEvent' option.");
        }
      }
      else if (options.position === 'joystick') {

        var joystickIntervalTime = 50;

        var dragging = false;

        var ratios = {
          x: model.focus.x,
          y: model.focus.y
        };

        if (options.positionEvent === 'move') {
          dragging = true;
          lazyRate = 0.5;

          $zone.on('mousemove', function(e){
            ratios = ratioOffsets(e);
          });
        }
        else if (options.positionEvent === 'hold') {
          lazyRate = 0.5;

          $zone.drag('start', function () {
            dragging = true;
            $el.addClass('mag--dragging');
          });

          $zone.drag('end', function () {
            dragging = false;
            $el.removeClass('mag--dragging');
          });

          $zone.drag(function( e, dd ){

            var offset = $zone.offset();
            ratios = ratioOffsetsFor($zone, e.pageX - offset.left, e.pageY - offset.top);
          });
        }
        else {
          throw new Error("Invalid 'positionEvent' option.");
        }

        var joystickInterval = setInterval(function () {
          if (! dragging) return;

          var focus = model.focus;

          var adjustedDragRate = dragRate;
          focus.x += (ratios.x - 0.5) * adjustedDragRate;
          focus.y += (ratios.y - 0.5) * adjustedDragRate;
          mag.compute(model);
        }, joystickIntervalTime);

      }
      else {
        throw new Error("Invalid 'position' option.");
      }

      $zone.on('mousewheel', function (e) {
        e.preventDefault();

        var zoom = model.zoom;
        var delta = e.deltaY;
        delta = delta > 0 ? delta + zoomRate : Math.abs(delta) - zoomRate;
        zoom *= delta;
        model.zoom = zoom;
        mag.compute(model);
      });

      var renderLoopInterval = setInterval(renderLoop, renderLoopIntervalTime);

    });
  };

  $.fn.mag = mag$;

  return mag$;
}));
<|endoftext|>


<commit_before>
var jsonPathCompiler = (function () {

   /**
    * tokenExprs is an array of pairs. Each pair contains a regular expression for matching a
    * jsonpath language feature and a function for parsing that feature.
    */
   var tokenExprs = (function(){
      /**
       * Expression for a named path node.
       *    foo
       *    ["foo"]
       *    [2]
       *
       * @returns {Object|false} either the object that was found, or false if nothing was found
       */
      function namedNodeExpr(previousExpr, capturing, name, pathStack, nodeStack, stackIndex ) {

         if( pathStack[stackIndex] != name ) {
            return false;
         }

         var previous = previousExpr(pathStack, nodeStack, stackIndex-1);

         return previous && returnValue(capturing, previous, nodeStack, stackIndex);
      }

      /**
       * Expression for:
       *    *
       *    [*]
       *
       * @returns {Object|false} either the object that was found, or false if nothing was found
       */
      function anyNodeExpr(previousExpr, capturing, _nameless, pathStack, nodeStack, stackIndex ){
         var previous = previousExpr(pathStack, nodeStack, stackIndex-1);

         return previous && returnValue(capturing, previous, nodeStack, stackIndex);
      }

      /**
       * Expression for .. (double dot) token
       *
       * @returns {Object|false} either the object that was found, or false if nothing was found
       */
      function multipleUnnamedNodesExpr(previousExpr, _neverCaptures, _nameless, pathStack, nodeStack, stackIndex ) {

         // past the start, not a match:
         if( stackIndex < -1 ) {
            return false;
         }

         return stackIndex == -1 || // -1 is the root
                previousExpr(pathStack, nodeStack, stackIndex) ||
                multipleUnnamedNodesExpr(previousExpr, _neverCaptures, _nameless, pathStack, nodeStack, stackIndex-1);
      }

      /**
       * Expression for $ - matches only the root element of the json
       *
       * @returns {Object|false} either the object that was found, or false if nothing was found
       */
      function rootExpr(_cantHaveExprsBeforeRoot, capturing, _nameless, pathStack, nodeStack, stackIndex ){
         return stackIndex == -1 && returnValue(capturing, true, nodeStack, stackIndex);
      }

      /**
       * Expression for . does no tests since . is just a seperator. Just passes through to the
       * next function in the chain.
       *
       * @returns {Object|false} either the object that was found, or false if nothing was found
       */
      function passthroughExpr(previousExpr, _neverCaptures, _nameless, pathStack, nodeStack, stackIndex) {
         return previousExpr(pathStack, nodeStack, stackIndex);
      }

      /** extraction of some common logic used by expression when they have matched.
       *  If is a capturing node, will return it's item on the nodestack. Otherwise, will return the item
       *  from the nodestack given by the previous expression, or true if none
       */
      function returnValue(capturing, previousExprEvaluation, nodeStack, stackIndex) {
         return capturing? nodeStack[stackIndex+1] : (previousExprEvaluation || true);
      }

      /**
       * Each of the sub-arrays has at index 0 a pattern matching the token.
       * At index 1 is the expression function to return a function to parse that expression
       */
      function tokenExpr(pattern, parser) {
         return {pattern:pattern, parser:parser};
      }

      return [
         tokenExpr(/^(\$?)(\w+)/        , namedNodeExpr),
         tokenExpr(/^(\$?)\[(\d+)\]/    , namedNodeExpr),
         tokenExpr(/^(\$?)\["(\w+)"\]/  , namedNodeExpr),
         tokenExpr(/^\.\./              , multipleUnnamedNodesExpr),
         tokenExpr(/^(\$?)!/            , rootExpr),
         tokenExpr(/^(\$?)\*/           , anyNodeExpr),
         tokenExpr(/^(\$?)\[\*\]/       , anyNodeExpr),
         tokenExpr(/^\./                , passthroughExpr)
      ];
   })(); // end of tokenExprs definition

   /**
    * Given a parser for a token, parse the statement ending in that token against a pathStack
    *
    * @returns {Object|false} either the object that was found, or false if nothing was found
    */
   function statement(expr, pathStack, nodeStack){

      var exprMatch = expr(pathStack, nodeStack, pathStack.length-1);

      // Returning exactly true indicates that there has been a match but no node is captured.
      // By default, the node at the top of the stack gets returned. Just like in css4 selector
      // spec, if there is no $, the last node in the selector is the one being styled.

      return exprMatch === true ? lastOf(nodeStack) : exprMatch;
   }

   /**
    * compile the next part of a jsonPath
    */
   function compileNextToken( jsonPath, compiledSoFar ) {

      for (var i = 0; i < tokenExprs.length; i++) {
         var tokenMatch = tokenExprs[i].pattern.exec(jsonPath);

         if(tokenMatch) {
            var capturing = !!tokenMatch[1],
                name = tokenMatch[2],
                parser = tokenExprs[i].parser.bind(null, compiledSoFar, capturing, name),
                remainingString = jsonPath.substr(tokenMatch[0].length);

            return remainingString? compileNextToken(remainingString, parser) : parser;
         }
      }

      // couldn't find any match, jsonPath is probably invalid:
      throw Error('got stuck at "' + jsonPath + '"');
   }

   /**
    * A function that, given a jsonPath string, returns a function that tests against that
    * jsonPath.
    *
    *    String -> (String[] -> Boolean)
    */
   return function (jsonPath) {
      try {
         return statement.bind(null, compileNextToken(jsonPath, function(){return true}));
      } catch( e ) {
         throw Error('Could not compile "' + jsonPath + '" because ' + e);
      }
   };

})();
<commit_msg>make jsonpath parser a little more elegant<commit_after>
var jsonPathCompiler = (function () {

   /**
    * tokenExprs is an array of pairs. Each pair contains a regular expression for matching a
    * jsonpath language feature and a function for parsing that feature.
    */
   var tokenExprs = (function(){

      /**
       * Expression for:
       *    *
       *    [*]
       *
       * @returns {Object|false} either the object that was found, or false if nothing was found
       */
      function unnamedNodeExpr(previousExpr, capturing, _nameless, pathStack, nodeStack, stackIndex ){
         var previous = previousExpr(pathStack, nodeStack, stackIndex-1);

         return previous && returnValueForMatch(capturing, previous, nodeStack, stackIndex);
      }

      /**
       * Expression for a named path node, expressed as:
       *    foo
       *    ["foo"]
       *    [2]
       *
       * @returns {Object|false} either the object that was found, or false if nothing was found
       */
      function namedNodeExpr(previousExpr, capturing, name, pathStack, nodeStack, stackIndex ) {

         // in implementation, is like unnamednodeExpr except that we need the name to match.
         // Once name matches, defer to unnamedNodeExpr:
         return (pathStack[stackIndex] == name) && unnamedNodeExpr(previousExpr, capturing, name, pathStack, nodeStack, stackIndex );
      }

      /**
       * Expression for .. (double dot) token
       *
       * @returns {Object|false} either the object that was found, or false if nothing was found
       */
      function multipleUnnamedNodesExpr(previousExpr, _neverCaptures, _nameless, pathStack, nodeStack, stackIndex ) {

         // past the start, not a match:
         if( stackIndex < -1 ) {
            return false;
         }

         return stackIndex == -1 || // -1 is the root
                previousExpr(pathStack, nodeStack, stackIndex) ||
                multipleUnnamedNodesExpr(previousExpr, _neverCaptures, _nameless, pathStack, nodeStack, stackIndex-1);
      }

      /**
       * Expression for $ - matches only the root element of the json
       *
       * @returns {Object|false} either the object that was found, or false if nothing was found
       */
      function rootExpr(_cantHaveExprsBeforeRoot, capturing, _nameless, pathStack, nodeStack, stackIndex ){
         return stackIndex == -1 && returnValueForMatch(capturing, true, nodeStack, stackIndex);
      }

      /**
       * Expression for . does no tests since . is just a separator. Just passes through to the
       * next function in the chain.
       *
       * @returns {Object|false} either the object that was found, or false if nothing was found
       */
      function passthroughExpr(previousExpr, _neverCaptures, _nameless, pathStack, nodeStack, stackIndex) {
         return previousExpr(pathStack, nodeStack, stackIndex);
      }

      /** extraction of some common logic used by expression when they have matched.
       *  If is a capturing node, will return it's item on the nodestack. Otherwise, will return the item
       *  from the nodestack given by the previous expression, or true if none
       */
      function returnValueForMatch(capturing, previousExprEvaluation, nodeStack, stackIndex) {
         return capturing? nodeStack[stackIndex+1] : (previousExprEvaluation || true);
      }

      /**
       * Each of the sub-arrays has at index 0 a pattern matching the token.
       * At index 1 is the expression function to return a function to parse that expression
       */
      function tokenExpr(pattern, parser) {
         return {pattern:pattern, parser:parser};
      }

      return [
         tokenExpr(/^(\$?)(\w+)/        , namedNodeExpr),
         tokenExpr(/^(\$?)\[(\d+)\]/    , namedNodeExpr),
         tokenExpr(/^(\$?)\["(\w+)"\]/  , namedNodeExpr),
         tokenExpr(/^\.\./              , multipleUnnamedNodesExpr),
         tokenExpr(/^(\$?)!/            , rootExpr),
         tokenExpr(/^(\$?)\*/           , unnamedNodeExpr),
         tokenExpr(/^(\$?)\[\*\]/       , unnamedNodeExpr),
         tokenExpr(/^\./                , passthroughExpr)
      ];
   })(); // end of tokenExprs definition

   /**
    * Given a parser for a token, parse the statement ending in that token against a pathStack
    *
    * @returns {Object|false} either the object that was found, or false if nothing was found
    */
   function statement(expr, pathStack, nodeStack){

      var exprMatch = expr(pathStack, nodeStack, pathStack.length-1);

      // Returning exactly true indicates that there has been a match but no node is captured.
      // By default, the node at the top of the stack gets returned. Just like in css4 selector
      // spec, if there is no $, the last node in the selector is the one being styled.

      return exprMatch === true ? lastOf(nodeStack) : exprMatch;
   }

   /**
    * Recursively compile a jsonPath into a function.
    * Each recursive call wraps the parser generated by its inner calls.
    */
   function compileNextToken( jsonPath, compiledSoFar ) {

      for (var i = 0; i < tokenExprs.length; i++) {
         var tokenMatch = tokenExprs[i].pattern.exec(jsonPath);

         if(tokenMatch) {
            var capturing = !!tokenMatch[1],
                name = tokenMatch[2],
                parser = tokenExprs[i].parser.bind(null, compiledSoFar, capturing, name),
                remainingString = jsonPath.substr(tokenMatch[0].length);

            // Return when there is nothing left to parse.
            return remainingString? compileNextToken(remainingString, parser) : parser;
         }
      }

      // couldn't find any match, jsonPath is probably invalid:
      throw Error('"' + jsonPath + '" could not be tokenised');
   }

   /**
    * A function that, given a jsonPath string, returns a function that tests against that
    * jsonPath.
    *
    *    String jsonPath -> (String[] pathStack, Object[] nodeStack) -> Boolean|Object
    *
    * The returned function returns false if there was no match, the node which was captured (using $)
    * if any expressions in the jsonPath are capturing, or true if there is a match but no capture.
    */
   return function (jsonPath) {
      try {
         return statement.bind(null, compileNextToken(jsonPath, function(){return true}));
      } catch( e ) {
         throw Error('Could not compile "' + jsonPath + '" because ' + e);
      }
   };

})();
<|endoftext|>


<commit_before>"use strict"

/**
 * model utility functions:
 *    - convert from/to client/backend data representations
 */
define(['jquery', 'model/diff'],
function($, model_diff) {

    /**
     * read by adapting from backend format
     */
    function adapt_format_read_node(n_raw) {
        var ret;

        ret = $.extend({
            'type' : n_raw['__type'].toLowerCase(), // discard all
            // labels except
            'state' : 'temp',
        }, n_raw);

        delete ret.__label_set;

        return ret;
    }

    /**
     * write by adapting to backend format
     */
    function adapt_format_write_node(n_raw) {
        var ret = $.extend({
            '__type' : n_raw.type,
        }, n_raw);

        delete ret.type;
        delete ret.state;

        return ret
    }

    /**
     * read by adapting from backend format
     */
    function adapt_format_read_link(l_raw) {
        var ret;

        ret = $.extend({
            'sourceId' : l_raw['__src'],
            'targetId' : l_raw['__dst'],
            'type' : l_raw['__type'].toLowerCase(), // discard all
            // labels except
            'state' : 'temp',
        }, l_raw);

        ret['name'] = ret['type'];

        delete ret.__src;
        delete ret.__dst;
        delete ret.__type;

        return ret;
    }

    /**
     * write by adapting to backend format
     */
    function adapt_format_write_link(l_raw) {
        var ret = $.extend({
            '__src' : l_raw.sourceId,
            '__dst' : l_raw.targetId,
            '__type' : 'textual_link',
        }, l_raw);

        delete ret.source;
        delete ret.target;
        delete ret.state;

        return ret;
    }

    /**
     * write adapt diff from node set, link set. sets may be passed by reference
     * as they are cloned
     */
    function adapt_format_write_topo_diff(n_set, l_set) {

        var new_n_set = $.extend([], n_set);
        var new_l_set = $.extend([], l_set);

        // filter out 'bubble' nodes
        new_n_set = new_n_set.filter(function(n) {
            return 'bubble' != n.type;
        });

        new_n_set = $.map(new_n_set, function(n, _) {
            return adapt_format_write_node(n);
        })

        new_l_set = $.map(new_l_set, function(l, _) {
            return adapt_format_write_link(l);
        })

        var topo_diff = new model_diff.new_topo_diff({
            node_set_add : new_n_set,
            link_set_add : new_l_set
        });
        return topo_diff;
    }

    return {
        adapt_format_read_node : adapt_format_read_node,
        adapt_format_read_link : adapt_format_read_link,
        adapt_format_write_node : adapt_format_write_node,
        adapt_format_write_link : adapt_format_write_link,
        adapt_format_write_topo_diff : adapt_format_write_topo_diff,
    }
});<commit_msg>update util read node,link<commit_after>"use strict"

/**
 * model utility functions:
 *    - convert from/to client/backend data representations
 */
define(['jquery', 'model/diff'],
function($, model_diff) {

    /**
     * read by adapting from backend format
     */
    function adapt_format_read_node(n_raw) {
        var ret;

        ret = $.extend({
            'type' : n_raw['__label_set'][0].toLowerCase(), // discard all
            // labels except
            'state' : 'temp',
        }, n_raw);

        delete ret.__label_set;

        return ret;
    }

    /**
     * write by adapting to backend format
     */
    function adapt_format_write_node(n_raw) {
        var ret = $.extend({
            '__type' : n_raw.type,
        }, n_raw);

        delete ret.type;
        delete ret.state;

        return ret
    }

    /**
     * read by adapting from backend format
     */
    function adapt_format_read_link(l_raw) {
        var ret;

        ret = $.extend({
            '__src_id' : l_raw['__src'],
            '__dst_id' : l_raw['__dst'],
            'type' : l_raw['__label_set'][0].toLowerCase(), // discard all
            // labels except
            'state' : 'temp',
        }, l_raw);

        ret['name'] = ret['type'];

        delete ret.__src;
        delete ret.__dst;
        delete ret.__type;

        return ret;
    }

    /**
     * write by adapting to backend format
     */
    function adapt_format_write_link(l_raw) {
        var ret = $.extend({
            '__src' : l_raw.sourceId,
            '__dst' : l_raw.targetId,
            '__type' : 'textual_link',
        }, l_raw);

        delete ret.source;
        delete ret.target;
        delete ret.state;

        return ret;
    }

    /**
     * write adapt diff from node set, link set. sets may be passed by reference
     * as they are cloned
     */
    function adapt_format_write_topo_diff(n_set, l_set) {

        var new_n_set = $.extend([], n_set);
        var new_l_set = $.extend([], l_set);

        // filter out 'bubble' nodes
        new_n_set = new_n_set.filter(function(n) {
            return 'bubble' != n.type;
        });

        new_n_set = $.map(new_n_set, function(n, _) {
            return adapt_format_write_node(n);
        })

        new_l_set = $.map(new_l_set, function(l, _) {
            return adapt_format_write_link(l);
        })

        var topo_diff = new model_diff.new_topo_diff({
            node_set_add : new_n_set,
            link_set_add : new_l_set
        });
        return topo_diff;
    }

    return {
        adapt_format_read_node : adapt_format_read_node,
        adapt_format_read_link : adapt_format_read_link,
        adapt_format_write_node : adapt_format_write_node,
        adapt_format_write_link : adapt_format_write_link,
        adapt_format_write_topo_diff : adapt_format_write_topo_diff,
    }
});<|endoftext|>


<commit_before>import ModelTile from './ModelTile'
import TileType from './TileType'

export default class Map {
  constructor({ game, gridSizeX, gridSizeY, tileWidth, tileHeight }) {
    this.gridSizeX = gridSizeX
    this.gridSizeY = gridSizeY
    this.tileWidth = tileWidth
    this.tileHeight = tileHeight
    this.game = game
    this.grid = []

    // World map size: top left corner x & y, width, height
    game.world.setBounds(0, 0, gridSizeX * tileWidth, gridSizeY * tileHeight)
  }

  addTileWithGridCoordinates (gx, gy, tileType) {
    var tile = new ModelTile(gx, gy, tileType)
    this.grid[gy * this.gridSizeX + gx] = tile
  }

  addTileWithPixelCoordinates (px, py, tileType) {
    var gx = this.pixelsToGridX(px)
    var gy = this.pixelsToGridY(py)
    this.addTileWithGridCoordinates(gx, gy, tileType)
  }

  getTileWithGridCoordinates (gx, gy) {
    return this.grid[gy * this.gridSizeX + gx]
  }

  getTileWithPixelCoordinates (px, py) {
    var gx = this.pixelsToGridX(px)
    var gy = this.pixelsToGridY(py)
    return this.getTileWithGridCoordinates(gx, gy)
  }

  removeTileWithGridCoordinates (gx, gy) {
    this.grid[gy * this.gridSizeX + gx] = undefined
  }

  removeTileWithPixelCoordinates (px, py) {
    this.grid[this.pixelsToGridX(py) * this.gridSizeX + this.pixelsToGridX(px)] = undefined
  }

  update () {
    // map mouse demo, deletes tile from grid
    if (this.game.input.activePointer.isDown) {
      var x = this.game.input.activePointer.position.x + this.game.game.camera.x
      var y = this.game.input.activePointer.position.y + this.game.game.camera.y

      var test = this.getTileWithPixelCoordinates(x, y)

      if (typeof test !== 'undefined') {
        this.removeTileWithPixelCoordinates(x, y)
      }
    }
  }

  // TEST DATA
  createMapHalfForestHalfWater () {
    var limit = 0.2
    var tileTypes = TileType.call()

    for (var i = 0; i < this.gridSizeY; i++) {

      if (i % 2 === 0) {
        for (var j = 0; j < this.gridSizeX; j++) {
          var r = Math.random()
          if (r > limit) {

            this.addTileWithGridCoordinates(j, i, tileTypes.grass)
          } else {
            this.addTileWithGridCoordinates(j, i, tileTypes.farm)

          }
        }
      } else {

        for (var k = 0; k < this.gridSizeX; k++) {
          var r = Math.random()

          if (r > limit) {

            this.addTileWithGridCoordinates(k, i, tileTypes.grass)
          } else {
            if (r > 0.08) {
              this.addTileWithGridCoordinates(k, i, tileTypes.forest2)
            } else {
              this.addTileWithGridCoordinates(k, i, tileTypes.water2)
            }
          }

          // last tile check
          if (i === (this.gridSizeY - 1) && k === (this.gridSizeX - 1)) {
            this.removeTileWithGridCoordinates(k, i)
          }
        }
      }
    }

  }

  // Pixel-Grid-Pixel conversion helpers

  pixelsToGridX (x) {
    return Math.floor(x / this.tileWidth)
  }

  pixelsToGridY (y) {
    return Math.floor(y / this.tileHeight)
  }

  gridToPixelsX (x) {
    return x * this.tileWidth
  }

  gridToPixelsY (y) {
    return y * this.tileHeight
  }
}
<commit_msg>limiitti (leveys - menu) hiiren vaikutusalueelle kartan puolella<commit_after>import ModelTile from './ModelTile'
import TileType from './TileType'

export default class Map {
  constructor({ game, gridSizeX, gridSizeY, tileWidth, tileHeight }) {
    this.gridSizeX = gridSizeX
    this.gridSizeY = gridSizeY
    this.tileWidth = tileWidth
    this.tileHeight = tileHeight
    this.game = game
    this.grid = []

    // World map size: top left corner x & y, width, height
    game.world.setBounds(0, 0, gridSizeX * tileWidth, gridSizeY * tileHeight)
  }

  addTileWithGridCoordinates (gx, gy, tileType) {
    var tile = new ModelTile(gx, gy, tileType)
    this.grid[gy * this.gridSizeX + gx] = tile
  }

  addTileWithPixelCoordinates (px, py, tileType) {
    var gx = this.pixelsToGridX(px)
    var gy = this.pixelsToGridY(py)
    this.addTileWithGridCoordinates(gx, gy, tileType)
  }

  getTileWithGridCoordinates (gx, gy) {
    return this.grid[gy * this.gridSizeX + gx]
  }

  getTileWithPixelCoordinates (px, py) {
    var gx = this.pixelsToGridX(px)
    var gy = this.pixelsToGridY(py)
    return this.getTileWithGridCoordinates(gx, gy)
  }

  removeTileWithGridCoordinates (gx, gy) {
    this.grid[gy * this.gridSizeX + gx] = undefined
  }

  removeTileWithPixelCoordinates (px, py) {
    this.grid[this.pixelsToGridX(py) * this.gridSizeX + this.pixelsToGridX(px)] = undefined
  }

  update () {
    // map mouse demo, deletes tile from grid
    if (this.game.input.activePointer.isDown && this.game.input.activePointer.position.x <= (this.game.camera.width - 256)) {
      var x = this.game.input.activePointer.position.x + this.game.game.camera.x
      var y = this.game.input.activePointer.position.y + this.game.game.camera.y

      var test = this.getTileWithPixelCoordinates(x, y)

      if (typeof test !== 'undefined') {
        this.removeTileWithPixelCoordinates(x, y)
      }
    }
  }

  // TEST DATA
  createMapHalfForestHalfWater () {
    var limit = 0.2
    var tileTypes = TileType.call()

    for (var i = 0; i < this.gridSizeY; i++) {

      if (i % 2 === 0) {
        for (var j = 0; j < this.gridSizeX; j++) {
          var r = Math.random()
          if (r > limit) {

            this.addTileWithGridCoordinates(j, i, tileTypes.grass)
          } else {
            this.addTileWithGridCoordinates(j, i, tileTypes.farm)

          }
        }
      } else {

        for (var k = 0; k < this.gridSizeX; k++) {
          var r = Math.random()

          if (r > limit) {

            this.addTileWithGridCoordinates(k, i, tileTypes.grass)
          } else {
            if (r > 0.08) {
              this.addTileWithGridCoordinates(k, i, tileTypes.forest2)
            } else {
              this.addTileWithGridCoordinates(k, i, tileTypes.water2)
            }
          }

          // last tile check
          if (i === (this.gridSizeY - 1) && k === (this.gridSizeX - 1)) {
            this.removeTileWithGridCoordinates(k, i)
          }
        }
      }
    }

  }

  // Pixel-Grid-Pixel conversion helpers

  pixelsToGridX (x) {
    return Math.floor(x / this.tileWidth)
  }

  pixelsToGridY (y) {
    return Math.floor(y / this.tileHeight)
  }

  gridToPixelsX (x) {
    return x * this.tileWidth
  }

  gridToPixelsY (y) {
    return y * this.tileHeight
  }
}
<|endoftext|>


<commit_before>"use strict";angular.module("ngTouchend",[]).directive("ngTouchend",function(){return{controller:function(n,t){function e(e){var u=t.attr("ng-touchend");n.$event=e,n.$apply(u)}t.bind("touchend",e)}}});
<commit_msg>Reverted<commit_after>"use strict";

angular.module("ngTouchend", []).directive("ngTouchend", function () {
  return {
    controller: function ($scope, $element, $attrs) {
      $element.bind('touchend', onTouchEnd);

      function onTouchEnd(event) {
        var method = $element.attr('ng-touchend');
        $scope.$event = event;
        $scope.$apply(method);
      };
    }
  };
});
<|endoftext|>


<commit_before>/**
    @constructor
    @param {string} id          Уникальный id класса. Важно! Это не id инстанса, у всех блоков этого класса один id.
    @param {!Object} params     Параметры блока (участвуют при построении ключа).
    @param {no.View=} parent    Родительский view.
*/
no.View = function(id, params, parent) {
    this.id = id;
    this.params = params;
    this.parent = parent || null;

    var info = this.info = no.View.info(id);

    /** @type { Object.<string, { type, view: no.View }> } */
    var views = this.views = {};

    var layout = info.layout;
    for (var view_id in layout) {
        var type = layout[ view_id ];

        var view = (type === null) ? this.subBox( view_id, params ) : this.subView( view_id, params );
        views[ view_id ] = view;
    }

    /** @type {Element} */
    this.node;

    /** @type {boolean|undefined} */
    this.status;
};

/**
    @typedef {function(
        new:no.View,
        string,
        !Object
    )}
*/
no.View.type_class;

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @typedef {{
        layout: Object,
        models: Object
    }}
*/
no.View.type_info;

// ----------------------------------------------------------------------------------------------------------------- //

/** @type { Object.<string, no.View.type_info> } */
no.View._infos = {};

/** @type { Object.<string, no.View.type_class> } */
no.View._classes = {};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @param {string} id
    @param {no.View.type_info=} info
    @param {no.View.type_class=} class_
*/
no.View.register = function(id, info, class_) {
    info = info || {};
    class_ = class_ || no.View;

    var models = info.models = info.models || [];

    var keyParams = {};
    for (var i = 0, l = info.models.length; i < l; i++) {
        var model_id = info.models[i];
        no.extend( keyParams, no.Model.info( model_id ).keyParams );
    }

    var layout = info.layout = info.layout || {};

    for (var view_id in layout) {
        if (layout[ view_id ] !== null) { // Это не box.
            var viewParams = no.View.info( view_id )._keyParams;
            for (var i = 0, l = viewParams.length; i < l; i++) {
                keyParams[ viewParams[i] ] = true;
            }
        }
    }

    info._keyParams = no.object.keys(keyParams).sort();

    no.View._infos[id] = info;
    no.View._classes[id] = class_;
};

/**
    @param {string} id
    @return {no.View.type_info}
*/
no.View.info = function(id) {
    return no.View._infos[id];
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @param {string} view_id
    @param {!Object} params
    @return {no.View}
*/
no.View.make = function(view_id, params, parent) {
    var class_ = no.View._classes[ view_id ];

    return new class_( view_id, params, parent );
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @param {string} view_id
    @return {no.View}
*/
no.View.prototype.subView = function(view_id) {
    return no.View.make( view_id, this.params, this );
};

/**
    @param {string} box_id
    @param {!Object} params
    @return {no.Box}
*/
no.View.prototype.subBox = function(box_id, params) {
    return new no.Box( box_id, params, this );
};

// ----------------------------------------------------------------------------------------------------------------- //

no.View.prototype.getKey = function() {
    return no.View.getKey( this.id, this.params, this.info );
};

/**
    @param {string} view_id
    @param {Object} params
    @return {string}
*/
no.View.getKey = function(view_id, params, info) {
    info || ( info = no.View.info(view_id) );

    var key = 'view=' + view_id;

    var keyParams = info._keyParams || [];
    for (var i = 0, l = keyParams.length; i < l; i++) {
        var pName = keyParams[i];
        var pValue = params[pName];
        if (pValue) {
            key += '&' + pName + '=' + pValue;
        }
    }

    return key;
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @return {boolean}
*/
no.View.prototype.isCached = function() {
    return this.status;
};

/**
    @return {boolean}
*/
no.View.prototype.needUpdate = function(update) {
    return !this.status;
};

// ----------------------------------------------------------------------------------------------------------------- //

no.View.prototype.hide = function() {
    this.node.style.display = 'none';
};

no.View.prototype.show = function() {
    this.node.style.display = '';
};

// ----------------------------------------------------------------------------------------------------------------- //

no.View.prototype.onhtmlinit = no.pe;

no.View.prototype.onhtmldestroy = no.pe;

no.View.prototype.onshow = no.pe;

no.View.prototype.onhide = no.pe;

no.View.prototype.onrepaint = no.pe;

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @param {string} id
    @param {no.Update} update
    @return {Object}
*/
no.View.getLayoutTree = function(id, update) {

    var pLayout = update.layout; // page layout.

    var tree = {};
    tree[id] = viewTree(id);

    return tree;

    function viewTree(id) {
        var vLayout = no.View.info(id).layout; // view layout.

        if (no.object.isEmpty( vLayout )) { // Обычный блок, без подблоков или боксов.
            return true;
        } else {
            var tree = {};

            for (var view_id in vLayout) {
                var type = vLayout[ view_id ];
                tree[ view_id ] = (type === null) ? boxTree( view_id, id ) : viewTree( view_id );
            }

            return tree;
        }
    }

    function boxTree(id, parent_id) {
        var tree = {};

        var views = pLayout[ parent_id ][ id ];
        for (var i = 0, l = views.length; i < l; i++) {
            var view_id = views[i];
            tree[ view_id ] = viewTree( view_id );
        }

        return tree;
    }

};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    Рекурсивно проходим по дереву блоков и ищем блоки, нуждающиеся в обновлении.
    Для каждого такого блока строим его layout tree и сохраняем в массиве trees.
    При этом под-дерево уже не обрабатываем (потому что оно все уже содержится в layout tree).

    В последствие, массив trees используется как "каркас" для наложения шаблона.
    Он показывает, какие блоки нужно сгенерить и какова структура этих блоков.

    @param {no.Update} update
    @param {Array} trees
*/
no.View.prototype.getUpdateTrees = function(update, trees) {
    if ( this.needUpdate(update) ) {
        trees.push( no.View.getLayoutTree( this.id, update ) );
    } else {
        this.processChildren(function(view) {
            view.getUpdateTrees(update, trees);
        });
    }
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @param {Element} node
    @param {no.Update} update
    @param {boolean} replace
*/
no.View.prototype.update = function(node, update, replace) {
    if (!this.node) {
        this.node = no.byClass( 'view-' + this.id, node )[0];
        if (!this.node) { return; }
    }

    this.processChildren(function(view) {
        view.update(node, update, false);
    });

    if (replace) {
        no.replaceNode(this.node, node);
    }

    this.status = true;
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    Рекурсивно обходим все дерево блока и применяем к каждому потомку (блоку или боксу) переданный callback.

    При этом сперва вызываем callback, а потом уже обрабатываем под-дерево.
    Если же callback возвращает false, то подблоки не обрабатываем.

    @param {function((no.View|no.Box)): (boolean|undefined)} callback
*/
no.View.prototype.processTree = function(callback) {
    var r = callback(this);
    if (r === false) { return; }

    this.processChildren(function(view) {
        view.processTree(callback);
    });
};

/**
    Применяем переданный callback ко всем "детям" блока.
    Т.е. к непосредственным потомкам этого блока.

    @param {function((no.View|no.Box))} callback
*/
no.View.prototype.processChildren = function(callback) {
    var views = this.views;
    for (var view_id in views) {
        callback( views[ view_id ] );
    }
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    Превращаем массив id-шников блоков в массив ключей блоков,
    соответствующих переданным параметрам.

    @param {Array.<string>} ids
    @param {Object} params
    @return {Array.<string>}
*/
no.View.ids2keys = function(ids, params) {
    var keys = [];
    for (var i = 0, l = ids.length; i < l; i++) {
        keys.push( no.View.getKey( ids[i], params ) );
    }
    return keys;
};

// ----------------------------------------------------------------------------------------------------------------- //

no.view2model = {};

no.view2model._cache = {};

no.view2model.addModel = function(id, key) {

};

no.view2model.removeModel = function(id, key) {

};

no.view2model.addView = function(id, key) {

};

no.view2model.removeView = function(id, key) {

};

// ----------------------------------------------------------------------------------------------------------------- //

<commit_msg>Переименования<commit_after>/**
    @constructor
    @param {string} id          Уникальный id класса. Важно! Это не id инстанса, у всех блоков этого класса один id.
    @param {!Object} params     Параметры блока (участвуют при построении ключа).
    @param {no.View=} parent    Родительский view.
*/
no.View = function(id, params, parent) {
    this.id = id;
    this.params = params;
    this.parent = parent || null;

    var info = this.info = no.View.info(id);

    /** @type { Object.<string, { type, view: no.View }> } */
    var views = this.views = {};

    var layout = info.layout;
    for (var view_id in layout) {
        var type = layout[ view_id ];

        var view = (type === null) ? this.subBox( view_id, params ) : this.subView( view_id, params );
        views[ view_id ] = view;
    }

    /** @type {Element} */
    this.node;

    /** @type {boolean|undefined} */
    this.status;
};

/**
    @typedef {function(
        new:no.View,
        string,
        !Object
    )}
*/
no.View.type_class;

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @typedef {{
        layout: Object,
        models: Object
    }}
*/
no.View.type_info;

// ----------------------------------------------------------------------------------------------------------------- //

/** @type { Object.<string, no.View.type_info> } */
no.View._infos = {};

/** @type { Object.<string, no.View.type_class> } */
no.View._classes = {};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @param {string} id
    @param {no.View.type_info=} info
    @param {no.View.type_class=} class_
*/
no.View.register = function(id, info, class_) {
    info = info || {};
    class_ = class_ || no.View;

    var models = info.models = info.models || [];

    var keyParams = {};
    for (var i = 0, l = info.models.length; i < l; i++) {
        var model_id = info.models[i];
        no.extend( keyParams, no.Model.info( model_id ).keyParams );
    }

    var layout = info.layout = info.layout || {};

    for (var view_id in layout) {
        if (layout[ view_id ] !== null) { // Это не box.
            var viewParams = no.View.info( view_id )._keyParams;
            for (var i = 0, l = viewParams.length; i < l; i++) {
                keyParams[ viewParams[i] ] = true;
            }
        }
    }

    info._keyParams = no.object.keys(keyParams).sort();

    no.View._infos[id] = info;
    no.View._classes[id] = class_;
};

/**
    @param {string} id
    @return {no.View.type_info}
*/
no.View.info = function(id) {
    return no.View._infos[id];
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @param {string} view_id
    @param {!Object} params
    @param {no.View=} parent
    @return {no.View}
*/
no.View.create = function(view_id, params, parent) {
    var class_ = no.View._classes[ view_id ];

    return new class_( view_id, params, parent );
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @param {string} view_id
    @return {no.View}
*/
no.View.prototype.subView = function(view_id) {
    return no.View.create( view_id, this.params, this );
};

/**
    @param {string} box_id
    @param {!Object} params
    @return {no.Box}
*/
no.View.prototype.subBox = function(box_id, params) {
    return new no.Box( box_id, params, this );
};

// ----------------------------------------------------------------------------------------------------------------- //

no.View.prototype.getKey = function() {
    return no.View.getKey( this.id, this.params, this.info );
};

/**
    @param {string} view_id
    @param {Object} params
    @return {string}
*/
no.View.getKey = function(view_id, params, info) {
    info || ( info = no.View.info(view_id) );

    var key = 'view=' + view_id;

    var keyParams = info._keyParams || [];
    for (var i = 0, l = keyParams.length; i < l; i++) {
        var pName = keyParams[i];
        var pValue = params[pName];
        if (pValue) {
            key += '&' + pName + '=' + pValue;
        }
    }

    return key;
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @return {boolean}
*/
no.View.prototype.isCached = function() {
    return this.status;
};

/**
    @return {boolean}
*/
no.View.prototype.needUpdate = function(update) {
    return !this.status;
};

// ----------------------------------------------------------------------------------------------------------------- //

no.View.prototype.hide = function() {
    var node = this.node;
    if (node) {
        node.style.display = 'none';
    }
};

no.View.prototype.show = function() {
    var node = this.node;
    if (node) {
        node.style.display = '';
    }
};

// ----------------------------------------------------------------------------------------------------------------- //

no.View.prototype.onhtmlinit = no.pe;

no.View.prototype.onhtmldestroy = no.pe;

no.View.prototype.onshow = no.pe;

no.View.prototype.onhide = no.pe;

no.View.prototype.onrepaint = no.pe;

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @param {string} id
    @param {no.Update} update
    @return {Object}
*/
no.View.getLayoutTree = function(id, update) {

    var pLayout = update.layout; // page layout.

    var tree = {};
    tree[id] = viewTree(id);

    return tree;

    function viewTree(id) {
        var vLayout = no.View.info(id).layout; // view layout.

        if (no.object.isEmpty( vLayout )) { // Обычный блок, без подблоков или боксов.
            return true;
        } else {
            var tree = {};

            for (var view_id in vLayout) {
                var type = vLayout[ view_id ];
                tree[ view_id ] = (type === null) ? boxTree( view_id, id ) : viewTree( view_id );
            }

            return tree;
        }
    }

    function boxTree(id, parent_id) {
        var tree = {};

        var views = pLayout[ parent_id ][ id ];
        for (var i = 0, l = views.length; i < l; i++) {
            var view_id = views[i];
            tree[ view_id ] = viewTree( view_id );
        }

        return tree;
    }

};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    Рекурсивно проходим по дереву блоков и ищем блоки, нуждающиеся в обновлении.
    Для каждого такого блока строим его layout tree и сохраняем в массиве trees.
    При этом под-дерево уже не обрабатываем (потому что оно все уже содержится в layout tree).

    В последствие, массив trees используется как "каркас" для наложения шаблона.
    Он показывает, какие блоки нужно сгенерить и какова структура этих блоков.

    @param {no.Update} update
    @param {Array} trees
*/
no.View.prototype.getUpdateTrees = function(update, trees) {
    if ( this.needUpdate(update) ) {
        trees.push( no.View.getLayoutTree( this.id, update ) );
    } else {
        this.processChildren(function(view) {
            view.getUpdateTrees(update, trees);
        });
    }
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    @param {Element} node
    @param {no.Update} update
    @param {boolean} replace
*/
no.View.prototype.update = function(node, update, replace) {
    if (!this.node) {
        this.node = no.byClass( 'view-' + this.id, node )[0];
        if (!this.node) { return; }
    }

    this.processChildren(function(view) {
        view.update(node, update, false);
    });

    if (replace) {
        no.replaceNode(this.node, node);
    }

    this.status = true;
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    Рекурсивно обходим все дерево блока и применяем к каждому потомку (блоку или боксу) переданный callback.

    При этом сперва вызываем callback, а потом уже обрабатываем под-дерево.
    Если же callback возвращает false, то подблоки не обрабатываем.

    @param {function((no.View|no.Box)): (boolean|undefined)} callback
*/
no.View.prototype.processTree = function(callback) {
    var r = callback(this);
    if (r === false) { return; }

    this.processChildren(function(view) {
        view.processTree(callback);
    });
};

/**
    Применяем переданный callback ко всем "детям" блока.
    Т.е. к непосредственным потомкам этого блока.

    @param {function((no.View|no.Box))} callback
*/
no.View.prototype.processChildren = function(callback) {
    var views = this.views;
    for (var view_id in views) {
        callback( views[ view_id ] );
    }
};

// ----------------------------------------------------------------------------------------------------------------- //

/**
    Превращаем массив id-шников блоков в массив ключей блоков,
    соответствующих переданным параметрам.

    @param {Array.<string>} ids
    @param {Object} params
    @return {Array.<string>}
*/
no.View.ids2keys = function(ids, params) {
    var keys = [];
    for (var i = 0, l = ids.length; i < l; i++) {
        keys.push( no.View.getKey( ids[i], params ) );
    }
    return keys;
};

// ----------------------------------------------------------------------------------------------------------------- //

no.view2model = {};

no.view2model._cache = {};

no.view2model.addModel = function(id, key) {

};

no.view2model.removeModel = function(id, key) {

};

no.view2model.addView = function(id, key) {

};

no.view2model.removeView = function(id, key) {

};

// ----------------------------------------------------------------------------------------------------------------- //

<|endoftext|>


<commit_before>/**
 * @author Rob Taylor [manix84@gmail.com]
 */
define('number/pad', function () {

    /**
     * Adds a repeating string to the start of a number. The number is converted to a string to accomplish this.
     * @exports number/pad
     *
     * @param  {Number} input - Number to be padded.
     * @param  {Number} [padLength] - The length you want the number to be outputted at. Defaults to "2"
     * @return {String} (EG: "01")
     */
    var pad = function (input, padLength) {
        input = String(input);
        padLength = padLength || 2;

        while (input.length < padLength) {
            input = "0" + input;
        }
        return input;
    };

    return pad;
});
<commit_msg>Adding error state when the number passed in is not actually a number.<commit_after>/**
 * @author Rob Taylor [manix84@gmail.com]
 */
define('number/pad', function () {

    /**
     * Adds a repeating string to the start of a number. The number is converted to a string to accomplish this.
     * @exports number/pad
     *
     * @param  {Number} input - Number to be padded.
     * @param  {Number} [padLength] - The length you want the number to be outputted at. Defaults to "2"
     * @return {String} (EG: "01")
     */
    var pad = function (input, padLength) {
        if (isNaN(input)) {
            throw new Error('Second argument must be an array.');
        }
        input = String(input);
        padLength = padLength || 2;

        while (input.length < padLength) {
            input = "0" + input;
        }
        return input;
    };

    return pad;
});
<|endoftext|>