;(function(undefined) { 'use strict'; if (typeof sigma === 'undefined') throw 'sigma is not declared'; sigma.utils.pkg('sigma.classes'); /** * The camera constructor. It just initializes its attributes and methods. * * @param {string} id The id. * @param {sigma.classes.graph} graph The graph. * @param {configurable} settings The settings function. * @param {?object} options Eventually some overriding options. * @return {camera} Returns the fresh new camera instance. */ sigma.classes.camera = function(id, graph, settings, options) { sigma.classes.dispatcher.extend(this); Object.defineProperty(this, 'graph', { value: graph }); Object.defineProperty(this, 'id', { value: id }); Object.defineProperty(this, 'readPrefix', { value: 'read_cam' + id + ':' }); Object.defineProperty(this, 'prefix', { value: 'cam' + id + ':' }); this.x = 0; this.y = 0; this.ratio = 1; this.angle = 0; this.isAnimated = false; this.settings = (typeof options === 'object' && options) ? settings.embedObject(options) : settings; }; /** * Updates the camera position. * * @param {object} coordinates The new coordinates object. * @return {camera} Returns the camera. */ sigma.classes.camera.prototype.goTo = function(coordinates) { if (!this.settings('enableCamera')) return this; var i, l, c = coordinates || {}, keys = ['x', 'y', 'ratio', 'angle']; for (i = 0, l = keys.length; i < l; i++) if (c[keys[i]] !== undefined) { if (typeof c[keys[i]] === 'number' && !isNaN(c[keys[i]])) this[keys[i]] = c[keys[i]]; else throw 'Value for "' + keys[i] + '" is not a number.'; } this.dispatchEvent('coordinatesUpdated'); return this; }; /** * This method takes a graph and computes for each node and edges its * coordinates relatively to the center of the camera. Basically, it will * compute the coordinates that will be used by the graphic renderers. * * Since it should be possible to use different cameras and different * renderers, it is possible to specify a prefix to put before the new * coordinates (to get something like "node.camera1_x") * * @param {?string} read The prefix of the coordinates to read. * @param {?string} write The prefix of the coordinates to write. * @param {?object} options Eventually an object of options. Those can be: * - A restricted nodes array. * - A restricted edges array. * - A width. * - A height. * @return {camera} Returns the camera. */ sigma.classes.camera.prototype.applyView = function(read, write, options) { options = options || {}; write = write !== undefined ? write : this.prefix; read = read !== undefined ? read : this.readPrefix; var nodes = options.nodes || this.graph.nodes(), edges = options.edges || this.graph.edges(); var i, l, node, relCos = Math.cos(this.angle) / this.ratio, relSin = Math.sin(this.angle) / this.ratio, nodeRatio = Math.pow(this.ratio, this.settings('nodesPowRatio')), edgeRatio = Math.pow(this.ratio, this.settings('edgesPowRatio')), xOffset = (options.width || 0) / 2 - this.x * relCos - this.y * relSin, yOffset = (options.height || 0) / 2 - this.y * relCos + this.x * relSin; for (i = 0, l = nodes.length; i < l; i++) { node = nodes[i]; node[write + 'x'] = (node[read + 'x'] || 0) * relCos + (node[read + 'y'] || 0) * relSin + xOffset; node[write + 'y'] = (node[read + 'y'] || 0) * relCos - (node[read + 'x'] || 0) * relSin + yOffset; node[write + 'size'] = (node[read + 'size'] || 0) / nodeRatio; } for (i = 0, l = edges.length; i < l; i++) { edges[i][write + 'size'] = (edges[i][read + 'size'] || 0) / edgeRatio; } return this; }; /** * This function converts the coordinates of a point from the frame of the * camera to the frame of the graph. * * @param {number} x The X coordinate of the point in the frame of the * camera. * @param {number} y The Y coordinate of the point in the frame of the * camera. * @return {object} The point coordinates in the frame of the graph. */ sigma.classes.camera.prototype.graphPosition = function(x, y, vector) { var X = 0, Y = 0, cos = Math.cos(this.angle), sin = Math.sin(this.angle); // Revert the origin differential vector: if (!vector) { X = - (this.x * cos + this.y * sin) / this.ratio; Y = - (this.y * cos - this.x * sin) / this.ratio; } return { x: (x * cos + y * sin) / this.ratio + X, y: (y * cos - x * sin) / this.ratio + Y }; }; /** * This function converts the coordinates of a point from the frame of the * graph to the frame of the camera. * * @param {number} x The X coordinate of the point in the frame of the * graph. * @param {number} y The Y coordinate of the point in the frame of the * graph. * @return {object} The point coordinates in the frame of the camera. */ sigma.classes.camera.prototype.cameraPosition = function(x, y, vector) { var X = 0, Y = 0, cos = Math.cos(this.angle), sin = Math.sin(this.angle); // Revert the origin differential vector: if (!vector) { X = - (this.x * cos + this.y * sin) / this.ratio; Y = - (this.y * cos - this.x * sin) / this.ratio; } return { x: ((x - X) * cos - (y - Y) * sin) * this.ratio, y: ((y - Y) * cos + (x - X) * sin) * this.ratio }; }; /** * This method returns the transformation matrix of the camera. This is * especially useful to apply the camera view directly in shaders, in case of * WebGL rendering. * * @return {array} The transformation matrix. */ sigma.classes.camera.prototype.getMatrix = function() { var scale = sigma.utils.matrices.scale(1 / this.ratio), rotation = sigma.utils.matrices.rotation(this.angle), translation = sigma.utils.matrices.translation(-this.x, -this.y), matrix = sigma.utils.matrices.multiply( translation, sigma.utils.matrices.multiply( rotation, scale ) ); return matrix; }; /** * Taking a width and a height as parameters, this method returns the * coordinates of the rectangle representing the camera on screen, in the * graph's referentiel. * * To keep displaying labels of nodes going out of the screen, the method * keeps a margin around the screen in the returned rectangle. * * @param {number} width The width of the screen. * @param {number} height The height of the screen. * @return {object} The rectangle as x1, y1, x2 and y2, representing * two opposite points. */ sigma.classes.camera.prototype.getRectangle = function(width, height) { var widthVect = this.cameraPosition(width, 0, true), heightVect = this.cameraPosition(0, height, true), centerVect = this.cameraPosition(width / 2, height / 2, true), marginX = this.cameraPosition(width / 4, 0, true).x, marginY = this.cameraPosition(0, height / 4, true).y; return { x1: this.x - centerVect.x - marginX, y1: this.y - centerVect.y - marginY, x2: this.x - centerVect.x + marginX + widthVect.x, y2: this.y - centerVect.y - marginY + widthVect.y, height: Math.sqrt( Math.pow(heightVect.x, 2) + Math.pow(heightVect.y + 2 * marginY, 2) ) }; }; }).call(this);