Created
August 4, 2015 20:06
-
-
Save danlamanna/c6fb7253db1ae3e3d7c9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** @namespace */ | |
| var geo = {}; // jshint ignore: line | |
| window.geo = geo; // jshint ignore: line | |
| geo.renderers = {}; | |
| geo.features = {}; | |
| geo.fileReaders = {}; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convenient function to define JS inheritance | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.inherit = function (C, P) { // jshint ignore: line | |
| "use strict"; | |
| var F = inherit.func(); | |
| F.prototype = P.prototype; | |
| C.prototype = new F(); | |
| C.prototype.constructor = C; | |
| }; | |
| geo.inherit.func = function () { | |
| "use strict"; | |
| return function () {}; | |
| }; | |
| // Should get rid of this at some point. | |
| window.inherit = geo.inherit; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Register a new file reader type | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.registerFileReader = function (name, func) { | |
| "use strict"; | |
| if (geo.fileReaders === undefined) { | |
| geo.fileReaders = {}; | |
| } | |
| geo.fileReaders[name] = func; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new file reader | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.createFileReader = function (name, opts) { | |
| "use strict"; | |
| if (geo.fileReaders.hasOwnProperty(name)) { | |
| return geo.fileReaders[name](opts); | |
| } | |
| return null; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Register a new renderer type | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.registerRenderer = function (name, func) { | |
| "use strict"; | |
| if (geo.renderers === undefined) { | |
| geo.renderers = {}; | |
| } | |
| geo.renderers[name] = func; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create new instance of the renderer | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.createRenderer = function (name, layer, canvas) { | |
| "use strict"; | |
| if (geo.renderers.hasOwnProperty(name)) { | |
| var ren = geo.renderers[name]( | |
| {"layer": layer, "canvas": canvas} | |
| ); | |
| ren._init(); | |
| return ren; | |
| } | |
| return null; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Register a new feature type | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.registerFeature = function (category, name, func) { | |
| "use strict"; | |
| if (geo.features === undefined) { | |
| geo.features = {}; | |
| } | |
| if (!(category in geo.features)) { | |
| geo.features[category] = {}; | |
| } | |
| // TODO Add warning if the name already exists | |
| geo.features[category][name] = func; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create new instance of the renderer | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.createFeature = function (name, layer, renderer, arg) { | |
| "use strict"; | |
| var category = renderer.api(), | |
| options = {"layer": layer, "renderer": renderer}; | |
| if (category in geo.features && name in geo.features[category]) { | |
| if (arg !== undefined) { | |
| $.extend(true, options, arg); | |
| } | |
| return geo.features[category][name](options); | |
| } | |
| return null; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Register a new layer type | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.registerLayer = function (name, func) { | |
| "use strict"; | |
| if (geo.layers === undefined) { | |
| geo.layers = {}; | |
| } | |
| geo.layers[name] = func; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create new instance of the layer | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.createLayer = function (name, map, arg) { | |
| "use strict"; | |
| /// Default renderer is vgl | |
| var options = {"map": map, "renderer": "vgl"}, | |
| layer = null; | |
| if (name in geo.layers) { | |
| if (arg !== undefined) { | |
| $.extend(true, options, arg); | |
| } | |
| layer = geo.layers[name](options); | |
| layer._init(); | |
| return layer; | |
| } else { | |
| return null; | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Register a new widget type | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.registerWidget = function (category, name, func) { | |
| "use strict"; | |
| if (geo.widgets === undefined) { | |
| geo.widgets = {}; | |
| } | |
| if (!(category in geo.widgets)) { | |
| geo.widgets[category] = {}; | |
| } | |
| // TODO Add warning if the name already exists | |
| geo.widgets[category][name] = func; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create new instance of the widget | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.createWidget = function (name, layer, renderer, arg) { | |
| "use strict"; | |
| var category = renderer.api(), | |
| options = {"layer": layer, "renderer": renderer}; | |
| if (category in geo.widgets && name in geo.widgets[category]) { | |
| if (arg !== undefined) { | |
| $.extend(true, options, arg); | |
| } | |
| return geo.widgets[category][name](options); | |
| } | |
| return null; | |
| }; | |
| // Add a polyfill for window.requestAnimationFrame. | |
| if (!window.requestAnimationFrame) { | |
| window.requestAnimationFrame = function (func) { | |
| "use strict"; | |
| window.setTimeout(func, 15); | |
| }; | |
| } | |
| // Add a polyfill for Math.log2 | |
| if (!Math.log2) { | |
| Math.log2 = function () { | |
| "use strict"; | |
| return Math.log.apply(Math, arguments) / Math.LN2; | |
| }; | |
| } | |
| /*global geo*/ | |
| geo.version = "0.4.2"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| if(typeof ogs === 'undefined') { | |
| var ogs = {}; | |
| } | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create namespace for the given name | |
| * | |
| * @param ns_string | |
| * @returns {*|{}} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ogs.namespace = function(ns_string) { | |
| 'use strict'; | |
| var parts = ns_string.split('.'), parent = ogs, i; | |
| // strip redundant leading global | |
| if (parts[0] === "ogs") { | |
| parts = parts.slice(1); | |
| } | |
| for (i = 0; i < parts.length; i += 1) { | |
| // create a property if it doesn't exist | |
| if (typeof parent[parts[i]] === "undefined") { | |
| parent[parts[i]] = {}; | |
| } | |
| parent = parent[parts[i]]; | |
| } | |
| return parent; | |
| }; | |
| /** vgl namespace */ | |
| var vgl = ogs.namespace("gl"); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convenient function to define JS inheritance | |
| * | |
| * @param C | |
| * @param P | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| function inherit(C, P) { | |
| "use strict"; | |
| var F = function() { | |
| }; | |
| F.prototype = P.prototype; | |
| C.prototype = new F(); | |
| C.uber = P.prototype; | |
| C.prototype.constructor = C; | |
| } | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convenient function to get size of an object | |
| * | |
| * @param obj | |
| * @returns {number} * | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| Object.size = function(obj) { | |
| "use strict"; | |
| var size = 0, key = null; | |
| for (key in obj) { | |
| if (obj.hasOwnProperty(key)) { | |
| size++; | |
| } | |
| } | |
| return size; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2, bitwise: true*/ | |
| /*global vgl, gl, ogs, vec3, vec4, mat4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Wrap GL enums. Currently to get values of the enums we need to create | |
| * or access the context. | |
| * | |
| * Using enums from here: | |
| * https://github.com/toji/dart-gl-enums/blob/master/lib/gl_enums.dart | |
| * | |
| * @class | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.GL = { | |
| ColorBufferBit : 0x00004000, | |
| DepthBufferBit : 0x00000100 | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class timestamp | |
| * | |
| * @class | |
| * @returns {vgl.timestamp} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| var m_globalModifiedTime = 0; | |
| vgl.timestamp = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.timestamp)) { | |
| return new vgl.timestamp(); | |
| } | |
| var m_modifiedTime = 0; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update modified time | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.modified = function() { | |
| ++m_globalModifiedTime; | |
| m_modifiedTime = m_globalModifiedTime; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get modified time | |
| * | |
| * @returns {number} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.getMTime = function() { | |
| return m_modifiedTime; | |
| }; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class object | |
| * | |
| * @class | |
| * @returns {vgl.object} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.object = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.object)) { | |
| return new vgl.object(); | |
| } | |
| /** @private */ | |
| var m_modifiedTime = vgl.timestamp(); | |
| m_modifiedTime.modified(); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Mark the object modified | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.modified = function() { | |
| m_modifiedTime.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return modified time of the object | |
| * | |
| * @returns {*} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getMTime = function() { | |
| return m_modifiedTime.getMTime(); | |
| }; | |
| return this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class event | |
| * | |
| * @class event | |
| * @returns {vgl.event} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.event = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.event)) { | |
| return new vgl.event(); | |
| } | |
| vgl.object.call(this); | |
| return this; | |
| }; | |
| inherit(vgl.event, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * types | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.event.keyPress = "vgl.event.keyPress"; | |
| vgl.event.mousePress = "vgl.event.mousePress"; | |
| vgl.event.mouseRelease = "vgl.event.mouseRelease"; | |
| vgl.event.contextMenu = "vgl.event.contextMenu"; | |
| vgl.event.configure = "vgl.event.configure"; | |
| vgl.event.enable = "vgl.event.enable"; | |
| vgl.event.mouseWheel = "vgl.event.mouseWheel"; | |
| vgl.event.keyRelease = "vgl.event.keyRelease"; | |
| vgl.event.middleButtonPress = "vgl.event.middleButtonPress"; | |
| vgl.event.startInteraction = "vgl.event.startInteraction"; | |
| vgl.event.enter = "vgl.event.enter"; | |
| vgl.event.rightButtonPress = "vgl.event.rightButtonPress"; | |
| vgl.event.middleButtonRelease = "vgl.event.middleButtonRelease"; | |
| vgl.event.char = "vgl.event.char"; | |
| vgl.event.disable = "vgl.event.disable"; | |
| vgl.event.endInteraction = "vgl.event.endInteraction"; | |
| vgl.event.mouseMove = "vgl.event.mouseMove"; | |
| vgl.event.mouseOut = "vgl.event.mouseOut"; | |
| vgl.event.expose = "vgl.event.expose"; | |
| vgl.event.timer = "vgl.event.timer"; | |
| vgl.event.leftButtonPress = "vgl.event.leftButtonPress"; | |
| vgl.event.leave = "vgl.event.leave"; | |
| vgl.event.rightButtonRelease = "vgl.event.rightButtonRelease"; | |
| vgl.event.leftButtonRelease = "vgl.event.leftButtonRelease"; | |
| vgl.event.click = "vgl.event.click"; | |
| vgl.event.dblClick = "vgl.event.dblClick"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class boundingObject | |
| * | |
| * @class | |
| * @return {vgl.boundingObject} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.boundingObject = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.boundingObject)) { | |
| return new vgl.boundingObject(); | |
| } | |
| vgl.object.call(this); | |
| /** @private */ | |
| var m_bounds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], | |
| m_computeBoundsTimestamp = vgl.timestamp(), | |
| m_boundsDirtyTimestamp = vgl.timestamp(); | |
| m_computeBoundsTimestamp.modified(); | |
| m_boundsDirtyTimestamp.modified(); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get current bounds of the object | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.bounds = function() { | |
| return m_bounds; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Check if bounds are valid | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.hasValidBounds = function(bounds) { | |
| if (bounds[0] == Number.MAX_VALUE || | |
| bounds[1] == -Number.MAX_VALUE || | |
| bounds[2] == Number.MAX_VALUE || | |
| bounds[3] == -Number.MAX_VALUE || | |
| bounds[4] == Number.MAX_VALUE || | |
| bounds[5] == -Number.MAX_VALUE) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set current bounds of the object | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setBounds = function(minX, maxX, minY, maxY, minZ, maxZ) { | |
| if (!this.hasValidBounds([minX, maxX, minY, maxY, minZ, maxZ])) { | |
| return; | |
| } | |
| m_bounds[0] = minX; | |
| m_bounds[1] = maxX; | |
| m_bounds[2] = minY; | |
| m_bounds[3] = maxY; | |
| m_bounds[4] = minZ; | |
| m_bounds[5] = maxZ; | |
| this.modified(); | |
| m_computeBoundsTimestamp.modified(); | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Reset bounds to default values | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.resetBounds = function() { | |
| m_bounds[0] = Number.MAX_VALUE; | |
| m_bounds[1] = -Number.MAX_VALUE; | |
| m_bounds[2] = Number.MAX_VALUE; | |
| m_bounds[3] = -Number.MAX_VALUE; | |
| m_bounds[4] = Number.MAX_VALUE; | |
| m_bounds[5] = -Number.MAX_VALUE; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute bounds of the object | |
| * | |
| * Should be implemented by the concrete class | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeBounds = function() { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return bounds computation modification time | |
| * | |
| * @returns {vgl.timestamp} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeBoundsTimestamp = function() { | |
| return m_computeBoundsTimestamp; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return bounds dirty timestamp | |
| * | |
| * @returns {vgl.timestamp} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.boundsDirtyTimestamp = function() { | |
| return m_boundsDirtyTimestamp; | |
| }; | |
| this.resetBounds(); | |
| return this; | |
| }; | |
| vgl.boundingObject.ReferenceFrame = { | |
| "Relative" : 0, | |
| "Absolute" : 1 | |
| }; | |
| inherit(vgl.boundingObject, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class node | |
| * | |
| * @class | |
| * @returns {vgl.node} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.node = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.node)) { | |
| return new vgl.node(); | |
| } | |
| vgl.boundingObject.call(this); | |
| /** @private */ | |
| var m_parent = null, | |
| m_material = null, | |
| m_visible = true, | |
| m_overlay = false; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Accept visitor for scene traversal | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.accept = function(visitor) { | |
| visitor.visit(this); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return active material used by the node | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.material = function() { | |
| return m_material; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set material to be used the node | |
| * | |
| * @param material | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setMaterial = function(material) { | |
| if (material !== m_material) { | |
| m_material = material; | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Check if the node is visible or node | |
| * | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.visible = function() { | |
| return m_visible; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Turn ON/OFF visibility of the node | |
| * | |
| * @param flag | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setVisible = function(flag) { | |
| if (flag !== m_visible) { | |
| m_visible = flag; | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return current parent of the node | |
| * | |
| * @returns {null} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.parent = function() { | |
| return m_parent; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set parent of the node | |
| * | |
| * @param parent | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setParent = function(parent) { | |
| if (parent !== m_parent) { | |
| if (m_parent !== null) { | |
| m_parent.removeChild(this); | |
| } | |
| m_parent = parent; | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Check if the node is an overlay node | |
| * | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.overlay = function() { | |
| return m_overlay; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set if the node is an overlay node or not | |
| * | |
| * @param flag | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setOverlay = function(flag) { | |
| if (m_overlay !== flag) { | |
| m_overlay = flag; | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /* | |
| * Traverse parent and their parent and so on | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.ascend = function(visitor) { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Traverse children | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.traverse = function(visitor) { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Mark that the bounds are modified | |
| * | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.boundsModified = function() { | |
| // @todo Implement this | |
| this.boundsDirtyTimestamp().modified(); | |
| if (m_parent !== null) { | |
| m_parent.boundsModified(); | |
| } | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.node, vgl.boundingObject); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class groupNode | |
| * | |
| * @class | |
| * @returns {vgl.groupNode} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.groupNode = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.groupNode)) { | |
| return new vgl.groupNode(); | |
| } | |
| vgl.node.call(this); | |
| var m_children = []; | |
| // Reference to base class methods | |
| this.b_setVisible = this.setVisible; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Turn on / off visibility | |
| * | |
| * @param flag | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setVisible = function(flag) { | |
| var i; | |
| if (this.b_setVisible(flag) !== true) { | |
| return false; | |
| } | |
| for (i = 0; i < m_children.length; ++i) { | |
| m_children[i].setVisible(flag); | |
| } | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Make the incoming node as child of the group node | |
| * | |
| * @param childNode | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.addChild = function(childNode) { | |
| if (childNode instanceof vgl.node) { | |
| if (m_children.indexOf(childNode) === -1) { | |
| childNode.setParent(this); | |
| m_children.push(childNode); | |
| this.boundsDirtyTimestamp().modified(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Remove parent-child relationship between the group and incoming node | |
| * | |
| * @param childNode | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.removeChild = function(childNode) { | |
| if (childNode.parent() === this) { | |
| var index = m_children.indexOf(childNode); | |
| m_children.splice(index, 1); | |
| this.boundsDirtyTimestamp().modified(); | |
| return true; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Remove parent-child relationship between child nodes and the group node | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.removeChildren = function() { | |
| var i; | |
| for (i = 0; i < m_children.length; ++i) { | |
| this.removeChild(m_children[i]); | |
| } | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return children of this group node | |
| * | |
| * @returns {Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.children = function() { | |
| return m_children; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return true if this group node has node as a child, false otherwise. | |
| * | |
| * @param node | |
| * @returns {bool} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.hasChild = function(node) { | |
| var i = 0, child = false; | |
| for (i = 0; i < m_children.length; i++) { | |
| if (m_children[i] === node) { | |
| child = true; | |
| break; | |
| } | |
| } | |
| return child; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Accept a visitor and traverse the scene tree | |
| * | |
| * @param visitor | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.accept = function(visitor) { | |
| visitor.visit(this); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Traverse the scene | |
| * | |
| * @param visitor | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.traverse = function(visitor) { | |
| switch (visitor.type()) { | |
| case visitor.UpdateVisitor: | |
| this.traverseChildrenAndUpdateBounds(visitor); | |
| break; | |
| case visitor.CullVisitor: | |
| this.traverseChildren(visitor); | |
| break; | |
| default: | |
| break; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Traverse all of the children and update the bounds for each | |
| * | |
| * @param visitor | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.traverseChildrenAndUpdateBounds = function(visitor) { | |
| var i; | |
| if (this.m_parent && this.boundsDirtyTimestamp().getMTime() > | |
| this.computeBoundsTimestamp().getMTime()) { | |
| // Flag parents bounds dirty. | |
| this.m_parent.boundsDirtyTimestamp.modified(); | |
| } | |
| this.computeBounds(); | |
| if (visitor.mode() === visitor.TraverseAllChildren) { | |
| for (i = 0; i < m_children.length(); ++i) { | |
| m_children[i].accept(visitor); | |
| this.updateBounds(m_children[i]); | |
| } | |
| } | |
| this.computeBoundsTimestamp().modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Traverse children of the group node | |
| * | |
| * @param visitor | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.traverseChildren = function(visitor) { | |
| var i; | |
| if (visitor.mode() === vgl.vesVisitor.TraverseAllChildren) { | |
| for (i = 0; i < m_children.length(); ++i) { | |
| m_children[i].accept(visitor); | |
| } | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute bounds for the group node | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeBounds = function() { | |
| var i = 0; | |
| if (this.computeBoundsTimestamp().getMTime() > | |
| this.boundsDirtyTimestamp().getMTime()) { | |
| return; | |
| } | |
| for (i = 0; i < m_children.length; ++i) { | |
| this.updateBounds(m_children[i]); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update bounds for the group node | |
| * | |
| * This method is used internally to update bounds of the group node by | |
| * traversing each of its child. | |
| * | |
| * @param child | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.updateBounds = function(child) { | |
| // FIXME: This check should not be required and possibly is incorrect | |
| if (child.overlay()) { | |
| return; | |
| } | |
| // Make sure that child bounds are upto date | |
| child.computeBounds(); | |
| var bounds = this.bounds(), | |
| childBounds = child.bounds(), | |
| istep = 0, | |
| jstep = 0, | |
| i; | |
| for (i = 0; i < 3; ++i) { | |
| istep = i * 2; | |
| jstep = i * 2 + 1; | |
| if (childBounds[istep] < bounds[istep]) { | |
| bounds[istep] = childBounds[istep]; | |
| } | |
| if (childBounds[jstep] > bounds[jstep]) { | |
| bounds[jstep] = childBounds[jstep]; | |
| } | |
| } | |
| this.setBounds(bounds[0], bounds[1], bounds[2], bounds[3], | |
| bounds[4], bounds[5]); | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.groupNode, vgl.node); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec3, vec4, mat4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class actor | |
| * | |
| * @class | |
| * @returns {vgl.actor} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| vgl.actor = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.actor)) { | |
| return new vgl.actor(); | |
| } | |
| vgl.node.call(this); | |
| /** @private */ | |
| var m_transformMatrix = mat4.create(), | |
| m_referenceFrame = vgl.boundingObject.ReferenceFrame.Relative, | |
| m_mapper = null; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get transformation matrix used by the actor | |
| * | |
| * @returns {mat4} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.matrix = function() { | |
| return m_transformMatrix; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set transformation matrix for the actor | |
| * | |
| * @param {mat4} 4X4 transformation matrix | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setMatrix = function(tmatrix) { | |
| if (tmatrix !== m_transformMatrix) { | |
| m_transformMatrix = tmatrix; | |
| this.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get reference frame for the transformations | |
| * | |
| * @returns {String} Possible values are Absolute or Relative | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.referenceFrame = function() { | |
| return m_referenceFrame; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set reference frame for the transformations | |
| * | |
| * @param {vgl.boundingObject.ReferenceFrame} | |
| * referenceFrame Possible values are (Absolute | Relative) | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setReferenceFrame = function(referenceFrame) { | |
| if (referenceFrame !== m_referenceFrame) { | |
| m_referenceFrame = referenceFrame; | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return mapper where actor gets it behavior and data | |
| * | |
| * @returns {vgl.mapper} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.mapper = function() { | |
| return m_mapper; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Connect an actor to its data source | |
| * | |
| * @param {vgl.mapper} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setMapper = function(mapper) { | |
| if (mapper !== m_mapper) { | |
| m_mapper = mapper; | |
| this.boundsModified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @todo | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.accept = function(visitor) { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @todo | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.ascend = function(visitor) { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute object space to world space matrix | |
| * @todo | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeLocalToWorldMatrix = function(matrix, visitor) { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute world space to object space matrix | |
| * @todo | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeWorldToLocalMatrix = function(matrix, visitor) { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute actor bounds | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeBounds = function() { | |
| if (m_mapper === null || m_mapper === undefined) { | |
| this.resetBounds(); | |
| return; | |
| } | |
| var computeBoundsTimestamp = this.computeBoundsTimestamp(), | |
| mapperBounds, minPt, maxPt, actorMatrix, newBounds; | |
| if (this.boundsDirtyTimestamp().getMTime() > computeBoundsTimestamp.getMTime() || | |
| m_mapper.boundsDirtyTimestamp().getMTime() > computeBoundsTimestamp.getMTime()) { | |
| m_mapper.computeBounds(); | |
| mapperBounds = m_mapper.bounds(); | |
| minPt = [mapperBounds[0], mapperBounds[2], mapperBounds[4]]; | |
| maxPt = [mapperBounds[1], mapperBounds[3], mapperBounds[5]]; | |
| vec3.transformMat4(minPt, minPt, m_transformMatrix); | |
| vec3.transformMat4(maxPt, maxPt, m_transformMatrix); | |
| newBounds = [ | |
| minPt[0] > maxPt[0] ? maxPt[0] : minPt[0], | |
| minPt[0] > maxPt[0] ? minPt[0] : maxPt[0], | |
| minPt[1] > maxPt[1] ? maxPt[1] : minPt[1], | |
| minPt[1] > maxPt[1] ? minPt[1] : maxPt[1], | |
| minPt[2] > maxPt[2] ? maxPt[2] : minPt[2], | |
| minPt[2] > maxPt[2] ? minPt[2] : maxPt[2] | |
| ]; | |
| this.setBounds(newBounds[0], newBounds[1], | |
| newBounds[2], newBounds[3], | |
| newBounds[4], newBounds[5]); | |
| computeBoundsTimestamp.modified(); | |
| } | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.actor, vgl.node); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Freeze javascript object | |
| * | |
| * @param obj | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.freezeObject = function(obj) { | |
| 'use strict'; | |
| /** | |
| * Freezes an object, using Object.freeze if available, otherwise returns | |
| * the object unchanged. This function should be used in setup code to prevent | |
| * errors from completely halting JavaScript execution in legacy browsers. | |
| * | |
| * @exports freezeObject | |
| */ | |
| var freezedObject = Object.freeze(obj); | |
| if (typeof freezedObject === 'undefined') { | |
| freezedObject = function(o) { | |
| return o; | |
| }; | |
| } | |
| return freezedObject; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns the first parameter if not undefined, | |
| * otherwise the second parameter. | |
| * | |
| * @class | |
| * @returns {vgl.defaultValue} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.defaultValue = function(a, b) { | |
| 'use strict'; | |
| if (typeof a !== 'undefined') { | |
| return a; | |
| } | |
| return b; | |
| }; | |
| vgl.defaultValue.EMPTY_OBJECT = vgl.freezeObject({}); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $, Uint16Array*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of geojson reader | |
| * | |
| * This contains code that reads a geoJSON file and produces rendering | |
| * primitives from it. | |
| * | |
| * @class | |
| * @returns {vgl.geojsonReader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.geojsonReader = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.geojsonReader)) { | |
| return new vgl.geojsonReader(); | |
| } | |
| var m_scalarFormat = "none", | |
| m_scalarRange = null; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Read scalars | |
| * | |
| * @param coordinates | |
| * @param geom | |
| * @param size_estimate | |
| * @param idx | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readScalars = function(coordinates, geom, size_estimate, idx) { | |
| var array = null, | |
| s = null, | |
| r = null, | |
| g = null, | |
| b = null; | |
| if (this.m_scalarFormat === "values" && coordinates.length === 4) | |
| { | |
| s = coordinates[3]; | |
| array = geom.sourceData(vgl.vertexAttributeKeys.Scalar); | |
| if (!array) { | |
| array = new vgl.sourceDataSf(); | |
| if (this.m_scalarRange) { | |
| array.setScalarRange(this.m_scalarRange[0],this.m_scalarRange[1]); | |
| } | |
| if (size_estimate !== undefined) { | |
| //array.length = size_estimate; //no, slow on Safari | |
| array.data().length = size_estimate; | |
| } | |
| geom.addSource(array); | |
| } | |
| if (size_estimate === undefined) { | |
| array.pushBack(s); | |
| } else { | |
| array.insertAt(idx, s); | |
| } | |
| } else if (this.m_scalarFormat === "rgb" && coordinates.length === 6) { | |
| array = geom.sourceData(vgl.vertexAttributeKeys.Color); | |
| if (!array) { | |
| array = new vgl.sourceDataC3fv(); | |
| if (size_estimate !== undefined) { | |
| array.length = size_estimate*3; | |
| } | |
| geom.addSource(array); | |
| } | |
| r = coordinates[3]; | |
| g = coordinates[4]; | |
| b = coordinates[5]; | |
| if (size_estimate === undefined) { | |
| array.pushBack([r,g,b]); | |
| } else { | |
| array.insertAt(idx, [r,g,b]); | |
| } | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Read point data | |
| * | |
| * @param coordinates | |
| * @returns {vgl.geometryData} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readPoint = function(coordinates) { | |
| var geom = new vgl.geometryData(), | |
| vglpoints = new vgl.points(), | |
| vglcoords = new vgl.sourceDataP3fv(), | |
| indices = new Uint16Array(1), | |
| x = null, | |
| y = null, | |
| z = null, | |
| i = null; | |
| geom.addSource(vglcoords); | |
| for (i = 0; i < 1; i++) { | |
| indices[i] = i; | |
| x = coordinates[0]; | |
| y = coordinates[1]; | |
| z = 0.0; | |
| if (coordinates.length>2) { | |
| z = coordinates[2]; | |
| } | |
| //console.log("read " + x + "," + y + "," + z); | |
| vglcoords.pushBack([x,y,z]); | |
| //attributes | |
| this.readScalars(coordinates, geom); | |
| } | |
| vglpoints.setIndices(indices); | |
| geom.addPrimitive(vglpoints); | |
| geom.setName("aPoint"); | |
| return geom; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Read multipoint data | |
| * | |
| * @param coordinates | |
| * @returns {vgl.geometryData} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readMultiPoint = function(coordinates) { | |
| var geom = new vgl.geometryData(), | |
| vglpoints = new vgl.points(), | |
| vglcoords = new vgl.sourceDataP3fv(), | |
| indices = new Uint16Array(coordinates.length), | |
| pntcnt = 0, | |
| estpntcnt = coordinates.length, | |
| x = null, | |
| y = null, | |
| z = null, | |
| i; | |
| //preallocate with size estimate | |
| vglcoords.data().length = estpntcnt * 3; //x,y,z | |
| for (i = 0; i < coordinates.length; i++) { | |
| indices[i] = i; | |
| x = coordinates[i][0]; | |
| y = coordinates[i][1]; | |
| z = 0.0; | |
| if (coordinates[i].length>2) { | |
| z = coordinates[i][2]; | |
| } | |
| //console.log("read " + x + "," + y + "," + z); | |
| vglcoords.insertAt(pntcnt, [x,y,z]); | |
| //attributes | |
| this.readScalars(coordinates[i], geom, estpntcnt, pntcnt); | |
| pntcnt++; | |
| } | |
| vglpoints.setIndices(indices); | |
| geom.addPrimitive(vglpoints); | |
| geom.addSource(vglcoords); | |
| geom.setName("manyPoints"); | |
| return geom; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Read line string data | |
| * | |
| * @param coordinates | |
| * @returns {vgl.geometryData} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readLineString = function(coordinates) { | |
| var geom = new vgl.geometryData(), | |
| vglline = new vgl.lineStrip(), | |
| vglcoords = new vgl.sourceDataP3fv(), | |
| indices = [], | |
| i = null, | |
| x = null, | |
| y = null, | |
| z = null; | |
| vglline.setIndicesPerPrimitive(coordinates.length); | |
| for (i = 0; i < coordinates.length; i++) { | |
| indices.push(i); | |
| x = coordinates[i][0]; | |
| y = coordinates[i][1]; | |
| z = 0.0; | |
| if (coordinates[i].length>2) { | |
| z = coordinates[i][2]; | |
| } | |
| //console.log("read " + x + "," + y + "," + z); | |
| vglcoords.pushBack([x,y,z]); | |
| //attributes | |
| this.readScalars(coordinates[i], geom); | |
| } | |
| vglline.setIndices(indices); | |
| geom.addPrimitive(vglline); | |
| geom.addSource(vglcoords); | |
| geom.setName("aLineString"); | |
| return geom; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Read multi line string | |
| * | |
| * @param coordinates | |
| * @returns {vgl.geometryData} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readMultiLineString = function(coordinates) { | |
| var geom = new vgl.geometryData(), | |
| vglcoords = new vgl.sourceDataP3fv(), | |
| pntcnt = 0, | |
| //lines should be at least 2 verts long, underest OK | |
| estpntcnt = coordinates.length * 2, | |
| i = null, | |
| j = null, | |
| x = null, | |
| y = null, | |
| z = null, | |
| indices = null, | |
| vglline = null, | |
| thisLineLength = null; | |
| // Preallocate with size estimate | |
| vglcoords.data().length = estpntcnt*3; //x,y,z | |
| for (j = 0; j < coordinates.length; j++) { | |
| indices = []; | |
| //console.log("getting line " + j); | |
| vglline = new vgl.lineStrip(); | |
| thisLineLength = coordinates[j].length; | |
| vglline.setIndicesPerPrimitive(thisLineLength); | |
| for (i = 0; i < thisLineLength; i++) { | |
| indices.push(pntcnt); | |
| x = coordinates[j][i][0]; | |
| y = coordinates[j][i][1]; | |
| z = 0.0; | |
| if (coordinates[j][i].length>2) { | |
| z = coordinates[j][i][2]; | |
| } | |
| //console.log("read " + x + "," + y + "," + z); | |
| vglcoords.insertAt(pntcnt, [x,y,z]); | |
| //attributes | |
| this.readScalars(coordinates[j][i], geom, estpntcnt*2, pntcnt); | |
| pntcnt++; | |
| } | |
| vglline.setIndices(indices); | |
| geom.addPrimitive(vglline); | |
| } | |
| geom.setName("aMultiLineString"); | |
| geom.addSource(vglcoords); | |
| return geom; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Read polygon data | |
| * | |
| * @param coordinates | |
| * @returns {vgl.geometryData} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readPolygon = function(coordinates) { | |
| //TODO: ignoring holes given in coordinates[1...] | |
| //TODO: ignoring convex | |
| //TODO: implement ear clipping in VGL instead of this to handle both | |
| var geom = new vgl.geometryData(), | |
| vglcoords = new vgl.sourceDataP3fv(), | |
| x = null, | |
| y = null, | |
| z = null, | |
| thisPolyLength = coordinates[0].length, | |
| vl = 1, | |
| i = null, | |
| indices = null, | |
| vgltriangle = null; | |
| for (i = 0; i < thisPolyLength; i++) { | |
| x = coordinates[0][i][0]; | |
| y = coordinates[0][i][1]; | |
| z = 0.0; | |
| if (coordinates[0][i].length>2) { | |
| z = coordinates[0][i][2]; | |
| } | |
| //console.log("read " + x + "," + y + "," + z); | |
| vglcoords.pushBack([x,y,z]); | |
| //attributes | |
| this.readScalars(coordinates[0][i], geom); | |
| if (i > 1) { | |
| //console.log("Cutting new triangle 0,"+ vl+ ","+ i); | |
| indices = new Uint16Array([0,vl,i]); | |
| vgltriangle = new vgl.triangles(); | |
| vgltriangle.setIndices(indices); | |
| geom.addPrimitive(vgltriangle); | |
| vl = i; | |
| } | |
| } | |
| geom.setName("POLY"); | |
| geom.addSource(vglcoords); | |
| return geom; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Read multi polygon data | |
| * | |
| * @param coordinates | |
| * @returns {vgl.geometryData} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readMultiPolygon = function(coordinates) { | |
| var geom = new vgl.geometryData(), | |
| vglcoords = new vgl.sourceDataP3fv(), | |
| ccount = 0, | |
| numPolys = coordinates.length, | |
| pntcnt = 0, | |
| estpntcnt = numPolys* 3, // assume triangles, underest is fine | |
| vgltriangle = new vgl.triangles(), | |
| indexes = [], | |
| i = null, | |
| j = null, | |
| x = null, | |
| y = null, | |
| z = null, | |
| thisPolyLength = null, | |
| vf = null, | |
| vl = null, | |
| flip = null, | |
| flipped = false, | |
| tcount = 0; | |
| //var time1 = new Date().getTime() | |
| //var a = 0; | |
| //var b = 0; | |
| //var c = 0; | |
| //var d = 0; | |
| //preallocate with size estimate | |
| vglcoords.data().length = numPolys*3; //x,y,z | |
| for (j = 0; j < numPolys; j++) { | |
| //console.log("getting poly " + j); | |
| thisPolyLength = coordinates[j][0].length; | |
| vf = ccount; | |
| vl = ccount+1; | |
| flip = [false,false,false]; | |
| for (i = 0; i < thisPolyLength; i++) { | |
| //var timea = new Date().getTime() | |
| x = coordinates[j][0][i][0]; | |
| y = coordinates[j][0][i][1]; | |
| z = 0.0; | |
| if (coordinates[j][0][i].length>2) { | |
| z = coordinates[j][0][i][2]; | |
| } | |
| flipped = false; | |
| if (x > 180) { | |
| flipped = true; | |
| x = x - 360; | |
| } | |
| if (i === 0) { | |
| flip[0] = flipped; | |
| } else { | |
| flip[1+(i-1)%2] = flipped; | |
| } | |
| //var timeb = new Date().getTime(); | |
| //console.log("read " + x + "," + y + "," + z); | |
| vglcoords.insertAt(pntcnt, [x,y,z]); | |
| //var timec = new Date().getTime(); | |
| //attributes | |
| this.readScalars(coordinates[j][0][i], geom, estpntcnt, pntcnt); | |
| pntcnt++; | |
| //var timed = new Date().getTime() | |
| if (i > 1) { | |
| //if (vl < 50) { | |
| //console.log("Cutting new triangle " + tcount + ":" + vf + "," + vl + "," + ccount); | |
| //console.log(indexes); | |
| //} | |
| if (flip[0] === flip[1] && flip[1] === flip[2]) { | |
| //indexes = indexes.concat([vf,vl,ccount]); //no, very slow in Safari | |
| indexes[tcount*3+0] = vf | |
| indexes[tcount*3+1] = vl | |
| indexes[tcount*3+2] = ccount | |
| tcount++; | |
| } | |
| //else { | |
| // //TODO: duplicate triangles that straddle boundary on either side | |
| //} | |
| vl = ccount; | |
| } | |
| ccount++; | |
| //var timee = new Date().getTime() | |
| //a = a + (timeb-timea) | |
| //b = b + (timec-timeb) | |
| //c = c + (timed-timec) | |
| //d = d + (timee-timed) | |
| } | |
| } | |
| vgltriangle.setIndices(indexes); | |
| geom.addPrimitive(vgltriangle); | |
| //console.log("NUMPOLYS " + pntcnt); | |
| //console.log("RMP: ", a, ",", b, ",", c, ",", d) | |
| //var time2 = new Date().getTime() | |
| geom.setName("aMultiPoly"); | |
| geom.addSource(vglcoords); | |
| //var time3 = new Date().getTime() | |
| //console.log("RMP: ", time2-time1, ",", time3-time2) | |
| return geom; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @param object | |
| * @returns {*} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readGJObjectInt = function(object) { | |
| if (!object.hasOwnProperty('type')) { | |
| //console.log("uh oh, not a geojson object"); | |
| return null; | |
| } | |
| //look for properties type annotation | |
| if (object.properties && | |
| object.properties.ScalarFormat && | |
| object.properties.ScalarFormat === "values") { | |
| this.m_scalarFormat = "values"; | |
| if (object.properties.ScalarRange) { | |
| this.m_scalarRange = object.properties.ScalarRange; | |
| } | |
| } | |
| if (object.properties && | |
| object.properties.ScalarFormat && | |
| object.properties.ScalarFormat === "rgb") { | |
| this.m_scalarFormat = "rgb"; | |
| } | |
| //TODO: ignoring "crs" and "bbox" and misc meta data on all of these, | |
| //best to handle as references into original probably | |
| var ret, | |
| type = object.type, | |
| next = null, | |
| nextset = null, | |
| i = null; | |
| switch (type) { | |
| case "Point": | |
| //console.log("parsed Point"); | |
| ret = this.readPoint(object.coordinates); | |
| break; | |
| case "MultiPoint": | |
| //console.log("parsed MultiPoint"); | |
| ret = this.readMultiPoint(object.coordinates); | |
| break; | |
| case "LineString": | |
| //console.log("parsed LineString"); | |
| ret = this.readLineString(object.coordinates); | |
| break; | |
| case "MultiLineString": | |
| //console.log("parsed MultiLineString"); | |
| ret = this.readMultiLineString(object.coordinates); | |
| break; | |
| case "Polygon": | |
| //console.log("parsed Polygon"); | |
| ret = this.readPolygon(object.coordinates); | |
| break; | |
| case "MultiPolygon": | |
| //console.log("parsed MultiPolygon"); | |
| ret = this.readMultiPolygon(object.coordinates); | |
| break; | |
| case "GeometryCollection": | |
| //console.log("parsed GeometryCollection"); | |
| nextset = []; | |
| for (i = 0; i < object.geometries.length; i++) { | |
| next = this.readGJObject(object.geometries[i]); | |
| nextset.push(next); | |
| } | |
| ret = nextset; | |
| break; | |
| case "Feature": | |
| //console.log("parsed Feature"); | |
| next = this.readGJObject(object.geometry); | |
| ret = next; | |
| break; | |
| case "FeatureCollection": | |
| //console.log("parsed FeatureCollection"); | |
| nextset = []; | |
| for (i = 0; i < object.features.length; i++) { | |
| next = this.readGJObject(object.features[i]); | |
| nextset.push(next); | |
| } | |
| ret = nextset; | |
| break; | |
| default: | |
| console.log("Don't understand type " + type); | |
| ret = null; | |
| break; | |
| } | |
| return ret; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @param object | |
| * @returns {*} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readGJObject = function(object) { | |
| //var time1, time2; | |
| var ret; | |
| //time1 = new Date().getTime() | |
| ret = this.readGJObjectInt(object); | |
| //time2 = new Date().getTime() | |
| //console.log("ELAPSED: ", time2-time1) | |
| return ret; | |
| }; | |
| /** | |
| * Linearize geometries | |
| * | |
| * @param geoms | |
| * @param geom | |
| */ | |
| this.linearizeGeoms = function(geoms, geom) { | |
| var i = null; | |
| if( Object.prototype.toString.call( geom ) === '[object Array]' ) { | |
| for (i = 0; i < geom.length; i++) { | |
| this.linearizeGeoms(geoms, geom[i]); | |
| } | |
| } | |
| else { | |
| geoms.push(geom); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Read geometries from geojson object | |
| * | |
| * @param object | |
| * @returns {Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readGeomObject = function(object) { | |
| var geom, | |
| geoms = []; | |
| geom = this.readGJObject(object); | |
| this.linearizeGeoms(geoms, geom); | |
| return geoms; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Given a buffer get rendering primitives | |
| * | |
| * @param buffer | |
| * @returns {*} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getPrimitives = function(buffer) { | |
| //console.log("Parsing geoJSON"); | |
| if (!buffer) { | |
| return []; | |
| } | |
| var obj = JSON.parse(buffer), | |
| geom = this.readGJObject(obj), | |
| geoms = []; | |
| this.m_scalarFormat = "none"; | |
| this.m_scalarRange = null; | |
| this.linearizeGeoms(geoms, geom); | |
| return { "geoms":geoms, | |
| "scalarFormat":this.m_scalarFormat, | |
| "scalarRange":this.m_scalarRange }; | |
| }; | |
| return this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.data = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.data)) { | |
| return new vgl.data(); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return data type. Should be implemented by the derived class | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.type = function() { | |
| }; | |
| }; | |
| vgl.data.raster = 0; | |
| vgl.data.point = 1; | |
| vgl.data.lineString = 2; | |
| vgl.data.polygon = 3; | |
| vgl.data.geometry = 10; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, Uint16Array, gl, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Vertex attribute keys | |
| * | |
| * @type {{Position: number, Normal: number, TextureCoordinate: number, | |
| * Color: number, Scalar: number}} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| var vertexAttributeKeys = { | |
| "Position" : 0, | |
| "Normal" : 1, | |
| "TextureCoordinate" : 2, | |
| "Color" : 3, | |
| "Scalar" : 4 | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class primitive | |
| * | |
| * @class | |
| * @return {vgl.primitive} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.primitive = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.primitive)) { | |
| return new vgl.primitive(); | |
| } | |
| /** @private */ | |
| var m_indicesPerPrimitive = 0, | |
| m_primitiveType = 0, | |
| m_indicesValueType = 0, | |
| m_indices = null; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get indices of the primitive | |
| * | |
| * @returns {null} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.indices = function() { | |
| return m_indices; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create indices array for the primitive | |
| * @param type | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.createIndices = function(type) { | |
| // TODO Check for the type | |
| m_indices = new Uint16Array(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return the number of indices | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.numberOfIndices = function() { | |
| return m_indices.length; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return size of indices in bytes | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.sizeInBytes = function() { | |
| return m_indices.length * Uint16Array.BYTES_PER_ELEMENT; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /* | |
| * Return primitive type g | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.primitiveType = function() { | |
| return m_primitiveType; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set primitive type | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setPrimitiveType = function(type) { | |
| m_primitiveType = type; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return count of indices that form a primitives | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.indicesPerPrimitive = function() { | |
| return m_indicesPerPrimitive; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set count of indices that form a primitive | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setIndicesPerPrimitive = function(count) { | |
| m_indicesPerPrimitive = count; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return indices value type | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.indicesValueType = function() { | |
| return m_indicesValueType; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set indices value type | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setIndicesValueType = function(type) { | |
| m_indicesValueType = type; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set indices from a array | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setIndices = function(indicesArray) { | |
| // TODO Check for the type | |
| m_indices = new Uint16Array(indicesArray); | |
| }; | |
| return this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class triangleStrip | |
| * | |
| * @returns {vgl.triangleStrip} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.triangleStrip = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.triangleStrip)) { | |
| return new vgl.triangleStrip(); | |
| } | |
| vgl.primitive.call(this); | |
| this.setPrimitiveType(gl.TRIANGLE_STRIP); | |
| this.setIndicesValueType(gl.UNSIGNED_SHORT); | |
| this.setIndicesPerPrimitive(3); | |
| return this; | |
| }; | |
| inherit(vgl.triangleStrip, vgl.primitive); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class triangles | |
| * | |
| * @returns {vgl.triangles} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| vgl.triangles = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.triangles)) { | |
| return new vgl.triangles(); | |
| } | |
| vgl.primitive.call(this); | |
| this.setPrimitiveType(gl.TRIANGLES); | |
| this.setIndicesValueType(gl.UNSIGNED_SHORT); | |
| this.setIndicesPerPrimitive(3); | |
| return this; | |
| }; | |
| inherit(vgl.triangles, vgl.primitive); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * create a instance of lines primitive type | |
| * | |
| * @returns {vgl.lines} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.lines = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.lines)) { | |
| return new vgl.lines(); | |
| } | |
| vgl.primitive.call(this); | |
| this.setPrimitiveType(gl.LINES); | |
| this.setIndicesValueType(gl.UNSIGNED_SHORT); | |
| this.setIndicesPerPrimitive(2); | |
| return this; | |
| }; | |
| inherit(vgl.lines, vgl.primitive); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * create a instance of line strip primitive type | |
| * | |
| * @returns {vgl.lineStrip} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.lineStrip = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.lineStrip)) { | |
| return new vgl.lineStrip(); | |
| } | |
| vgl.primitive.call(this); | |
| this.setPrimitiveType(gl.LINE_STRIP); | |
| this.setIndicesValueType(gl.UNSIGNED_SHORT); | |
| this.setIndicesPerPrimitive(2); | |
| return this; | |
| }; | |
| inherit(vgl.lineStrip, vgl.primitive); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class points | |
| * | |
| * @returns {vgl.points} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.points = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.points)) { | |
| return new vgl.points(); | |
| } | |
| vgl.primitive.call(this); | |
| this.setPrimitiveType(gl.POINTS); | |
| this.setIndicesValueType(gl.UNSIGNED_SHORT); | |
| this.setIndicesPerPrimitive(1); | |
| return this; | |
| }; | |
| inherit(vgl.points, vgl.primitive); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class vertexDataP3f | |
| * | |
| * @returns {vgl.vertexDataP3f} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.vertexDataP3f = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.vertexDataP3f)) { | |
| return new vgl.vertexDataP3f(); | |
| } | |
| /** @private */ | |
| this.m_position = []; | |
| return this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class vertexDataP3N3f | |
| * | |
| * @class | |
| * @returns {vgl.vertexDataP3N3f} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.vertexDataP3N3f = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.vertexDataP3N3f)) { | |
| return new vgl.vertexDataP3N3f(); | |
| } | |
| this.m_position = []; | |
| this.m_normal = []; | |
| return this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class vertexDataP3T3f | |
| * | |
| * @class | |
| * @returns {vgl.vertexDataP3T3f} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.vertexDataP3T3f = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.vertexDataP3T3f)) { | |
| return new vgl.vertexDataP3T3f(); | |
| } | |
| this.m_position = []; | |
| this.m_texCoordinate = []; | |
| return this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class sourceData | |
| * @class | |
| * @returns {vgl.sourceData} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.sourceData = function(arg) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.sourceData)) { | |
| return new vgl.sourceData(arg); | |
| } | |
| arg = arg || {}; | |
| var m_attributesMap = {}, | |
| m_data = [], | |
| m_name = arg.name || "Source " + new Date().toISOString(), | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Attribute data for the source | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| vglAttributeData = function() { | |
| // Number of components per group | |
| // Type of data type (GL_FLOAT etc) | |
| this.m_numberOfComponents = 0; | |
| // Size of data type | |
| this.m_dataType = 0; | |
| this.m_dataTypeSize = 0; | |
| // Specifies whether fixed-point data values should be normalized | |
| // (true) or converted directly as fixed-point values (false) | |
| // when they are accessed. | |
| this.m_normalized = false; | |
| // Strides for each attribute. | |
| this.m_stride = 0; | |
| // Offset | |
| this.m_offset = 0; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return raw data for this source | |
| * | |
| * @returns {Array or Float32Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.data = function() { | |
| return m_data; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return raw data for this source | |
| * | |
| * @returns {Array or Float32Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getData = function() { | |
| return data(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * If the raw data is not a Float32Array, convert it to one. Then, return | |
| * raw data for this source | |
| * | |
| * @returns {Float32Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.dataToFloat32Array = function () { | |
| if (!(m_data instanceof Float32Array)) { | |
| m_data = new Float32Array(m_data); | |
| } | |
| return m_data; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set data for this source | |
| * | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setData = function (data) { | |
| if (!(data instanceof Array) && !(data instanceof Float32Array)) { | |
| console.log("[error] Requires array"); | |
| return; | |
| } | |
| if (data instanceof Float32Array) { | |
| m_data = data; | |
| } else { | |
| m_data = data.slice(0); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add new attribute data to the source | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.addAttribute = function(key, dataType, sizeOfDataType, offset, stride, | |
| noOfComponents, normalized) { | |
| if (!m_attributesMap.hasOwnProperty(key)) { | |
| var newAttr = new vglAttributeData(); | |
| newAttr.m_dataType = dataType; | |
| newAttr.m_dataTypeSize = sizeOfDataType; | |
| newAttr.m_offset = offset; | |
| newAttr.m_stride = stride; | |
| newAttr.m_numberOfComponents = noOfComponents; | |
| newAttr.m_normalized = normalized; | |
| m_attributesMap[key] = newAttr; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return size of the source data | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.sizeOfArray = function() { | |
| return Object.size(m_data); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return length of array | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.lengthOfArray = function() { | |
| return m_data.length; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return size of the source data in bytes | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| /* | |
| * TODO: code below is probably wrong. | |
| * Example: | |
| * format P3N3f | |
| * m_data = [ 1, 2, 3, 4, 5, 6 ]; // contains one vertex, one normal, m_data.length == 6 | |
| * | |
| * The inner loop computes: | |
| * sizeInBytes += 3 * 4; // for position | |
| * sizeInBytes += 3 * 4; // for normal | |
| * | |
| * Then sizeInBytes *= 6; // m_data.length == 6 | |
| * which gives sizeInBytes == 144 bytes when it should have been 4*6 = 24 | |
| */ | |
| this.sizeInBytes = function() { | |
| var sizeInBytes = 0, | |
| keys = this.keys(), i; | |
| for (i = 0; i < keys.length(); ++i) { | |
| sizeInBytes += this.numberOfComponents(keys[i]) | |
| * this.sizeOfAttributeDataType(keys[i]); | |
| } | |
| sizeInBytes *= this.sizeOfArray(); | |
| return sizeInBytes; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Check if there is attribute exists of a given key type | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.hasKey = function(key) { | |
| return m_attributesMap.hasOwnProperty(key); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return keys of all attributes | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.keys = function() { | |
| return Object.keys(m_attributesMap); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return number of attributes of source data | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.numberOfAttributes = function() { | |
| return Object.size(m_attributesMap); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return number of components of the attribute data | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.attributeNumberOfComponents = function(key) { | |
| if (m_attributesMap.hasOwnProperty(key)) { | |
| return m_attributesMap[key].m_numberOfComponents; | |
| } | |
| return 0; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return if the attribute data is normalized | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.normalized = function(key) { | |
| if (m_attributesMap.hasOwnProperty(key)) { | |
| return m_attributesMap[key].m_normalized; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return size of the attribute data type | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.sizeOfAttributeDataType = function(key) { | |
| if (m_attributesMap.hasOwnProperty(key)) { | |
| return m_attributesMap[key].m_dataTypeSize; | |
| } | |
| return 0; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return attribute data type | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.attributeDataType = function(key) { | |
| if (m_attributesMap.hasOwnProperty(key)) { | |
| return m_attributesMap[key].m_dataType; | |
| } | |
| return undefined; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return attribute offset | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.attributeOffset = function(key) { | |
| if (m_attributesMap.hasOwnProperty(key)) { | |
| return m_attributesMap[key].m_offset; | |
| } | |
| return 0; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return attribute stride | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.attributeStride = function(key) { | |
| if (m_attributesMap.hasOwnProperty(key)) { | |
| return m_attributesMap[key].m_stride; | |
| } | |
| return 0; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Virtual function to insert new vertex data at the end | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.pushBack = function(vertexData) { | |
| // Should be implemented by the base class | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Insert new data block to the raw data | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.insert = function(data) { | |
| var i; | |
| //m_data = m_data.concat(data); //no, slow on Safari | |
| /* If we will are given a Float32Array and don't have any other data, use | |
| * it directly. */ | |
| if (!m_data.length && data.length && data instanceof Float32Array) { | |
| m_data = data; | |
| return; | |
| } | |
| /* If our internal array is immutable and we will need to change it, create | |
| * a regular mutable array from it. */ | |
| if (!m_data.slice && (m_data.length || !data.slice)) { | |
| m_data = Array.prototype.slice.call(m_data); | |
| } | |
| if (!data.length) { | |
| /* data is a singular value, so append it to our array */ | |
| m_data[m_data.length] = data; | |
| } else { | |
| /* We don't have any data currently, so it is faster to copy the data | |
| * using slice. */ | |
| if (!m_data.length && data.slice) { | |
| m_data = data.slice(0); | |
| } else { | |
| for (i = 0; i < data.length; i++) { | |
| m_data[m_data.length] = data[i]; | |
| } | |
| } | |
| } | |
| }; | |
| this.insertAt = function(index, data) { | |
| var i; | |
| if (!data.length) { | |
| m_data[index] = data; | |
| } else { | |
| for (i = 0; i < data.length; i++) { | |
| m_data[index*data.length+i] = data[i]; | |
| } | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return name of the source data | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.name = function() { | |
| return m_name; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set name of the source data | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setName = function(name) { | |
| m_name = name; | |
| }; | |
| return this; | |
| }; | |
| vgl.sourceDataAnyfv = function(size, key, arg) { | |
| if (!(this instanceof vgl.sourceDataAnyfv)) { | |
| return new vgl.sourceDataAnyfv(size, key, arg); | |
| } | |
| vgl.sourceData.call(this, arg); | |
| this.addAttribute(key, gl.FLOAT, | |
| 4, 0, size * 4, size, false); | |
| this.pushBack = function(value) { | |
| this.insert(value); | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.sourceDataAnyfv, vgl.sourceData); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class sourceDataP3T3f | |
| * | |
| * @returns {vgl.sourceDataP3T3f} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.sourceDataP3T3f = function(arg) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.sourceDataP3T3f)) { | |
| return new vgl.sourceDataP3T3f(arg); | |
| } | |
| vgl.sourceData.call(this, arg); | |
| this.addAttribute(vgl.vertexAttributeKeys.Position, gl.FLOAT, 4, 0, 6 * 4, 3, | |
| false); | |
| this.addAttribute(vgl.vertexAttributeKeys.TextureCoordinate, gl.FLOAT, 4, 12, | |
| 6 * 4, 3, false); | |
| this.pushBack = function(value) { | |
| this.insert(value.m_position); | |
| this.insert(value.m_texCoordinate); | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.sourceDataP3T3f, vgl.sourceData); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class sourceDataP3N3f | |
| * | |
| * @returns {vgl.sourceDataP3N3f} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.sourceDataP3N3f = function(arg) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.sourceDataP3N3f)) { | |
| return new vgl.sourceDataP3N3f(arg); | |
| } | |
| vgl.sourceData.call(this, arg); | |
| this.addAttribute(vgl.vertexAttributeKeys.Position, gl.FLOAT, 4, 0, 6 * 4, 3, | |
| false); | |
| this.addAttribute(vgl.vertexAttributeKeys.Normal, gl.FLOAT, 4, 12, 6 * 4, 3, | |
| false); | |
| this.pushBack = function(value) { | |
| this.insert(value.m_position); | |
| this.insert(value.m_normal); | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.sourceDataP3N3f, vgl.sourceData); | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class sourceDataP3fv | |
| * | |
| * @returns {vgl.sourceDataP3fv} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.sourceDataP3fv = function(arg) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.sourceDataP3fv)) { | |
| return new vgl.sourceDataP3fv(arg); | |
| } | |
| vgl.sourceData.call(this, arg); | |
| this.addAttribute(vgl.vertexAttributeKeys.Position, gl.FLOAT, 4, 0, 3 * 4, 3, | |
| false); | |
| this.pushBack = function(value) { | |
| this.insert(value); | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.sourceDataP3fv, vgl.sourceData); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class sourceDataT2fv | |
| * | |
| * @returns {vgl.sourceDataT2fv} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.sourceDataT2fv = function(arg) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.sourceDataT2fv)) { | |
| return new vgl.sourceDataT2fv(arg); | |
| } | |
| vgl.sourceData.call(this, arg); | |
| this.addAttribute(vgl.vertexAttributeKeys.TextureCoordinate, gl.FLOAT, 4, 0, | |
| 2 * 4, 2, false); | |
| this.pushBack = function(value) { | |
| this.insert(value); | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.sourceDataT2fv, vgl.sourceData); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class sourceDataC3fv | |
| * | |
| * @returns {vgl.sourceDataC3fv} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.sourceDataC3fv = function(arg) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.sourceDataC3fv)) { | |
| return new vgl.sourceDataC3fv(arg); | |
| } | |
| vgl.sourceData.call(this, arg); | |
| this.addAttribute(vgl.vertexAttributeKeys.Color, gl.FLOAT, 4, 0, 3 * 4, 3, false); | |
| this.pushBack = function(value) { | |
| this.insert(value); | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.sourceDataC3fv, vgl.sourceData); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class sourceDataSf meant to hold scalar float values | |
| * | |
| * @class | |
| * @returns {vgl.sourceDataSf} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.sourceDataSf = function(arg) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.sourceDataSf)) { | |
| return new vgl.sourceDataSf(arg); | |
| } | |
| var m_min = null, | |
| m_max = null, | |
| m_fixedmin = null, | |
| m_fixedmax = null; | |
| vgl.sourceData.call(this, arg); | |
| this.addAttribute(vgl.vertexAttributeKeys.Scalar, gl.FLOAT, 4, 0, 4, 1, false); | |
| this.pushBack = function(value) { | |
| if (m_max === null || value > m_max) { | |
| m_max = value; | |
| } | |
| if (m_min === null || value < m_min) { | |
| m_min = value; | |
| } | |
| //this.insert(value); //no, slow on Safari | |
| this.data()[this.data().length] = value; | |
| }; | |
| this.insertAt = function(index, value) { | |
| if (m_max === null || value > m_max) { | |
| m_max = value; | |
| } | |
| if (m_min === null || value < m_min) { | |
| m_min = value; | |
| } | |
| //call superclass ?? | |
| //vgl.sourceData.insertAt.call(this, index, value); | |
| this.data()[index] = value; | |
| }; | |
| this.scalarRange = function() { | |
| if (m_fixedmin === null || m_fixedmax === null) { | |
| return [m_min, m_max]; | |
| } | |
| return [m_fixedmin, m_fixedmax]; | |
| }; | |
| this.setScalarRange = function(min, max) { | |
| m_fixedmin = min; | |
| m_fixedmax = max; | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.sourceDataSf, vgl.sourceData); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class sourceDataDf meant to hold data float values | |
| * | |
| * This source array is the best way to pass a array of floats to the shader | |
| * that has one entry for each of the vertices. | |
| * | |
| * @class | |
| * @returns {vgl.sourceDataDf} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.sourceDataDf = function(arg) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.sourceDataDf)) { | |
| return new vgl.sourceDataDf(arg); | |
| } | |
| var m_min = null, | |
| m_max = null, | |
| m_fixedmin = null, | |
| m_fixedmax = null; | |
| vgl.sourceData.call(this, arg); | |
| this.addAttribute(vgl.vertexAttributeKeys.Scalar, gl.FLOAT, | |
| 4, 0, 4, 1, false); | |
| this.pushBack = function(value) { | |
| this.data()[this.data().length] = value; | |
| }; | |
| this.insertAt = function(index, value) { | |
| this.data()[index] = value; | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.sourceDataDf, vgl.sourceData); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class geometryData | |
| * | |
| * @class | |
| * @returns {vgl.geometryData} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| vgl.geometryData = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.geometryData)) { | |
| return new vgl.geometryData(); | |
| } | |
| vgl.data.call(this); | |
| /** @private */ | |
| var m_name = "", | |
| m_primitives = [], | |
| m_sources = [], | |
| m_bounds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], | |
| m_computeBoundsTimestamp = vgl.timestamp(), | |
| m_boundsDirtyTimestamp = vgl.timestamp(); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return type | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.type = function() { | |
| return vgl.data.geometry; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return ID of the geometry data | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.name = function() { | |
| return m_name; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set name of the geometry data | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setName = function(name) { | |
| m_name = name; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add new source | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.addSource = function(source, sourceName) { | |
| // @todo Check if the incoming source has duplicate keys | |
| if (sourceName !== undefined) { | |
| source.setName(sourceName); | |
| } | |
| // NOTE This might not work on IE8 or lower | |
| if (m_sources.indexOf(source) === -1) { | |
| m_sources.push(source); | |
| if (source.hasKey(vgl.vertexAttributeKeys.Position)) { | |
| m_boundsDirtyTimestamp.modified(); | |
| } | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return source for a given index. Returns 0 if not found. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.source = function(index) { | |
| if (index < m_sources.length) { | |
| return m_sources[index]; | |
| } | |
| return 0; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return source with a specified name. Returns 0 if not found. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.sourceByName = function (sourceName) { | |
| for (var i = 0; i < m_sources.length; i += 1) { | |
| if (m_sources[i].name() === sourceName) { | |
| return m_sources[i]; | |
| } | |
| } | |
| return 0; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return number of sources | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.numberOfSources = function() { | |
| return m_sources.length; | |
| }; | |
| /** | |
| * Return source data given a key | |
| */ | |
| this.sourceData = function(key) { | |
| var i; | |
| for (i = 0; i < m_sources.length; ++i) { | |
| if (m_sources[i].hasKey(key)) { | |
| return m_sources[i]; | |
| } | |
| } | |
| return null; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add new primitive | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.addPrimitive = function(primitive) { | |
| m_primitives.push(primitive); | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return primitive for a given index. Returns null if not found. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.primitive = function(index) { | |
| if (index < m_primitives.length) { | |
| return m_primitives[index]; | |
| } | |
| return null; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return number of primitives | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.numberOfPrimitives = function() { | |
| return m_primitives.length; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return bounds [minX, maxX, minY, maxY, minZ, maxZ] | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.bounds = function() { | |
| if (m_boundsDirtyTimestamp.getMTime() > m_computeBoundsTimestamp.getMTime()) { | |
| this.computeBounds(); | |
| } | |
| return m_bounds; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Check if bounds are dirty or mark them as such. | |
| * | |
| * @param dirty: true to set bounds as dirty. | |
| * Return true if bounds are dirty. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.boundsDirty = function (dirty) { | |
| if (dirty) { | |
| m_boundsDirtyTimestamp.modified(); | |
| } | |
| return m_boundsDirtyTimestamp.getMTime() > m_computeBoundsTimestamp.getMTime(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Reset bounds | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.resetBounds = function() { | |
| m_bounds[0] = 0.0; | |
| m_bounds[1] = 0.0; | |
| m_bounds[2] = 0.0; | |
| m_bounds[3] = 0.0; | |
| m_bounds[4] = 0.0; | |
| m_bounds[5] = 0.0; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set bounds | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setBounds = function(minX, maxX, minY, maxY, minZ, maxZ) { | |
| m_bounds[0] = minX; | |
| m_bounds[1] = maxX; | |
| m_bounds[2] = minY; | |
| m_bounds[3] = maxY; | |
| m_bounds[4] = minZ; | |
| m_bounds[5] = maxZ; | |
| m_computeBoundsTimestamp.modified(); | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute bounds | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeBounds = function() { | |
| if (m_boundsDirtyTimestamp.getMTime() > m_computeBoundsTimestamp.getMTime()) { | |
| var attr = vgl.vertexAttributeKeys.Position, | |
| sourceData = this.sourceData(attr), | |
| data = sourceData.data(), | |
| numberOfComponents = sourceData.attributeNumberOfComponents(attr), | |
| stride = sourceData.attributeStride(attr), | |
| offset = sourceData.attributeOffset(attr), | |
| sizeOfDataType = sourceData.sizeOfAttributeDataType(attr), | |
| count = data.length, | |
| j, ib, jb, maxv, minv, | |
| value = null, | |
| vertexIndex; | |
| // We advance by index, not by byte | |
| stride /= sizeOfDataType; | |
| offset /= sizeOfDataType; | |
| this.resetBounds(); | |
| for (j = 0; j < numberOfComponents; ++j) { | |
| ib = j * 2; | |
| jb = j * 2 + 1; | |
| if (count) { | |
| maxv = minv = m_bounds[jb] = data[offset + j]; | |
| } else { | |
| maxv = minv = 0; | |
| } | |
| for (vertexIndex = offset + stride + j; vertexIndex < count; | |
| vertexIndex += stride) { | |
| value = data[vertexIndex]; | |
| if (value > maxv) { | |
| maxv = value; | |
| } | |
| if (value < minv) { | |
| minv = value; | |
| } | |
| } | |
| m_bounds[ib] = minv; m_bounds[jb] = maxv; | |
| } | |
| m_computeBoundsTimestamp.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns the vertex closest to a given position | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.findClosestVertex = function(point) { | |
| var attr = vgl.vertexAttributeKeys.Position, | |
| sourceData = this.sourceData(attr), | |
| sizeOfDataType = sourceData.sizeOfAttributeDataType(attr), | |
| numberOfComponents = sourceData.attributeNumberOfComponents(attr), | |
| data = sourceData.data(), | |
| stride = sourceData.attributeStride(attr) / sizeOfDataType, | |
| offset = sourceData.attributeOffset(attr) / sizeOfDataType, | |
| minDist = Number.MAX_VALUE, | |
| minIndex = null, | |
| vi, vPos, dx, dy, dz, dist, i; | |
| // assume positions are always triplets | |
| if (numberOfComponents !== 3) { | |
| console.log('[warning] Find closest vertex assumes three' + | |
| 'component vertex '); | |
| } | |
| if (!point.z) { | |
| point = {x:point.x, y:point.y, z:0}; | |
| } | |
| for (vi = offset, i = 0; vi < data.length; vi += stride, i++) { | |
| vPos = [data[vi], | |
| data[vi + 1], | |
| data[vi + 2]]; | |
| dx = vPos[0] - point.x; | |
| dy = vPos[1] - point.y; | |
| dz = vPos[2] - point.z; | |
| dist = Math.sqrt(dx*dx + dy*dy + dz*dz); | |
| if (dist < minDist) { | |
| minDist = dist; | |
| minIndex = i; | |
| } | |
| } | |
| return minIndex; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns the requested vertex position | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getPosition = function(index) { | |
| var attr = vgl.vertexAttributeKeys.Position, | |
| sourceData = this.sourceData(attr), | |
| sizeOfDataType = sourceData.sizeOfAttributeDataType(attr), | |
| numberOfComponents = sourceData.attributeNumberOfComponents(attr), | |
| data = sourceData.data(), | |
| stride = sourceData.attributeStride(attr) / sizeOfDataType, | |
| offset = sourceData.attributeOffset(attr) / sizeOfDataType; | |
| // assume positions are always triplets | |
| if (numberOfComponents !== 3) { | |
| console.log("[warning] getPosition assumes three component data"); | |
| } | |
| return [ data[offset + index*stride], | |
| data[offset + index*stride + 1], | |
| data[offset + index*stride + 2] ]; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns the scalar corresponding to a given vertex index | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getScalar = function(index) { | |
| var attr = vgl.vertexAttributeKeys.Scalar, | |
| sourceData = this.sourceData(attr), | |
| numberOfComponents, sizeOfDataType, data, stride, offset; | |
| if (!sourceData) { | |
| return null; | |
| } | |
| numberOfComponents = sourceData.attributeNumberOfComponents(attr); | |
| sizeOfDataType = sourceData.sizeOfAttributeDataType(attr); | |
| data = sourceData.data(); | |
| stride = sourceData.attributeStride(attr) / sizeOfDataType; | |
| offset = sourceData.attributeOffset(attr) / sizeOfDataType; | |
| //console.log("index for scalar is " + index); | |
| //console.log("offset for scalar is " + offset); | |
| //console.log("stride for scalar is " + stride); | |
| //console.log("have " + data.length + " scalars"); | |
| if (index * stride + offset >= data.length) { | |
| console.log("access out of bounds in getScalar"); | |
| } | |
| return data[index * stride + offset]; | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.geometryData, vgl.data); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, gl, ogs, vec4, Float32Array, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class mapper | |
| * | |
| * @class | |
| * @returns {vgl.mapper} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.mapper = function(arg) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.mapper)) { | |
| return new vgl.mapper(arg); | |
| } | |
| vgl.boundingObject.call(this); | |
| /** @private */ | |
| arg = arg || {}; | |
| var m_dirty = true, | |
| m_color = [ 0.0, 1.0, 1.0 ], | |
| m_geomData = null, | |
| m_buffers = [], | |
| m_bufferVertexAttributeMap = {}, | |
| m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw, | |
| m_glCompileTimestamp = vgl.timestamp(); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Delete cached VBO if any | |
| * | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function deleteVertexBufferObjects() { | |
| var i; | |
| for (i = 0; i < m_buffers.length; ++i) { | |
| gl.deleteBuffer(m_buffers[i]); | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create new VBO for all its geometryData sources and primitives | |
| * | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function createVertexBufferObjects() { | |
| if (m_geomData) { | |
| var numberOfSources = m_geomData.numberOfSources(), | |
| i, j, k, bufferId = null, keys, ks, numberOfPrimitives, data; | |
| for (i = 0; i < numberOfSources; ++i) { | |
| bufferId = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, bufferId); | |
| data = m_geomData.source(i).data(); | |
| if (!(data instanceof Float32Array)) { | |
| data = new Float32Array(data); | |
| } | |
| gl.bufferData(gl.ARRAY_BUFFER, data, | |
| m_dynamicDraw ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW); | |
| keys = m_geomData.source(i).keys(); | |
| ks = []; | |
| for (j = 0; j < keys.length; ++j) { | |
| ks.push(keys[j]); | |
| } | |
| m_bufferVertexAttributeMap[i] = ks; | |
| m_buffers[i] = bufferId; | |
| } | |
| numberOfPrimitives = m_geomData.numberOfPrimitives(); | |
| for (k = 0; k < numberOfPrimitives; ++k) { | |
| bufferId = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, bufferId); | |
| gl.bufferData(gl.ARRAY_BUFFER, m_geomData.primitive(k) | |
| .indices(), gl.STATIC_DRAW); | |
| m_buffers[i++] = bufferId; | |
| } | |
| m_glCompileTimestamp.modified(); | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Clear cache related to buffers | |
| * | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function cleanUpDrawObjects() { | |
| m_bufferVertexAttributeMap = {}; | |
| m_buffers = []; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Setup draw objects; Delete old ones and create new ones | |
| * | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function setupDrawObjects() { | |
| // Delete buffer objects from past if any. | |
| deleteVertexBufferObjects(); | |
| // Clear any cache related to buffers | |
| cleanUpDrawObjects(); | |
| // Now construct the new ones. | |
| createVertexBufferObjects(); | |
| m_dirty = false; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute bounds of the data | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeBounds = function() { | |
| if (m_geomData === null || typeof m_geomData === 'undefined') { | |
| this.resetBounds(); | |
| return; | |
| } | |
| var computeBoundsTimestamp = this.computeBoundsTimestamp(), | |
| boundsDirtyTimestamp = this.boundsDirtyTimestamp(), | |
| geomBounds = null; | |
| if (boundsDirtyTimestamp.getMTime() > computeBoundsTimestamp.getMTime()) { | |
| geomBounds = m_geomData.bounds(); | |
| this.setBounds(geomBounds[0], geomBounds[1], geomBounds[2], | |
| geomBounds[3], geomBounds[4], geomBounds[5]) ; | |
| computeBoundsTimestamp.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get solid color of the geometry | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.color = function() { | |
| return m_color; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set solid color of the geometry. Default is teal [1.0, 1.0, 1.0] | |
| * | |
| * @param r Red component of the color [0.0 - 1.0] | |
| * @param g Green component of the color [0.0 - 1.0] | |
| * @param b Blue component of the color [0.0 - 1.0] | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setColor = function(r, g, b) { | |
| m_color[0] = r; | |
| m_color[1] = g; | |
| m_color[2] = b; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return stored geometry data if any | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.geometryData = function() { | |
| return m_geomData; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Connect mapper to its geometry data | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setGeometryData = function(geom) { | |
| if (m_geomData !== geom) { | |
| m_geomData = geom; | |
| this.modified(); | |
| this.boundsDirtyTimestamp().modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update the buffer used for a named source. | |
| * | |
| * @param {String} sourceName The name of the source to update. | |
| * @param {Object[] or Float32Array} values The values to use for the source. | |
| * If not specified, use the source's own buffer. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.updateSourceBuffer = function (sourceName, values) { | |
| var bufferIndex = -1; | |
| for (var i = 0; i < m_geomData.numberOfSources(); i += 1) { | |
| if (m_geomData.source(i).name() === sourceName) { | |
| bufferIndex = i; | |
| break; | |
| } | |
| } | |
| if (bufferIndex < 0 || bufferIndex >= m_buffers.length) { | |
| return false; | |
| } | |
| if (!values) { | |
| values = m_geomData.source(i).dataToFloat32Array(); | |
| } | |
| gl.bindBuffer(gl.ARRAY_BUFFER, m_buffers[bufferIndex]); | |
| if (values instanceof Float32Array) { | |
| gl.bufferSubData(gl.ARRAY_BUFFER, 0, values); | |
| } else { | |
| gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(values)); | |
| } | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the buffer used for a named source. If the current buffer isn't a | |
| * Float32Array, it is converted to one. This array can then be modified | |
| * directly, after which updateSourceBuffer can be called to update the | |
| * GL array. | |
| * | |
| * @param {String} sourceName The name of the source to update. | |
| * @returns {Float32Array} An array used for this source. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getSourceBuffer = function (sourceName) { | |
| var source = m_geomData.sourceByName(sourceName); | |
| if (!source) { | |
| return new Float32Array(); | |
| } | |
| return source.dataToFloat32Array(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Render the mapper | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.render = function(renderState) { | |
| if (this.getMTime() > m_glCompileTimestamp.getMTime()) { | |
| setupDrawObjects(renderState); | |
| } | |
| // Fixed vertex color | |
| gl.vertexAttrib3fv(vgl.vertexAttributeKeys.Color, this.color()); | |
| // TODO Use renderState | |
| var bufferIndex = 0, | |
| j = 0, i, noOfPrimitives = null, primitive = null; | |
| for (i in m_bufferVertexAttributeMap) { | |
| if (m_bufferVertexAttributeMap.hasOwnProperty(i)) { | |
| gl.bindBuffer(gl.ARRAY_BUFFER, m_buffers[bufferIndex]); | |
| for (j = 0; j < m_bufferVertexAttributeMap[i].length; ++j) { | |
| renderState.m_material | |
| .bindVertexData(renderState, m_bufferVertexAttributeMap[i][j]); | |
| } | |
| ++bufferIndex; | |
| } | |
| } | |
| noOfPrimitives = m_geomData.numberOfPrimitives(); | |
| for (j = 0; j < noOfPrimitives; ++j) { | |
| gl.bindBuffer(gl.ARRAY_BUFFER, m_buffers[bufferIndex++]); | |
| primitive = m_geomData.primitive(j); | |
| switch(primitive.primitiveType()) { | |
| case gl.POINTS: | |
| gl.drawArrays (gl.POINTS, 0, primitive.numberOfIndices()); | |
| break; | |
| case gl.LINES: | |
| gl.drawArrays (gl.LINES, 0, primitive.numberOfIndices()); | |
| break; | |
| case gl.LINE_STRIP: | |
| gl.drawArrays (gl.LINE_STRIP, 0, primitive.numberOfIndices()); | |
| break; | |
| case gl.TRIANGLES: | |
| gl.drawArrays (gl.TRIANGLES, 0, primitive.numberOfIndices()); | |
| break; | |
| case gl.TRIANGLE_STRIP: | |
| gl.drawArrays (gl.TRIANGLE_STRIP, 0, primitive.numberOfIndices()); | |
| break; | |
| } | |
| gl.bindBuffer (gl.ARRAY_BUFFER, null); | |
| } | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.mapper, vgl.boundingObject); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.groupMapper = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.groupMapper)) { | |
| return new vgl.groupMapper(); | |
| } | |
| vgl.mapper.call(this); | |
| /** @private */ | |
| var m_createMappersTimestamp = vgl.timestamp(), | |
| m_mappers = [], | |
| m_geomDataArray = []; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return stored geometry data if any | |
| * | |
| * @param index optional | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.geometryData = function(index) { | |
| if (index !== undefined && index < m_geomDataArray.length ) { | |
| return m_geomDataArray[index]; | |
| } | |
| if (m_geomDataArray.length > 0) { | |
| return m_geomDataArray[0]; | |
| } | |
| return null; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Connect mapper to its geometry data | |
| * | |
| * @param geom {vgl.geomData} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setGeometryData = function(geom) { | |
| if (m_geomDataArray.length === 1) { | |
| if (m_geomDataArray[0] === geom) { | |
| return; | |
| } | |
| } | |
| m_geomDataArray = []; | |
| m_geomDataArray.push(geom); | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return stored geometry data array if any | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.geometryDataArray = function() { | |
| return m_geomDataArray; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Connect mapper to its geometry data | |
| * | |
| * @param geoms {Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setGeometryDataArray = function(geoms) { | |
| if (geoms instanceof Array) { | |
| if (m_geomDataArray !== geoms) { | |
| m_geomDataArray = []; | |
| m_geomDataArray = geoms; | |
| this.modified(); | |
| return true; | |
| } | |
| } else { | |
| console.log('[error] Requies array of geometry data'); | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute bounds of the data | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeBounds = function() { | |
| if (m_geomDataArray === null || | |
| m_geomDataArray === undefined) { | |
| this.resetBounds(); | |
| return; | |
| } | |
| var computeBoundsTimestamp = this.computeBoundsTimestamp(), | |
| boundsDirtyTimestamp = this.boundsDirtyTimestamp(), | |
| m_bounds = this.bounds(), | |
| geomBounds = null, | |
| i = null; | |
| if (boundsDirtyTimestamp.getMTime() > | |
| computeBoundsTimestamp.getMTime()) { | |
| for (i = 0; i < m_geomDataArray.length; ++i) { | |
| geomBounds = m_geomDataArray[i].bounds(); | |
| if (m_bounds[0] > geomBounds[0]) { | |
| m_bounds[0] = geomBounds[0]; | |
| } | |
| if (m_bounds[1] < geomBounds[1]) { | |
| m_bounds[1] = geomBounds[1]; | |
| } | |
| if (m_bounds[2] > geomBounds[2]) { | |
| m_bounds[2] = geomBounds[2]; | |
| } | |
| if (m_bounds[3] < geomBounds[3]) { | |
| m_bounds[3] = geomBounds[3]; | |
| } | |
| if (m_bounds[4] > geomBounds[4]) { | |
| m_bounds[4] = geomBounds[4]; | |
| } | |
| if (m_bounds[5] < geomBounds[5]) { | |
| m_bounds[5] = geomBounds[5]; | |
| } | |
| } | |
| this.modified(); | |
| computeBoundsTimestamp.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Render the mapper | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.render = function(renderState) { | |
| var i = null; | |
| if (this.getMTime() > m_createMappersTimestamp.getMTime()) { | |
| // NOTE Hoping that it will release the graphics resources | |
| for (i = 0; i < m_geomDataArray.length; ++i) { | |
| m_mappers.push(vgl.mapper()); | |
| m_mappers[i].setGeometryData(m_geomDataArray[i]); | |
| } | |
| m_createMappersTimestamp.modified(); | |
| } | |
| for (i = 0; i < m_mappers.length; ++i) { | |
| m_mappers[i].render(renderState); | |
| } | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.groupMapper, vgl.mapper); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.materialAttributeType = { | |
| "Undefined" : 0x0, | |
| "ShaderProgram" : 0x1, | |
| "Texture" : 0x2, | |
| "Blend" : 0x3, | |
| "Depth" : 0x4 | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class materialAttribute | |
| * | |
| * @class | |
| * @param type | |
| * @returns {vgl.materialAttribute} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.materialAttribute = function(type) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.materialAttribute)) { | |
| return new vgl.materialAttribute(); | |
| } | |
| vgl.object.call(this); | |
| /** @private */ | |
| var m_type = type, | |
| m_enabled = true; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return tyep of the material attribute | |
| * | |
| * @returns {*} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.type = function() { | |
| return m_type; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return if material attribute is enabled or not | |
| * | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.enabled = function() { | |
| return m_enabled; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Setup (initialize) the material attribute | |
| * | |
| * @param renderState | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setup = function(renderState) { | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind and activate the material attribute | |
| * | |
| * @param renderState | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.bind = function(renderState) { | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Undo bind and deactivate the material | |
| * | |
| * @param renderState | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.undoBind = function(renderState) { | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize vertex data for the material attribute | |
| * | |
| * @param renderState | |
| * @param key | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setupVertexData = function(renderState, key) { | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind and activate vertex specific data | |
| * | |
| * @param renderState | |
| * @param key | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.bindVertexData = function(renderState, key) { | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Undo bind and deactivate vertex specific data | |
| * | |
| * @param renderState | |
| * @param key | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.undoBindVertexData = function(renderState, key) { | |
| return false; | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.materialAttribute, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, gl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of clas blendFunction | |
| * | |
| * @class | |
| * @param source | |
| * @param destination | |
| * @returns {vgl.blendFunction} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.blendFunction = function(source, destination) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.blendFunction)) { | |
| return new vgl.blendFunction(source, destination); | |
| } | |
| /** @private */ | |
| var m_source = source, | |
| m_destination = destination; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Apply blend function to the current state | |
| * | |
| * @param {vgl.renderState} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.apply = function(renderState) { | |
| gl.blendFuncSeparate(m_source, m_destination, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); | |
| }; | |
| return this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class blend | |
| * | |
| * @returns {vgl.blend} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| vgl.blend = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.blend)) { | |
| return new vgl.blend(); | |
| } | |
| vgl.materialAttribute.call( | |
| this, vgl.materialAttributeType.Blend); | |
| /** @private */ | |
| var m_wasEnabled = false, | |
| m_blendFunction = vgl.blendFunction(gl.SRC_ALPHA, | |
| gl.ONE_MINUS_SRC_ALPHA); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind blend attribute | |
| * | |
| * @param {vgl.renderState} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.bind = function(renderState) { | |
| m_wasEnabled = gl.isEnabled(gl.BLEND); | |
| if (this.enabled()) { | |
| gl.enable(gl.BLEND); | |
| m_blendFunction.apply(renderState); | |
| } | |
| else { | |
| gl.disable(gl.BLEND); | |
| } | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Undo bind blend attribute | |
| * | |
| * @param {vgl.renderState} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.undoBind = function(renderState) { | |
| if (m_wasEnabled) { | |
| gl.enable(gl.BLEND); | |
| } | |
| else { | |
| gl.disable(gl.BLEND); | |
| } | |
| return true; | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.blend, vgl.materialAttribute); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class material | |
| * | |
| * @class | |
| * @returns {vgl.material} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.material = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.material)) { | |
| return new vgl.material(); | |
| } | |
| vgl.object.call(this); | |
| // / Private member variables | |
| var m_shaderProgram = new vgl.shaderProgram(), | |
| m_binNumber = 100, | |
| m_textureAttributes = {}, | |
| m_attributes = {}; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return bin number for the material | |
| * | |
| * @default 100 | |
| * @returns {number} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.binNumber = function() { | |
| return m_binNumber; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set bin number for the material | |
| * | |
| * @param binNo | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setBinNumber = function(binNo) { | |
| m_binNumber = binNo; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Check if incoming attribute already exists in the material | |
| * | |
| * @param attr | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.exists = function(attr) { | |
| if (attr.type() === vgl.materialAttribute.Texture) { | |
| return m_textureAttributes.hasOwnProperty(attr); | |
| } | |
| return m_attributes.hasOwnProperty(attr); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get uniform given a name | |
| * @param name Uniform name | |
| * @returns {vgl.uniform} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.uniform = function(name) { | |
| if (m_shaderProgram) { | |
| return m_shaderProgram.uniform(name); | |
| } | |
| return null; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get material attribute | |
| * @param attr Attribute name | |
| * @returns {vgl.materialAttribute} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.attribute = function(name) { | |
| if (m_attributes.hasOwnProperty(name)) { | |
| return m_attributes[name]; | |
| } | |
| if (m_textureAttributes.hasOwnProperty(name)) { | |
| return m_textureAttributes[name]; | |
| } | |
| return null; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set a new attribute for the material | |
| * | |
| * This method replace any existing attribute except for textures as | |
| * materials can have multiple textures. | |
| * | |
| * @param attr | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setAttribute = function(attr) { | |
| if (attr.type() === vgl.materialAttributeType.Texture && | |
| m_textureAttributes[attr.textureUnit()] !== attr) { | |
| m_textureAttributes[attr.textureUnit()] = attr; | |
| this.modified(); | |
| return true; | |
| } | |
| if (m_attributes[attr.type()] === attr) { | |
| return false; | |
| } | |
| // Shader is a very special attribute | |
| if (attr.type() === vgl.materialAttributeType.ShaderProgram) { | |
| m_shaderProgram = attr; | |
| } | |
| m_attributes[attr.type()] = attr; | |
| this.modified(); | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add a new attribute to the material. | |
| * | |
| * @param attr | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.addAttribute = function(attr) { | |
| if (this.exists(attr)) { | |
| return false; | |
| } | |
| if (attr.type() === vgl.materialAttributeType.Texture) { | |
| // TODO Currently we don't check if we are replacing or not. | |
| // It would be nice to have a flag for it. | |
| m_textureAttributes[attr.textureUnit()] = attr; | |
| this.modified(); | |
| return true; | |
| } | |
| // Shader is a very special attribute | |
| if (attr.type() === vgl.materialAttributeType.ShaderProgram) { | |
| m_shaderProgram = attr; | |
| } | |
| m_attributes[attr.type()] = attr; | |
| this.modified(); | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return shader program used by the material | |
| * | |
| * @returns {vgl.shaderProgram} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.shaderProgram = function() { | |
| return m_shaderProgram; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Activate the material | |
| * | |
| * @param renderState | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.render = function(renderState) { | |
| this.bind(renderState); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Deactivate the material | |
| * | |
| * @param renderState | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.remove = function(renderState) { | |
| this.undoBind(renderState); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind and activate material states | |
| * | |
| * @param renderState | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.bind = function(renderState) { | |
| var key = null; | |
| for (key in m_attributes) { | |
| if (m_attributes.hasOwnProperty(key)) { | |
| m_attributes[key].bind(renderState); | |
| } | |
| } | |
| for (key in m_textureAttributes) { | |
| if (m_textureAttributes.hasOwnProperty(key)) { | |
| m_textureAttributes[key].bind(renderState); | |
| } | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Undo-bind and de-activate material states | |
| * | |
| * @param renderState | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.undoBind = function(renderState) { | |
| var key = null; | |
| for (key in m_attributes) { | |
| if (m_attributes.hasOwnProperty(key)) { | |
| m_attributes[key].undoBind(renderState); | |
| } | |
| } | |
| for (key in m_textureAttributes) { | |
| if (m_textureAttributes.hasOwnProperty(key)) { | |
| m_textureAttributes[key].undoBind(renderState); | |
| } | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind vertex data | |
| * | |
| * @param renderState | |
| * @param key | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.bindVertexData = function(renderState, key) { | |
| var i = null; | |
| for (i in m_attributes) { | |
| if (m_attributes.hasOwnProperty(i)) { | |
| m_attributes[i].bindVertexData(renderState, key); | |
| } | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Undo bind vertex data | |
| * | |
| * @param renderState | |
| * @param key | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.undoBindVertexData = function(renderState, key) { | |
| var i = null; | |
| for (i in m_attributes) { | |
| if (m_attributes.hasOwnProperty(i)) { | |
| m_attributes.undoBindVertexData(renderState, key); | |
| } | |
| } | |
| }; | |
| return this; | |
| }; | |
| vgl.material.RenderBin = { | |
| "Base" : 0, | |
| "Default" : 100, | |
| "Opaque" : 100, | |
| "Transparent" : 1000, | |
| "Overlay" : 10000 | |
| }; | |
| inherit(vgl.material, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, bitwise:true, indent: 2*/ | |
| /*global vgl, gl, ogs, vec2, vec3, vec4, mat4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class renderState | |
| * | |
| * @returns {vgl.renderState} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.renderState = function() { | |
| 'use strict'; | |
| this.m_modelViewMatrix = mat4.create(); | |
| this.m_normalMatrix = mat4.create(); | |
| this.m_projectionMatrix = null; | |
| this.m_material = null; | |
| this.m_mapper = null; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class renderer * | |
| * | |
| * @returns {vgl.renderer} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| vgl.renderer = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.renderer)) { | |
| return new vgl.renderer(); | |
| } | |
| vgl.object.call(this); | |
| /** @private */ | |
| var m_sceneRoot = new vgl.groupNode(), | |
| m_camera = new vgl.camera(), | |
| m_nearClippingPlaneTolerance = null, | |
| m_x = 0, | |
| m_y = 0, | |
| m_width = 0, | |
| m_height = 0, | |
| m_resizable = true, | |
| m_resetScene = true, | |
| m_layer = 0, | |
| m_resetClippingRange = true; | |
| m_camera.addChild(m_sceneRoot); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get width of the renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.width = function() { | |
| return m_width; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get height of the renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.height = function() { | |
| return m_height; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get layer this renderer is associated with | |
| * | |
| * @return {Number} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.layer = function () { | |
| return m_layer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set the layer this renderer is associated with. | |
| * | |
| * @param layerNo | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setLayer = function(layerNo) { | |
| m_layer = layerNo; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.isResizable = function() { | |
| return m_resizable; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setResizable = function(r) { | |
| m_resizable = r; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get background color | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.backgroundColor = function() { | |
| return m_camera.clearColor(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set background color of the renderer | |
| * | |
| * @param r | |
| * @param g | |
| * @param b | |
| * @param a | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setBackgroundColor = function(r, g, b, a) { | |
| m_camera.setClearColor(r, g, b, a); | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get scene root | |
| * | |
| * @returns {vgl.groupNode} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.sceneRoot = function() { | |
| return m_sceneRoot; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get main camera of the renderer | |
| * | |
| * @returns {vgl.camera} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.camera = function() { | |
| return m_camera; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Render the scene | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.render = function() { | |
| var i, renSt, children, actor = null, sortedActors = [], | |
| mvMatrixInv = mat4.create(), clearColor = null; | |
| gl.enable(gl.DEPTH_TEST); | |
| gl.depthFunc(gl.LEQUAL); | |
| if (m_camera.clearMask() & vgl.GL.ColorBufferBit) { | |
| clearColor = m_camera.clearColor(); | |
| gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); | |
| } | |
| if (m_camera.clearMask() & vgl.GL.DepthBufferBit) { | |
| gl.clearDepth(m_camera.clearDepth()); | |
| } | |
| gl.clear(m_camera.clearMask()); | |
| // Set the viewport for this renderer | |
| gl.viewport(m_x, m_y, m_width, m_height); | |
| renSt = new vgl.renderState(); | |
| children = m_sceneRoot.children(); | |
| if (children.length > 0 && m_resetScene) { | |
| this.resetCamera(); | |
| m_resetScene = false; | |
| } | |
| for ( i = 0; i < children.length; ++i) { | |
| actor = children[i]; | |
| // Compute the bounds even if the actor is not visible | |
| actor.computeBounds(); | |
| // If bin number is < 0, then don't even bother | |
| // rendering the data | |
| if (!actor.visible() || actor.material().binNumber() < 0) { | |
| continue; | |
| } | |
| sortedActors.push([actor.material().binNumber(), actor]); | |
| } | |
| // Now perform sorting | |
| sortedActors.sort(function(a, b) {return a[0] - b[0];}); | |
| for ( i = 0; i < sortedActors.length; ++i) { | |
| actor = sortedActors[i][1]; | |
| if (actor.referenceFrame() === | |
| vgl.boundingObject.ReferenceFrame.Relative) { | |
| mat4.multiply(renSt.m_modelViewMatrix, m_camera.viewMatrix(), | |
| actor.matrix()); | |
| renSt.m_projectionMatrix = m_camera.projectionMatrix(); | |
| } else { | |
| renSt.m_modelViewMatrix = actor.matrix(); | |
| renSt.m_projectionMatrix = mat4.create(); | |
| mat4.ortho(renSt.m_projectionMatrix, 0, m_width, 0, m_height, -1, 1); | |
| } | |
| mat4.invert(mvMatrixInv, renSt.m_modelViewMatrix); | |
| mat4.transpose(renSt.m_normalMatrix, mvMatrixInv); | |
| renSt.m_material = actor.material(); | |
| renSt.m_mapper = actor.mapper(); | |
| // TODO Fix this shortcut | |
| renSt.m_material.render(renSt); | |
| renSt.m_mapper.render(renSt); | |
| renSt.m_material.remove(renSt); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Automatically set up the camera based on visible actors | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.resetCamera = function() { | |
| m_camera.computeBounds(); | |
| var vn = m_camera.directionOfProjection(), | |
| visibleBounds = m_camera.bounds(), | |
| center = [ | |
| (visibleBounds[0] + visibleBounds[1]) / 2.0, | |
| (visibleBounds[2] + visibleBounds[3]) / 2.0, | |
| (visibleBounds[4] + visibleBounds[5]) / 2.0 | |
| ], | |
| diagonals = [ | |
| visibleBounds[1] - visibleBounds[0], | |
| visibleBounds[3] - visibleBounds[2], | |
| visibleBounds[5] - visibleBounds[4] | |
| ], | |
| radius = 0.0, | |
| aspect = m_camera.viewAspect(), | |
| angle = m_camera.viewAngle(), | |
| distance = null, | |
| vup = null; | |
| if (diagonals[0] > diagonals[1]) { | |
| if (diagonals[0] > diagonals[2]) { | |
| radius = diagonals[0] / 2.0; | |
| } else { | |
| radius = diagonals[2] / 2.0; | |
| } | |
| } else { | |
| if (diagonals[1] > diagonals[2]) { | |
| radius = diagonals[1] / 2.0; | |
| } else { | |
| radius = diagonals[2] / 2.0; | |
| } | |
| } | |
| // @todo Need to figure out what's happening here | |
| if (aspect >= 1.0) { | |
| angle = 2.0 * Math.atan(Math.tan(angle * 0.5) / aspect); | |
| } else { | |
| angle = 2.0 * Math.atan(Math.tan(angle * 0.5) * aspect); | |
| } | |
| distance = radius / Math.sin(angle * 0.5); | |
| vup = m_camera.viewUpDirection(); | |
| if (Math.abs(vec3.dot(vup, vn)) > 0.999) { | |
| m_camera.setViewUpDirection(-vup[2], vup[0], vup[1]); | |
| } | |
| m_camera.setFocalPoint(center[0], center[1], center[2]); | |
| m_camera.setPosition(center[0] + distance * -vn[0], | |
| center[1] + distance * -vn[1], center[2] + distance * -vn[2]); | |
| this.resetCameraClippingRange(visibleBounds); | |
| }; | |
| this.hasValidBounds = function(bounds) { | |
| if (bounds[0] == Number.MAX_VALUE || | |
| bounds[1] == -Number.MAX_VALUE || | |
| bounds[2] == Number.MAX_VALUE || | |
| bounds[3] == -Number.MAX_VALUE || | |
| bounds[4] == Number.MAX_VALUE || | |
| bounds[5] == -Number.MAX_VALUE) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Recalculate camera's clipping range | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.resetCameraClippingRange = function(bounds) { | |
| if (typeof bounds === 'undefined') { | |
| m_camera.computeBounds(); | |
| bounds = m_camera.bounds(); | |
| } | |
| if (!this.hasValidBounds(bounds)) { | |
| return; | |
| } | |
| var vn = m_camera.viewPlaneNormal(), | |
| position = m_camera.position(), | |
| a = -vn[0], | |
| b = -vn[1], | |
| c = -vn[2], | |
| d = -(a*position[0] + b*position[1] + c*position[2]), | |
| range = vec2.create(), | |
| dist = null, | |
| i = null, | |
| j = null, | |
| k = null; | |
| if (!m_resetClippingRange) { | |
| return; | |
| } | |
| // Set the max near clipping plane and the min far clipping plane | |
| range[0] = a * bounds[0] + b * bounds[2] + c * bounds[4] + d; | |
| range[1] = 1e-18; | |
| // Find the closest / farthest bounding box vertex | |
| for (k = 0; k < 2; k++ ) { | |
| for (j = 0; j < 2; j++) { | |
| for (i = 0; i < 2; i++) { | |
| dist = a * bounds[i] + b * bounds[2 + j] + c * bounds[4 + k] + d; | |
| range[0] = (dist < range[0]) ? (dist) : (range[0]); | |
| range[1] = (dist > range[1]) ? (dist) : (range[1]); | |
| } | |
| } | |
| } | |
| // Do not let the range behind the camera throw off the calculation. | |
| if (range[0] < 0.0) { | |
| range[0] = 0.0; | |
| } | |
| // Give ourselves a little breathing room | |
| range[0] = 0.99 * range[0] - (range[1] - range[0]) * 0.5; | |
| range[1] = 1.01 * range[1] + (range[1] - range[0]) * 0.5; | |
| // Make sure near is not bigger than far | |
| range[0] = (range[0] >= range[1]) ? (0.01 * range[1]) : (range[0]); | |
| // Make sure near is at least some fraction of far - this prevents near | |
| // from being behind the camera or too close in front. How close is too | |
| // close depends on the resolution of the depth buffer. | |
| if (!m_nearClippingPlaneTolerance) { | |
| m_nearClippingPlaneTolerance = 0.01; | |
| if (gl !== null && gl.getParameter(gl.DEPTH_BITS) > 16) { | |
| m_nearClippingPlaneTolerance = 0.001; | |
| } | |
| } | |
| // make sure the front clipping range is not too far from the far clippnig | |
| // range, this is to make sure that the zbuffer resolution is effectively | |
| // used. | |
| if (range[0] < m_nearClippingPlaneTolerance*range[1]) { | |
| range[0] = m_nearClippingPlaneTolerance*range[1]; | |
| } | |
| m_camera.setClippingRange(range[0], range[1]); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Resize viewport given a width and height | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.resize = function(width, height) { | |
| // @note: where do m_x and m_y come from? | |
| this.positionAndResize(m_x, m_y, width, height); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Resize viewport given a position, width and height | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.positionAndResize = function(x, y, width, height) { | |
| // TODO move this code to camera | |
| if (x < 0 || y < 0 || width < 0 || height < 0) { | |
| console.log('[error] Invalid position and resize values', | |
| x, y, width, height); | |
| } | |
| //If we're allowing this renderer to resize ... | |
| if (m_resizable) { | |
| m_width = width; | |
| m_height = height; | |
| m_camera.setViewAspect(m_width / m_height); | |
| this.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add new actor to the collection | |
| * | |
| * @param actor | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.addActor = function(actor) { | |
| if (actor instanceof vgl.actor) { | |
| m_sceneRoot.addChild(actor); | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return true if this renderer has this actor attached, false otherwise. | |
| * | |
| * @param actor | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.hasActor = function(actor) { | |
| return m_sceneRoot.hasChild(actor); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add an array of actors to the collection | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.addActors = function(actors) { | |
| var i = null; | |
| if (actors instanceof Array) { | |
| for (i = 0; i < actors.length; ++i) { | |
| m_sceneRoot.addChild(actors[i]); | |
| } | |
| this.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Remove the actor from the collection | |
| * | |
| * @param actor | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.removeActor = function(actor) { | |
| if (m_sceneRoot.children().indexOf(actor) !== -1) { | |
| m_sceneRoot.removeChild(actor); | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Remove actors from the collection | |
| * | |
| * @param actors | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.removeActors = function(actors) { | |
| if (!(actors instanceof Array)) { | |
| return false; | |
| } | |
| var i; | |
| for (i = 0; i < actors.length; ++i) { | |
| m_sceneRoot.removeChild(actors[i]); | |
| } | |
| this.modified(); | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Remove all actors for a renderer | |
| * | |
| * @returns {*} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.removeAllActors = function() { | |
| return m_sceneRoot.removeChildren(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform a point in the world space to display space | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.worldToDisplay = function(worldPt, viewMatrix, projectionMatrix, width, | |
| height) { | |
| var viewProjectionMatrix = mat4.create(), | |
| winX = null, | |
| winY = null, | |
| winZ = null, | |
| winW = null, | |
| clipPt = null; | |
| mat4.multiply(viewProjectionMatrix, projectionMatrix, viewMatrix); | |
| // Transform world to clipping coordinates | |
| clipPt = vec4.create(); | |
| vec4.transformMat4(clipPt, worldPt, viewProjectionMatrix); | |
| if (clipPt[3] !== 0.0) { | |
| clipPt[0] = clipPt[0] / clipPt[3]; | |
| clipPt[1] = clipPt[1] / clipPt[3]; | |
| clipPt[2] = clipPt[2] / clipPt[3]; | |
| clipPt[3] = 1.0; | |
| } | |
| winX = (((clipPt[0]) + 1) / 2.0) * width; | |
| // We calculate -point3D.getY() because the screen Y axis is | |
| // oriented top->down | |
| winY = ((1 - clipPt[1]) / 2.0) * height; | |
| winZ = clipPt[2]; | |
| winW = clipPt[3]; | |
| return vec4.fromValues(winX, winY, winZ, winW); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform a point in display space to world space | |
| * @param displayPt | |
| * @param viewMatrix | |
| * @param projectionMatrix | |
| * @param width | |
| * @param height | |
| * @returns {vec4} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.displayToWorld = function(displayPt, viewMatrix, projectionMatrix, | |
| width, height) { | |
| var x = (2.0 * displayPt[0] / width) - 1, | |
| y = -(2.0 * displayPt[1] / height) + 1, | |
| z = displayPt[2], | |
| viewProjectionInverse = mat4.create(), | |
| worldPt = null; | |
| mat4.multiply(viewProjectionInverse, projectionMatrix, viewMatrix); | |
| mat4.invert(viewProjectionInverse, viewProjectionInverse); | |
| worldPt = vec4.fromValues(x, y, z, 1); | |
| vec4.transformMat4(worldPt, worldPt, viewProjectionInverse); | |
| if (worldPt[3] !== 0.0) { | |
| worldPt[0] = worldPt[0] / worldPt[3]; | |
| worldPt[1] = worldPt[1] / worldPt[3]; | |
| worldPt[2] = worldPt[2] / worldPt[3]; | |
| worldPt[3] = 1.0; | |
| } | |
| return worldPt; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the focusDisplayPoint | |
| * @returns {vec4} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.focusDisplayPoint = function() { | |
| var focalPoint = m_camera.focalPoint(), | |
| focusWorldPt = vec4.fromValues( | |
| focalPoint[0], focalPoint[1], focalPoint[2], 1); | |
| return this.worldToDisplay( | |
| focusWorldPt, m_camera.viewMatrix(), | |
| m_camera.projectionMatrix(), m_width, m_height); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Will the scene be reset. | |
| * @returns {bool} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.resetScene = function() { | |
| return m_resetScene; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * If true the scene will be reset, otherwise the scene will not be | |
| * automatically reset. | |
| * | |
| * @param reset | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setResetScene = function(reset) { | |
| if (m_resetScene !== reset) { | |
| m_resetScene = reset; | |
| this.modified() | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Will the clipping range be reset | |
| * @returns {bool} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.resetClippingRange = function() { | |
| return m_resetClippingRange; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * If true the camera clipping range will be reset, otherwise the scene will | |
| * not be automatically reset. | |
| * | |
| * @param reset | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setResetClippingRange = function(reset) { | |
| if (m_resetClippingRange !== reset) { | |
| m_resetClippingRange = reset; | |
| this.modified() | |
| } | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.renderer, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // TODO Current we support only one context | |
| var gl = null; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class renderWindow | |
| * | |
| * @class | |
| * @returns {vgl.renderWindow} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.renderWindow = function(canvas) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.renderWindow)) { | |
| return new vgl.renderWindow(canvas); | |
| } | |
| vgl.object.call(this); | |
| /** @private */ | |
| var m_x = 0, | |
| m_y = 0, | |
| m_width = 400, | |
| m_height = 400, | |
| m_canvas = canvas, | |
| m_activeRender = null, | |
| m_renderers = []; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get size of the render window | |
| * | |
| * @returns {Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.windowSize = function() { | |
| return [ m_width, m_height ]; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set size of the render window | |
| * | |
| * @param width | |
| * @param height | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setWindowSize = function(width, height) { | |
| if (m_width !== width || m_height !== height) { | |
| m_width = width; | |
| m_height = height; | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get window position (top left coordinates) | |
| * | |
| * @returns {Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.windowPosition = function() { | |
| return [ m_x, m_y ]; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set window position (top left coordinates) | |
| * | |
| * @param x | |
| * @param y | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setWindowPosition = function(x, y) { | |
| if ((m_x !== x) || (m_y !== y)) { | |
| m_x = x; | |
| m_y = y; | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return all renderers contained in the render window | |
| * @returns {Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.renderers = function() { | |
| return m_renderers; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get active renderer of the the render window | |
| * | |
| * @returns vgl.renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.activeRenderer = function() { | |
| return m_activeRender; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add renderer to the render window | |
| * | |
| * @param ren | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.addRenderer = function(ren) { | |
| if (this.hasRenderer(ren) === false) { | |
| m_renderers.push(ren); | |
| if (m_activeRender === null) { | |
| m_activeRender = ren; | |
| } | |
| if (ren.layer() !== 0) { | |
| ren.camera().setClearMask(vgl.GL.DepthBufferBit); | |
| } | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Remove renderer from the render window | |
| * | |
| * @param ren | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.removeRenderer = function(ren) { | |
| var index = m_renderers.indexOf(ren); | |
| if (index !== -1) { | |
| if (m_activeRender === ren) { | |
| m_activeRender = null; | |
| } | |
| m_renderers.splice(index, 1); | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return a renderer at a given index | |
| * | |
| * @param index | |
| * @returns {vgl.renderer} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getRenderer = function(index) { | |
| if (index < m_renderers.length) { | |
| return m_renderers[index]; | |
| } | |
| console.log("[WARNING] Out of index array"); | |
| return null; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Check if the renderer exists | |
| * | |
| * @param ren | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.hasRenderer = function(ren) { | |
| var i; | |
| for (i = 0; i < m_renderers.length; ++i) { | |
| if (ren === m_renderers[i]) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Resize window | |
| * | |
| * @param width | |
| * @param height | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.resize = function(width, height) { | |
| this.positionAndResize(m_x, m_y, width, height); | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Resize and reposition the window | |
| * | |
| * @param x | |
| * @param y | |
| * @param width | |
| * @param height | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.positionAndResize = function(x, y, width, height) { | |
| m_x = x; | |
| m_y = y; | |
| m_width = width; | |
| m_height = height; | |
| var i; | |
| for (i = 0; i < m_renderers.length; ++i) { | |
| m_renderers[i].positionAndResize(m_x, m_y, m_width, m_height); | |
| } | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create the window | |
| * | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.createWindow = function() { | |
| // Initialize the global variable gl to null. | |
| gl = null; | |
| try { | |
| // Try to grab the standard context. If it fails, fallback to | |
| // experimental. | |
| gl = m_canvas.getContext("webgl") | |
| || m_canvas.getContext("experimental-webgl"); | |
| // Set width and height of renderers if not set already | |
| var i; | |
| for (i = 0; i < m_renderers.length; ++i) { | |
| if ((m_renderers[i].width() > m_width) || m_renderers[i].width() === 0 | |
| || (m_renderers[i].height() > m_height) | |
| || m_renderers[i].height() === 0) { | |
| m_renderers[i].resize(m_x, m_y, m_width, m_height); | |
| } | |
| } | |
| return true; | |
| } | |
| catch (e) { | |
| } | |
| // If we don't have a GL context, give up now | |
| if (!gl) { | |
| console("[ERROR] Unable to initialize WebGL. Your browser may not support it."); | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Delete this window and release any graphics resources | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.deleteWindow = function() { | |
| // TODO | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Render the scene | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.render = function() { | |
| var i; | |
| m_renderers.sort(function(a, b) {return a.layer() - b.layer();}); | |
| for (i = 0; i < m_renderers.length; ++i) { | |
| m_renderers[i].render(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the focusDisplayPoint from the activeRenderer | |
| * @returns {vec4} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.focusDisplayPoint = function() { | |
| return m_activeRender.focusDisplayPoint(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform a point in display space to world space | |
| * @param {Number} x | |
| * @param {Number} y | |
| * @param {vec4} focusDisplayPoint | |
| * @returns {vec4} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.displayToWorld = function(x, y, focusDisplayPoint, ren) { | |
| ren = ren === undefined ? ren = m_activeRender : ren; | |
| var camera = ren.camera(); | |
| if(!focusDisplayPoint) { | |
| focusDisplayPoint = ren.focusDisplayPoint(); | |
| } | |
| return ren.displayToWorld( | |
| vec4.fromValues(x, y, focusDisplayPoint[2], 1.0), camera.viewMatrix(), | |
| camera.projectionMatrix(), m_width, m_height); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform a point in display space to world space | |
| * @param {Number} x | |
| * @param {Number} y | |
| * @param {vec4} focusDisplayPoint | |
| * @returns {vec4} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.worldToDisplay = function(x, y, z, ren) { | |
| ren = ren === undefined ? ren = m_activeRender : ren; | |
| var camera = ren.camera(); | |
| return ren.worldToDisplay( | |
| vec4.fromValues(x, y, z, 1.0), camera.viewMatrix(), | |
| camera.projectionMatrix(), m_width, m_height); | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.renderWindow, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2, bitwise: true*/ | |
| /*global vgl, gl, ogs, vec3, vec4, mat4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class camera | |
| * | |
| * @class | |
| * @returns {vgl.camera} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.camera = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.camera)) { | |
| return new vgl.camera(); | |
| } | |
| vgl.groupNode.call(this); | |
| /** @private */ | |
| var m_viewAngle = (Math.PI * 30) / 180.0, | |
| m_position = vec4.fromValues(0.0, 0.0, 1.0, 1.0), | |
| m_focalPoint = vec4.fromValues(0.0, 0.0, 0.0, 1.0), | |
| m_centerOfRotation = vec3.fromValues(0.0, 0.0, 0.0), | |
| m_viewUp = vec4.fromValues(0.0, 1.0, 0.0, 0.0), | |
| m_rightDir = vec4.fromValues(1.0, 0.0, 0.0, 0.0), | |
| m_near = 0.01, | |
| m_far = 10000.0, | |
| m_viewAspect = 1.0, | |
| m_directionOfProjection = vec4.fromValues(0.0, 0.0, -1.0, 0.0), | |
| m_viewPlaneNormal = vec4.fromValues(0.0, 0.0, 1.0, 0.0), | |
| m_viewMatrix = mat4.create(), | |
| m_projectionMatrix = mat4.create(), | |
| m_computeModelViewMatrixTime = vgl.timestamp(), | |
| m_computeProjectMatrixTime = vgl.timestamp(), | |
| m_left = -1.0, | |
| m_right = 1.0, | |
| m_top = +1.0, | |
| m_bottom = -1.0, | |
| m_enableTranslation = true, | |
| m_enableRotation = true, | |
| m_enableScale = true, | |
| m_enableParallelProjection = false, | |
| m_clearColor = [1.0, 1.0, 1.0, 1.0], | |
| m_clearDepth = 1.0, | |
| m_clearMask = vgl.GL.ColorBufferBit | | |
| vgl.GL.DepthBufferBit; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get view angle of the camera | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.viewAngle = function() { | |
| return m_viewAngle; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set view angle of the camera in degrees, which is converted to radians. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setViewAngleDegrees = function(a) { | |
| m_viewAngle = (Math.PI * a) / 180.0; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set view angle of the camera in degrees, which is converted to radians. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setViewAngle = function(a) { | |
| if (m_enableScale) { | |
| m_viewAngle = a; | |
| this.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get position of the camera | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.position = function() { | |
| return m_position; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set position of the camera | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setPosition = function(x, y, z) { | |
| if (m_enableTranslation) { | |
| m_position = vec4.fromValues(x, y, z, 1.0); | |
| this.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get focal point of the camera | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.focalPoint = function() { | |
| return m_focalPoint; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set focal point of the camera | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setFocalPoint = function(x, y, z) { | |
| if (m_enableRotation && m_enableTranslation) { | |
| m_focalPoint = vec4.fromValues(x, y, z, 1.0); | |
| this.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get view-up direction of camera | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.viewUpDirection = function() { | |
| return m_viewUp; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set view-up direction of the camera | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setViewUpDirection = function(x, y, z) { | |
| m_viewUp = vec4.fromValues(x, y, z, 0); | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get center of rotation for camera | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.centerOfRotation = function() { | |
| return m_centerOfRotation; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set center of rotation for camera | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setCenterOfRotation = function(centerOfRotation) { | |
| m_centerOfRotation = centerOfRotation; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get clipping range of the camera | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.clippingRange = function() { | |
| return [m_near, m_far]; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set clipping range of the camera | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setClippingRange = function(near, far) { | |
| m_near = near; | |
| m_far = far; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get view aspect | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.viewAspect = function() { | |
| return m_viewAspect; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set view aspect | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setViewAspect = function(aspect) { | |
| m_viewAspect = aspect; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return active mode for scaling (enabled / disabled) | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.enableScale = function(flag) { | |
| return m_enableScale; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Enable/disable scaling | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setEnableScale = function(flag) { | |
| if (flag !== m_enableScale) { | |
| m_enableScale = flag; | |
| this.modified(); | |
| return true; | |
| } | |
| return m_enableScale; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return active mode for rotation (enabled / disabled) | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.enableRotation = function(f) { | |
| return m_enableRotation; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Enable / disable rotation | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setEnableRotation = function(flag) { | |
| if (flag !== m_enableRotation) { | |
| m_enableRotation = flag; | |
| this.modified(); | |
| return true; | |
| } | |
| return m_enableRotation; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return active mode for translation (enabled/disabled) | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.enableTranslation = function(flag) { | |
| return m_enableTranslation; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Enable / disable translation | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setEnableTranslation = function(flag) { | |
| if (flag !== m_enableTranslation) { | |
| m_enableTranslation = flag; | |
| this.modified(); | |
| return true; | |
| } | |
| return m_enableTranslation; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return if parallel projection is enabled | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.isEnabledParallelProjection = function() { | |
| return m_enableParallelProjection; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Enable / disable parallel projection | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.enableParallelProjection = function(flag) { | |
| if (flag !== m_enableParallelProjection) { | |
| m_enableParallelProjection = flag; | |
| this.modified(); | |
| return true; | |
| } | |
| return m_enableParallelProjection; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Enable / disable parallel projection | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setEnnableParallelProjection = function(flag) { | |
| return enableParallelProjection(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set parallel projection parameters | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setParallelProjection = function(left, right, top, bottom) { | |
| m_left = left; | |
| m_right = right; | |
| m_top = top; | |
| m_bottom = bottom; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return direction of projection | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.directionOfProjection = function() { | |
| this.computeDirectionOfProjection(); | |
| return m_directionOfProjection; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return view plane normal direction | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.viewPlaneNormal = function() { | |
| this.computeViewPlaneNormal(); | |
| return m_viewPlaneNormal; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return view-matrix for the camera This method does not compute the | |
| * view-matrix for the camera. It is assumed that a call to computeViewMatrix | |
| * has been made earlier. | |
| * | |
| * @returns {mat4} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.viewMatrix = function() { | |
| return this.computeViewMatrix(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return camera projection matrix This method does not compute the | |
| * projection-matrix for the camera. It is assumed that a call to | |
| * computeProjectionMatrix has been made earlier. | |
| * | |
| * @returns {mat4} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.projectionMatrix = function() { | |
| return this.computeProjectionMatrix(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return clear mask used by this camera | |
| * | |
| * @returns {number} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.clearMask = function() { | |
| return m_clearMask; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set clear mask for camera | |
| * | |
| * @param mask | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setClearMask = function(mask) { | |
| m_clearMask = mask; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get clear color (background color) of the camera | |
| * | |
| * @returns {Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.clearColor = function() { | |
| return m_clearColor; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set clear color (background color) for the camera | |
| * | |
| * @param color RGBA | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setClearColor = function(r, g, b, a) { | |
| m_clearColor[0] = r; | |
| m_clearColor[1] = g; | |
| m_clearColor[2] = b; | |
| m_clearColor[3] = a; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * | |
| * @returns {{1.0: null}} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.clearDepth = function() { | |
| return m_clearDepth; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * | |
| * @param depth | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setClearDepth = function(depth) { | |
| m_clearDepth = depth; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute direction of projection | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeDirectionOfProjection = function() { | |
| vec3.subtract(m_directionOfProjection, m_focalPoint, m_position); | |
| vec3.normalize(m_directionOfProjection, m_directionOfProjection); | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute view plane normal | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeViewPlaneNormal = function() { | |
| m_viewPlaneNormal[0] = -m_directionOfProjection[0]; | |
| m_viewPlaneNormal[1] = -m_directionOfProjection[1]; | |
| m_viewPlaneNormal[2] = -m_directionOfProjection[2]; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Move camera closer or further away from the scene | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.zoom = function(d, dir) { | |
| if (d === 0) { | |
| return; | |
| } | |
| if (!m_enableTranslation) { | |
| return; | |
| } | |
| d = d * vec3.distance(m_focalPoint, m_position); | |
| if (!dir) { | |
| dir = m_directionOfProjection; | |
| m_position[0] = m_focalPoint[0] - d * dir[0]; | |
| m_position[1] = m_focalPoint[1] - d * dir[1]; | |
| m_position[2] = m_focalPoint[2] - d * dir[2]; | |
| } else { | |
| m_position[0] = m_position[0] + d * dir[0]; | |
| m_position[1] = m_position[1] + d * dir[1]; | |
| m_position[2] = m_position[2] + d * dir[2]; | |
| } | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Move camera sideways | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.pan = function(dx, dy, dz) { | |
| if (!m_enableTranslation) { | |
| return; | |
| } | |
| m_position[0] += dx; | |
| m_position[1] += dy; | |
| m_position[2] += dz; | |
| m_focalPoint[0] += dx; | |
| m_focalPoint[1] += dy; | |
| m_focalPoint[2] += dz; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute camera coordinate axes | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeOrthogonalAxes = function() { | |
| this.computeDirectionOfProjection(); | |
| vec3.cross(m_rightDir, m_directionOfProjection, m_viewUp); | |
| vec3.normalize(m_rightDir, m_rightDir); | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Rotate camera around center of rotation | |
| * @param dx Rotation around vertical axis in degrees | |
| * @param dy Rotation around horizontal axis in degrees | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.rotate = function(dx, dy) { | |
| if (!m_enableRotation) { | |
| return; | |
| } | |
| // Convert degrees into radians | |
| dx = 0.5 * dx * (Math.PI / 180.0); | |
| dy = 0.5 * dy * (Math.PI / 180.0); | |
| var mat = mat4.create(), | |
| inverseCenterOfRotation = new vec3.create(); | |
| mat4.identity(mat); | |
| inverseCenterOfRotation[0] = -m_centerOfRotation[0]; | |
| inverseCenterOfRotation[1] = -m_centerOfRotation[1]; | |
| inverseCenterOfRotation[2] = -m_centerOfRotation[2]; | |
| mat4.translate(mat, mat, m_centerOfRotation); | |
| mat4.rotate(mat, mat, dx, m_viewUp); | |
| mat4.rotate(mat, mat, dy, m_rightDir); | |
| mat4.translate(mat, mat, inverseCenterOfRotation); | |
| vec4.transformMat4(m_position, m_position, mat); | |
| vec4.transformMat4(m_focalPoint, m_focalPoint, mat); | |
| // Update viewup vector | |
| vec4.transformMat4(m_viewUp, m_viewUp, mat); | |
| vec4.normalize(m_viewUp, m_viewUp); | |
| // Update direction of projection | |
| this.computeOrthogonalAxes(); | |
| // Mark modified | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute camera view matrix | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeViewMatrix = function() { | |
| if (m_computeModelViewMatrixTime.getMTime() < this.getMTime()) { | |
| mat4.lookAt(m_viewMatrix, m_position, m_focalPoint, m_viewUp); | |
| m_computeModelViewMatrixTime.modified(); | |
| } | |
| return m_viewMatrix; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute camera projection matrix | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeProjectionMatrix = function() { | |
| if (m_computeProjectMatrixTime.getMTime() < this.getMTime()) { | |
| if (!m_enableParallelProjection) { | |
| mat4.perspective(m_projectionMatrix, m_viewAngle, m_viewAspect, m_near, m_far); | |
| } else { | |
| console.log('paralle projection'); | |
| mat4.ortho(m_projectionMatrix, | |
| m_left, m_right, m_bottom, m_top, m_near, m_far); | |
| } | |
| m_computeProjectMatrixTime.modified(); | |
| } | |
| return m_projectionMatrix; | |
| }; | |
| this.computeDirectionOfProjection(); | |
| return this; | |
| }; | |
| inherit(vgl.camera, vgl.groupNode); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class interactorStyle | |
| * | |
| * @class vgl.interactorStyle | |
| * interactorStyle is a base class for all interactor styles | |
| * @returns {vgl.interactorStyle} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| vgl.interactorStyle = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.interactorStyle)) { | |
| return new vgl.interactorStyle(); | |
| } | |
| vgl.object.call(this); | |
| // Private member variables | |
| var m_that = this, | |
| m_viewer = null; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return viewer referenced by the interactor style | |
| * | |
| * @returns {null} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.viewer = function() { | |
| return m_viewer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set viewer for the interactor style | |
| * | |
| * @param viewer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setViewer = function(viewer) { | |
| if (viewer !== m_viewer) { | |
| m_viewer = viewer; | |
| $(m_viewer).on(vgl.event.mousePress, m_that.handleMouseDown); | |
| $(m_viewer).on(vgl.event.mouseRelease, m_that.handleMouseUp); | |
| $(m_viewer).on(vgl.event.mouseMove, m_that.handleMouseMove); | |
| $(m_viewer).on(vgl.event.mouseOut, m_that.handleMouseOut); | |
| $(m_viewer).on(vgl.event.mouseWheel, m_that.handleMouseWheel); | |
| $(m_viewer).on(vgl.event.keyPress, m_that.handleKeyPress); | |
| $(m_viewer).on(vgl.event.mouseContextMenu, m_that.handleContextMenu); | |
| $(m_viewer).on(vgl.event.click, m_that.handleClick); | |
| $(m_viewer).on(vgl.event.dblClick, m_that.handleDoubleClick); | |
| this.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse down event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseDown = function(event) { | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse up event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseUp = function(event) { | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse move event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseMove = function(event) { | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse move event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseOut = function(event) { | |
| return true; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse wheel event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseWheel = function(event) { | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle click event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleClick = function(event) { | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle double click event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleDoubleClick = function(event) { | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle key press event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleKeyPress = function(event) { | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle context menu event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleContextMenu = function(event) { | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Reset to default | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.reset = function() { | |
| return true; | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.interactorStyle, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of trackballInteractorStyle | |
| * | |
| * @class vgl.trackballInteractorStyle | |
| * @returns {vgl.trackballInteractorStyle} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.trackballInteractorStyle = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.trackballInteractorStyle)) { | |
| return new vgl.trackballInteractorStyle(); | |
| } | |
| vgl.interactorStyle.call(this); | |
| var m_that = this, | |
| m_leftMouseBtnDown = false, | |
| m_rightMouseBtnDown = false, | |
| m_midMouseBtnDown = false, | |
| m_outsideCanvas, | |
| m_currPos = {x: 0, y: 0}, | |
| m_lastPos = {x: 0, y: 0}; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse move event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseMove = function(event) { | |
| var canvas = m_that.viewer().canvas(), | |
| width = m_that.viewer().renderWindow().windowSize()[0], | |
| height = m_that.viewer().renderWindow().windowSize()[1], | |
| ren = m_that.viewer().renderWindow().activeRenderer(), | |
| cam = ren.camera(), coords = m_that.viewer().relMouseCoords(event), | |
| fp, fdp, fwp, dp1, dp2, wp1, wp2, coords, dx, dy, dz, | |
| coords, m_zTrans; | |
| m_outsideCanvas = false; | |
| m_currPos = {x: 0, y: 0}; | |
| if ((coords.x < 0) || (coords.x > width)) { | |
| m_currPos.x = 0; | |
| m_outsideCanvas = true; | |
| } else { | |
| m_currPos.x = coords.x; | |
| } | |
| if ((coords.y < 0) || (coords.y > height)) { | |
| m_currPos.y = 0; | |
| m_outsideCanvas = true; | |
| } else { | |
| m_currPos.y = coords.y; | |
| } | |
| if (m_outsideCanvas === true) { | |
| return; | |
| } | |
| fp = cam.focalPoint(); | |
| fwp = vec4.fromValues(fp[0], fp[1], fp[2], 1); | |
| fdp = ren.worldToDisplay(fwp, cam.viewMatrix(), | |
| cam.projectionMatrix(), width, height); | |
| dp1 = vec4.fromValues(m_currPos.x, m_currPos.y, fdp[2], 1.0); | |
| dp2 = vec4.fromValues(m_lastPos.x, m_lastPos.y, fdp[2], 1.0); | |
| wp1 = ren.displayToWorld(dp1, cam.viewMatrix(), cam.projectionMatrix(), | |
| width, height); | |
| wp2 = ren.displayToWorld(dp2, cam.viewMatrix(), cam.projectionMatrix(), | |
| width, height); | |
| dx = wp1[0] - wp2[0]; | |
| dy = wp1[1] - wp2[1]; | |
| dz = wp1[2] - wp2[2]; | |
| if (m_midMouseBtnDown) { | |
| cam.pan(-dx, -dy, -dz); | |
| m_that.viewer().render(); | |
| } | |
| if (m_leftMouseBtnDown) { | |
| cam.rotate((m_lastPos.x - m_currPos.x), | |
| (m_lastPos.y - m_currPos.y)); | |
| ren.resetCameraClippingRange(); | |
| m_that.viewer().render(); | |
| } | |
| if (m_rightMouseBtnDown) { | |
| /// 2.0 is the speed up factor | |
| m_zTrans = 2.0 * (m_currPos.y - m_lastPos.y) / height; | |
| // Calculate zoom scale here | |
| if (m_zTrans > 0) { | |
| cam.zoom(1 - Math.abs(m_zTrans)); | |
| } else { | |
| cam.zoom(1 + Math.abs(m_zTrans)); | |
| } | |
| ren.resetCameraClippingRange(); | |
| m_that.viewer().render(); | |
| } | |
| m_lastPos.x = m_currPos.x; | |
| m_lastPos.y = m_currPos.y; | |
| return false; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse down event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseDown = function(event) { | |
| var coords; | |
| if (event.button === 0) { | |
| m_leftMouseBtnDown = true; | |
| } | |
| if (event.button === 1) { | |
| m_midMouseBtnDown = true; | |
| } | |
| if (event.button === 2) { | |
| m_rightMouseBtnDown = true; | |
| } | |
| coords = m_that.view.relMouseCoords(event); | |
| if (coords.x < 0) { | |
| m_lastPos.x = 0; | |
| } else { | |
| m_lastPos.x = coords.x; | |
| } | |
| if (coords.y < 0) { | |
| m_lastPos.y = 0; | |
| } else { | |
| m_lastPos.y = coords.y; | |
| } | |
| return false; | |
| }; | |
| // @note We never get mouse up from scroll bar: See the bug report here | |
| // http://bugs.jquery.com/ticket/8184 | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse up event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseUp = function(event) { | |
| if (event.button === 0) { | |
| m_leftMouseBtnDown = false; | |
| } | |
| if (event.button === 1) { | |
| m_midMouseBtnDown = false; | |
| } | |
| if (event.button === 2) { | |
| m_rightMouseBtnDown = false; | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse wheel event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseWheel = function(event) { | |
| var ren = m_that.viewer().renderWindow().activeRenderer(), | |
| cam = ren.camera(); | |
| // TODO Compute zoom factor intelligently | |
| if (event.originalEvent.wheelDelta < 0) { | |
| cam.zoom(0.9); | |
| } else { | |
| cam.zoom(1.1); | |
| } | |
| ren.resetCameraClippingRange(); | |
| m_that.viewer().render(); | |
| return true; | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.trackballInteractorStyle, vgl.interactorStyle); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of pvwInteractorStyle (for ParaViewWeb) | |
| * | |
| * @class vgl.pvwInteractorStyle | |
| * @returns {vgl.pvwInteractorStyle} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.pvwInteractorStyle = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.pvwInteractorStyle)) { | |
| return new vgl.pvwInteractorStyle(); | |
| } | |
| vgl.trackballInteractorStyle.call(this); | |
| var m_that = this, | |
| m_leftMouseButtonDown = false, | |
| m_rightMouseButtonDown = false, | |
| m_middleMouseButtonDown = false, | |
| m_width, | |
| m_height, | |
| m_renderer, | |
| m_camera, | |
| m_outsideCanvas, | |
| m_coords, | |
| m_currentMousePos, | |
| m_focalPoint, | |
| m_focusWorldPt, | |
| m_focusDisplayPt, | |
| m_displayPt1, | |
| m_displayPt2, | |
| m_worldPt1, | |
| m_worldPt2, | |
| m_dx, | |
| m_dy, | |
| m_dz, | |
| m_zTrans, | |
| m_mouseLastPos = { | |
| x: 0, | |
| y: 0 | |
| }; | |
| function render() { | |
| m_renderer.resetCameraClippingRange(); | |
| m_that.viewer().render(); | |
| } | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse move event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseMove = function(event) { | |
| var rens = [], i = null, secCameras = [], deltaxy = null; | |
| m_width = m_that.viewer().renderWindow().windowSize()[0]; | |
| m_height = m_that.viewer().renderWindow().windowSize()[1]; | |
| m_renderer = m_that.viewer().renderWindow().activeRenderer(); | |
| m_camera = m_renderer.camera(); | |
| m_outsideCanvas = false; | |
| m_coords = m_that.viewer().relMouseCoords(event); | |
| m_currentMousePos = { | |
| x: 0, | |
| y: 0 | |
| }; | |
| // Get secondary cameras | |
| rens = m_that.viewer().renderWindow().renderers(); | |
| for (i = 0; i < rens.length; ++i) { | |
| if (m_renderer !== rens[i]) { | |
| secCameras.push(rens[i].camera()); | |
| } | |
| } | |
| if ((m_coords.x < 0) || (m_coords.x > m_width)) { | |
| m_currentMousePos.x = 0; | |
| m_outsideCanvas = true; | |
| } else { | |
| m_currentMousePos.x = m_coords.x; | |
| } | |
| if ((m_coords.y < 0) || (m_coords.y > m_height)) { | |
| m_currentMousePos.y = 0; | |
| m_outsideCanvas = true; | |
| } else { | |
| m_currentMousePos.y = m_coords.y; | |
| } | |
| if (m_outsideCanvas === true) { | |
| return; | |
| } | |
| m_focalPoint = m_camera.focalPoint(); | |
| m_focusWorldPt = vec4.fromValues(m_focalPoint[0], m_focalPoint[1], m_focalPoint[2], 1); | |
| m_focusDisplayPt = m_renderer.worldToDisplay(m_focusWorldPt, m_camera.viewMatrix(), | |
| m_camera.projectionMatrix(), m_width, m_height); | |
| m_displayPt1 = vec4.fromValues( | |
| m_currentMousePos.x, m_currentMousePos.y, m_focusDisplayPt[2], 1.0); | |
| m_displayPt2 = vec4.fromValues( | |
| m_mouseLastPos.x, m_mouseLastPos.y, m_focusDisplayPt[2], 1.0); | |
| m_worldPt1 = m_renderer.displayToWorld( | |
| m_displayPt1, m_camera.viewMatrix(), m_camera.projectionMatrix(), m_width, m_height); | |
| m_worldPt2 = m_renderer.displayToWorld( | |
| m_displayPt2, m_camera.viewMatrix(), m_camera.projectionMatrix(), m_width, m_height); | |
| m_dx = m_worldPt1[0] - m_worldPt2[0]; | |
| m_dy = m_worldPt1[1] - m_worldPt2[1]; | |
| m_dz = m_worldPt1[2] - m_worldPt2[2]; | |
| if (m_middleMouseButtonDown) { | |
| m_camera.pan(-m_dx, -m_dy, -m_dz); | |
| render(); | |
| } | |
| if (m_leftMouseButtonDown) { | |
| deltaxy = [(m_mouseLastPos.x - m_currentMousePos.x), | |
| (m_mouseLastPos.y - m_currentMousePos.y)]; | |
| m_camera.rotate(deltaxy[0], deltaxy[1]); | |
| // Apply rotation to all other cameras | |
| for (i = 0; i < secCameras.length; ++i) { | |
| secCameras[i].rotate(deltaxy[0], deltaxy[1]); | |
| } | |
| // Apply rotation to all other cameras | |
| for (i = 0; i < rens.length; ++i) { | |
| rens[i].resetCameraClippingRange(); | |
| } | |
| render(); | |
| } | |
| if (m_rightMouseButtonDown) { | |
| /// 2.0 is the speed up factor. | |
| m_zTrans = 2.0 * (m_currentMousePos.y - m_mouseLastPos.y) / m_height; | |
| // Calculate zoom scale here | |
| if (m_zTrans > 0) { | |
| m_camera.zoom(1 - Math.abs(m_zTrans)); | |
| } else { | |
| m_camera.zoom(1 + Math.abs(m_zTrans)); | |
| } | |
| render(); | |
| } | |
| m_mouseLastPos.x = m_currentMousePos.x; | |
| m_mouseLastPos.y = m_currentMousePos.y; | |
| return false; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse down event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseDown = function(event) { | |
| if (event.button === 0) { | |
| m_leftMouseButtonDown = true; | |
| } | |
| if (event.button === 1) { | |
| m_middleMouseButtonDown = true; | |
| } | |
| if (event.button === 2) { | |
| m_rightMouseButtonDown = true; | |
| } | |
| m_coords = m_that.viewer().relMouseCoords(event); | |
| if (m_coords.x < 0) { | |
| m_mouseLastPos.x = 0; | |
| } else { | |
| m_mouseLastPos.x = m_coords.x; | |
| } | |
| if (m_coords.y < 0) { | |
| m_mouseLastPos.y = 0; | |
| } else { | |
| m_mouseLastPos.y = m_coords.y; | |
| } | |
| return false; | |
| }; | |
| // @note We never get mouse up from scroll bar: See the bug report here | |
| // http://bugs.jquery.com/ticket/8184 | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse up event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseUp = function(event) { | |
| var canvas = m_that.viewer().canvas(); | |
| if (event.button === 0) { | |
| m_leftMouseButtonDown = false; | |
| } | |
| if (event.button === 1) { | |
| m_middleMouseButtonDown = false; | |
| } | |
| if (event.button === 2) { | |
| m_rightMouseButtonDown = false; | |
| } | |
| return false; | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.pvwInteractorStyle, vgl.trackballInteractorStyle); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global window, vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class viewer | |
| * | |
| * @param canvas | |
| * @returns {vgl.viewer} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.viewer = function(canvas) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.viewer)) { | |
| return new vgl.viewer(canvas); | |
| } | |
| vgl.object.call(this); | |
| var m_that = this, | |
| m_canvas = canvas, | |
| m_ready = true, | |
| m_interactorStyle = null, | |
| m_renderer = vgl.renderer(), | |
| m_renderWindow = vgl.renderWindow(m_canvas); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get canvas of the viewer | |
| * | |
| * @returns {*} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.canvas = function() { | |
| return m_canvas; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return render window of the viewer | |
| * | |
| * @returns {vgl.renderWindow} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.renderWindow = function() { | |
| return m_renderWindow; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize the viewer | |
| * | |
| * This is a must call or otherwise render context may not initialized | |
| * properly. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.init = function() { | |
| if (m_renderWindow !== null) { | |
| m_renderWindow.createWindow(); | |
| } | |
| else { | |
| console.log("[ERROR] No render window attached"); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get interactor style of the viewer | |
| * | |
| * @returns {vgl.interactorStyle} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.interactorStyle = function() { | |
| return m_interactorStyle; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set interactor style to be used by the viewer | |
| * | |
| * @param {vgl.interactorStyle} style | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setInteractorStyle = function(style) { | |
| if (style !== m_interactorStyle) { | |
| m_interactorStyle = style; | |
| m_interactorStyle.setViewer(this); | |
| this.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse down event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseDown = function(event) { | |
| if (m_ready === true) { | |
| var fixedEvent = $.event.fix(event || window.event); | |
| // Only prevent default action for right mouse button | |
| if (event.button === 2) { | |
| fixedEvent.preventDefault(); | |
| } | |
| fixedEvent.state = 'down'; | |
| fixedEvent.type = vgl.event.mousePress; | |
| $(m_that).trigger(fixedEvent); | |
| } | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse up event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseUp = function(event) { | |
| if (m_ready === true) { | |
| var fixedEvent = $.event.fix(event || window.event); | |
| fixedEvent.preventDefault(); | |
| fixedEvent.state = 'up'; | |
| fixedEvent.type = vgl.event.mouseRelease; | |
| $(m_that).trigger(fixedEvent); | |
| } | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse move event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseMove = function(event) { | |
| if (m_ready === true) { | |
| var fixedEvent = $.event.fix(event || window.event); | |
| fixedEvent.preventDefault(); | |
| fixedEvent.type = vgl.event.mouseMove; | |
| $(m_that).trigger(fixedEvent); | |
| } | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse wheel scroll | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseWheel = function(event) { | |
| if (m_ready === true) { | |
| var fixedEvent = $.event.fix(event || window.event); | |
| fixedEvent.preventDefault(); | |
| fixedEvent.type = vgl.event.mouseWheel; | |
| $(m_that).trigger(fixedEvent); | |
| } | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse move event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleMouseOut = function(event) { | |
| if (m_ready === true) { | |
| var fixedEvent = $.event.fix(event || window.event); | |
| fixedEvent.preventDefault(); | |
| fixedEvent.type = vgl.event.mouseOut; | |
| $(m_that).trigger(fixedEvent); | |
| } | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle key press event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleKeyPress = function(event) { | |
| if (m_ready === true) { | |
| var fixedEvent = $.event.fix(event || window.event); | |
| fixedEvent.preventDefault(); | |
| fixedEvent.type = vgl.event.keyPress; | |
| $(m_that).trigger(fixedEvent); | |
| } | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle context menu event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleContextMenu = function(event) { | |
| if (m_ready === true) { | |
| var fixedEvent = $.event.fix(event || window.event); | |
| fixedEvent.preventDefault(); | |
| fixedEvent.type = vgl.event.contextMenu; | |
| $(m_that).trigger(fixedEvent); | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle click event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleClick = function(event) { | |
| if (m_ready === true) { | |
| var fixedEvent = $.event.fix(event || window.event); | |
| fixedEvent.preventDefault(); | |
| fixedEvent.type = vgl.event.click; | |
| $(m_that).trigger(fixedEvent); | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle double click event | |
| * | |
| * @param event | |
| * @returns {boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.handleDoubleClick = function(event) { | |
| if (m_ready === true) { | |
| var fixedEvent = $.event.fix(event || window.event); | |
| fixedEvent.preventDefault(); | |
| fixedEvent.type = vgl.event.dblClick; | |
| $(m_that).trigger(fixedEvent); | |
| } | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get mouse coodinates related to canvas | |
| * | |
| * @param event | |
| * @returns {{x: number, y: number}} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.relMouseCoords = function(event) { | |
| if (event.pageX === undefined || event.pageY === undefined) { | |
| throw "Missing attributes pageX and pageY on the event"; | |
| } | |
| var totalOffsetX = 0, | |
| totalOffsetY = 0, | |
| canvasX = 0, | |
| canvasY = 0, | |
| currentElement = m_canvas; | |
| do { | |
| totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft; | |
| totalOffsetY += currentElement.offsetTop - currentElement.scrollTop; | |
| } while (currentElement = currentElement.offsetParent); | |
| canvasX = event.pageX - totalOffsetX; | |
| canvasY = event.pageY - totalOffsetY; | |
| return { | |
| x: canvasX, | |
| y: canvasY | |
| }; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Render | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.render = function() { | |
| m_renderWindow.render(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind canvas mouse events to their default handlers | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.bindEventHandlers = function() { | |
| $(m_canvas).on('mousedown', this.handleMouseDown); | |
| $(m_canvas).on('mouseup', this.handleMouseUp); | |
| $(m_canvas).on('mousemove', this.handleMouseMove); | |
| $(m_canvas).on('mousewheel', this.handleMouseWheel); | |
| $(m_canvas).on('contextmenu', this.handleContextMenu); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Undo earlier binded handlers for canvas mouse events | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.unbindEventHandlers = function() { | |
| $(m_canvas).off('mousedown', this.handleMouseDown); | |
| $(m_canvas).off('mouseup', this.handleMouseUp); | |
| $(m_canvas).off('mousemove', this.handleMouseMove); | |
| $(m_canvas).off('mousewheel', this.handleMouseWheel); | |
| $(m_canvas).off('contextmenu', this.handleContextMenu); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function() { | |
| this.bindEventHandlers(); | |
| m_renderWindow.addRenderer(m_renderer); | |
| } | |
| this._init(); | |
| return this; | |
| }; | |
| inherit(vgl.viewer, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global gl, vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class shader | |
| * | |
| * @param type | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.shader = function(type) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.shader)) { | |
| return new vgl.shader(type); | |
| } | |
| vgl.object.call(this); | |
| var m_shaderHandle = null, | |
| m_compileTimestamp = vgl.timestamp(), | |
| m_shaderType = type, | |
| m_shaderSource = ""; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get shader handle | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.shaderHandle = function() { | |
| return m_shaderHandle; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get type of the shader | |
| * | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.shaderType = function() { | |
| return m_shaderType; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get shader source | |
| * | |
| * @returns {string} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.shaderSource = function() { | |
| return m_shaderSource; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set shader source | |
| * | |
| * @param {string} source | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setShaderSource = function(source) { | |
| m_shaderSource = source; | |
| this.modified(); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compile the shader | |
| * | |
| * @returns {null} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.compile = function() { | |
| if (this.getMTime() < m_compileTimestamp.getMTime()) { | |
| return m_shaderHandle; | |
| } | |
| gl.deleteShader(m_shaderHandle); | |
| m_shaderHandle = gl.createShader(m_shaderType); | |
| gl.shaderSource(m_shaderHandle, m_shaderSource); | |
| gl.compileShader(m_shaderHandle); | |
| // See if it compiled successfully | |
| if (!gl.getShaderParameter(m_shaderHandle, gl.COMPILE_STATUS)) { | |
| console.log("[ERROR] An error occurred compiling the shaders: " | |
| + gl.getShaderInfoLog(m_shaderHandle)); | |
| console.log(m_shaderSource); | |
| gl.deleteShader(m_shaderHandle); | |
| return null; | |
| } | |
| m_compileTimestamp.modified(); | |
| return m_shaderHandle; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Attach shader to the program | |
| * | |
| * @param programHandle | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.attachShader = function(programHandle) { | |
| gl.attachShader(programHandle, m_shaderHandle); | |
| }; | |
| }; | |
| inherit(vgl.shader, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, gl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instace of class shaderProgram | |
| * | |
| * @class | |
| * @returns {vgl.shaderProgram} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.shaderProgram = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.shaderProgram)) { | |
| return new vgl.shaderProgram(); | |
| } | |
| vgl.materialAttribute.call( | |
| this, vgl.materialAttributeType.ShaderProgram); | |
| /** @private */ | |
| var m_programHandle = 0, | |
| m_compileTimestamp = vgl.timestamp(), | |
| m_shaders = [], | |
| m_uniforms = [], | |
| m_vertexAttributes = {}, | |
| m_uniformNameToLocation = {}, | |
| m_vertexAttributeNameToLocation = {}; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Query uniform location in the program | |
| * | |
| * @param name | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.queryUniformLocation = function(name) { | |
| return gl.getUniformLocation(m_programHandle, name); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Query attribute location in the program | |
| * | |
| * @param name | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.queryAttributeLocation = function(name) { | |
| return gl.getAttribLocation(m_programHandle, name); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add a new shader to the program | |
| * | |
| * @param shader | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.addShader = function(shader) { | |
| if (m_shaders.indexOf(shader) > -1) { | |
| return false; | |
| } | |
| var i; | |
| for (i = 0; i < m_shaders.length; ++i) { | |
| if (m_shaders[i].shaderType() === shader.shaderType()) { | |
| m_shaders.splice(m_shaders.indexOf(shader), 1); | |
| } | |
| } | |
| m_shaders.push(shader); | |
| this.modified(); | |
| return true; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add a new uniform to the program | |
| * | |
| * @param uniform | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.addUniform = function(uniform) { | |
| if (m_uniforms.indexOf(uniform) > -1) { | |
| return false; | |
| } | |
| m_uniforms.push(uniform); | |
| this.modified(); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add a new vertex attribute to the program | |
| * | |
| * @param attr | |
| * @param key | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.addVertexAttribute = function(attr, key) { | |
| m_vertexAttributes[key] = attr; | |
| this.modified(); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get uniform location | |
| * | |
| * This method does not perform any query into the program but relies on | |
| * the fact that it depends on a call to queryUniformLocation earlier. | |
| * | |
| * @param name | |
| * @returns {number} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.uniformLocation = function(name) { | |
| return m_uniformNameToLocation[name]; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get attribute location | |
| * | |
| * This method does not perform any query into the program but relies on the | |
| * fact that it depends on a call to queryUniformLocation earlier. | |
| * | |
| * @param name | |
| * @returns {number} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.attributeLocation = function(name) { | |
| return m_vertexAttributeNameToLocation[name]; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get uniform object using name as the key | |
| * | |
| * @param name | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.uniform = function(name) { | |
| var i; | |
| for (i = 0; i < m_uniforms.length; ++i) { | |
| if (m_uniforms[i].name() === name) { | |
| return m_uniforms[i]; | |
| } | |
| } | |
| return null; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update all uniforms | |
| * | |
| * This method should be used directly unless required | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.updateUniforms = function() { | |
| var i; | |
| for (i = 0; i < m_uniforms.length; ++i) { | |
| m_uniforms[i].callGL(m_uniformNameToLocation[m_uniforms[i].name()]); | |
| } | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Link shader program | |
| * | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.link = function() { | |
| gl.linkProgram(m_programHandle); | |
| // If creating the shader program failed, alert | |
| if (!gl.getProgramParameter(m_programHandle, gl.LINK_STATUS)) { | |
| console.log("[ERROR] Unable to initialize the shader program."); | |
| return false; | |
| } | |
| return true; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Use the shader program | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.use = function() { | |
| gl.useProgram(m_programHandle); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Peform any clean up required when the program gets deleted | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.cleanUp = function() { | |
| this.deleteVertexAndFragment(); | |
| this.deleteProgram(); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Delete the shader program | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.deleteProgram = function() { | |
| gl.deleteProgram(m_programHandle); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Delete vertex and fragment shaders | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.deleteVertexAndFragment = function() { | |
| var i; | |
| for (i = 0; i < m_shaders.length; ++i) { | |
| gl.deleteShader(m_shaders[i].shaderHandle()); | |
| } | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind the program with its shaders | |
| * | |
| * @param renderState | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.bind = function(renderState) { | |
| var i = 0; | |
| if (m_programHandle === 0 | |
| || (m_compileTimestamp.getMTime() < this.getMTime())) { | |
| m_programHandle = gl.createProgram(); | |
| if (m_programHandle === 0) { | |
| console.log("[ERROR] Cannot create Program Object"); | |
| return false; | |
| } | |
| // Compile shaders | |
| for (i = 0; i < m_shaders.length; ++i) { | |
| m_shaders[i].compile(); | |
| m_shaders[i].attachShader(m_programHandle); | |
| } | |
| this.bindAttributes(); | |
| // link program | |
| if (!this.link()) { | |
| console.log("[ERROR] Failed to link Program"); | |
| this.cleanUp(); | |
| } | |
| this.use(); | |
| this.bindUniforms(); | |
| m_compileTimestamp.modified(); | |
| } | |
| else { | |
| this.use(); | |
| } | |
| // Call update callback. | |
| for (i = 0; i < m_uniforms.length; ++i) { | |
| m_uniforms[i].update(renderState, this); | |
| } | |
| // Now update values to GL. | |
| this.updateUniforms(); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Undo binding of the shader program | |
| * | |
| * @param renderState | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.undoBind = function(renderState) { | |
| // Do nothing | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind vertex data | |
| * | |
| * @param renderState | |
| * @param key | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.bindVertexData = function(renderState, key) { | |
| if (m_vertexAttributes.hasOwnProperty(key)) { | |
| m_vertexAttributes[key].bindVertexData(renderState, key); | |
| } | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Undo bind vetex data | |
| * | |
| * @param renderState | |
| * @param key | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.undoBindVertexData = function(renderState, key) { | |
| if (m_vertexAttributes.hasOwnProperty(key)) { | |
| m_vertexAttributes[key].undoBindVertexData(renderState, key); | |
| } | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind uniforms | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.bindUniforms = function() { | |
| var i; | |
| for (i = 0; i < m_uniforms.length; ++i) { | |
| m_uniformNameToLocation[m_uniforms[i].name()] = this | |
| .queryUniformLocation(m_uniforms[i].name()); | |
| } | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind vertex attributes | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.bindAttributes = function() { | |
| var key, name; | |
| for (key in m_vertexAttributes) { | |
| name = m_vertexAttributes[key].name(); | |
| gl.bindAttribLocation(m_programHandle, key, name); | |
| m_vertexAttributeNameToLocation[name] = key; | |
| } | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.shaderProgram, vgl.materialAttribute); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global Uint8Array, vgl, gl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class texture | |
| * | |
| * @class | |
| * @returns {vgl.texture} | |
| */ | |
| /////////////////////////////////////////////////////////////////////////////// | |
| vgl.texture = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.texture)) { | |
| return new vgl.texture(); | |
| } | |
| vgl.materialAttribute.call( | |
| this, vgl.materialAttributeType.Texture); | |
| this.m_width = 0; | |
| this.m_height = 0; | |
| this.m_depth = 0; | |
| this.m_textureHandle = null; | |
| this.m_textureUnit = 0; | |
| this.m_pixelFormat = null; | |
| this.m_pixelDataType = null; | |
| this.m_internalFormat = null; | |
| this.m_image = null; | |
| var m_setupTimestamp = vgl.timestamp(), | |
| m_that = this; | |
| function activateTextureUnit() { | |
| switch (m_that.m_textureUnit) { | |
| case 0: | |
| gl.activeTexture(gl.TEXTURE0); | |
| break; | |
| case 1: | |
| gl.activeTexture(gl.TEXTURE1); | |
| break; | |
| case 2: | |
| gl.activeTexture(gl.TEXTURE2); | |
| break; | |
| case 3: | |
| gl.activeTexture(gl.TEXTURE3); | |
| break; | |
| case 4: | |
| gl.activeTexture(gl.TEXTURE4); | |
| break; | |
| case 5: | |
| gl.activeTexture(gl.TEXTURE5); | |
| break; | |
| case 6: | |
| gl.activeTexture(gl.TEXTURE6); | |
| break; | |
| case 7: | |
| gl.activeTexture(gl.TEXTURE7); | |
| break; | |
| case 8: | |
| gl.activeTexture(gl.TEXTURE8); | |
| break; | |
| case 9: | |
| gl.activeTexture(gl.TEXTURE9); | |
| break; | |
| case 10: | |
| gl.activeTexture(gl.TEXTURE10); | |
| break; | |
| case 11: | |
| gl.activeTexture(gl.TEXTURE11); | |
| break; | |
| case 12: | |
| gl.activeTexture(gl.TEXTURE12); | |
| break; | |
| case 13: | |
| gl.activeTexture(gl.TEXTURE13); | |
| break; | |
| case 14: | |
| gl.activeTexture(gl.TEXTURE14); | |
| break; | |
| case 15: | |
| gl.activeTexture(gl.TEXTURE15); | |
| break; | |
| default: | |
| throw "[error] Texture unit " + this.m_textureUnit + | |
| " is not supported"; | |
| } | |
| } | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create texture, update parameters, and bind data | |
| * | |
| * @param renderState | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setup = function(renderState) { | |
| activateTextureUnit(); | |
| gl.deleteTexture(this.m_textureHandle); | |
| this.m_textureHandle = gl.createTexture(); | |
| gl.bindTexture(gl.TEXTURE_2D, this.m_textureHandle); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
| gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); | |
| gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); | |
| if (this.m_image !== null) { | |
| this.updateDimensions(); | |
| this.computeInternalFormatUsingImage(); | |
| // console.log("m_internalFormat " + this.m_internalFormat); | |
| // console.log("m_pixelFormat " + this.m_pixelFormat); | |
| // console.log("m_pixelDataType " + this.m_pixelDataType); | |
| // FOR now support only 2D textures | |
| gl.texImage2D(gl.TEXTURE_2D, 0, this.m_internalFormat, | |
| this.m_pixelFormat, this.m_pixelDataType, this.m_image); | |
| } | |
| else { | |
| gl.texImage2D(gl.TEXTURE_2D, 0, this.m_internalFormat, | |
| this.m_pixelFormat, this.m_pixelDataType, null); | |
| } | |
| gl.bindTexture(gl.TEXTURE_2D, null); | |
| m_setupTimestamp.modified(); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create texture and if already created use it | |
| * | |
| * @param renderState | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.bind = function(renderState) { | |
| // TODO Call setup via material setup | |
| if (this.getMTime() > m_setupTimestamp.getMTime()) { | |
| this.setup(renderState); | |
| } | |
| activateTextureUnit(); | |
| gl.bindTexture(gl.TEXTURE_2D, this.m_textureHandle); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Turn off the use of this texture | |
| * | |
| * @param renderState | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.undoBind = function(renderState) { | |
| gl.bindTexture(gl.TEXTURE_2D, null); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get image used by the texture | |
| * | |
| * @returns {vgl.image} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.image = function() { | |
| return this.m_image; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set image for the texture | |
| * | |
| * @param {vgl.image} image | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setImage = function(image) { | |
| if (image !== null) { | |
| this.m_image = image; | |
| this.updateDimensions(); | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get texture unit of the texture | |
| * | |
| * @returns {number} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.textureUnit = function() { | |
| return this.m_textureUnit; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set texture unit of the texture. Default is 0. | |
| * | |
| * @param {number} unit | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setTextureUnit = function(unit) { | |
| if (this.m_textureUnit === unit) { | |
| return false; | |
| } | |
| this.m_textureUnit = unit; | |
| this.modified(); | |
| return true; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get width of the texture | |
| * | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.width = function() { | |
| return this.m_width; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set width of the texture | |
| * | |
| * @param {number} width | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setWidth = function(width) { | |
| if (this.m_image === null) { | |
| return false; | |
| } | |
| this.m_width = width; | |
| this.modified(); | |
| return true; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get depth of the texture | |
| * | |
| * @returns {number} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.depth = function() { | |
| return this.m_depth; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set depth of the texture | |
| * | |
| * @param {number} depth | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setDepth = function(depth) { | |
| if (this.m_image === null) { | |
| return false; | |
| } | |
| this.m_depth = depth; | |
| this.modified(); | |
| return true; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the texture handle (id) of the texture | |
| * | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.textureHandle = function() { | |
| return this.m_textureHandle; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get internal format of the texture | |
| * | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.internalFormat = function() { | |
| return this.m_internalFormat; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set internal format of the texture | |
| * | |
| * @param internalFormat | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setInternalFormat = function(internalFormat) { | |
| if (this.m_internalFormat !== internalFormat) { | |
| this.m_internalFormat = internalFormat; | |
| this.modified(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get pixel format of the texture | |
| * | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.pixelFormat = function() { | |
| return this.m_pixelFormat; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set pixel format of the texture | |
| * | |
| * @param pixelFormat | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setPixelFormat = function(pixelFormat) { | |
| if (this.m_image === null) { | |
| return false; | |
| } | |
| this.m_pixelFormat = pixelFormat; | |
| this.modified(); | |
| return true; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get pixel data type | |
| * | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.pixelDataType = function() { | |
| return this.m_pixelDataType; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set pixel data type | |
| * | |
| * @param pixelDataType | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setPixelDataType = function(pixelDataType) { | |
| if (this.m_image === null) { | |
| return false; | |
| } | |
| this.m_pixelDataType = pixelDataType; | |
| this.modified(); | |
| return true; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute internal format of the texture | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.computeInternalFormatUsingImage = function() { | |
| // Currently image does not define internal format | |
| // and hence it's pixel format is the only way to query | |
| // information on how color has been stored. | |
| // switch (this.m_image.pixelFormat()) { | |
| // case gl.RGB: | |
| // this.m_internalFormat = gl.RGB; | |
| // break; | |
| // case gl.RGBA: | |
| // this.m_internalFormat = gl.RGBA; | |
| // break; | |
| // case gl.Luminance: | |
| // this.m_internalFormat = gl.Luminance; | |
| // break; | |
| // case gl.LuminanceAlpha: | |
| // this.m_internalFormat = gl.LuminanceAlpha; | |
| // break; | |
| // // Do nothing when image pixel format is none or undefined. | |
| // default: | |
| // break; | |
| // }; | |
| // TODO Fix this | |
| this.m_internalFormat = gl.RGBA; | |
| this.m_pixelFormat = gl.RGBA; | |
| this.m_pixelDataType = gl.UNSIGNED_BYTE; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update texture dimensions | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.updateDimensions = function() { | |
| if (this.m_image !== null) { | |
| this.m_width = this.m_image.width; | |
| this.m_height = this.m_image.height; | |
| this.m_depth = 0; // Only 2D images are supported now | |
| } | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.texture, vgl.materialAttribute); | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class lookupTable | |
| * | |
| * @class | |
| * @returns {vgl.lookupTable} | |
| */ | |
| /////////////////////////////////////////////////////////////////////////////// | |
| vgl.lookupTable = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.lookupTable)) { | |
| return new vgl.lookupTable(); | |
| } | |
| vgl.texture.call(this); | |
| var m_setupTimestamp = vgl.timestamp(), | |
| m_range = [0,0]; | |
| this.m_colorTable = //paraview bwr colortable | |
| [0.07514311,0.468049805,1,1, | |
| 0.247872569,0.498782363,1,1, | |
| 0.339526309,0.528909511,1,1, | |
| 0.409505078,0.558608486,1,1, | |
| 0.468487184,0.588057293,1,1, | |
| 0.520796675,0.617435078,1,1, | |
| 0.568724526,0.646924167,1,1, | |
| 0.613686735,0.676713218,1,1, | |
| 0.656658579,0.707001303,1,1, | |
| 0.698372844,0.738002964,1,1, | |
| 0.739424025,0.769954435,1,1, | |
| 0.780330104,0.803121429,1,1, | |
| 0.821573924,0.837809045,1,1, | |
| 0.863634967,0.874374691,1,1, | |
| 0.907017747,0.913245283,1,1, | |
| 0.936129275,0.938743558,0.983038586,1, | |
| 0.943467973,0.943498599,0.943398095,1, | |
| 0.990146732,0.928791426,0.917447482,1, | |
| 1,0.88332677,0.861943246,1, | |
| 1,0.833985467,0.803839606,1, | |
| 1,0.788626485,0.750707739,1, | |
| 1,0.746206642,0.701389973,1, | |
| 1,0.70590052,0.654994046,1, | |
| 1,0.667019783,0.610806959,1, | |
| 1,0.6289553,0.568237474,1, | |
| 1,0.591130233,0.526775617,1, | |
| 1,0.552955184,0.485962266,1, | |
| 1,0.513776083,0.445364274,1, | |
| 1,0.472800903,0.404551679,1, | |
| 1,0.428977855,0.363073592,1, | |
| 1,0.380759558,0.320428137,1, | |
| 0.961891484,0.313155629,0.265499262,1, | |
| 0.916482116,0.236630659,0.209939162,1].map( | |
| function(x) {return x*255;}); | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create lookup table, initialize parameters, and bind data to it | |
| * | |
| * @param {vgl.renderState} renderState | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setup = function(renderState) { | |
| if (this.textureUnit() === 0) { | |
| gl.activeTexture(gl.TEXTURE0); | |
| } else if (this.textureUnit() === 1) { | |
| gl.activeTexture(gl.TEXTURE1); | |
| } | |
| gl.deleteTexture(this.m_textureHandle); | |
| this.m_textureHandle = gl.createTexture(); | |
| gl.bindTexture(gl.TEXTURE_2D, this.m_textureHandle); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
| gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); | |
| this.m_width = this.m_colorTable.length/4; | |
| this.m_height = 1; | |
| this.m_depth = 0; | |
| gl.texImage2D(gl.TEXTURE_2D, | |
| 0, gl.RGBA, this.m_width, this.m_height, this.m_depth, | |
| gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(this.m_colorTable)); | |
| gl.bindTexture(gl.TEXTURE_2D, null); | |
| m_setupTimestamp.modified(); | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get color table used by the lookup table | |
| * | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.colorTable = function() { | |
| return this.m_colorTable; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set color table used by the lookup table | |
| * | |
| * @param colors | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setColorTable = function(colors) { | |
| if (this.m_colorTable === colors) { | |
| return false; | |
| } | |
| this.m_colorTable = colors; | |
| this.modified(); | |
| return true; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get scalar range | |
| * | |
| * @returns {Array} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.range = function() { | |
| return m_range; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set scalar range for the lookup table | |
| * | |
| * @param range | |
| * @returns {boolean} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.setRange = function(range) { | |
| if (m_range === range) { | |
| return false; | |
| } | |
| m_range = range; | |
| this.modified(); | |
| return true; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Given a [min,max] range update the lookup table range | |
| * | |
| * @param range | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.updateRange = function(range) { | |
| if (!(range instanceof Array)) { | |
| console.log('[error] Invalid data type for range. Requires array [min,max]'); | |
| } | |
| if (range[0] < m_range[0]) { | |
| m_range[0] = range[0]; | |
| this.modified(); | |
| } | |
| if (range[1] > m_range[1]) { | |
| m_range[1] = range[1]; | |
| this.modified(); | |
| } | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.lookupTable, vgl.texture); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, gl, ogs, vec2, vec3, vec4, mat3, mat4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class uniform | |
| * | |
| * @param type | |
| * @param name | |
| * @returns {vgl.uniform} OpenGL uniform encapsulation | |
| */ | |
| /////////////////////////////////////////////////////////////////////////////// | |
| vgl.uniform = function(type, name) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.uniform)) { | |
| return new vgl.uniform(); | |
| } | |
| this.getTypeNumberOfComponents = function(type) { | |
| switch (type) { | |
| case gl.FLOAT: | |
| case gl.INT: | |
| case gl.BOOL: | |
| return 1; | |
| case gl.FLOAT_VEC2: | |
| case gl.INT_VEC2: | |
| case gl.BOOL_VEC2: | |
| return 2; | |
| case gl.FLOAT_VEC3: | |
| case gl.INT_VEC3: | |
| case gl.BOOLT_VEC3: | |
| return 3; | |
| case gl.FLOAT_VEC4: | |
| case gl.INT_VEC4: | |
| case gl.BOOL_VEC4: | |
| return 4; | |
| case gl.FLOAT_MAT3: | |
| return 9; | |
| case gl.FLOAT_MAT4: | |
| return 16; | |
| default: | |
| return 0; | |
| } | |
| }; | |
| var m_type = type, | |
| m_name = name, | |
| m_dataArray = [], | |
| m_numberOfElements = 1; | |
| m_dataArray.length = this.getTypeNumberOfComponents(m_type); | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get name of the uniform | |
| * | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.name = function() { | |
| return m_name; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get type of the uniform | |
| * | |
| * @returns {*} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.type = function() { | |
| return m_type; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get value of the uniform | |
| * | |
| * @returns {Array} | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.get = function() { | |
| return m_dataArray; | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set value of the uniform | |
| * | |
| * @param value | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.set = function(value) { | |
| var i = 0; | |
| if (m_dataArray.length === 16) { | |
| for (i = 0; i < 16; ++i) { | |
| m_dataArray[i] = value[i]; | |
| } | |
| } | |
| else if (m_dataArray.length === 9) { | |
| for (i = 0; i < 9; ++i) { | |
| m_dataArray[i] = value[i]; | |
| } | |
| } | |
| else if (m_dataArray.length === 4) { | |
| for (i = 0; i < 4; ++i) { | |
| m_dataArray[i] = value[i]; | |
| } | |
| } | |
| else if (m_dataArray.length === 3) { | |
| for (i = 0; i < 3; ++i) { | |
| m_dataArray[i] = value[i]; | |
| } | |
| } | |
| else if (m_dataArray.length === 2) { | |
| for (i = 0; i < 2; ++i) { | |
| m_dataArray[i] = value[i]; | |
| } | |
| } | |
| else { | |
| m_dataArray[0] = value; | |
| } | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Call GL and pass updated values to the current shader | |
| * | |
| * @param location | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.callGL = function(location) { | |
| if (this.m_numberElements < 1) { | |
| return; | |
| } | |
| switch (m_type) { | |
| case gl.BOOL: | |
| case gl.INT: | |
| gl.uniform1iv(location, m_dataArray); | |
| break; | |
| case gl.FLOAT: | |
| gl.uniform1fv(location, m_dataArray); | |
| break; | |
| case gl.FLOAT_VEC2: | |
| gl.uniform2fv(location, m_dataArray); | |
| break; | |
| case gl.FLOAT_VEC3: | |
| gl.uniform3fv(location, m_dataArray); | |
| break; | |
| case gl.FLOAT_VEC4: | |
| gl.uniform4fv(location, m_dataArray); | |
| break; | |
| case gl.FLOAT_MAT3: | |
| gl.uniformMatrix3fv(location, gl.FALSE, m_dataArray); | |
| break; | |
| case gl.FLOAT_MAT4: | |
| gl.uniformMatrix4fv(location, gl.FALSE, m_dataArray); | |
| break; | |
| default: | |
| break; | |
| } | |
| }; | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Virtual method to update the uniform | |
| * | |
| * Should be implemented by the derived class. | |
| * | |
| * @param renderState | |
| * @param program | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.update = function(renderState, program) { | |
| // Should be implemented by the derived class | |
| }; | |
| return this; | |
| }; | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create new instance of class modelViewUniform | |
| * | |
| * @param name | |
| * @returns {vgl.modelViewUniform} | |
| */ | |
| /////////////////////////////////////////////////////////////////////////////// | |
| vgl.modelViewUniform = function(name) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.modelViewUniform)) { | |
| return new vgl.modelViewUniform(name); | |
| } | |
| if (name.length === 0) { | |
| name = "modelViewMatrix"; | |
| } | |
| vgl.uniform.call(this, gl.FLOAT_MAT4, name); | |
| this.set(mat4.create()); | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update the uniform given a render state and shader program | |
| * | |
| * @param {vgl.renderState} renderState | |
| * @param {vgl.shaderProgram} program | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.update = function(renderState, program) { | |
| this.set(renderState.m_modelViewMatrix); | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.modelViewUniform, vgl.uniform); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class projectionUniform | |
| * | |
| * @param name | |
| * @returns {vgl.projectionUniform} | |
| */ | |
| /////////////////////////////////////////////////////////////////////////////// | |
| vgl.projectionUniform = function(name) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.projectionUniform)) { | |
| return new vgl.projectionUniform(name); | |
| } | |
| if (name.length === 0) { | |
| name = "projectionMatrix"; | |
| } | |
| vgl.uniform.call(this, gl.FLOAT_MAT4, name); | |
| this.set(mat4.create()); | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update the uniform given a render state and shader program | |
| * | |
| * @param renderState | |
| * @param program | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.update = function(renderState, program) { | |
| this.set(renderState.m_projectionMatrix); | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.projectionUniform, vgl.uniform); | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class floatUniform | |
| * | |
| * @param name | |
| * @param value | |
| * @returns {vgl.floatUniform} | |
| */ | |
| /////////////////////////////////////////////////////////////////////////////// | |
| vgl.floatUniform = function(name, value) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.floatUniform)) { | |
| return new vgl.floatUniform(name, value); | |
| } | |
| if (name.length === 0) { | |
| name = "floatUniform"; | |
| } | |
| value = value === undefined ? 1.0 : value; | |
| vgl.uniform.call(this, gl.FLOAT, name); | |
| this.set(value); | |
| }; | |
| inherit(vgl.floatUniform, vgl.uniform); | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create new instance of class normalMatrixUniform | |
| * | |
| * @param name | |
| * @returns {vgl.normalMatrixUniform} | |
| */ | |
| /////////////////////////////////////////////////////////////////////////////// | |
| vgl.normalMatrixUniform = function(name) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.normalMatrixUniform)) { | |
| return new vgl.normalMatrixUniform(name); | |
| } | |
| if (name.length === 0) { | |
| name = "normalMatrix"; | |
| } | |
| vgl.uniform.call(this, gl.FLOAT_MAT4, name); | |
| this.set(mat4.create()); | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update the uniform given a render state and shader program | |
| * | |
| * @param {vgl.renderState} renderState | |
| * @param {vgl.shaderProgram} program | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.update = function(renderState, program) { | |
| this.set(renderState.m_normalMatrix); | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.normalMatrixUniform, vgl.uniform); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, gl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Keys to identify vertex attributes | |
| * | |
| * @type {{Position: number, Normal: number, TextureCoordinate: number, | |
| * Color: number, Scalar: number, Scalar2: number, Scalar3: number, | |
| * Scalar4: number, Scalar5: number, Scalar6: number, Scalar7: number, | |
| * CountAttributeIndex: number}} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.vertexAttributeKeys = { | |
| "Position" : 0, | |
| "Normal" : 1, | |
| "TextureCoordinate" : 2, | |
| "Color" : 3, | |
| "CountAttributeIndex" : 4 | |
| }; | |
| vgl.vertexAttributeKeysIndexed = { | |
| "Zero" : 0, | |
| "One" : 1, | |
| "Two" : 2, | |
| "Three" : 3, | |
| "Four" : 4, | |
| "Five" : 5, | |
| "Six" : 6, | |
| "Seven" : 7, | |
| "Eight" : 8, | |
| "Nine" : 9 | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of vertexAttribute | |
| * | |
| * @param {string} name | |
| * @returns {vgl.vertexAttribute} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.vertexAttribute = function(name) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.vertexAttribute)) { | |
| return new vgl.vertexAttribute(name); | |
| } | |
| var m_name = name; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get name of the vertex attribute | |
| * | |
| * @returns {string} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.name = function() { | |
| return m_name; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind vertex data to the given render state | |
| * | |
| * @param {vgl.renderState} renderState | |
| * @param {vgl.vertexAttributeKeys} key | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.bindVertexData = function(renderState, key) { | |
| var geometryData = renderState.m_mapper.geometryData(), | |
| sourceData = geometryData.sourceData(key), | |
| program = renderState.m_material.shaderProgram(); | |
| gl.vertexAttribPointer(program.attributeLocation(m_name), sourceData | |
| .attributeNumberOfComponents(key), sourceData.attributeDataType(key), | |
| sourceData.normalized(key), sourceData | |
| .attributeStride(key), sourceData | |
| .attributeOffset(key)); | |
| gl.enableVertexAttribArray(program.attributeLocation(m_name)); | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Undo bind vertex data for a given render state | |
| * | |
| * @param {vgl.renderState} renderState | |
| * @param {vgl.vertexAttributeKeys} key | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.undoBindVertexData = function(renderState, key) { | |
| var program = renderState.m_material.shaderProgram(); | |
| gl.disableVertexAttribArray(program.attributeLocation(m_name)); | |
| }; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class source | |
| * | |
| * @returns {vgl.source} | |
| */ | |
| /////////////////////////////////////////////////////////////////////////////// | |
| vgl.source = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.source)) { | |
| return new vgl.source(); | |
| } | |
| vgl.object.call(this); | |
| ///////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Virtual function to create a source instance | |
| */ | |
| ///////////////////////////////////////////////////////////////////////////// | |
| this.create = function() { | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.source, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class planeSource | |
| * | |
| * @class | |
| * @returns {vgl.planeSource} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.planeSource = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.planeSource)) { | |
| return new vgl.planeSource(); | |
| } | |
| vgl.source.call(this); | |
| var m_origin = [ 0.0, 0.0, 0.0 ], | |
| m_point1 = [ 1.0, 0.0, 0.0 ], | |
| m_point2 = [ 0.0, 1.0, 0.0 ], | |
| m_normal = [ 0.0, 0.0, 1.0 ], | |
| m_xresolution = 1, | |
| m_yresolution = 1, | |
| m_geom = null; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set origin of the plane | |
| * | |
| * @param x | |
| * @param y | |
| * @param z | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setOrigin = function(x, y, z) { | |
| m_origin[0] = x; | |
| m_origin[1] = y; | |
| m_origin[2] = z; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set point that defines the first axis of the plane | |
| * | |
| * @param x | |
| * @param y | |
| * @param z | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setPoint1 = function(x, y, z) { | |
| m_point1[0] = x; | |
| m_point1[1] = y; | |
| m_point1[2] = z; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set point that defines the first axis of the plane | |
| * | |
| * @param x | |
| * @param y | |
| * @param z | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setPoint2 = function(x, y, z) { | |
| m_point2[0] = x; | |
| m_point2[1] = y; | |
| m_point2[2] = z; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a plane geometry given input parameters | |
| * | |
| * @returns {null} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.create = function() { | |
| m_geom = new vgl.geometryData(); | |
| var x = [], tc = [], v1 = [], v2 = [], | |
| pts = [], i, j, k, ii, numPts, numPolys, | |
| posIndex = 0, normIndex = 0, colorIndex = 0, texCoordIndex = 0, | |
| positions = [], normals = [], colors = [], | |
| texCoords = [], indices = [], tristrip = null, | |
| sourcePositions = null, sourceColors = null, sourceTexCoords; | |
| x.length = 3; | |
| tc.length = 2; | |
| v1.length = 3; | |
| v2.length = 3; | |
| pts.length = 3; | |
| // Check input | |
| for (i = 0; i < 3; i++) { | |
| v1[i] = m_point1[i] - m_origin[i]; | |
| v2[i] = m_point2[i] - m_origin[i]; | |
| } | |
| // TODO Compute center and normal | |
| // Set things up; allocate memory | |
| numPts = (m_xresolution + 1) * (m_yresolution + 1); | |
| numPolys = m_xresolution * m_yresolution * 2; | |
| positions.length = 3 * numPts; | |
| normals.length = 3 * numPts; | |
| texCoords.length = 2 * numPts; | |
| indices.length = numPts; | |
| for (k = 0, i = 0; i < (m_yresolution + 1); i++) { | |
| tc[1] = i / m_yresolution; | |
| for (j = 0; j < (m_xresolution + 1); j++) { | |
| tc[0] = j / m_xresolution; | |
| for (ii = 0; ii < 3; ii++) { | |
| x[ii] = m_origin[ii] + tc[0] * v1[ii] + tc[1] * v2[ii]; | |
| } | |
| positions[posIndex++] = x[0]; | |
| positions[posIndex++] = x[1]; | |
| positions[posIndex++] = x[2]; | |
| colors[colorIndex++] = 1.0; | |
| colors[colorIndex++] = 1.0; | |
| colors[colorIndex++] = 1.0; | |
| normals[normIndex++] = m_normal[0]; | |
| normals[normIndex++] = m_normal[1]; | |
| normals[normIndex++] = m_normal[2]; | |
| texCoords[texCoordIndex++] = tc[0]; | |
| texCoords[texCoordIndex++] = tc[1]; | |
| } | |
| } | |
| /// Generate polygon connectivity | |
| for (i = 0; i < m_yresolution; i++) { | |
| for (j = 0; j < m_xresolution; j++) { | |
| pts[0] = j + i * (m_xresolution + 1); | |
| pts[1] = pts[0] + 1; | |
| pts[2] = pts[0] + m_xresolution + 2; | |
| pts[3] = pts[0] + m_xresolution + 1; | |
| } | |
| } | |
| for (i = 0; i < numPts; ++i) { | |
| indices[i] = i; | |
| } | |
| tristrip = new vgl.triangleStrip(); | |
| tristrip.setIndices(indices); | |
| sourcePositions = vgl.sourceDataP3fv(); | |
| sourcePositions.pushBack(positions); | |
| sourceColors = vgl.sourceDataC3fv(); | |
| sourceColors.pushBack(colors); | |
| sourceTexCoords = vgl.sourceDataT2fv(); | |
| sourceTexCoords.pushBack(texCoords); | |
| m_geom.addSource(sourcePositions); | |
| m_geom.addSource(sourceColors); | |
| m_geom.addSource(sourceTexCoords); | |
| m_geom.addPrimitive(tristrip); | |
| return m_geom; | |
| }; | |
| }; | |
| inherit(vgl.planeSource, vgl.source); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class pointSource | |
| * | |
| * @class | |
| * @returns {vgl.pointSource} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.pointSource = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.pointSource)) { | |
| return new vgl.pointSource(); | |
| } | |
| vgl.source.call(this); | |
| var m_this = this, | |
| m_positions = [], | |
| m_colors = [], | |
| m_textureCoords = [], | |
| m_size = [], | |
| m_geom = null; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get positions for the points | |
| * | |
| * @param positions | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getPositions = function(positions) { | |
| return m_positions; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set positions for the source | |
| * | |
| * @param positions | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setPositions = function(positions) { | |
| if (positions instanceof Array) { | |
| m_positions = positions; | |
| } | |
| else { | |
| console | |
| .log("[ERROR] Invalid data type for positions. Array is required."); | |
| } | |
| m_this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get colors for the points | |
| * | |
| * @param positions | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getColors = function(positions) { | |
| return m_colors; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set colors for the points | |
| * | |
| * @param colors | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setColors = function(colors) { | |
| if (colors instanceof Array) { | |
| m_colors = colors; | |
| } | |
| else { | |
| console.log("[ERROR] Invalid data type for colors. Array is required."); | |
| } | |
| m_this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get colors for the points | |
| * | |
| * @param positions | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getSize = function(positions) { | |
| return m_size; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set colors for the points | |
| * | |
| * @param colors | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setSize = function(size) { | |
| m_size = size; | |
| this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set texture coordinates for the points | |
| * | |
| * @param texcoords | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setTextureCoordinates = function(texcoords) { | |
| if (texcoords instanceof Array) { | |
| m_textureCoords = texcoords; | |
| } | |
| else { | |
| console.log("[ERROR] Invalid data type for " | |
| + "texture coordinates. Array is required."); | |
| } | |
| m_this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a point geometry given input parameters | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.create = function() { | |
| m_geom = new vgl.geometryData(); | |
| if (m_positions.length % 3 !== 0) { | |
| console.log("[ERROR] Invalid length of the points array"); | |
| return; | |
| } | |
| var numPts = m_positions.length / 3, | |
| i = 0, | |
| indices = [], | |
| pointsPrimitive, | |
| sourcePositions, | |
| sourceColors, | |
| sourceTexCoords, | |
| sourceSize; | |
| indices.length = numPts; | |
| for (i = 0; i < numPts; ++i) { | |
| indices[i] = i; | |
| } | |
| /// Generate array of size if needed | |
| sourceSize = vgl.sourceDataDf(); | |
| if (numPts !== m_size.length) { | |
| for (i = 0; i < numPts; ++i) { | |
| sourceSize.pushBack(m_size); | |
| } | |
| } else { | |
| sourceSize.setData(m_size); | |
| } | |
| m_geom.addSource(sourceSize); | |
| pointsPrimitive = new vgl.points(); | |
| pointsPrimitive.setIndices(indices); | |
| sourcePositions = vgl.sourceDataP3fv(); | |
| sourcePositions.pushBack(m_positions); | |
| m_geom.addSource(sourcePositions); | |
| if ((m_colors.length > 0) && m_colors.length === m_positions.length) { | |
| sourceColors = vgl.sourceDataC3fv(); | |
| sourceColors.pushBack(m_colors); | |
| m_geom.addSource(sourceColors); | |
| } | |
| else if ((m_colors.length > 0) && m_colors.length !== m_positions.length) { | |
| console | |
| .log("[ERROR] Number of colors are different than number of points"); | |
| } | |
| if ((m_textureCoords.length > 0) | |
| && m_textureCoords.length === m_positions.length) { | |
| sourceTexCoords = vgl.sourceDataT2fv(); | |
| sourceTexCoords.pushBack(m_textureCoords); | |
| m_geom.addSource(sourceTexCoords); | |
| } | |
| else if ((m_textureCoords.length > 0) | |
| && (m_textureCoords.length / 2) !== (m_positions.length / 3)) { | |
| console | |
| .log("[ERROR] Number of texture coordinates are different than number of points"); | |
| } | |
| m_geom.addPrimitive(pointsPrimitive); | |
| return m_geom; | |
| }; | |
| }; | |
| inherit(vgl.pointSource, vgl.source); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class lineSource | |
| * | |
| * @class | |
| * @returns {vgl.lineSource} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.lineSource = function(positions, colors) { | |
| 'use strict'; | |
| if (!(this instanceof vgl.lineSource)) { | |
| return new vgl.lineSource(); | |
| } | |
| vgl.source.call(this); | |
| var m_positions = positions, | |
| m_colors = colors, | |
| m_height = null, | |
| m_geom = null; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set start positions for the lines | |
| * | |
| * @param positions | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setPositions = function(positions) { | |
| if (positions instanceof Array) { | |
| m_positions = positions; | |
| this.modified(); | |
| return true; | |
| } | |
| console | |
| .log("[ERROR] Invalid data type for positions. Array is required."); | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set colors for the lines | |
| * | |
| * @param colors | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setColors = function(colors) { | |
| if (colors instanceof Array) { | |
| m_colors = colors; | |
| this.modified(); | |
| return true; | |
| } | |
| console.log("[ERROR] Invalid data type for colors. Array is required."); | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a point geometry given input parameters | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.create = function() { | |
| if (!m_positions) { | |
| console.log("[error] Invalid positions"); | |
| return; | |
| } | |
| if (m_positions.length % 3 !== 0) { | |
| console.log("[error] Line source requires 3d points"); | |
| return; | |
| } | |
| if (m_positions.length % 3 !== 0) { | |
| console.log("[ERROR] Invalid length of the points array"); | |
| return; | |
| } | |
| var m_geom = new vgl.geometryData(), | |
| numPts = m_positions.length / 3, | |
| i, | |
| indices = [], | |
| linesPrimitive, | |
| sourcePositions, | |
| sourceColors; | |
| indices.length = numPts; | |
| for (i = 0; i < numPts; ++i) { | |
| indices[i] = i; | |
| } | |
| linesPrimitive = new vgl.lines(); | |
| linesPrimitive.setIndices(indices); | |
| sourcePositions = vgl.sourceDataP3fv(); | |
| sourcePositions.pushBack(m_positions); | |
| m_geom.addSource(sourcePositions); | |
| if ( m_colors && (m_colors.length > 0) && | |
| m_colors.length === m_positions.length) { | |
| sourceColors = vgl.sourceDataC3fv(); | |
| sourceColors.pushBack(m_colors); | |
| m_geom.addSource(sourceColors); | |
| } | |
| else if (m_colors && (m_colors.length > 0) && | |
| m_colors.length !== m_positions.length) { | |
| console | |
| .log("[error] Number of colors are different than number of points"); | |
| } | |
| m_geom.addPrimitive(linesPrimitive); | |
| return m_geom; | |
| }; | |
| }; | |
| inherit(vgl.lineSource, vgl.source); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global document, vgl, gl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class utils | |
| * | |
| * Utility class provides helper functions such as functions to create | |
| * shaders, geometry etc. | |
| * | |
| * @returns {vgl.utils} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.utils)) { | |
| return new vgl.utils(); | |
| } | |
| vgl.object.call(this); | |
| return this; | |
| }; | |
| inherit(vgl.utils, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Helper function to compute power of 2 number | |
| * | |
| * @param value | |
| * @param pow | |
| * | |
| * @returns {number} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.computePowerOfTwo = function(value, pow) { | |
| 'use strict'; | |
| pow = pow || 1; | |
| while (pow < value) { | |
| pow *= 2; | |
| } | |
| return pow; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of default vertex shader that uses a texture | |
| * | |
| * Helper function to create default vertex shader | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createTextureVertexShader = function(context) { | |
| 'use strict'; | |
| var vertexShaderSource = [ | |
| 'attribute vec3 vertexPosition;', | |
| 'attribute vec3 textureCoord;', | |
| 'uniform mediump float pointSize;', | |
| 'uniform mat4 modelViewMatrix;', | |
| 'uniform mat4 projectionMatrix;', | |
| 'varying highp vec3 iTextureCoord;', | |
| 'void main(void)', | |
| '{', | |
| 'gl_PointSize = pointSize;', | |
| 'gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);', | |
| ' iTextureCoord = textureCoord;', '}' ].join('\n'), | |
| shader = new vgl.shader(gl.VERTEX_SHADER); | |
| shader.setShaderSource(vertexShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of default fragment shader that uses a texture | |
| * | |
| * Helper function to create default fragment shader with sampler | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createTextureFragmentShader = function(context) { | |
| 'use strict'; | |
| var fragmentShaderSource = [ | |
| 'varying highp vec3 iTextureCoord;', | |
| 'uniform sampler2D sampler2d;', | |
| 'uniform mediump float opacity;', | |
| 'void main(void) {', | |
| 'gl_FragColor = vec4(texture2D(sampler2d, vec2(iTextureCoord.s, iTextureCoord.t)).xyz, opacity);', | |
| '}' ].join('\n'), | |
| shader = new vgl.shader(gl.FRAGMENT_SHADER); | |
| shader.setShaderSource(fragmentShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create variation of createTextureFragmentShader which uses texture alpha | |
| * | |
| * Helper function to create default fragment shader with sampler | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createRgbaTextureFragmentShader = function (context) { | |
| 'use strict'; | |
| var fragmentShaderSource = [ | |
| 'varying highp vec3 iTextureCoord;', | |
| 'uniform sampler2D sampler2d;', | |
| 'uniform mediump float opacity;', | |
| 'void main(void) {', | |
| ' mediump vec4 color = vec4(texture2D(sampler2d, vec2(iTextureCoord.s, iTextureCoord.t)).xyzw);', | |
| ' color.w *= opacity;', | |
| ' gl_FragColor = color;', | |
| '}' | |
| ].join('\n'), | |
| shader = new vgl.shader(gl.FRAGMENT_SHADER); | |
| shader.setShaderSource(fragmentShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of default vertex shader | |
| * | |
| * Helper function to create default vertex shader | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createVertexShader = function(context) { | |
| 'use strict'; | |
| var vertexShaderSource = [ | |
| 'attribute vec3 vertexPosition;', | |
| 'attribute vec3 vertexColor;', | |
| 'uniform mediump float pointSize;', | |
| 'uniform mat4 modelViewMatrix;', | |
| 'uniform mat4 projectionMatrix;', | |
| 'varying mediump vec3 iVertexColor;', | |
| 'varying highp vec3 iTextureCoord;', | |
| 'void main(void)', | |
| '{', | |
| 'gl_PointSize = pointSize;', | |
| 'gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);', | |
| ' iVertexColor = vertexColor;', '}' ].join('\n'), | |
| shader = new vgl.shader(gl.VERTEX_SHADER); | |
| shader.setShaderSource(vertexShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of default vertex shader | |
| * | |
| * Helper function to create default vertex shader | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createPointVertexShader = function(context) { | |
| 'use strict'; | |
| var vertexShaderSource = [ | |
| 'attribute vec3 vertexPosition;', | |
| 'attribute vec3 vertexColor;', | |
| 'attribute float vertexSize;', | |
| 'uniform mat4 modelViewMatrix;', | |
| 'uniform mat4 projectionMatrix;', | |
| 'varying mediump vec3 iVertexColor;', | |
| 'varying highp vec3 iTextureCoord;', | |
| 'void main(void)', | |
| '{', | |
| 'gl_PointSize = vertexSize;', | |
| 'gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);', | |
| ' iVertexColor = vertexColor;', '}' ].join('\n'), | |
| shader = new vgl.shader(gl.VERTEX_SHADER); | |
| shader.setShaderSource(vertexShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of vertex shader with a solid color | |
| * | |
| * Helper function to create default vertex shader | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createVertexShaderSolidColor = function(context) { | |
| 'use strict'; | |
| var vertexShaderSource = [ | |
| 'attribute vec3 vertexPosition;', | |
| 'uniform mediump float pointSize;', | |
| 'uniform mat4 modelViewMatrix;', | |
| 'uniform mat4 projectionMatrix;', | |
| 'void main(void)', | |
| '{', | |
| 'gl_PointSize = pointSize;', | |
| 'gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);', | |
| '}' ].join('\n'), | |
| shader = new vgl.shader(gl.VERTEX_SHADER); | |
| shader.setShaderSource(vertexShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of vertex shader that passes values through | |
| * for color mapping | |
| * | |
| * Helper function to create default vertex shader | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createVertexShaderColorMap = function(context, min, max) { | |
| 'use strict'; | |
| var vertexShaderSource = [ | |
| 'attribute vec3 vertexPosition;', | |
| 'attribute float vertexScalar;', | |
| 'uniform mediump float pointSize;', | |
| 'uniform mat4 modelViewMatrix;', | |
| 'uniform mat4 projectionMatrix;', | |
| 'uniform float lutMin;', | |
| 'uniform float lutMax;', | |
| 'varying mediump float iVertexScalar;', | |
| 'void main(void)', | |
| '{', | |
| 'gl_PointSize = pointSize;', | |
| 'gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);', | |
| 'iVertexScalar = (vertexScalar-lutMin)/(lutMax-lutMin);', | |
| '}' ].join('\n'), | |
| shader = new vgl.shader(gl.VERTEX_SHADER); | |
| shader.setShaderSource(vertexShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of default fragment shader | |
| * | |
| * Helper function to create default fragment shader | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createFragmentShader = function(context) { | |
| 'use strict'; | |
| var fragmentShaderSource = [ 'varying mediump vec3 iVertexColor;', | |
| 'uniform mediump float opacity;', | |
| 'void main(void) {', | |
| 'gl_FragColor = vec4(iVertexColor, opacity);', | |
| '}' ].join('\n'), | |
| shader = new vgl.shader(gl.FRAGMENT_SHADER); | |
| shader.setShaderSource(fragmentShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a Phong vertex shader | |
| * | |
| * Helper function to create Phong vertex shader | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createPhongVertexShader = function(context) { | |
| 'use strict'; | |
| var vertexShaderSource = [ | |
| 'attribute highp vec3 vertexPosition;', | |
| 'attribute mediump vec3 vertexNormal;', | |
| 'attribute mediump vec3 vertexColor;', | |
| 'uniform highp mat4 projectionMatrix;', | |
| 'uniform mat4 modelViewMatrix;', | |
| 'uniform mat4 normalMatrix;', | |
| 'varying highp vec4 varPosition;', | |
| 'varying mediump vec3 varNormal;', | |
| 'varying mediump vec3 iVertexColor;', | |
| 'void main(void)', | |
| '{', | |
| 'varPosition = modelViewMatrix * vec4(vertexPosition, 1.0);', | |
| 'gl_Position = projectionMatrix * varPosition;', | |
| 'varNormal = vec3(normalMatrix * vec4(vertexNormal, 0.0));', | |
| 'iVertexColor = vertexColor;', | |
| '}' ].join('\n'), | |
| shader = new vgl.shader(gl.VERTEX_SHADER); | |
| shader.setShaderSource(vertexShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of Phong fragment shader | |
| * | |
| * Helper function to create Phong fragment shader | |
| * | |
| * NOTE: Shader assumes directional light | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createPhongFragmentShader = function(context) { | |
| 'use strict'; | |
| var fragmentShaderSource = [ | |
| 'precision mediump float;', | |
| 'varying vec3 varNormal;', | |
| 'varying vec4 varPosition;', | |
| 'varying mediump vec3 iVertexColor;', | |
| 'const vec3 lightPos = vec3(0.0, 0.0,10000.0);', | |
| 'const vec3 ambientColor = vec3(0.01, 0.01, 0.01);', | |
| 'const vec3 specColor = vec3(1.0, 1.0, 1.0);', | |
| 'void main() {', | |
| 'vec3 normal = normalize(varNormal);', | |
| 'vec3 lightDir = normalize(lightPos);', | |
| 'vec3 reflectDir = -reflect(lightDir, normal);', | |
| 'vec3 viewDir = normalize(-varPosition.xyz);', | |
| 'float lambertian = max(dot(lightDir,normal), 0.0);', | |
| 'float specular = 0.0;', | |
| 'if(lambertian > 0.0) {', | |
| 'float specAngle = max(dot(reflectDir, viewDir), 0.0);', | |
| 'specular = pow(specAngle, 64.0);', | |
| '}', | |
| 'gl_FragColor = vec4(ambientColor +', | |
| 'lambertian*iVertexColor +', | |
| 'specular*specColor, 1.0);', | |
| '}' ].join('\n'), | |
| shader = new vgl.shader(gl.FRAGMENT_SHADER); | |
| shader.setShaderSource(fragmentShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of fragment shader with an assigned constant color. | |
| * | |
| * Helper function to create default fragment shader | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createFragmentShaderSolidColor = function(context, color) { | |
| 'use strict'; | |
| var fragmentShaderSource = ['uniform mediump float opacity;', | |
| 'void main(void) {', | |
| 'gl_FragColor = vec4(' + color[0] + ',' + color[1] + ',' + color[2] + ', opacity);', | |
| '}' ].join('\n'), | |
| shader = new vgl.shader(gl.FRAGMENT_SHADER); | |
| shader.setShaderSource(fragmentShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of fragment shader that maps values into colors bia lookup table | |
| * | |
| * Helper function to create default fragment shader | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createFragmentShaderColorMap = function(context) { | |
| 'use strict'; | |
| var fragmentShaderSource = [ | |
| 'varying mediump float iVertexScalar;', | |
| 'uniform sampler2D sampler2d;', | |
| 'uniform mediump float opacity;', | |
| 'void main(void) {', | |
| 'gl_FragColor = vec4(texture2D(sampler2d, vec2(iVertexScalar, 0.0)).xyz, opacity);', | |
| '}' ].join('\n'), | |
| shader = new vgl.shader(gl.FRAGMENT_SHADER); | |
| shader.setShaderSource(fragmentShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of vertex shader for point sprites | |
| * | |
| * Helper function to create default point sprites vertex shader | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createPointSpritesVertexShader = function(context) { | |
| 'use strict'; | |
| var vertexShaderSource = [ | |
| 'attribute vec3 vertexPosition;', | |
| 'attribute vec3 vertexColor;', | |
| 'uniform mediump vec2 pointSize;', | |
| 'uniform mat4 modelViewMatrix;', | |
| 'uniform mat4 projectionMatrix;', | |
| 'uniform float height;', | |
| 'varying mediump vec3 iVertexColor;', | |
| 'varying highp float iVertexScalar;', | |
| 'void main(void)', | |
| '{', | |
| 'mediump float realPointSize = pointSize.y;', | |
| 'if (pointSize.x > pointSize.y) {', | |
| ' realPointSize = pointSize.x;}', | |
| 'gl_PointSize = realPointSize ;', | |
| 'iVertexScalar = vertexPosition.z;', | |
| 'gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition.xy, height, 1.0);', | |
| ' iVertexColor = vertexColor;', '}' ].join('\n'), | |
| shader = new vgl.shader(gl.VERTEX_SHADER); | |
| shader.setShaderSource(vertexShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of fragment shader for point sprites | |
| * | |
| * Helper function to create default point sprites fragment shader | |
| * | |
| * @param context | |
| * @returns {vgl.shader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createPointSpritesFragmentShader = function(context) { | |
| 'use strict'; | |
| var fragmentShaderSource = [ | |
| 'varying mediump vec3 iVertexColor;', | |
| 'varying highp float iVertexScalar;', | |
| 'uniform sampler2D opacityLookup;', | |
| 'uniform highp float lutMin;', | |
| 'uniform highp float lutMax;', | |
| 'uniform sampler2D scalarsToColors;', | |
| 'uniform int useScalarsToColors;', | |
| 'uniform int useVertexColors;', | |
| 'uniform mediump vec2 pointSize;', | |
| 'uniform mediump float vertexColorWeight;', | |
| 'void main(void) {', | |
| 'mediump vec2 realTexCoord;', | |
| 'if (pointSize.x > pointSize.y) {', | |
| ' realTexCoord = vec2(1.0, pointSize.y/pointSize.x) * gl_PointCoord;', | |
| '} else {', | |
| ' realTexCoord = vec2(pointSize.x/pointSize.y, 1.0) * gl_PointCoord;', | |
| '}', | |
| 'highp float texOpacity = texture2D(opacityLookup, realTexCoord).w;', | |
| 'if (useScalarsToColors == 1) {', | |
| ' gl_FragColor = vec4(texture2D(scalarsToColors, vec2((iVertexScalar - lutMin)/(lutMax - lutMin), 0.0)).xyz, texOpacity);', | |
| '} else if (useVertexColors == 1) {', | |
| ' gl_FragColor = vec4(iVertexColor, texOpacity);', | |
| '} else {', | |
| ' gl_FragColor = vec4(texture2D(opacityLookup, realTexCoord).xyz, texOpacity);', | |
| '}}' | |
| ].join('\n'), | |
| shader = new vgl.shader(gl.FRAGMENT_SHADER); | |
| shader.setShaderSource(fragmentShaderSource); | |
| return shader; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of texture material | |
| * | |
| * Helper function to create a texture material | |
| * | |
| * @returns {vgl.material} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createTextureMaterial = function(isRgba) { | |
| 'use strict'; | |
| var mat = new vgl.material(), | |
| blend = new vgl.blend(), | |
| prog = new vgl.shaderProgram(), | |
| vertexShader = vgl.utils.createTextureVertexShader(gl), | |
| fragmentShader = null, | |
| posVertAttr = new vgl.vertexAttribute("vertexPosition"), | |
| texCoordVertAttr = new vgl.vertexAttribute("textureCoord"), | |
| pointsizeUniform = new vgl.floatUniform("pointSize", 5.0), | |
| modelViewUniform = new vgl.modelViewUniform("modelViewMatrix"), | |
| projectionUniform = new vgl.projectionUniform("projectionMatrix"), | |
| samplerUniform = new vgl.uniform(gl.INT, "sampler2d"), | |
| opacityUniform = null; | |
| samplerUniform.set(0); | |
| prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position); | |
| prog.addVertexAttribute(texCoordVertAttr, | |
| vgl.vertexAttributeKeys.TextureCoordinate); | |
| prog.addUniform(pointsizeUniform); | |
| prog.addUniform(modelViewUniform); | |
| prog.addUniform(projectionUniform); | |
| if (isRgba) { | |
| fragmentShader = vgl.utils.createRgbaTextureFragmentShader(gl); | |
| } else { | |
| fragmentShader = vgl.utils.createTextureFragmentShader(gl); | |
| } | |
| opacityUniform = new vgl.floatUniform('opacity', 1.0); | |
| prog.addUniform(opacityUniform); | |
| prog.addShader(fragmentShader); | |
| prog.addShader(vertexShader); | |
| mat.addAttribute(prog); | |
| mat.addAttribute(blend); | |
| return mat; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of geometry material | |
| * | |
| * Helper function to create geometry material | |
| * | |
| * @returns {vgl.material} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createGeometryMaterial = function() { | |
| 'use strict'; | |
| var mat = new vgl.material(), | |
| blend = new vgl.blend(), | |
| prog = new vgl.shaderProgram(), | |
| pointSize = 5.0, | |
| opacity = 0.5, | |
| vertexShader = vgl.utils.createVertexShader(gl), | |
| fragmentShader = vgl.utils.createFragmentShader(gl), | |
| posVertAttr = new vgl.vertexAttribute("vertexPosition"), | |
| colorVertAttr = new vgl.vertexAttribute("vertexColor"), | |
| pointsizeUniform = new vgl.floatUniform("pointSize", pointSize), | |
| opacityUniform = new vgl.floatUniform("opacity", opacity), | |
| modelViewUniform = new vgl.modelViewUniform("modelViewMatrix"), | |
| projectionUniform = new vgl.projectionUniform("projectionMatrix"); | |
| prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position); | |
| prog.addVertexAttribute(colorVertAttr, vgl.vertexAttributeKeys.Color); | |
| prog.addUniform(pointsizeUniform); | |
| prog.addUniform(opacityUniform); | |
| prog.addUniform(modelViewUniform); | |
| prog.addUniform(projectionUniform); | |
| prog.addShader(fragmentShader); | |
| prog.addShader(vertexShader); | |
| mat.addAttribute(prog); | |
| mat.addAttribute(blend); | |
| return mat; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of geometry material | |
| * | |
| * Helper function to create geometry material | |
| * | |
| * @returns {vgl.material} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createPointGeometryMaterial = function(opacity) { | |
| 'use strict'; | |
| var mat = new vgl.material(), | |
| blend = new vgl.blend(), | |
| prog = new vgl.shaderProgram(), | |
| opacity = opacity === undefined ? 1.0 : opacity, | |
| vertexShader = vgl.utils.createPointVertexShader(gl), | |
| fragmentShader = vgl.utils.createFragmentShader(gl), | |
| posVertAttr = new vgl.vertexAttribute("vertexPosition"), | |
| colorVertAttr = new vgl.vertexAttribute("vertexColor"), | |
| sizeVertAttr = new vgl.vertexAttribute("vertexSize"), | |
| opacityUniform = new vgl.floatUniform("opacity", opacity), | |
| modelViewUniform = new vgl.modelViewUniform("modelViewMatrix"), | |
| projectionUniform = new vgl.projectionUniform("projectionMatrix"); | |
| prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position); | |
| prog.addVertexAttribute(colorVertAttr, vgl.vertexAttributeKeys.Color); | |
| prog.addVertexAttribute(sizeVertAttr, vgl.vertexAttributeKeys.Scalar); | |
| prog.addUniform(opacityUniform); | |
| prog.addUniform(modelViewUniform); | |
| prog.addUniform(projectionUniform); | |
| prog.addShader(fragmentShader); | |
| prog.addShader(vertexShader); | |
| mat.addAttribute(prog); | |
| mat.addAttribute(blend); | |
| return mat; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of geometry material with the phong shader | |
| * | |
| * Helper function to create color phong shaded geometry material | |
| * | |
| * @returns {vgl.material} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createPhongMaterial = function() { | |
| 'use strict'; | |
| var mat = new vgl.material(), | |
| blend = new vgl.blend(), | |
| prog = new vgl.shaderProgram(), | |
| vertexShader = vgl.utils.createPhongVertexShader(gl), | |
| fragmentShader = vgl.utils.createPhongFragmentShader(gl), | |
| posVertAttr = new vgl.vertexAttribute("vertexPosition"), | |
| normalVertAttr = new vgl.vertexAttribute("vertexNormal"), | |
| colorVertAttr = new vgl.vertexAttribute("vertexColor"), | |
| opacityUniform = new vgl.floatUniform("opacity", 1.0), | |
| modelViewUniform = new vgl.modelViewUniform("modelViewMatrix"), | |
| normalUniform = new vgl.normalMatrixUniform("normalMatrix"), | |
| projectionUniform = new vgl.projectionUniform("projectionMatrix"); | |
| prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position); | |
| prog.addVertexAttribute(normalVertAttr, vgl.vertexAttributeKeys.Normal); | |
| prog.addVertexAttribute(colorVertAttr, vgl.vertexAttributeKeys.Color); | |
| prog.addUniform(opacityUniform); | |
| prog.addUniform(modelViewUniform); | |
| prog.addUniform(projectionUniform); | |
| prog.addUniform(normalUniform); | |
| prog.addShader(fragmentShader); | |
| prog.addShader(vertexShader); | |
| mat.addAttribute(prog); | |
| mat.addAttribute(blend); | |
| return mat; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of colored geometry material | |
| * | |
| * Helper function to create color geometry material | |
| * | |
| * @returns {vgl.material} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createColorMaterial = function() { | |
| 'use strict'; | |
| var mat = new vgl.material(), | |
| blend = new vgl.blend(), | |
| prog = new vgl.shaderProgram(), | |
| vertexShader = vgl.utils.createVertexShader(gl), | |
| fragmentShader = vgl.utils.createFragmentShader(gl), | |
| posVertAttr = new vgl.vertexAttribute("vertexPosition"), | |
| texCoordVertAttr = new vgl.vertexAttribute("textureCoord"), | |
| colorVertAttr = new vgl.vertexAttribute("vertexColor"), | |
| pointsizeUniform = new vgl.floatUniform("pointSize", 5.0), | |
| opacityUniform = new vgl.floatUniform("opacity", 0.5), | |
| modelViewUniform = new vgl.modelViewUniform("modelViewMatrix"), | |
| projectionUniform = new vgl.projectionUniform("projectionMatrix"); | |
| prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position); | |
| prog.addVertexAttribute(colorVertAttr, vgl.vertexAttributeKeys.Color); | |
| prog.addVertexAttribute(texCoordVertAttr, | |
| vgl.vertexAttributeKeys.TextureCoordinate); | |
| prog.addUniform(pointsizeUniform); | |
| prog.addUniform(opacityUniform); | |
| prog.addUniform(modelViewUniform); | |
| prog.addUniform(projectionUniform); | |
| prog.addShader(fragmentShader); | |
| prog.addShader(vertexShader); | |
| mat.addAttribute(prog); | |
| mat.addAttribute(blend); | |
| return mat; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of geometry material | |
| * | |
| * Helper function to create geometry material | |
| * | |
| * @returns {vgl.material} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createColorMappedMaterial = function(lut) { | |
| 'use strict'; | |
| if (!lut) { | |
| lut = new vgl.lookupTable(); | |
| } | |
| var scalarRange = lut.range(), | |
| mat = new vgl.material(), | |
| blend = new vgl.blend(), | |
| prog = new vgl.shaderProgram(), | |
| vertexShader = vgl.utils.createVertexShaderColorMap( | |
| gl,scalarRange[0],scalarRange[1]), | |
| fragmentShader = vgl.utils.createFragmentShaderColorMap(gl), | |
| posVertAttr = new vgl.vertexAttribute("vertexPosition"), | |
| scalarVertAttr = new vgl.vertexAttribute("vertexScalar"), | |
| pointsizeUniform = new vgl.floatUniform("pointSize", 5.0), | |
| opacityUniform = new vgl.floatUniform("opacity", 0.5), | |
| lutMinUniform = new vgl.floatUniform("lutMin", scalarRange[0]), | |
| lutMaxUniform = new vgl.floatUniform("lutMax", scalarRange[1]), | |
| modelViewUniform = new vgl.modelViewUniform("modelViewMatrix"), | |
| projectionUniform = new vgl.projectionUniform("projectionMatrix"), | |
| samplerUniform = new vgl.uniform(gl.FLOAT, "sampler2d"), | |
| lookupTable = lut; | |
| samplerUniform.set(0); | |
| prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position); | |
| prog.addVertexAttribute(scalarVertAttr, vgl.vertexAttributeKeys.Scalar); | |
| prog.addUniform(pointsizeUniform); | |
| prog.addUniform(opacityUniform); | |
| prog.addUniform(lutMinUniform); | |
| prog.addUniform(lutMaxUniform); | |
| prog.addUniform(modelViewUniform); | |
| prog.addUniform(projectionUniform); | |
| prog.addShader(fragmentShader); | |
| prog.addShader(vertexShader); | |
| mat.addAttribute(prog); | |
| mat.addAttribute(blend); | |
| mat.addAttribute(lookupTable); | |
| return mat; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update color mapped material | |
| * | |
| * @param mat | |
| * @param scalarRange | |
| * @param lut | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.updateColorMappedMaterial = function(mat, lut) { | |
| 'use strict'; | |
| if (!mat) { | |
| console.log('[warning] Invalid material. Nothing to update.'); | |
| return; | |
| } | |
| if (!lut) { | |
| console.log('[warning] Invalid lookup table. Nothing to update.'); | |
| return; | |
| } | |
| var lutMin = mat.shaderProgram().uniform('lutMin'), | |
| lutMax = mat.shaderProgram().uniform('lutMax'); | |
| lutMin.set(lut.range()[0]); | |
| lutMax.set(lut.range()[1]); | |
| // This will replace the existing lookup table | |
| mat.setAttribute(lut); | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of solid color material | |
| * | |
| * Helper function to create geometry material | |
| * | |
| * @returns {vgl.material} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createSolidColorMaterial = function(color) { | |
| 'use strict'; | |
| if (!color) { | |
| color = [1.0,1.0,1.0]; | |
| } | |
| var mat = new vgl.material(), | |
| blend = new vgl.blend(), | |
| prog = new vgl.shaderProgram(), | |
| vertexShader = vgl.utils.createVertexShaderSolidColor(gl), | |
| fragmentShader = vgl.utils.createFragmentShaderSolidColor(gl, color), | |
| posVertAttr = new vgl.vertexAttribute("vertexPosition"), | |
| pointsizeUniform = new vgl.floatUniform("pointSize", 5.0), | |
| opacityUniform = new vgl.floatUniform("opacity", 1.0), | |
| modelViewUniform = new vgl.modelViewUniform("modelViewMatrix"), | |
| projectionUniform = new vgl.projectionUniform("projectionMatrix"); | |
| prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position); | |
| prog.addUniform(pointsizeUniform); | |
| prog.addUniform(opacityUniform); | |
| prog.addUniform(modelViewUniform); | |
| prog.addUniform(projectionUniform); | |
| prog.addShader(fragmentShader); | |
| prog.addShader(vertexShader); | |
| mat.addAttribute(prog); | |
| mat.addAttribute(blend); | |
| return mat; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of point sprites material | |
| * | |
| * Helper function to create point sprites material | |
| * | |
| * @returns {vgl.material} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createPointSpritesMaterial = function(image, lut) { | |
| 'use strict'; | |
| var scalarRange = lut === undefined ? [0, 1] : lut.range(), | |
| mat = new vgl.material(), | |
| blend = new vgl.blend(), | |
| prog = new vgl.shaderProgram(), | |
| vertexShader = vgl.utils.createPointSpritesVertexShader(gl), | |
| fragmentShader = vgl.utils.createPointSpritesFragmentShader(gl), | |
| posVertAttr = new vgl.vertexAttribute("vertexPosition"), | |
| colorVertAttr = new vgl.vertexAttribute("vertexColor"), | |
| heightUniform = new vgl.floatUniform("height", 0.0), | |
| vertexColorWeightUniform = | |
| new vgl.floatUniform("vertexColorWeight", 0.0), | |
| lutMinUniform = new vgl.floatUniform("lutMin", scalarRange[0]), | |
| lutMaxUniform = new vgl.floatUniform("lutMax", scalarRange[1]), | |
| modelViewUniform = new vgl.modelViewUniform("modelViewMatrix"), | |
| projectionUniform = new vgl.projectionUniform("projectionMatrix"), | |
| samplerUniform = new vgl.uniform(gl.INT, "opacityLookup"), | |
| scalarsToColors = new vgl.uniform(gl.INT, "scalarsToColors"), | |
| useScalarsToColors = new vgl.uniform(gl.INT, "useScalarsToColors"), | |
| useVertexColors = new vgl.uniform(gl.INT, "useVertexColors"), | |
| pointSize = new vgl.uniform(gl.FLOAT_VEC2, "pointSize"), | |
| texture = new vgl.texture(); | |
| samplerUniform.set(0); | |
| scalarsToColors.set(1); | |
| useScalarsToColors.set(0); | |
| useVertexColors.set(0); | |
| pointSize.set([1.0, 1.0]); | |
| prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position); | |
| prog.addVertexAttribute(colorVertAttr, vgl.vertexAttributeKeys.Color); | |
| prog.addUniform(heightUniform); | |
| prog.addUniform(vertexColorWeightUniform); | |
| prog.addUniform(modelViewUniform); | |
| prog.addUniform(projectionUniform); | |
| prog.addUniform(samplerUniform); | |
| prog.addUniform(useVertexColors); | |
| prog.addUniform(useScalarsToColors); | |
| prog.addUniform(pointSize); | |
| prog.addShader(fragmentShader); | |
| prog.addShader(vertexShader); | |
| mat.addAttribute(prog); | |
| mat.addAttribute(blend); | |
| if (lut) { | |
| prog.addUniform(scalarsToColors); | |
| useScalarsToColors.set(1); | |
| prog.addUniform(lutMinUniform); | |
| prog.addUniform(lutMaxUniform); | |
| lut.setTextureUnit(1); | |
| mat.addAttribute(lut); | |
| } | |
| texture.setImage(image); | |
| texture.setTextureUnit(0); | |
| mat.addAttribute(texture); | |
| return mat; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of an actor that contains a plane geometry | |
| * | |
| * Function to create a plane node This method will create a plane actor | |
| * with texture coordinates, eventually normal, and plane material. | |
| * | |
| * @returns {vgl.actor} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createPlane = function(originX, originY, originZ, | |
| point1X, point1Y, point1Z, | |
| point2X, point2Y, point2Z) { | |
| 'use strict'; | |
| var mapper = new vgl.mapper(), | |
| planeSource = new vgl.planeSource(), | |
| mat = vgl.utils.createGeometryMaterial(), | |
| actor = new vgl.actor(); | |
| planeSource.setOrigin(originX, originY, originZ); | |
| planeSource.setPoint1(point1X, point1Y, point1Z); | |
| planeSource.setPoint2(point2X, point2Y, point2Z); | |
| mapper.setGeometryData(planeSource.create()); | |
| actor.setMapper(mapper); | |
| actor.setMaterial(mat); | |
| return actor; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of an actor that contains a texture plane geometry | |
| * | |
| * Helper function to create a plane textured node This method will create | |
| * a plane actor with texture coordinates, eventually normal, and plane | |
| * material. | |
| * | |
| * @returns {vgl.actor} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createTexturePlane = function(originX, originY, originZ, | |
| point1X, point1Y, point1Z, | |
| point2X, point2Y, point2Z, | |
| isRgba) { | |
| 'use strict'; | |
| var mapper = new vgl.mapper(), | |
| planeSource = new vgl.planeSource(), | |
| mat = vgl.utils.createTextureMaterial(isRgba), | |
| actor = new vgl.actor(); | |
| planeSource.setOrigin(originX, originY, originZ); | |
| planeSource.setPoint1(point1X, point1Y, point1Z); | |
| planeSource.setPoint2(point2X, point2Y, point2Z); | |
| mapper.setGeometryData(planeSource.create()); | |
| actor.setMapper(mapper); | |
| actor.setMaterial(mat); | |
| return actor; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of an actor that contains points | |
| * | |
| * Helper function to create a point node This method will create a point | |
| * actor with texture coordinates, eventually normal, and plane material. | |
| * | |
| * @returns {vgl.actor} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createPoints = function(positions, size, colors, texcoords, opacity) { | |
| 'use strict'; | |
| if (!positions) { | |
| console.log("[ERROR] Cannot create points without positions"); | |
| return null; | |
| } | |
| var opacity = opacity === undefined ? 1.0 : opacity, | |
| mapper = new vgl.mapper(), | |
| pointSource = new vgl.pointSource(), | |
| mat = vgl.utils.createPointGeometryMaterial(opacity), | |
| actor = new vgl.actor(); | |
| pointSource.setPositions(positions); | |
| if (colors) { | |
| pointSource.setColors(colors); | |
| } | |
| if (texcoords) { | |
| pointSource.setTextureCoordinates(texcoords); | |
| } | |
| if (size) { | |
| pointSource.setSize(size) | |
| } else { | |
| pointSource.setSize(1.0); | |
| } | |
| mapper.setGeometryData(pointSource.create()); | |
| actor.setMapper(mapper); | |
| actor.setMaterial(mat); | |
| return actor; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of an actor that contains point sprites | |
| * | |
| * Helper function to create a point sprites node This method will create | |
| * a point sprites actor with texture coordinates, normals, and a point sprites | |
| * material. | |
| * | |
| * @returns {vgl.actor} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createPointSprites = function(image, positions, colors, | |
| texcoords) { | |
| 'use strict'; | |
| if (!image) { | |
| console.log("[ERROR] Point sprites requires an image"); | |
| return null; | |
| } | |
| if (!positions) { | |
| console.log("[ERROR] Cannot create points without positions"); | |
| return null; | |
| } | |
| var mapper = new vgl.mapper(), | |
| pointSource = new vgl.pointSource(), | |
| mat = vgl.utils.createPointSpritesMaterial(image), | |
| actor = new vgl.actor(); | |
| pointSource.setPositions(positions); | |
| if (colors) { | |
| pointSource.setColors(colors); | |
| } | |
| if (texcoords) { | |
| pointSource.setTextureCoordinates(texcoords); | |
| } | |
| mapper.setGeometryData(pointSource.create()); | |
| actor.setMapper(mapper); | |
| actor.setMaterial(mat); | |
| return actor; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create lines given positions, colors, and desired length | |
| * | |
| * @param positions | |
| * @param colors | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createLines = function(positions, colors) { | |
| 'use strict'; | |
| if (!positions) { | |
| console.log("[ERROR] Cannot create points without positions"); | |
| return null; | |
| } | |
| var mapper = new vgl.mapper(), | |
| lineSource = new vgl.lineSource(), | |
| mat = vgl.utils.createGeometryMaterial(), | |
| actor = new vgl.actor(); | |
| lineSource.setPositions(positions); | |
| if (colors) { | |
| lineSource.setColors(colors); | |
| } | |
| mapper.setGeometryData(lineSource.create()); | |
| actor.setMapper(mapper); | |
| actor.setMaterial(mat); | |
| return actor; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create color legend | |
| * | |
| * @param lookupTable | |
| * @param width | |
| * @param height | |
| * @param origin | |
| * @param divs | |
| * @returns {Array} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.createColorLegend = function(varname, lookupTable, origin, | |
| width, height, countMajor, | |
| countMinor) { | |
| 'use strict'; | |
| if (!lookupTable) { | |
| console.log('[error] Invalid lookup table'); | |
| return []; | |
| } | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create labels for the legend | |
| * | |
| * @param ticks | |
| * @param range | |
| * @param divs | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| function createLabels(varname, positions, range) { | |
| if (!positions) { | |
| console.log('[error] Create labels requires positions (x,y,z) array'); | |
| return; | |
| } | |
| if (positions.length % 3 !== 0) { | |
| console.log('[error] Create labels require positions array contain 3d points'); | |
| return; | |
| } | |
| if (!range) { | |
| console.log('[error] Create labels requires Valid range'); | |
| return; | |
| } | |
| var actor = null, | |
| size = vgl.utils.computePowerOfTwo(48), | |
| index = 0, | |
| actors = [], | |
| origin = [], | |
| pt1 = [], | |
| pt2 = [], | |
| delta = (positions[6] - positions[0]), | |
| axisLabelOffset = 4, i; | |
| origin.length = 3; | |
| pt1.length = 3; | |
| pt2.length = 3; | |
| // For now just create labels for end points | |
| for (i = 0; i < 2; ++i) { | |
| index = i * (positions.length - 3); | |
| origin[0] = positions[index] - delta; | |
| origin[1] = positions[index + 1] - 2 * delta; | |
| origin[2] = positions[index + 2]; | |
| pt1[0] = positions[index] + delta; | |
| pt1[1] = origin[1]; | |
| pt1[2] = origin[2]; | |
| pt2[0] = origin[0]; | |
| pt2[1] = positions[1]; | |
| pt2[2] = origin[2]; | |
| actor = vgl.utils.createTexturePlane( | |
| origin[0], origin[1], origin[2], | |
| pt1[0], pt1[1], pt1[2], | |
| pt2[0], pt2[1], pt2[2], true); | |
| actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute); | |
| actor.material().setBinNumber(vgl.material.RenderBin.Overlay); | |
| actor.material().addAttribute(vgl.utils.create2DTexture( | |
| range[i].toFixed(2).toString(), 12, null)); | |
| actors.push(actor); | |
| } | |
| // Create axis label | |
| origin[0] = (positions[0] + positions[positions.length - 3] - size) * 0.5; | |
| origin[1] = positions[1] + axisLabelOffset; | |
| origin[2] = positions[2]; | |
| pt1[0] = origin[0] + size; | |
| pt1[1] = origin[1]; | |
| pt1[2] = origin[2]; | |
| pt2[0] = origin[0]; | |
| pt2[1] = origin[1] + size; | |
| pt2[2] = origin[2]; | |
| actor = vgl.utils.createTexturePlane( | |
| origin[0], origin[1], origin[2], | |
| pt1[0], pt1[1], pt1[2], | |
| pt2[0], pt2[1], pt2[2], true); | |
| actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute); | |
| actor.material().setBinNumber(vgl.material.RenderBin.Overlay); | |
| actor.material().addAttribute(vgl.utils.create2DTexture( | |
| varname, 24, null)); | |
| actors.push(actor); | |
| return actors; | |
| } | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // TODO Currently we assume that the ticks are laid on x-axis | |
| // and this is on a 2D plane (ignoring Z axis. For now lets | |
| // not draw minor ticks. | |
| /** | |
| * Create ticks and labels | |
| * | |
| * @param originX | |
| * @param originY | |
| * @param originZ | |
| * @param pt1X | |
| * @param pt1Y | |
| * @param pt1Z | |
| * @param pt2X | |
| * @param pt2Y | |
| * @param pt2Z | |
| * @param divs | |
| * @param heightMajor | |
| * @param heightMinor | |
| * @returns {Array} Returns array of vgl.actor | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| function createTicksAndLabels(varname, lut, | |
| originX, originY, originZ, | |
| pt1X, pt1Y, pt1Z, | |
| pt2X, pt2Y, pt2Z, | |
| countMajor, countMinor, | |
| heightMajor, heightMinor) { | |
| var width = pt2X - pt1X, | |
| index = null, | |
| delta = width / countMajor, | |
| positions = [], | |
| actor = null, | |
| actors = []; | |
| for (index = 0; index <= countMajor; ++index) { | |
| positions.push(pt1X + delta * index); | |
| positions.push(pt1Y); | |
| positions.push(pt1Z); | |
| positions.push(pt1X + delta * index); | |
| positions.push(pt1Y + heightMajor); | |
| positions.push(pt1Z); | |
| } | |
| // TODO: Fix this | |
| //actor = vgl.utils.createLines(positions, null); | |
| //actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute); | |
| //actor.material().setBinNumber(vgl.material.RenderBin.Overlay); | |
| //actors.push(actor); | |
| actors = actors.concat(createLabels(varname, positions, lut.range())); | |
| return actors; | |
| } | |
| // TODO Currently we create only one type of legend | |
| var pt1X = origin[0] + width, | |
| pt1Y = origin[1], | |
| pt1Z = 0.0, | |
| pt2X = origin[0], | |
| pt2Y = origin[1] + height, | |
| pt2Z = 0.0, | |
| actors = [], | |
| actor = null, | |
| mapper = null, | |
| mat = null, | |
| group = vgl.groupNode(); | |
| actor = vgl.utils.createTexturePlane( | |
| origin[0], origin[1], origin[2], | |
| pt1X, pt1Y, pt1Z, | |
| pt2X, pt2Y, pt2Z, true | |
| ); | |
| mat = actor.material(); | |
| mat.addAttribute(lookupTable); | |
| actor.setMaterial(mat); | |
| group.addChild(actor); | |
| actor.material().setBinNumber(vgl.material.RenderBin.Overlay); | |
| actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute); | |
| actors.push(actor); | |
| actors = actors.concat(createTicksAndLabels( | |
| varname, | |
| lookupTable, | |
| origin[0], origin[1], origin[1], | |
| pt2X, pt1Y, pt1Z, | |
| pt1X, pt1Y, pt1Z, | |
| countMajor, countMinor, 5, 3)); | |
| // TODO This needs to change so that we can return a group node | |
| // which should get appended to the scene graph | |
| return actors; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create 2D texture by rendering text using canvas2D context | |
| * | |
| * @param textToWrite | |
| * @param textSize | |
| * @param color | |
| * @returns {vgl.texture} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.utils.create2DTexture = function(textToWrite, textSize, | |
| color, font, alignment, baseline, bold) { | |
| 'use strict'; | |
| var canvas = document.getElementById('textRendering'), | |
| ctx = null, | |
| texture = vgl.texture(); | |
| font = font || 'sans-serif'; | |
| alignment = alignment || 'center'; | |
| baseline = baseline || 'bottom'; | |
| if (typeof bold === 'undefined') { | |
| bold = true; | |
| } | |
| if (!canvas) { | |
| canvas = document.createElement('canvas'); | |
| } | |
| ctx = canvas.getContext('2d'); | |
| canvas.setAttribute('id', 'textRendering'); | |
| canvas.style.display = 'none'; | |
| // Make width and height equal so that we get pretty looking text. | |
| canvas.height = vgl.utils.computePowerOfTwo(8 * textSize); | |
| canvas.width = canvas.height; | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0)'; | |
| ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); | |
| // This determines the text colour, it can take a hex value or rgba value (e.g. rgba(255,0,0,0.5)) | |
| ctx.fillStyle = 'rgba(200, 85, 10, 1.0)'; | |
| // This determines the alignment of text, e.g. left, center, right | |
| ctx.textAlign = alignment; | |
| // This determines the baseline of the text, e.g. top, middle, bottom | |
| ctx.textBaseline = baseline; | |
| // This determines the size of the text and the font family used | |
| ctx.font = 4 * textSize + "px " + font; | |
| if (bold) { | |
| ctx.font = "bold " + ctx.font; | |
| } | |
| ctx.fillText(textToWrite, canvas.width/2, canvas.height/2, canvas.width); | |
| texture.setImage(canvas); | |
| texture.updateDimensions(); | |
| return texture; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class picker | |
| * | |
| * @class vgl.picker | |
| * @returns {vgl.picker} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.picker = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.picker)) { | |
| return new vgl.picker(); | |
| } | |
| vgl.object.call(this); | |
| /** @private */ | |
| var m_that = this, | |
| m_tolerance = 0.025, | |
| m_actors = []; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get actors intersected | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getActors = function() { | |
| return m_actors; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Perform pick operation | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.pick = function(selectionX, selectionY, renderer) { | |
| // Check if variables are acceptable | |
| if (typeof(selectionX) === "undefined"){ | |
| return 0; | |
| } | |
| if (typeof(selectionY) === "undefined"){ | |
| return 0; | |
| } | |
| if (typeof(renderer) === "undefined"){ | |
| return 0; | |
| } | |
| // Clean list of actors intersected previously | |
| m_actors = []; | |
| // | |
| var camera = renderer.camera(), | |
| width = renderer.width(), | |
| height = renderer.height(), | |
| fpoint = camera.focalPoint(), | |
| focusWorldPt = vec4.fromValues(fpoint[0], fpoint[1], fpoint[2], 1.0), | |
| focusDisplayPt = renderer.worldToDisplay( | |
| focusWorldPt, camera.viewMatrix(), | |
| camera.projectionMatrix(), width, height), | |
| displayPt = vec4.fromValues(selectionX, | |
| selectionY, focusDisplayPt[2], 1.0), | |
| // Convert selection point into world coordinates | |
| worldPt = renderer.displayToWorld(displayPt, camera.viewMatrix(), | |
| camera.projectionMatrix(), width, height), | |
| cameraPos = camera.position(), ray = [], actors, count, i, bb, | |
| tmin, tmax, tymin, tymax, tzmin, tzmax, actor; | |
| for (i = 0; i < 3; ++i){ | |
| ray[i] = worldPt[i] - cameraPos[i]; | |
| } | |
| // Go through all actors and check if intersects | |
| actors = renderer.sceneRoot().children(); | |
| count = 0; | |
| for (i = 0; i < actors.length; ++i) { | |
| actor = actors[i]; | |
| if (actor.visible() === true) { | |
| bb = actor.bounds(); | |
| // Ray-aabb intersection - Smits' method | |
| if (ray[0] >= 0){ | |
| tmin = (bb[0] - cameraPos[0])/ray[0]; | |
| tmax = (bb[1] - cameraPos[0])/ray[0]; | |
| } else { | |
| tmin = (bb[1] - cameraPos[0])/ray[0]; | |
| tmax = (bb[0] - cameraPos[0])/ray[0]; | |
| } | |
| if (ray[1] >= 0){ | |
| tymin = (bb[2] - cameraPos[1])/ray[1]; | |
| tymax = (bb[3] - cameraPos[1])/ray[1]; | |
| } else { | |
| tymin = (bb[3] - cameraPos[1])/ray[1]; | |
| tymax = (bb[2] - cameraPos[1])/ray[1]; | |
| } | |
| if ((tmin > tymax) || (tymin > tmax)) { | |
| continue; | |
| } | |
| if (tymin > tmin) { | |
| tmin = tymin; | |
| } | |
| if (tymax < tmax) { | |
| tmax = tymax; | |
| } | |
| if (ray[2] >= 0) { | |
| tzmin = (bb[4] - cameraPos[2])/ray[2]; | |
| tzmax = (bb[5] - cameraPos[2])/ray[2]; | |
| } else { | |
| tzmin = (bb[5] - cameraPos[2])/ray[2]; | |
| tzmax = (bb[4] - cameraPos[2])/ray[2]; | |
| } | |
| if ((tmin > tzmax) || (tzmin > tmax)) { | |
| continue; | |
| } | |
| if (tzmin > tmin) { | |
| tmin = tzmin; | |
| } | |
| if (tzmax < tmax) { | |
| tmax = tzmax; | |
| } | |
| m_actors[count++] = actor; | |
| } | |
| } | |
| return count; | |
| }; | |
| return this; | |
| }; | |
| inherit(vgl.picker, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2*/ | |
| /*global vgl, ogs, vec4, inherit, $, Uint16Array*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of shapefile reader | |
| * | |
| * This contains code that reads a shapefile and produces vgl geometries | |
| * | |
| * @class | |
| * @returns {vgl.shapefileReader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.shapefileReader = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.shapefileReader)) { | |
| return new vgl.shapefileReader(); | |
| } | |
| var m_that = this; | |
| var SHP_HEADER_LEN = 8; | |
| var SHP_NULL = 0; | |
| var SHP_POINT = 1; | |
| var SHP_POLYGON = 5; | |
| var SHP_POLYLINE = 3; | |
| this.int8 = function (data, offset) { | |
| return data.charCodeAt (offset); | |
| }; | |
| this.bint32 = function (data, offset) { | |
| return ( | |
| ((data.charCodeAt (offset) & 0xff) << 24) + | |
| ((data.charCodeAt (offset + 1) & 0xff) << 16) + | |
| ((data.charCodeAt (offset + 2) & 0xff) << 8) + | |
| (data.charCodeAt (offset + 3) & 0xff) | |
| ); | |
| }; | |
| this.lint32 = function (data, offset) { | |
| return ( | |
| ((data.charCodeAt (offset + 3) & 0xff) << 24) + | |
| ((data.charCodeAt (offset + 2) & 0xff) << 16) + | |
| ((data.charCodeAt (offset + 1) & 0xff) << 8) + | |
| (data.charCodeAt (offset) & 0xff) | |
| ); | |
| }; | |
| this.bint16 = function (data, offset) { | |
| return ( | |
| ((data.charCodeAt (offset) & 0xff) << 8) + | |
| (data.charCodeAt (offset + 1) & 0xff) | |
| ); | |
| }; | |
| this.lint16 = function (data, offset) { | |
| return ( | |
| ((data.charCodeAt (offset + 1) & 0xff) << 8) + | |
| (data.charCodeAt (offset) & 0xff) | |
| ); | |
| }; | |
| this.ldbl64 = function (data, offset) { | |
| var b0 = data.charCodeAt (offset) & 0xff; | |
| var b1 = data.charCodeAt (offset + 1) & 0xff; | |
| var b2 = data.charCodeAt (offset + 2) & 0xff; | |
| var b3 = data.charCodeAt (offset + 3) & 0xff; | |
| var b4 = data.charCodeAt (offset + 4) & 0xff; | |
| var b5 = data.charCodeAt (offset + 5) & 0xff; | |
| var b6 = data.charCodeAt (offset + 6) & 0xff; | |
| var b7 = data.charCodeAt (offset + 7) & 0xff; | |
| var sign = 1 - 2 * (b7 >> 7); | |
| var exp = (((b7 & 0x7f) << 4) + ((b6 & 0xf0) >> 4)) - 1023; | |
| //var frac = (b6 & 0x0f) * Math.pow (2, -4) + b5 * Math.pow (2, -12) + b4 * | |
| // Math.pow (2, -20) + b3 * Math.pow (2, -28) + b2 * Math.pow (2, -36) + b1 * | |
| // Math.pow (2, -44) + b0 * Math.pow (2, -52); | |
| //return sign * (1 + frac) * Math.pow (2, exp); | |
| var frac = (b6 & 0x0f) * Math.pow (2, 48) + b5 * Math.pow (2, 40) + b4 * | |
| Math.pow (2, 32) + b3 * Math.pow (2, 24) + b2 * | |
| Math.pow (2, 16) + b1 * Math.pow (2, 8) + b0; | |
| return sign * (1 + frac * Math.pow (2, -52)) * Math.pow (2, exp); | |
| }; | |
| this.lfloat32 = function (data, offset) { | |
| var b0 = data.charCodeAt (offset) & 0xff; | |
| var b1 = data.charCodeAt (offset + 1) & 0xff; | |
| var b2 = data.charCodeAt (offset + 2) & 0xff; | |
| var b3 = data.charCodeAt (offset + 3) & 0xff; | |
| var sign = 1 - 2 * (b3 >> 7); | |
| var exp = (((b3 & 0x7f) << 1) + ((b2 & 0xfe) >> 7)) - 127; | |
| var frac = (b2 & 0x7f) * Math.pow (2, 16) + b1 * Math.pow (2, 8) + b0; | |
| return sign * (1 + frac * Math.pow (2, -23)) * Math.pow (2, exp); | |
| }; | |
| this.str = function (data, offset, length) { | |
| var chars = []; | |
| var index = offset; | |
| while (index < offset + length) { | |
| var c = data[index]; | |
| if (c.charCodeAt (0) !== 0) | |
| chars.push (c); | |
| else { | |
| break; | |
| } | |
| index ++; | |
| } | |
| return chars.join (''); | |
| }; | |
| this.readHeader = function (data) { | |
| var code = this.bint32(data, 0); | |
| var length = this.bint32(data, 24); | |
| var version = this.lint32(data, 28); | |
| var shapetype = this.lint32(data, 32); | |
| var xmin = this.ldbl64(data, 36); | |
| var ymin = this.ldbl64(data, 44); | |
| var xmax = this.ldbl64(data, 52); | |
| var ymax = this.ldbl64(data, 60); | |
| return { | |
| code: code, | |
| length: length, | |
| version: version, | |
| shapetype: shapetype, | |
| bounds: new Box (vect (xmin, ymin), vect (xmax, ymax)) | |
| }; | |
| }; | |
| this.loadShx = function (data) { | |
| var indices = []; | |
| var appendIndex = function (offset) { | |
| indices.push (2 * m_that.bint32(data, offset)); | |
| return offset + 8; | |
| }; | |
| var offset = 100; | |
| while (offset < data.length) { | |
| offset = appendIndex (offset); | |
| } | |
| return indices; | |
| }; | |
| this.Shapefile = function (options) { | |
| var path = options.path; | |
| $.ajax ({ | |
| url: path + '.shx', | |
| mimeType: 'text/plain; charset=x-user-defined', | |
| success: function (data) { | |
| var indices = this.loadShx(data); | |
| $.ajax ({ | |
| url: path + '.shp', | |
| mimeType: 'text/plain; charset=x-user-defined', | |
| success: function (data) { | |
| $.ajax ({ | |
| url: path + '.dbf', | |
| mimeType: 'text/plain; charset=x-user-defined', | |
| success: function (dbf_data) { | |
| var layer = this.loadShp (data, dbf_data, indices, options); | |
| options.success (layer); | |
| } | |
| }); | |
| } | |
| }); | |
| } | |
| }); | |
| }; | |
| this.localShapefile = function(options) { | |
| var shxFile = options.shx; | |
| var shpFile = options.shp; | |
| var dbfFile = options.dbf; | |
| var shxReader = new FileReader(); | |
| shxReader.onloadend = function() { | |
| var indices = m_that.loadShx(shxReader.result); | |
| var shpReader = new FileReader(); | |
| shpReader.onloadend = function() { | |
| var shpData = shpReader.result; | |
| var dbfReader = new FileReader(); | |
| dbfReader.onloadend = function() { | |
| var dbfData = dbfReader.result; | |
| var layer = m_that.loadShp(shpData, dbfData, indices, options); | |
| options.success(layer); | |
| }; | |
| dbfReader.readAsBinaryString(dbfFile); | |
| }; | |
| shpReader.readAsBinaryString(shpFile); | |
| }; | |
| shxReader.readAsBinaryString(shxFile); | |
| }; | |
| this.loadDBF = function (data) { | |
| var readHeader = function (offset) { | |
| var name = m_that.str(data, offset, 10); | |
| var type = m_that.str(data, offset + 11, 1); | |
| var length = m_that.int8(data, offset + 16); | |
| return { | |
| name: name, | |
| type: type, | |
| length: length | |
| }; | |
| }; | |
| // Level of the dBASE file | |
| var level = m_that.int8(data, 0); | |
| if (level == 4) { | |
| throw "Level 7 dBASE not supported"; | |
| } | |
| // Date of last update | |
| var year = m_that.int8(data, 1); | |
| var month = m_that.int8(data, 2); | |
| var day = m_that.int8(data, 3); | |
| var num_entries = m_that.lint32(data, 4); | |
| var header_size = m_that.lint16(data, 8); | |
| var record_size = m_that.lint16(data, 10); | |
| var FIELDS_START = 32; | |
| var HEADER_LENGTH = 32; | |
| var header_offset = FIELDS_START; | |
| var headers = []; | |
| while (header_offset < header_size - 1) { | |
| headers.push (readHeader(header_offset)); | |
| header_offset += HEADER_LENGTH; | |
| } | |
| var records = []; | |
| var record_offset = header_size; | |
| while (record_offset < header_size + num_entries * record_size) { | |
| var declare = m_that.str(data, record_offset, 1); | |
| if (declare == '*') { | |
| // Record size in the header include the size of the delete indicator | |
| record_offset += record_size; | |
| } | |
| else { | |
| // Move offset to the start of the actual data | |
| record_offset ++; | |
| var record = {}; | |
| for (var i = 0; i < headers.length; i ++) { | |
| var header = headers[i]; | |
| var value; | |
| if (header.type == 'C') { | |
| value = m_that.str(data, record_offset, header.length).trim (); | |
| } | |
| else if (header.type == 'N') { | |
| value = parseFloat (m_that.str (data, record_offset, header.length)); | |
| } | |
| record_offset += header.length; | |
| record[header.name] = value; | |
| } | |
| records.push(record); | |
| } | |
| } | |
| return records; | |
| }; | |
| this.loadShp = function (data, dbf_data, indices, options) { | |
| var features = []; | |
| var readRing = function (offset, start, end) { | |
| var ring = []; | |
| for (var i = end - 1; i >= start; i --) { | |
| var x = m_that.ldbl64(data, offset + 16 * i); | |
| var y = m_that.ldbl64(data, offset + 16 * i + 8); | |
| ring.push ([x, y]); | |
| } | |
| //if (ring.length <= 3) | |
| // return []; | |
| return ring; | |
| }; | |
| var readRecord = function (offset) { | |
| var index = m_that.bint32(data, offset); | |
| var record_length = m_that.bint32(data, offset + 4); | |
| var record_offset = offset + 8; | |
| var geom_type = m_that.lint32(data, record_offset); | |
| if (geom_type == SHP_NULL) { | |
| console.log ("NULL Shape"); | |
| //return offset + 12; | |
| } | |
| else if (geom_type == SHP_POINT) { | |
| var x = m_that.ldbl64(data, record_offset + 4); | |
| var y = m_that.ldbl64(data, record_offset + 12); | |
| features.push ({ | |
| type: 'Point', | |
| attr: {}, | |
| geom: [[x, y]] | |
| }); | |
| } | |
| else if (geom_type == SHP_POLYGON) { | |
| var num_parts = m_that.lint32(data, record_offset + 36); | |
| var num_points = m_that.lint32(data, record_offset + 40); | |
| var parts_start = offset + 52; | |
| var points_start = offset + 52 + 4 * num_parts; | |
| var rings = []; | |
| for (var i = 0; i < num_parts; i ++) { | |
| var start = m_that.lint32(data, parts_start + i * 4); | |
| var end; | |
| if (i + 1 < num_parts) { | |
| end = m_that.lint32(data, parts_start + (i + 1) * 4); | |
| } | |
| else { | |
| end = num_points; | |
| } | |
| var ring = readRing (points_start, start, end); | |
| rings.push (ring); | |
| } | |
| features.push ({ | |
| type: 'Polygon', | |
| attr: {}, | |
| geom: [rings] | |
| }); | |
| } | |
| else if (geom_type == SHP_POLYLINE) { | |
| var num_parts = m_that.lint32(data, record_offset + 36); | |
| var num_points = m_that.lint32(data, record_offset + 40); | |
| var parts_start = offset + 52; | |
| var points_start = offset + 52 + 4 * num_parts; | |
| var rings = []; | |
| for (var i = 0; i < num_parts; i ++) { | |
| var start = m_that.lint32(data, parts_start + i * 4); | |
| var end; | |
| if (i + 1 < num_parts) { | |
| end = m_that.lint32(data, parts_start + (i + 1) * 4); | |
| } | |
| else { | |
| end = num_points; | |
| } | |
| var ring = readRing (points_start, start, end); | |
| rings.push (ring); | |
| } | |
| features.push ({ | |
| type: 'Polyline', | |
| attr: {}, | |
| geom: [rings] | |
| }); | |
| } | |
| else { | |
| throw "Not Implemented: " + geom_type; | |
| } | |
| //return offset + 2 * record_length + SHP_HEADER_LEN; | |
| }; | |
| var attr = this.loadDBF(dbf_data); | |
| //var offset = 100; | |
| //while (offset < length * 2) { | |
| // offset = readRecord (offset); | |
| //} | |
| for (var i = 0; i < indices.length; i ++) { | |
| var offset = indices[i]; | |
| readRecord (offset); | |
| } | |
| var layer = []; //new Layer (); | |
| for (var i = 0; i < features.length; i ++) { | |
| var feature = features[i]; | |
| feature.attr = attr[i]; | |
| layer.push (feature); | |
| } | |
| return layer; | |
| }; | |
| return this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @module vgl | |
| */ | |
| /*jslint devel: true, forin: true, newcap: true, plusplus: true*/ | |
| /*jslint white: true, continue:true, indent: 2, bitwise: true*/ | |
| /*global vgl, vec4, mat4, inherit, unescape*/ | |
| /*global Float32Array, Int8Array, gl, Uint16Array, $*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // | |
| // vbgModule.vtkReader class | |
| // This contains code that unpack a json base64 encoded vtkdataset, | |
| // such as those produced by ParaView's webGL exporter (where much | |
| // of the code originated from) and convert it to VGL representation. | |
| // | |
| ////////////////////////////////////////////////////////////////////////////// | |
| vgl.vtkReader = function() { | |
| 'use strict'; | |
| if (!(this instanceof vgl.vtkReader)) { | |
| return new vgl.vtkReader(); | |
| } | |
| var m_base64Chars = | |
| ['A','B','C','D','E','F','G','H','I','J','K','L','M', | |
| 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', | |
| 'a','b','c','d','e','f','g','h','i','j','k','l','m', | |
| 'n','o','p','q','r','s','t','u','v','w','x','y','z', | |
| '0','1','2','3','4','5','6','7','8','9','+','/'], | |
| m_reverseBase64Chars = [], | |
| m_vtkObjectList = {}, | |
| m_vglObjects = {}, | |
| m_vtkRenderedList = {}, | |
| m_vtkObjHashList = {}, | |
| m_vtkObjectCount = 0, | |
| m_vtkScene = null, | |
| m_node = null, | |
| END_OF_INPUT = -1, | |
| m_base64Str = "", | |
| m_base64Count = 0, | |
| m_pos = 0, | |
| m_viewer = null, | |
| i = 0; | |
| //initialize the array here if not already done. | |
| if (m_reverseBase64Chars.length === 0) { | |
| for ( i = 0; i < m_base64Chars.length; i++) { | |
| m_reverseBase64Chars[m_base64Chars[i]] = i; | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * ntos | |
| * | |
| * @param n | |
| * @returns unescaped n | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.ntos = function (n) { | |
| var unN; | |
| unN = n.toString(16); | |
| if (unN.length === 1) { | |
| unN = '0' + unN; | |
| } | |
| unN = '%' + unN; | |
| return unescape(unN); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * readReverseBase64 | |
| * | |
| * @returns | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readReverseBase64 = function () { | |
| var nextCharacter; | |
| if (!m_base64Str) { | |
| return END_OF_INPUT; | |
| } | |
| while (true) { | |
| if (m_base64Count >= m_base64Str.length) { | |
| return END_OF_INPUT; | |
| } | |
| nextCharacter = m_base64Str.charAt(m_base64Count); | |
| m_base64Count++; | |
| if (m_reverseBase64Chars[nextCharacter]) { | |
| return m_reverseBase64Chars[nextCharacter]; | |
| } | |
| if (nextCharacter === 'A') { | |
| return 0; | |
| } | |
| } | |
| return END_OF_INPUT; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * decode64 | |
| * | |
| * @param str | |
| * @returns result | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.decode64 = function(str) { | |
| var result = '', | |
| inBuffer = new Array(4), | |
| done = false; | |
| m_base64Str = str; | |
| m_base64Count = 0; | |
| while (!done && | |
| (inBuffer[0] = this.readReverseBase64()) !== END_OF_INPUT && | |
| (inBuffer[1] = this.readReverseBase64()) !== END_OF_INPUT) { | |
| inBuffer[2] = this.readReverseBase64(); | |
| inBuffer[3] = this.readReverseBase64(); | |
| result += this.ntos((((inBuffer[0] << 2) & 0xff) | inBuffer[1] >> 4)); | |
| if (inBuffer[2] !== END_OF_INPUT) { | |
| result += this.ntos((((inBuffer[1] << 4) & 0xff) | inBuffer[2] >> 2)); | |
| if (inBuffer[3] !== END_OF_INPUT) { | |
| result += this.ntos((((inBuffer[2] << 6) & 0xff) | inBuffer[3])); | |
| } else { | |
| done = true; | |
| } | |
| } else { | |
| done = true; | |
| } | |
| } | |
| return result; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * readNumber | |
| * | |
| * @param ss | |
| * @returns v | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readNumber = function(ss) { | |
| var v = ((ss[m_pos++]) + | |
| (ss[m_pos++] << 8) + | |
| (ss[m_pos++] << 16) + | |
| (ss[m_pos++] << 24)); | |
| return v; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * readF3Array | |
| * | |
| * @param numberOfPoints | |
| * @param ss | |
| * @returns points | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readF3Array = function(numberOfPoints, ss) { | |
| var size = numberOfPoints*4*3, test = new Int8Array(size), | |
| points = null, i; | |
| for(i = 0; i < size; i++) { | |
| test[i] = ss[m_pos++]; | |
| } | |
| points = new Float32Array(test.buffer); | |
| return points; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * readColorArray | |
| * | |
| * @param numberOfPoints | |
| * @param ss | |
| * @param vglcolors | |
| * @returns points | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.readColorArray = function (numberOfPoints, ss, vglcolors) { | |
| var i,r,g,b,idx = 0, tmp = new Array(numberOfPoints*3); | |
| for(i = 0; i < numberOfPoints; i++) { | |
| tmp[idx++] = ss[m_pos++]/255.0; | |
| tmp[idx++] = ss[m_pos++]/255.0; | |
| tmp[idx++] = ss[m_pos++]/255.0; | |
| m_pos++; | |
| } | |
| vglcolors.insert(tmp); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * parseObject | |
| * | |
| * @param buffer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.parseObject = function(vtkObject) { | |
| var geom = new vgl.geometryData(), mapper = vgl.mapper(), ss = [], | |
| type = null, data = null, size, matrix = null, material = null, | |
| actor, colorMapData, shaderProg, opacityUniform, lookupTable, | |
| colorTable, windowSize, width, height, position; | |
| //dehexlify | |
| // data = this.decode64(vtkObject.data); | |
| data = atob(vtkObject.data); | |
| for(i = 0; i < data.length; i++) { | |
| ss[i] = data.charCodeAt(i) & 0xff; | |
| } | |
| //Determine the Object type | |
| m_pos = 0; | |
| size = this.readNumber(ss); | |
| type = String.fromCharCode(ss[m_pos++]); | |
| geom.setName(type); | |
| // Lines | |
| if (type === 'L') { | |
| matrix = this.parseLineData(geom, ss); | |
| material = vgl.utils.createGeometryMaterial(); | |
| } | |
| // Mesh | |
| else if (type === 'M') { | |
| matrix = this.parseMeshData(geom, ss); | |
| material = vgl.utils.createPhongMaterial(); | |
| } | |
| // Points | |
| else if (type === 'P'){ | |
| matrix = this.parsePointData(geom, ss); | |
| material = vgl.utils.createGeometryMaterial(); | |
| } | |
| // ColorMap | |
| else if (type === 'C') { | |
| colorMapData = this.parseColorMapData(geom, ss, size); | |
| colorTable = []; | |
| for (i = 0; i < colorMapData.colors.length; i++) { | |
| colorTable.push(colorMapData.colors[i][1]) | |
| colorTable.push(colorMapData.colors[i][2]) | |
| colorTable.push(colorMapData.colors[i][3]) | |
| colorTable.push(colorMapData.colors[i][0] * 255) | |
| } | |
| lookupTable = new vgl.lookupTable(); | |
| lookupTable.setColorTable(colorTable); | |
| windowSize = m_viewer.renderWindow().windowSize(); | |
| width = colorMapData.size[0]*windowSize[0]; | |
| height = colorMapData.size[1]*windowSize[1]; | |
| position = [colorMapData.position[0]*windowSize[0], | |
| (1-colorMapData.position[1])*windowSize[1], 0]; | |
| position[1] = position[1]-height; | |
| // For now hardcode the height | |
| height = 30; | |
| return vgl.utils.createColorLegend(colorMapData.title, | |
| lookupTable, position, width, height, 3, 0); | |
| } | |
| // Unknown | |
| else { | |
| console.log("Ignoring unrecognized encoded data type " + type); | |
| } | |
| mapper.setGeometryData(geom); | |
| //default opacity === solid. If were transparent, set it lower. | |
| if (vtkObject.hasTransparency) { | |
| shaderProg = material.shaderProgram(); | |
| opacityUniform = shaderProg.uniform("opacity"); | |
| shaderProg.addUniform(new vgl.floatUniform("opacity", 0.5)); | |
| material.setBinNumber(1000); | |
| } | |
| actor = vgl.actor(); | |
| actor.setMapper(mapper); | |
| actor.setMaterial(material); | |
| actor.setMatrix(mat4.transpose(mat4.create(), matrix)); | |
| return [actor]; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * parseLineData | |
| * | |
| * @param geom, ss | |
| * @returns matrix | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.parseLineData = function(geom, ss) { | |
| var vglpoints = null, vglcolors = null, vgllines = null, | |
| matrix = mat4.create(), | |
| numberOfIndex, numberOfPoints, points, | |
| temp, index, size, m, i, | |
| p = null, idx = 0; | |
| numberOfPoints = this.readNumber(ss); | |
| p = new Array(numberOfPoints*3); | |
| //Getting Points | |
| vglpoints = new vgl.sourceDataP3fv(); | |
| points = this.readF3Array(numberOfPoints, ss); | |
| for(i = 0; i < numberOfPoints; i++) { | |
| p[idx++] = points[i*3/*+0*/]; | |
| p[idx++] = points[i*3+1]; | |
| p[idx++] = points[i*3+2]; | |
| } | |
| vglpoints.insert(p); | |
| geom.addSource(vglpoints); | |
| //Getting Colors | |
| vglcolors = new vgl.sourceDataC3fv(); | |
| this.readColorArray(numberOfPoints, ss, vglcolors); | |
| geom.addSource(vglcolors); | |
| //Getting connectivity | |
| vgllines = new vgl.lines(); | |
| geom.addPrimitive(vgllines); | |
| numberOfIndex = this.readNumber(ss); | |
| temp = new Int8Array(numberOfIndex*2); | |
| for(i = 0; i < numberOfIndex*2; i++) { | |
| temp[i] = ss[m_pos++]; | |
| } | |
| index = new Uint16Array(temp.buffer); | |
| vgllines.setIndices(index); | |
| vgllines.setPrimitiveType(gl.LINES); | |
| //Getting Matrix | |
| size = 16*4; | |
| temp = new Int8Array(size); | |
| for(i=0; i<size; i++) { | |
| temp[i] = ss[m_pos++]; | |
| } | |
| m = new Float32Array(temp.buffer); | |
| mat4.copy(matrix, m); | |
| return matrix; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * parseMeshData | |
| * | |
| * @param geom, ss | |
| * @returns matrix | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.parseMeshData = function(geom, ss) { | |
| var vglpoints = null, vglcolors = null, vgllines = null, | |
| normals = null, matrix = mat4.create(), v1 = null, | |
| vgltriangles = null, numberOfIndex, numberOfPoints, | |
| points, temp, index, size, m, i, tcoord, | |
| pn = null, idx = 0; | |
| numberOfPoints = this.readNumber(ss); | |
| pn = new Array(numberOfPoints*6); | |
| //Getting Points | |
| vglpoints = new vgl.sourceDataP3N3f(); | |
| points = this.readF3Array(numberOfPoints, ss); | |
| //Getting Normals | |
| normals = this.readF3Array(numberOfPoints, ss); | |
| for(i = 0; i < numberOfPoints; i++) { | |
| pn[idx++] = points[i*3/*+0*/]; | |
| pn[idx++] = points[i*3+1]; | |
| pn[idx++] = points[i*3+2]; | |
| pn[idx++] = normals[i*3/*+0*/]; | |
| pn[idx++] = normals[i*3+1]; | |
| pn[idx++] = normals[i*3+2]; | |
| } | |
| vglpoints.insert(pn); | |
| geom.addSource(vglpoints); | |
| //Getting Colors | |
| vglcolors = new vgl.sourceDataC3fv(); | |
| this.readColorArray(numberOfPoints, ss, vglcolors); | |
| geom.addSource(vglcolors); | |
| //Getting connectivity | |
| temp = []; | |
| vgltriangles = new vgl.triangles(); | |
| numberOfIndex = this.readNumber(ss); | |
| temp = new Int8Array(numberOfIndex*2); | |
| for(i = 0; i < numberOfIndex*2; i++) { | |
| temp[i] = ss[m_pos++]; | |
| } | |
| index = new Uint16Array(temp.buffer); | |
| vgltriangles.setIndices(index); | |
| geom.addPrimitive(vgltriangles); | |
| //Getting Matrix | |
| size = 16*4; | |
| temp = new Int8Array(size); | |
| for(i = 0; i < size; i++) { | |
| temp[i] = ss[m_pos++]; | |
| } | |
| m = new Float32Array(temp.buffer); | |
| mat4.copy(matrix, m); | |
| //Getting TCoord | |
| //TODO: renderer is not doing anything with this yet | |
| tcoord = null; | |
| return matrix; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * parsePointData | |
| * | |
| * @param geom, ss | |
| * @returns matrix | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.parsePointData = function(geom, ss) { | |
| var numberOfPoints, points, indices, temp, size, | |
| matrix = mat4.create(), vglpoints = null, | |
| vglcolors = null, vglVertexes = null, m, | |
| p = null, idx = 0; | |
| numberOfPoints = this.readNumber(ss); | |
| p = new Array(numberOfPoints*3); | |
| //Getting Points and creating 1:1 connectivity | |
| vglpoints = new vgl.sourceDataP3fv(); | |
| points = this.readF3Array(numberOfPoints, ss); | |
| indices = new Uint16Array(numberOfPoints); | |
| for (i = 0; i < numberOfPoints; i++) { | |
| indices[i] = i; | |
| p[idx++] = points[i*3/*+0*/]; | |
| p[idx++] = points[i*3+1]; | |
| p[idx++] = points[i*3+2]; | |
| } | |
| vglpoints.insert(p); | |
| geom.addSource(vglpoints); | |
| //Getting Colors | |
| vglcolors = new vgl.sourceDataC3fv(); | |
| this.readColorArray(numberOfPoints, ss, vglcolors); | |
| geom.addSource(vglcolors); | |
| //Getting connectivity | |
| vglVertexes = new vgl.points(); | |
| vglVertexes.setIndices(indices); | |
| geom.addPrimitive(vglVertexes); | |
| //Getting matrix | |
| size = 16*4; | |
| temp = new Int8Array(size); | |
| for(i = 0; i < size; i++) { | |
| temp[i] = ss[m_pos++]; | |
| } | |
| m = new Float32Array(temp.buffer); | |
| mat4.copy(matrix, m); | |
| return matrix; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * parseColorMapData | |
| * | |
| * @param geom, ss | |
| * @returns matrix | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.parseColorMapData = function(geom, ss, numColors) { | |
| var tmpArray, size, xrgb, i, c, obj = {}; | |
| // Set number of colors | |
| obj.numOfColors = numColors; | |
| // Getting Position | |
| size = 8; | |
| tmpArray = new Int8Array(size); | |
| for(i=0; i < size; i++) { | |
| tmpArray[i] = ss[m_pos++]; | |
| } | |
| obj.position = new Float32Array(tmpArray.buffer); | |
| // Getting Size | |
| size = 8; | |
| tmpArray = new Int8Array(size); | |
| for(i=0; i < size; i++) { | |
| tmpArray[i] = ss[m_pos++]; | |
| } | |
| obj.size = new Float32Array(tmpArray.buffer); | |
| //Getting Colors | |
| obj.colors = []; | |
| for(c=0; c < obj.numOfColors; c++){ | |
| tmpArray = new Int8Array(4); | |
| for(i=0; i < 4; i++) { | |
| tmpArray[i] = ss[m_pos++]; | |
| } | |
| xrgb = [ | |
| new Float32Array(tmpArray.buffer)[0], | |
| ss[m_pos++], | |
| ss[m_pos++], | |
| ss[m_pos++] | |
| ]; | |
| obj.colors[c] = xrgb; | |
| } | |
| obj.orientation = ss[m_pos++]; | |
| obj.numOfLabels = ss[m_pos++]; | |
| obj.title = ""; | |
| while(m_pos < ss.length) { | |
| obj.title += String.fromCharCode(ss[m_pos++]); | |
| } | |
| return obj; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * parseSceneMetadata | |
| * | |
| * @param data | |
| * @returns renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.parseSceneMetadata = function(renderer, layer) { | |
| var sceneRenderer = m_vtkScene.Renderers[layer], | |
| camera = renderer.camera(), bgc, localWidth, localHeight; | |
| localWidth = (sceneRenderer.size[0] - sceneRenderer.origin[0])*m_node.width; | |
| localHeight = (sceneRenderer.size[1] - sceneRenderer.origin[1])*m_node.height; | |
| renderer.resize(localWidth, localHeight); | |
| /// We are setting the center to the focal point because of | |
| /// a possible paraview web bug. The center of rotation isn't | |
| /// getting updated correctly on resetCamera. | |
| camera.setCenterOfRotation( | |
| [sceneRenderer.LookAt[1], sceneRenderer.LookAt[2], | |
| sceneRenderer.LookAt[3]]); | |
| camera.setViewAngleDegrees(sceneRenderer.LookAt[0]); | |
| camera.setPosition( | |
| sceneRenderer.LookAt[7], sceneRenderer.LookAt[8], | |
| sceneRenderer.LookAt[9]); | |
| camera.setFocalPoint( | |
| sceneRenderer.LookAt[1], sceneRenderer.LookAt[2], | |
| sceneRenderer.LookAt[3]); | |
| camera.setViewUpDirection( | |
| sceneRenderer.LookAt[4], sceneRenderer.LookAt[5], | |
| sceneRenderer.LookAt[6]); | |
| if (layer === 0) | |
| { | |
| bgc = sceneRenderer.Background1; | |
| renderer.setBackgroundColor(bgc[0], bgc[1], bgc[2], 1); | |
| } else { | |
| renderer.setResizable(false); | |
| } | |
| renderer.setLayer(layer); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * initScene | |
| * | |
| * @returns viewer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.initScene = function() { | |
| var renderer, layer; | |
| if ( m_vtkScene === null ) { | |
| return m_viewer; | |
| } | |
| for(layer = m_vtkScene.Renderers.length - 1; layer >= 0; layer--) { | |
| renderer = this.getRenderer(layer); | |
| this.parseSceneMetadata(renderer, layer); | |
| } | |
| return m_viewer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * createViewer - Creates a viewer object. | |
| * | |
| * @param | |
| * | |
| * @returns viewer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.createViewer = function(node) { | |
| var interactorStyle; | |
| if(m_viewer === null) { | |
| m_node = node; | |
| m_viewer = vgl.viewer(node); | |
| m_viewer.init(); | |
| m_vtkRenderedList[0] = m_viewer.renderWindow().activeRenderer(); | |
| m_viewer.renderWindow().resize(node.width, node.height); | |
| interactorStyle = vgl.pvwInteractorStyle(); | |
| m_viewer.setInteractorStyle(interactorStyle); | |
| } | |
| return m_viewer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * deleteViewer - Deletes the viewer object associated with the reader. | |
| * | |
| * @returns void | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.deleteViewer = function() { | |
| m_vtkRenderedList = {}; | |
| m_viewer = null; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * updateCanvas - | |
| * | |
| * @param | |
| * | |
| * @returns void | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.updateCanvas = function(node) { | |
| m_node = node; | |
| m_viewer.renderWindow().resize(node.width, node.height); | |
| return m_viewer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * clearVtkObjectData - Clear out the list of VTK geometry data. | |
| * | |
| * @param void | |
| * @returns void | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.numObjects = function() { | |
| return m_vtkObjectCount; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * getRenderer - Gets (or creates) the renderer for a layer. | |
| * | |
| * @param layer | |
| * @returns renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.getRenderer = function(layer) { | |
| var renderer; | |
| renderer = m_vtkRenderedList[layer]; | |
| if (renderer === null || typeof renderer === 'undefined') { | |
| renderer = new vgl.renderer(); | |
| renderer.setResetScene(false); | |
| renderer.setResetClippingRange(false); | |
| m_viewer.renderWindow().addRenderer(renderer); | |
| if (layer !== 0) { | |
| renderer.camera().setClearMask(vgl.GL.DepthBufferBit); | |
| } | |
| m_vtkRenderedList[layer] = renderer; | |
| } | |
| return renderer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * setVtkScene - Set the VTK scene data for camera initialization. | |
| * | |
| * @param scene | |
| * @returns void | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.setVtkScene = function(scene) { | |
| m_vtkScene = scene; | |
| }; | |
| return this; | |
| }; | |
| vgl.DataBuffers = function (initialSize) { | |
| if (!(this instanceof vgl.DataBuffers)) { | |
| return new vgl.DataBuffers(initialSize); | |
| } | |
| var data = {}; | |
| var size; | |
| if (!initialSize && initialSize !== 0) | |
| size = 256; | |
| else | |
| size = initialSize; | |
| var current = 0; | |
| var copyArray = function (dst, src, start, count) { | |
| if (!dst) | |
| console.log ('ack'); | |
| if (!start) | |
| start = 0; | |
| if (!count) | |
| count = src.length; | |
| for (var i = 0; i < count; i ++) { | |
| dst[start + i] = src[i]; | |
| } | |
| }; | |
| var resize = function (min_expand) { | |
| var new_size = size; | |
| /* If the array would increase substantially, don't just double its | |
| * size. If the array has been increasing gradually, double it as the | |
| * expectation is that it will increase again. */ | |
| if (new_size * 2 < min_expand) { | |
| new_size = min_expand; | |
| } | |
| while (new_size < min_expand) | |
| new_size *= 2; | |
| size = new_size; | |
| for (var name in data) { | |
| var newArray = new Float32Array (new_size * data[name].len); | |
| var oldArray = data[name].array; | |
| copyArray (newArray, oldArray); | |
| data[name].array = newArray; | |
| data[name].dirty = true; | |
| } | |
| }; | |
| this.create = function (name, len) { | |
| if (!len) | |
| throw "Length of buffer must be a positive integer"; | |
| var array = new Float32Array (size * len); | |
| data[name] = { | |
| array: array, | |
| len: len, | |
| dirty: false | |
| }; | |
| return data[name].array; | |
| }; | |
| this.alloc = function (num) { | |
| if ((current + num) >= size) | |
| resize (current + num); | |
| var start = current; | |
| current += num; | |
| return start; | |
| }; | |
| this.get = function (name) { | |
| return data[name].array; | |
| }; | |
| this.write = function (name, array, start, count) { | |
| copyArray (data[name].array, array, start * data[name].len, count * data[name].len); | |
| data[name].dirty = true; | |
| }; | |
| this.repeat = function (name, elem, start, count) { | |
| for (var i = 0; i < count; i ++) { | |
| copyArray (data[name].array, elem, | |
| (start + i) * data[name].len, data[name].len); | |
| } | |
| data[name].dirty = true; | |
| }; | |
| this.count = function () { | |
| return current; | |
| }; | |
| this.data = function (name) { | |
| return data[name].array; | |
| }; | |
| }; | |
| (function () { | |
| "use strict"; | |
| var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | |
| /** | |
| * Takes a variable number of arguments and returns the first numeric value | |
| * it finds. | |
| * @private | |
| */ | |
| function setNumeric() { | |
| var i; | |
| for (i = 0; i < arguments.length; i += 1) { | |
| if (isFinite(arguments[i])) { | |
| return arguments[i]; | |
| } | |
| } | |
| } | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Contains utility classes and methods used by geojs. | |
| * @namespace | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.util = { | |
| /** | |
| * Returns true if the given point lies in the given polygon. | |
| * Algorithm description: | |
| * http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html | |
| * @param {geo.screenPosition} point The test point | |
| * @param {geo.screenPosition[]} outer The outer boundary of the polygon | |
| * @param {geo.screenPosition[][]?} inner Inner boundaries (holes) | |
| */ | |
| pointInPolygon: function (point, outer, inner) { | |
| var inside = false, n = outer.length; | |
| if (n < 3) { | |
| // we need 3 coordinates for this to make sense | |
| return false; | |
| } | |
| outer.forEach(function (vert, i) { | |
| var j = (n + i - 1) % n; | |
| var intersect = ( | |
| ((outer[i].y > point.y) !== (outer[j].y > point.y)) && | |
| (point.x < (outer[j].x - outer[i].x) * | |
| (point.y - outer[i].y) / | |
| (outer[j].y - outer[i].y) + outer[i].x) | |
| ); | |
| if (intersect) { | |
| inside = !inside; | |
| } | |
| }); | |
| (inner || []).forEach(function (hole) { | |
| inside = inside && !geo.util.pointInPolygon(point, hole); | |
| }); | |
| return inside; | |
| }, | |
| /** | |
| * Returns true if the argument is a function. | |
| */ | |
| isFunction: function (f) { | |
| return typeof f === "function"; | |
| }, | |
| /** | |
| * Returns the argument if it is function, otherwise returns a function | |
| * that returns the argument. | |
| */ | |
| ensureFunction: function (f) { | |
| if (geo.util.isFunction(f)) { | |
| return f; | |
| } else { | |
| return function () { return f; }; | |
| } | |
| }, | |
| /** | |
| * Return a random string of length n || 8. | |
| */ | |
| randomString: function (n) { | |
| var s, i, r; | |
| n = n || 8; | |
| s = ""; | |
| for (i = 0; i < n; i += 1) { | |
| r = Math.floor(Math.random() * chars.length); | |
| s += chars.substring(r, r + 1); | |
| } | |
| return s; | |
| }, | |
| /** | |
| * Convert a color from hex value or css name to rgb objects | |
| */ | |
| convertColor: function (color) { | |
| if (color.r !== undefined && color.g !== undefined && | |
| color.b !== undefined) { | |
| return color; | |
| } | |
| if (typeof color === "string") { | |
| if (geo.util.cssColors.hasOwnProperty(color)) { | |
| color = geo.util.cssColors[color]; | |
| } else if (color.charAt(0) === "#") { | |
| color = parseInt(color.slice(1), 16); | |
| } | |
| } | |
| // jshint -W016 | |
| if (isFinite(color)) { | |
| color = { | |
| r: ((color & 0xff0000) >> 16) / 255, | |
| g: ((color & 0xff00) >> 8) / 255, | |
| b: ((color & 0xff)) / 255 | |
| }; | |
| } | |
| // jshint +W016 | |
| return color; | |
| }, | |
| /** | |
| * Normalize a coordinate object into {x: ..., y: ..., z: ... } form. | |
| * Accepts 2-3d arrays, | |
| * latlng objects | |
| * latitude -> lat -> y | |
| * longitude -> lon -> lng -> x | |
| */ | |
| normalizeCoordinates: function (p) { | |
| p = p || {}; | |
| if (Array.isArray(p)) { | |
| return { | |
| x: p[0], | |
| y: p[1], | |
| z: p[2] || 0 | |
| }; | |
| } | |
| if (p instanceof geo.latlng) { | |
| return { | |
| x: p.lng(), | |
| y: p.lat(), | |
| z: 0 | |
| }; | |
| } | |
| return { | |
| x: setNumeric( | |
| p.x, | |
| p.longitude, | |
| p.lng, | |
| p.lon, | |
| 0 | |
| ), | |
| y: setNumeric( | |
| p.y, | |
| p.latitude, | |
| p.lat, | |
| 0 | |
| ), | |
| z: setNumeric( | |
| p.z, | |
| p.elevation, | |
| p.elev, | |
| p.height, | |
| 0 | |
| ) | |
| }; | |
| } | |
| }; | |
| geo.util.cssColors = { | |
| aliceblue: 0xf0f8ff, | |
| antiquewhite: 0xfaebd7, | |
| aqua: 0x00ffff, | |
| aquamarine: 0x7fffd4, | |
| azure: 0xf0ffff, | |
| beige: 0xf5f5dc, | |
| bisque: 0xffe4c4, | |
| black: 0x000000, | |
| blanchedalmond: 0xffebcd, | |
| blue: 0x0000ff, | |
| blueviolet: 0x8a2be2, | |
| brown: 0xa52a2a, | |
| burlywood: 0xdeb887, | |
| cadetblue: 0x5f9ea0, | |
| chartreuse: 0x7fff00, | |
| chocolate: 0xd2691e, | |
| coral: 0xff7f50, | |
| cornflowerblue: 0x6495ed, | |
| cornsilk: 0xfff8dc, | |
| crimson: 0xdc143c, | |
| cyan: 0x00ffff, | |
| darkblue: 0x00008b, | |
| darkcyan: 0x008b8b, | |
| darkgoldenrod: 0xb8860b, | |
| darkgray: 0xa9a9a9, | |
| darkgreen: 0x006400, | |
| darkgrey: 0xa9a9a9, | |
| darkkhaki: 0xbdb76b, | |
| darkmagenta: 0x8b008b, | |
| darkolivegreen: 0x556b2f, | |
| darkorange: 0xff8c00, | |
| darkorchid: 0x9932cc, | |
| darkred: 0x8b0000, | |
| darksalmon: 0xe9967a, | |
| darkseagreen: 0x8fbc8f, | |
| darkslateblue: 0x483d8b, | |
| darkslategray: 0x2f4f4f, | |
| darkslategrey: 0x2f4f4f, | |
| darkturquoise: 0x00ced1, | |
| darkviolet: 0x9400d3, | |
| deeppink: 0xff1493, | |
| deepskyblue: 0x00bfff, | |
| dimgray: 0x696969, | |
| dimgrey: 0x696969, | |
| dodgerblue: 0x1e90ff, | |
| firebrick: 0xb22222, | |
| floralwhite: 0xfffaf0, | |
| forestgreen: 0x228b22, | |
| fuchsia: 0xff00ff, | |
| gainsboro: 0xdcdcdc, | |
| ghostwhite: 0xf8f8ff, | |
| gold: 0xffd700, | |
| goldenrod: 0xdaa520, | |
| gray: 0x808080, | |
| green: 0x008000, | |
| greenyellow: 0xadff2f, | |
| grey: 0x808080, | |
| honeydew: 0xf0fff0, | |
| hotpink: 0xff69b4, | |
| indianred: 0xcd5c5c, | |
| indigo: 0x4b0082, | |
| ivory: 0xfffff0, | |
| khaki: 0xf0e68c, | |
| lavender: 0xe6e6fa, | |
| lavenderblush: 0xfff0f5, | |
| lawngreen: 0x7cfc00, | |
| lemonchiffon: 0xfffacd, | |
| lightblue: 0xadd8e6, | |
| lightcoral: 0xf08080, | |
| lightcyan: 0xe0ffff, | |
| lightgoldenrodyellow: 0xfafad2, | |
| lightgray: 0xd3d3d3, | |
| lightgreen: 0x90ee90, | |
| lightgrey: 0xd3d3d3, | |
| lightpink: 0xffb6c1, | |
| lightsalmon: 0xffa07a, | |
| lightseagreen: 0x20b2aa, | |
| lightskyblue: 0x87cefa, | |
| lightslategray: 0x778899, | |
| lightslategrey: 0x778899, | |
| lightsteelblue: 0xb0c4de, | |
| lightyellow: 0xffffe0, | |
| lime: 0x00ff00, | |
| limegreen: 0x32cd32, | |
| linen: 0xfaf0e6, | |
| magenta: 0xff00ff, | |
| maroon: 0x800000, | |
| mediumaquamarine: 0x66cdaa, | |
| mediumblue: 0x0000cd, | |
| mediumorchid: 0xba55d3, | |
| mediumpurple: 0x9370db, | |
| mediumseagreen: 0x3cb371, | |
| mediumslateblue: 0x7b68ee, | |
| mediumspringgreen: 0x00fa9a, | |
| mediumturquoise: 0x48d1cc, | |
| mediumvioletred: 0xc71585, | |
| midnightblue: 0x191970, | |
| mintcream: 0xf5fffa, | |
| mistyrose: 0xffe4e1, | |
| moccasin: 0xffe4b5, | |
| navajowhite: 0xffdead, | |
| navy: 0x000080, | |
| oldlace: 0xfdf5e6, | |
| olive: 0x808000, | |
| olivedrab: 0x6b8e23, | |
| orange: 0xffa500, | |
| orangered: 0xff4500, | |
| orchid: 0xda70d6, | |
| palegoldenrod: 0xeee8aa, | |
| palegreen: 0x98fb98, | |
| paleturquoise: 0xafeeee, | |
| palevioletred: 0xdb7093, | |
| papayawhip: 0xffefd5, | |
| peachpuff: 0xffdab9, | |
| peru: 0xcd853f, | |
| pink: 0xffc0cb, | |
| plum: 0xdda0dd, | |
| powderblue: 0xb0e0e6, | |
| purple: 0x800080, | |
| red: 0xff0000, | |
| rosybrown: 0xbc8f8f, | |
| royalblue: 0x4169e1, | |
| saddlebrown: 0x8b4513, | |
| salmon: 0xfa8072, | |
| sandybrown: 0xf4a460, | |
| seagreen: 0x2e8b57, | |
| seashell: 0xfff5ee, | |
| sienna: 0xa0522d, | |
| silver: 0xc0c0c0, | |
| skyblue: 0x87ceeb, | |
| slateblue: 0x6a5acd, | |
| slategray: 0x708090, | |
| slategrey: 0x708090, | |
| snow: 0xfffafa, | |
| springgreen: 0x00ff7f, | |
| steelblue: 0x4682b4, | |
| tan: 0xd2b48c, | |
| teal: 0x008080, | |
| thistle: 0xd8bfd8, | |
| tomato: 0xff6347, | |
| turquoise: 0x40e0d0, | |
| violet: 0xee82ee, | |
| wheat: 0xf5deb3, | |
| white: 0xffffff, | |
| whitesmoke: 0xf5f5f5, | |
| yellow: 0xffff00, | |
| yellowgreen: 0x9acd32 | |
| }; | |
| }()); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /* | |
| * Includes several support classes adapted from wigglemaps. | |
| * | |
| * https://github.com/dotskapes/wigglemaps | |
| * | |
| * Copyright 2013 Preston and Krejci (dotSkapes Virtual Lab) | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /* jshint ignore: start */ | |
| (function () { | |
| 'use strict'; | |
| var RangeNode = function (elem, start, end, current) { | |
| this.data = elem[current]; | |
| this.left = null; | |
| this.right = null; | |
| if (start !== current) | |
| this.left = new RangeNode(elem, start, current - 1, parseInt((start + (current - 1)) / 2, 10)); | |
| if (end !== current) | |
| this.right = new RangeNode(elem, current + 1, end, parseInt((end + (current + 1)) / 2, 10)); | |
| this.elem = elem; | |
| this.start = start; | |
| this.end = end; | |
| this.subtree = null; /* This is populated as needed */ | |
| this.search = rangeNodeSearch; | |
| }; | |
| var rangeNodeSearch = function (result, box) { | |
| var m_this = this; | |
| var xrange = function (b) { | |
| return (b.x_in(m_this.elem[m_this.start]) && | |
| b.x_in(m_this.elem[m_this.end])); | |
| }; | |
| var yrange = function (b, start, end) { | |
| return (b.y_in(m_this.subtree[start]) && | |
| b.y_in(m_this.subtree[end])); | |
| }; | |
| var subquery = function (result, box, start, end, current) { | |
| if (yrange(box, start, end)) { | |
| for (var i = start; i <= end; i ++) { | |
| result.push(m_this.subtree[i]); | |
| } | |
| return; | |
| } | |
| if (box.y_in(m_this.subtree[current])) | |
| result.push(m_this.subtree[current]); | |
| if (box.y_left(m_this.subtree[current])){ | |
| if (current !== end) | |
| subquery(result, box, current + 1, end, parseInt((end + (current + 1)) / 2, 10)); | |
| } else if (box.x_right(m_this.subtree[current])) { | |
| if (current !== start) | |
| subquery(result, box, start, current - 1, parseInt((start + (current - 1)) / 2, 10)); | |
| } else { | |
| if (current !== end) | |
| subquery(result, box, current + 1, end, parseInt((end + (current + 1)) / 2, 10)); | |
| if (current !== start) | |
| subquery(result, box, start, current - 1, parseInt((start + (current - 1)) / 2, 10)); | |
| } | |
| }; | |
| if (xrange(box)) { | |
| if (!this.subtree) { | |
| this.subtree = this.elem.slice(this.start, this.end + 1); | |
| this.subtree.sort(function (a, b) { | |
| return a.y - b.y; | |
| }); | |
| } | |
| subquery(result, box, 0, this.subtree.length - 1, parseInt((this.subtree.length - 1) / 2, 10)); | |
| return; | |
| } else { | |
| if (box.contains(this.data)) | |
| result.push(this.data); | |
| if (box.x_left(this.data)) { | |
| if (this.right) | |
| this.right.search(result, box); | |
| } else if (box.x_right(this.data)) { | |
| if (this.left) | |
| this.left.search(result, box); | |
| } else { | |
| if (this.left) | |
| this.left.search(result, box); | |
| if (this.right) | |
| this.right.search(result, box); | |
| } | |
| } | |
| }; | |
| var RangeTree = function (elem) { | |
| elem.sort(function (a, b) { | |
| return a.x - b.x; | |
| }); | |
| if (elem.length > 0) | |
| this.root = new RangeNode(elem, 0, elem.length - 1, parseInt((elem.length - 1) / 2, 10)); | |
| else | |
| this.root = null; | |
| this.search = function (_box) { | |
| if (!this.root) | |
| return []; | |
| //var box = new Box (min, max); | |
| var box = _box.clone (); | |
| var result = []; | |
| this.root.search (result, box); | |
| return result; | |
| }; | |
| }; | |
| var Box = function (v1, v2) { | |
| this.min = v1.clone (); | |
| this.max = v2.clone (); | |
| this.contains = function (p) { | |
| return (v1.x <= p.x) && (v2.x >= p.x) && (v1.y <= p.y) && (v2.y >= p.y); | |
| }; | |
| this.x_in = function (p) { | |
| return (v1.x <= p.x) && (v2.x >= p.x); | |
| }; | |
| this.x_left = function (p) { | |
| return (v1.x >= p.x); | |
| }; | |
| this.x_right = function (p) { | |
| return (v2.x <= p.x); | |
| }; | |
| this.y_in = function (p) { | |
| return (v1.y <= p.y) && (v2.y >= p.y); | |
| }; | |
| this.y_left = function (p) { | |
| return (v1.y >= p.y); | |
| }; | |
| this.y_right = function (p) { | |
| return (v2.y <= p.y); | |
| }; | |
| this.area = function () { | |
| return (this.max.x - this.min.x) * (this.max.y - this.min.y); | |
| }; | |
| this.height = function () { | |
| return this.max.y - this.min.y; | |
| }; | |
| this.width = function () { | |
| return this.max.x - this.min.x; | |
| }; | |
| this.vertex = function (index) { | |
| switch (index) { | |
| case 0: | |
| return this.min.clone (); | |
| case 1: | |
| return new vect (this.max.x, this.min.y); | |
| case 2: | |
| return this.max.clone (); | |
| case 3: | |
| return new vect (this.min.x, this.max.y); | |
| default: | |
| throw "Index out of bounds: " + index ; | |
| } | |
| }; | |
| this.intersects = function (box) { | |
| for (var i = 0; i < 4; i ++) { | |
| for (var j = 0; j < 4; j ++) { | |
| if (vect.intersects (this.vertex (i), this.vertex ((i + 1) % 4), | |
| box.vertex (j), box.vertex ((j + 1) % 4))) | |
| return true; | |
| } | |
| } | |
| if (this.contains (box.min) && | |
| this.contains (box.max) && | |
| this.contains (new vect (box.min.x, box.max.y)) && | |
| this.contains (new vect (box.max.x, box.min.y))) | |
| return true; | |
| if (box.contains (this.min) && | |
| box.contains (this.max) && | |
| box.contains (new vect (this.min.x, this.max.y)) && | |
| box.contains (new vect (this.max.x, this.min.y))) | |
| return true; | |
| return false; | |
| }; | |
| this.union = function (b) { | |
| this.min.x = Math.min (this.min.x, b.min.x); | |
| this.min.y = Math.min (this.min.y, b.min.y); | |
| this.max.x = Math.max (this.max.x, b.max.x); | |
| this.max.y = Math.max (this.max.y, b.max.y); | |
| }; | |
| this.centroid = function () { | |
| return new vect ((this.max.x + this.min.x) / 2, (this.max.y + this.min.y) / 2); | |
| }; | |
| this.clone = function () { | |
| return new Box (v1, v2); | |
| }; | |
| }; | |
| // A basic vector type. Supports standard 2D vector operations | |
| var Vector2D = function (x, y) { | |
| this.x = x; | |
| this.y = y; | |
| this.add = function (v) { | |
| this.x += v.x; | |
| this.y += v.y; | |
| return this; | |
| }; | |
| this.sub = function (v) { | |
| this.x -= v.x; | |
| this.y -= v.y; | |
| return this; | |
| }; | |
| this.scale = function (s) { | |
| this.x *= s; | |
| this.y *= s; | |
| return this; | |
| }; | |
| this.length = function () { | |
| return Math.sqrt (this.x * this.x + this.y * this.y); | |
| }; | |
| this.normalize = function () { | |
| var scale = this.length (); | |
| if (scale === 0) | |
| return this; | |
| this.x /= scale; | |
| this.y /= scale; | |
| return this; | |
| }; | |
| this.div = function (v) { | |
| this.x /= v.x; | |
| this.y /= v.y; | |
| return this; | |
| }; | |
| this.floor = function () { | |
| this.x = Math.floor (this.x); | |
| this.y = Math.floor (this.y); | |
| return this; | |
| }; | |
| this.zero = function (tol) { | |
| tol = tol || 0; | |
| return (this.length() <= tol); | |
| }; | |
| this.dot = function (v) { | |
| return (this.x * v.x) + (this.y * v.y); | |
| }; | |
| this.cross = function (v) { | |
| return (this.x * v.y) - (this.y * v.x); | |
| }; | |
| this.rotate = function (omega) { | |
| var cos = Math.cos (omega); | |
| var sin = Math.sin (omega); | |
| xp = cos * this.x - sin * this.y; | |
| yp = sin * this.x + cos * this.y; | |
| this.x = xp; | |
| this.y = yp; | |
| return this; | |
| }; | |
| this.clone = function () { | |
| return new Vector2D (this.x, this.y); | |
| }; | |
| this.array = function () { | |
| return [this.x, this.y]; | |
| }; | |
| }; | |
| // A shortcut for the vector constructor | |
| function vect (x, y) { | |
| return new Vector2D (x, y); | |
| } | |
| // Shorthand operations for vectors for operations that make new vectors | |
| vect.scale = function (v, s) { | |
| return v.clone ().scale (s); | |
| }; | |
| vect.add = function (v1, v2) { | |
| return v1.clone ().add (v2); | |
| }; | |
| vect.sub = function (v1, v2) { | |
| return v1.clone ().sub (v2); | |
| }; | |
| vect.dist = function (v1, v2) { | |
| return v1.clone ().sub (v2).length (); | |
| }; | |
| vect.dir = function (v1, v2) { | |
| return v1.clone ().sub (v2).normalize (); | |
| }; | |
| vect.dot = function (v1, v2) { | |
| return (v1.x * v2.x) + (v1.y * v2.y); | |
| }; | |
| vect.cross = function (v1, v2) { | |
| return (v1.x * v2.y) - (v1.y * v2.x); | |
| }; | |
| vect.left = function (a, b, c, tol) { | |
| if (!tol) | |
| tol = 0; | |
| var v1 = vect.sub (b, a); | |
| var v2 = vect.sub (c, a); | |
| return (vect.cross (v1, v2) >= -tol); | |
| }; | |
| vect.intersects = function (a, b, c, d, tol) { | |
| if (!tol) | |
| tol = 0; | |
| return (vect.left (a, b, c, tol) != vect.left (a, b, d, tol) && | |
| vect.left (c, d, b, tol) != vect.left (c, d, a, tol)); | |
| }; | |
| vect.intersect2dt = function (a, b, c, d) { | |
| var denom = a.x * (d.y - c.y) + | |
| b.x * (c.y - d.y) + | |
| d.x * (b.y - a.y) + | |
| c.x * (a.y - b.y); | |
| if (denom === 0) | |
| return Infinity; | |
| var num_s = a.x * (d.y - c.y) + | |
| c.x * (a.y - d.y) + | |
| d.x * (c.y - a.y); | |
| var s = num_s / denom; | |
| var num_t = -(a.x * (c.y - b.y) + | |
| b.x * (a.y - c.y) + | |
| c.x * (b.y - a.y)); | |
| var t = num_t / denom; | |
| return t; | |
| }; | |
| vect.intersect2dpos = function (a, b, c, d) { | |
| var denom = a.x * (d.y - c.y) + | |
| b.x * (c.y - d.y) + | |
| d.x * (b.y - a.y) + | |
| c.x * (a.y - b.y); | |
| if (denom === 0) | |
| return Infinity; | |
| var num_s = a.x * (d.y - c.y) + | |
| c.x * (a.y - d.y) + | |
| d.x * (c.y - a.y); | |
| var s = num_s / denom; | |
| /*var num_t = -(a.x * (c.y - b.y) + | |
| b.x * (a.y - c.y) + | |
| c.x * (b.y - a.y)); | |
| var t = num_t / denom;*/ | |
| var dir = vect.sub (b, a); | |
| dir.scale (s); | |
| return vect.add (a, dir); | |
| }; | |
| vect.rotate = function (v, omega) { | |
| var cos = Math.cos (omega); | |
| var sin = Math.sin (omega); | |
| xp = cos * v.x - sin * v.y; | |
| yp = sin * v.x + cos * v.y; | |
| var c = new vect (xp, yp); | |
| return c; | |
| }; | |
| vect.normalize = function (v) { | |
| return v.clone ().normalize (); | |
| }; | |
| // Export to geo.util module | |
| geo.util.RangeTree = RangeTree; | |
| geo.util.Box = Box; | |
| geo.util.vect = vect; | |
| }()); | |
| /* jshint ignore: end */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class object | |
| * | |
| * @class | |
| * @extends vgl.object | |
| * @returns {geo.object} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.object = function () { | |
| "use strict"; | |
| if (!(this instanceof geo.object)) { | |
| return new geo.object(); | |
| } | |
| var m_this = this, | |
| m_eventHandlers = {}, | |
| m_idleHandlers = [], | |
| m_deferredCount = 0; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind a handler that will be called once when all deferreds are resolved. | |
| * | |
| * @param {function} handler A function taking no arguments | |
| * @returns {geo.object[]|geo.object} this | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.onIdle = function (handler) { | |
| if (m_deferredCount) { | |
| m_idleHandlers.push(handler); | |
| } else { | |
| handler(); | |
| } | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add a new deferred object preventing idle event handlers from being called. | |
| * | |
| * @param {$.defer} defer A jquery defered object | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.addDeferred = function (defer) { | |
| m_deferredCount += 1; | |
| defer.done(function () { | |
| m_deferredCount -= 1; | |
| if (!m_deferredCount) { | |
| m_idleHandlers.splice(0, m_idleHandlers.length) | |
| .forEach(function (handler) { | |
| handler(); | |
| }); | |
| } | |
| }); | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Bind an event handler to this object | |
| * | |
| * @param {String} event | |
| * An event from {geo.events} | |
| * @param {function} handler | |
| * A function that will be called when ``event`` is triggered. The | |
| * function will be given an event object as a first parameter and | |
| * optionally a second argument provided by the triggerer. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.geoOn = function (event, handler) { | |
| if (Array.isArray(event)) { | |
| event.forEach(function (e) { | |
| m_this.geoOn(e, handler); | |
| }); | |
| return m_this; | |
| } | |
| if (!m_eventHandlers.hasOwnProperty(event)) { | |
| m_eventHandlers[event] = []; | |
| } | |
| m_eventHandlers[event].push(handler); | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Trigger an event (or events) on this object and call all handlers | |
| * | |
| * @param {String} event An event from {geo.events} | |
| * @param {Object} args An optional argument to pass to handlers | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.geoTrigger = function (event, args) { | |
| // if we have an array of events, recall with single events | |
| if (Array.isArray(event)) { | |
| event.forEach(function (e) { | |
| m_this.geoTrigger(e, args); | |
| }); | |
| return m_this; | |
| } | |
| if (m_eventHandlers.hasOwnProperty(event)) { | |
| m_eventHandlers[event].forEach(function (handler) { | |
| handler.call(m_this, args); | |
| }); | |
| } | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Remove handlers from an event (or an array of events). If no event is | |
| * provided all hanlders will be removed. | |
| * | |
| * @param {string?} event An event from {geo.events} | |
| * @param {object?} arg A function or array of functions to remove from the events | |
| * or if falsey remove all handlers from the events | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.geoOff = function (event, arg) { | |
| if (event === undefined) { | |
| m_eventHandlers = {}; | |
| m_idleHandlers = []; | |
| m_deferredCount = 0; | |
| } | |
| if (Array.isArray(event)) { | |
| event.forEach(function (e) { | |
| m_this.geoOff(e, arg); | |
| }); | |
| return m_this; | |
| } | |
| if (!arg) { | |
| m_eventHandlers[event] = []; | |
| } else if (Array.isArray(arg)) { | |
| arg.forEach(function (handler) { | |
| m_this.geoOff(event, handler); | |
| }); | |
| return m_this; | |
| } | |
| // What do we do if the handler is not already bound? | |
| // ignoring for now... | |
| if (m_eventHandlers.hasOwnProperty(event)) { | |
| m_eventHandlers[event] = m_eventHandlers[event].filter(function (f) { | |
| return f !== arg; | |
| } | |
| ); | |
| } | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Free all resources and destroy the object. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_this.geoOff(); | |
| }; | |
| vgl.object.call(this); | |
| return this; | |
| }; | |
| inherit(geo.object, vgl.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class sceneObject, which extends the object's | |
| * event handling with a tree-based event propagation. | |
| * | |
| * @class | |
| * @extends geo.object | |
| * @returns {geo.sceneObject} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.sceneObject = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.sceneObject)) { | |
| return new geo.sceneObject(); | |
| } | |
| geo.object.call(this, arg); | |
| var m_this = this, | |
| m_parent = null, | |
| m_children = [], | |
| s_exit = this._exit, | |
| s_trigger = this.geoTrigger, | |
| s_addDeferred = this.addDeferred, | |
| s_onIdle = this.onIdle; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Override object.addDeferred to propagate up the scene tree. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.addDeferred = function (defer) { | |
| if (m_parent) { | |
| m_parent.addDeferred(defer); | |
| } else { | |
| s_addDeferred(defer); | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Override object.onIdle to propagate up the scene tree. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.onIdle = function (handler) { | |
| if (m_parent) { | |
| m_parent.onIdle(handler); | |
| } else { | |
| s_onIdle(handler); | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/set parent of the object | |
| * @param {?geo.sceneObject} parent | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.parent = function (arg) { | |
| if (arg === undefined) { | |
| return m_parent; | |
| } | |
| m_parent = arg; | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add a child (or an array of children) to the object | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.addChild = function (child) { | |
| if (Array.isArray(child)) { | |
| child.forEach(m_this.addChild); | |
| return m_this; | |
| } | |
| child.parent(m_this); | |
| m_children.push(child); | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Remove a child (or array of children) from the object | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.removeChild = function (child) { | |
| if (Array.isArray(child)) { | |
| child.forEach(m_this.removeChild); | |
| return m_this; | |
| } | |
| m_children = m_children.filter(function (c) { return c !== child; }); | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get an array of child objects | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.children = function () { | |
| return m_children.slice(); | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Force redraw of a scene object, to be implemented by subclasses. | |
| * Base class just calls draw of child objects. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.draw = function (arg) { | |
| m_this.children().forEach(function (child) { | |
| child.draw(arg); | |
| }); | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Trigger an event (or events) on this object and call all handlers. | |
| * @param {String} event the event to trigger | |
| * @param {Object} args arbitrary argument to pass to the handler | |
| * @param {Boolean} childrenOnly if true, only propagate down the tree | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.geoTrigger = function (event, args, childrenOnly) { | |
| var geoArgs; | |
| args = args || {}; | |
| geoArgs = args.geo || {}; | |
| args.geo = geoArgs; | |
| // stop propagation if requested by the handler | |
| if (geoArgs.stopPropagation) { | |
| return m_this; | |
| } | |
| // If the event was not triggered by the parent, just propagate up the tree | |
| if (!childrenOnly && m_parent && geoArgs._triggeredBy !== m_parent) { | |
| geoArgs._triggeredBy = m_this; | |
| m_parent.geoTrigger(event, args); | |
| return m_this; | |
| } | |
| // call the object's own handlers | |
| s_trigger.call(m_this, event, args); | |
| // stop propagation if requested by the handler | |
| if (geoArgs.stopPropagation) { | |
| return m_this; | |
| } | |
| // trigger the event on the children | |
| m_children.forEach(function (child) { | |
| geoArgs._triggeredBy = m_this; | |
| child.geoTrigger(event, args); | |
| }); | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Free all resources and destroy the object. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_this.children = []; | |
| delete m_this.parent; | |
| s_exit(); | |
| }; | |
| return this; | |
| }; | |
| inherit(geo.sceneObject, geo.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class timestamp | |
| * | |
| * @class | |
| * @extends vgl.timestamp | |
| * @returns {geo.timestamp} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.timestamp = function () { | |
| 'use strict'; | |
| if (!(this instanceof geo.timestamp)) { | |
| return new geo.timestamp(); | |
| } | |
| vgl.timestamp.call(this); | |
| }; | |
| inherit(geo.timestamp, vgl.timestamp); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create an instance of quadratic surface generator | |
| * in Cartesian coordinates by the equation | |
| * <code>(x / a)^2 + (y / b)^2 + (z / c)^2 = 1</code>. Used | |
| * primarily to create planetary bodies | |
| * | |
| * @class | |
| * @param {Number} [x=0] Radius in X direction | |
| * @param {Number} [y=0] Radius in Y direction | |
| * @param {Number} [z=0] Radius in Z direction | |
| * | |
| * @returns {geo.ellipsoid} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.ellipsoid = function (x, y, z) { | |
| 'use strict'; | |
| if (!(this instanceof geo.ellipsoid)) { | |
| return new geo.ellipsoid(x, y, z); | |
| } | |
| x = vgl.defaultValue(x, 0.0); | |
| y = vgl.defaultValue(y, 0.0); | |
| z = vgl.defaultValue(z, 0.0); | |
| if (x < 0.0 || y < 0.0 || z < 0.0) { | |
| return console.log('[error] Al radii components must be greater than zero'); | |
| } | |
| var m_this = this, | |
| m_radii = new vec3.fromValues(x, y, z), | |
| m_radiiSquared = new vec3.fromValues( | |
| x * x, y * y, z * z), | |
| m_minimumRadius = Math.min(x, y, z), | |
| m_maximumRadius = Math.max(x, y, z); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return radii of ellipsoid | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.radii = function () { | |
| return m_radii; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return squared radii of the ellipsoid | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.radiiSquared = function () { | |
| return m_radiiSquared; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return maximum radius of the ellipsoid | |
| * | |
| * @return {Number} The maximum radius of the ellipsoid | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.maximumRadius = function () { | |
| return m_maximumRadius; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return minimum radius of the ellipsoid | |
| * | |
| * @return {Number} The maximum radius of the ellipsoid | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.minimumRadius = function () { | |
| return m_minimumRadius; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Computes the normal of the plane tangent to the surface of | |
| * the ellipsoid at the provided position | |
| * | |
| * @param {Number} lat The cartographic latitude for which | |
| * to to determine the geodetic normal | |
| * @param {Number} lon The cartographic longitude for which | |
| * to to determine the geodetic normal | |
| * | |
| * @return {vec3} | |
| * | |
| * @exception {DeveloperError} cartographic is required. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.computeGeodeticSurfaceNormal = function (lat, lon) { | |
| if (typeof lat === 'undefined' || typeof lon === 'undefined') { | |
| throw '[error] Valid latitude and longitude is required'; | |
| } | |
| var cosLatitude = Math.cos(lat), | |
| result = vec3.create(); | |
| result[0] = cosLatitude * Math.cos(lon); | |
| result[1] = cosLatitude * Math.sin(lon); | |
| result[2] = Math.sin(lat); | |
| vec3.normalize(result, result); | |
| return result; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Converts the provided geographic latitude, longitude, | |
| * and height to WGS84 coordinate system | |
| * | |
| * @param {Number} lat Latitude in radians | |
| * @param {Number} lon Longitude in radians | |
| * @param {Number} elev Elevation | |
| * @return {vec3} Position in the WGS84 coordinate system | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.transformPoint = function (lat, lon, elev) { | |
| lat = lat * (Math.PI / 180.0); | |
| lon = lon * (Math.PI / 180.0); | |
| var n = m_this.computeGeodeticSurfaceNormal(lat, lon), | |
| k = vec3.create(), | |
| gamma = Math.sqrt(vec3.dot(n, k)), | |
| result = vec3.create(); | |
| vec3.multiply(k, m_radiiSquared, n); | |
| vec3.scale(k, k, 1 / gamma); | |
| vec3.scale(n, n, elev); | |
| vec3.add(result, n, k); | |
| return result; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Converts the provided geographic latitude, longitude, | |
| * and height to WGS84 coordinate system | |
| * | |
| * @param {vgl.geometryData} geom | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.transformGeometry = function (geom) { | |
| if (!geom) { | |
| throw '[error] Failed to transform to cartesian. Invalid geometry.'; | |
| } | |
| var sourceData = geom.sourceData(vgl.vertexAttributeKeys.Position), | |
| sourceDataArray = sourceData.data(), | |
| noOfComponents = sourceData.attributeNumberOfComponents( | |
| vgl.vertexAttributeKeys.Position), | |
| stride = sourceData.attributeStride( | |
| vgl.vertexAttributeKeys.Position), | |
| offset = sourceData.attributeOffset( | |
| vgl.vertexAttributeKeys.Position), | |
| sizeOfDataType = sourceData.sizeOfAttributeDataType( | |
| vgl.vertexAttributeKeys.Position), | |
| index = null, | |
| count = sourceDataArray.length * (1.0 / noOfComponents), | |
| gamma = null, | |
| n = null, | |
| j = 0, | |
| k = vec3.create(), | |
| result = vec3.create(); | |
| stride /= sizeOfDataType; | |
| offset /= sizeOfDataType; | |
| if (noOfComponents !== 3) { | |
| throw ('[error] Requires positions with three components'); | |
| } | |
| for (j = 0; j < count; j += 1) { | |
| index = j * stride + offset; | |
| sourceDataArray[index] = sourceDataArray[index] * (Math.PI / 180.0); | |
| sourceDataArray[index + 1] = sourceDataArray[index + 1] * (Math.PI / 180.0); | |
| n = m_this.computeGeodeticSurfaceNormal(sourceDataArray[index + 1], | |
| sourceDataArray[index]); | |
| vec3.multiply(k, m_radiiSquared, n); | |
| gamma = Math.sqrt(vec3.dot(n, k)); | |
| vec3.scale(k, k, 1 / gamma); | |
| vec3.scale(n, n, sourceDataArray[index + 2]); | |
| vec3.add(result, n, k); | |
| sourceDataArray[index] = result[0]; | |
| sourceDataArray[index + 1] = result[1]; | |
| sourceDataArray[index + 2] = result[2]; | |
| } | |
| }; | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * An Ellipsoid instance initialized to the WGS84 standard. | |
| * @memberof ellipsoid | |
| * | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| geo.ellipsoid.WGS84 = vgl.freezeObject( | |
| geo.ellipsoid(6378137.0, 6378137.0, 6356752.3142451793)); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * An Ellipsoid instance initialized to radii of (1.0, 1.0, 1.0). | |
| * @memberof ellipsoid | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| geo.ellipsoid.UNIT_SPHERE = vgl.freezeObject( | |
| geo.ellipsoid(1.0, 1.0, 1.0)); | |
| /** @namespace */ | |
| geo.mercator = { | |
| r_major: 6378137.0 //Equatorial Radius, WGS84 | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns the polar radius based on the projection. | |
| * | |
| * @param {Boolean} sperical | |
| * @returns {Number} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.r_minor = function (spherical) { | |
| 'use strict'; | |
| var r_minor; | |
| spherical = spherical !== undefined ? spherical : false; | |
| if (spherical) { | |
| r_minor = 6378137.0; | |
| } else { | |
| r_minor = 6356752.314245179; | |
| } | |
| return r_minor; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * 1/f=(a-b)/a , a=r_major, b=r_minor | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.f = function (spherical) { | |
| 'use strict'; | |
| return (geo.mercator.r_major - geo.mercator.r_minor(spherical)) / geo.mercator.r_major; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert longitude (Degree) to Tile X | |
| * | |
| * @param {float} lon | |
| * @param {integer} z | |
| * @returns {integer} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.long2tilex = function (lon, z) { | |
| 'use strict'; | |
| var rad = (lon + 180.0) / 360.0, | |
| f = Math.floor(rad * Math.pow(2.0, z)); | |
| return f; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert latitude (Degree) to Tile Y | |
| * | |
| * @param {float} lat | |
| * @param {integer} z | |
| * @returns {integer} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.lat2tiley = function (lat, z) { | |
| 'use strict'; | |
| var rad = lat * Math.PI / 180.0; | |
| return Math.floor((1.0 - rad / Math.PI) / 2.0 * Math.pow(2.0, z)); | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert Longitute (Degree) to Tile X and fraction. | |
| * | |
| * @param {float} lon | |
| * @param {integer} z | |
| * @returns {number[]} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.long2tilex2 = function (lon, z) { | |
| 'use strict'; | |
| var rad = (lon + 180.0) / 360.0, | |
| f = rad * Math.pow(2.0, z), | |
| ret = Math.floor(f), | |
| frac = f - ret; | |
| return [ret, frac]; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert Latitude (Degree) to Tile Y and fraction | |
| * | |
| * @param {float} lat | |
| * @param {integer} z | |
| * @returns {number[]} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.lat2tiley2 = function (lat, z) { | |
| 'use strict'; | |
| var rad = lat * Math.PI / 180.0, | |
| f = (1.0 - Math.log(Math.tan(rad) + 1.0 / Math.cos(rad)) / | |
| Math.PI) / 2.0 * Math.pow(2.0, z), | |
| ret = Math.floor(f), | |
| frac = f - ret; | |
| return [ret, frac]; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert Tile X to Longitute (Degree) | |
| * | |
| * @param {integer} x | |
| * @param {integer} z | |
| * @returns {float} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.tilex2long = function (x, z) { | |
| 'use strict'; | |
| return x / Math.pow(2.0, z) * 360.0 - 180.0; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert Tile Y to Latitute (Degree) | |
| * | |
| * @param {integer} y | |
| * @param {integer} z | |
| * @returns {float} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.tiley2lat = function (y, z) { | |
| 'use strict'; | |
| var n = Math.PI - 2.0 * Math.PI * y / Math.pow(2.0, z); | |
| return 180.0 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert spherical mercator Y to latitude | |
| * | |
| * @param {float} a | |
| * @returns {float} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.y2lat = function (a) { | |
| 'use strict'; | |
| return 180 / Math.PI * (2 * Math.atan(Math.exp(a * Math.PI / 180)) - Math.PI / 2); | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert latitude into Y position in spherical mercator | |
| * | |
| * @param {float} a | |
| * @returns {float} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.lat2y = function (a) { | |
| 'use strict'; | |
| return 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + a * (Math.PI / 180) / 2)); | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * | |
| * @param {float} d | |
| * @returns {number} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.deg2rad = function (d) { | |
| 'use strict'; | |
| var r = d * (Math.PI / 180.0); | |
| return r; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert radian to degree | |
| * | |
| * @param {float} r | |
| * @returns {number} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.rad2deg = function (r) { | |
| 'use strict'; | |
| var d = r / (Math.PI / 180.0); | |
| return d; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert latlon to mercator | |
| * | |
| * @param {float} lon | |
| * @param {float} lat | |
| * @returns {object} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.ll2m = function (lon, lat, spherical) { | |
| 'use strict'; | |
| if (lat > 89.5) { | |
| lat = 89.5; | |
| } | |
| if (lat < -89.5) { | |
| lat = -89.5; | |
| } | |
| var x = this.r_major * this.deg2rad(lon), | |
| temp = this.r_minor(spherical) / this.r_major, | |
| es = 1.0 - (temp * temp), | |
| eccent = Math.sqrt(es), | |
| phi = this.deg2rad(lat), | |
| sinphi = Math.sin(phi), | |
| con = eccent * sinphi, | |
| com = 0.5 * eccent, | |
| con2 = Math.pow((1.0 - con) / (1.0 + con), com), | |
| ts = Math.tan(0.5 * (Math.PI * 0.5 - phi)) / con2, | |
| y = -this.r_major * Math.log(ts), | |
| ret = {'x': x, 'y': y}; | |
| return ret; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert mercator to lat lon | |
| * | |
| * @param {float} x | |
| * @param {float} y | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.m2ll = function (x, y, spherical) { | |
| 'use strict'; | |
| var lon = this.rad2deg((x / this.r_major)), | |
| temp = this.r_minor(spherical) / this.r_major, | |
| e = Math.sqrt(1.0 - (temp * temp)), | |
| lat = this.rad2deg(this.pjPhi2(Math.exp(-(y / this.r_major)), e)), | |
| ret = {'lon': lon, 'lat': lat}; | |
| return ret; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * pjPhi2 | |
| * | |
| * @param {float} ts | |
| * @param {float} e | |
| * @returns {number} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mercator.pjPhi2 = function (ts, e) { | |
| 'use strict'; | |
| var N_ITER = 15, | |
| HALFPI = Math.PI / 2, | |
| TOL = 0.0000000001, | |
| con, dphi, | |
| i = N_ITER, | |
| eccnth = 0.5 * e, | |
| Phi = HALFPI - 2.0 * Math.atan(ts); | |
| do { | |
| con = e * Math.sin(Phi); | |
| dphi = HALFPI - 2.0 * Math.atan(ts * Math.pow( | |
| (1.0 - con) / (1.0 + con), eccnth)) - Phi; | |
| Phi += dphi; | |
| i -= 1; | |
| } while (Math.abs(dphi) > TOL && i); | |
| return Phi; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of latlng | |
| * | |
| * A latlng encapsulates geodesy coordinates defined by latitude and | |
| * longitude (depreciated) | |
| * | |
| * @class | |
| * @returns {geo.latlng} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.latlng = function (arg1, arg2, arg3) { | |
| "use strict"; | |
| if (!(this instanceof geo.latlng)) { | |
| return new geo.latlng(arg1, arg2, arg3); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| m_lat = arg2 === undefined && arg3 === undefined ? arg1.lat() : arg1, | |
| m_lng = arg2 === undefined && arg3 === undefined ? arg1.lng() : arg2, | |
| m_elv = arg2 === undefined && arg3 === undefined ? arg1.elv() : arg3; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return latitude | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.lat = function (val) { | |
| if (val === undefined) { | |
| return m_lat; | |
| } else { | |
| m_lat = val; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return longitude | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.lng = function (val) { | |
| if (val === undefined) { | |
| return m_lng; | |
| } else { | |
| m_lng = val; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return elevation | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.elv = function (val) { | |
| if (val === undefined) { | |
| return m_elv; | |
| } else { | |
| m_elv = val; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return x coordinate | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.x = function (val) { | |
| if (val === undefined) { | |
| return m_this.lng(); | |
| } else { | |
| m_lng = val; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return y coordinate | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.y = function (val) { | |
| if (val === undefined) { | |
| return m_this.lat(); | |
| } else { | |
| m_lat = val; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return z coordinate | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.z = function (val) { | |
| if (val === undefined) { | |
| return m_this.elv(); | |
| } else { | |
| m_elv = val; | |
| } | |
| }; | |
| return this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @class | |
| * @extends geo.sceneObject | |
| * @returns {geo.layer} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.layer = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.layer)) { | |
| return new geo.layer(arg); | |
| } | |
| arg = arg || {}; | |
| geo.sceneObject.call(this, arg); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_exit = this._exit, | |
| m_style = arg.style === undefined ? {"opacity": 0.5, | |
| "color": [0.8, 0.8, 0.8], | |
| "visible": true, | |
| "bin": 100} : arg.style, | |
| m_id = arg.id === undefined ? geo.layer.newLayerId() : arg.id, | |
| m_name = "", | |
| m_gcs = "EPSG:4326", | |
| m_timeRange = null, | |
| m_source = arg.source || null, | |
| m_map = arg.map === undefined ? null : arg.map, | |
| m_isReference = false, | |
| m_x = 0, | |
| m_y = 0, | |
| m_width = 0, | |
| m_height = 0, | |
| m_node = null, | |
| m_canvas = null, | |
| m_renderer = null, | |
| m_initialized = false, | |
| m_rendererName = arg.renderer === undefined ? "vgl" : arg.renderer, | |
| m_dataTime = geo.timestamp(), | |
| m_updateTime = geo.timestamp(), | |
| m_drawTime = geo.timestamp(), | |
| m_sticky = arg.sticky === undefined ? true : arg.sticky, | |
| m_active = arg.active === undefined ? true : arg.active; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get whether or not the layer is sticky (navigates with the map). | |
| * | |
| * @returns {Boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.sticky = function () { | |
| return m_sticky; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get whether or not the layer is active. An active layer will receive | |
| * native mouse when the layer is on top. Non-active layers will never | |
| * receive native mouse events. | |
| * | |
| * @returns {Boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.active = function () { | |
| return m_active; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set root node of the layer | |
| * | |
| * @returns {div} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.node = function () { | |
| return m_node; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set id of the layer | |
| * | |
| * @returns {String} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.id = function (val) { | |
| if (val === undefined) { | |
| return m_id; | |
| } | |
| m_id = geo.newLayerId(); | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set name of the layer | |
| * | |
| * @returns {String} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.name = function (val) { | |
| if (val === undefined) { | |
| return m_name; | |
| } | |
| m_name = val; | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set opacity of the layer | |
| * | |
| * @returns {Number} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.opacity = function (val) { | |
| if (val === undefined) { | |
| return m_style.opacity; | |
| } | |
| m_style.opacity = val; | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set visibility of the layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.visible = function (val) { | |
| if (val === undefined) { | |
| return m_style.visible; | |
| } | |
| m_style.visible = val; | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set bin of the layer | |
| * | |
| * @returns {Number} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.bin = function (val) { | |
| if (val === undefined) { | |
| return m_style.bin; | |
| } | |
| m_style.bin = val; | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set projection of the layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.gcs = function (val) { | |
| if (val === undefined) { | |
| return m_gcs; | |
| } | |
| m_gcs = val; | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform layer to the reference layer gcs | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.transform = function (val) { | |
| geo.transform.transformLayer(val, m_this, m_map.baseLayer()); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set time range of the layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.timeRange = function (val) { | |
| if (val === undefined) { | |
| return m_timeRange; | |
| } | |
| m_timeRange = val; | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set source of the layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.source = function (val) { | |
| if (val === undefined) { | |
| return m_source; | |
| } | |
| m_source = val; | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set map of the layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.map = function (val) { | |
| if (val === undefined) { | |
| return m_map; | |
| } | |
| m_map = val; | |
| m_map.node().append(m_node); | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get renderer for the layer if any | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.renderer = function () { | |
| return m_renderer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get canvas of the layer | |
| * | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.canvas = function () { | |
| return m_canvas; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get viewport of the layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.viewport = function () { | |
| return [m_x, m_y, m_width, m_height]; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return last time data got changed | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.dataTime = function () { | |
| return m_dataTime; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return the modified time for the last update that did something | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.updateTime = function () { | |
| return m_updateTime; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return the modified time for the last draw call that did something | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.drawTime = function () { | |
| return m_drawTime; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Run query and return results for it | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.query = function () { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set layer as the reference layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.referenceLayer = function (val) { | |
| if (val !== undefined) { | |
| m_isReference = val; | |
| m_this.modified(); | |
| return m_this; | |
| } | |
| return m_isReference; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set if the layer has been initialized | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.initialized = function (val) { | |
| if (val !== undefined) { | |
| m_initialized = val; | |
| return m_this; | |
| } | |
| return m_initialized; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform a point or array of points in GCS space to | |
| * local space of the layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.toLocal = function (input) { | |
| return input; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform a point or array of points in local space to | |
| * latitude-longitude space | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.fromLocal = function (input) { | |
| return input; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Init layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function () { | |
| if (m_initialized) { | |
| return m_this; | |
| } | |
| // Create top level div for the layer | |
| m_node = $(document.createElement("div")); | |
| m_node.attr("id", m_name); | |
| // TODO: need to position according to offsets from the map element | |
| // and maybe respond to events in case the map element moves | |
| // around the page. | |
| m_node.css("position", "absolute"); | |
| if (m_map) { | |
| m_map.node().append(m_node); | |
| } | |
| // Share context if have valid one | |
| if (m_canvas) { | |
| m_renderer = geo.createRenderer(m_rendererName, m_this, m_canvas); | |
| } else { | |
| m_renderer = geo.createRenderer(m_rendererName, m_this); | |
| m_canvas = m_renderer.canvas(); | |
| } | |
| if (!m_this.active()) { | |
| m_node.css("pointerEvents", "none"); | |
| } | |
| m_initialized = true; | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Clean up resouces | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_renderer._exit(); | |
| m_node.off(); | |
| m_node.remove(); | |
| m_node = null; | |
| arg = {}; | |
| m_canvas = null; | |
| m_renderer = null; | |
| s_exit(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Respond to resize event | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._resize = function (x, y, w, h) { | |
| m_x = x; | |
| m_y = y; | |
| m_width = w; | |
| m_height = h; | |
| m_node.width(w); | |
| m_node.height(h); | |
| m_this.modified(); | |
| m_this.geoTrigger(geo.event.resize, | |
| {x: x, y: y, width: m_width, height: m_height}); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return the width of the layer in pixels | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.width = function () { | |
| return m_width; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return the height of the layer in pixels | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.height = function () { | |
| return m_height; | |
| }; | |
| return this; | |
| }; | |
| /** | |
| * Gets a new id number for a layer. | |
| * @protected | |
| * @instance | |
| * @returns {number} | |
| */ | |
| geo.layer.newLayerId = (function () { | |
| "use strict"; | |
| var currentId = 1; | |
| return function () { | |
| var id = currentId; | |
| currentId += 1; | |
| return id; | |
| }; | |
| }() | |
| ); | |
| /** | |
| * General object specification for feature types. | |
| * @typedef geo.layer.spec | |
| * @type {object} | |
| * @property {string} [type="feature"] For feature compatibility | |
| * with more than one kind of creatable layer | |
| * @property {object[]} [data=[]] The default data array to | |
| * apply to each feature if none exists | |
| * @property {string} [renderer="vgl"] The renderer to use | |
| * @property {geo.feature.spec[]} [features=[]] Features | |
| * to add to the layer | |
| */ | |
| /** | |
| * Create a layer from an object. Any errors in the creation | |
| * of the layer will result in returning null. | |
| * @param {geo.map} map The map to add the layer to | |
| * @param {geo.layer.spec} spec The object specification | |
| * @returns {geo.layer|null} | |
| */ | |
| geo.layer.create = function (map, spec) { | |
| "use strict"; | |
| spec = spec || {}; | |
| // add osmLayer later | |
| spec.type = "feature"; | |
| if (spec.type !== "feature") { | |
| console.warn("Unsupported layer type"); | |
| return null; | |
| } | |
| spec.renderer = spec.renderer || "vgl"; | |
| if (spec.renderer !== "d3" && spec.renderer !== "vgl") { | |
| console.warn("Invalid renderer"); | |
| return null; | |
| } | |
| var layer = map.createLayer(spec.type, spec); | |
| if (!layer) { | |
| console.warn("Unable to create a layer"); | |
| return null; | |
| } | |
| // probably move this down to featureLayer eventually | |
| spec.features.forEach(function (f) { | |
| f.data = f.data || spec.data; | |
| f.feature = geo.feature.create(layer, f); | |
| }); | |
| return layer; | |
| }; | |
| inherit(geo.layer, geo.sceneObject); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Layer to draw points, lines, and polygons on the map The polydata layer | |
| * provide mechanisms to create and draw geometrical shapes such as points, | |
| * lines, and polygons. | |
| * @class | |
| * @extends geo.layer | |
| * @returns {geo.featureLayer} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.featureLayer = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.featureLayer)) { | |
| return new geo.featureLayer(arg); | |
| } | |
| geo.layer.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| m_features = [], | |
| s_init = this._init, | |
| s_exit = this._exit, | |
| s_update = this._update, | |
| s_draw = this.draw; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create feature give a name | |
| * | |
| * @returns {geo.Feature} Will return a new feature | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.createFeature = function (featureName, arg) { | |
| var newFeature = geo.createFeature( | |
| featureName, m_this, m_this.renderer(), arg); | |
| m_this.addChild(newFeature); | |
| m_features.push(newFeature); | |
| m_this.features(m_features); | |
| m_this.modified(); | |
| return newFeature; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Delete feature | |
| * | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.deleteFeature = function (feature) { | |
| var i; | |
| for (i = 0; i < m_features.length; i += 1) { | |
| if (m_features[i] === feature) { | |
| m_features[i]._exit(); | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| m_features.splice(i, 1); | |
| } | |
| } | |
| m_this.removeChild(feature); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set drawables | |
| * | |
| * @returns {Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.features = function (val) { | |
| if (val === undefined) { | |
| return m_features; | |
| } else { | |
| m_features = val.slice(0); | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| return m_this; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| * | |
| * Do not call parent _init method as its already been executed | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function () { | |
| if (m_this.initialized()) { | |
| return m_this; | |
| } | |
| /// Call super class init | |
| s_init.call(m_this); | |
| /// Bind events to handlers | |
| m_this.geoOn(geo.event.resize, function (event) { | |
| m_this.renderer()._resize(event.x, event.y, event.width, event.height); | |
| m_this._update({}); | |
| m_this.renderer()._render(); | |
| }); | |
| m_this.geoOn(geo.event.pan, function (event) { | |
| m_this._update({event: event}); | |
| m_this.renderer()._render(); | |
| }); | |
| m_this.geoOn(geo.event.zoom, function (event) { | |
| m_this._update({event: event}); | |
| m_this.renderer()._render(); | |
| }); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function (request) { | |
| var i; | |
| if (!m_features.length) { | |
| return m_this; | |
| } | |
| /// Call base class update | |
| s_update.call(m_this, request); | |
| if (!m_this.source() && m_features && m_features.length === 0) { | |
| console.log("[info] No valid data source found."); | |
| return; | |
| } | |
| if (m_this.dataTime().getMTime() > m_this.updateTime().getMTime()) { | |
| for (i = 0; i < m_features.length; i += 1) { | |
| m_features[i].renderer(m_this.renderer()); | |
| } | |
| } | |
| for (i = 0; i < m_features.length; i += 1) { | |
| m_features[i]._update(); | |
| } | |
| m_this.updateTime().modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Free all resources | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_this.clear(); | |
| s_exit(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Draw | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.draw = function () { | |
| // Call sceneObject.draw, which calls draw on all child objects. | |
| s_draw(); | |
| // Now call render on the renderer. In certain cases it may not do | |
| // anything if the if the child objects are drawn on the screen already. | |
| m_this.renderer()._render(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Clear all features in layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.clear = function () { | |
| var i; | |
| if (!m_features.length) { | |
| return m_this; | |
| } | |
| for (i = 0; i < m_features.length; i += 1) { | |
| m_features[i]._exit(); | |
| m_this.removeChild(m_features[i]); | |
| } | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| m_features = []; | |
| return m_this; | |
| }; | |
| return m_this; | |
| }; | |
| inherit(geo.featureLayer, geo.layer); | |
| // Now register it | |
| geo.registerLayer("feature", geo.featureLayer); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Common object containing all event types that are provided by the GeoJS | |
| * API. Each property contained here is a valid target for event handling | |
| * via {@link geo.object#geoOn}. The event object provided to handlers is | |
| * different for each event type. Each handler will generally be called | |
| * with a the <code>this</code> context being the class that caused the event.<br> | |
| * <br> | |
| * The following properties are common to all event objects: | |
| * | |
| * @namespace | |
| * @property type {string} The event type that was triggered | |
| * @property geo {object} A universal event object for controlling propagation | |
| * | |
| * @example | |
| * map.geoOn(geo.event.layerAdd, function (event) { | |
| * // event is an object with type: {@link geo.event.layerAdd} | |
| * }); | |
| * | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event = {}; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /* | |
| * Event types | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // The following were not triggered nor used anywhere. Removing until their | |
| // purpose is defined more clearly. | |
| // | |
| // geo.event.update = "geo_update"; | |
| // geo.event.opacityUpdate = "geo_opacityUpdate"; | |
| // geo.event.layerToggle = "geo_layerToggle"; | |
| // geo.event.layerSelect = "geo_layerSelect"; | |
| // geo.event.layerUnselect = "geo_layerUnselect"; | |
| // geo.event.rotate = "geo_rotate"; | |
| // geo.event.query = "geo_query"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered when a layer is added to the map. | |
| * | |
| * @property target {geo.map} The current map | |
| * @property layer {geo.layer} The new layer | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.layerAdd = "geo_layerAdd"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered when a layer is removed from the map. | |
| * | |
| * @property target {geo.map} The current map | |
| * @property layer {geo.layer} The old layer | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.layerRemove = "geo_layerRemove"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered when the map's zoom level is changed. Note that zoom is never | |
| * triggered on the map itself. Instead it is triggered individually on | |
| * layers, starting with the base layer. | |
| * | |
| * @property zoomLevel {Number} New zoom level | |
| * @property screenPosition {object} The screen position of mouse pointer | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.zoom = "geo_zoom"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered when the map is panned either by user interaction or map | |
| * transition. | |
| * | |
| * @property screenDelta {object} The number of pixels to pan the map by | |
| * @property center {object} The new map center | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.pan = "geo_pan"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered when the map's canvas is resized. | |
| * | |
| * @property width {Number} The new width in pixels | |
| * @property height {Number} The new height in pixels | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.resize = "geo_resize"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered on every call to {@link geo.map#draw} before the map is rendered. | |
| * | |
| * @property target {geo.map} The current map | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.draw = "geo_draw"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered on every call to {@link geo.map#draw} after the map is rendered. | |
| * | |
| * @property target {geo.map} The current map | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.drawEnd = "geo_drawEnd"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered on every "mousemove" over the map's DOM element. The event | |
| * object extends {@link geo.mouseState}. | |
| * @mixes geo.mouseState | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.mousemove = "geo_mousemove"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered on every "mousedown" over the map's DOM element. The event | |
| * object extends {@link geo.mouseState}. | |
| * @mixes geo.mouseState | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.mouseclick = "geo_mouseclick"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered on every "mousemove" during a brushing selection. | |
| * The event object extends {@link geo.brushSelection}. | |
| * @mixes geo.brushSelection | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.brush = "geo_brush"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered after a brush selection ends. | |
| * The event object extends {@link geo.brushSelection}. | |
| * @mixes geo.brushSelection | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.brushend = "geo_brushend"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered when a brush selection starts. | |
| * The event object extends {@link geo.brushSelection}. | |
| * @mixes geo.brushSelection | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.brushstart = "geo_brushstart"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered before a map navigation animation begins. Set | |
| * <code>event.geo.cancelAnimation</code> to cancel the animation | |
| * of the navigation. This will cause the map to navigate to the | |
| * target location immediately. Set <code>event.geo.cancelNavigation</code> | |
| * to cancel the navigation completely. The transition options can | |
| * be modified in place. | |
| * | |
| * @property {geo.geoPosition} center The target center | |
| * @property {Number} zoom The target zoom level | |
| * @property {Number} duration The duration of the transition in milliseconds | |
| * @property {function} ease The easing function | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.transitionstart = "geo_transitionstart"; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Triggered after a map navigation animation ends. | |
| * | |
| * @property {geo.geoPosition} center The target center | |
| * @property {Number} zoom The target zoom level | |
| * @property {Number} duration The duration of the transition in milliseconds | |
| * @property {function} ease The easing function | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.event.transitionend = "geo_transitionend"; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @namespace | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| geo.event.clock = { | |
| play: "geo_clock_play", | |
| stop: "geo_clock_stop", | |
| pause: "geo_clock_pause", | |
| change: "geo_clock_change" | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * This event object provides mouse/keyboard events that can be handled | |
| * by the features. This provides a similar interface as core events, | |
| * but with different names so the events don't interfere. Subclasses | |
| * can override this to provide custom events. | |
| * | |
| * These events will only be triggered on features which were instantiated | |
| * with the option "selectionAPI". | |
| * @namespace | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| geo.event.feature = { | |
| mousemove: "geo_feature_mousemove", | |
| mouseover: "geo_feature_mouseover", | |
| mouseout: "geo_feature_mouseout", | |
| mouseon: "geo_feature_mouseon", | |
| mouseoff: "geo_feature_mouseoff", | |
| mouseclick: "geo_feature_mouseclick", | |
| brushend: "geo_feature_brushend", | |
| brush: "geo_feature_brush" | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of mapInteractor | |
| * | |
| * @class | |
| * @extends geo.object | |
| * @returns {geo.mapInteractor} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.mapInteractor = function (args) { | |
| 'use strict'; | |
| if (!(this instanceof geo.mapInteractor)) { | |
| return new geo.mapInteractor(args); | |
| } | |
| geo.object.call(this); | |
| var m_options = args || {}, | |
| m_this = this, | |
| m_mouse, | |
| m_keyboard, | |
| m_state, | |
| $node, | |
| m_wheelQueue = { x: 0, y: 0 }, | |
| m_throttleTime = 10, | |
| m_wait = false, | |
| m_disableThrottle = true, | |
| m_selectionLayer = null, | |
| m_selectionPlane = null; | |
| // Helper method to decide if the current button/modifiers match a set of | |
| // conditions. | |
| // button: 'left' | 'right' | 'middle' | |
| // modifiers: [ 'alt' | 'meta' | 'ctrl' | 'shift' ] | |
| function eventMatch(button, modifiers) { | |
| /* jshint -W018 */ | |
| return (button === 'wheel' || m_mouse.buttons[button]) && | |
| (!!m_mouse.modifiers.alt) === (!!modifiers.alt) && | |
| (!!m_mouse.modifiers.meta) === (!!modifiers.meta) && | |
| (!!m_mouse.modifiers.shift) === (!!modifiers.shift) && | |
| (!!m_mouse.modifiers.ctrl) === (!!modifiers.ctrl); | |
| /* jshint +W018 */ | |
| } | |
| // Helper method to calculate the speed from a velocity | |
| function calcSpeed(v) { | |
| var x = v.x, y = v.y; | |
| return Math.sqrt(x * x + y * y); | |
| } | |
| // For throttling mouse events this is a function that | |
| // returns true if no actions are in progress and starts | |
| // a timer to block events for the next `m_throttleTime` ms. | |
| // If it returns false, the caller should ignore the | |
| // event. | |
| function doRespond() { | |
| if (m_disableThrottle) { | |
| return true; | |
| } | |
| if (m_wait) { | |
| return false; | |
| } | |
| m_wait = true; | |
| window.setTimeout(function () { | |
| m_wait = false; | |
| m_wheelQueue = { | |
| x: 0, | |
| y: 0 | |
| }; | |
| }, m_throttleTime); | |
| return true; | |
| } | |
| // copy the options object with defaults | |
| m_options = $.extend( | |
| true, | |
| { | |
| panMoveButton: 'left', | |
| panMoveModifiers: {}, | |
| zoomMoveButton: 'right', | |
| zoomMoveModifiers: {}, | |
| panWheelEnabled: false, | |
| panWheelModifiers: {}, | |
| zoomWheelEnabled: true, | |
| zoomWheelModifiers: {}, | |
| wheelScaleX: 1, | |
| wheelScaleY: 1, | |
| zoomScale: 1, | |
| selectionButton: 'left', | |
| selectionModifiers: {'shift': true}, | |
| momentum: { | |
| enabled: true, | |
| maxSpeed: 2.5, | |
| minSpeed: 0.01, | |
| drag: 0.01 | |
| }, | |
| spring: { | |
| enabled: false, | |
| springConstant: 0.00005 | |
| } | |
| }, | |
| m_options | |
| ); | |
| // options supported: | |
| // { | |
| // // button that must be pressed to initiate a pan on mousedown | |
| // panMoveButton: 'right' | 'left' | 'middle' | |
| // | |
| // // modifier keys that must be pressed to initiate a pan on mousemove | |
| // panMoveModifiers: { 'ctrl' | 'alt' | 'meta' | 'shift' } | |
| // | |
| // // button that must be pressed to initiate a zoom on mousedown | |
| // zoomMoveButton: 'right' | 'left' | 'middle' | |
| // | |
| // // modifier keys that must be pressed to initiate a zoom on mousemove | |
| // zoomMoveModifiers: { 'ctrl' | 'alt' | 'meta' | 'shift' } | |
| // | |
| // // enable or disable panning with the mouse wheel | |
| // panWheelEnabled: true | false | |
| // | |
| // // modifier keys that must be pressed to trigger a pan on wheel | |
| // panWheelModifiers: {...} | |
| // | |
| // // enable or disable zooming with the mouse wheel | |
| // zoomWheelEnabled: true | false | |
| // | |
| // // modifier keys that must be pressed to trigger a zoom on wheel | |
| // zoomWheelModifiers: {...} | |
| // | |
| // // wheel scale factor to change the magnitude of wheel interactions | |
| // wheelScaleX: 1 | |
| // wheelScaleY: 1 | |
| // | |
| // // zoom scale factor to change the magnitude of zoom move interactions | |
| // zoomScale: 1 | |
| // | |
| // // button that must be pressed to enable drag selection | |
| // selectionButton: 'right' | 'left' | 'middle' | |
| // | |
| // // keyboard modifiers that must be pressed to initiate a selection | |
| // selectionModifiers: {...} | |
| // | |
| // // enable momentum when panning | |
| // momentum: { | |
| // enabled: true | false, | |
| // drag: number, // drag coefficient | |
| // maxSpeed: number, // don't allow animation to pan faster than this | |
| // minSpeed: number // stop animations if the speed is less than this | |
| // } | |
| // | |
| // // enable spring clamping to screen edges to enforce clamping | |
| // spring: { | |
| // enabled: true | false, | |
| // springConstant: number, | |
| // } | |
| // } | |
| // A bunch of type definitions for api documentation: | |
| /** | |
| * General representation of rectangular bounds in world coordinates | |
| * @typedef geo.geoBounds | |
| * @type {object} | |
| * @property {geo.geoPosition} upperLeft Upper left corner | |
| * @property {geo.geoPosition} upperRight Upper right corner | |
| * @property {geo.geoPosition} lowerLeft Lower left corner | |
| * @property {geo.geoPosition} lowerRight Lower right corner | |
| */ | |
| /** | |
| * General representation of rectangular bounds in pixel coordinates | |
| * @typedef geo.screenBounds | |
| * @type {object} | |
| * @property {geo.screenPosition} upperLeft Upper left corner | |
| * @property {geo.screenPosition} upperRight Upper right corner | |
| * @property {geo.screenPosition} lowerLeft Lower left corner | |
| * @property {geo.screenPosition} lowerRight Lower right corner | |
| */ | |
| /** | |
| * General representation of a point on the screen. | |
| * @typedef geo.screenPosition | |
| * @type {object} | |
| * @property {Number} x Horizontal coordinate in pixels | |
| * @property {Number} y Vertical coordinate in pixels | |
| */ | |
| /** | |
| * General represention of a point on the earth. | |
| * @typedef geo.geoPosition | |
| * @type {object} | |
| * @property {Number} x Horizontal coordinate in degrees longitude | |
| * @property {Number} y Vertical coordinate in degrees latitude | |
| */ | |
| /** | |
| * The status of all mouse buttons. | |
| * @typedef geo.mouseButtons | |
| * @type {object} | |
| * @property {true|false} left The left mouse button | |
| * @property {true|false} right The right mouse button | |
| * @property {true|false} middle The middle mouse button | |
| */ | |
| /** | |
| * The status of all modifier keys these are copied from the | |
| * standard DOM events. | |
| * @typedef geo.modifierKeys | |
| * @type {object} | |
| * @property {true|false} alt <code>Event.alt</code> | |
| * @property {true|false} ctrl <code>Event.ctrl</code> | |
| * @property {true|false} shift <code>Event.shift</code> | |
| * @property {true|false} meta <code>Event.meta</code> | |
| */ | |
| /** | |
| * Provides information about the state of the mouse | |
| * @typedef geo.mouseState | |
| * @type {object} | |
| * @property {geo.screenPosition} page Mouse location in pixel space | |
| * @property {geo.geoPosition} map Mouse location in world space | |
| * @property {geo.mouseButtons} buttons The current state of the mouse buttons | |
| * @property {geo.modifierKeys} modifiers The current state of all modifier keys | |
| * @property {Date} time The timestamp the event took place | |
| * @property {Number} deltaTime The time in milliseconds since the last mouse event | |
| * @property {geo.screenPosition} velocity The velocity of the mouse pointer | |
| * in pixels per second | |
| */ | |
| /** | |
| * @typedef geo.brushSelection | |
| * @type {object} | |
| * @property {geo.screenBounds} display The selection bounds in pixel space | |
| * @property {geo.geoBounds} gcs The selection bounds in world space | |
| * @property {geo.mouseState} mouse The current mouse state | |
| * @property {geo.mouseState} origin The mouse state at the start of the | |
| * brush action | |
| */ | |
| // default mouse object | |
| m_mouse = { | |
| page: { // mouse position relative to the page | |
| x: 0, | |
| y: 0 | |
| }, | |
| map: { // mouse position relative to the map | |
| x: 0, | |
| y: 0 | |
| }, | |
| // mouse button status | |
| buttons: { | |
| left: false, | |
| right: false, | |
| middle: false | |
| }, | |
| // keyboard modifier status | |
| modifiers: { | |
| alt: false, | |
| ctrl: false, | |
| shift: false, | |
| meta: false | |
| }, | |
| // time the event was captured | |
| time: new Date(), | |
| // time elapsed since the last mouse event | |
| deltaTime: 1, | |
| // pixels/ms | |
| velocity: { | |
| x: 0, | |
| y: 0 | |
| } | |
| }; | |
| // default keyboard object | |
| // (keyboard events not implemented yet) | |
| m_keyboard = { | |
| }; | |
| // The interactor state determines what actions are taken in response to | |
| // core browser events. | |
| // | |
| // i.e. | |
| // { | |
| // 'action': 'pan', // an ongoing pan event | |
| // 'origin': {...}, // mouse object at the start of the action | |
| // 'delta': {x: *, y: *} // mouse movement since action start | |
| // // not including the current event | |
| // } | |
| // | |
| // { | |
| // 'action': 'zoom', // an ongoing zoom event | |
| // ... | |
| // } | |
| // | |
| // { | |
| // 'acton': 'select', | |
| // 'origin': {...}, | |
| // 'delta': {x: *, y: *} | |
| // } | |
| // | |
| // { | |
| // 'action': 'momentum', | |
| // 'origin': {...}, | |
| // 'handler': function () { }, // called in animation loop | |
| // 'timer': animate loop timer | |
| // } | |
| m_state = {}; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Connects events to a map. If the map is not set, then this does nothing. | |
| * @returns {geo.mapInteractor} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._connectEvents = function () { | |
| if (!m_options.map) { | |
| return m_this; | |
| } | |
| // prevent double binding to dom elements | |
| m_this._disconnectEvents(); | |
| // store the connected element | |
| $node = $(m_options.map.node()); | |
| // add event handlers | |
| $node.on('mousemove.geojs', m_this._handleMouseMove); | |
| $node.on('mousedown.geojs', m_this._handleMouseDown); | |
| $node.on('mouseup.geojs', m_this._handleMouseUp); | |
| $node.on('wheel.geojs', m_this._handleMouseWheel); | |
| if (m_options.panMoveButton === 'right' || | |
| m_options.zoomMoveButton === 'right') { | |
| $node.on('contextmenu.geojs', function () { return false; }); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Disonnects events to a map. If the map is not set, then this does nothing. | |
| * @returns {geo.mapInteractor} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._disconnectEvents = function () { | |
| if ($node) { | |
| $node.off('.geojs'); | |
| $node = null; | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Sets or gets map for this interactor, adds draw region layer if needed | |
| * | |
| * @param {geo.map} newMap optional | |
| * @returns {geo.interactorStyle|geo.map} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.map = function (val) { | |
| if (val !== undefined) { | |
| m_options.map = val; | |
| m_this._connectEvents(); | |
| return m_this; | |
| } | |
| return m_options.map; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Gets/sets the options object for the interactor. | |
| * | |
| * @param {Object} opts optional | |
| * @returns {geo.interactorStyle|Object} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.options = function (opts) { | |
| if (opts === undefined) { | |
| return $.extend({}, m_options); | |
| } | |
| $.extend(m_options, opts); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Stores the current mouse position from an event | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._getMousePosition = function (evt) { | |
| var offset = $node.offset(), dt, t; | |
| t = (new Date()).valueOf(); | |
| dt = t - m_mouse.time; | |
| m_mouse.time = t; | |
| m_mouse.deltaTime = dt; | |
| m_mouse.velocity = { | |
| x: (evt.pageX - m_mouse.page.x) / dt, | |
| y: (evt.pageY - m_mouse.page.y) / dt | |
| }; | |
| m_mouse.page = { | |
| x: evt.pageX, | |
| y: evt.pageY | |
| }; | |
| m_mouse.map = { | |
| x: evt.pageX - offset.left, | |
| y: evt.pageY - offset.top | |
| }; | |
| try { | |
| m_mouse.geo = m_this.map().displayToGcs(m_mouse.map); | |
| } catch (e) { | |
| // catch georeferencing problems and move on | |
| // needed for handling the map before the base layer | |
| // is attached | |
| m_mouse.geo = null; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Stores the current mouse button | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._getMouseButton = function (evt) { | |
| if (evt.which === 1) { | |
| m_mouse.buttons.left = evt.type !== 'mouseup'; | |
| } else if (evt.which === 3) { | |
| m_mouse.buttons.right = evt.type !== 'mouseup'; | |
| } else if (evt.which === 2) { | |
| m_mouse.buttons.middle = evt.type !== 'mouseup'; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Stores the current keyboard modifiers | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._getMouseModifiers = function (evt) { | |
| m_mouse.modifiers.alt = evt.altKey; | |
| m_mouse.modifiers.ctrl = evt.ctrlKey; | |
| m_mouse.modifiers.meta = evt.metaKey; | |
| m_mouse.modifiers.shift = evt.shiftKey; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Compute a selection information object. | |
| * @private | |
| * @returns {Object} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._getSelection = function () { | |
| var origin = m_state.origin, | |
| mouse = m_this.mouse(), | |
| map = m_this.map(), | |
| display = {}, gcs = {}; | |
| // TODO: clamp to map bounds | |
| // Get the display coordinates | |
| display.upperLeft = { | |
| x: Math.min(origin.map.x, mouse.map.x), | |
| y: Math.min(origin.map.y, mouse.map.y) | |
| }; | |
| display.lowerRight = { | |
| x: Math.max(origin.map.x, mouse.map.x), | |
| y: Math.max(origin.map.y, mouse.map.y) | |
| }; | |
| display.upperRight = { | |
| x: display.lowerRight.x, | |
| y: display.upperLeft.y | |
| }; | |
| display.lowerLeft = { | |
| x: display.upperLeft.x, | |
| y: display.lowerRight.y | |
| }; | |
| // Get the gcs coordinates | |
| gcs.upperLeft = map.displayToGcs(display.upperLeft); | |
| gcs.lowerRight = map.displayToGcs(display.lowerRight); | |
| gcs.upperRight = map.displayToGcs(display.upperRight); | |
| gcs.lowerLeft = map.displayToGcs(display.lowerLeft); | |
| m_selectionPlane.origin([ | |
| display.lowerLeft.x, | |
| display.lowerLeft.y, | |
| 0 | |
| ]); | |
| m_selectionPlane.upperLeft([ | |
| display.upperLeft.x, | |
| display.upperLeft.y, | |
| 0 | |
| ]); | |
| m_selectionPlane.lowerRight([ | |
| display.lowerRight.x, | |
| display.lowerRight.y, | |
| 0 | |
| ]); | |
| m_selectionPlane.draw(); | |
| return { | |
| display: display, | |
| gcs: gcs, | |
| mouse: mouse, | |
| origin: $.extend({}, m_state.origin) | |
| }; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Immediately cancel an ongoing action. | |
| * | |
| * @param {string?} action The action type, if null cancel any action | |
| * @returns {bool} If an action was canceled | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.cancel = function (action) { | |
| var out; | |
| if (!action) { | |
| out = !!m_state.action; | |
| } else { | |
| out = m_state.action === action; | |
| } | |
| if (out) { | |
| m_state = {}; | |
| } | |
| return out; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle event when a mouse button is pressed | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._handleMouseDown = function (evt) { | |
| var action = null; | |
| if (m_state.action === 'momentum') { | |
| // cancel momentum on click | |
| m_state = {}; | |
| } | |
| m_this._getMousePosition(evt); | |
| m_this._getMouseButton(evt); | |
| m_this._getMouseModifiers(evt); | |
| if (eventMatch(m_options.panMoveButton, m_options.panMoveModifiers)) { | |
| action = 'pan'; | |
| } else if (eventMatch(m_options.zoomMoveButton, m_options.zoomMoveModifiers)) { | |
| action = 'zoom'; | |
| } else if (eventMatch(m_options.selectionButton, m_options.selectionModifiers)) { | |
| action = 'select'; | |
| } | |
| m_mouse.velocity = { | |
| x: 0, | |
| y: 0 | |
| }; | |
| if (action) { | |
| // store the state object | |
| m_state = { | |
| action: action, | |
| origin: $.extend(true, {}, m_mouse), | |
| delta: {x: 0, y: 0} | |
| }; | |
| if (action === 'select') { | |
| // Make sure the old selection layer is gone. | |
| if (m_selectionLayer) { | |
| m_selectionLayer.clear(); | |
| m_this.map().deleteLayer(m_selectionLayer); | |
| m_selectionLayer = null; | |
| } | |
| // Create a feature layer and plane feature to show the selection bounds | |
| m_selectionLayer = m_this.map().createLayer('feature', {renderer: 'd3'}); | |
| m_selectionPlane = m_selectionLayer.createFeature('plane'); | |
| m_selectionPlane.style({ | |
| screenCoordinates: true, | |
| fillOpacity: function () { return 0.25; } | |
| }); | |
| m_this.map().geoTrigger(geo.event.brushstart, m_this._getSelection()); | |
| } | |
| // bind temporary handlers to document | |
| $(document).on('mousemove.geojs', m_this._handleMouseMoveDocument); | |
| $(document).on('mouseup.geojs', m_this._handleMouseUpDocument); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse move event | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._handleMouseMove = function (evt) { | |
| if (m_state.action) { | |
| // If currently performing a navigation action, the mouse | |
| // coordinates will be captured by the document handler. | |
| return; | |
| } | |
| m_this._getMousePosition(evt); | |
| m_this._getMouseButton(evt); | |
| m_this._getMouseModifiers(evt); | |
| m_this.map().geoTrigger(geo.event.mousemove, m_this.mouse()); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse move event on the document (temporary bindings) | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._handleMouseMoveDocument = function (evt) { | |
| var dx, dy, selectionObj; | |
| m_this._getMousePosition(evt); | |
| m_this._getMouseButton(evt); | |
| m_this._getMouseModifiers(evt); | |
| if (!m_state.action) { | |
| // This shouldn't happen | |
| console.log('WARNING: Invalid state in mapInteractor.'); | |
| return; | |
| } | |
| // when throttled, do nothing | |
| if (!doRespond()) { | |
| return; | |
| } | |
| // calculate the delta from the origin point to avoid | |
| // accumulation of floating point errors | |
| dx = m_mouse.map.x - m_state.origin.map.x - m_state.delta.x; | |
| dy = m_mouse.map.y - m_state.origin.map.y - m_state.delta.y; | |
| m_state.delta.x += dx; | |
| m_state.delta.y += dy; | |
| if (m_state.action === 'pan') { | |
| m_this.map().pan({x: dx, y: dy}); | |
| } else if (m_state.action === 'zoom') { | |
| m_this.map().zoom( | |
| m_this.map().zoom() - dy * m_options.zoomScale / 120 | |
| ); | |
| } else if (m_state.action === 'select') { | |
| // Get the bounds of the current selection | |
| selectionObj = m_this._getSelection(); | |
| m_this.map().geoTrigger(geo.event.brush, selectionObj); | |
| } | |
| // Prevent default to stop text selection in particular | |
| evt.preventDefault(); | |
| }; | |
| /** | |
| * Use interactor options to modify the mouse velocity by momentum | |
| * or spring equations depending on the current map state. | |
| * @private | |
| * @param {object} v Current velocity in pixels / ms | |
| * @param {number} deltaT The time delta | |
| * @returns {object} New velocity | |
| */ | |
| function modifyVelocity(v, deltaT) { | |
| deltaT = deltaT <= 0 ? 30 : deltaT; | |
| var sf = springForce(); | |
| var speed = calcSpeed(v); | |
| var vx = v.x / speed; | |
| var vy = v.y / speed; | |
| speed = speed * Math.exp(-m_options.momentum.drag * deltaT); | |
| // |force| + |velocity| < c <- stopping condition | |
| if (calcSpeed(sf) * deltaT + speed < m_options.momentum.minSpeed) { | |
| return null; | |
| } | |
| if (speed > 0) { | |
| vx = vx * speed; | |
| vy = vy * speed; | |
| } else { | |
| vx = 0; | |
| vy = 0; | |
| } | |
| return { | |
| x: vx - sf.x * deltaT, | |
| y: vy - sf.y * deltaT | |
| }; | |
| } | |
| /** | |
| * Get the spring force for the current map bounds | |
| * (This method might need to move elsewhere to deal | |
| * with different projections) | |
| * @private | |
| * @returns {object} The spring force | |
| */ | |
| function springForce() { | |
| var xplus, // force to the right | |
| xminus, // force to the left | |
| yplus, // force to the top | |
| yminus; // force to the bottom | |
| if (!m_options.spring.enabled) { | |
| return {x: 0, y: 0}; | |
| } | |
| // get screen coordinates of corners | |
| var ul = m_this.map().gcsToDisplay({ | |
| x: -180, | |
| y: 82 | |
| }); | |
| var lr = m_this.map().gcsToDisplay({ | |
| x: 180, | |
| y: -82 | |
| }); | |
| var c = m_options.spring.springConstant; | |
| // Arg... map needs to expose the canvas size | |
| var width = m_this.map().node().width(); | |
| var height = m_this.map().node().height(); | |
| xplus = c * Math.max(0, ul.x); | |
| xminus = c * Math.max(0, width - lr.x); | |
| yplus = c * Math.max(0, ul.y) / 2; | |
| yminus = c * Math.max(0, height - lr.y) / 2; | |
| return { | |
| x: xplus - xminus, | |
| y: yplus - yminus | |
| }; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle event when a mouse button is unpressed on the document. | |
| * Removes temporary bindings. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._handleMouseUpDocument = function (evt) { | |
| var selectionObj, oldAction; | |
| m_this._getMouseButton(evt); | |
| m_this._getMouseModifiers(evt); | |
| // unbind temporary handlers on document | |
| $(document).off('.geojs'); | |
| if (m_mouse.buttons.right) { | |
| evt.preventDefault(); | |
| } | |
| if (m_state.action === 'select') { | |
| selectionObj = m_this._getSelection(); | |
| m_selectionLayer.clear(); | |
| m_this.map().deleteLayer(m_selectionLayer); | |
| m_selectionLayer = null; | |
| m_selectionPlane = null; | |
| m_this.map().geoTrigger(geo.event.brushend, selectionObj); | |
| } | |
| // reset the interactor state | |
| oldAction = m_state.action; | |
| m_state = {}; | |
| // if momentum is enabled, start the action here | |
| if (m_options.momentum.enabled && oldAction === 'pan') { | |
| m_this.springBack(true); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle event when a mouse button is unpressed | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._handleMouseUp = function (evt) { | |
| m_this._getMouseButton(evt); | |
| m_this._getMouseModifiers(evt); | |
| // fire a click event here | |
| m_this.map().geoTrigger(geo.event.mouseclick, m_this.mouse()); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle mouse wheel event | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._handleMouseWheel = function (evt) { | |
| var zoomFactor, direction; | |
| // try to normalize deltas using the wheel event standard: | |
| // https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent | |
| evt.deltaFactor = 1; | |
| if (evt.originalEvent.deltaMode === 1) { | |
| // DOM_DELTA_LINE -- estimate line height | |
| evt.deltaFactor = 12; | |
| } else if (evt.originalEvent.deltaMode === 2) { | |
| // DOM_DELTA_PAGE -- get window height | |
| evt.deltaFactor = $(window).height(); | |
| } | |
| // If the browser doesn't support the standard then | |
| // just set the delta's to zero. | |
| evt.deltaX = evt.originalEvent.deltaX || 0; | |
| evt.deltaY = evt.originalEvent.deltaY || 0; | |
| m_this._getMouseModifiers(evt); | |
| evt.deltaX = evt.deltaX * m_options.wheelScaleX * evt.deltaFactor / 120; | |
| evt.deltaY = evt.deltaY * m_options.wheelScaleY * evt.deltaFactor / 120; | |
| evt.preventDefault(); | |
| if (!doRespond()) { | |
| m_wheelQueue.x += evt.deltaX; | |
| m_wheelQueue.y += evt.deltaY; | |
| return; | |
| } | |
| evt.deltaX += m_wheelQueue.x; | |
| evt.deltaY += m_wheelQueue.y; | |
| m_wheelQueue = { | |
| x: 0, | |
| y: 0 | |
| }; | |
| if (m_options.panWheelEnabled && | |
| eventMatch('wheel', m_options.panWheelModifiers)) { | |
| m_this.map().pan({ | |
| x: evt.deltaX, | |
| y: -evt.deltaY | |
| }); | |
| } else if (m_options.zoomWheelEnabled && | |
| eventMatch('wheel', m_options.zoomWheelModifiers)) { | |
| zoomFactor = -evt.deltaY; | |
| direction = m_mouse.map; | |
| m_this.map().zoom( | |
| m_this.map().zoom() + zoomFactor, | |
| direction | |
| ); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Start up a spring back action when the map bounds are out of range. | |
| * Not to be user callable. | |
| * @todo Move this and momentum handling to the map class | |
| * @protected | |
| * | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.springBack = function (initialVelocity) { | |
| if (m_state.action === 'momentum') { | |
| return; | |
| } | |
| if (!initialVelocity) { | |
| m_mouse.velocity = { | |
| x: 0, | |
| y: 0 | |
| }; | |
| } | |
| m_state.action = 'momentum'; | |
| m_state.origin = m_this.mouse(); | |
| m_state.start = new Date(); | |
| m_state.handler = function () { | |
| var v, s, last, dt; | |
| // Not sure the correct way to do this. We need the delta t for the | |
| // next time step... Maybe use a better interpolator and the time | |
| // parameter from requestAnimationFrame. | |
| dt = Math.min(m_mouse.deltaTime, 30); | |
| if (m_state.action !== 'momentum' || | |
| !m_this.map() || | |
| m_this.map().transition()) { | |
| // cancel if a new action was performed | |
| return; | |
| } | |
| last = m_state.start.valueOf(); | |
| m_state.start = new Date(); | |
| v = modifyVelocity(m_mouse.velocity, m_state.start - last); | |
| // stop panning when the speed is below the threshold | |
| if (!v) { | |
| m_state = {}; | |
| return; | |
| } | |
| s = calcSpeed(v); | |
| if (s > m_options.momentum.maxSpeed) { | |
| s = m_options.momentum.maxSpeed / s; | |
| v.x = v.x * s; | |
| v.y = v.y * s; | |
| } | |
| if (!isFinite(v.x) || !isFinite(v.y)) { | |
| v.x = 0; | |
| v.y = 0; | |
| } | |
| m_mouse.velocity.x = v.x; | |
| m_mouse.velocity.y = v.y; | |
| m_this.map().pan({ | |
| x: m_mouse.velocity.x * dt, | |
| y: m_mouse.velocity.y * dt | |
| }); | |
| if (m_state.handler) { | |
| window.requestAnimationFrame(m_state.handler); | |
| } | |
| }; | |
| if (m_state.handler) { | |
| window.requestAnimationFrame(m_state.handler); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle double click event | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._handleDoubleClick = function () { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Public method that unbinds all events | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.destroy = function () { | |
| m_this._disconnectEvents(); | |
| m_this.map(null); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get current mouse information | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.mouse = function () { | |
| return $.extend(true, {}, m_mouse); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get current keyboard information | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.keyboard = function () { | |
| return $.extend(true, {}, m_keyboard); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the current interactor state | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.state = function () { | |
| return $.extend(true, {}, m_state); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Simulate a DOM mouse event on connected map. | |
| * | |
| * The options for creating the events are as follows, not all | |
| * options are required for every event type. :: | |
| * | |
| * options = { | |
| * page: {x, y} // position on the page | |
| * map: {x, y} // position on the map (overrides page) | |
| * button: 'left' | 'right' | 'middle' | |
| * modifiers: [ 'alt' | 'ctrl' | 'meta' | 'shift' ] | |
| * wheelDelta: {x, y} | |
| * } | |
| * | |
| * @param {string} type Event type 'mousemove', 'mousedown', 'mouseup', ... | |
| * @param {object} options | |
| * @returns {mapInteractor} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.simulateEvent = function (type, options) { | |
| var evt, page, offset, which; | |
| if (!m_this.map()) { | |
| return m_this; | |
| } | |
| page = options.page || {}; | |
| if (options.map) { | |
| offset = $node.offset(); | |
| page.x = options.map.x + offset.left; | |
| page.y = options.map.y + offset.top; | |
| } | |
| if (options.button === 'left') { | |
| which = 1; | |
| } else if (options.button === 'right') { | |
| which = 3; | |
| } else if (options.button === 'middle') { | |
| which = 2; | |
| } | |
| options.modifiers = options.modifiers || []; | |
| options.wheelDelta = options.wheelDelta || {}; | |
| evt = $.Event( | |
| type, | |
| { | |
| pageX: page.x, | |
| pageY: page.y, | |
| which: which, | |
| altKey: options.modifiers.indexOf('alt') >= 0, | |
| ctrlKey: options.modifiers.indexOf('ctrl') >= 0, | |
| metaKey: options.modifiers.indexOf('meta') >= 0, | |
| shiftKey: options.modifiers.indexOf('shift') >= 0, | |
| originalEvent: { | |
| deltaX: options.wheelDelta.x, | |
| deltaY: options.wheelDelta.y, | |
| deltaMode: options.wheelMode | |
| } | |
| } | |
| ); | |
| $node.trigger(evt); | |
| }; | |
| this._connectEvents(); | |
| return this; | |
| }; | |
| inherit(geo.mapInteractor, geo.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Stores the current time for a map, triggers time keeping events, and | |
| * handles the animation state and interaction. | |
| * | |
| * @class geo.clock | |
| * @extends geo.object | |
| * @returns {geo.clock} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.clock = function (opts) { | |
| 'use strict'; | |
| if (!(this instanceof geo.clock)) { | |
| return new geo.clock(opts); | |
| } | |
| opts = opts || {}; | |
| geo.object.call(this, opts); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| m_now = new Date(0), | |
| m_start = null, | |
| m_end = null, | |
| m_step = null, | |
| m_rate = null, | |
| m_loop = Number.POSITIVE_INFINITY, | |
| m_currentLoop = 0, | |
| m_state = 'stop', | |
| m_currentAnimation = null, | |
| m_object = null; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the geo.object to trigger events on. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.object = function (arg) { | |
| if (arg === undefined) { | |
| return m_object; | |
| } | |
| m_object = arg; | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns true if attached to a valid geo.object. | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._attached = function () { | |
| return (m_object instanceof geo.object); | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the current time. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.now = function (arg) { | |
| var previous = m_now; | |
| if (arg === undefined) { | |
| return m_now; | |
| } | |
| m_now = arg; | |
| if (m_now !== previous && | |
| m_this._attached()) { | |
| m_this.object().geoTrigger(geo.event.clock.change, { | |
| previous: previous, | |
| current: m_now, | |
| clock: m_this | |
| }); | |
| } | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the animation start time. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.start = function (arg) { | |
| if (arg === undefined) { | |
| return m_start; | |
| } | |
| m_start = arg; | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the animation end time. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.end = function (arg) { | |
| if (arg === undefined) { | |
| return m_end; | |
| } | |
| m_end = arg; | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the animation time step. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.step = function (arg) { | |
| if (arg === undefined) { | |
| return m_step; | |
| } | |
| m_step = arg; | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set looping control of the clock. This controls how many times the | |
| * animation will repeat before stopping. Default | |
| * ``Number.POSITIVE_INFINITY``, the animation repeats forever. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.loop = function (arg) { | |
| if (arg === undefined) { | |
| return m_loop; | |
| } | |
| m_loop = arg; | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the animation state. Valid values are: | |
| * | |
| * * 'stop' | |
| * * 'play' | |
| * * 'pause' | |
| * | |
| * This will also trigger relevant events, but they may be fired | |
| * asynchronously. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.state = function (arg, step) { | |
| if (arg === undefined) { | |
| return m_state; | |
| } | |
| if (['stop', 'play', 'pause'].indexOf(arg) < 0) { | |
| console.log('WARNING: Ignored invalid state: ' + arg); | |
| return m_this; | |
| } | |
| if (arg === 'play' && m_state === 'stop') { | |
| // reset animation parameters | |
| m_currentLoop = 0; | |
| m_this.now(m_this.start()); | |
| } | |
| if (arg === 'play' && m_state !== 'play') { | |
| // Start a new animation. | |
| m_state = arg; | |
| m_this._animate(step || 1); | |
| } | |
| m_state = arg; | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the animation frame rate. This is approximately the number | |
| * of frames displayed per second. A null value will use the browser's | |
| * native requestAnimationFrame to draw new frames. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.framerate = function (arg) { | |
| if (arg === undefined) { | |
| return m_rate; | |
| } | |
| m_rate = arg; | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Step to the next frame in the animation. Pauses the animation if it is | |
| * playing. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.stepForward = function () { | |
| m_this.state('pause'); | |
| m_this._setNextFrame(1); | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Step to the previous frame in the animation. Pauses the animation if it is | |
| * playing. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.stepBackward = function () { | |
| m_this.state('pause'); | |
| m_this._setNextFrame(-1); | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Step to the next frame in the animation. Will set the state to stop | |
| * if the animation has reached the end and there are no more loops. | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._setNextFrame = function (step) { | |
| var next = new Date(m_this.now().valueOf() + step * m_this.step()); | |
| if (next >= m_this.end() || next <= m_this.start()) { | |
| if (m_this.loop() <= m_currentLoop) { | |
| m_this.state('stop'); | |
| return; | |
| } | |
| m_currentLoop += 1; | |
| if (step >= 0) { | |
| m_this.now(m_this.start()); | |
| } else { | |
| m_this.now(m_this.end()); | |
| } | |
| return; | |
| } | |
| m_this.now(next); | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Start an animation. | |
| * @param {integer} step The animation frame step (+1 for forward -1 for | |
| * reverse, etc). | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._animate = function (step) { | |
| var myAnimation = {}; | |
| m_currentAnimation = myAnimation; | |
| function frame() { | |
| if (myAnimation !== m_currentAnimation) { | |
| // A new animation has started, so kill this one. | |
| return; | |
| } | |
| m_this._setNextFrame(step); | |
| if (m_this.state() === 'play') { | |
| // Queue the next frame | |
| if (!m_this.framerate()) { | |
| window.requestAnimationFrame(frame); | |
| } else { | |
| window.setTimeout(frame, 1000 / m_this.framerate()); | |
| } | |
| } else if (m_this._attached()) { | |
| m_this.object().geoTrigger(geo.event.clock[m_this.state()], { | |
| current: m_this.now(), | |
| clock: m_this | |
| }); | |
| } | |
| } | |
| // trigger the play event | |
| if (m_this._attached()) { | |
| m_this.object().geoTrigger(geo.event.clock.play, { | |
| current: m_this.now(), | |
| clock: m_this | |
| }); | |
| } | |
| // Queue the first frame | |
| if (!m_this.framerate()) { | |
| window.requestAnimationFrame(frame); | |
| } else { | |
| window.setTimeout(frame, 1000 / m_this.framerate()); | |
| } | |
| }; | |
| }; | |
| inherit(geo.clock, geo.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class fileReader | |
| * | |
| * @class | |
| * @extends geo.object | |
| * @returns {geo.fileReader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.fileReader = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.fileReader)) { | |
| return new geo.fileReader(arg); | |
| } | |
| geo.object.call(this); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| arg = arg || {}; | |
| if (!(arg.layer instanceof geo.featureLayer)) { | |
| throw "fileReader must be given a feature layer"; | |
| } | |
| var m_layer = arg.layer; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the feature layer attached to the reader | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.layer = function () { | |
| return m_layer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Tells the caller if it can handle the given file by returning a boolean. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.canRead = function () { | |
| return false; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Reads the file object and calls the done function when finished. As an | |
| * argument to done, provides a boolean that reports if the read was a | |
| * success. Possibly, it can call done with an object containing details | |
| * of the read operation. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.read = function (file, done) { | |
| done(false); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return a FileReader with handlers attached. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function newFileReader(done, progress) { | |
| var reader = new FileReader(); | |
| if (progress) { | |
| reader.onprogress = progress; | |
| } | |
| reader.onloadend = function () { | |
| if (!reader.result) { | |
| done(reader.error); | |
| } | |
| done(reader.result); | |
| }; | |
| return reader; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Private method for reading a file object as a string. Calls done with | |
| * the string content when finished or an error object if unsuccessful. | |
| * Optionally, the caller can provide a progress method that is called | |
| * after reading each slice. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._getString = function (file, done, progress) { | |
| var reader = newFileReader(done, progress); | |
| reader.readAsText(file); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Like _getString, but returns an ArrayBuffer object. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._getArrayBuffer = function (file, done, progress) { | |
| var reader = newFileReader(done, progress); | |
| reader.readAsText(file); | |
| }; | |
| return this; | |
| }; | |
| inherit(geo.fileReader, geo.object); | |
| /*global File*/ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class jsonReader | |
| * | |
| * @class | |
| * @extends geo.fileReader | |
| * @returns {geo.jsonReader} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.jsonReader = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.jsonReader)) { | |
| return new geo.jsonReader(arg); | |
| } | |
| var m_this = this, m_style = arg.style || {}; | |
| m_style = $.extend({ | |
| 'strokeWidth': 2, | |
| 'strokeColor': {r: 0, g: 0, b: 0}, | |
| 'strokeOpacity': 1, | |
| 'fillColor': {r: 1, g: 0, b: 0}, | |
| 'fillOpacity': 1 | |
| }, m_style); | |
| geo.fileReader.call(this, arg); | |
| this.canRead = function (file) { | |
| if (file instanceof File) { | |
| return (file.type === 'application/json' || file.name.match(/\.json$/)); | |
| } else if (typeof file === 'string') { | |
| try { | |
| JSON.parse(file); | |
| } catch (e) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| try { | |
| if (Array.isArray(m_this._featureArray(file))) { | |
| return true; | |
| } | |
| } catch (e) {} | |
| return false; | |
| }; | |
| this._readObject = function (file, done, progress) { | |
| var object; | |
| function onDone(fileString) { | |
| if (typeof fileString !== 'string') { | |
| done(false); | |
| } | |
| // We have two possibilities here: | |
| // 1) fileString is a JSON string or is | |
| // a URL. | |
| try { | |
| object = JSON.parse(fileString); | |
| done(object); | |
| } catch (e) { | |
| if (!object) { | |
| $.ajax({ | |
| type: 'GET', | |
| url: fileString, | |
| dataType: 'text' | |
| }).done(function (data) { | |
| object = JSON.parse(data); | |
| done(object); | |
| }).fail(function () { | |
| done(false); | |
| }); | |
| } | |
| } | |
| } | |
| if (file instanceof File) { | |
| m_this._getString(file, onDone, progress); | |
| } else if (typeof file === 'string') { | |
| onDone(file); | |
| } else { | |
| done(file); | |
| } | |
| }; | |
| this._featureArray = function (spec) { | |
| if (spec.type === 'FeatureCollection') { | |
| return spec.features || []; | |
| } | |
| if (spec.type === 'GeometryCollection') { | |
| throw 'GeometryCollection not yet implemented.'; | |
| } | |
| if (Array.isArray(spec.coordinates)) { | |
| return spec; | |
| } | |
| throw 'Unsupported collection type: ' + spec.type; | |
| }; | |
| this._featureType = function (spec) { | |
| var geometry = spec.geometry || {}; | |
| if (geometry.type === 'Point' || geometry.type === 'MultiPoint') { | |
| return 'point'; | |
| } | |
| if (geometry.type === 'LineString') { | |
| return 'line'; | |
| } | |
| if (geometry.type === 'Polygon') { | |
| return 'polygon'; | |
| } | |
| return null; | |
| }; | |
| this._getCoordinates = function (spec) { | |
| var geometry = spec.geometry || {}, | |
| coordinates = geometry.coordinates || [], elv; | |
| if ((coordinates.length === 2 || coordinates.length === 3) && | |
| (isFinite(coordinates[0]) && isFinite(coordinates[1]))) { | |
| // Do we have a elevation component | |
| if (isFinite(coordinates[2])) { | |
| elv = coordinates[2]; | |
| } | |
| // special handling for single point coordinates | |
| return [{x: coordinates[0], y: coordinates[1], z: elv}]; | |
| } | |
| // need better handling here, but we can plot simple polygons | |
| // by taking just the outer linearring | |
| if (Array.isArray(coordinates[0][0])) { | |
| coordinates = coordinates[0]; | |
| } | |
| // return an array of latlng's for LineString, MultiPoint, etc... | |
| return coordinates.map(function (c) { | |
| return { | |
| x: c[0], | |
| y: c[1], | |
| z: c[2] | |
| }; | |
| }); | |
| }; | |
| this._getStyle = function (spec) { | |
| return spec.properties; | |
| }; | |
| this.read = function (file, done, progress) { | |
| function _done(object) { | |
| var features, allFeatures = []; | |
| features = m_this._featureArray(object); | |
| features.forEach(function (feature) { | |
| var type = m_this._featureType(feature), | |
| coordinates = m_this._getCoordinates(feature), | |
| style = m_this._getStyle(feature); | |
| if (type) { | |
| if (type === 'line') { | |
| style.fill = style.fill || false; | |
| allFeatures.push(m_this._addFeature( | |
| type, | |
| [coordinates], | |
| style, | |
| feature.properties | |
| )); | |
| } else if (type === 'point') { | |
| style.stroke = style.stroke || false; | |
| allFeatures.push(m_this._addFeature( | |
| type, | |
| coordinates, | |
| style, | |
| feature.properties | |
| )); | |
| } else if (type === 'polygon') { | |
| style.fill = style.fill === undefined ? true : style.fill; | |
| style.fillOpacity = ( | |
| style.fillOpacity === undefined ? 0.25 : style.fillOpacity | |
| ); | |
| // polygons not yet supported | |
| allFeatures.push(m_this._addFeature( | |
| 'line', | |
| [coordinates], | |
| style, | |
| feature.properties | |
| )); | |
| } | |
| } else { | |
| console.log('unsupported feature type: ' + feature.geometry.type); | |
| } | |
| }); | |
| if (done) { | |
| done(allFeatures); | |
| } | |
| } | |
| m_this._readObject(file, _done, progress); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build the data array for a feature given the coordinates and properties | |
| * from the geojson. | |
| * | |
| * @private | |
| * @param {Object[]} coordinates Coordinate data array | |
| * @param {Object} properties Geojson properties object | |
| * @param {Object} style Global style defaults | |
| * @returns {Object[]} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._buildData = function (coordinates, properties, style) { | |
| return coordinates.map(function (coord) { | |
| return { | |
| coordinates: coord, | |
| properties: properties, | |
| style: style | |
| }; | |
| }); | |
| }; | |
| this._addFeature = function (type, coordinates, style, properties) { | |
| var _style = $.extend({}, m_style, style); | |
| var feature = m_this.layer().createFeature(type) | |
| .data(m_this._buildData(coordinates, properties, style)) | |
| .style(_style); | |
| if (type === 'line') { | |
| feature.line(function (d) { return d.coordinates; }); | |
| } else { | |
| feature.position(function (d) { return d.coordinates; }); | |
| } | |
| return feature; | |
| }; | |
| }; | |
| inherit(geo.jsonReader, geo.fileReader); | |
| geo.registerFileReader('jsonReader', geo.jsonReader); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class map | |
| * | |
| * Creates a new map inside of the given HTML layer (Typically DIV) | |
| * @class | |
| * @extends geo.sceneObject | |
| * @returns {geo.map} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.map = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.map)) { | |
| return new geo.map(arg); | |
| } | |
| arg = arg || {}; | |
| geo.sceneObject.call(this, arg); | |
| arg.layers = arg.layers === undefined ? [] : arg.layers; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Private member variables | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_exit = this._exit, | |
| m_x = 0, | |
| m_y = 0, | |
| m_node = $(arg.node), | |
| m_width = arg.width || m_node.width(), | |
| m_height = arg.height || m_node.height(), | |
| m_gcs = arg.gcs === undefined ? "EPSG:4326" : arg.gcs, | |
| m_uigcs = arg.uigcs === undefined ? "EPSG:4326" : arg.uigcs, | |
| m_center = { x: 0, y: 0 }, | |
| m_zoom = arg.zoom === undefined ? 1 : arg.zoom, | |
| m_baseLayer = null, | |
| m_fileReader = null, | |
| m_interactor = null, | |
| m_validZoomRange = { min: 0, max: 16 }, | |
| m_transition = null, | |
| m_queuedTransition = null, | |
| m_clock = null, | |
| m_bounds = {}; | |
| arg.center = geo.util.normalizeCoordinates(arg.center); | |
| arg.autoResize = arg.autoResize === undefined ? true : arg.autoResize; | |
| arg.clampBounds = arg.clampBounds === undefined ? true : arg.clampBounds; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get map gcs | |
| * | |
| * @returns {string} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.gcs = function (arg) { | |
| if (arg === undefined) { | |
| return m_gcs; | |
| } | |
| m_gcs = arg; | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get map user interface GCS | |
| * | |
| * @returns {string} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.uigcs = function () { | |
| return m_uigcs; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get root node of the map | |
| * | |
| * @returns {object} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.node = function () { | |
| return m_node; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set zoom level of the map | |
| * | |
| * @returns {Number|geo.map} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.zoom = function (val, direction) { | |
| var base, evt, recenter = false; | |
| if (val === undefined) { | |
| return m_zoom; | |
| } | |
| val = Math.min(m_validZoomRange.max, Math.max(val, m_validZoomRange.min)); | |
| if (val === m_zoom) { | |
| return m_this; | |
| } | |
| base = m_this.baseLayer(); | |
| evt = { | |
| geo: {}, | |
| zoomLevel: val, | |
| screenPosition: direction, | |
| eventType: geo.event.zoom | |
| }; | |
| if (base) { | |
| base.renderer().geoTrigger(geo.event.zoom, evt, true); | |
| } | |
| recenter = evt.center; | |
| if (!evt.geo.preventDefault) { | |
| m_zoom = val; | |
| m_this._updateBounds(); | |
| m_this.children().forEach(function (child) { | |
| child.geoTrigger(geo.event.zoom, evt, true); | |
| }); | |
| m_this.modified(); | |
| } | |
| if (evt.center) { | |
| m_this.center(recenter); | |
| } else { | |
| m_this.pan({x: 0, y: 0}); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Pan the map by (x: dx, y: dy) pixels. | |
| * | |
| * @param {Object} delta | |
| * @param {bool?} force Disable bounds clamping | |
| * @returns {geo.map} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.pan = function (delta, force) { | |
| var base = m_this.baseLayer(), | |
| evt, pt, corner1, corner2; | |
| if (arg.clampBounds && !force && m_width && m_height) { | |
| pt = m_this.displayToGcs({ | |
| x: delta.x, | |
| y: delta.y | |
| }); | |
| corner1 = m_this.gcsToDisplay({ | |
| x: -180, | |
| y: 82 | |
| }); | |
| corner2 = m_this.gcsToDisplay({ | |
| x: 180, | |
| y: -82 | |
| }); | |
| if (corner1.x > 0 && corner2.x < m_width) { | |
| // if the map is too small horizontally | |
| delta.x = (-corner1.x + m_width - corner2.x) / 2; | |
| } else { | |
| delta.x = Math.max(Math.min(delta.x, -corner1.x), m_width - corner2.x); | |
| } | |
| if (corner1.y > 0 && corner2.y < m_height) { | |
| // if the map is too small horizontally | |
| delta.y = (-corner1.y + m_height - corner2.y) / 2; | |
| } else { | |
| delta.y = Math.max(Math.min(delta.y, -corner1.y), m_height - corner2.y); | |
| } | |
| } | |
| evt = { | |
| geo: {}, | |
| screenDelta: delta, | |
| eventType: geo.event.pan | |
| }; | |
| // first pan the base layer | |
| if (base) { | |
| base.renderer().geoTrigger(geo.event.pan, evt, true); | |
| } | |
| // If the base renderer says the pan is invalid, then cancel the action. | |
| if (evt.geo.preventDefault) { | |
| return; | |
| } | |
| m_center = m_this.displayToGcs({ | |
| x: m_width / 2, | |
| y: m_height / 2 | |
| }); | |
| m_this._updateBounds(); | |
| m_this.children().forEach(function (child) { | |
| child.geoTrigger(geo.event.pan, evt, true); | |
| }); | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set center of the map to the given geographic coordinates, or get the | |
| * current center. Uses bare objects {x: 0, y: 0}. | |
| * | |
| * @param {Object} coordinates | |
| * @returns {Object|geo.map} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.center = function (coordinates, force) { | |
| var newCenter, currentCenter; | |
| if (coordinates === undefined) { | |
| return m_center; | |
| } | |
| // get the screen coordinates of the new center | |
| coordinates = geo.util.normalizeCoordinates(coordinates); | |
| newCenter = m_this.gcsToDisplay(coordinates); | |
| currentCenter = m_this.gcsToDisplay(m_center); | |
| // call the pan method | |
| m_this.pan({ | |
| x: currentCenter.x - newCenter.x, | |
| y: currentCenter.y - newCenter.y | |
| }, force); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add layer to the map | |
| * | |
| * @param {geo.layer} layer to be added to the map | |
| * @return {geom.map} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.createLayer = function (layerName, arg) { | |
| var newLayer = geo.createLayer( | |
| layerName, m_this, arg); | |
| if (newLayer !== null || newLayer !== undefined) { | |
| newLayer._resize(m_x, m_y, m_width, m_height); | |
| } else { | |
| return null; | |
| } | |
| if (newLayer.referenceLayer() || m_this.children().length === 0) { | |
| m_this.baseLayer(newLayer); | |
| } | |
| m_this.addChild(newLayer); | |
| m_this.modified(); | |
| // TODO: need a better way to set the initial coordinates of a layer | |
| if (!newLayer.referenceLayer()) { | |
| m_this.center(m_this.center()); | |
| } | |
| m_this.geoTrigger(geo.event.layerAdd, { | |
| type: geo.event.layerAdd, | |
| target: m_this, | |
| layer: newLayer | |
| }); | |
| return newLayer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Remove layer from the map | |
| * | |
| * @param {geo.layer} layer that should be removed from the map | |
| * @return {geo.map} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.deleteLayer = function (layer) { | |
| if (layer !== null && layer !== undefined) { | |
| layer._exit(); | |
| m_this.removeChild(layer); | |
| m_this.modified(); | |
| m_this.geoTrigger(geo.event.layerRemove, { | |
| type: geo.event.layerRemove, | |
| target: m_this, | |
| layer: layer | |
| }); | |
| } | |
| /// Return deleted layer (similar to createLayer) as in the future | |
| /// we may provide extension of this method to support deletion of | |
| /// layer using id or some sort. | |
| return layer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Toggle visibility of a layer | |
| * | |
| * @param {geo.layer} layer | |
| * @returns {Boolean} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.toggle = function (layer) { | |
| if (layer !== null && layer !== undefined) { | |
| layer.visible(!layer.visible()); | |
| m_this.modified(); | |
| m_this.geoTrigger(geo.event.layerToggle, { | |
| type: geo.event.layerToggle, | |
| target: m_this, | |
| layer: layer | |
| }); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Resize map | |
| * | |
| * @param {Number} x x-offset in display space | |
| * @param {Number} y y-offset in display space | |
| * @param {Number} w width in display space | |
| * @param {Number} h height in display space | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.resize = function (x, y, w, h) { | |
| var i, layers = m_this.children(); | |
| m_x = x; | |
| m_y = y; | |
| m_width = w; | |
| m_height = h; | |
| for (i = 0; i < layers.length; i += 1) { | |
| layers[i]._resize(x, y, w, h); | |
| } | |
| m_this.geoTrigger(geo.event.resize, { | |
| type: geo.event.resize, | |
| target: m_this, | |
| x: m_x, | |
| y: m_y, | |
| width: w, | |
| height: h | |
| }); | |
| m_this._updateBounds(); | |
| m_this.pan({x: 0, y: 0}); | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert from gcs coordinates to display coordinates | |
| * | |
| * @param {*} input {[geo.latlng], [{x:_x, y: _y}], [x1,y1, x2, y2]} | |
| * @return {object} | |
| * | |
| * @note Currently only lat-lon inputs are supported | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.gcsToDisplay = function (input) { | |
| var world, output; | |
| /// Now handle different data types | |
| if ((input instanceof Array && | |
| input.length > 0) || input instanceof Object) { | |
| world = m_baseLayer.toLocal(input); | |
| output = m_baseLayer.renderer().worldToDisplay(world); | |
| } else { | |
| /// Everything else | |
| throw "Conversion method latLonToDisplay does not handle " + input; | |
| } | |
| return output; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert from display to latitude longitude coordinates | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.displayToGcs = function (input) { | |
| var output; | |
| /// Now handle different data types | |
| if ((input instanceof Array && input.length > 0) || | |
| input instanceof Object) { | |
| output = m_baseLayer.renderer().displayToWorld(input); | |
| output = m_baseLayer.fromLocal(output); | |
| } else { | |
| throw "Conversion method displayToGcs does not handle " + input; | |
| } | |
| return output; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Queries each layer for information at this location. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.query = function () { | |
| // TODO Implement this | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Sets or gets base layer for this map | |
| * | |
| * @param {geo.layer} baseLayer optional | |
| * @returns {geo.map|geo.layer} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.baseLayer = function (baseLayer) { | |
| var save; | |
| if (baseLayer !== undefined) { | |
| // The GCS of the layer must match the map | |
| if (m_gcs !== baseLayer.gcs()) { | |
| m_this.gcs(baseLayer.gcs()); | |
| } | |
| m_baseLayer = baseLayer; | |
| // Set the layer as the reference layer | |
| m_baseLayer.referenceLayer(true); | |
| if (arg.center) { | |
| // This assumes that the base layer is initially centered at | |
| // (0, 0). May want to add an explicit call to the base layer | |
| // to set a given center. | |
| m_this.center(arg.center, true); | |
| } | |
| save = m_zoom; | |
| m_zoom = null; | |
| m_this.zoom(save); | |
| m_this._updateBounds(); | |
| // This forces the map into a state with valid bounds | |
| // when clamping is on. The original call to center | |
| // is forced to initialize the camera position in the | |
| // base layer so no adjustment is done there. | |
| m_this.pan({x: 0, y: 0}); | |
| return m_this; | |
| } | |
| return m_baseLayer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Manually force to render map | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.draw = function () { | |
| var i, layers = m_this.children(); | |
| m_this.geoTrigger(geo.event.draw, { | |
| type: geo.event.draw, | |
| target: m_this | |
| } | |
| ); | |
| m_this._update(); | |
| for (i = 0; i < layers.length; i += 1) { | |
| layers[i].draw(); | |
| } | |
| m_this.geoTrigger(geo.event.drawEnd, { | |
| type: geo.event.drawEnd, | |
| target: m_this | |
| } | |
| ); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Attach a file reader to a layer in the map to be used as a drop target. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.fileReader = function (readerType, opts) { | |
| var layer, renderer; | |
| opts = opts || {}; | |
| if (!readerType) { | |
| return m_fileReader; | |
| } | |
| layer = opts.layer; | |
| if (!layer) { | |
| renderer = opts.renderer; | |
| if (!renderer) { | |
| renderer = "d3"; | |
| } | |
| layer = m_this.createLayer("feature", {renderer: renderer}); | |
| } | |
| opts.layer = layer; | |
| opts.renderer = renderer; | |
| m_fileReader = geo.createFileReader(readerType, opts); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize the map | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| var i; | |
| if (m_node === undefined || m_node === null) { | |
| throw "Map require DIV node"; | |
| } | |
| if (arg !== undefined && arg.layers !== undefined) { | |
| for (i = 0; i < arg.layers.length; i += 1) { | |
| if (i === 0) { | |
| m_this.baseLayer(arg.layers[i]); | |
| } | |
| m_this.addLayer(arg.layers[i]); | |
| } | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update map | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function (request) { | |
| var i, layers = m_this.children(); | |
| for (i = 0; i < layers.length; i += 1) { | |
| layers[i]._update(request); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Exit this map | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.exit = function () { | |
| var i, layers = m_this.children(); | |
| for (i = 0; i < layers.length; i += 1) { | |
| layers[i]._exit(); | |
| } | |
| if (m_this.interactor()) { | |
| m_this.interactor().destroy(); | |
| m_this.interactor(null); | |
| } | |
| m_this.node().off(".geo"); | |
| $(window).off("resize", resizeSelf); | |
| s_exit(); | |
| }; | |
| this._init(arg); | |
| // set up drag/drop handling | |
| this.node().on("dragover.geo", function (e) { | |
| var evt = e.originalEvent; | |
| if (m_this.fileReader()) { | |
| evt.stopPropagation(); | |
| evt.preventDefault(); | |
| evt.dataTransfer.dropEffect = "copy"; | |
| } | |
| }) | |
| .on("drop.geo", function (e) { | |
| var evt = e.originalEvent, reader = m_this.fileReader(), | |
| i, file; | |
| function done() { | |
| m_this.draw(); | |
| } | |
| if (reader) { | |
| evt.stopPropagation(); | |
| evt.preventDefault(); | |
| for (i = 0; i < evt.dataTransfer.files.length; i += 1) { | |
| file = evt.dataTransfer.files[i]; | |
| if (reader.canRead(file)) { | |
| reader.read(file, done); // to do: trigger event on done | |
| } | |
| } | |
| } | |
| }); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the map interactor | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.interactor = function (arg) { | |
| if (arg === undefined) { | |
| return m_interactor; | |
| } | |
| m_interactor = arg; | |
| // this makes it possible to set a null interactor | |
| // i.e. map.interactor(null); | |
| if (m_interactor) { | |
| m_interactor.map(m_this); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the map clock | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.clock = function (arg) { | |
| if (arg === undefined) { | |
| return m_clock; | |
| } | |
| m_clock = arg; | |
| if (m_clock) { | |
| m_clock.object(m_this); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the min/max zoom range. | |
| * | |
| * @param {Object} arg {min: minimumzoom, max: maximumzom} | |
| * @returns {Object|geo.map} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.zoomRange = function (arg) { | |
| if (arg === undefined) { | |
| return $.extend({}, m_validZoomRange); | |
| } | |
| m_validZoomRange.min = arg.min; | |
| m_validZoomRange.max = arg.max; | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Start an animated zoom/pan. | |
| * | |
| * Options: | |
| * <pre> | |
| * opts = { | |
| * center: { x: ... , y: ... } // the new center | |
| * zoom: ... // the new zoom level | |
| * duration: ... // the duration (in ms) of the transition | |
| * ease: ... // an easing function [0, 1] -> [0, 1] | |
| * } | |
| * </pre> | |
| * | |
| * Call with no arguments to return the current transition information. | |
| * | |
| * @param {object?} opts | |
| * @returns {geo.map} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.transition = function (opts) { | |
| if (opts === undefined) { | |
| return m_transition; | |
| } | |
| if (m_transition) { | |
| m_queuedTransition = opts; | |
| return m_this; | |
| } | |
| function interp1(p0, p1, t) { | |
| return p0 + (p1 - p0) * t; | |
| } | |
| function defaultInterp(p0, p1) { | |
| return function (t) { | |
| return [ | |
| interp1(p0[0], p1[0], t), | |
| interp1(p0[1], p1[1], t), | |
| interp1(p0[2], p1[2], t) | |
| ]; | |
| }; | |
| } | |
| // Transform zoom level into z-coordinate and inverse | |
| function zoom2z(z) { | |
| return 360 * Math.pow(2, -1 - z); | |
| } | |
| function z2zoom(z) { | |
| return -1 - Math.log2(z / 360); | |
| } | |
| var defaultOpts = { | |
| center: m_this.center(), | |
| zoom: m_this.zoom(), | |
| duration: 1000, | |
| ease: function (t) { | |
| return t; | |
| }, | |
| interp: defaultInterp, | |
| done: null, | |
| zCoord: true | |
| }; | |
| if (opts.center) { | |
| opts.center = geo.util.normalizeCoordinates(opts.center); | |
| } | |
| $.extend(defaultOpts, opts); | |
| m_transition = { | |
| start: { | |
| center: m_this.center(), | |
| zoom: m_this.zoom() | |
| }, | |
| end: { | |
| center: defaultOpts.center, | |
| zoom: defaultOpts.zoom | |
| }, | |
| ease: defaultOpts.ease, | |
| zCoord: defaultOpts.zCoord, | |
| done: defaultOpts.done, | |
| duration: defaultOpts.duration | |
| }; | |
| if (defaultOpts.zCoord) { | |
| m_transition.interp = defaultOpts.interp( | |
| [ | |
| m_transition.start.center.x, | |
| m_transition.start.center.y, | |
| zoom2z(m_transition.start.zoom) | |
| ], | |
| [ | |
| m_transition.end.center.x, | |
| m_transition.end.center.y, | |
| zoom2z(m_transition.end.zoom) | |
| ] | |
| ); | |
| } else { | |
| m_transition.interp = defaultOpts.interp( | |
| [ | |
| m_transition.start.center.x, | |
| m_transition.start.center.y, | |
| m_transition.start.zoom | |
| ], | |
| [ | |
| m_transition.end.center.x, | |
| m_transition.end.center.y, | |
| m_transition.end.zoom | |
| ] | |
| ); | |
| } | |
| function anim(time) { | |
| var done = m_transition.done, next; | |
| next = m_queuedTransition; | |
| if (!m_transition.start.time) { | |
| m_transition.start.time = time; | |
| m_transition.end.time = time + defaultOpts.duration; | |
| } | |
| m_transition.time = time - m_transition.start.time; | |
| if (time >= m_transition.end.time || next) { | |
| if (!next) { | |
| m_this.center(m_transition.end.center); | |
| m_this.zoom(m_transition.end.zoom); | |
| } | |
| m_transition = null; | |
| m_this.geoTrigger(geo.event.transitionend, defaultOpts); | |
| if (done) { | |
| done(); | |
| } | |
| if (next) { | |
| m_queuedTransition = null; | |
| m_this.transition(next); | |
| } | |
| return; | |
| } | |
| var z = m_transition.ease( | |
| (time - m_transition.start.time) / defaultOpts.duration | |
| ); | |
| var p = m_transition.interp(z); | |
| if (m_transition.zCoord) { | |
| p[2] = z2zoom(p[2]); | |
| } | |
| m_this.center({ | |
| x: p[0], | |
| y: p[1] | |
| }); | |
| m_this.zoom(p[2]); | |
| window.requestAnimationFrame(anim); | |
| } | |
| m_this.geoTrigger(geo.event.transitionstart, defaultOpts); | |
| if (defaultOpts.cancelNavigation) { | |
| m_this.geoTrigger(geo.event.transitionend, defaultOpts); | |
| return m_this; | |
| } else if (defaultOpts.cancelAnimation) { | |
| // run the navigation synchronously | |
| defaultOpts.duration = 0; | |
| anim(0); | |
| } else { | |
| window.requestAnimationFrame(anim); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update the internally cached map bounds. | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._updateBounds = function () { | |
| m_bounds.lowerLeft = m_this.displayToGcs({ | |
| x: 0, | |
| y: m_height | |
| }); | |
| m_bounds.lowerRight = m_this.displayToGcs({ | |
| x: m_width, | |
| y: m_height | |
| }); | |
| m_bounds.upperLeft = m_this.displayToGcs({ | |
| x: 0, | |
| y: 0 | |
| }); | |
| m_bounds.upperRight = m_this.displayToGcs({ | |
| x: m_width, | |
| y: 0 | |
| }); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/set the locations of the current map corners as latitudes/longitudes. | |
| * When provided the argument should be an object containing the keys | |
| * lowerLeft and upperRight declaring the desired new map bounds. The | |
| * new bounds will contain at least the min/max lat/lngs provided. In any | |
| * case, the actual new bounds will be returned by this function. | |
| * | |
| * @param {geo.geoBounds} [bds] The requested map bounds | |
| * @return {geo.geoBounds} The actual new map bounds | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.bounds = function (bds) { | |
| var nav; | |
| if (bds === undefined) { | |
| return m_bounds; | |
| } | |
| nav = m_this.zoomAndCenterFromBounds(bds); | |
| m_this.zoom(nav.zoom); | |
| m_this.center(nav.center); | |
| return m_bounds; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the center zoom level necessary to display the given lat/lon bounds. | |
| * | |
| * @param {geo.geoBounds} [bds] The requested map bounds | |
| * @return {object} Object containing keys "center" and "zoom" | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.zoomAndCenterFromBounds = function (bds) { | |
| var ll, ur, dx, dy, zx, zy, center; | |
| // Caveat: | |
| // Much of the following is invalid for alternative map projections. These | |
| // computations should really be defered to the base layer, but there is | |
| // no clear path for doing that with the current base layer api. | |
| // extract bounds info and check for validity | |
| ll = geo.util.normalizeCoordinates(bds.lowerLeft || {}); | |
| ur = geo.util.normalizeCoordinates(bds.upperRight || {}); | |
| if (ll.x >= ur.x || ll.y >= ur.y) { | |
| throw new Error("Invalid bounds provided"); | |
| } | |
| center = { | |
| x: (ll.x + ur.x) / 2, | |
| y: (ll.y + ur.y) / 2 | |
| }; | |
| // calculate the current extend | |
| dx = m_bounds.upperRight.x - m_bounds.lowerLeft.x; | |
| dy = m_bounds.upperRight.y - m_bounds.lowerLeft.y; | |
| // calculate the zoom levels necessary to fit x and y bounds | |
| zx = m_zoom - Math.log2((ur.x - ll.x) / dx); | |
| zy = m_zoom - Math.log2((ur.y - ll.y) / dy); | |
| return { | |
| zoom: Math.min(zx, zy), | |
| center: center | |
| }; | |
| }; | |
| this.interactor(arg.interactor || geo.mapInteractor()); | |
| this.clock(arg.clock || geo.clock()); | |
| function resizeSelf() { | |
| m_this.resize(0, 0, m_node.width(), m_node.height()); | |
| } | |
| if (arg.autoResize) { | |
| $(window).resize(resizeSelf); | |
| } | |
| return this; | |
| }; | |
| /** | |
| * General object specification for map types. Any additional | |
| * values in the object are passed to the map constructor. | |
| * @typedef geo.map.spec | |
| * @type {object} | |
| * @property {object[]} [data=[]] The default data array to | |
| * apply to each feature if none exists | |
| * @property {geo.layer.spec[]} [layers=[]] Layers to create | |
| */ | |
| /** | |
| * Create a map from an object. Any errors in the creation | |
| * of the map will result in returning null. | |
| * @param {geo.map.spec} spec The object specification | |
| * @returns {geo.map|null} | |
| */ | |
| geo.map.create = function (spec) { | |
| "use strict"; | |
| var map = geo.map(spec); | |
| if (!map) { | |
| console.warn("Could not create map."); | |
| return null; | |
| } | |
| spec.data = spec.data || []; | |
| spec.layers = spec.layers || []; | |
| spec.layers.forEach(function (l) { | |
| l.data = l.data || spec.data; | |
| l.layer = geo.layer.create(map, l); | |
| }); | |
| return map; | |
| }; | |
| inherit(geo.map, geo.sceneObject); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class feature | |
| * | |
| * @class | |
| * @extends geo.sceneObject | |
| * @returns {geo.feature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.feature = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.feature)) { | |
| return new geo.feature(arg); | |
| } | |
| geo.sceneObject.call(this); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| arg = arg || {}; | |
| var m_this = this, | |
| s_exit = this._exit, | |
| m_selectionAPI = arg.selectionAPI === undefined ? false : arg.selectionAPI, | |
| m_style = {}, | |
| m_layer = arg.layer === undefined ? null : arg.layer, | |
| m_gcs = arg.gcs === undefined ? "EPSG:4326" : arg.gcs, | |
| m_visible = arg.visible === undefined ? true : arg.visible, | |
| m_bin = arg.bin === undefined ? 0 : arg.bin, | |
| m_renderer = arg.renderer === undefined ? null : arg.renderer, | |
| m_dataTime = geo.timestamp(), | |
| m_buildTime = geo.timestamp(), | |
| m_updateTime = geo.timestamp(), | |
| m_selectedFeatures = []; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Private method to bind mouse handlers on the map element. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._bindMouseHandlers = function () { | |
| // Don't bind handlers for improved performance on features that don't | |
| // require it. | |
| if (!m_selectionAPI) { | |
| return; | |
| } | |
| // First unbind to be sure that the handlers aren't bound twice. | |
| m_this._unbindMouseHandlers(); | |
| m_this.geoOn(geo.event.mousemove, m_this._handleMousemove); | |
| m_this.geoOn(geo.event.mouseclick, m_this._handleMouseclick); | |
| m_this.geoOn(geo.event.brushend, m_this._handleBrushend); | |
| m_this.geoOn(geo.event.brush, m_this._handleBrush); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Private method to unbind mouse handlers on the map element. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._unbindMouseHandlers = function () { | |
| m_this.geoOff(geo.event.mousemove, m_this._handleMousemove); | |
| m_this.geoOff(geo.event.mouseclick, m_this._handleMouseclick); | |
| m_this.geoOff(geo.event.brushend, m_this._handleBrushend); | |
| m_this.geoOff(geo.event.brush, m_this._handleBrush); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * For binding mouse events, use functions with | |
| * the following call signatures: | |
| * | |
| * function handler(arg) { | |
| * // arg.data - the data object of the feature | |
| * // arg.index - the index inside the data array of the featue | |
| * // arg.mouse - mouse information object (see src/core/mapInteractor.js) | |
| * } | |
| * | |
| * i.e. | |
| * | |
| * feature.geoOn(geo.event.feature.mousemove, function (arg) { | |
| * // do something with the feature marker. | |
| * }); | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Search for features containing the given point. | |
| * | |
| * Returns an object: :: | |
| * | |
| * { | |
| * data: [...] // an array of data objects for matching features | |
| * index: [...] // an array of indices of the matching features | |
| * } | |
| * | |
| * @argument {Object} coordinate | |
| * @returns {Object} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.pointSearch = function () { | |
| // base class method does nothing | |
| return { | |
| index: [], | |
| found: [] | |
| }; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Private mousemove handler | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._handleMousemove = function () { | |
| var mouse = m_this.layer().map().interactor().mouse(), | |
| data = m_this.data(), | |
| over = m_this.pointSearch(mouse.geo), | |
| newFeatures = [], oldFeatures = [], lastTop = -1, top = -1; | |
| // Get the index of the element that was previously on top | |
| if (m_selectedFeatures.length) { | |
| lastTop = m_selectedFeatures[m_selectedFeatures.length - 1]; | |
| } | |
| // There are probably faster ways of doing this: | |
| newFeatures = over.index.filter(function (i) { | |
| return m_selectedFeatures.indexOf(i) < 0; | |
| }); | |
| oldFeatures = m_selectedFeatures.filter(function (i) { | |
| return over.index.indexOf(i) < 0; | |
| }); | |
| geo.feature.eventID += 1; | |
| // Fire events for mouse in first. | |
| newFeatures.forEach(function (i, idx) { | |
| m_this.geoTrigger(geo.event.feature.mouseover, { | |
| data: data[i], | |
| index: i, | |
| mouse: mouse, | |
| eventID: geo.feature.eventID, | |
| top: idx === newFeatures.length - 1 | |
| }, true); | |
| }); | |
| geo.feature.eventID += 1; | |
| // Fire events for mouse out next | |
| oldFeatures.forEach(function (i, idx) { | |
| m_this.geoTrigger(geo.event.feature.mouseout, { | |
| data: data[i], | |
| index: i, | |
| mouse: mouse, | |
| eventID: geo.feature.eventID, | |
| top: idx === oldFeatures.length - 1 | |
| }, true); | |
| }); | |
| geo.feature.eventID += 1; | |
| // Fire events for mouse move last | |
| over.index.forEach(function (i, idx) { | |
| m_this.geoTrigger(geo.event.feature.mousemove, { | |
| data: data[i], | |
| index: i, | |
| mouse: mouse, | |
| eventID: geo.feature.eventID, | |
| top: idx === over.index.length - 1 | |
| }, true); | |
| }); | |
| // Replace the selected features array | |
| m_selectedFeatures = over.index; | |
| // Get the index of the element that is now on top | |
| if (m_selectedFeatures.length) { | |
| top = m_selectedFeatures[m_selectedFeatures.length - 1]; | |
| } | |
| if (lastTop !== top) { | |
| // The element on top changed so we need to fire mouseon/mouseoff | |
| if (lastTop !== -1) { | |
| m_this.geoTrigger(geo.event.feature.mouseoff, { | |
| data: data[lastTop], | |
| index: lastTop, | |
| mouse: mouse | |
| }, true); | |
| } | |
| if (top !== -1) { | |
| m_this.geoTrigger(geo.event.feature.mouseon, { | |
| data: data[top], | |
| index: top, | |
| mouse: mouse | |
| }, true); | |
| } | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Private mouseclick handler | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._handleMouseclick = function () { | |
| var mouse = m_this.layer().map().interactor().mouse(), | |
| data = m_this.data(), | |
| over = m_this.pointSearch(mouse.geo); | |
| geo.feature.eventID += 1; | |
| over.index.forEach(function (i, idx) { | |
| m_this.geoTrigger(geo.event.feature.mouseclick, { | |
| data: data[i], | |
| index: i, | |
| mouse: mouse, | |
| eventID: geo.feature.eventID, | |
| top: idx === over.index.length - 1 | |
| }, true); | |
| }); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Private brush handler. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._handleBrush = function (brush) { | |
| var idx = m_this.boxSearch(brush.gcs.lowerLeft, brush.gcs.upperRight), | |
| data = m_this.data(); | |
| geo.feature.eventID += 1; | |
| idx.forEach(function (i, idx) { | |
| m_this.geoTrigger(geo.event.feature.brush, { | |
| data: data[i], | |
| index: i, | |
| mouse: brush.mouse, | |
| brush: brush, | |
| eventID: geo.feature.eventID, | |
| top: idx === idx.length - 1 | |
| }, true); | |
| }); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Private brushend handler. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._handleBrushend = function (brush) { | |
| var idx = m_this.boxSearch(brush.gcs.lowerLeft, brush.gcs.upperRight), | |
| data = m_this.data(); | |
| geo.feature.eventID += 1; | |
| idx.forEach(function (i, idx) { | |
| m_this.geoTrigger(geo.event.feature.brushend, { | |
| data: data[i], | |
| index: i, | |
| mouse: brush.mouse, | |
| brush: brush, | |
| eventID: geo.feature.eventID, | |
| top: idx === idx.length - 1 | |
| }, true); | |
| }); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set style used by the feature | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.style = function (arg1, arg2) { | |
| if (arg1 === undefined) { | |
| return m_style; | |
| } else if (typeof arg1 === "string" && arg2 === undefined) { | |
| return m_style[arg1]; | |
| } else if (arg2 === undefined) { | |
| m_style = $.extend({}, m_style, arg1); | |
| m_this.modified(); | |
| return m_this; | |
| } else { | |
| m_style[arg1] = arg2; | |
| m_this.modified(); | |
| return m_this; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * A uniform getter that always returns a function even for constant styles. | |
| * Maybe extend later to support accessor-like objects. If undefined input, | |
| * return all the styles as an object. | |
| * | |
| * @param {string|undefined} key | |
| * @return {function} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.style.get = function (key) { | |
| var tmp, out; | |
| if (key === undefined) { | |
| var all = {}, k; | |
| for (k in m_style) { | |
| if (m_style.hasOwnProperty(k)) { | |
| all[k] = m_this.style.get(k); | |
| } | |
| } | |
| return all; | |
| } | |
| if (key.toLowerCase().match(/color$/)) { | |
| if (geo.util.isFunction(m_style[key])) { | |
| tmp = geo.util.ensureFunction(m_style[key]); | |
| out = function () { | |
| return geo.util.convertColor( | |
| tmp.apply(this, arguments) | |
| ); | |
| }; | |
| } else { | |
| // if the color is not a function, only convert it once | |
| out = geo.util.ensureFunction(geo.util.convertColor(m_style[key])); | |
| } | |
| } else { | |
| out = geo.util.ensureFunction(m_style[key]); | |
| } | |
| return out; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get layer referenced by the feature | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.layer = function () { | |
| return m_layer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get renderer used by the feature | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.renderer = function () { | |
| return m_renderer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get list of drawables or nodes that are context/api specific. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.drawables = function () { | |
| return m_this._drawables(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set projection of the feature | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.gcs = function (val) { | |
| if (val === undefined) { | |
| return m_gcs; | |
| } else { | |
| m_gcs = val; | |
| m_this.modified(); | |
| return m_this; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set visibility of the feature | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.visible = function (val) { | |
| if (val === undefined) { | |
| return m_visible; | |
| } else { | |
| m_visible = val; | |
| m_this.modified(); | |
| // bind or unbind mouse handlers on visibility change | |
| if (m_visible) { | |
| m_this._bindMouseHandlers(); | |
| } else { | |
| m_this._unbindMouseHandlers(); | |
| } | |
| return m_this; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set bin of the feature | |
| * | |
| * Bin number is typically used for sorting the order of rendering | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.bin = function (val) { | |
| if (val === undefined) { | |
| return m_bin; | |
| } else { | |
| m_bin = val; | |
| m_this.modified(); | |
| return m_this; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set timestamp of data change | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.dataTime = function (val) { | |
| if (val === undefined) { | |
| return m_dataTime; | |
| } else { | |
| m_dataTime = val; | |
| m_this.modified(); | |
| return m_this; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set timestamp of last time build happened | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.buildTime = function (val) { | |
| if (val === undefined) { | |
| return m_buildTime; | |
| } else { | |
| m_buildTime = val; | |
| m_this.modified(); | |
| return m_this; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set timestamp of last time update happened | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.updateTime = function (val) { | |
| if (val === undefined) { | |
| return m_updateTime; | |
| } else { | |
| m_updateTime = val; | |
| m_this.modified(); | |
| return m_this; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set data | |
| * | |
| * @returns {Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.data = function (data) { | |
| if (data === undefined) { | |
| return m_this.style("data") || []; | |
| } else { | |
| m_this.style("data", data); | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| return m_this; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Query if the selection API is enabled for this feature. | |
| * @returns {bool} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.selectionAPI = function () { | |
| return m_selectionAPI; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| * | |
| * Derived class should implement this | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| if (!m_layer) { | |
| throw "Feature requires a valid layer"; | |
| } | |
| m_style = $.extend({}, | |
| {"opacity": 1.0}, arg.style === undefined ? {} : | |
| arg.style); | |
| m_this._bindMouseHandlers(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build | |
| * | |
| * Derived class should implement this | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get context specific drawables | |
| * | |
| * Derived class should implement this | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._drawables = function () { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update | |
| * | |
| * Derived class should implement this | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Destroy | |
| * | |
| * Derived class should implement this | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_this._unbindMouseHandlers(); | |
| m_selectedFeatures = []; | |
| m_style = {}; | |
| arg = {}; | |
| s_exit(); | |
| }; | |
| this._init(arg); | |
| return this; | |
| }; | |
| /** | |
| * The most recent feature event triggered. | |
| * @type {number} | |
| */ | |
| geo.feature.eventID = 0; | |
| /** | |
| * General object specification for feature types. | |
| * @typedef geo.feature.spec | |
| * @type {object} | |
| * @property {string} type A supported feature type. | |
| * @property {object[]} [data=[]] An array of arbitrary objects used to | |
| * construct the feature. These objects (and their associated | |
| * indices in the array) will be passed back to style and attribute | |
| * accessors provided by the user. In general the number of | |
| * "markers" drawn will be equal to the length of this array. | |
| */ | |
| /** | |
| * Create a feature from an object. The implementation here is | |
| * meant to define the general interface of creating features | |
| * from a javascript object. See documentation from individual | |
| * feature types for specific details. In case of an error in | |
| * the arguments this method will return null; | |
| * @param {geo.layer} layer The layer to add the feature to | |
| * @param {geo.feature.spec} [spec={}] The object specification | |
| * @returns {geo.feature|null} | |
| */ | |
| geo.feature.create = function (layer, spec) { | |
| "use strict"; | |
| var type = spec.type; | |
| // Check arguments | |
| if (!layer instanceof geo.layer) { | |
| console.warn("Invalid layer"); | |
| return null; | |
| } | |
| if (typeof spec !== "object") { | |
| console.warn("Invalid spec"); | |
| return null; | |
| } | |
| var feature = layer.createFeature(type); | |
| if (!feature) { | |
| console.warn("Could not create feature type '" + type + "'"); | |
| return null; | |
| } | |
| spec = spec || {}; | |
| spec.data = spec.data || []; | |
| return feature.style(spec); | |
| }; | |
| inherit(geo.feature, geo.sceneObject); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class pointFeature | |
| * | |
| * @class | |
| * @extends geo.feature | |
| * @returns {geo.pointFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.pointFeature = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.pointFeature)) { | |
| return new geo.pointFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.feature.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_init = this._init, | |
| m_rangeTree = null, | |
| m_rangeTreeTime = geo.timestamp(), | |
| s_data = this.data, | |
| m_maxRadius = 0; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set position | |
| * | |
| * @returns {geo.pointFeature} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.position = function (val) { | |
| if (val === undefined) { | |
| return m_this.style("position"); | |
| } else { | |
| m_this.style("position", val); | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update the current range tree object. Should be called whenever the | |
| * data changes. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._updateRangeTree = function () { | |
| if (m_rangeTreeTime.getMTime() >= m_this.dataTime().getMTime()) { | |
| return; | |
| } | |
| var pts, position, | |
| radius = m_this.style.get("radius"), | |
| stroke = m_this.style.get("stroke"), | |
| strokeWidth = m_this.style.get("strokeWidth"); | |
| position = m_this.position(); | |
| m_maxRadius = 0; | |
| // create an array of positions in geo coordinates | |
| pts = m_this.data().map(function (d, i) { | |
| var pt = position(d); | |
| pt.idx = i; | |
| // store the maximum point radius | |
| m_maxRadius = Math.max( | |
| m_maxRadius, | |
| radius(d, i) + (stroke(d, i) ? strokeWidth(d, i) : 0) | |
| ); | |
| return pt; | |
| }); | |
| m_rangeTree = new geo.util.RangeTree(pts); | |
| m_rangeTreeTime.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns an array of datum indices that contain the given point. | |
| * Largely adapted from wigglemaps pointQuerier: | |
| * | |
| * https://github.com/dotskapes/wigglemaps/blob/cf5bed3fbfe2c3e48d31799462a80c564be1fb60/src/query/PointQuerier.js | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.pointSearch = function (p) { | |
| var min, max, data, idx = [], box, found = [], ifound = [], map, pt, | |
| stroke = m_this.style.get("stroke"), | |
| strokeWidth = m_this.style.get("strokeWidth"), | |
| radius = m_this.style.get("radius"); | |
| if (!m_this.selectionAPI()) { | |
| return []; | |
| } | |
| data = m_this.data(); | |
| if (!data || !data.length) { | |
| return { | |
| found: [], | |
| index: [] | |
| }; | |
| } | |
| map = m_this.layer().map(); | |
| pt = map.gcsToDisplay(p); | |
| // Get the upper right corner in geo coordinates | |
| min = map.displayToGcs({ | |
| x: pt.x - m_maxRadius, | |
| y: pt.y + m_maxRadius // GCS coordinates are bottom to top | |
| }); | |
| // Get the lower left corner in geo coordinates | |
| max = map.displayToGcs({ | |
| x: pt.x + m_maxRadius, | |
| y: pt.y - m_maxRadius | |
| }); | |
| // Find points inside the bounding box | |
| box = new geo.util.Box(geo.util.vect(min.x, min.y), geo.util.vect(max.x, max.y)); | |
| m_this._updateRangeTree(); | |
| m_rangeTree.search(box).forEach(function (q) { | |
| idx.push(q.idx); | |
| }); | |
| // Filter by circular region | |
| idx.forEach(function (i) { | |
| var d = data[i], | |
| p = m_this.position()(d, i), | |
| dx, dy, rad; | |
| rad = radius(data[i], i); | |
| rad += stroke(data[i], i) ? strokeWidth(data[i], i) : 0; | |
| p = map.gcsToDisplay(p); | |
| dx = p.x - pt.x; | |
| dy = p.y - pt.y; | |
| if (Math.sqrt(dx * dx + dy * dy) <= rad) { | |
| found.push(d); | |
| ifound.push(i); | |
| } | |
| }); | |
| return { | |
| data: found, | |
| index: ifound | |
| }; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns an array of datum indices that are contained in the given box. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.boxSearch = function (lowerLeft, upperRight) { | |
| var pos = m_this.position(), | |
| idx = []; | |
| // TODO: use the range tree | |
| m_this.data().forEach(function (d, i) { | |
| var p = pos(d); | |
| if (p.x >= lowerLeft.x && | |
| p.x <= upperRight.x && | |
| p.y >= lowerLeft.y && | |
| p.y <= upperRight.y | |
| ) { | |
| idx.push(i); | |
| } | |
| }); | |
| return idx; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Overloaded data method that updates the internal range tree on write. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.data = function (data) { | |
| if (data === undefined) { | |
| return s_data(); | |
| } | |
| s_data(data); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns the bounding box for a given datum in screen coordinates as an | |
| * object: :: | |
| * | |
| * { | |
| * min: { | |
| * x: value, | |
| * y: value | |
| * }, | |
| * max: { | |
| * x: value, | |
| * y: value | |
| * } | |
| * } | |
| * | |
| * @returns {object} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._boundingBox = function (d) { | |
| var pt, radius; | |
| // get the position in geo coordinates | |
| pt = m_this.position()(d); | |
| // convert to screen coordinates | |
| pt = m_this.layer().map().gcsToDisplay(pt); | |
| // get the radius of the points (should we add stroke width?) | |
| radius = m_this.style().radius(d); | |
| return { | |
| min: { | |
| x: pt.x - radius, | |
| y: pt.y - radius | |
| }, | |
| max: { | |
| x: pt.x + radius, | |
| y: pt.y + radius | |
| } | |
| }; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| s_init.call(m_this, arg); | |
| var defaultStyle = $.extend( | |
| {}, | |
| { | |
| radius: 10.0, | |
| stroke: true, | |
| strokeColor: { r: 0.0, g: 1.0, b: 0.0 }, | |
| strokeWidth: 2.0, | |
| strokeOpacity: 1.0, | |
| fillColor: { r: 1.0, g: 0.0, b: 0.0 }, | |
| fill: true, | |
| fillOpacity: 1.0, | |
| sprites: false, | |
| sprites_image: null, | |
| position: function (d) { return d; } | |
| }, | |
| arg.style === undefined ? {} : arg.style | |
| ); | |
| if (arg.position !== undefined) { | |
| defaultStyle.position = arg.position; | |
| } | |
| m_this.style(defaultStyle); | |
| m_this.dataTime().modified(); | |
| }; | |
| return m_this; | |
| }; | |
| geo.event.pointFeature = $.extend({}, geo.event.feature); | |
| /** | |
| * Object specification for a point feature. | |
| * | |
| * @extends geo.feature.spec // need to make a jsdoc plugin for this to work | |
| * @typedef geo.pointFeature.spec | |
| * @type {object} | |
| */ | |
| /** | |
| * Create a pointFeature from an object. | |
| * @see {@link geo.feature.create} | |
| * @param {geo.layer} layer The layer to add the feature to | |
| * @param {geo.pointFeature.spec} spec The object specification | |
| * @returns {geo.pointFeature|null} | |
| */ | |
| geo.pointFeature.create = function (layer, renderer, spec) { | |
| "use strict"; | |
| spec.type = "point"; | |
| return geo.feature.create(layer, spec); | |
| }; | |
| inherit(geo.pointFeature, geo.feature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class lineFeature | |
| * | |
| * @class | |
| * @extends geo.feature | |
| * @returns {geo.lineFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.lineFeature = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.lineFeature)) { | |
| return new geo.lineFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.feature.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_init = this._init; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set line accessor | |
| * | |
| * @returns {geo.pointFeature} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.line = function (val) { | |
| if (val === undefined) { | |
| return m_this.style("line"); | |
| } else { | |
| m_this.style("line", val); | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set position accessor | |
| * | |
| * @returns {geo.pointFeature} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.position = function (val) { | |
| if (val === undefined) { | |
| return m_this.style("position"); | |
| } else { | |
| m_this.style("position", val); | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns an array of datum indices that contain the given point. | |
| * This is a slow implementation with runtime order of the number of | |
| * vertices. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.pointSearch = function (p) { | |
| var data, pt, map, line, width, indices = [], found = [], pos; | |
| data = m_this.data(); | |
| if (!data || !data.length) { | |
| return { | |
| found: [], | |
| index: [] | |
| }; | |
| } | |
| map = m_this.layer().map(); | |
| line = m_this.line(); | |
| width = m_this.style.get("strokeWidth"); | |
| pos = m_this.position(); | |
| pt = map.gcsToDisplay(p); | |
| // minimum l2 distance squared from | |
| // q -> line(u, v) | |
| function lineDist2(q, u, v) { | |
| var t, l2 = dist2(u, v); | |
| if (l2 < 1) { | |
| // u, v are within 1 pixel | |
| return dist2(q, u); | |
| } | |
| t = ((q.x - u.x) * (v.x - u.x) + (q.y - u.y) * (v.y - u.y)) / l2; | |
| if (t < 0) { return dist2(q, u); } | |
| if (t > 1) { return dist2(q, v); } | |
| return dist2( | |
| q, | |
| { | |
| x: u.x + t * (v.x - u.x), | |
| y: u.y + t * (v.y - u.y) | |
| } | |
| ); | |
| } | |
| // l2 distance squared from u to v | |
| function dist2(u, v) { | |
| var dx = u.x - v.x, | |
| dy = u.y - v.y; | |
| return dx * dx + dy * dy; | |
| } | |
| // for each line | |
| data.forEach(function (d, index) { | |
| var last = null; | |
| try { | |
| line(d, index).forEach(function (current, j) { | |
| // get the screen coordinates of the current point | |
| var p = pos(current, j, d, index); | |
| var s = map.gcsToDisplay(p); | |
| var r = Math.ceil(width(p, j, d, index) / 2) + 2; | |
| r = r * r; | |
| if (last) { | |
| // test the line segment s -> last | |
| if (lineDist2(pt, s, last) <= r) { | |
| // short circuit the loop here | |
| throw "found"; | |
| } | |
| } | |
| last = s; | |
| }); | |
| } catch (err) { | |
| if (err !== "found") { | |
| throw err; | |
| } | |
| found.push(d); | |
| indices.push(index); | |
| } | |
| }); | |
| return { | |
| data: found, | |
| index: indices | |
| }; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns an array of line indices that are contained in the given box. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.boxSearch = function (lowerLeft, upperRight, opts) { | |
| var pos = m_this.position(), | |
| idx = [], | |
| line = m_this.line(); | |
| opts = opts || {}; | |
| opts.partial = opts.partial || false; | |
| if (opts.partial) { | |
| throw "Unimplemented query method."; | |
| } | |
| m_this.data().forEach(function (d, i) { | |
| var inside = true; | |
| line(d, i).forEach(function (e, j) { | |
| if (!inside) { return; } | |
| var p = pos(e, j, d, i); | |
| if (!(p.x >= lowerLeft.x && | |
| p.x <= upperRight.x && | |
| p.y >= lowerLeft.y && | |
| p.y <= upperRight.y) | |
| ) { | |
| inside = false; | |
| } | |
| }); | |
| if (inside) { | |
| idx.push(i); | |
| } | |
| }); | |
| return idx; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| s_init.call(m_this, arg); | |
| var defaultStyle = $.extend( | |
| {}, | |
| { | |
| "strokeWidth": 1.0, | |
| // Default to gold color for lines | |
| "strokeColor": { r: 1.0, g: 0.8431372549, b: 0.0 }, | |
| "strokeStyle": "solid", | |
| "strokeOpacity": 1.0, | |
| "line": function (d) { return d; }, | |
| "position": function (d) { return d; } | |
| }, | |
| arg.style === undefined ? {} : arg.style | |
| ); | |
| if (arg.line !== undefined) { | |
| defaultStyle.line = arg.line; | |
| } | |
| if (arg.position !== undefined) { | |
| defaultStyle.position = arg.position; | |
| } | |
| m_this.style(defaultStyle); | |
| m_this.dataTime().modified(); | |
| }; | |
| this._init(arg); | |
| return this; | |
| }; | |
| /** | |
| * Create a lineFeature from an object. | |
| * @see {@link geo.feature.create} | |
| * @param {geo.layer} layer The layer to add the feature to | |
| * @param {geo.lineFeature.spec} spec The object specification | |
| * @returns {geo.lineFeature|null} | |
| */ | |
| geo.lineFeature.create = function (layer, spec) { | |
| "use strict"; | |
| spec.type = "line"; | |
| return geo.feature.create(layer, spec); | |
| }; | |
| inherit(geo.lineFeature, geo.feature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class pathFeature | |
| * | |
| * @class | |
| * @extends geo.feature | |
| * @returns {geo.pathFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.pathFeature = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.pathFeature)) { | |
| return new geo.pathFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.feature.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| m_position = arg.position === undefined ? [] : arg.position, | |
| s_init = this._init; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set positions | |
| * | |
| * @returns {geo.pathFeature} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.position = function (val) { | |
| if (val === undefined) { | |
| return m_position; | |
| } | |
| // Copy incoming array of positions | |
| m_position = val; | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| s_init.call(m_this, arg); | |
| var defaultStyle = $.extend( | |
| {}, | |
| { | |
| "strokeWidth": function () { return 1; }, | |
| "strokeColor": function () { return { r: 1.0, g: 1.0, b: 1.0 }; } | |
| }, | |
| arg.style === undefined ? {} : arg.style | |
| ); | |
| m_this.style(defaultStyle); | |
| if (m_position) { | |
| m_this.dataTime().modified(); | |
| } | |
| }; | |
| this._init(arg); | |
| return this; | |
| }; | |
| inherit(geo.pathFeature, geo.feature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class polygonFeature | |
| * | |
| * @class | |
| * @extends geo.feature | |
| * @returns {geo.polygonFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.polygonFeature = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.polygonFeature)) { | |
| return new geo.polygonFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.feature.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| m_position, | |
| m_polygon, | |
| s_init = this._init, | |
| s_data = this.data, | |
| m_coordinates = {outer: [], inner: []}; | |
| if (arg.line === undefined) { | |
| m_polygon = function (d) { | |
| return d; | |
| }; | |
| } else { | |
| m_polygon = arg.polygon; | |
| } | |
| if (arg.position === undefined) { | |
| m_position = function (d) { | |
| return d; | |
| }; | |
| } else { | |
| m_position = arg.position; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Override the parent data method to keep track of changes to the | |
| * internal coordinates. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.data = function (arg) { | |
| var ret = s_data(arg); | |
| if (arg !== undefined) { | |
| getCoordinates(); | |
| } | |
| return ret; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the internal coordinates whenever the data changes. For now, we do | |
| * the computation in world coordinates, but we will need to work in GCS | |
| * for other projections. | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function getCoordinates() { | |
| var posFunc = m_this.position(), | |
| polyFunc = m_this.polygon(); | |
| m_coordinates = m_this.data().map(function (d, i) { | |
| var poly = polyFunc(d); | |
| var outer, inner; | |
| outer = (poly.outer || []).map(function (d0, j) { | |
| return posFunc.call(m_this, d0, j, d, i); | |
| }); | |
| inner = (poly.inner || []).map(function (hole) { | |
| return (hole || []).map(function (d0, k) { | |
| return posFunc.call(m_this, d0, k, d, i); | |
| }); | |
| }); | |
| return { | |
| outer: outer, | |
| inner: inner | |
| }; | |
| }); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set line accessor | |
| * | |
| * @returns {geo.pointFeature} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.polygon = function (val) { | |
| if (val === undefined) { | |
| return m_polygon; | |
| } else { | |
| m_polygon = val; | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| getCoordinates(); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set position accessor | |
| * | |
| * @returns {geo.pointFeature} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.position = function (val) { | |
| if (val === undefined) { | |
| return m_position; | |
| } else { | |
| m_position = val; | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| getCoordinates(); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Point searce method for selection api. Returns markers containing the | |
| * given point. | |
| * @argument {Object} coordinate | |
| * @returns {Object} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.pointSearch = function (coordinate) { | |
| var found = [], indices = [], data = m_this.data(); | |
| m_coordinates.forEach(function (coord, i) { | |
| var inside = geo.util.pointInPolygon( | |
| coordinate, | |
| coord.outer, | |
| coord.inner | |
| ); | |
| if (inside) { | |
| indices.push(i); | |
| found.push(data[i]); | |
| } | |
| }); | |
| return { | |
| index: indices, | |
| found: found | |
| }; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| s_init.call(m_this, arg); | |
| var defaultStyle = $.extend( | |
| {}, | |
| { | |
| "fillColor": { r: 0.0, g: 0.5, b: 0.5 }, | |
| "fillOpacity": 1.0 | |
| }, | |
| arg.style === undefined ? {} : arg.style | |
| ); | |
| m_this.style(defaultStyle); | |
| if (m_position) { | |
| m_this.dataTime().modified(); | |
| } | |
| }; | |
| this._init(arg); | |
| return this; | |
| }; | |
| inherit(geo.polygonFeature, geo.feature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class planeFeature | |
| * | |
| * @class | |
| * @extends geo.polygonFeature | |
| * @returns {geo.planeFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.planeFeature = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.planeFeature)) { | |
| return new geo.planeFeature(arg); | |
| } | |
| arg = arg || {}; | |
| // Defaults | |
| arg.ul = arg.ul === undefined ? [0.0, 1.0, 0.0] : arg.ul; | |
| arg.lr = arg.lr === undefined ? [1.0, 0.0, 0.0] : arg.lr; | |
| arg.depth = arg.depth === undefined ? 0.0 : arg.depth; | |
| geo.polygonFeature.call(this, arg); | |
| var m_this = this, | |
| m_origin = [arg.ul.x, arg.lr.y, arg.depth], | |
| m_upperLeft = [arg.ul.x, arg.ul.y, arg.depth], | |
| m_lowerRight = [arg.lr.x, arg.lr.y, arg.depth], | |
| m_defaultDepth = arg.depth, | |
| m_drawOnAsyncResourceLoad = arg.drawOnAsyncResourceLoad === undefined ? | |
| true : false, | |
| s_init = this._init; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set origin | |
| * | |
| * @returns {geo.planeFeature} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.origin = function (val) { | |
| if (val === undefined) { | |
| return m_origin; | |
| } else if (val instanceof Array) { | |
| if (val.length > 3 || val.length < 2) { | |
| throw "Upper left point requires point in 2 or 3 dimension"; | |
| } | |
| m_origin = val.slice(0); | |
| if (m_origin.length === 2) { | |
| m_origin[2] = m_defaultDepth; | |
| } | |
| } else if (val instanceof geo.latlng) { | |
| m_origin = [val.x(), val.y(), m_defaultDepth]; | |
| } | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set pt1 | |
| * | |
| * @returns {geo.planeFeature} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.upperLeft = function (val) { | |
| if (val === undefined) { | |
| return m_upperLeft; | |
| } else if (val instanceof Array) { | |
| if (val.length > 3 || val.length < 2) { | |
| throw "Upper left point requires point in 2 or 3 dimension"; | |
| } | |
| m_upperLeft = val.slice(0); | |
| if (m_upperLeft.length === 2) { | |
| m_upperLeft[2] = m_defaultDepth; | |
| } | |
| } else if (val instanceof geo.latlng) { | |
| m_upperLeft = [val.x(), val.y(), m_defaultDepth]; | |
| } | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set origin | |
| * | |
| * @returns {geo.planeFeature} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.lowerRight = function (val) { | |
| if (val === undefined) { | |
| return m_lowerRight; | |
| } else if (val instanceof Array) { | |
| if (val.length > 3 || val.length < 2) { | |
| throw "Upper left point requires point in 2 or 3 dimension"; | |
| } | |
| m_lowerRight = val.slice(0); | |
| if (m_lowerRight.length === 2) { | |
| m_lowerRight[2] = m_defaultDepth; | |
| } | |
| m_this.dataTime().modified(); | |
| } else if (val instanceof geo.latlng) { | |
| m_lowerRight = [val.x(), val.y(), m_defaultDepth]; | |
| } | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set if draw should happen as soon as a async resource is loaded | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.drawOnAsyncResourceLoad = function (val) { | |
| if (val === undefined) { | |
| return m_drawOnAsyncResourceLoad; | |
| } else { | |
| m_drawOnAsyncResourceLoad = val; | |
| return m_this; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| var style = null; | |
| s_init.call(m_this, arg); | |
| style = m_this.style(); | |
| if (style.image === undefined) { | |
| style.image = null; | |
| } | |
| m_this.style(style); | |
| }; | |
| this._init(arg); | |
| return this; | |
| }; | |
| inherit(geo.planeFeature, geo.polygonFeature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class vectorFeature | |
| * | |
| * @class | |
| * @extends geo.feature | |
| * @returns {geo.vectorFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.vectorFeature = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.vectorFeature)) { | |
| return new geo.vectorFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.feature.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_init = this._init, | |
| s_style = this.style; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the accessor for the origin of the vector. This is the point | |
| * that the vector base resides at. Defaults to (0, 0, 0). | |
| * @param {geo.accessor|geo.geoPosition} [accessor] The origin accessor | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.origin = function (val) { | |
| if (val === undefined) { | |
| return s_style('origin'); | |
| } else { | |
| s_style('origin', val); | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the accessor for the displacement (coordinates) of the vector. | |
| * @param {geo.accessor|geo.geoPosition} [accessor] The accessor | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.delta = function (val) { | |
| if (val === undefined) { | |
| return s_style('delta'); | |
| } else { | |
| s_style('delta', val); | |
| m_this.dataTime().modified(); | |
| m_this.modified(); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| * @protected | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| s_init.call(m_this, arg); | |
| var defaultStyle = $.extend( | |
| {}, | |
| { | |
| strokeColor: 'black', | |
| strokeWidth: 2.0, | |
| strokeOpacity: 1.0, | |
| // TODO: define styles for the end markers | |
| // originStyle: 'none', | |
| // endStyle: 'arrow', | |
| origin: {x: 0, y: 0, z: 0}, | |
| delta: function (d) { return d; }, | |
| scale: null // size scaling factor (null -> renderer decides) | |
| }, | |
| arg.style === undefined ? {} : arg.style | |
| ); | |
| if (arg.origin !== undefined) { | |
| defaultStyle.origin = arg.origin; | |
| } | |
| m_this.style(defaultStyle); | |
| m_this.dataTime().modified(); | |
| }; | |
| }; | |
| inherit(geo.vectorFeature, geo.feature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class geomFeature | |
| * | |
| * @class | |
| * @extends geo.feature | |
| * @returns {geo.geomFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.geomFeature = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.geomFeature)) { | |
| return new geo.geomFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.feature.call(this, arg); | |
| arg.style = arg.style === undefined ? $.extend({}, { | |
| "color": [1.0, 1.0, 1.0], | |
| "point_sprites": false, | |
| "point_sprites_image": null | |
| }, arg.style) : arg.style; | |
| // Update style | |
| this.style(arg.style); | |
| return this; | |
| }; | |
| inherit(geo.geomFeature, geo.feature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class graphFeature | |
| * | |
| * @class | |
| * @extends geo.feature | |
| * @returns {geo.graphFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.graphFeature = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.graphFeature)) { | |
| return new geo.graphFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.feature.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_draw = this.draw, | |
| s_style = this.style, | |
| m_nodes = null, | |
| m_points = null, | |
| m_children = function (d) { return d.children; }, | |
| m_links = [], | |
| s_init = this._init, | |
| s_exit = this._exit; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| s_init.call(m_this, arg); | |
| var defaultStyle = $.extend(true, {}, | |
| { | |
| nodes: { | |
| radius: 5.0, | |
| fill: true, | |
| fillColor: { r: 1.0, g: 0.0, b: 0.0 }, | |
| strokeColor: { r: 0, g: 0, b: 0 } | |
| }, | |
| links: { | |
| strokeColor: { r: 0.0, g: 0.0, b: 0.0 } | |
| }, | |
| linkType: "path" /* 'path' || 'line' */ | |
| }, | |
| arg.style === undefined ? {} : arg.style | |
| ); | |
| m_this.style(defaultStyle); | |
| m_this.nodes(function (d) { return d; }); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Call child _build methods | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| m_this.children().forEach(function (child) { | |
| child._build(); | |
| }); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Call child _update methods | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| m_this.children().forEach(function (child) { | |
| child._update(); | |
| }); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Custom _exit method to remove all sub-features | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_this.data([]); | |
| m_links.forEach(function (link) { | |
| link._exit(); | |
| m_this.removeChild(link); | |
| }); | |
| m_links = []; | |
| m_points._exit(); | |
| m_this.removeChild(m_points); | |
| s_exit(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set style | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.style = function (arg, arg2) { | |
| var out = s_style.call(m_this, arg, arg2); | |
| if (out !== m_this) { | |
| return out; | |
| } | |
| // set styles for sub-features | |
| m_points.style(arg.nodes); | |
| m_links.forEach(function (l) { | |
| l.style(arg.links); | |
| }); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set links accessor. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.links = function (arg) { | |
| if (arg === undefined) { | |
| return m_children; | |
| } | |
| m_children = geo.util.ensureFunction(arg); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set nodes | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.nodes = function (val) { | |
| if (val === undefined) { | |
| return m_nodes; | |
| } | |
| m_nodes = val; | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get internal node feature | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.nodeFeature = function () { | |
| return m_points; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get internal link features | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.linkFeatures = function () { | |
| return m_links; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build the feature for drawing | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.draw = function () { | |
| var layer = m_this.layer(), | |
| data = m_this.data(), | |
| nLinks = 0, | |
| style; | |
| // get the feature style object | |
| style = m_this.style(); | |
| // Bind data to the point nodes | |
| m_points.data(data); | |
| m_points.style(style.nodes); | |
| // get links from node connections | |
| data.forEach(function (source) { | |
| (source.children || []).forEach(function (target) { | |
| var link; | |
| nLinks += 1; | |
| if (m_links.length < nLinks) { | |
| link = geo.createFeature( | |
| style.linkType, layer, layer.renderer() | |
| ).style(style.links); | |
| m_this.addChild(link); | |
| m_links.push(link); | |
| } | |
| m_links[nLinks - 1].data([source, target]); | |
| }); | |
| }); | |
| m_links.splice(nLinks, m_links.length - nLinks).forEach(function (l) { | |
| l._exit(); | |
| m_this.removeChild(l); | |
| }); | |
| s_draw(); | |
| return m_this; | |
| }; | |
| m_points = geo.createFeature( | |
| "point", | |
| this.layer(), | |
| this.layer().renderer() | |
| ); | |
| m_this.addChild(m_points); | |
| if (arg.nodes) { | |
| this.nodes(arg.nodes); | |
| } | |
| this._init(arg); | |
| return this; | |
| }; | |
| inherit(geo.graphFeature, geo.feature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform geometric data of a feature from source projection to destination | |
| * projection. | |
| * | |
| * @namespace | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.transform = {}; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Custom transform for a feature used for OpenStreetMap | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.transform.osmTransformFeature = function (destGcs, feature, inplace) { | |
| /// TODO | |
| /// Currently we make assumption that incoming feature is in 4326 | |
| /// which may not be true. | |
| "use strict"; | |
| if (!feature) { | |
| console.log("[warning] Invalid (null) feature"); | |
| return; | |
| } | |
| if (feature.gcs() === destGcs) { | |
| return; | |
| } | |
| if (!(feature instanceof geo.pointFeature || | |
| feature instanceof geo.lineFeature)) { | |
| throw "Supports only point or line feature"; | |
| } | |
| var noOfComponents = null, | |
| pointOffset = 0, | |
| count = null, | |
| inPos = null, | |
| outPos = null, | |
| srcGcs = feature.gcs(), | |
| i, | |
| yCoord; | |
| inplace = !!inplace; | |
| if (feature instanceof geo.pointFeature || | |
| feature instanceof geo.lineFeature) { | |
| /// If source GCS is not in 4326, transform it first into 4326 | |
| /// before we transform it for OSM. | |
| if (srcGcs !== "EPSG:4326") { | |
| geo.transform.transformFeature("EPSG:4326", feature, true); | |
| } | |
| inPos = feature.positions(); | |
| count = inPos.length; | |
| if (!(inPos instanceof Array)) { | |
| throw "Supports Array of 2D and 3D points"; | |
| } | |
| if (inPos.length > 0 && inPos[0] instanceof geo.latlng) { | |
| noOfComponents = 2; | |
| pointOffset = 1; | |
| } else { | |
| noOfComponents = (count % 2 === 0 ? 2 : | |
| (count % 3 === 0 ? 3 : null)); | |
| pointOffset = noOfComponents; | |
| } | |
| if (noOfComponents !== 2 && noOfComponents !== 3) { | |
| throw "Transform points require points in 2D or 3D"; | |
| } | |
| if (inplace) { | |
| outPos = inPos; | |
| } else { | |
| outPos = inPos.slice(0); | |
| } | |
| for (i = 0; i < count; i += pointOffset) { | |
| /// Y goes from 0 (top edge is 85.0511 °N) to 2zoom − 1 | |
| /// (bottom edge is 85.0511 °S) in a Mercator projection. | |
| if (inPos[i] instanceof geo.latlng) { | |
| yCoord = inPos[i].lat(); | |
| } else { | |
| yCoord = inPos[i + 1]; | |
| } | |
| if (yCoord > 85.0511) { | |
| yCoord = 85.0511; | |
| } | |
| if (yCoord < -85.0511) { | |
| yCoord = -85.0511; | |
| } | |
| if (inPos[i] instanceof geo.latlng) { | |
| outPos[i] = geo.latlng(geo.mercator.lat2y(yCoord), outPos[i].lng()); | |
| } else { | |
| outPos[i + 1] = geo.mercator.lat2y(yCoord); | |
| } | |
| } | |
| if (inplace) { | |
| feature.positions(outPos); | |
| feature.gcs(destGcs); | |
| } | |
| return outPos; | |
| } | |
| return null; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform a feature to destination GCS | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.transform.transformFeature = function (destGcs, feature, inplace) { | |
| "use strict"; | |
| if (!feature) { | |
| throw "Invalid (null) feature"; | |
| } | |
| if (!(feature instanceof geo.pointFeature || | |
| feature instanceof geo.lineFeature)) { | |
| throw "Supports only point or line feature"; | |
| } | |
| if (feature.gcs() === destGcs) { | |
| return feature.positions(); | |
| } | |
| if (destGcs === "EPSG:3857") { | |
| return geo.transform.osmTransformFeature(destGcs, feature, inplace); | |
| } | |
| var noOfComponents = null, | |
| pointOffset = 0, | |
| count = null, | |
| inPos = null, | |
| outPos = null, | |
| projPoint = null, | |
| srcGcs = feature.gcs(), | |
| i, | |
| projSrcGcs = new proj4.Proj(srcGcs), | |
| projDestGcs = new proj4.Proj(destGcs); | |
| inplace = !!inplace; | |
| if (feature instanceof geo.pointFeature || | |
| feature instanceof geo.lineFeature) { | |
| inPos = feature.positions(); | |
| count = inPos.length; | |
| if (!(inPos instanceof Array)) { | |
| throw "Supports Array of 2D and 3D points"; | |
| } | |
| if (inPos.length > 0 && inPos[0] instanceof geo.latlng) { | |
| noOfComponents = 2; | |
| pointOffset = 1; | |
| } else { | |
| noOfComponents = (count % 2 === 0 ? 2 : | |
| (count % 3 === 0 ? 3 : null)); | |
| pointOffset = noOfComponents; | |
| } | |
| if (noOfComponents !== 2 && noOfComponents !== 3) { | |
| throw "Transform points require points in 2D or 3D"; | |
| } | |
| if (inplace) { | |
| outPos = inPos; | |
| } else { | |
| outPos = []; | |
| outPos.length = inPos.length; | |
| } | |
| for (i = 0; i < count; i += pointOffset) { | |
| if (noOfComponents === 2) { | |
| projPoint = new proj4.Point(inPos[i], inPos[i + 1], 0.0); | |
| } else { | |
| projPoint = new proj4.Point(inPos[i], inPos[i + 1], inPos[i + 2]); | |
| } | |
| proj4.transform(projSrcGcs, projDestGcs, projPoint); | |
| if (noOfComponents === 2) { | |
| outPos[i] = projPoint.x; | |
| outPos[i + 1] = projPoint.y; | |
| } else { | |
| outPos[i] = projPoint.x; | |
| outPos[i + 1] = projPoint.y; | |
| outPos[i + 2] = projPoint.z; | |
| } | |
| } | |
| if (inplace) { | |
| feature.positions(outPos); | |
| feature.gcs(destGcs); | |
| } | |
| return outPos; | |
| } | |
| return null; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform geometric data of a layer from source projection to destination | |
| * projection. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.transform.transformLayer = function (destGcs, layer, baseLayer) { | |
| "use strict"; | |
| var features, count, i; | |
| if (!layer) { | |
| throw "Requires valid layer for tranformation"; | |
| } | |
| if (!baseLayer) { | |
| throw "Requires baseLayer used by the map"; | |
| } | |
| if (layer === baseLayer) { | |
| return; | |
| } | |
| if (layer instanceof geo.featureLayer) { | |
| features = layer.features(); | |
| count = features.length; | |
| i = 0; | |
| for (i = 0; i < count; i += 1) { | |
| if (destGcs === "EPSG:3857" && baseLayer instanceof geo.osmLayer) { | |
| geo.transform.osmTransformFeature( | |
| destGcs, features[i], true); | |
| } else { | |
| geo.transform.transformFeature( | |
| destGcs, features[i], true); | |
| } | |
| } | |
| layer.gcs(destGcs); | |
| } else { | |
| throw "Only feature layer transformation is supported"; | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform position coordinates from source projection to destination | |
| * projection. | |
| * | |
| * @param {string} srcGcs GCS of the coordinates | |
| * @param {string} destGcs Desired GCS of the transformed coordinates | |
| * @param {object} coordinates | |
| * @return {geo.latlng|geo.latlng[]} Transformed coordinates | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.transform.transformCoordinates = function (srcGcs, destGcs, coordinates, | |
| numberOfComponents) { | |
| "use strict"; | |
| var i, count, offset, xCoord, yCoord, zCoord, xAcc, | |
| yAcc, zAcc, writer, output, projPoint, | |
| projSrcGcs = new proj4.Proj(srcGcs), | |
| projDestGcs = new proj4.Proj(destGcs); | |
| /// Default Z accessor | |
| zAcc = function () { | |
| return 0.0; | |
| }; | |
| if (destGcs === srcGcs) { | |
| return coordinates; | |
| } | |
| /// TODO: Can we check for EPSG code? | |
| if (!destGcs || !srcGcs) { | |
| throw "Invalid source or destination GCS"; | |
| } | |
| /// Helper methods | |
| function handleLatLngCoordinates() { | |
| if (coordinates[0] && coordinates[0] instanceof geo.latlng) { | |
| xAcc = function (index) { | |
| return coordinates[index].x(); | |
| }; | |
| yAcc = function (index) { | |
| return coordinates[index].y(); | |
| }; | |
| writer = function (index, x, y) { | |
| output[index] = geo.latlng(y, x); | |
| }; | |
| } else { | |
| xAcc = function () { | |
| return coordinates.x(); | |
| }; | |
| yAcc = function () { | |
| return coordinates.y(); | |
| }; | |
| writer = function (index, x, y) { | |
| output = geo.latlng(y, x); | |
| }; | |
| } | |
| } | |
| /// Helper methods | |
| function handleArrayCoordinates() { | |
| if (coordinates[0] instanceof Array) { | |
| if (coordinates[0].length === 2) { | |
| xAcc = function (index) { | |
| return coordinates[index][0]; | |
| }; | |
| yAcc = function (index) { | |
| return coordinates[index][1]; | |
| }; | |
| writer = function (index, x, y) { | |
| output[index] = [x, y]; | |
| }; | |
| } else if (coordinates[0].length === 3) { | |
| xAcc = function (index) { | |
| return coordinates[index][0]; | |
| }; | |
| yAcc = function (index) { | |
| return coordinates[index][1]; | |
| }; | |
| zAcc = function (index) { | |
| return coordinates[index][2]; | |
| }; | |
| writer = function (index, x, y, z) { | |
| output[index] = [x, y, z]; | |
| }; | |
| } else { | |
| throw "Invalid coordinates. Requires two or three components per array"; | |
| } | |
| } else { | |
| if (coordinates.length === 2) { | |
| offset = 2; | |
| xAcc = function (index) { | |
| return coordinates[index * offset]; | |
| }; | |
| yAcc = function (index) { | |
| return coordinates[index * offset + 1]; | |
| }; | |
| writer = function (index, x, y) { | |
| output[index] = x; | |
| output[index + 1] = y; | |
| }; | |
| } else if (coordinates.length === 3) { | |
| offset = 3; | |
| xAcc = function (index) { | |
| return coordinates[index * offset]; | |
| }; | |
| yAcc = function (index) { | |
| return coordinates[index * offset + 1]; | |
| }; | |
| zAcc = function (index) { | |
| return coordinates[index * offset + 2]; | |
| }; | |
| writer = function (index, x, y, z) { | |
| output[index] = x; | |
| output[index + 1] = y; | |
| output[index + 2] = z; | |
| }; | |
| } else if (numberOfComponents) { | |
| if (numberOfComponents === 2 || numberOfComponents || 3) { | |
| offset = numberOfComponents; | |
| xAcc = function (index) { | |
| return coordinates[index]; | |
| }; | |
| yAcc = function (index) { | |
| return coordinates[index + 1]; | |
| }; | |
| if (numberOfComponents === 2) { | |
| writer = function (index, x, y) { | |
| output[index] = x; | |
| output[index + 1] = y; | |
| }; | |
| } else { | |
| zAcc = function (index) { | |
| return coordinates[index + 2]; | |
| }; | |
| writer = function (index, x, y, z) { | |
| output[index] = x; | |
| output[index + 1] = y; | |
| output[index + 2] = z; | |
| }; | |
| } | |
| } else { | |
| throw "Number of components should be two or three"; | |
| } | |
| } else { | |
| throw "Invalid coordinates"; | |
| } | |
| } | |
| } | |
| /// Helper methods | |
| function handleObjectCoordinates() { | |
| if (coordinates[0] && | |
| "x" in coordinates[0] && | |
| "y" in coordinates[0]) { | |
| xAcc = function (index) { | |
| return coordinates[index].x; | |
| }; | |
| yAcc = function (index) { | |
| return coordinates[index].y; | |
| }; | |
| if ("z" in coordinates[0]) { | |
| zAcc = function (index) { | |
| return coordinates[index].z; | |
| }; | |
| writer = function (index, x, y, z) { | |
| output[i] = {x: x, y: y, z: z}; | |
| }; | |
| } else { | |
| writer = function (index, x, y) { | |
| output[index] = {x: x, y: y}; | |
| }; | |
| } | |
| } else if (coordinates && | |
| "x" in coordinates && "y" in coordinates) { | |
| xAcc = function () { | |
| return coordinates.x; | |
| }; | |
| yAcc = function () { | |
| return coordinates.y; | |
| }; | |
| if ("z" in coordinates) { | |
| zAcc = function () { | |
| return coordinates.z; | |
| }; | |
| writer = function (index, x, y, z) { | |
| output = {x: x, y: y, z: z}; | |
| }; | |
| } else { | |
| writer = function (index, x, y) { | |
| output = {x: x, y: y}; | |
| }; | |
| } | |
| } else { | |
| throw "Invalid coordinates"; | |
| } | |
| } | |
| if (coordinates instanceof Array) { | |
| output = []; | |
| output.length = coordinates.length; | |
| count = coordinates.length; | |
| if (coordinates[0] instanceof Array || | |
| coordinates[0] instanceof geo.latlng || | |
| coordinates[0] instanceof Object) { | |
| offset = 1; | |
| if (coordinates[0] instanceof Array) { | |
| handleArrayCoordinates(); | |
| } else if (coordinates[0] instanceof geo.latlng) { | |
| handleLatLngCoordinates(); | |
| } else if (coordinates[0] instanceof Object) { | |
| handleObjectCoordinates(); | |
| } | |
| } else { | |
| handleArrayCoordinates(); | |
| } | |
| } else if (coordinates && coordinates instanceof Object) { | |
| count = 1; | |
| offset = 1; | |
| if (coordinates instanceof geo.latlng) { | |
| handleLatLngCoordinates(); | |
| } else if (coordinates && "x" in coordinates && "y" in coordinates) { | |
| handleObjectCoordinates(); | |
| } else { | |
| throw "Coordinates are not valid"; | |
| } | |
| } | |
| if (destGcs === "EPSG:3857" && srcGcs === "EPSG:4326") { | |
| for (i = 0; i < count; i += offset) { | |
| /// Y goes from 0 (top edge is 85.0511 °N) to 2zoom − 1 | |
| /// (bottom edge is 85.0511 °S) in a Mercator projection. | |
| xCoord = xAcc(i); | |
| yCoord = yAcc(i); | |
| zCoord = zAcc(i); | |
| if (yCoord > 85.0511) { | |
| yCoord = 85.0511; | |
| } | |
| if (yCoord < -85.0511) { | |
| yCoord = -85.0511; | |
| } | |
| writer(i, xCoord, geo.mercator.lat2y(yCoord), zCoord); | |
| } | |
| return output; | |
| } else { | |
| for (i = 0; i < count; i += offset) { | |
| projPoint = new proj4.Point(xAcc(i), yAcc(i), zAcc(i)); | |
| proj4.transform(projSrcGcs, projDestGcs, projPoint); | |
| writer(i, projPoint.x, projPoint.y, projPoint.z); | |
| return output; | |
| } | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class renderer | |
| * | |
| * @class | |
| * @extends geo.object | |
| * @returns {geo.renderer} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.renderer = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.renderer)) { | |
| return new geo.renderer(arg); | |
| } | |
| geo.object.call(this); | |
| arg = arg || {}; | |
| var m_this = this, | |
| m_layer = arg.layer === undefined ? null : arg.layer, | |
| m_canvas = arg.canvas === undefined ? null : arg.canvas, | |
| m_initialized = false; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get layer of the renderer | |
| * | |
| * @returns {*} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.layer = function () { | |
| return m_layer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get canvas for the renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.canvas = function (val) { | |
| if (val === undefined) { | |
| return m_canvas; | |
| } else { | |
| m_canvas = val; | |
| m_this.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get map that this renderer belongs to | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.map = function () { | |
| if (m_layer) { | |
| return m_layer.map(); | |
| } else { | |
| return null; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get base layer that belongs to this renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.baseLayer = function () { | |
| if (m_this.map()) { | |
| return m_this.map().baseLayer(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set if renderer has been initialized | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.initialized = function (val) { | |
| if (val === undefined) { | |
| return m_initialized; | |
| } else { | |
| m_initialized = val; | |
| return m_this; | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get render API used by the renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.api = function () { | |
| throw "Should be implemented by derivied classes"; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Reset to default | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.reset = function () { | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert array of points from world to GCS coordinate space | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.worldToGcs = function () { | |
| throw "Should be implemented by derivied classes"; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert array of points from display to GCS space | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.displayToGcs = function () { | |
| throw "Should be implemented by derivied classes"; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert array of points from display to GCS space | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.gcsToDisplay = function () { | |
| throw "Should be implemented by derivied classes"; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert array of points from world to display space | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.worldToDisplay = function () { | |
| throw "Should be implemented by derivied classes"; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert array of points from display to world space | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.displayToWorld = function () { | |
| throw "Should be implemented by derivied classes"; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get mouse coodinates related to canvas | |
| * | |
| * @param {object} event | |
| * @returns {object} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.relMouseCoords = function (event) { | |
| var totalOffsetX = 0, | |
| totalOffsetY = 0, | |
| canvasX = 0, | |
| canvasY = 0, | |
| currentElement = m_this.canvas(); | |
| do { | |
| totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft; | |
| totalOffsetY += currentElement.offsetTop - currentElement.scrollTop; | |
| currentElement = currentElement.offsetParent; | |
| } while (currentElement); | |
| canvasX = event.pageX - totalOffsetX; | |
| canvasY = event.pageY - totalOffsetY; | |
| return { | |
| x: canvasX, | |
| y: canvasY | |
| }; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function () { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle resize event | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._resize = function () { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Render | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._render = function () { | |
| }; | |
| return this; | |
| }; | |
| inherit(geo.renderer, geo.object); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of osmLayer | |
| * | |
| * @class | |
| * @extends geo.featureLayer | |
| * | |
| * @param {Object} arg - arg can contain following keys: baseUrl, | |
| * imageFormat (such as png or jpeg), and displayLast | |
| * (to decide whether or not render tiles from last zoom level). | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.osmLayer = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.osmLayer)) { | |
| return new geo.osmLayer(arg); | |
| } | |
| geo.featureLayer.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Private member variables | |
| * | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_exit = this._exit, | |
| m_tiles = {}, | |
| m_hiddenBinNumber = -1, | |
| m_lastVisibleBinNumber = -1, | |
| m_visibleBinNumber = 1000, | |
| m_pendingNewTiles = [], | |
| m_pendingInactiveTiles = [], | |
| m_numberOfCachedTiles = 0, | |
| m_tileCacheSize = 100, | |
| m_baseUrl = "http://tile.openstreetmap.org/", | |
| m_mapOpacity = 1.0, | |
| m_imageFormat = "png", | |
| m_updateTimerId = null, | |
| m_lastVisibleZoom = null, | |
| m_visibleTilesRange = {}, | |
| s_init = this._init, | |
| m_pendingNewTilesStat = {}, | |
| s_update = this._update, | |
| m_updateDefer = null, | |
| m_zoom = null, | |
| m_tileUrl, | |
| m_tileUrlFromTemplate; | |
| if (arg && arg.baseUrl !== undefined) { | |
| m_baseUrl = arg.baseUrl; | |
| } | |
| if (m_baseUrl.charAt(m_baseUrl.length - 1) !== "/") { | |
| m_baseUrl += "/"; | |
| } | |
| if (arg && arg.mapOpacity !== undefined) { | |
| m_mapOpacity = arg.mapOpacity; | |
| } | |
| if (arg && arg.imageFormat !== undefined) { | |
| m_imageFormat = arg.imageFormat; | |
| } | |
| if (arg && arg.displayLast !== undefined && arg.displayLast) { | |
| m_lastVisibleBinNumber = 999; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns a url string containing the requested tile. This default | |
| * version uses the open street map standard, but the user can | |
| * change the default behavior. | |
| * | |
| * @param {integer} zoom The zoom level | |
| * @param {integer} x The tile from the xth row | |
| * @param {integer} y The tile from the yth column | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| m_tileUrl = function (zoom, x, y) { | |
| return m_baseUrl + zoom + "/" + x + | |
| "/" + y + "." + m_imageFormat; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns an OSM tile server formatting function from a standard format | |
| * string: replaces <zoom>, <x>, and <y>. | |
| * | |
| * @param {string} base The tile format string | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| m_tileUrlFromTemplate = function (base) { | |
| return function (zoom, x, y) { | |
| return base.replace("<zoom>", zoom) | |
| .replace("<x>", x) | |
| .replace("<y>", y); | |
| }; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Check if a tile is visible in current view | |
| * | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function isTileVisible(tile) { | |
| if (tile.zoom in m_visibleTilesRange) { | |
| if (tile.index_x >= m_visibleTilesRange[tile.zoom].startX && | |
| tile.index_x <= m_visibleTilesRange[tile.zoom].endX && | |
| tile.index_y >= m_visibleTilesRange[tile.zoom].startY && | |
| tile.index_y <= m_visibleTilesRange[tile.zoom].endY) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Draw new tiles and remove the old ones | |
| * | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function drawTiles() { | |
| m_this._removeTiles(); | |
| m_this.draw(); | |
| delete m_pendingNewTilesStat[m_updateTimerId]; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set tile cache size | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.tileCacheSize = function (val) { | |
| if (val === undefined) { | |
| return m_tileCacheSize; | |
| } | |
| m_tileCacheSize = val; | |
| m_this.modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set the tile url formatting function. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.tileUrl = function (val) { | |
| if (val === undefined) { | |
| return m_tileUrl; | |
| } else if (typeof val === "string") { | |
| m_tileUrl = m_tileUrlFromTemplate(val); | |
| } else { | |
| m_tileUrl = val; | |
| } | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform a point or array of points in latitude-longitude-altitude | |
| * space to local space of the layer | |
| * | |
| * @param {*} input | |
| * Input can be of following types: | |
| * | |
| * 1. geo.latlng | |
| * 2. [geo.latlng] | |
| * 3. [x1,y1, x2, y2] | |
| * 4. [[x,y]] | |
| * 5. {x:val: y:val, z:val}, | |
| * 6. [{x:val: y:val}] | |
| * | |
| * returns geo.latlng, [geo.latlng], or {x:lon, y:lat}, [{x:lon, y:lat}] | |
| * [x1,y1, x2, y2], [[x,y]] | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.toLocal = function (input) { | |
| var i, output, delta; | |
| /// Now handle different data types | |
| if (input instanceof Array && input.length > 0) { | |
| output = []; | |
| output.length = input.length; | |
| /// Input is array of geo.latlng | |
| if (input[0] instanceof geo.latlng) { | |
| for (i = 0; i < input.length; i += 1) { | |
| output[i] = geo.latlng(input[i]); | |
| output[i].lat(geo.mercator.lat2y(output[i].lat())); | |
| } | |
| } else if (input[0] instanceof Array) { | |
| delta = input % 3 === 0 ? 3 : 2; | |
| if (delta === 2) { | |
| for (i = 0; i < input.length; i += delta) { | |
| output[i] = input[i]; | |
| output[i + 1] = geo.mercator.lat2y(input[i + 1]); | |
| } | |
| } else { | |
| for (i = 0; i < input.length; i += delta) { | |
| output[i] = input[i]; | |
| output[i + 1] = geo.mercator.lat2y(input[i + 1]); | |
| output[i + 2] = input[i + 2]; | |
| } | |
| } | |
| } else if (input[0] instanceof Object && | |
| "x" in input[0] && "y" in input[0] && "z" in input[0]) { | |
| /// Input is array of object | |
| output[i] = { x: input[i].x, y: geo.mercator.lat2y(input[i].y), | |
| z: input[i].z }; | |
| } else if (input[0] instanceof Object && | |
| "x" in input[0] && "y" in input[0] && "z" in input[0]) { | |
| /// Input is array of object | |
| output[i] = { x: input[i].x, y: geo.mercator.lat2y(input[i].y)}; | |
| } else if (input.length >= 2) { | |
| output = input.slice(0); | |
| output[1] = geo.mercator.lat2y(input[1]); | |
| } | |
| } else if (input instanceof geo.latlng) { | |
| output = {}; | |
| output.x = input.x(); | |
| output.y = geo.mercator.lat2y(input.y()); | |
| } else { | |
| output = {}; | |
| output.x = input.x; | |
| output.y = geo.mercator.lat2y(input.y); | |
| } | |
| return output; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Transform a point or array of points in local space to | |
| * latitude-longitude space | |
| * | |
| * @input Input An object, array, of array of objects/array representing 2D | |
| * point in space. [x,y], [[x,y]], [{x:val: y:val}], {x:val, y:val} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.fromLocal = function (input) { | |
| var i, output; | |
| if (input instanceof Array && input.length > 0) { | |
| output = []; | |
| output.length = input.length; | |
| if (input[0] instanceof Object) { | |
| for (i = 0; i < input.length; i += 1) { | |
| output[i] = {}; | |
| output[i].x = input[i].x; | |
| output[i].y = geo.mercator.y2lat(input[i].y); | |
| } | |
| } else if (input[0] instanceof Array) { | |
| for (i = 0; i < input.length; i += 1) { | |
| output[i] = input[i]; | |
| output[i][1] = geo.mercator.y2lat(input[i][1]); | |
| } | |
| } else { | |
| for (i = 0; i < input.length; i += 1) { | |
| output[i] = input[i]; | |
| output[i + 1] = geo.mercator.y2lat(input[i + 1]); | |
| } | |
| } | |
| } else { | |
| output = {}; | |
| output.x = input.x; | |
| output.y = geo.mercator.y2lat(input.y); | |
| } | |
| return output; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Check if a tile exists in the cache | |
| * | |
| * @param {number} zoom The zoom value for the map [1-17] | |
| * @param {number} x X axis tile index | |
| * @param {number} y Y axis tile index | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._hasTile = function (zoom, x, y) { | |
| if (!m_tiles[zoom]) { | |
| return false; | |
| } | |
| if (!m_tiles[zoom][x]) { | |
| return false; | |
| } | |
| if (!m_tiles[zoom][x][y]) { | |
| return false; | |
| } | |
| return true; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new tile | |
| * @param {number} x X axis tile index | |
| * @param {number} y Y axis tile index | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._addTile = function (request, zoom, x, y) { | |
| if (!m_tiles[zoom]) { | |
| m_tiles[zoom] = {}; | |
| } | |
| if (!m_tiles[zoom][x]) { | |
| m_tiles[zoom][x] = {}; | |
| } | |
| if (m_tiles[zoom][x][y]) { | |
| return; | |
| } | |
| /// Compute corner points | |
| var noOfTilesX = Math.max(1, Math.pow(2, zoom)), | |
| noOfTilesY = Math.max(1, Math.pow(2, zoom)), | |
| /// Convert into mercator | |
| totalLatDegrees = 360.0, | |
| lonPerTile = 360.0 / noOfTilesX, | |
| latPerTile = totalLatDegrees / noOfTilesY, | |
| llx = -180.0 + x * lonPerTile, | |
| lly = -totalLatDegrees * 0.5 + y * latPerTile, | |
| urx = -180.0 + (x + 1) * lonPerTile, | |
| ury = -totalLatDegrees * 0.5 + (y + 1) * latPerTile, | |
| tile = new Image(); | |
| tile.LOADING = true; | |
| tile.LOADED = false; | |
| tile.REMOVED = false; | |
| tile.REMOVING = false; | |
| tile.INVALID = false; | |
| tile.crossOrigin = "anonymous"; | |
| tile.zoom = zoom; | |
| tile.index_x = x; | |
| tile.index_y = y; | |
| tile.llx = llx; | |
| tile.lly = lly; | |
| tile.urx = urx; | |
| tile.ury = ury; | |
| tile.lastused = new Date(); | |
| tile.src = m_tileUrl(zoom, x, Math.pow(2, zoom) - 1 - y); | |
| m_tiles[zoom][x][y] = tile; | |
| m_pendingNewTiles.push(tile); | |
| m_numberOfCachedTiles += 1; | |
| return tile; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Clear tiles that are no longer required | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| /* jshint -W089 */ | |
| this._removeTiles = function () { | |
| var i, x, y, tile, zoom, currZoom = m_zoom, | |
| lastZoom = m_lastVisibleZoom; | |
| if (!m_tiles) { | |
| return m_this; | |
| } | |
| for (zoom in m_tiles) { | |
| for (x in m_tiles[zoom]) { | |
| for (y in m_tiles[zoom][x]) { | |
| tile = m_tiles[zoom][x][y]; | |
| if (tile) { | |
| tile.REMOVING = true; | |
| m_pendingInactiveTiles.push(tile); | |
| } | |
| } | |
| } | |
| } | |
| /// First remove the tiles if we have cached more than max cached limit | |
| m_pendingInactiveTiles.sort(function (a, b) { | |
| return a.lastused - b.lastused; | |
| }); | |
| i = 0; | |
| /// Get rid of tiles if we have reached our threshold. However, | |
| /// If the tile is required for current zoom, then do nothing. | |
| /// Also do not delete the tile if it is from the previous zoom | |
| while (m_numberOfCachedTiles > m_tileCacheSize && | |
| i < m_pendingInactiveTiles.length) { | |
| tile = m_pendingInactiveTiles[i]; | |
| if (isTileVisible(tile)) { | |
| i += 1; | |
| } else { | |
| m_this.deleteFeature(tile.feature); | |
| delete m_tiles[tile.zoom][tile.index_x][tile.index_y]; | |
| m_pendingInactiveTiles.splice(i, 1); | |
| m_numberOfCachedTiles -= 1; | |
| } | |
| } | |
| for (i = 0; i < m_pendingInactiveTiles.length; i += 1) { | |
| tile = m_pendingInactiveTiles[i]; | |
| tile.REMOVING = false; | |
| tile.REMOVED = false; | |
| if (tile.zoom !== currZoom && tile.zoom === lastZoom) { | |
| tile.feature.bin(m_lastVisibleBinNumber); | |
| } else if (tile.zoom !== currZoom) { | |
| tile.feature.bin(m_hiddenBinNumber); | |
| } else { | |
| tile.lastused = new Date(); | |
| tile.feature.bin(m_visibleBinNumber); | |
| } | |
| tile.feature._update(); | |
| } | |
| m_pendingInactiveTiles = []; | |
| return m_this; | |
| }; | |
| /* jshint +W089 */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create / delete tiles as necessary | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._addTiles = function (request) { | |
| var feature, ren = m_this.renderer(), | |
| /// First get corner points | |
| /// In display coordinates the origin is on top left corner (0, 0) | |
| llx = 0.0, lly = m_this.height(), urx = m_this.width(), ury = 0.0, | |
| temp = null, tile = null, tile1x = null, tile1y = null, tile2x = null, | |
| tile2y = null, invJ = null, i = 0, j = 0, lastStartX, lastStartY, | |
| lastEndX, lastEndY, currStartX, currStartY, currEndX, currEndY, | |
| worldPt1 = ren.displayToWorld([llx, lly]), | |
| worldPt2 = ren.displayToWorld([urx, ury]), | |
| worldDeltaY = null, displayDeltaY = null, | |
| worldDelta = null, displayDelta = null, | |
| noOfTilesRequired = null, worldDeltaPerTile = null, | |
| minDistWorldDeltaPerTile = null, distWorldDeltaPerTile; | |
| worldPt1[0] = Math.max(worldPt1[0], -180.0); | |
| worldPt1[0] = Math.min(worldPt1[0], 180.0); | |
| worldPt1[1] = Math.max(worldPt1[1], -180.0); | |
| worldPt1[1] = Math.min(worldPt1[1], 180.0); | |
| worldPt2[0] = Math.max(worldPt2[0], -180.0); | |
| worldPt2[0] = Math.min(worldPt2[0], 180.0); | |
| worldPt2[1] = Math.max(worldPt2[1], -180.0); | |
| worldPt2[1] = Math.min(worldPt2[1], 180.0); | |
| /// Compute tile zoom | |
| worldDelta = Math.abs(worldPt2[0] - worldPt1[0]); | |
| worldDeltaY = Math.abs(worldPt2[1] - worldPt1[1]); | |
| displayDelta = urx - llx; | |
| displayDeltaY = lly - ury; | |
| /// Reuse variables | |
| if (displayDeltaY > displayDelta) { | |
| displayDelta = displayDeltaY; | |
| worldDelta = worldDeltaY; | |
| } | |
| noOfTilesRequired = Math.round(displayDelta / 256.0); | |
| worldDeltaPerTile = worldDelta / noOfTilesRequired; | |
| /// Minimize per pixel distortion | |
| minDistWorldDeltaPerTile = Number.POSITIVE_INFINITY; | |
| for (i = 20; i >= 2; i = i - 1) { | |
| distWorldDeltaPerTile = Math.abs(360.0 / Math.pow(2, i) - worldDeltaPerTile); | |
| if (distWorldDeltaPerTile < minDistWorldDeltaPerTile) { | |
| minDistWorldDeltaPerTile = distWorldDeltaPerTile; | |
| m_zoom = i; | |
| } | |
| } | |
| /// Compute tilex and tiley | |
| tile1x = geo.mercator.long2tilex(worldPt1[0], m_zoom); | |
| tile1y = geo.mercator.lat2tiley(worldPt1[1], m_zoom); | |
| tile2x = geo.mercator.long2tilex(worldPt2[0], m_zoom); | |
| tile2y = geo.mercator.lat2tiley(worldPt2[1], m_zoom); | |
| /// Clamp tilex and tiley | |
| tile1x = Math.max(tile1x, 0); | |
| tile1x = Math.min(Math.pow(2, m_zoom) - 1, tile1x); | |
| tile1y = Math.max(tile1y, 0); | |
| tile1y = Math.min(Math.pow(2, m_zoom) - 1, tile1y); | |
| tile2x = Math.max(tile2x, 0); | |
| tile2x = Math.min(Math.pow(2, m_zoom) - 1, tile2x); | |
| tile2y = Math.max(tile2y, 0); | |
| tile2y = Math.min(Math.pow(2, m_zoom) - 1, tile2y); | |
| /// Check and update variables appropriately if view | |
| /// direction is flipped. This should not happen but | |
| /// just in case. | |
| if (tile1x > tile2x) { | |
| temp = tile1x; | |
| tile1x = tile2x; | |
| tile2x = temp; | |
| } | |
| if (tile2y > tile1y) { | |
| temp = tile1y; | |
| tile1y = tile2y; | |
| tile2y = temp; | |
| } | |
| /// Compute current tile indices | |
| currStartX = tile1x; | |
| currEndX = tile2x; | |
| currStartY = (Math.pow(2, m_zoom) - 1 - tile1y); | |
| currEndY = (Math.pow(2, m_zoom) - 1 - tile2y); | |
| if (currEndY < currStartY) { | |
| temp = currStartY; | |
| currStartY = currEndY; | |
| currEndY = temp; | |
| } | |
| /// Compute last tile indices | |
| lastStartX = geo.mercator.long2tilex(worldPt1[0], | |
| m_lastVisibleZoom); | |
| lastStartY = geo.mercator.lat2tiley(worldPt1[1], | |
| m_lastVisibleZoom); | |
| lastEndX = geo.mercator.long2tilex(worldPt2[0], | |
| m_lastVisibleZoom); | |
| lastEndY = geo.mercator.lat2tiley(worldPt2[1], | |
| m_lastVisibleZoom); | |
| lastStartY = Math.pow(2, m_lastVisibleZoom) - 1 - lastStartY; | |
| lastEndY = Math.pow(2, m_lastVisibleZoom) - 1 - lastEndY; | |
| if (lastEndY < lastStartY) { | |
| temp = lastStartY; | |
| lastStartY = lastEndY; | |
| lastEndY = temp; | |
| } | |
| m_visibleTilesRange = {}; | |
| m_visibleTilesRange[m_zoom] = { startX: currStartX, endX: currEndX, | |
| startY: currStartY, endY: currEndY }; | |
| m_visibleTilesRange[m_lastVisibleZoom] = | |
| { startX: lastStartX, endX: lastEndX, | |
| startY: lastStartY, endY: lastEndY }; | |
| m_pendingNewTilesStat[m_updateTimerId] = { total: | |
| ((tile2x - tile1x + 1) * (tile1y - tile2y + 1)), count: 0 }; | |
| for (i = tile1x; i <= tile2x; i += 1) { | |
| for (j = tile2y; j <= tile1y; j += 1) { | |
| invJ = (Math.pow(2, m_zoom) - 1 - j); | |
| if (!m_this._hasTile(m_zoom, i, invJ)) { | |
| tile = m_this._addTile(request, m_zoom, i, invJ); | |
| } else { | |
| tile = m_tiles[m_zoom][i][invJ]; | |
| tile.feature.bin(m_visibleBinNumber); | |
| if (tile.LOADED && m_updateTimerId in m_pendingNewTilesStat) { | |
| m_pendingNewTilesStat[m_updateTimerId].count += 1; | |
| } | |
| tile.lastused = new Date(); | |
| tile.feature._update(); | |
| } | |
| tile.updateTimerId = m_updateTimerId; | |
| } | |
| } | |
| // define a function here to set tile properties after it is loaded | |
| function tileOnLoad(tile) { | |
| var defer = $.Deferred(); | |
| m_this.addDeferred(defer); | |
| return function () { | |
| if (tile.INVALID) { | |
| return; | |
| } | |
| tile.LOADING = false; | |
| tile.LOADED = true; | |
| if ((tile.REMOVING || tile.REMOVED) && | |
| tile.feature && | |
| tile.zoom !== m_zoom) { | |
| tile.feature.bin(m_hiddenBinNumber); | |
| tile.REMOVING = false; | |
| tile.REMOVED = true; | |
| } else { | |
| tile.REMOVED = false; | |
| tile.lastused = new Date(); | |
| tile.feature.bin(m_visibleBinNumber); | |
| } | |
| if (tile.updateTimerId === m_updateTimerId && | |
| m_updateTimerId in m_pendingNewTilesStat) { | |
| tile.feature.bin(m_visibleBinNumber); | |
| m_pendingNewTilesStat[m_updateTimerId].count += 1; | |
| } else { | |
| tile.REMOVED = true; | |
| tile.feature.bin(m_hiddenBinNumber); | |
| } | |
| tile.feature._update(); | |
| if (m_updateTimerId in m_pendingNewTilesStat && | |
| m_pendingNewTilesStat[m_updateTimerId].count >= | |
| m_pendingNewTilesStat[m_updateTimerId].total) { | |
| drawTiles(); | |
| } | |
| defer.resolve(); | |
| }; | |
| } | |
| /// And now finally add them | |
| for (i = 0; i < m_pendingNewTiles.length; i += 1) { | |
| tile = m_pendingNewTiles[i]; | |
| feature = m_this.createFeature( | |
| "plane", {drawOnAsyncResourceLoad: false, onload: tileOnLoad(tile)}) | |
| .origin([tile.llx, tile.lly]) | |
| .upperLeft([tile.llx, tile.ury]) | |
| .lowerRight([tile.urx, tile.lly]) | |
| .gcs("EPSG:3857") | |
| .style({image: tile, opacity: m_mapOpacity}); | |
| tile.feature = feature; | |
| tile.feature._update(); | |
| } | |
| m_pendingNewTiles = []; | |
| if (m_updateTimerId in m_pendingNewTilesStat && | |
| m_pendingNewTilesStat[m_updateTimerId].count >= | |
| m_pendingNewTilesStat[m_updateTimerId].total) { | |
| drawTiles(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update OSM tiles as needed | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function updateOSMTiles(request) { | |
| if (request === undefined) { | |
| request = {}; | |
| } | |
| if (!m_zoom) { | |
| m_zoom = m_this.map().zoom(); | |
| } | |
| if (!m_lastVisibleZoom) { | |
| m_lastVisibleZoom = m_zoom; | |
| } | |
| /// Add tiles that are currently visible | |
| m_this._addTiles(request); | |
| /// Update the zoom | |
| if (m_lastVisibleZoom !== m_zoom) { | |
| m_lastVisibleZoom = m_zoom; | |
| } | |
| m_this.updateTime().modified(); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create / delete tiles as necessary | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._updateTiles = function (request) { | |
| var defer = $.Deferred(); | |
| m_this.addDeferred(defer); | |
| if (m_updateTimerId !== null) { | |
| clearTimeout(m_updateTimerId); | |
| m_updateDefer.resolve(); | |
| m_updateDefer = defer; | |
| if (m_updateTimerId in m_pendingNewTilesStat) { | |
| delete m_pendingNewTilesStat[m_updateTimerId]; | |
| } | |
| /// Set timeout for 60 ms. 60 ms seems to playing well | |
| /// with the events. Also, 60ms corresponds to 15 FPS. | |
| m_updateTimerId = setTimeout(function () { | |
| updateOSMTiles(request); | |
| m_updateDefer.resolve(); | |
| }, 100); | |
| } else { | |
| m_updateDefer = defer; | |
| m_updateTimerId = setTimeout(function () { | |
| updateOSMTiles(request); | |
| m_updateDefer.resolve(); | |
| }, 0); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| * | |
| * Do not call parent _init method as its already been executed | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function () { | |
| s_init.call(m_this); | |
| m_this.gcs("EPSG:3857"); | |
| m_this.map().zoomRange({ | |
| min: 0, | |
| max: 18 | |
| }); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update layer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function (request) { | |
| /// Update tiles (create new / delete old etc...) | |
| m_this._updateTiles(request); | |
| /// Now call base class update | |
| s_update.call(m_this, request); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update baseUrl for map tiles. Map all tiles as needing to be refreshed. | |
| * If no argument is given the tiles will be forced refreshed. | |
| * | |
| * @param baseUrl: the new baseUrl for the map. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| /* jshint -W089 */ | |
| this.updateBaseUrl = function (baseUrl) { | |
| if (baseUrl && baseUrl.charAt(m_baseUrl.length - 1) !== "/") { | |
| baseUrl += "/"; | |
| } | |
| if (baseUrl !== m_baseUrl) { | |
| if (baseUrl !== undefined) { | |
| m_baseUrl = baseUrl; | |
| } | |
| var tile, x, y, zoom; | |
| for (zoom in m_tiles) { | |
| for (x in m_tiles[zoom]) { | |
| for (y in m_tiles[zoom][x]) { | |
| tile = m_tiles[zoom][x][y]; | |
| tile.INVALID = true; | |
| m_this.deleteFeature(tile.feature); | |
| } | |
| } | |
| } | |
| m_tiles = {}; | |
| m_pendingNewTiles = []; | |
| m_pendingInactiveTiles = []; | |
| m_numberOfCachedTiles = 0; | |
| m_visibleTilesRange = {}; | |
| m_pendingNewTilesStat = {}; | |
| if (m_updateTimerId !== null) { | |
| clearTimeout(m_updateTimerId); | |
| m_updateTimerId = null; | |
| } | |
| this._update(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set map opacity | |
| * | |
| * @returns {geo.osmLayer} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.mapOpacity = function (val) { | |
| if (val === undefined) { | |
| return m_mapOpacity; | |
| } else if (val !== m_mapOpacity) { | |
| m_mapOpacity = val; | |
| var zoom, x, y, tile; | |
| for (zoom in m_tiles) { | |
| for (x in m_tiles[zoom]) { | |
| for (y in m_tiles[zoom][x]) { | |
| tile = m_tiles[zoom][x][y]; | |
| tile.feature.style().opacity = val; | |
| tile.feature._update(); | |
| } | |
| } | |
| } | |
| m_this.modified(); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Exit | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_tiles = {}; | |
| m_pendingNewTiles = []; | |
| m_pendingInactiveTiles = []; | |
| m_numberOfCachedTiles = 0; | |
| m_visibleTilesRange = {}; | |
| m_pendingNewTilesStat = {}; | |
| s_exit(); | |
| }; | |
| if (arg && arg.tileUrl !== undefined) { | |
| this.tileUrl(arg.tileUrl); | |
| } | |
| return this; | |
| }; | |
| inherit(geo.osmLayer, geo.featureLayer); | |
| geo.registerLayer("osm", geo.osmLayer); | |
| /** | |
| * @namespace | |
| */ | |
| geo.gl = {}; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class vglRenderer | |
| * | |
| * @class | |
| * @extends geo.renderer | |
| * @param canvas | |
| * @returns {geo.gl.renderer} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gl.renderer = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.gl.renderer)) { | |
| return new geo.gl.renderer(arg); | |
| } | |
| geo.renderer.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get context specific renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.contextRenderer = function () { | |
| throw 'Should be implemented by derived classes'; | |
| }; | |
| return this; | |
| }; | |
| inherit(geo.gl.renderer, geo.renderer); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of lineFeature | |
| * | |
| * @class | |
| * @extends geo.lineFeature | |
| * @returns {geo.gl.lineFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gl.lineFeature = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.gl.lineFeature)) { | |
| return new geo.gl.lineFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.lineFeature.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_exit = this._exit, | |
| m_actor = null, | |
| m_mapper = null, | |
| m_material = null, | |
| m_pixelWidthUnif = null, | |
| m_aspectUniform = null, | |
| m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw, | |
| s_init = this._init, | |
| s_update = this._update; | |
| function createVertexShader() { | |
| var vertexShaderSource = [ | |
| '#ifdef GL_ES', | |
| ' precision highp float;', | |
| '#endif', | |
| 'attribute vec3 pos;', | |
| 'attribute vec3 prev;', | |
| 'attribute vec3 next;', | |
| 'attribute float offset;', | |
| 'attribute vec3 strokeColor;', | |
| 'attribute float strokeOpacity;', | |
| 'attribute float strokeWidth;', | |
| 'uniform mat4 modelViewMatrix;', | |
| 'uniform mat4 projectionMatrix;', | |
| 'uniform float pixelWidth;', | |
| 'uniform float aspect;', | |
| 'varying vec3 strokeColorVar;', | |
| 'varying float strokeWidthVar;', | |
| 'varying float strokeOpacityVar;', | |
| 'void main(void)', | |
| '{', | |
| /* If any vertex has been deliberately set to a negative opacity, | |
| * skip doing computations on it. */ | |
| ' if (strokeOpacity < 0.0) {', | |
| ' gl_Position = vec4(2, 2, 0, 1);', | |
| ' return;', | |
| ' }', | |
| ' const float PI = 3.14159265358979323846264;', | |
| ' vec4 worldPos = projectionMatrix * modelViewMatrix * vec4(pos.xyz, 1);', | |
| ' if (worldPos.w != 0.0) {', | |
| ' worldPos = worldPos/worldPos.w;', | |
| ' }', | |
| ' vec4 worldNext = projectionMatrix * modelViewMatrix * vec4(next.xyz, 1);', | |
| ' if (worldNext.w != 0.0) {', | |
| ' worldNext = worldNext/worldNext.w;', | |
| ' }', | |
| ' vec4 worldPrev = projectionMatrix* modelViewMatrix * vec4(prev.xyz, 1);', | |
| ' if (worldPrev.w != 0.0) {', | |
| ' worldPrev = worldPrev/worldPrev.w;', | |
| ' }', | |
| ' strokeColorVar = strokeColor;', | |
| ' strokeWidthVar = strokeWidth;', | |
| ' strokeOpacityVar = strokeOpacity;', | |
| ' vec2 deltaNext = worldNext.xy - worldPos.xy;', | |
| ' vec2 deltaPrev = worldPos.xy - worldPrev.xy;', | |
| ' float angleNext = 0.0, anglePrev = 0.0;', | |
| ' if (deltaNext.xy != vec2(0.0, 0.0))', | |
| ' angleNext = atan(deltaNext.y / aspect, deltaNext.x);', | |
| ' if (deltaPrev.xy == vec2(0.0, 0.0)) anglePrev = angleNext;', | |
| ' else anglePrev = atan(deltaPrev.y / aspect, deltaPrev.x);', | |
| ' if (deltaNext.xy == vec2(0.0, 0.0)) angleNext = anglePrev;', | |
| ' float angle = (anglePrev + angleNext) / 2.0;', | |
| ' float cosAngle = cos(anglePrev - angle);', | |
| ' if (cosAngle < 0.1) { cosAngle = sign(cosAngle) * 1.0; angle = 0.0; }', | |
| ' float distance = (offset * strokeWidth * pixelWidth) /', | |
| ' cosAngle;', | |
| ' worldPos.x += distance * sin(angle);', | |
| ' worldPos.y -= distance * cos(angle) * aspect;', | |
| ' gl_Position = worldPos;', | |
| '}' | |
| ].join('\n'), | |
| shader = new vgl.shader(gl.VERTEX_SHADER); | |
| shader.setShaderSource(vertexShaderSource); | |
| return shader; | |
| } | |
| function createFragmentShader() { | |
| var fragmentShaderSource = [ | |
| '#ifdef GL_ES', | |
| ' precision highp float;', | |
| '#endif', | |
| 'varying vec3 strokeColorVar;', | |
| 'varying float strokeWidthVar;', | |
| 'varying float strokeOpacityVar;', | |
| 'void main () {', | |
| ' gl_FragColor = vec4 (strokeColorVar, strokeOpacityVar);', | |
| '}' | |
| ].join('\n'), | |
| shader = new vgl.shader(gl.FRAGMENT_SHADER); | |
| shader.setShaderSource(fragmentShaderSource); | |
| return shader; | |
| } | |
| function createGLLines() { | |
| var data = m_this.data(), | |
| i, j, k, v, | |
| numSegments = 0, len, | |
| lineItem, lineItemData, | |
| vert = [{}, {}], vertTemp, | |
| pos, posIdx3, | |
| position = [], | |
| posFunc = m_this.position(), | |
| strkWidthFunc = m_this.style.get('strokeWidth'), | |
| strkColorFunc = m_this.style.get('strokeColor'), | |
| strkOpacityFunc = m_this.style.get('strokeOpacity'), | |
| order = m_this.featureVertices(), | |
| posBuf, nextBuf, prevBuf, offsetBuf, indicesBuf, | |
| strokeWidthBuf, strokeColorBuf, strokeOpacityBuf, | |
| dest, dest3, | |
| geom = m_mapper.geometryData(); | |
| for (i = 0; i < data.length; i += 1) { | |
| lineItem = m_this.line()(data[i], i); | |
| numSegments += lineItem.length - 1; | |
| for (j = 0; j < lineItem.length; j += 1) { | |
| pos = posFunc(lineItem[j], j, lineItem, i); | |
| if (pos instanceof geo.latlng) { | |
| position.push(pos.x()); | |
| position.push(pos.y()); | |
| position.push(0.0); | |
| } else { | |
| position.push(pos.x); | |
| position.push(pos.y); | |
| position.push(pos.z || 0.0); | |
| } | |
| } | |
| } | |
| position = geo.transform.transformCoordinates( | |
| m_this.gcs(), m_this.layer().map().gcs(), | |
| position, 3); | |
| len = numSegments * order.length; | |
| posBuf = getBuffer(geom, 'pos', len * 3); | |
| nextBuf = getBuffer(geom, 'next', len * 3); | |
| prevBuf = getBuffer(geom, 'prev', len * 3); | |
| offsetBuf = getBuffer(geom, 'offset', len * 1); | |
| strokeWidthBuf = getBuffer(geom, 'strokeWidth', len * 1); | |
| strokeColorBuf = getBuffer(geom, 'strokeColor', len * 3); | |
| strokeOpacityBuf = getBuffer(geom, 'strokeOpacity', len * 1); | |
| indicesBuf = geom.primitive(0).indices(); | |
| if (!(indicesBuf instanceof Uint16Array) || indicesBuf.length !== len) { | |
| indicesBuf = new Uint16Array(len); | |
| geom.primitive(0).setIndices(indicesBuf); | |
| } | |
| for (i = posIdx3 = dest = dest3 = 0; i < data.length; i += 1) { | |
| lineItem = m_this.line()(data[i], i); | |
| for (j = 0; j < lineItem.length; j += 1, posIdx3 += 3) { | |
| lineItemData = lineItem[j]; | |
| /* swap entries in vert so that vert[0] is the first vertex, and | |
| * vert[1] will be reused for the second vertex */ | |
| if (j) { | |
| vertTemp = vert[0]; | |
| vert[0] = vert[1]; | |
| vert[1] = vertTemp; | |
| } | |
| vert[1].pos = posIdx3; | |
| vert[1].prev = posIdx3 - (j ? 3 : 0); | |
| vert[1].next = posIdx3 + (j + 1 < lineItem.length ? 3 : 0); | |
| vert[1].strokeWidth = strkWidthFunc(lineItemData, j, lineItem, i); | |
| vert[1].strokeColor = strkColorFunc(lineItemData, j, lineItem, i); | |
| vert[1].strokeOpacity = strkOpacityFunc(lineItemData, j, lineItem, i); | |
| if (j) { | |
| for (k = 0; k < order.length; k += 1, dest += 1, dest3 += 3) { | |
| v = vert[order[k][0]]; | |
| posBuf[dest3] = position[v.pos]; | |
| posBuf[dest3 + 1] = position[v.pos + 1]; | |
| posBuf[dest3 + 2] = position[v.pos + 2]; | |
| prevBuf[dest3] = position[v.prev]; | |
| prevBuf[dest3 + 1] = position[v.prev + 1]; | |
| prevBuf[dest3 + 2] = position[v.prev + 2]; | |
| nextBuf[dest3] = position[v.next]; | |
| nextBuf[dest3 + 1] = position[v.next + 1]; | |
| nextBuf[dest3 + 2] = position[v.next + 2]; | |
| offsetBuf[dest] = order[k][1]; | |
| /* We can ignore the indicies (they will all be zero) */ | |
| strokeWidthBuf[dest] = v.strokeWidth; | |
| strokeColorBuf[dest3] = v.strokeColor.r; | |
| strokeColorBuf[dest3 + 1] = v.strokeColor.g; | |
| strokeColorBuf[dest3 + 2] = v.strokeColor.b; | |
| strokeOpacityBuf[dest] = v.strokeOpacity; | |
| } | |
| } | |
| } | |
| } | |
| geom.boundsDirty(true); | |
| m_mapper.modified(); | |
| m_mapper.boundsDirtyTimestamp().modified(); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get a buffer for a geometry source. If a buffer already exists and is | |
| * the correct size, return it. Otherwise, allocate a new buffer; any data | |
| * in an old buffer is discarded. | |
| * | |
| * @param geom: the geometry to reference and modify. | |
| * @param srcName: the name of the source. | |
| * @param len: the number of elements for the array. | |
| * @returns {Float32Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function getBuffer(geom, srcName, len) { | |
| var src = geom.sourceByName(srcName), data; | |
| data = src.data(); | |
| if (data instanceof Float32Array && data.length === len) { | |
| return data; | |
| } | |
| data = new Float32Array(len); | |
| src.setData(data); | |
| return data; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return the arrangement of vertices used for each line segment. | |
| * | |
| * @returns {Number} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.featureVertices = function () { | |
| return [[0, 1], [1, -1], [0, -1], [0, 1], [1, 1], [1, -1]]; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return the number of vertices used for each line segment. | |
| * | |
| * @returns {Number} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.verticesPerFeature = function () { | |
| return this.featureVertices().length; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| var prog = vgl.shaderProgram(), | |
| vs = createVertexShader(), | |
| fs = createFragmentShader(), | |
| // Vertex attributes | |
| posAttr = vgl.vertexAttribute('pos'), | |
| prvAttr = vgl.vertexAttribute('prev'), | |
| nxtAttr = vgl.vertexAttribute('next'), | |
| offAttr = vgl.vertexAttribute('offset'), | |
| strkWidthAttr = vgl.vertexAttribute('strokeWidth'), | |
| strkColorAttr = vgl.vertexAttribute('strokeColor'), | |
| strkOpacityAttr = vgl.vertexAttribute('strokeOpacity'), | |
| // Shader uniforms | |
| mviUnif = new vgl.modelViewUniform('modelViewMatrix'), | |
| prjUnif = new vgl.projectionUniform('projectionMatrix'), | |
| geom = vgl.geometryData(), | |
| // Sources | |
| posData = vgl.sourceDataP3fv({'name': 'pos'}), | |
| prvPosData = vgl.sourceDataAnyfv( | |
| 3, vgl.vertexAttributeKeysIndexed.Four, {'name': 'prev'}), | |
| nxtPosData = vgl.sourceDataAnyfv( | |
| 3, vgl.vertexAttributeKeysIndexed.Five, {'name': 'next'}), | |
| offPosData = vgl.sourceDataAnyfv( | |
| 1, vgl.vertexAttributeKeysIndexed.Six, {'name': 'offset'}), | |
| strkWidthData = vgl.sourceDataAnyfv( | |
| 1, vgl.vertexAttributeKeysIndexed.One, {'name': 'strokeWidth'}), | |
| strkColorData = vgl.sourceDataAnyfv( | |
| 3, vgl.vertexAttributeKeysIndexed.Two, {'name': 'strokeColor'}), | |
| strkOpacityData = vgl.sourceDataAnyfv( | |
| 1, vgl.vertexAttributeKeysIndexed.Three, | |
| {'name': 'strokeOpacity'}), | |
| // Primitive indices | |
| triangles = vgl.triangles(); | |
| m_pixelWidthUnif = new vgl.floatUniform('pixelWidth', | |
| 1.0 / m_this.renderer().width()); | |
| m_aspectUniform = new vgl.floatUniform('aspect', | |
| m_this.renderer().width() / m_this.renderer().height()); | |
| s_init.call(m_this, arg); | |
| m_material = vgl.material(); | |
| m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw}); | |
| prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position); | |
| prog.addVertexAttribute(strkWidthAttr, vgl.vertexAttributeKeysIndexed.One); | |
| prog.addVertexAttribute(strkColorAttr, vgl.vertexAttributeKeysIndexed.Two); | |
| prog.addVertexAttribute(strkOpacityAttr, vgl.vertexAttributeKeysIndexed.Three); | |
| prog.addVertexAttribute(prvAttr, vgl.vertexAttributeKeysIndexed.Four); | |
| prog.addVertexAttribute(nxtAttr, vgl.vertexAttributeKeysIndexed.Five); | |
| prog.addVertexAttribute(offAttr, vgl.vertexAttributeKeysIndexed.Six); | |
| prog.addUniform(mviUnif); | |
| prog.addUniform(prjUnif); | |
| prog.addUniform(m_pixelWidthUnif); | |
| prog.addUniform(m_aspectUniform); | |
| prog.addShader(fs); | |
| prog.addShader(vs); | |
| m_material.addAttribute(prog); | |
| m_material.addAttribute(vgl.blend()); | |
| m_actor = vgl.actor(); | |
| m_actor.setMaterial(m_material); | |
| m_actor.setMapper(m_mapper); | |
| geom.addSource(posData); | |
| geom.addSource(prvPosData); | |
| geom.addSource(nxtPosData); | |
| geom.addSource(strkWidthData); | |
| geom.addSource(strkColorData); | |
| geom.addSource(strkOpacityData); | |
| geom.addSource(offPosData); | |
| geom.addPrimitive(triangles); | |
| m_mapper.setGeometryData(geom); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return list of actors | |
| * | |
| * @returns {vgl.actor[]} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.actors = function () { | |
| if (!m_actor) { | |
| return []; | |
| } | |
| return [m_actor]; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| if (m_actor) { | |
| m_this.renderer().contextRenderer().removeActor(m_actor); | |
| } | |
| createGLLines(); | |
| m_this.renderer().contextRenderer().addActor(m_actor); | |
| m_this.buildTime().modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| s_update.call(m_this); | |
| if (m_this.dataTime().getMTime() >= m_this.buildTime().getMTime() || | |
| m_this.updateTime().getMTime() <= m_this.getMTime()) { | |
| m_this._build(); | |
| } | |
| m_pixelWidthUnif.set(1.0 / m_this.renderer().width()); | |
| m_aspectUniform.set(m_this.renderer().width() / | |
| m_this.renderer().height()); | |
| m_actor.setVisible(m_this.visible()); | |
| m_actor.material().setBinNumber(m_this.bin()); | |
| m_this.updateTime().modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Destroy | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_this.renderer().contextRenderer().removeActor(m_actor); | |
| s_exit(); | |
| }; | |
| this._init(arg); | |
| return this; | |
| }; | |
| inherit(geo.gl.lineFeature, geo.lineFeature); | |
| // Now register it | |
| geo.registerFeature('vgl', 'line', geo.gl.lineFeature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of pointFeature | |
| * | |
| * @class | |
| * @extends geo.pointFeature | |
| * @returns {geo.gl.pointFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gl.pointFeature = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.gl.pointFeature)) { | |
| return new geo.gl.pointFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.pointFeature.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_exit = this._exit, | |
| m_actor = null, | |
| m_mapper = null, | |
| m_pixelWidthUniform = null, | |
| m_aspectUniform = null, | |
| m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw, | |
| m_primitiveShape = "sprite", // arg can change this, below | |
| s_init = this._init, | |
| s_update = this._update, | |
| vertexShaderSource = null, | |
| fragmentShaderSource = null; | |
| if (arg.primitiveShape === "triangle" || | |
| arg.primitiveShape === "square" || | |
| arg.primitiveShape === "sprite") { | |
| m_primitiveShape = arg.primitiveShape; | |
| } | |
| vertexShaderSource = [ | |
| "#ifdef GL_ES", | |
| " precision highp float;", | |
| "#endif", | |
| "attribute vec3 pos;", | |
| "attribute float rad;", | |
| "attribute vec3 fillColor;", | |
| "attribute vec3 strokeColor;", | |
| "attribute float fillOpacity;", | |
| "attribute float strokeWidth;", | |
| "attribute float strokeOpacity;", | |
| "attribute float fill;", | |
| "attribute float stroke;", | |
| "uniform float pixelWidth;", | |
| "uniform float aspect;", | |
| "uniform mat4 modelViewMatrix;", | |
| "uniform mat4 projectionMatrix;", | |
| "varying vec4 fillColorVar;", | |
| "varying vec4 strokeColorVar;", | |
| "varying float radiusVar;", | |
| "varying float strokeWidthVar;", | |
| "varying float fillVar;", | |
| "varying float strokeVar;" | |
| ]; | |
| if (m_primitiveShape !== "sprite") { | |
| vertexShaderSource = vertexShaderSource.concat([ | |
| "attribute vec2 unit;", | |
| "varying vec3 unitVar;" | |
| ]); | |
| } | |
| vertexShaderSource.push.apply(vertexShaderSource, [ | |
| "void main(void)", | |
| "{", | |
| " strokeWidthVar = strokeWidth;", | |
| " // No stroke or fill implies nothing to draw", | |
| " if (stroke < 1.0 || strokeWidth <= 0.0 || strokeOpacity <= 0.0) {", | |
| " strokeVar = 0.0;", | |
| " strokeWidthVar = 0.0;", | |
| " }", | |
| " else", | |
| " strokeVar = 1.0;", | |
| " if (fill < 1.0 || rad <= 0.0 || fillOpacity <= 0.0)", | |
| " fillVar = 0.0;", | |
| " else", | |
| " fillVar = 1.0;", | |
| /* If the point has no visible pixels, skip doing computations on it. */ | |
| " if (fillVar == 0.0 && strokeVar == 0.0) {", | |
| " gl_Position = vec4(2, 2, 0, 1);", | |
| " return;", | |
| " }", | |
| " fillColorVar = vec4 (fillColor, fillOpacity);", | |
| " strokeColorVar = vec4 (strokeColor, strokeOpacity);", | |
| " radiusVar = rad;" | |
| ]); | |
| if (m_primitiveShape === "sprite") { | |
| vertexShaderSource.push.apply(vertexShaderSource, [ | |
| " gl_Position = (projectionMatrix * modelViewMatrix * vec4(pos, 1.0)).xyzw;", | |
| " gl_PointSize = 2.0 * (rad + strokeWidthVar); ", | |
| "}" | |
| ]); | |
| } else { | |
| vertexShaderSource.push.apply(vertexShaderSource, [ | |
| " unitVar = vec3 (unit, 1.0);", | |
| " vec4 p = (projectionMatrix * modelViewMatrix * vec4(pos, 1.0)).xyzw;", | |
| " if (p.w != 0.0) {", | |
| " p = p / p.w;", | |
| " }", | |
| " p += (rad + strokeWidthVar) * ", | |
| " vec4 (unit.x * pixelWidth, unit.y * pixelWidth * aspect, 0.0, 1.0);", | |
| " gl_Position = vec4(p.xyz, 1.0);", | |
| "}" | |
| ]); | |
| } | |
| vertexShaderSource = vertexShaderSource.join("\n"); | |
| fragmentShaderSource = [ | |
| "#ifdef GL_ES", | |
| " precision highp float;", | |
| "#endif", | |
| "uniform float aspect;", | |
| "varying vec4 fillColorVar;", | |
| "varying vec4 strokeColorVar;", | |
| "varying float radiusVar;", | |
| "varying float strokeWidthVar;", | |
| "varying float fillVar;", | |
| "varying float strokeVar;" | |
| ]; | |
| if (m_primitiveShape !== "sprite") { | |
| fragmentShaderSource.push("varying vec3 unitVar;"); | |
| } | |
| fragmentShaderSource.push.apply(fragmentShaderSource, [ | |
| "void main () {", | |
| " vec4 strokeColor, fillColor;", | |
| " float endStep;", | |
| " // No stroke or fill implies nothing to draw", | |
| " if (fillVar == 0.0 && strokeVar == 0.0)", | |
| " discard;" | |
| ]); | |
| if (m_primitiveShape === "sprite") { | |
| fragmentShaderSource.push( | |
| " float rad = 2.0 * length (gl_PointCoord - vec2(0.5));"); | |
| } else { | |
| fragmentShaderSource.push( | |
| " float rad = length (unitVar.xy);"); | |
| } | |
| fragmentShaderSource.push.apply(fragmentShaderSource, [ | |
| " if (rad > 1.0)", | |
| " discard;", | |
| " // If there is no stroke, the fill region should transition to nothing", | |
| " if (strokeVar == 0.0) {", | |
| " strokeColor = vec4 (fillColorVar.rgb, 0.0);", | |
| " endStep = 1.0;", | |
| " } else {", | |
| " strokeColor = strokeColorVar;", | |
| " endStep = radiusVar / (radiusVar + strokeWidthVar);", | |
| " }", | |
| " // Likewise, if there is no fill, the stroke should transition to nothing", | |
| " if (fillVar == 0.0)", | |
| " fillColor = vec4 (strokeColor.rgb, 0.0);", | |
| " else", | |
| " fillColor = fillColorVar;", | |
| " // Distance to antialias over", | |
| " float antialiasDist = 3.0 / (2.0 * radiusVar);", | |
| " if (rad < endStep) {", | |
| " float step = smoothstep (endStep - antialiasDist, endStep, rad);", | |
| " gl_FragColor = mix (fillColor, strokeColor, step);", | |
| " } else {", | |
| " float step = smoothstep (1.0 - antialiasDist, 1.0, rad);", | |
| " gl_FragColor = mix (strokeColor, vec4 (strokeColor.rgb, 0.0), step);", | |
| " }", | |
| "}" | |
| ]); | |
| fragmentShaderSource = fragmentShaderSource.join("\n"); | |
| function createVertexShader() { | |
| var shader = new vgl.shader(gl.VERTEX_SHADER); | |
| shader.setShaderSource(vertexShaderSource); | |
| return shader; | |
| } | |
| function createFragmentShader() { | |
| var shader = new vgl.shader(gl.FRAGMENT_SHADER); | |
| shader.setShaderSource(fragmentShaderSource); | |
| return shader; | |
| } | |
| function pointPolygon(x, y, w, h) { | |
| var verts; | |
| switch (m_primitiveShape) { | |
| case "triangle": | |
| /* Use an equilateral triangle. While this has 30% more area than a | |
| * square, the reduction in vertices should help more than the | |
| * processing the additional fragments. */ | |
| verts = [ | |
| x, y - h * 2, | |
| x - w * Math.sqrt(3.0), y + h, | |
| x + w * Math.sqrt(3.0), y + h | |
| ]; | |
| break; | |
| case "sprite": | |
| /* Point sprite uses only one vertex per point. */ | |
| verts = [x, y]; | |
| break; | |
| default: // "square" | |
| /* Use a surrounding square split diagonally into two triangles. */ | |
| verts = [ | |
| x - w, y + h, | |
| x - w, y - h, | |
| x + w, y + h, | |
| x - w, y - h, | |
| x + w, y - h, | |
| x + w, y + h | |
| ]; | |
| break; | |
| } | |
| return verts; | |
| } | |
| function createGLPoints() { | |
| // unit and associated data is not used when drawing sprite | |
| var i, j, numPts = m_this.data().length, | |
| unit = pointPolygon(0, 0, 1, 1), | |
| position = new Array(numPts * 3), posBuf, posVal, posFunc, | |
| unitBuf, indices, | |
| radius, radiusVal, radFunc, | |
| stroke, strokeVal, strokeFunc, | |
| strokeWidth, strokeWidthVal, strokeWidthFunc, | |
| strokeOpacity, strokeOpacityVal, strokeOpactityFunc, | |
| strokeColor, strokeColorVal, strokeColorFunc, | |
| fill, fillVal, fillFunc, | |
| fillOpacity, fillOpacityVal, fillOpacityFunc, | |
| fillColor, fillColorVal, fillColorFunc, | |
| vpf = m_this.verticesPerFeature(), | |
| data = m_this.data(), | |
| item, ivpf, ivpf3, iunit, i3, | |
| geom = m_mapper.geometryData(); | |
| posFunc = m_this.position(); | |
| radFunc = m_this.style.get("radius"); | |
| strokeFunc = m_this.style.get("stroke"); | |
| strokeWidthFunc = m_this.style.get("strokeWidth"); | |
| strokeOpactityFunc = m_this.style.get("strokeOpacity"); | |
| strokeColorFunc = m_this.style.get("strokeColor"); | |
| fillFunc = m_this.style.get("fill"); | |
| fillOpacityFunc = m_this.style.get("fillOpacity"); | |
| fillColorFunc = m_this.style.get("fillColor"); | |
| /* It is more efficient to do a transform on a single array rather than on | |
| * an array of arrays or an array of objects. */ | |
| for (i = i3 = 0; i < numPts; i += 1, i3 += 3) { | |
| posVal = posFunc(data[i]); | |
| position[i3] = posVal.x; | |
| position[i3 + 1] = posVal.y; | |
| position[i3 + 2] = posVal.z || 0; | |
| } | |
| position = geo.transform.transformCoordinates( | |
| m_this.gcs(), m_this.layer().map().gcs(), | |
| position, 3); | |
| posBuf = getBuffer(geom, "pos", vpf * numPts * 3); | |
| if (m_primitiveShape !== "sprite") { | |
| unitBuf = getBuffer(geom, "unit", vpf * numPts * 2); | |
| } | |
| radius = getBuffer(geom, "rad", vpf * numPts * 1); | |
| stroke = getBuffer(geom, "stroke", vpf * numPts * 1); | |
| strokeWidth = getBuffer(geom, "strokeWidth", vpf * numPts * 1); | |
| strokeOpacity = getBuffer(geom, "strokeOpacity", vpf * numPts * 1); | |
| strokeColor = getBuffer(geom, "strokeColor", vpf * numPts * 3); | |
| fill = getBuffer(geom, "fill", vpf * numPts * 1); | |
| fillOpacity = getBuffer(geom, "fillOpacity", vpf * numPts * 1); | |
| fillColor = getBuffer(geom, "fillColor", vpf * numPts * 3); | |
| indices = geom.primitive(0).indices(); | |
| if (!(indices instanceof Uint16Array) || indices.length !== vpf * numPts) { | |
| indices = new Uint16Array(vpf * numPts); | |
| geom.primitive(0).setIndices(indices); | |
| } | |
| for (i = ivpf = ivpf3 = iunit = i3 = 0; i < numPts; i += 1, i3 += 3) { | |
| item = data[i]; | |
| if (m_primitiveShape !== "sprite") { | |
| for (j = 0; j < unit.length; j += 1, iunit += 1) { | |
| unitBuf[iunit] = unit[j]; | |
| } | |
| } | |
| /* We can ignore the indicies (they will all be zero) */ | |
| radiusVal = radFunc(item); | |
| strokeVal = strokeFunc(item) ? 1.0 : 0.0; | |
| strokeWidthVal = strokeWidthFunc(item); | |
| strokeOpacityVal = strokeOpactityFunc(item); | |
| strokeColorVal = strokeColorFunc(item); | |
| fillVal = fillFunc(item) ? 1.0 : 0.0; | |
| fillOpacityVal = fillOpacityFunc(item); | |
| fillColorVal = fillColorFunc(item); | |
| for (j = 0; j < vpf; j += 1, ivpf += 1, ivpf3 += 3) { | |
| posBuf[ivpf3] = position[i3]; | |
| posBuf[ivpf3 + 1] = position[i3 + 1]; | |
| posBuf[ivpf3 + 2] = position[i3 + 2]; | |
| radius[ivpf] = radiusVal; | |
| stroke[ivpf] = strokeVal; | |
| strokeWidth[ivpf] = strokeWidthVal; | |
| strokeOpacity[ivpf] = strokeOpacityVal; | |
| strokeColor[ivpf3] = strokeColorVal.r; | |
| strokeColor[ivpf3 + 1] = strokeColorVal.g; | |
| strokeColor[ivpf3 + 2] = strokeColorVal.b; | |
| fill[ivpf] = fillVal; | |
| fillOpacity[ivpf] = fillOpacityVal; | |
| fillColor[ivpf3] = fillColorVal.r; | |
| fillColor[ivpf3 + 1] = fillColorVal.g; | |
| fillColor[ivpf3 + 2] = fillColorVal.b; | |
| } | |
| } | |
| geom.boundsDirty(true); | |
| m_mapper.modified(); | |
| m_mapper.boundsDirtyTimestamp().modified(); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get a buffer for a geometry source. If a buffer already exists and is | |
| * the correct size, return it. Otherwise, allocate a new buffer; any data | |
| * in an old buffer is discarded. | |
| * | |
| * @param geom: the geometry to reference and modify. | |
| * @param srcName: the name of the source. | |
| * @param len: the number of elements for the array. | |
| * @returns {Float32Array} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function getBuffer(geom, srcName, len) { | |
| var src = geom.sourceByName(srcName), data; | |
| data = src.data(); | |
| if (data instanceof Float32Array && data.length === len) { | |
| return data; | |
| } | |
| data = new Float32Array(len); | |
| src.setData(data); | |
| return data; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return list of actors | |
| * | |
| * @returns {vgl.actor[]} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.actors = function () { | |
| if (!m_actor) { | |
| return []; | |
| } | |
| return [m_actor]; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return the number of vertices used for each point. | |
| * | |
| * @returns {Number} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.verticesPerFeature = function () { | |
| var unit = pointPolygon(0, 0, 1, 1); | |
| return unit.length / 2; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function () { | |
| var prog = vgl.shaderProgram(), | |
| vertexShader = createVertexShader(), | |
| fragmentShader = createFragmentShader(), | |
| posAttr = vgl.vertexAttribute("pos"), | |
| unitAttr = vgl.vertexAttribute("unit"), | |
| radAttr = vgl.vertexAttribute("rad"), | |
| strokeWidthAttr = vgl.vertexAttribute("strokeWidth"), | |
| fillColorAttr = vgl.vertexAttribute("fillColor"), | |
| fillAttr = vgl.vertexAttribute("fill"), | |
| strokeColorAttr = vgl.vertexAttribute("strokeColor"), | |
| strokeAttr = vgl.vertexAttribute("stroke"), | |
| fillOpacityAttr = vgl.vertexAttribute("fillOpacity"), | |
| strokeOpacityAttr = vgl.vertexAttribute("strokeOpacity"), | |
| modelViewUniform = new vgl.modelViewUniform("modelViewMatrix"), | |
| projectionUniform = new vgl.projectionUniform("projectionMatrix"), | |
| mat = vgl.material(), | |
| blend = vgl.blend(), | |
| geom = vgl.geometryData(), | |
| sourcePositions = vgl.sourceDataP3fv({"name": "pos"}), | |
| sourceUnits = vgl.sourceDataAnyfv( | |
| 2, vgl.vertexAttributeKeysIndexed.One, {"name": "unit"}), | |
| sourceRadius = vgl.sourceDataAnyfv( | |
| 1, vgl.vertexAttributeKeysIndexed.Two, {"name": "rad"}), | |
| sourceStrokeWidth = vgl.sourceDataAnyfv( | |
| 1, vgl.vertexAttributeKeysIndexed.Three, {"name": "strokeWidth"}), | |
| sourceFillColor = vgl.sourceDataAnyfv( | |
| 3, vgl.vertexAttributeKeysIndexed.Four, {"name": "fillColor"}), | |
| sourceFill = vgl.sourceDataAnyfv( | |
| 1, vgl.vertexAttributeKeysIndexed.Five, {"name": "fill"}), | |
| sourceStrokeColor = vgl.sourceDataAnyfv( | |
| 3, vgl.vertexAttributeKeysIndexed.Six, {"name": "strokeColor"}), | |
| sourceStroke = vgl.sourceDataAnyfv( | |
| 1, vgl.vertexAttributeKeysIndexed.Seven, {"name": "stroke"}), | |
| sourceAlpha = vgl.sourceDataAnyfv( | |
| 1, vgl.vertexAttributeKeysIndexed.Eight, {"name": "fillOpacity"}), | |
| sourceStrokeOpacity = vgl.sourceDataAnyfv( | |
| 1, vgl.vertexAttributeKeysIndexed.Nine, {"name": "strokeOpacity"}), | |
| primitive = new vgl.triangles(); | |
| if (m_primitiveShape === "sprite") { | |
| primitive = new vgl.points(); | |
| } | |
| m_pixelWidthUniform = new vgl.floatUniform("pixelWidth", | |
| 2.0 / m_this.renderer().width()); | |
| m_aspectUniform = new vgl.floatUniform("aspect", | |
| m_this.renderer().width() / m_this.renderer().height()); | |
| s_init.call(m_this, arg); | |
| m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw}); | |
| // TODO: Right now this is ugly but we will fix it. | |
| prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position); | |
| if (m_primitiveShape !== "sprite") { | |
| prog.addVertexAttribute(unitAttr, vgl.vertexAttributeKeysIndexed.One); | |
| } | |
| prog.addVertexAttribute(radAttr, vgl.vertexAttributeKeysIndexed.Two); | |
| prog.addVertexAttribute(strokeWidthAttr, vgl.vertexAttributeKeysIndexed.Three); | |
| prog.addVertexAttribute(fillColorAttr, vgl.vertexAttributeKeysIndexed.Four); | |
| prog.addVertexAttribute(fillAttr, vgl.vertexAttributeKeysIndexed.Five); | |
| prog.addVertexAttribute(strokeColorAttr, vgl.vertexAttributeKeysIndexed.Six); | |
| prog.addVertexAttribute(strokeAttr, vgl.vertexAttributeKeysIndexed.Seven); | |
| prog.addVertexAttribute(fillOpacityAttr, vgl.vertexAttributeKeysIndexed.Eight); | |
| prog.addVertexAttribute(strokeOpacityAttr, vgl.vertexAttributeKeysIndexed.Nine); | |
| prog.addUniform(m_pixelWidthUniform); | |
| prog.addUniform(m_aspectUniform); | |
| prog.addUniform(modelViewUniform); | |
| prog.addUniform(projectionUniform); | |
| prog.addShader(fragmentShader); | |
| prog.addShader(vertexShader); | |
| mat.addAttribute(prog); | |
| mat.addAttribute(blend); | |
| m_actor = vgl.actor(); | |
| m_actor.setMaterial(mat); | |
| m_actor.setMapper(m_mapper); | |
| geom.addSource(sourcePositions); | |
| geom.addSource(sourceUnits); | |
| geom.addSource(sourceRadius); | |
| geom.addSource(sourceStrokeWidth); | |
| geom.addSource(sourceFillColor); | |
| geom.addSource(sourceFill); | |
| geom.addSource(sourceStrokeColor); | |
| geom.addSource(sourceStroke); | |
| geom.addSource(sourceAlpha); | |
| geom.addSource(sourceStrokeOpacity); | |
| geom.addPrimitive(primitive); | |
| m_mapper.setGeometryData(geom); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| if (m_actor) { | |
| m_this.renderer().contextRenderer().removeActor(m_actor); | |
| } | |
| createGLPoints(); | |
| m_this.renderer().contextRenderer().addActor(m_actor); | |
| m_this.renderer().contextRenderer().render(); | |
| m_this.buildTime().modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| s_update.call(m_this); | |
| // For now build if the data or style changes. In the future we may | |
| // we able to partially update the data using dynamic gl buffers. | |
| if (m_this.dataTime().getMTime() >= m_this.buildTime().getMTime() || | |
| m_this.updateTime().getMTime() < m_this.getMTime()) { | |
| m_this._build(); | |
| } | |
| // Update uniforms | |
| m_pixelWidthUniform.set(2.0 / m_this.renderer().width()); | |
| m_aspectUniform.set(m_this.renderer().width() / | |
| m_this.renderer().height()); | |
| m_actor.setVisible(m_this.visible()); | |
| m_actor.material().setBinNumber(m_this.bin()); | |
| m_this.updateTime().modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Destroy | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_this.renderer().contextRenderer().removeActor(m_actor); | |
| s_exit(); | |
| }; | |
| m_this._init(); | |
| return this; | |
| }; | |
| inherit(geo.gl.pointFeature, geo.pointFeature); | |
| // Now register it | |
| geo.registerFeature("vgl", "point", geo.gl.pointFeature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of geomFeature | |
| * | |
| * @class | |
| * @extends geo.geomFeature | |
| * @param {vgl.geometryData} arg | |
| * @returns {geo.gl.geomFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gl.geomFeature = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.gl.geomFeature)) { | |
| return new geo.gl.geomFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.geomFeature.call(this, arg); | |
| // Initialize | |
| var m_this = this, | |
| m_geom = arg.geom || null, | |
| m_actor = vgl.actor(), | |
| m_mapper = vgl.mapper(), | |
| m_material = null, | |
| m_scalar = null, | |
| m_color = arg.color || [1.0, 1.0, 1.0], | |
| m_buildTime = null, | |
| m_noOfPrimitives = 0; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| var style = m_this.style(); | |
| // Vertex color gets the preference | |
| if (m_geom !== null) { | |
| m_scalar = m_geom.sourceData(vgl.vertexAttributeKeys.Scalar); | |
| m_color = m_geom.sourceData(vgl.vertexAttributeKeys.Color); | |
| m_mapper.setGeometryData(m_geom); | |
| } | |
| this.setMapper(m_mapper); | |
| if (style.point_sprites !== undefined && style.point_sprites && | |
| style.point_sprites_image !== undefined && | |
| style.point_sprites_image !== null && | |
| m_noOfPrimitives === 1 && | |
| m_geom.primitive(0).primitiveType() === gl.POINTS) { | |
| m_material = vgl.utils.createPointSpritesMaterial( | |
| style.point_sprites_image); | |
| } else if (m_scalar) { | |
| if (m_color instanceof vgl.lookupTable) { | |
| m_color.updateRange(m_scalar.scalarRange()); | |
| m_material = vgl.utils.createColorMappedMaterial(m_color); | |
| } else { | |
| m_color = vgl.lookupTable(); | |
| m_color.updateRange(m_scalar.scalarRange()); | |
| m_material = vgl.utils.createColorMappedMaterial(m_color); | |
| } | |
| } else if (m_color) { | |
| m_material = vgl.utils.createColorMaterial(); | |
| } else { | |
| m_material = vgl.utils.createSolidColorMaterial(); | |
| } | |
| m_actor.setMaterial(m_material); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update | |
| * | |
| * @private | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| if (m_buildTime && | |
| m_buildTime.getMTime() < m_this.getMTime()) { | |
| if (m_color instanceof vgl.lookupTable) { | |
| vgl.utils.updateColorMappedMaterial(m_this.material(), | |
| m_this.style.color); | |
| }/* else { | |
| // TODO | |
| }*/ | |
| } else { | |
| m_buildTime = vgl.timestamp(); | |
| m_buildTime.modified(); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get/Set geometry | |
| * | |
| * @returns {geo.gl.geomFeature} | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.geometry = function (val) { | |
| if (val === undefined) { | |
| return m_geom; | |
| } else { | |
| m_geom = val; | |
| m_this.modified(); | |
| return m_this; | |
| } | |
| }; | |
| return this; | |
| }; | |
| inherit(geo.gl.geomFeature, geo.geomFeature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a plane feature given a lower left corner point geo.latlng | |
| * and and upper right corner point geo.latlng | |
| * @class | |
| * @extends geo.planeFeature | |
| * @param lowerleft | |
| * @param upperright | |
| * @returns {geo.planeFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gl.planeFeature = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.gl.planeFeature)) { | |
| return new geo.gl.planeFeature(arg); | |
| } | |
| geo.planeFeature.call(this, arg); | |
| var m_this = this, | |
| s_exit = this._exit, | |
| m_actor = null, | |
| m_onloadCallback = arg.onload === undefined ? null : arg.onload; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Gets the coordinates for this plane | |
| * | |
| * @returns {Array} [[origin], [upper left] [lower right]] | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.coords = function () { | |
| return [m_this.origin(), m_this.upperLeft(), m_this.lowerRight()]; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build this feature | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| var or = m_this.origin(), | |
| ul = m_this.upperLeft(), | |
| lr = m_this.lowerRight(), | |
| /// img could be a source or an Image | |
| img = m_this.style().image, | |
| image = null, | |
| texture = null; | |
| /// TODO If for some reason base layer changes its gcs at run time | |
| /// then we need to trigger an event to rebuild every feature | |
| or = geo.transform.transformCoordinates(m_this.gcs(), | |
| m_this.layer().map().gcs(), | |
| or); | |
| ul = geo.transform.transformCoordinates(m_this.gcs(), | |
| m_this.layer().map().gcs(), | |
| ul); | |
| lr = geo.transform.transformCoordinates(m_this.gcs(), | |
| m_this.layer().map().gcs(), | |
| lr); | |
| m_this.buildTime().modified(); | |
| if (m_actor) { | |
| m_this.renderer().contextRenderer().removeActor(m_actor); | |
| } | |
| if (img && img instanceof Image) { | |
| image = img; | |
| } else if (img) { | |
| image = new Image(); | |
| image.src = img; | |
| } | |
| if (!image) { | |
| m_actor = vgl.utils.createPlane(or[0], or[1], or[2], | |
| ul[0], ul[1], ul[2], | |
| lr[0], lr[1], lr[2]); | |
| m_actor.material().shaderProgram().uniform("opacity").set( | |
| m_this.style().opacity !== undefined ? m_this.style().opacity : 1); | |
| m_this.renderer().contextRenderer().addActor(m_actor); | |
| } else { | |
| m_actor = vgl.utils.createTexturePlane(or[0], or[1], or[2], | |
| lr[0], lr[1], lr[2], | |
| ul[0], ul[1], ul[2], true); | |
| m_actor.material().shaderProgram().uniform("opacity").set( | |
| m_this.style().opacity !== undefined ? m_this.style().opacity : 1); | |
| texture = vgl.texture(); | |
| m_this.visible(false); | |
| /// TODO: Is there a reliable way to make sure that image is loaded already? | |
| m_this.renderer().contextRenderer().addActor(m_actor); | |
| if (image.complete) { | |
| texture.setImage(image); | |
| m_actor.material().addAttribute(texture); | |
| /// NOTE Currently we assume that we want to show the feature as | |
| /// soon as the image gets loaded. However, there might be a case | |
| /// where we want to lock down the visibility. We will deal with that | |
| /// later. | |
| m_this.visible(true); | |
| if (m_onloadCallback) { | |
| m_onloadCallback.call(m_this); | |
| } | |
| //} | |
| } else { | |
| image.onload = function () { | |
| texture.setImage(image); | |
| m_actor.material().addAttribute(texture); | |
| /// NOTE Currently we assume that we want to show the feature as | |
| /// soon as the image gets loaded. However, there might be a case | |
| /// where we want to lock down the visibility. We will deal with that | |
| /// later. | |
| m_this.visible(true); | |
| if (m_onloadCallback) { | |
| m_onloadCallback.call(m_this); | |
| } | |
| if (m_this.drawOnAsyncResourceLoad()) { | |
| m_this._update(); | |
| m_this.layer().draw(); | |
| } | |
| }; | |
| } | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| if (m_this.buildTime().getMTime() <= m_this.dataTime().getMTime()) { | |
| m_this._build(); | |
| } | |
| if (m_this.updateTime().getMTime() <= m_this.getMTime()) { | |
| m_actor.setVisible(m_this.visible()); | |
| m_actor.material().setBinNumber(m_this.bin()); | |
| m_actor.material().shaderProgram().uniform("opacity").set( | |
| m_this.style().opacity !== undefined ? m_this.style().opacity : 1); | |
| } | |
| m_this.updateTime().modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Destroy | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_this.renderer().contextRenderer().removeActor(m_actor); | |
| s_exit(); | |
| }; | |
| return this; | |
| }; | |
| inherit(geo.gl.planeFeature, geo.planeFeature); | |
| // Now register it | |
| geo.registerFeature("vgl", "plane", geo.gl.planeFeature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of polygonFeature | |
| * | |
| * @class | |
| * @extends geo.polygonFeature | |
| * @returns {geo.gl.polygonFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gl.polygonFeature = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.gl.polygonFeature)) { | |
| return new geo.gl.polygonFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.polygonFeature.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_exit = this._exit, | |
| m_actor = vgl.actor(), | |
| m_mapper = vgl.mapper(), | |
| m_material = vgl.material(), | |
| s_init = this._init, | |
| s_update = this._update; | |
| function createVertexShader() { | |
| var vertexShaderSource = [ | |
| 'attribute vec3 pos;', | |
| 'attribute vec3 fillColor;', | |
| 'attribute float fillOpacity;', | |
| 'uniform mat4 modelViewMatrix;', | |
| 'uniform mat4 projectionMatrix;', | |
| 'uniform float pixelWidth;', | |
| 'varying vec3 fillColorVar;', | |
| 'varying float fillOpacityVar;', | |
| 'void main(void)', | |
| '{', | |
| ' vec4 clipPos = projectionMatrix * modelViewMatrix * vec4(pos.xyz, 1);', | |
| ' if (clipPos.w != 0.0) {', | |
| ' clipPos = clipPos/clipPos.w;', | |
| ' }', | |
| ' fillColorVar = fillColor;', | |
| ' fillOpacityVar = fillOpacity;', | |
| ' gl_Position = clipPos;', | |
| '}' | |
| ].join('\n'), | |
| shader = new vgl.shader(gl.VERTEX_SHADER); | |
| shader.setShaderSource(vertexShaderSource); | |
| return shader; | |
| } | |
| function createFragmentShader() { | |
| var fragmentShaderSource = [ | |
| '#ifdef GL_ES', | |
| ' precision highp float;', | |
| '#endif', | |
| 'varying vec3 fillColorVar;', | |
| 'varying float fillOpacityVar;', | |
| 'void main () {', | |
| ' gl_FragColor = vec4 (fillColorVar, fillOpacityVar);', | |
| '}' | |
| ].join('\n'), | |
| shader = new vgl.shader(gl.FRAGMENT_SHADER); | |
| shader.setShaderSource(fragmentShaderSource); | |
| return shader; | |
| } | |
| function createGLPolygons() { | |
| var i = null, | |
| numPts = null, | |
| start = null, | |
| itemIndex = 0, | |
| polygonItemCoordIndex = 0, | |
| position = [], | |
| fillColor = [], | |
| fillOpacity = [], | |
| fillColorNew = [], | |
| fillOpacityNew = [], | |
| posFunc = null, | |
| fillColorFunc = null, | |
| polygonItem = null, | |
| fillOpacityFunc = null, | |
| buffers = vgl.DataBuffers(1024), | |
| sourcePositions = vgl.sourceDataP3fv(), | |
| sourceFillColor = | |
| vgl.sourceDataAnyfv(3, vgl.vertexAttributeKeysIndexed.Two), | |
| sourceFillOpacity = | |
| vgl.sourceDataAnyfv(1, vgl.vertexAttributeKeysIndexed.Three), | |
| trianglePrimitive = vgl.triangles(), | |
| geom = vgl.geometryData(), | |
| polygon = null, | |
| holes = null, | |
| extRing = null, | |
| extIndex = 0, | |
| extLength = null, | |
| intIndex = 0, | |
| posInstance = null, | |
| triangulator = new PNLTRI.Triangulator(), | |
| triangList = null, | |
| newTriangList = null, | |
| fillColorInstance = null, | |
| currentIndex = null; | |
| posFunc = m_this.position(); | |
| fillColorFunc = m_this.style.get('fillColor'); | |
| fillOpacityFunc = m_this.style.get('fillOpacity'); | |
| m_this.data().forEach(function (item) { | |
| polygon = m_this.polygon()(item, itemIndex); | |
| polygonItem = polygon.outer || []; | |
| holes = polygon.inner || []; | |
| polygonItemCoordIndex = 0; | |
| extRing = []; | |
| extIndex = 0; | |
| extLength = polygonItem.length - 1; | |
| extRing[0] = []; | |
| intIndex = 0; | |
| polygonItem.forEach(function (extRingCoords) { | |
| if (extIndex !== extLength) { | |
| //extRing = extRing.concat(extRingCoords); | |
| posInstance = posFunc(extRingCoords, | |
| polygonItemCoordIndex, | |
| item, itemIndex); | |
| if (posInstance instanceof geo.latlng) { | |
| extRing[0].push({ | |
| x: posInstance.x(), y: posInstance.y(), i: fillColor.length | |
| }); | |
| } else { | |
| extRing[0].push({ | |
| x: posInstance.x, y: posInstance.y, i: fillColor.length | |
| }); | |
| } | |
| fillColorInstance = fillColorFunc(extRingCoords, | |
| polygonItemCoordIndex, | |
| item, itemIndex); | |
| fillColor.push([fillColorInstance.r, | |
| fillColorInstance.g, | |
| fillColorInstance.b]); | |
| fillOpacity.push(fillOpacityFunc(extRingCoords, | |
| polygonItemCoordIndex, | |
| item, | |
| itemIndex)); | |
| polygonItemCoordIndex += 1; | |
| } | |
| extIndex += 1; | |
| }); | |
| polygonItemCoordIndex = 0; | |
| holes.forEach(function (hole) { | |
| extRing[intIndex + 1] = []; | |
| hole.forEach(function (intRingCoords) { | |
| posInstance = posFunc(intRingCoords, polygonItemCoordIndex, | |
| item, itemIndex); | |
| if (posInstance instanceof geo.latlng) { | |
| extRing[intIndex + 1].push({ | |
| x: posInstance.x(), y: posInstance.y(), i: fillColor.length | |
| }); | |
| } else { | |
| extRing[intIndex + 1].push({ | |
| x: posInstance.x, y: posInstance.y, i: fillColor.length | |
| }); | |
| } | |
| fillColorInstance = fillColorFunc(intRingCoords, | |
| polygonItemCoordIndex, | |
| item, itemIndex); | |
| fillColor.push([fillColorInstance.r, | |
| fillColorInstance.g, | |
| fillColorInstance.b]); | |
| fillOpacity.push(fillOpacityFunc(intRingCoords, | |
| polygonItemCoordIndex, | |
| item, itemIndex)); | |
| polygonItemCoordIndex += 1; | |
| }); | |
| intIndex += 1; | |
| }); | |
| //console.log("extRing ", extRing); | |
| //console.log("result", PolyK.Triangulate(extRing)); | |
| triangList = triangulator.triangulate_polygon(extRing); | |
| newTriangList = []; | |
| triangList.forEach(function (newIndices) { | |
| Array.prototype.push.apply(newTriangList, newIndices); | |
| }); | |
| for (i = 1; i < extRing.length; i += 1) { | |
| extRing[0] = extRing[0].concat(extRing[i]); | |
| } | |
| newTriangList.forEach(function (polygonIndex) { | |
| var polygonItemCoords = extRing[0][polygonIndex]; | |
| position.push([polygonItemCoords.x, | |
| polygonItemCoords.y, | |
| polygonItemCoords.z || 0.0]); | |
| fillColorNew.push(fillColor[polygonItemCoords.i]); | |
| fillOpacityNew.push(fillOpacity[polygonItemCoords.i]); | |
| }); | |
| itemIndex += 1; | |
| }); | |
| position = geo.transform.transformCoordinates( | |
| m_this.gcs(), m_this.layer().map().gcs(), | |
| position, 3); | |
| buffers.create('pos', 3); | |
| buffers.create('indices', 1); | |
| buffers.create('fillColor', 3); | |
| buffers.create('fillOpacity', 1); | |
| numPts = position.length; | |
| start = buffers.alloc(numPts); | |
| currentIndex = start; | |
| //console.log("numPts ", numPts); | |
| for (i = 0; i < numPts; i += 1) { | |
| buffers.write('pos', position[i], start + i, 1); | |
| buffers.write('indices', [i], start + i, 1); | |
| buffers.write('fillColor', fillColorNew[i], start + i, 1); | |
| buffers.write('fillOpacity', [fillOpacityNew[i]], start + i, 1); | |
| } | |
| //console.log(buffers.get('fillColor')); | |
| sourcePositions.pushBack(buffers.get('pos')); | |
| geom.addSource(sourcePositions); | |
| sourceFillColor.pushBack(buffers.get('fillColor')); | |
| geom.addSource(sourceFillColor); | |
| sourceFillOpacity.pushBack(buffers.get('fillOpacity')); | |
| geom.addSource(sourceFillOpacity); | |
| //console.log(buffers.get('indices')); | |
| trianglePrimitive.setIndices(buffers.get('indices')); | |
| geom.addPrimitive(trianglePrimitive); | |
| m_mapper.setGeometryData(geom); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| var blend = vgl.blend(), | |
| prog = vgl.shaderProgram(), | |
| posAttr = vgl.vertexAttribute('pos'), | |
| fillColorAttr = vgl.vertexAttribute('fillColor'), | |
| fillOpacityAttr = vgl.vertexAttribute('fillOpacity'), | |
| modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'), | |
| projectionUniform = new vgl.projectionUniform('projectionMatrix'), | |
| vertexShader = createVertexShader(), | |
| fragmentShader = createFragmentShader(); | |
| s_init.call(m_this, arg); | |
| prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position); | |
| prog.addVertexAttribute(fillColorAttr, vgl.vertexAttributeKeysIndexed.Two); | |
| prog.addVertexAttribute(fillOpacityAttr, vgl.vertexAttributeKeysIndexed.Three); | |
| prog.addUniform(modelViewUniform); | |
| prog.addUniform(projectionUniform); | |
| prog.addShader(fragmentShader); | |
| prog.addShader(vertexShader); | |
| m_material.addAttribute(prog); | |
| m_material.addAttribute(blend); | |
| m_actor.setMapper(m_mapper); | |
| m_actor.setMaterial(m_material); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| if (m_actor) { | |
| m_this.renderer().contextRenderer().removeActor(m_actor); | |
| } | |
| createGLPolygons(); | |
| m_this.renderer().contextRenderer().addActor(m_actor); | |
| m_this.buildTime().modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| s_update.call(m_this); | |
| if (m_this.dataTime().getMTime() >= m_this.buildTime().getMTime() || | |
| m_this.updateTime().getMTime() <= m_this.getMTime()) { | |
| m_this._build(); | |
| } | |
| m_actor.setVisible(m_this.visible()); | |
| m_actor.material().setBinNumber(m_this.bin()); | |
| m_this.updateTime().modified(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Destroy | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_this.renderer().contextRenderer().removeActor(m_actor); | |
| s_exit(); | |
| }; | |
| this._init(arg); | |
| return this; | |
| }; | |
| inherit(geo.gl.polygonFeature, geo.polygonFeature); | |
| // Now register it | |
| geo.registerFeature('vgl', 'polygon', geo.gl.polygonFeature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Single VGL viewer | |
| * | |
| * This singleton instance is used to share a single GL context across multiple | |
| * vlgRenderer and therefore layers. | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gl._vglViewerInstances = { | |
| viewers: [], | |
| maps: [] | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Retrives the singleton, lazily constructs as necessary. | |
| * | |
| * @return {vgl.viewer} the single viewer instance. | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gl.vglViewerInstance = function (map) { | |
| "use strict"; | |
| var mapIdx, | |
| maps = geo.gl._vglViewerInstances.maps, | |
| viewers = geo.gl._vglViewerInstances.viewers, | |
| canvas; | |
| function makeViewer() { | |
| canvas = $(document.createElement("canvas")); | |
| canvas.attr("class", "webgl-canvas"); | |
| var viewer = vgl.viewer(canvas.get(0)); | |
| viewer.renderWindow().removeRenderer( | |
| viewer.renderWindow().activeRenderer()); | |
| viewer.init(); | |
| return viewer; | |
| } | |
| for (mapIdx = 0; mapIdx < maps.length; mapIdx += 1) { | |
| if (map === maps[mapIdx]) { | |
| break; | |
| } | |
| } | |
| if (map !== maps[mapIdx]) { | |
| maps[mapIdx] = map; | |
| viewers[mapIdx] = makeViewer(); | |
| } | |
| viewers[mapIdx]._exit = function () { | |
| if (canvas) { | |
| canvas.off(); | |
| canvas.remove(); | |
| } | |
| }; | |
| return viewers[mapIdx]; | |
| }; | |
| geo.gl.vglViewerInstance.deleteCache = function (viewer) { | |
| "use strict"; | |
| var mapIdx, | |
| maps = geo.gl._vglViewerInstances.maps, | |
| viewers = geo.gl._vglViewerInstances.viewers; | |
| for (mapIdx = 0; mapIdx < viewers.length; mapIdx += 1) { | |
| if (viewer === undefined || viewer === viewers[mapIdx]) { | |
| viewer._exit(); | |
| maps.splice(mapIdx, 1); | |
| viewers.splice(mapIdx, 1); | |
| } | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class vglRenderer | |
| * | |
| * @class | |
| * @extends geo.gl.renderer | |
| * @param canvas | |
| * @returns {geo.gl.vglRenderer} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gl.vglRenderer = function (arg) { | |
| "use strict"; | |
| if (!(this instanceof geo.gl.vglRenderer)) { | |
| return new geo.gl.vglRenderer(arg); | |
| } | |
| geo.gl.renderer.call(this, arg); | |
| var m_this = this, | |
| s_exit = this._exit, | |
| m_viewer = geo.gl.vglViewerInstance(this.layer().map()), | |
| m_contextRenderer = vgl.renderer(), | |
| m_width = 0, | |
| m_height = 0, | |
| s_init = this._init; | |
| m_contextRenderer.setResetScene(false); | |
| /// TODO: Move this API to the base class | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return width of the renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.width = function () { | |
| return m_width; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return height of the renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.height = function () { | |
| return m_height; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert input data in display space to world space | |
| * | |
| * @param {object} input {x:val, y:val}, [{x:val, y:val}], | |
| * [{x:val, y:val}], [x1,y1], [[x,y]] | |
| * | |
| * @returns {object} {x:val, y:val, z:val, w:val}, [{x:val, y:val, z:val, w:val}], | |
| [[x, y, z, w]], [x1,y1,z1,w] | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.displayToWorld = function (input) { | |
| var i, | |
| delta, | |
| ren = m_this.contextRenderer(), | |
| cam = ren.camera(), | |
| fdp = ren.focusDisplayPoint(), | |
| output, | |
| temp, | |
| point; | |
| /// Handle if the input is an array [...] | |
| if (input instanceof Array && input.length > 0) { | |
| output = []; | |
| /// Input is array of object {x:val, y:val} | |
| if (input[0] instanceof Object) { | |
| delta = 1; | |
| for (i = 0; i < input.length; i += delta) { | |
| point = input[i]; | |
| temp = ren.displayToWorld(vec4.fromValues( | |
| point.x, point.y, fdp[2], 1.0), | |
| cam.viewMatrix(), cam.projectionMatrix(), | |
| m_width, m_height); | |
| output.push({x: temp[0], y: temp[1], z: temp[2], w: temp[3]}); | |
| } | |
| /// Input is array of 2d array [[x,y], [x,y]] | |
| } else if (input[0] instanceof Array) { | |
| delta = 1; | |
| for (i = 0; i < input.length; i += delta) { | |
| point = input[i]; | |
| temp = ren.displayToWorld(vec4.fromValues( | |
| point[0], point[1], fdp[2], 1.0), | |
| cam.viewMatrix(), cam.projectionMatrix(), | |
| m_width, m_height); | |
| output.push(temp); | |
| } | |
| /// Input is flat array [x1,y1,x2,y2] | |
| } else { | |
| delta = input.length % 3 === 0 ? 3 : 2; | |
| for (i = 0; i < input.length; i += delta) { | |
| temp = ren.displayToWorld(vec4.fromValues( | |
| input[i], | |
| input[i + 1], | |
| fdp[2], | |
| 1.0), cam.viewMatrix(), cam.projectionMatrix(), | |
| m_width, m_height); | |
| output.push(temp[0]); | |
| output.push(temp[1]); | |
| output.push(temp[2]); | |
| output.push(temp[3]); | |
| } | |
| } | |
| /// Input is object {x:val, y:val} | |
| } else if (input instanceof Object) { | |
| output = {}; | |
| temp = ren.displayToWorld(vec4.fromValues( | |
| input.x, input.y, fdp[2], 1.0), | |
| cam.viewMatrix(), cam.projectionMatrix(), | |
| m_width, m_height); | |
| output = {x: temp[0], y: temp[1], z: temp[2], w: temp[3]}; | |
| } else { | |
| throw "Display to world conversion requires array of 2D/3D points"; | |
| } | |
| return output; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert input data in world space to display space | |
| * | |
| * @param {object} input {x:val, y:val} or {x:val, y:val, z:val} or [{x:val, y:val}] | |
| * [{x:val, y:val, z:val}] or [[x,y]] or [[x,y,z]] or [x1,y1,z1, x2, y2, z2] | |
| * | |
| * @returns {object} {x:val, y:val} or [{x:val, y:val}] or [[x,y]] or | |
| * [x1,y1, x2, y2] | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.worldToDisplay = function (input) { | |
| var i, temp, delta, | |
| ren = m_this.contextRenderer(), cam = ren.camera(), | |
| fp = cam.focalPoint(), output = []; | |
| /// Input is an array | |
| if (input instanceof Array && input.length > 0) { | |
| output = []; | |
| /// Input is an array of objects | |
| if (input[0] instanceof Object) { | |
| delta = 1; | |
| for (i = 0; i < input.length; i += delta) { | |
| temp = ren.worldToDisplay(vec4.fromValues( | |
| input[i].x, input[i].y, fp[2], 1.0), cam.viewMatrix(), | |
| cam.projectionMatrix(), | |
| m_width, m_height); | |
| output[i] = { x: temp[0], y: temp[1], z: temp[2] }; | |
| } | |
| } else if (input[0] instanceof Array) { | |
| /// Input is an array of array | |
| delta = 1; | |
| for (i = 0; i < input.length; i += delta) { | |
| temp = ren.worldToDisplay( | |
| vec4.fromValues(input[i][0], input[i][1], fp[2], 1.0), | |
| cam.viewMatrix(), cam.projectionMatrix(), m_width, m_height); | |
| output[i].push(temp); | |
| } | |
| } else { | |
| /// Input is a flat array of 2 or 3 dimension | |
| delta = input.length % 3 === 0 ? 3 : 2; | |
| if (delta === 2) { | |
| for (i = 0; i < input.length; i += delta) { | |
| temp = ren.worldToDisplay(vec4.fromValues( | |
| input[i], input[i + 1], fp[2], 1.0), cam.viewMatrix(), | |
| cam.projectionMatrix(), | |
| m_width, m_height); | |
| output.push(temp[0]); | |
| output.push(temp[1]); | |
| output.push(temp[2]); | |
| } | |
| } else { | |
| for (i = 0; i < input.length; i += delta) { | |
| temp = ren.worldToDisplay(vec4.fromValues( | |
| input[i], input[i + 1], input[i + 2], 1.0), cam.viewMatrix(), | |
| cam.projectionMatrix(), | |
| m_width, m_height); | |
| output.push(temp[0]); | |
| output.push(temp[1]); | |
| output.push(temp[2]); | |
| } | |
| } | |
| } | |
| } else if (input instanceof Object) { | |
| temp = ren.worldToDisplay(vec4.fromValues( | |
| input.x, input.y, fp[2], 1.0), cam.viewMatrix(), | |
| cam.projectionMatrix(), | |
| m_width, m_height); | |
| output = {x: temp[0], y: temp[1], z: temp[2]}; | |
| } else { | |
| throw "World to display conversion requires array of 2D/3D points"; | |
| } | |
| return output; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get context specific renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.contextRenderer = function () { | |
| return m_contextRenderer; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get API used by the renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.api = function () { | |
| return "vgl"; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function () { | |
| if (m_this.initialized()) { | |
| return m_this; | |
| } | |
| s_init.call(m_this); | |
| m_this.canvas($(m_viewer.canvas())); | |
| if (m_viewer.renderWindow().renderers().length > 0) { | |
| m_contextRenderer.setLayer(m_viewer.renderWindow().renderers().length); | |
| m_contextRenderer.setResetScene(false); | |
| } | |
| m_viewer.renderWindow().addRenderer(m_contextRenderer); | |
| m_this.layer().node().append(m_this.canvas()); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle resize event | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._resize = function (x, y, w, h) { | |
| m_width = w; | |
| m_height = h; | |
| m_this.canvas().attr("width", w); | |
| m_this.canvas().attr("height", h); | |
| m_viewer.renderWindow().positionAndResize(x, y, w, h); | |
| m_this._render(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Render | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._render = function () { | |
| m_viewer.render(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Exit | |
| * @todo remove all vgl objects | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| geo.gl.vglViewerInstance.deleteCache(m_viewer); | |
| s_exit(); | |
| }; | |
| this._updateRendererCamera = function () { | |
| var vglRenderer = m_this.contextRenderer(), | |
| renderWindow = m_viewer.renderWindow(), | |
| camera = vglRenderer.camera(), | |
| pos, fp, cr; | |
| vglRenderer.resetCameraClippingRange(); | |
| pos = camera.position(); | |
| fp = camera.focalPoint(); | |
| cr = camera.clippingRange(); | |
| renderWindow.renderers().forEach(function (renderer) { | |
| var cam = renderer.camera(); | |
| if (cam !== camera) { | |
| cam.setPosition(pos[0], pos[1], pos[2]); | |
| cam.setFocalPoint(fp[0], fp[1], fp[2]); | |
| cam.setClippingRange(cr[0], cr[1]); | |
| renderer.render(); | |
| } | |
| }); | |
| }; | |
| // connect to interactor events | |
| this.geoOn(geo.event.pan, function (evt) { | |
| var vglRenderer = m_this.contextRenderer(), | |
| camera, | |
| focusPoint, | |
| centerDisplay, | |
| centerGeo, | |
| newCenterDisplay, | |
| newCenterGeo, | |
| renderWindow, | |
| layer = m_this.layer(); | |
| // only the base layer needs to respond | |
| if (layer.map().baseLayer() !== layer) { | |
| return; | |
| } | |
| // skip handling if the renderer is unconnected | |
| if (!vglRenderer || !vglRenderer.camera()) { | |
| console.log("Pan event triggered on unconnected vgl renderer."); | |
| } | |
| renderWindow = m_viewer.renderWindow(); | |
| camera = vglRenderer.camera(); | |
| focusPoint = renderWindow.focusDisplayPoint(); | |
| // Calculate the center in display coordinates | |
| centerDisplay = [m_width / 2, m_height / 2, 0]; | |
| // Calculate the center in world coordinates | |
| centerGeo = renderWindow.displayToWorld( | |
| centerDisplay[0], | |
| centerDisplay[1], | |
| focusPoint, | |
| vglRenderer | |
| ); | |
| newCenterDisplay = [ | |
| centerDisplay[0] + evt.screenDelta.x, | |
| centerDisplay[1] + evt.screenDelta.y | |
| ]; | |
| newCenterGeo = renderWindow.displayToWorld( | |
| newCenterDisplay[0], | |
| newCenterDisplay[1], | |
| focusPoint, | |
| vglRenderer | |
| ); | |
| camera.pan( | |
| centerGeo[0] - newCenterGeo[0], | |
| centerGeo[1] - newCenterGeo[1], | |
| centerGeo[2] - newCenterGeo[2] | |
| ); | |
| evt.center = { | |
| x: newCenterGeo[0], | |
| y: newCenterGeo[1], | |
| z: newCenterGeo[2] | |
| }; | |
| m_this._updateRendererCamera(); | |
| }); | |
| this.geoOn(geo.event.zoom, function (evt) { | |
| var vglRenderer = m_this.contextRenderer(), | |
| camera, | |
| renderWindow, | |
| layer = m_this.layer(), | |
| center, | |
| dir, | |
| focusPoint, | |
| position, | |
| newZ; | |
| // only the base layer needs to respond | |
| if (layer.map().baseLayer() !== layer) { | |
| return; | |
| } | |
| // skip handling if the renderer is unconnected | |
| if (!vglRenderer || !vglRenderer.camera()) { | |
| console.log("Zoom event triggered on unconnected vgl renderer."); | |
| } | |
| renderWindow = m_viewer.renderWindow(); | |
| camera = vglRenderer.camera(); | |
| focusPoint = camera.focalPoint(); | |
| position = camera.position(); | |
| newZ = 360 * Math.pow(2, -evt.zoomLevel); | |
| evt.pan = null; | |
| if (evt.screenPosition) { | |
| center = renderWindow.displayToWorld( | |
| evt.screenPosition.x, | |
| evt.screenPosition.y, | |
| focusPoint, | |
| vglRenderer | |
| ); | |
| dir = [center[0] - position[0], center[1] - position[1], center[2] - position[2]]; | |
| evt.center = layer.fromLocal({ | |
| x: position[0] + dir[0] * (1 - newZ / position[2]), | |
| y: position[1] + dir[1] * (1 - newZ / position[2]) | |
| }); | |
| } | |
| camera.setPosition(position[0], position[1], 360 * Math.pow(2, -evt.zoomLevel)); | |
| m_this._updateRendererCamera(); | |
| }); | |
| return this; | |
| }; | |
| inherit(geo.gl.vglRenderer, geo.gl.renderer); | |
| geo.registerRenderer("vgl", geo.gl.vglRenderer); | |
| /** @namespace */ | |
| geo.d3 = {}; | |
| (function () { | |
| 'use strict'; | |
| var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz', | |
| strLength = 8; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get a random string to use as a div ID | |
| * @returns {string} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.d3.uniqueID = function () { | |
| var strArray = [], | |
| i; | |
| strArray.length = strLength; | |
| for (i = 0; i < strLength; i += 1) { | |
| strArray[i] = chars.charAt(Math.floor(Math.random() * chars.length)); | |
| } | |
| return strArray.join(''); | |
| }; | |
| // event propagated when the d3 renderer rescales its group element | |
| geo.event.d3Rescale = 'geo_d3_rescale'; | |
| }()); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * D3 specific subclass of object which adds an id property for d3 selections | |
| * on groups of objects by class id. | |
| * @class | |
| * @extends geo.sceneObject | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.d3.object = function (arg) { | |
| 'use strict'; | |
| // this is used to extend other geojs classes, so only generate | |
| // a new object when that is not the case... like if this === window | |
| if (!(this instanceof geo.object)) { | |
| return new geo.d3.object(arg); | |
| } | |
| geo.sceneObject.call(this); | |
| var m_id = 'd3-' + geo.d3.uniqueID(), | |
| s_exit = this._exit, | |
| m_this = this, | |
| s_draw = this.draw; | |
| this._d3id = function () { | |
| return m_id; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns a d3 selection for the feature elements | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.select = function () { | |
| return m_this.renderer().select(m_this._d3id()); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Redraw the object. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.draw = function () { | |
| m_this._update(); | |
| s_draw(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Removes the element from the svg and the renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_this.renderer()._removeFeature(m_this._d3id()); | |
| s_exit(); | |
| }; | |
| return this; | |
| }; | |
| inherit(geo.d3.object, geo.sceneObject); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of pointFeature | |
| * | |
| * @class | |
| * @extends geo.pointFeature | |
| * @extends geo.d3.object | |
| * @returns {geo.d3.pointFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.d3.pointFeature = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.d3.pointFeature)) { | |
| return new geo.d3.pointFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.pointFeature.call(this, arg); | |
| geo.d3.object.call(this); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_init = this._init, | |
| s_update = this._update, | |
| m_buildTime = geo.timestamp(), | |
| m_style = {}, | |
| m_sticky; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| s_init.call(m_this, arg); | |
| m_sticky = m_this.layer().sticky(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| var data = m_this.data(), | |
| s_style = m_this.style.get(), | |
| m_renderer = m_this.renderer(), | |
| pos_func = m_this.position(); | |
| // call super-method | |
| s_update.call(m_this); | |
| // default to empty data array | |
| if (!data) { data = []; } | |
| // fill in d3 renderer style object defaults | |
| m_style.id = m_this._d3id(); | |
| m_style.data = data; | |
| m_style.append = 'circle'; | |
| m_style.attributes = { | |
| r: m_renderer._convertScale(s_style.radius), | |
| cx: function (d) { | |
| return m_renderer.worldToDisplay(pos_func(d)).x; | |
| }, | |
| cy: function (d) { | |
| return m_renderer.worldToDisplay(pos_func(d)).y; | |
| } | |
| }; | |
| m_style.style = s_style; | |
| m_style.classes = ['d3PointFeature']; | |
| // pass to renderer to draw | |
| m_this.renderer()._drawFeatures(m_style); | |
| // update time stamps | |
| m_buildTime.modified(); | |
| m_this.updateTime().modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| s_update.call(m_this); | |
| if (m_this.getMTime() >= m_buildTime.getMTime()) { | |
| m_this._build(); | |
| } | |
| return m_this; | |
| }; | |
| this._init(arg); | |
| return this; | |
| }; | |
| inherit(geo.d3.pointFeature, geo.pointFeature); | |
| // Now register it | |
| geo.registerFeature('d3', 'point', geo.d3.pointFeature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class lineFeature | |
| * | |
| * @class | |
| * @extends geo.lineFeature | |
| * @extends geo.d3.object | |
| * @returns {geo.d3.lineFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.d3.lineFeature = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.d3.lineFeature)) { | |
| return new geo.d3.lineFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.lineFeature.call(this, arg); | |
| geo.d3.object.call(this); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_init = this._init, | |
| m_buildTime = geo.timestamp(), | |
| s_update = this._update; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| s_init.call(m_this, arg); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| var data = m_this.data() || [], | |
| s_style = m_this.style(), | |
| m_renderer = m_this.renderer(), | |
| pos_func = m_this.position(), | |
| line = d3.svg.line() | |
| .x(function (d) { return m_renderer.worldToDisplay(d).x; }) | |
| .y(function (d) { return m_renderer.worldToDisplay(d).y; }); | |
| s_update.call(m_this); | |
| s_style.fill = function () { return false; }; | |
| data.forEach(function (item, idx) { | |
| var m_style; | |
| var ln = m_this.line()(item, idx); | |
| var style = {}, key; | |
| function wrapStyle(func) { | |
| if (geo.util.isFunction(func)) { | |
| return function () { | |
| return func(ln[0], 0, item, idx); | |
| }; | |
| } else { | |
| return func; | |
| } | |
| } | |
| for (key in s_style) { | |
| if (s_style.hasOwnProperty(key)) { | |
| style[key] = wrapStyle(s_style[key]); | |
| } | |
| } | |
| // item is an object representing a single line | |
| // m_this.line()(item) is an array of coordinates | |
| m_style = { | |
| data: [ln.map(function (d, i) { return pos_func(d, i, item, idx);})], | |
| append: 'path', | |
| attributes: { | |
| d: line | |
| }, | |
| id: m_this._d3id() + idx, | |
| classes: ['d3LineFeature', 'd3SubLine-' + idx], | |
| style: style | |
| }; | |
| m_renderer._drawFeatures(m_style); | |
| }); | |
| m_buildTime.modified(); | |
| m_this.updateTime().modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| s_update.call(m_this); | |
| if (m_this.getMTime() >= m_buildTime.getMTime()) { | |
| m_this._build(); | |
| } | |
| return m_this; | |
| }; | |
| this._init(arg); | |
| return this; | |
| }; | |
| inherit(geo.d3.lineFeature, geo.lineFeature); | |
| geo.registerFeature('d3', 'line', geo.d3.lineFeature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class pathFeature | |
| * | |
| * @class | |
| * @extends geo.pathFeature | |
| * @extends geo.d3.object | |
| * @returns {geo.d3.pathFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.d3.pathFeature = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.d3.pathFeature)) { | |
| return new geo.d3.pathFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.pathFeature.call(this, arg); | |
| geo.d3.object.call(this); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_init = this._init, | |
| m_buildTime = geo.timestamp(), | |
| s_update = this._update, | |
| m_style = {}; | |
| m_style.style = {}; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| s_init.call(m_this, arg); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| var data = m_this.data() || [], | |
| s_style = m_this.style(), | |
| m_renderer = m_this.renderer(), | |
| tmp, diag; | |
| s_update.call(m_this); | |
| diag = function (d) { | |
| var p = { | |
| source: d.source, | |
| target: d.target | |
| }; | |
| return d3.svg.diagonal()(p); | |
| }; | |
| tmp = []; | |
| data.forEach(function (d, i) { | |
| var src, trg; | |
| if (i < data.length - 1) { | |
| src = d; | |
| trg = data[i + 1]; | |
| tmp.push({ | |
| source: m_renderer.worldToDisplay(src), | |
| target: m_renderer.worldToDisplay(trg) | |
| }); | |
| } | |
| }); | |
| m_style.data = tmp; | |
| m_style.attributes = { | |
| d: diag | |
| }; | |
| m_style.id = m_this._d3id(); | |
| m_style.append = 'path'; | |
| m_style.classes = ['d3PathFeature']; | |
| m_style.style = $.extend({ | |
| 'fill': function () { return false; }, | |
| 'fillColor': function () { return { r: 0, g: 0, b: 0 }; } | |
| }, s_style); | |
| m_this.renderer()._drawFeatures(m_style); | |
| m_buildTime.modified(); | |
| m_this.updateTime().modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update | |
| * | |
| * @override | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| s_update.call(m_this); | |
| if (m_this.dataTime().getMTime() >= m_buildTime.getMTime()) { | |
| m_this._build(); | |
| } | |
| return m_this; | |
| }; | |
| this._init(arg); | |
| return this; | |
| }; | |
| inherit(geo.d3.pathFeature, geo.pathFeature); | |
| geo.registerFeature('d3', 'path', geo.d3.pathFeature); | |
| /** | |
| * @class | |
| * @extends geo.graphFeature | |
| */ | |
| geo.d3.graphFeature = function (arg) { | |
| 'use strict'; | |
| var m_this = this; | |
| if (!(this instanceof geo.d3.graphFeature)) { | |
| return new geo.d3.graphFeature(arg); | |
| } | |
| geo.graphFeature.call(this, arg); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns a d3 selection for the graph elements | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.select = function () { | |
| var renderer = m_this.renderer(), | |
| selection = {}, | |
| node = m_this.nodeFeature(), | |
| links = m_this.linkFeatures(); | |
| selection.nodes = renderer.select(node._d3id()); | |
| selection.links = links.map(function (link) { | |
| return renderer.select(link._d3id()); | |
| }); | |
| return selection; | |
| }; | |
| return this; | |
| }; | |
| inherit(geo.d3.graphFeature, geo.graphFeature); | |
| geo.registerFeature('d3', 'graph', geo.d3.graphFeature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a plane feature given a lower left corner point geo.latlng | |
| * and and upper right corner point geo.latlng | |
| * | |
| * *CURRENTLY BROKEN* | |
| * | |
| * @class | |
| * @extends geo.planeFeature | |
| * @param lowerleft | |
| * @param upperright | |
| * @returns {geo.d3.planeFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.d3.planeFeature = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.d3.planeFeature)) { | |
| return new geo.d3.planeFeature(arg); | |
| } | |
| geo.planeFeature.call(this, arg); | |
| geo.d3.object.call(this); | |
| var m_this = this, | |
| m_style = {}, | |
| s_update = this._update, | |
| s_init = this._init, | |
| m_buildTime = geo.timestamp(); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Normalize a coordinate as an object {x: ..., y: ...} | |
| * | |
| * @private | |
| * @returns {Object} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| function normalize(pt) { | |
| if (Array.isArray(pt)) { | |
| return { | |
| x: pt[0], | |
| y: pt[1] | |
| }; | |
| } else if (pt instanceof geo.latlng) { | |
| return { | |
| x: pt.x(), | |
| y: pt.y() | |
| }; | |
| } | |
| return pt; | |
| } | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build the feature object and pass to the renderer for drawing. | |
| * | |
| * @private | |
| * @returns {geo.d3.planeFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| var origin = normalize(m_this.origin()), | |
| ul = normalize(m_this.upperLeft()), | |
| lr = normalize(m_this.lowerRight()), | |
| renderer = m_this.renderer(), | |
| s = m_this.style(); | |
| delete s.fill_color; | |
| delete s.color; | |
| delete s.opacity; | |
| if (!s.screenCoordinates) { | |
| origin = renderer.worldToDisplay(origin); | |
| ul = renderer.worldToDisplay(ul); | |
| lr = renderer.worldToDisplay(lr); | |
| } | |
| m_style.id = m_this._d3id(); | |
| m_style.style = s; | |
| m_style.attributes = { | |
| x: ul.x, | |
| y: ul.y, | |
| width: lr.x - origin.x, | |
| height: origin.y - ul.y | |
| }; | |
| m_style.append = 'rect'; | |
| m_style.data = [0]; | |
| m_style.classes = ['d3PlaneFeature']; | |
| renderer._drawFeatures(m_style); | |
| m_buildTime.modified(); | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Redraw the plane feature if necessary. | |
| * | |
| * @private | |
| * @returns {geo.d3.planeFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| s_update.call(m_this); | |
| if (m_this.dataTime().getMTime() >= m_buildTime.getMTime()) { | |
| m_this._build(); | |
| } | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initializes the plane feature style (over-riding the parent default). | |
| * | |
| * @private | |
| * @returns {geo.d3.planeFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| s_init.call(m_this, arg || {}); | |
| m_this.style({ | |
| stroke: function () { return false; }, | |
| fill: function () { return true; }, | |
| fillColor: function () { return {r: 0.3, g: 0.3, b: 0.3}; }, | |
| fillOpacity: function () { return 0.5; } | |
| }); | |
| return m_this; | |
| }; | |
| this._init(); | |
| return this; | |
| }; | |
| inherit(geo.d3.planeFeature, geo.planeFeature); | |
| geo.registerFeature('d3', 'plane', geo.d3.planeFeature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of vectorFeature | |
| * | |
| * @class | |
| * @extends geo.vectorFeature | |
| * @extends geo.d3.object | |
| * @returns {geo.d3.vectorFeature} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.d3.vectorFeature = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.d3.vectorFeature)) { | |
| return new geo.d3.vectorFeature(arg); | |
| } | |
| arg = arg || {}; | |
| geo.vectorFeature.call(this, arg); | |
| geo.d3.object.call(this); | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| var m_this = this, | |
| s_init = this._init, | |
| s_exit = this._exit, | |
| s_update = this._update, | |
| m_buildTime = geo.timestamp(), | |
| m_style = {}, | |
| m_sticky; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Generate a unique ID for a marker definition | |
| * @private | |
| * @param {object} d Unused datum (for d3 compat) | |
| * @param {number} i The marker index | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function markerID(d, i) { | |
| return m_this._d3id() + '_marker_' + i; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add marker styles for vector arrows. | |
| * @private | |
| * @param {object[]} data The vector data array | |
| * @param {function} stroke The stroke accessor | |
| * @param {function} opacity The opacity accessor | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function updateMarkers(data, stroke, opacity) { | |
| var renderer = m_this.renderer(); | |
| var sel = m_this.renderer()._definitions() | |
| .selectAll('marker.geo-vector') | |
| .data(data); | |
| sel.enter() | |
| .append('marker') | |
| .attr('id', markerID) | |
| .attr('class', 'geo-vector') | |
| .attr('viewBox', '0 0 10 10') | |
| .attr('refX', '1') | |
| .attr('refY', '5') | |
| .attr('markerWidth', '5') | |
| .attr('markerHeight', '5') | |
| .attr('orient', 'auto') | |
| .append('path') | |
| .attr('d', 'M 0 0 L 10 5 L 0 10 z'); | |
| sel.exit().remove(); | |
| sel.style('stroke', renderer._convertColor(stroke)) | |
| .style('fill', renderer._convertColor(stroke)) | |
| .style('opacity', opacity); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| * @protected | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function (arg) { | |
| s_init.call(m_this, arg); | |
| m_sticky = m_this.layer().sticky(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Build | |
| * @protected | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._build = function () { | |
| var data = m_this.data(), | |
| s_style = m_this.style.get(), | |
| m_renderer = m_this.renderer(), | |
| orig_func = m_this.origin(), | |
| size_func = m_this.delta(), | |
| cache = [], | |
| scale = m_this.style('scale'), | |
| max = Number.NEGATIVE_INFINITY; | |
| // call super-method | |
| s_update.call(m_this); | |
| // default to empty data array | |
| if (!data) { data = []; } | |
| // cache the georeferencing | |
| cache = data.map(function (d, i) { | |
| var origin = m_renderer.worldToDisplay(orig_func(d, i)), | |
| delta = size_func(d, i); | |
| max = Math.max(max, delta.x * delta.x + delta.y * delta.y); | |
| return { | |
| x1: origin.x, | |
| y1: origin.y, | |
| dx: delta.x, | |
| dy: -delta.y | |
| }; | |
| }); | |
| max = Math.sqrt(max); | |
| if (!scale) { | |
| scale = 75 / max; | |
| } | |
| function getScale() { | |
| return scale / m_renderer.scaleFactor(); | |
| } | |
| // fill in d3 renderer style object defaults | |
| m_style.id = m_this._d3id(); | |
| m_style.data = data; | |
| m_style.append = 'line'; | |
| m_style.attributes = { | |
| x1: function (d, i) { | |
| return cache[i].x1; | |
| }, | |
| y1: function (d, i) { | |
| return cache[i].y1; | |
| }, | |
| x2: function (d, i) { | |
| return cache[i].x1 + getScale() * cache[i].dx; | |
| }, | |
| y2: function (d, i) { | |
| return cache[i].y1 + getScale() * cache[i].dy; | |
| }, | |
| 'marker-end': function (d, i) { | |
| return 'url(#' + markerID(d, i) + ')'; | |
| } | |
| }; | |
| m_style.style = { | |
| stroke: function () { return true; }, | |
| strokeColor: s_style.strokeColor, | |
| strokeWidth: s_style.strokeWidth, | |
| strokeOpacity: s_style.strokeOpacity | |
| }; | |
| m_style.classes = ['d3VectorFeature']; | |
| // Add markers to the defition list | |
| updateMarkers(data, s_style.strokeColor, s_style.strokeOpacity); | |
| // pass to renderer to draw | |
| m_this.renderer()._drawFeatures(m_style); | |
| // update time stamps | |
| m_buildTime.modified(); | |
| m_this.updateTime().modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update | |
| * @protected | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| s_update.call(m_this); | |
| if (m_this.getMTime() >= m_buildTime.getMTime()) { | |
| m_this._build(); | |
| } else { | |
| updateMarkers( | |
| m_style.data, | |
| m_style.style.strokeColor, | |
| m_style.style.strokeOpacity | |
| ); | |
| } | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Exit | |
| * @protected | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| s_exit.call(m_this); | |
| m_style = {}; | |
| updateMarkers([], null, null); | |
| }; | |
| this._init(arg); | |
| return this; | |
| }; | |
| inherit(geo.d3.vectorFeature, geo.vectorFeature); | |
| // Now register it | |
| geo.registerFeature('d3', 'vector', geo.d3.vectorFeature); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class d3Renderer | |
| * | |
| * @class | |
| * @extends geo.renderer | |
| * @returns {geo.d3.d3Renderer} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.d3.d3Renderer = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.d3.d3Renderer)) { | |
| return new geo.d3.d3Renderer(arg); | |
| } | |
| geo.renderer.call(this, arg); | |
| var s_exit = this._exit; | |
| geo.d3.object.call(this, arg); | |
| arg = arg || {}; | |
| var m_this = this, | |
| m_sticky = null, | |
| m_features = {}, | |
| m_corners = null, | |
| m_width = null, | |
| m_height = null, | |
| m_scale = 1, | |
| m_dx = 0, | |
| m_dy = 0, | |
| m_svg = null, | |
| m_defs = null; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set attributes to a d3 selection. | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function setAttrs(select, attrs) { | |
| var key; | |
| for (key in attrs) { | |
| if (attrs.hasOwnProperty(key)) { | |
| select.attr(key, attrs[key]); | |
| } | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Meta functions for converting from geojs styles to d3. | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._convertColor = function (f, g) { | |
| f = geo.util.ensureFunction(f); | |
| g = g || function () { return true; }; | |
| return function () { | |
| var c = 'none'; | |
| if (g.apply(this, arguments)) { | |
| c = f.apply(this, arguments); | |
| if (c.hasOwnProperty('r') && | |
| c.hasOwnProperty('g') && | |
| c.hasOwnProperty('b')) { | |
| c = d3.rgb(255 * c.r, 255 * c.g, 255 * c.b); | |
| } | |
| } | |
| return c; | |
| }; | |
| }; | |
| this._convertPosition = function (f) { | |
| f = geo.util.ensureFunction(f); | |
| return function () { | |
| return m_this.worldToDisplay(f.apply(this, arguments)); | |
| }; | |
| }; | |
| this._convertScale = function (f) { | |
| f = geo.util.ensureFunction(f); | |
| return function () { | |
| return f.apply(this, arguments) / m_scale; | |
| }; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set styles to a d3 selection. Ignores unkown style keys. | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function setStyles(select, styles) { | |
| /* jshint validthis:true */ | |
| var key, k, f; | |
| function fillFunc() { | |
| if (styles.fill.apply(this, arguments)) { | |
| return null; | |
| } else { | |
| return 'none'; | |
| } | |
| } | |
| function strokeFunc() { | |
| if (styles.stroke.apply(this, arguments)) { | |
| return null; | |
| } else { | |
| return 'none'; | |
| } | |
| } | |
| for (key in styles) { | |
| if (styles.hasOwnProperty(key)) { | |
| f = null; | |
| k = null; | |
| if (key === 'strokeColor') { | |
| k = 'stroke'; | |
| f = m_this._convertColor(styles[key], styles.stroke); | |
| } else if (key === 'stroke' && styles[key]) { | |
| k = 'stroke'; | |
| f = strokeFunc; | |
| } else if (key === 'strokeWidth') { | |
| k = 'stroke-width'; | |
| f = m_this._convertScale(styles[key]); | |
| } else if (key === 'strokeOpacity') { | |
| k = 'stroke-opacity'; | |
| f = styles[key]; | |
| } else if (key === 'fillColor') { | |
| k = 'fill'; | |
| f = m_this._convertColor(styles[key], styles.fill); | |
| } else if (key === 'fill' && !styles.hasOwnProperty('fillColor')) { | |
| k = 'fill'; | |
| f = fillFunc; | |
| } else if (key === 'fillOpacity') { | |
| k = 'fill-opacity'; | |
| f = styles[key]; | |
| } | |
| if (k) { | |
| select.style(k, f); | |
| } | |
| } | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the map instance or return null if not connected to a map. | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function getMap() { | |
| var layer = m_this.layer(); | |
| if (!layer) { | |
| return null; | |
| } | |
| return layer.map(); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the svg group element associated with this renderer instance. | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function getGroup() { | |
| return m_svg.select('.group-' + m_this._d3id()); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set the initial lat-lon coordinates of the map view. | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function initCorners() { | |
| var layer = m_this.layer(), | |
| map = layer.map(), | |
| width = m_this.layer().width(), | |
| height = m_this.layer().height(); | |
| m_width = width; | |
| m_height = height; | |
| if (!m_width || !m_height) { | |
| throw 'Map layer has size 0'; | |
| } | |
| m_corners = { | |
| 'upperLeft': map.displayToGcs({'x': 0, 'y': 0}), | |
| 'lowerRight': map.displayToGcs({'x': width, 'y': height}) | |
| }; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Set the translation, scale, and zoom for the current view. | |
| * @note rotation not yet supported | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function setTransform() { | |
| if (!m_corners) { | |
| initCorners(); | |
| } | |
| if (!m_sticky) { | |
| return; | |
| } | |
| var layer = m_this.layer(), | |
| map = layer.map(), | |
| upperLeft = map.gcsToDisplay(m_corners.upperLeft), | |
| lowerRight = map.gcsToDisplay(m_corners.lowerRight), | |
| group = getGroup(), | |
| dx, dy, scale; | |
| // calculate the translation | |
| dx = upperLeft.x; | |
| dy = upperLeft.y; | |
| // calculate the scale | |
| scale = (lowerRight.y - upperLeft.y) / m_height; | |
| // set the group transform property | |
| group.attr('transform', 'matrix(' + [scale, 0, 0, scale, dx, dy].join() + ')'); | |
| // set internal variables | |
| m_scale = scale; | |
| m_dx = dx; | |
| m_dy = dy; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert from screen pixel coordinates to the local coordinate system | |
| * in the SVG group element taking into account the transform. | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function baseToLocal(pt) { | |
| return { | |
| x: (pt.x - m_dx) / m_scale, | |
| y: (pt.y - m_dy) / m_scale | |
| }; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert from the local coordinate system in the SVG group element | |
| * to screen pixel coordinates. | |
| * @private | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| function localToBase(pt) { | |
| return { | |
| x: pt.x * m_scale + m_dx, | |
| y: pt.y * m_scale + m_dy | |
| }; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._init = function () { | |
| if (!m_this.canvas()) { | |
| var canvas; | |
| m_svg = d3.select(m_this.layer().node().get(0)).append('svg'); | |
| // create a global svg definitions element | |
| m_defs = m_svg.append('defs'); | |
| var shadow = m_defs | |
| .append('filter') | |
| .attr('id', 'geo-highlight') | |
| .attr('x', '-100%') | |
| .attr('y', '-100%') | |
| .attr('width', '300%') | |
| .attr('height', '300%'); | |
| shadow | |
| .append('feMorphology') | |
| .attr('operator', 'dilate') | |
| .attr('radius', 2) | |
| .attr('in', 'SourceAlpha') | |
| .attr('result', 'dilateOut'); | |
| shadow | |
| .append('feGaussianBlur') | |
| .attr('stdDeviation', 5) | |
| .attr('in', 'dilateOut') | |
| .attr('result', 'blurOut'); | |
| shadow | |
| .append('feColorMatrix') | |
| .attr('type', 'matrix') | |
| .attr('values', '-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0') | |
| .attr('in', 'blurOut') | |
| .attr('result', 'invertOut'); | |
| shadow | |
| .append('feBlend') | |
| .attr('in', 'SourceGraphic') | |
| .attr('in2', 'invertOut') | |
| .attr('mode', 'normal'); | |
| canvas = m_svg.append('g'); | |
| shadow = m_defs.append('filter') | |
| .attr('id', 'geo-blur') | |
| .attr('x', '-100%') | |
| .attr('y', '-100%') | |
| .attr('width', '300%') | |
| .attr('height', '300%'); | |
| shadow | |
| .append('feGaussianBlur') | |
| .attr('stdDeviation', 20) | |
| .attr('in', 'SourceGraphic'); | |
| m_sticky = m_this.layer().sticky(); | |
| m_svg.attr('class', m_this._d3id()); | |
| m_svg.attr('width', m_this.layer().node().width()); | |
| m_svg.attr('height', m_this.layer().node().height()); | |
| canvas.attr('class', 'group-' + m_this._d3id()); | |
| m_this.canvas(canvas); | |
| } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert from coordinates in the svg group element to lat/lon. | |
| * Supports objects or arrays of objects. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.displayToWorld = function (pt) { | |
| var map = getMap(); | |
| if (!map) { | |
| throw 'Cannot project until this layer is connected to a map.'; | |
| } | |
| if (Array.isArray(pt)) { | |
| pt = pt.map(function (x) { | |
| return map.displayToGcs(localToBase(x)); | |
| }); | |
| } else { | |
| pt = map.displayToGcs(localToBase(pt)); | |
| } | |
| return pt; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Convert from lat/lon to pixel coordinates in the svg group element. | |
| * Supports objects or arrays of objects. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.worldToDisplay = function (pt) { | |
| var map = getMap(); | |
| if (!map) { | |
| throw 'Cannot project until this layer is connected to a map.'; | |
| } | |
| var v; | |
| if (Array.isArray(pt)) { | |
| v = pt.map(function (x) { | |
| return baseToLocal(map.gcsToDisplay(x)); | |
| }); | |
| } else { | |
| v = baseToLocal(map.gcsToDisplay(pt)); | |
| } | |
| return v; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get API used by the renderer | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.api = function () { | |
| return 'd3'; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return the current scaling factor to build features that shouldn't | |
| * change size during zooms. For example: | |
| * | |
| * selection.append('circle') | |
| * .attr('r', r0 / renderer.scaleFactor()); | |
| * | |
| * This will create a circle element with radius r0 independent of the | |
| * current zoom level. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.scaleFactor = function () { | |
| return m_scale; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Handle resize event | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._resize = function (x, y, w, h) { | |
| if (!m_corners) { | |
| initCorners(); | |
| } | |
| m_svg.attr('width', w); | |
| m_svg.attr('height', h); | |
| setTransform(); | |
| m_this.layer().geoTrigger(geo.event.d3Rescale, { scale: m_scale }, true); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update noop for geo.d3.object api. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._update = function () { | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Exit | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_features = {}; | |
| m_this.canvas().remove(); | |
| s_exit(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the definitions dom element for the layer | |
| * @protected | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._definitions = function () { | |
| return m_defs; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new feature element from an object that describes the feature | |
| * attributes. To be called from feature classes only. | |
| * | |
| * Input: | |
| * { | |
| * id: A unique string identifying the feature. | |
| * data: Array of data objects used in a d3 data method. | |
| * index: A function that returns a unique id for each data element. | |
| * style: An object containing element CSS styles. | |
| * attributes: An object containing element attributes. | |
| * classes: An array of classes to add to the elements. | |
| * append: The element type as used in d3 append methods. | |
| * } | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._drawFeatures = function (arg) { | |
| m_features[arg.id] = { | |
| data: arg.data, | |
| index: arg.dataIndex, | |
| style: arg.style, | |
| attributes: arg.attributes, | |
| classes: arg.classes, | |
| append: arg.append | |
| }; | |
| return m_this.__render(arg.id); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Updates a feature by performing a d3 data join. If no input id is | |
| * provided then this method will update all features. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.__render = function (id) { | |
| var key; | |
| if (id === undefined) { | |
| for (key in m_features) { | |
| if (m_features.hasOwnProperty(key)) { | |
| m_this.__render(key); | |
| } | |
| } | |
| return m_this; | |
| } | |
| var data = m_features[id].data, | |
| index = m_features[id].index, | |
| style = m_features[id].style, | |
| attributes = m_features[id].attributes, | |
| classes = m_features[id].classes, | |
| append = m_features[id].append, | |
| selection = m_this.select(id).data(data, index); | |
| selection.enter().append(append); | |
| selection.exit().remove(); | |
| setAttrs(selection, attributes); | |
| selection.attr('class', classes.concat([id]).join(' ')); | |
| setStyles(selection, style); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns a d3 selection for the given feature id. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.select = function (id) { | |
| return getGroup().selectAll('.' + id); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Removes a feature from the layer. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._removeFeature = function (id) { | |
| m_this.select(id).remove(); | |
| delete m_features[id]; | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Override draw method to do nothing. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.draw = function () { | |
| }; | |
| // connect to pan event | |
| this.layer().geoOn(geo.event.pan, setTransform); | |
| // connect to zoom event | |
| this.layer().geoOn(geo.event.zoom, function () { | |
| setTransform(); | |
| m_this.__render(); | |
| m_this.layer().geoTrigger(geo.event.d3Rescale, { scale: m_scale }, true); | |
| }); | |
| this.layer().geoOn(geo.event.resize, function (event) { | |
| m_this._resize(event.x, event.y, event.width, event.height); | |
| }); | |
| this._init(arg); | |
| return this; | |
| }; | |
| inherit(geo.d3.d3Renderer, geo.renderer); | |
| geo.registerRenderer('d3', geo.d3.d3Renderer); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * @namespace | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gui = {}; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class uiLayer | |
| * | |
| * @class | |
| * @extends {geo.layer} | |
| * @returns {geo.gui.uiLayer} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gui.uiLayer = function (arg) { | |
| 'use strict'; | |
| // The widget stays fixed on the screen. (only available in d3 at the moment) | |
| arg.renderer = 'd3'; | |
| arg.sticky = false; | |
| if (!(this instanceof geo.gui.uiLayer)) { | |
| return new geo.gui.uiLayer(arg); | |
| } | |
| geo.layer.call(this, arg); | |
| var m_this = this, | |
| s_exit = this._exit; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new ui control | |
| * | |
| * @returns {geo.gui.Widget} Will return a new control widget | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.createWidget = function (widgetName, arg) { | |
| var newWidget = geo.createWidget( | |
| widgetName, m_this, m_this.renderer(), arg); | |
| m_this.addChild(newWidget); | |
| newWidget._init(); | |
| m_this.modified(); | |
| return newWidget; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Delete a ui control | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.deleteWidget = function (widget) { | |
| widget._exit(); | |
| m_this.removeChild(widget); | |
| m_this.modified(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Free memory and destroy the layer. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_this.children().forEach(function (child) { | |
| m_this.deleteWidget(child); | |
| }); | |
| s_exit(); | |
| }; | |
| }; | |
| inherit(geo.gui.uiLayer, geo.layer); | |
| geo.registerLayer('ui', geo.gui.uiLayer); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class widget | |
| * | |
| * @class | |
| * @extends {geo.sceneObject} | |
| * @returns {geo.gui.widget} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gui.widget = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.gui.widget)) { | |
| return new geo.gui.widget(arg); | |
| } | |
| geo.sceneObject.call(this, arg); | |
| var m_this = this, | |
| s_exit = this._exit, | |
| m_layer = arg.layer; | |
| this._init = function () { | |
| m_this.modified(); | |
| }; | |
| this._exit = function () { | |
| m_this.children().forEach(function (child) { | |
| m_this._deleteFeature(child); | |
| }); | |
| s_exit(); | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create feature give a name | |
| * | |
| * @returns {geo.Feature} Will return a new feature | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._createFeature = function (featureName, arg) { | |
| var newFeature = geo.createFeature( | |
| featureName, m_this, m_this.renderer(), arg); | |
| m_this.addChild(newFeature); | |
| m_this.modified(); | |
| return newFeature; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Delete feature | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this._deleteFeature = function (feature) { | |
| m_this.removeChild(feature); | |
| feature._exit(); | |
| return m_this; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Return the layer associated with this widget. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| this.layer = function () { | |
| return m_layer; | |
| }; | |
| }; | |
| inherit(geo.gui.widget, geo.sceneObject); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class sliderWidget | |
| * | |
| * @class | |
| * @extends {geo.gui.widget} | |
| * @returns {geo.gui.sliderWidget} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gui.sliderWidget = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.gui.sliderWidget)) { | |
| return new geo.gui.sliderWidget(arg); | |
| } | |
| geo.gui.widget.call(this, arg); | |
| var m_this = this, | |
| s_exit = this._exit, | |
| m_xscale, | |
| m_yscale, | |
| m_plus, | |
| m_minus, | |
| m_track, | |
| m_nub, | |
| m_width = 20, // Approximate size of the widget in pixels | |
| m_height = 100, | |
| m_nubSize = 10, | |
| m_plusIcon, | |
| m_minusIcon, | |
| m_group, | |
| m_lowContrast, | |
| m_highlightDur = 100; | |
| /* http://icomoon.io */ | |
| /* CC BY 3.0 http://creativecommons.org/licenses/by/3.0/ */ | |
| /* jshint -W101 */ | |
| m_plusIcon = 'M512 81.92c-237.568 0-430.080 192.614-430.080 430.080 0 237.568 192.563 430.080 430.080 430.080s430.080-192.563 430.080-430.080c0-237.517-192.563-430.080-430.080-430.080zM564.326 564.326v206.182h-104.653v-206.182h-206.234v-104.653h206.182v-206.234h104.704v206.182h206.182v104.704h-206.182z'; | |
| m_minusIcon = 'M512 81.92c-237.568 0-430.080 192.614-430.080 430.080 0 237.568 192.563 430.080 430.080 430.080s430.080-192.563 430.080-430.080c0-237.517-192.563-430.080-430.080-430.080zM770.56 459.674v104.704h-517.12v-104.704h517.12z'; | |
| /* jshint +W101 */ | |
| // Define off-white gray colors for low contrast ui (unselected). | |
| m_lowContrast = { | |
| white: '#f4f4f4', | |
| black: '#505050' | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Add an icon from a path string. Returns a d3 group element. | |
| * | |
| * @function | |
| * @argument {String} icon svg path string | |
| * @argument {Array} base where to append the element (d3 selection) | |
| * @argument {Number} cx Center x-coordinate | |
| * @argument {Number} cy Center y-coordinate | |
| * @argument {Number} size Icon size in pixels | |
| * @returns {object} | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| function put_icon(icon, base, cx, cy, size) { | |
| var g = base.append('g'); | |
| // the scale factor | |
| var s = size / 1024; | |
| g.append('g') | |
| .append('g') | |
| .attr( | |
| 'transform', | |
| 'translate(' + cx + ',' + cy + ') scale(' + s + ') translate(-512,-512)' | |
| ) | |
| .append('path') | |
| .attr('d', icon) | |
| .attr('class', 'geo-glyphicon'); | |
| return g; | |
| } | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initialize the slider widget in the map. | |
| * | |
| * @function | |
| * @returns {geo.gui.sliderWidget} | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._init = function () { | |
| var svg = m_this.layer().renderer().canvas(), | |
| x0 = 40, | |
| y0 = 40 + m_width, | |
| map = m_this.layer().map(); | |
| // create d3 scales for positioning | |
| // TODO: make customizable and responsive | |
| m_xscale = d3.scale.linear().domain([-4, 4]).range([x0, x0 + m_width]); | |
| m_yscale = d3.scale.linear().domain([0, 1]).range([y0, y0 + m_height]); | |
| // Create the main group element | |
| svg = svg.append('g').classed('geo-ui-slider', true); | |
| m_group = svg; | |
| // Create + zoom button | |
| m_plus = svg.append('g'); | |
| m_plus.append('circle') | |
| .datum({ | |
| fill: 'white', | |
| stroke: null | |
| }) | |
| .classed('geo-zoom-in', true) | |
| .attr('cx', m_xscale(0)) | |
| .attr('cy', m_yscale(0.0) - m_width + 2) | |
| .attr('r', m_width / 2) | |
| .style({ | |
| 'cursor': 'pointer' | |
| }) | |
| .on('click', function () { | |
| var z = map.zoom(); | |
| map.transition({ | |
| zoom: z + 1, | |
| ease: d3.ease('cubic-in-out'), | |
| duration: 500 | |
| }); | |
| }) | |
| .on('mousedown', function () { | |
| d3.event.stopPropagation(); | |
| }); | |
| put_icon( | |
| m_plusIcon, | |
| m_plus, | |
| m_xscale(0), | |
| m_yscale(0) - m_width + 2, | |
| m_width + 5 | |
| ).style('cursor', 'pointer') | |
| .style('pointer-events', 'none') | |
| .select('path') | |
| .datum({ | |
| fill: 'black', | |
| stroke: null | |
| }); | |
| // Create the - zoom button | |
| m_minus = svg.append('g'); | |
| m_minus.append('circle') | |
| .datum({ | |
| fill: 'white', | |
| stroke: null | |
| }) | |
| .classed('geo-zoom-out', true) | |
| .attr('cx', m_xscale(0)) | |
| .attr('cy', m_yscale(1.0) + m_width - 2) | |
| .attr('r', m_width / 2) | |
| .style({ | |
| 'cursor': 'pointer' | |
| }) | |
| .on('click', function () { | |
| var z = map.zoom(); | |
| map.transition({ | |
| zoom: z - 1, | |
| ease: d3.ease('cubic-in-out'), | |
| duration: 500 | |
| }); | |
| }) | |
| .on('mousedown', function () { | |
| d3.event.stopPropagation(); | |
| }); | |
| put_icon( | |
| m_minusIcon, | |
| m_minus, | |
| m_xscale(0), | |
| m_yscale(1) + m_width - 2, | |
| m_width + 5 | |
| ).style('cursor', 'pointer') | |
| .style('pointer-events', 'none') | |
| .select('path') | |
| .datum({ | |
| fill: 'black', | |
| stroke: null | |
| }); | |
| // Respond to a mouse event on the widget | |
| function respond(evt, trans) { | |
| var z = m_yscale.invert(d3.mouse(m_this.layer().node()[0])[1]), | |
| zrange = map.zoomRange(); | |
| z = (1 - z) * (zrange.max - zrange.min) + zrange.min; | |
| if (trans) { | |
| map.transition({ | |
| zoom: z, | |
| ease: d3.ease('cubic-in-out'), | |
| duration: 500, | |
| done: m_this._update() | |
| }); | |
| } else { | |
| map.zoom(z); | |
| m_this._update(); | |
| } | |
| evt.stopPropagation(); | |
| } | |
| // Create the track | |
| m_track = svg.append('rect') | |
| .datum({ | |
| fill: 'white', | |
| stroke: 'black' | |
| }) | |
| .classed('geo-zoom-track', true) | |
| .attr('x', m_xscale(0) - m_width / 6) | |
| .attr('y', m_yscale(0)) | |
| .attr('rx', m_width / 10) | |
| .attr('ry', m_width / 10) | |
| .attr('width', m_width / 3) | |
| .attr('height', m_height) | |
| .style({ | |
| 'cursor': 'pointer' | |
| }) | |
| .on('click', function () { | |
| respond(d3.event, true); | |
| }); | |
| // Create the nub | |
| m_nub = svg.append('rect') | |
| .datum({ | |
| fill: 'black', | |
| stroke: null | |
| }) | |
| .classed('geo-zoom-nub', true) | |
| .attr('x', m_xscale(-4)) | |
| .attr('y', m_yscale(0.5) - m_nubSize / 2) | |
| .attr('rx', 3) | |
| .attr('ry', 3) | |
| .attr('width', m_width) | |
| .attr('height', m_nubSize) | |
| .style({ | |
| 'cursor': 'pointer' | |
| }) | |
| .on('mousedown', function () { | |
| d3.select(document).on('mousemove.geo.slider', function () { | |
| respond(d3.event); | |
| }); | |
| d3.select(document).on('mouseup.geo.slider', function () { | |
| respond(d3.event); | |
| d3.select(document).on('.geo.slider', null); | |
| }); | |
| d3.event.stopPropagation(); | |
| }); | |
| var mouseOver = function () { | |
| d3.select(this).attr('filter', 'url(#geo-highlight)'); | |
| m_group.selectAll('rect,path,circle').transition() | |
| .duration(m_highlightDur) | |
| .style('fill', function (d) { | |
| return d.fill || null; | |
| }) | |
| .style('stroke', function (d) { | |
| return d.stroke || null; | |
| }); | |
| }; | |
| var mouseOut = function () { | |
| d3.select(this).attr('filter', null); | |
| m_group.selectAll('circle,rect,path').transition() | |
| .duration(m_highlightDur) | |
| .style('fill', function (d) { | |
| return m_lowContrast[d.fill] || null; | |
| }) | |
| .style('stroke', function (d) { | |
| return m_lowContrast[d.stroke] || null; | |
| }); | |
| }; | |
| m_group.selectAll('*') | |
| .on('mouseover', mouseOver) | |
| .on('mouseout', mouseOut); | |
| // Update the nub position on zoom | |
| m_this.layer().geoOn(geo.event.zoom, function () { | |
| m_this._update(); | |
| }); | |
| mouseOut(); | |
| m_this._update(); | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Removes the slider element from the map and unbinds all handlers. | |
| * | |
| * @function | |
| * @returns {geo.gui.sliderWidget} | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._exit = function () { | |
| m_group.remove(); | |
| m_this.layer().geoOff(geo.event.zoom); | |
| s_exit(); | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Update the slider widget state in reponse to map changes. I.e. zoom | |
| * range changes. | |
| * | |
| * @function | |
| * @returns {geo.gui.sliderWidget} | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._update = function (obj) { | |
| var map = m_this.layer().map(), | |
| zoomRange = map.zoomRange(), | |
| zoom = map.zoom(), | |
| zoomScale = d3.scale.linear(); | |
| obj = obj || {}; | |
| zoom = obj.value || zoom; | |
| zoomScale.domain([zoomRange.min, zoomRange.max]) | |
| .range([1, 0]) | |
| .clamp(true); | |
| m_nub.attr('y', m_yscale(zoomScale(zoom)) - m_nubSize / 2); | |
| }; | |
| }; | |
| inherit(geo.gui.sliderWidget, geo.gui.widget); | |
| geo.registerWidget('d3', 'slider', geo.gui.sliderWidget); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Create a new instance of class legendWidget | |
| * | |
| * @class | |
| * @extends geo.gui.widget | |
| * @returns {geo.gui.legendWidget} | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| geo.gui.legendWidget = function (arg) { | |
| 'use strict'; | |
| if (!(this instanceof geo.gui.legendWidget)) { | |
| return new geo.gui.legendWidget(arg); | |
| } | |
| geo.gui.widget.call(this, arg); | |
| /** @private */ | |
| var m_this = this, | |
| m_categories = [], | |
| m_top = null, | |
| m_group = null, | |
| m_border = null, | |
| m_spacing = 20, // distance in pixels between lines | |
| m_padding = 12; // padding in pixels inside the border | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get or set the category array associated with | |
| * the legend. Each element of this array is | |
| * an object: :: | |
| * { | |
| * name: string, | |
| * style: object, | |
| * type: 'point' | 'line' | ... | |
| * } | |
| * | |
| * The style property can contain the following feature styles: | |
| * * fill: bool | |
| * * fillColor: object | string | |
| * * fillOpacity: number | |
| * * stroke: bool | |
| * * strokeColor: object | string | |
| * * strokeWidth: number | |
| * * strokeOpacity: number | |
| * | |
| * The type controls how the element is displayed, point as a circle, | |
| * line as a line segment. Any other value will display as a rounded | |
| * rectangle. | |
| * | |
| * @param {object[]?} categories The categories to display | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.categories = function (arg) { | |
| if (arg === undefined) { | |
| return m_categories.slice(); | |
| } | |
| m_categories = arg.slice().map(function (d) { | |
| if (d.type === 'line') { | |
| d.style.fill = false; | |
| d.style.stroke = true; | |
| } | |
| return d; | |
| }); | |
| m_this.draw(); | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get the widget's size | |
| * @return {{width: number, height: number}} The size in pixels | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.size = function () { | |
| var width = 1, height; | |
| var test = m_this.layer().renderer().canvas().append('text') | |
| .style('opacity', 1e-6); | |
| m_categories.forEach(function (d) { | |
| test.text(d.name); | |
| width = Math.max(width, test.node().getBBox().width); | |
| }); | |
| test.remove(); | |
| height = m_spacing * (m_categories.length + 1); | |
| return { | |
| width: width + 50, | |
| height: height | |
| }; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Redraw the legend | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this.draw = function () { | |
| m_this._init(); | |
| function applyColor(selection) { | |
| selection.style('fill', function (d) { | |
| if (d.style.fill || d.style.fill === undefined) { | |
| return d.style.fillColor; | |
| } else { | |
| return 'none'; | |
| } | |
| }) | |
| .style('fill-opacity', function (d) { | |
| if (d.style.fillOpacity === undefined) { | |
| return 1; | |
| } | |
| return d.style.fillOpacity; | |
| }) | |
| .style('stroke', function (d) { | |
| if (d.style.stroke || d.style.stroke === undefined) { | |
| return d.style.strokeColor; | |
| } else { | |
| return 'none'; | |
| } | |
| }) | |
| .style('stroke-opacity', function (d) { | |
| if (d.style.strokeOpacity === undefined) { | |
| return 1; | |
| } | |
| return d.style.strokeOpacity; | |
| }) | |
| .style('stroke-width', function (d) { | |
| if (d.style.strokeWidth === undefined) { | |
| return 1.5; | |
| } | |
| return d.style.strokeWidth; | |
| }); | |
| } | |
| m_border.attr('height', m_this.size().height + 2 * m_padding) | |
| .style('display', null); | |
| var scale = m_this._scale(); | |
| var labels = m_group.selectAll('g.geo-label') | |
| .data(m_categories, function (d) { return d.name; }); | |
| var g = labels.enter().append('g') | |
| .attr('class', 'geo-label') | |
| .attr('transform', function (d, i) { | |
| return 'translate(0,' + scale.y(i) + ')'; | |
| }); | |
| applyColor(g.filter(function (d) { | |
| return d.type !== 'point' && d.type !== 'line'; | |
| }).append('rect') | |
| .attr('x', 0) | |
| .attr('y', -6) | |
| .attr('rx', 5) | |
| .attr('ry', 5) | |
| .attr('width', 40) | |
| .attr('height', 12) | |
| ); | |
| applyColor(g.filter(function (d) { | |
| return d.type === 'point'; | |
| }).append('circle') | |
| .attr('cx', 20) | |
| .attr('cy', 0) | |
| .attr('r', 6) | |
| ); | |
| applyColor(g.filter(function (d) { | |
| return d.type === 'line'; | |
| }).append('line') | |
| .attr('x1', 0) | |
| .attr('y1', 0) | |
| .attr('x2', 40) | |
| .attr('y2', 0) | |
| ); | |
| g.append('text') | |
| .attr('x', '50px') | |
| .attr('y', 0) | |
| .attr('dy', '0.3em') | |
| .text(function (d) { | |
| return d.name; | |
| }); | |
| return m_this; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Get scales for the x and y axis for the current size. | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._scale = function () { | |
| return { | |
| x: d3.scale.linear() | |
| .domain([0, 1]) | |
| .range([0, m_this.size().width]), | |
| y: d3.scale.linear() | |
| .domain([0, m_categories.length - 1]) | |
| .range([m_padding / 2, m_this.size().height - m_padding / 2]) | |
| }; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Private initialization. Creates the widget's DOM container and internal | |
| * variables. | |
| * @private | |
| */ | |
| ////////////////////////////////////////////////////////////////////////////// | |
| this._init = function () { | |
| var w = m_this.size().width + 2 * m_padding, | |
| h = m_this.size().height + 2 * m_padding, | |
| nw = m_this.layer().map().node().width(), | |
| margin = 20; | |
| if (m_top) { | |
| m_top.remove(); | |
| } | |
| m_top = m_this.layer().renderer().canvas().append('g') | |
| .attr('transform', 'translate(' + (nw - w - margin) + ',' + margin + ')'); | |
| m_group = m_top | |
| .append('g') | |
| .attr('transform', 'translate(' + [m_padding - 1.5, m_padding] + ')'); | |
| m_border = m_group.append('rect') | |
| .attr('x', -m_padding) | |
| .attr('y', -m_padding) | |
| .attr('width', w) | |
| .attr('height', h) | |
| .attr('rx', 3) | |
| .attr('ry', 3) | |
| .style({ | |
| 'stroke': 'black', | |
| 'stroke-width': '1.5px', | |
| 'fill': 'white', | |
| 'fill-opacity': 0.75, | |
| 'display': 'none' | |
| }); | |
| m_group.on('mousedown', function () { | |
| d3.event.stopPropagation(); | |
| }); | |
| m_group.on('mouseover', function () { | |
| m_border.transition() | |
| .duration(250) | |
| .style('fill-opacity', 1); | |
| }); | |
| m_group.on('mouseout', function () { | |
| m_border.transition() | |
| .duration(250) | |
| .style('fill-opacity', 0.75); | |
| }); | |
| }; | |
| this.geoOn(geo.event.resize, function () { | |
| this.draw(); | |
| }); | |
| }; | |
| inherit(geo.gui.legendWidget, geo.gui.widget); | |
| geo.registerWidget('d3', 'legend', geo.gui.legendWidget); | |
| /*jscs:disable validateIndentation*/ | |
| (function ($, geo, d3) { | |
| 'use strict'; | |
| var load = function () { | |
| // This requires jquery ui, which we don't want to make a | |
| // hard requirement, so bail out here if the widget factory | |
| // is not available and throw a helpful message when the | |
| // tries to use it. | |
| if (!$.widget) { | |
| $.fn.geojsMap = function () { | |
| throw new Error( | |
| 'The geojs jquery plugin requires jquery ui to be available.' | |
| ); | |
| }; | |
| return; | |
| } | |
| /** | |
| * Takes an option key and returns true if it should | |
| * return a color accessor. | |
| * @private | |
| */ | |
| function isColorKey(key) { | |
| return key.slice(key.length - 5, key.length) | |
| .toLowerCase() === 'color'; | |
| } | |
| /** | |
| * Take an array of data and an accessor for a color property | |
| * and return a wrapped accessor mapping to actual color | |
| * values. This allows users to pass arbitrary strings | |
| * or numbers as any color property and this will wrap | |
| * a categorical scale or linear scale. | |
| * | |
| * Requires d3 | |
| * @private | |
| * @param {Object[]} data A data array | |
| * @param {(string|number|function)} acc A color accessor | |
| * @return {function} | |
| */ | |
| function makeColorScale(data, acc) { | |
| if (!d3) { | |
| console.warn('d3 is unavailable, cannot apply color scales.'); | |
| return acc; | |
| } | |
| var domain; | |
| var cannotHandle = false; | |
| var doNotHandle = true; | |
| var categorical = false; | |
| var min = Number.POSITIVE_INFINITY; | |
| var max = Number.NEGATIVE_INFINITY; | |
| function wrap(func) { | |
| if (geo.util.isFunction(func)) { | |
| return function () { | |
| return func(acc.apply(this, arguments)); | |
| }; | |
| } else { | |
| return func(acc); | |
| } | |
| } | |
| if (geo.util.isFunction(acc)) { | |
| domain = d3.set(data.map(acc)).values(); | |
| } else { | |
| domain = [acc]; | |
| } | |
| domain.forEach(function (v) { | |
| if (!(typeof v === 'string' && | |
| typeof geo.util.convertColor(v) === 'object')) { | |
| // This is to handle cases when values are css names or | |
| // hex strings. We don't want to apply a categorical | |
| // scale. | |
| doNotHandle = false; | |
| } | |
| if (typeof v === 'string') { | |
| categorical = true; | |
| } else if (!isFinite(v)) { | |
| cannotHandle = true; | |
| } else if (+v > max) { | |
| max = +v; | |
| } else if (+v < min) { | |
| min = +v; | |
| } | |
| }); | |
| if (cannotHandle) { | |
| // At least one value is not a string or a numeric value. | |
| // Pass the bare accessor back to geojs to handle it. | |
| return acc; | |
| } | |
| if (doNotHandle) { | |
| return acc; | |
| } | |
| if (categorical) { | |
| if (domain.length <= 10) { | |
| return wrap(d3.scale.category10().domain(domain)); | |
| } else if (domain.length <= 20) { | |
| return wrap(d3.scale.category20().domain(domain)); | |
| } else { | |
| // TODO: sort domain by most used and make an "other" category | |
| return wrap(d3.scale.category20().domain(domain)); | |
| } | |
| } | |
| // http://colorbrewer2.org/?type=diverging&scheme=RdYlBu&n=3 | |
| return wrap(d3.scale.linear() | |
| .range([ | |
| 'rgb(252,141,89)', | |
| 'rgb(255,255,191)', | |
| 'rgb(145,191,219)' | |
| ]) | |
| .domain([ | |
| min, | |
| (min + max) / 2, | |
| max | |
| ])); | |
| } | |
| /** | |
| * @class geojsMap | |
| * @memberOf jQuery.fn | |
| * | |
| * @description Generates a geojs map inside an element. | |
| * | |
| * | |
| * Due to current limitations in geojs, only a single map can be instantiated | |
| * on a page. Trying to create a second map will throw an error | |
| * (see issue | |
| * <a href="https://github.com/OpenGeoscience/geojs/issues/154">#154</a>). | |
| * | |
| * @example <caption>Create a map with the default options.</caption> | |
| * $("#map").geojsMap(); | |
| * @example <caption>Create a map with a given initial center and zoom</caption> | |
| * $("#map").geojsMap({ | |
| * longitude: -125, | |
| * latitude: 35, | |
| * zoom: 5 | |
| * }); | |
| * @example <caption>Create a map with points</caption> | |
| * $("#map").geojsMap({ | |
| * data: [...], | |
| * layers: [{ | |
| * renderer: 'vgl', | |
| * features: [{ | |
| * type: 'point', | |
| * radius: 5, | |
| * position: function (d) { return {x: d.geometry.x, y: d.geometry.y} }, | |
| * fillColor: function (d, i) { return i < 5 ? 'red' : 'blue' }, | |
| * stroke: false | |
| * }] | |
| * }] | |
| * }; | |
| * @example <caption>Create a map with points, lines and multiple layers</caption> | |
| * $("#map").geojsMap({ | |
| * center: { x: -130, y: 40 }, | |
| * zoom: 3, | |
| * layers: [{ | |
| * renderer: 'vgl', | |
| * features: [{ | |
| * data: [...], | |
| * type: 'point', | |
| * radius: 5, | |
| * position: function (d) { return {x: d.geometry.x, y: d.geometry.y} }, | |
| * fillColor: function (d, i) { return i < 5 ? 'red' : 'blue' }, | |
| * stroke: false | |
| * }] | |
| * }, | |
| * { | |
| * renderer: 'd3', | |
| * features[{ | |
| * data: [...], | |
| * type: 'line', | |
| * position: function (d) { return { x: d[0], y: d[1] } }, | |
| * line: function (d) { return d.coordinates; }, | |
| * strokeWidth: 3, | |
| * strokeColor: 'black', | |
| * strokeOpacity: 0.5 | |
| * }] | |
| * }] | |
| * }; | |
| */ | |
| // jscs:disable requireSpaceBetweenArguments | |
| $.widget('geojs.geojsMap', /** @lends jQuery.fn.geojsMap */{ | |
| // jscs:enable requireSpaceBetweenArguments | |
| /** | |
| * A coordinate object as accepted by geojs to express positions in an | |
| * arbitrary coordinate system (geographic, screen, etc). Coordinates returned by | |
| * geojs methods are always expressed with "x" and "y" properties, but | |
| * it will accept any of the aliased properties. | |
| * @typedef coordinate | |
| * @type {object} | |
| * @property {number} longitude Alias: "x", "lng", or "lon" | |
| * @property {number} latitude Alias: "y" or "lat" | |
| * @property {number} [elevation=0] Alias: "z", "elev", or "height" | |
| */ | |
| /** | |
| * Colors can be expressed in multiple ways: | |
| * <ul> | |
| * <li>css name (<code>"steelblue"</code>)</li> | |
| * <li>24 bit hex value (<code>0xff0051</code>)</li> | |
| * <li>25 bit hex string (<code>"#ff0051"</code>)</li> | |
| * <li>rgb object (values from 0-1, <code>{r: 1, g: 0.5, b: 0}</code>)</li> | |
| * </ul> | |
| * @typedef color | |
| * @type {*} | |
| */ | |
| /** | |
| * Point feature options object. All styles can be | |
| * given as accessor functions or constants. Accessor | |
| * functions are called with the following signature: | |
| * <pre> | |
| * function func(d, i) { | |
| * // d - data object | |
| * // i - index of d in the data array | |
| * // this - geo.pointFeature | |
| * } | |
| * </pre> | |
| * Pass null to remove saved options from previous calls. | |
| * @typedef pointOptions | |
| * @type {Object} | |
| * @property {Object[]} data Data array | |
| * @property {coordinate} position Location of the point center | |
| * @property {number} radius | |
| * Radius of the circle in pixels (ignored when <code>size</code> | |
| * is present) | |
| * @property {function} size | |
| * A function returning a numerical value | |
| * @property {boolean} fill Presence or absence of the fill | |
| * @property {color} fillColor Interior color | |
| * @property {float} fillOpacity Opacity of the interior <code>[0,1]</code> | |
| * @property {boolean} stroke Presence or absence of the stroke | |
| * @property {color} strokeColor Stroke color | |
| * @property {float} strokeOpacity Opacity of the stroke <code>[0,1]</code> | |
| */ | |
| /** | |
| * @instance | |
| * @description | |
| * Map options (not fully implemented). | |
| * @example <caption>Get the current map center</caption> | |
| * var center=$("#map").geojsMap("center"); | |
| * @example <caption>Pan the map to a new center</caption> | |
| * $("#map").geojsMap("center", {lat: 10, lng: -100}); | |
| * @property {object[]} [data=[]] The default data array used for | |
| * features/layers not already containing data. | |
| * @property {coordinate} [center={lat: 0, lng: 0}] The map center | |
| * @property {number} [zoom=0] The zoom level (floating point >= 0) | |
| * @property {(number|null)} [width=null] | |
| * The width of the map in pixels or null for 100% | |
| * @property {(number|null)} [height=null] | |
| * The height of the map in pixels or null for 100% | |
| * @property {geo.layer.spec[]} [layers=[]] | |
| * Describes layers added to the map | |
| * @property {boolean} [autoresize=true] | |
| * Resize the map on <code>window.resize</code> (initialization only) | |
| * @property {string} [tileServer] | |
| * The open street map tile server spec default: | |
| * <code>http://tile.openstreetmap.org/<zoom>/<x>/<y>.png</code> | |
| */ | |
| options: { | |
| center: {latitude: 0, longitude: 0}, | |
| zoom: 0, | |
| width: null, | |
| height: null, | |
| layers: [], | |
| data: [], | |
| tileUrl: 'http://tile.openstreetmap.org/<zoom>/<x>/<y>.png', | |
| // These options are for future use, but shouldn't | |
| // be changed at the moment, so they aren't documented. | |
| baseLayer: 'osm', | |
| baseRenderer: 'vgl' | |
| }, | |
| /** | |
| * Internal constructor | |
| * @instance | |
| * @protected | |
| */ | |
| _create: function () { | |
| if (this._map || !this.element.length) { | |
| // when called multiple times on a single element, do nothing | |
| return; | |
| } | |
| // create the map | |
| this._map = geo.map({ | |
| width: this.options.width, | |
| height: this.options.height, | |
| zoom: this.options.zoom, | |
| center: this.options.center, | |
| node: this.element.get(0) | |
| }); | |
| // create the base layer | |
| this._baseLayer = this._map.createLayer( | |
| this.options.baseLayer, | |
| { | |
| renderer: this.options.baseRenderer, | |
| tileUrl: this.options.tileUrl | |
| } | |
| ); | |
| // Trigger a resize to a valid size before adding | |
| // the feature layer to handle some of the bugs that | |
| // occur when initializing onto a node of size 0. | |
| this._resize({width: 800, height: 600}); | |
| this._layers = []; | |
| this.update(); | |
| }, | |
| /** | |
| * Update the layers and features using a new array of | |
| * {@link geo.layer.spec} objects. All existing layers | |
| * and features are deleted. If only the data has changed, | |
| * you can usually just call {@link jQuery.fn.geojsMap#redraw redraw}. | |
| * @instance | |
| * @param {geo.layer.spec[]} [layers] New map layers | |
| * @example <caption>Delete and recreate all existing layers</caption> | |
| * $("#map").geojsMap("update"); | |
| * @example <caption>Remove all existing feature layers.</caption> | |
| * $("#map").geojsMap("update", []); | |
| */ | |
| update: function (layers) { | |
| var m_this = this; | |
| this.options.layers = layers || this.options.layers || []; | |
| // delete existing layers | |
| this._layers.forEach(function (layer) { | |
| layer.clear(); | |
| m_this._map.deleteLayer(layer); | |
| }); | |
| // create new layers | |
| this._layers = this.options.layers.map(function (layer) { | |
| layer.data = layer.data || m_this.options.data; | |
| // Until auto color scaling gets moved into geojs core, we will | |
| // mutate the spec and replace the color and radius options. | |
| (layer.features || []).forEach(function (feature) { | |
| var data = feature.data || layer.data || []; | |
| var scl; | |
| if (feature.type === 'point') { | |
| if (feature.size) { | |
| feature._size = geo.util.ensureFunction(feature.size); | |
| } else if (feature.size === null) { | |
| delete feature._size; | |
| } | |
| if (data.length && feature._size) { | |
| scl = d3.scale.linear() | |
| .domain( | |
| d3.extent(data, feature._size) | |
| ) | |
| .range([5, 20]); | |
| feature.radius = function () { | |
| // TODO: wrong `this` (wait for style refactor) | |
| return scl(feature._size.apply(this, arguments)); | |
| }; | |
| } | |
| delete feature.size; | |
| } | |
| var key; | |
| for (key in feature) { | |
| if (feature.hasOwnProperty(key) && | |
| isColorKey(key)) { | |
| feature[key] = makeColorScale(data, feature[key]); | |
| } | |
| } | |
| }); | |
| return geo.layer.create(m_this._map, layer); | |
| }); | |
| // trigger an initial draw | |
| this.redraw(); | |
| return this; | |
| }, | |
| /** | |
| * Return the geojs map object. | |
| * @instance | |
| * @returns {geo.map} | |
| */ | |
| map: function () { | |
| return this._map; | |
| }, | |
| /** | |
| * Set the tile server URL. | |
| * @instance | |
| * @param {string} url The url format string of an OSM tile server. | |
| */ | |
| tileUrl: function (url) { | |
| this._baseLayer.tileUrl(url); | |
| this._baseLayer.updateBaseUrl(); | |
| return this; | |
| }, | |
| /** | |
| * Resize the map canvas. | |
| * @instance | |
| * @protected | |
| * @param {object?} size Explicit size or use this.options. | |
| */ | |
| _resize: function (size) { | |
| var width = this.options.width, | |
| height = this.options.height; | |
| if (size) { | |
| width = size.width; | |
| height = size.height; | |
| } | |
| if (!width) { | |
| width = this.element.width(); | |
| } | |
| if (!height) { | |
| height = this.element.height(); | |
| } | |
| this._map.resize(0, 0, width, height); | |
| }, | |
| /** | |
| * Do a full redraw of the map. In general, users shouldn't need to | |
| * call this method, but it could be useful when accessing lower | |
| * level features of the mapping api. | |
| * @todo This function may need to go through each feature and call | |
| * {@link geo.feature#modified} to properly update. | |
| * @instance | |
| */ | |
| redraw: function () { | |
| this._resize(); | |
| return this; | |
| } | |
| }); | |
| // Some argument type definitions used only by this plugin: | |
| /** | |
| * A geojs renderer is one of the following: | |
| * <ul> | |
| * <li><code>"vgl"</code>: Uses webGL</li> | |
| * <li><code>"d3"</code>: Uses svg</li> | |
| * </ul> | |
| * @typedef renderer | |
| * @type {string} | |
| */ | |
| }; | |
| $(load); | |
| })($ || window.$, geo || window.geo, d3 || window.d3); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment