/* * SceneJS WebGL Scene Graph Library for JavaScript * http://scenejs.org/ * Dual licensed under the MIT or GPL Version 2 licenses. * http://scenejs.org/license * Copyright 2010, Lindsay Kay * * Includes WebGLTrace * Various functions for helping debug WebGL apps. * http://github.com/jackpal/webgltrace * Copyright (c) 2009 The Chromium Authors. All rights reserved. * * Includes WebGL-Debug * Various functions for helping debug WebGL apps. * http://khronos.org/webgl/wiki/Debugging * Copyright (c) 2009 The Chromium Authors. All rights reserved. */ /** Generic map of IDs to items - can generate own IDs or accept given IDs. * Given IDs should be strings in order to not clash with internally generated IDs, which are numbers. */ var SceneJS_Map = function() { this.items = []; this.lastUniqueId = 0; this.addItem = function() { var item; if (arguments.length == 2) { var id = arguments[0]; item = arguments[1]; if (this.items[id]) { // Won't happen if given ID is string throw SceneJS_errorModule.fatalError(SceneJS.errors.ID_CLASH, "ID clash: '" + id + "'"); } this.items[id] = item; return id; } else { while (true) { item = arguments[0]; var findId = this.lastUniqueId++ ; if (!this.items[findId]) { this.items[findId] = item; return findId; } } } }; this.removeItem = function(id) { this.items[id] = null; }; }; /** * @class SceneJS * SceneJS name space * @singleton */ var SceneJS = { /** Version of this release */ VERSION: '2.0.0', /** Names of supported WebGL canvas contexts */ SUPPORTED_WEBGL_CONTEXT_NAMES:["webgl", "experimental-webgl", "webkit-3d", "moz-webgl", "moz-glweb20"], /** * Node classes * @private */ _nodeTypes: {}, /** * Map of existing scene nodes */ _scenes: {}, /** Extension point to create a new node type. * * @param {string} type Name of new subtype * @param {string} superType Optional name of super-type - {@link SceneJS_node} by default * @return {class} New node class */ createNodeType : function(type, superType) { if (!type) { throw SceneJS_errorModule.fatalError("createNodeType param 'type' is null or undefined"); } if (typeof type != "string") { throw SceneJS_errorModule.fatalError("createNodeType param 'type' should be a string"); } if (this._nodeTypes[type]) { throw SceneJS_errorModule.fatalError("createNodeType node of param 'type' already defined: '" + superType + "'"); } var supa = this._nodeTypes[superType]; if (!supa) { supa = SceneJS._Node; } var nodeType = function() { // Create class supa.apply(this, arguments); this.attr.type = type; }; SceneJS._inherit(nodeType, supa); this._nodeTypes[type] = nodeType; return nodeType; }, _registerNode : function(type, nodeClass) { this._nodeTypes[type] = nodeClass; }, /** * Factory function to create a "scene" node */ createScene : function(json) { if (!json) { throw SceneJS_errorModule.fatalError("createScene param 'json' is null or undefined"); } json.type = "scene"; if (!json.id) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "createScene 'id' is mandatory for 'scene' node"); } if (this._scenes[json.id]) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "createScene - scene id '" + json.id + "' already taken by another scene"); } var newNode = this._parseNodeJSON(json, undefined); // Scene references itself as the owner scene this._scenes[json.id] = newNode; SceneJS_eventModule.fireEvent(SceneJS_eventModule.NODE_CREATED, { nodeId : newNode.attr.id, json: json }); return SceneJS._selectNode(newNode); }, /** Returns true if the "scene" node with the given ID exists */ sceneExists : function(sceneId) { return this._scenes[sceneId] ? true : false; }, /** Select a "scene" node */ scene : function(sceneId) { var scene = this._scenes[sceneId]; if (!scene) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "withScene scene not found: '" + sceneId + "'"); } return SceneJS._selectNode(scene); }, _parseNodeJSON : function(json, scene) { var newNode = this._createNode(json, scene); scene = scene || newNode; newNode.scene = scene; if (json.nodes) { var len = json.nodes.length; for (var i = 0; i < len; i++) { newNode.addNode(SceneJS._parseNodeJSON(json.nodes[i], scene)); } } return newNode; }, _createNode : function(json, scene) { json.type = json.type || "node"; var nodeType; if (json.type == "node") { nodeType = SceneJS._Node; } else { nodeType = this._nodeTypes[json.type]; if (!nodeType) { throw SceneJS_errorModule.fatalError("Scene node type not supported in SceneJS " + SceneJS.VERSION + ": '" + json.type + "'"); } } return new nodeType(json, scene); }, /** * Shallow copy of JSON node configs, filters out JSON-specific properties like "nodes" * @private */ _copyCfg : function (cfg) { var cfg2 = {}; for (var key in cfg) { if (cfg.hasOwnProperty(key) && key != "nodes") { cfg2[key] = cfg[key]; } } return cfg2; }, /** Nodes that are scheduled to be destroyed. When a node is destroyed it is added here, then at the end of each * render traversal, each node in here is popped and has {@link SceneJS_node#destroy} called. * @private */ _destroyedNodes : [], /** Action the scheduled destruction of nodes * @private */ _actionNodeDestroys : function() { if (this._destroyedNodes.length > 0) { var node; for (var i = this._destroyedNodes.length - 1; i >= 0; i--) { node = this._destroyedNodes[i]; node._doDestroy(); SceneJS_eventModule.fireEvent(SceneJS_eventModule.NODE_DESTROYED, { nodeId : node.attr.id }); } this._destroyedNodes = []; } }, /*---------------------------------------------------------------------------------------------------------------- * Messaging System * * *--------------------------------------------------------------------------------------------------------------*/ Message : new (function() { /** Sends a message to SceneJS - docs at http://scenejs.wikispaces.com/SceneJS+Messaging+System * * @param message */ this.sendMessage = function (message) { if (!message) { throw SceneJS_errorModule.fatalError("sendMessage param 'message' null or undefined"); } var commandId = message.command; if (!commandId) { throw SceneJS_errorModule.fatalError("Message element expected: 'command'"); } var commandService = SceneJS.Services.getService(SceneJS.Services.COMMAND_SERVICE_ID); var command = commandService.getCommand(commandId); if (!command) { throw SceneJS_errorModule.fatalError("Message command not supported: '" + commandId + "' - perhaps this command needs to be added to the SceneJS Command Service?"); } var ctx = {}; command.execute(ctx, message); }; })(), /** * @private */ _inherit : function(DerivedClassName, BaseClassName) { DerivedClassName.prototype = new BaseClassName(); DerivedClassName.prototype.constructor = DerivedClassName; }, /** Creates a namespace * @private */ _namespace : function() { var a = arguments, o = null, i, j, d, rt; for (i = 0; i < a.length; ++i) { d = a[i].split("."); rt = d[0]; eval('if (typeof ' + rt + ' == "undefined"){' + rt + ' = {};} o = ' + rt + ';'); for (j = 1; j < d.length; ++j) { o[d[j]] = o[d[j]] || {}; o = o[d[j]]; } } }, /** * Returns a key for a vacant slot in the given map * @private */ // Add a new function that returns a unique map key. _last_unique_id: 0, _createKeyForMap : function(keyMap, prefix) { while (true) { var key = prefix ? prefix + SceneJS._last_unique_id++ : SceneJS._last_unique_id++; if (!keyMap[key]) { return key; } } }, /** Stolen from GLGE:https://github.com/supereggbert/GLGE/blob/master/glge-compiled.js#L1656 */ _createUUID:function() { var data = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"]; var data2 = ["8","9","A","B"]; var uuid = []; for (var i = 0; i < 38; i++) { switch (i) { case 8:uuid.push("-"); break; case 13:uuid.push("-"); break; case 18:uuid.push("-"); break; case 14:uuid.push("4"); break; case 19:uuid.push(data2[Math.round(Math.random() * 3)]); break; default:uuid.push(data[Math.round(Math.random() * 15)]); break; } } return uuid.join(""); }, _getBaseURL : function(url) { var i = url.lastIndexOf("/"); if (i == 0 || i == -1) { return ""; } return url.substr(0, i + 1); }, /** * Returns true if object is an array * @private */ _isArray : function(testObject) { return testObject && !(testObject.propertyIsEnumerable('length')) && typeof testObject === 'object' && typeof testObject.length === 'number'; }, _shallowClone : function(o) { var o2 = {}; for (var name in o) { if (o.hasOwnProperty(name)) { o2[name] = o[name]; } } return o2; } , /** Add properties of o to o2 where undefined or null on o2 */ _applyIf : function(o, o2) { for (var name in o) { if (o.hasOwnProperty(name)) { if (o2[name] == undefined || o2[name] == null) { o2[name] = o[name]; } } } return o2; }, /** Add properties of o to o2, overwriting them on o2 if already there. * The optional clear flag causes properties on o2 to be cleared first */ _apply : function(o, o2, clear) { var name; if (clear) { for (name in o2) { if (o2.hasOwnProperty(name)) { delete o2[name]; } } } for (name in o) { if (o.hasOwnProperty(name)) { o2[name] = o[name]; } } return o2; }, /** Lazy-bound state resources published by "suppliers" */ _compilationStates : new (function() { var suppliers = {}; this.setSupplier = function(type, supplier) { suppliers[type] = supplier; }; this.getState = function(type, sceneId, id) { var s = suppliers[type]; if (!s) { throw SceneJS_errorModule.fatalError("Internal error - Compilation state supplier not found: '" + type + "'"); } return s.get(sceneId, id); }; })() }; /** * @class The basic scene node type */ SceneJS._Node = function(cfg, scene) { /* Public properties are stored on the _attr map */ this.attr = {}; this.attr.type = "node"; this.attr.sid = null; this.attr.data = {}; if (cfg) { this.attr.id = cfg.id; this.attr.type = cfg.type || "node"; this.attr.data = cfg.data; this.attr.enabled = cfg.enabled === false ? false : true; this.attr.sid = cfg.sid; this.attr.info = cfg.info; this.scene = scene || this; } /* Child nodes */ this.children = []; this.parent = null; this.listeners = {}; this.numListeners = 0; // Useful for quick check whether node observes any events /* When compiled, a node increments this each time it discovers that it can cache more state, so that it * knows not to recompute that state when next compiled. Since internal state is usually dependent on the * states of higher nodes, this is reset whenever the node is attached to a new parent. */ this._compileMemoLevel = 0; /* Deregister default ID */ if (this.attr.id) { // SceneJS_sceneNodeMaps.removeItem(this.attr.id); } /* Register again by whatever ID we now have */ if (this.scene && this.scene.nodeMap) { if (this.attr.id) { this.scene.nodeMap.addItem(this.attr.id, this); } else { this.attr.id = this.scene.nodeMap.addItem(this); } } if (cfg) { this._createCore(cfg.coreId || cfg.resource); this.setTags(cfg.tags || {}); if (this._init) { this._init(cfg); } } }; SceneJS._Node.prototype.constructor = SceneJS._Node; SceneJS._Node.prototype._cores = {}; SceneJS._Node.prototype._createCore = function(coreId) { var sceneId = this.scene.attr.id; var sceneCores = this._cores[sceneId]; if (!sceneCores) { sceneCores = this._cores[sceneId] = {}; } var nodeCores = sceneCores[this.attr.type]; if (!nodeCores) { nodeCores = sceneCores[this.attr.type] = {}; } if (coreId) { /* Attempt to reuse a core */ this.core = nodeCores[coreId]; if (this.core) { this.core._nodeCount++; } } else { coreId = SceneJS._createUUID(); } if (!this.core) { this.core = nodeCores[coreId] = { _coreId: coreId, _nodeCount : 1 }; } // this.state = new SceneJS_State({ // core: this.core // }); return this.core; }; /** * Returns the ID of this node's core */ SceneJS._Node.prototype.getCoreId = function() { return this.core._coreId; }; /** * Backwards compatibility */ SceneJS._Node.prototype.getResource = SceneJS._Node.prototype.getCoreId; SceneJS._Node.prototype._tags = {}; SceneJS._Node.prototype.setTags = function(tags) { }; /** * Dumps anything that was memoized on this node to reduce recompilation work */ SceneJS._Node.prototype._resetCompilationMemos = function() { this._compileMemoLevel = 0; }; /** * Same as _resetCompilationMemos, also called on sub-nodes */ SceneJS._Node.prototype._resetTreeCompilationMemos = function() { this._resetCompilationMemos(); for (var i = 0; i < this.children.length; i++) { this.children[i]._resetTreeCompilationMemos(); } }; SceneJS._Node.prototype._flagDirty = function() { this._compileMemoLevel = 0; // if (this.attr._childStates && this.attr._dirty) { // this._flagDirtyState(this.attr); // } }; //SceneJS._Node.prototype._flagDirtyState = function(attr) { // attr._dirty = true; // if (attr._childStates) { // for (var i = 0, len = attr._childStates.length; i < len; i++) { // if (!attr._childStates[i]._dirty) { // this._flagDirtyState(attr._childStates[i]); // } // } // } //}; /** @private */ SceneJS._Node.prototype._compile = function() { this._compileNodes(); }; /** @private * * Recursively renders a node's child list. */ SceneJS._Node.prototype._compileNodes = function() { // Selected children - useful for Selector node if (this.listeners["rendered"]) { SceneJS_nodeEventsModule.preVisitNode(this); } var children = this.children; // Set of child nodes we'll be rendering var numChildren = children.length; var child; var i; if (numChildren > 0) { for (i = 0; i < numChildren; i++) { child = children[i]; if (SceneJS_compileModule.preVisitNode(child)) { child._compile(); } SceneJS_compileModule.postVisitNode(child); } } if (this.listeners["rendered"]) { SceneJS_nodeEventsModule.postVisitNode(this); } }; // ///** // * Wraps _compile to fire built-in events either side of rendering. // * @private */ //SceneJS._Node.prototype._compileWithEvents = function() { // // /* As scene is traversed, SceneJS_sceneStatusModule will track the counts // * of nodes that are still initialising // * // * If we are listening to "loading-status" events on this node, then we'll // * get a snapshot of those stats, then report the difference from that // * via the event once we have rendered this node. // */ //// var loadStatusSnapshot; //// if (this.listeners["loading-status"]) { //// loadStatusSnapshot = SceneJS_sceneStatusModule.getStatusSnapshot(); //// } // this._compile(); //// if (this.listeners["loading-status"]) { // Report diff of loading stats that occurred while rending this tree //// this._fireEvent("loading-status", SceneJS_sceneStatusModule.diffStatus(loadStatusSnapshot)); //// } // //}; /** * Returns the SceneJS-assigned ID of the node. * @returns {string} Node's ID */ SceneJS._Node.prototype.getID = function() { return this.attr.id; }; /** * Alias for {@link #getID()} to assist resolution of the ID by JSON query API * @returns {string} Node's ID */ SceneJS._Node.prototype.getId = SceneJS._Node.prototype.getID; /** * Returns the type ID of the node. For the Node base class, it is "node", * which is overriden in sub-classes. * @returns {string} Type ID */ SceneJS._Node.prototype.getType = function() { return this.attr.type; }; /** * Returns the data object attached to this node. * @returns {Object} data object */ SceneJS._Node.prototype.getData = function() { return this.attr.data; }; /** * Sets a data object on this node. * @param {Object} data Data object */ SceneJS._Node.prototype.setData = function(data) { this.attr.data = data; return this; }; /** * Returns the node's optional subidentifier, which must be unique within the scope * of the parent node. * @returns {string} Node SID * @deprecated */ SceneJS._Node.prototype.getSID = function() { return this.attr.sid; }; /** Returns the SceneJS.Scene to which this node belongs. * Returns node if this is a SceneJS_scene. * @returns {SceneJS.Scene} Scene node */ SceneJS._Node.prototype.getScene = function() { return this.scene; }; /** * Returns the number of child nodes * @returns {int} Number of child nodes */ SceneJS._Node.prototype.getNumNodes = function() { return this.children.length; }; /** Returns child nodes * @returns {Array} Child nodes */ SceneJS._Node.prototype.getNodes = function() { var list = new Array(this.children.length); var len = this.children.length; for (var i = 0; i < len; i++) { list[i] = this.children[i]; } return list; }; /** Returns child node at given index. Returns null if no node at that index. * @param {Number} index The child index * @returns {Node} Child node, or null if not found */ SceneJS._Node.prototype.getNodeAt = function(index) { if (index < 0 || index >= this.children.length) { return null; } return this.children[index]; }; /** Returns first child node. Returns null if no child nodes. * @returns {Node} First child node, or null if not found */ SceneJS._Node.prototype.getFirstNode = function() { if (this.children.length == 0) { return null; } return this.children[0]; }; /** Returns last child node. Returns null if no child nodes. * @returns {Node} Last child node, or null if not found */ SceneJS._Node.prototype.getLastNode = function() { if (this.children.length == 0) { return null; } return this.children[this.children.length - 1]; }; /** Returns child node with the given ID. * Returns null if no such child node found. * @param {String} sid The child's SID * @returns {Node} Child node, or null if not found */ SceneJS._Node.prototype.getNode = function(id) { for (var i = 0; i < this.children.length; i++) { if (this.children[i].attr.id == id) { return this.children[i]; } } return null; }; /** Disconnects the child node at the given index from its parent node * @param {int} index Child node index * @returns {Node} The disconnected child node if located, else null */ SceneJS._Node.prototype.disconnectNodeAt = function(index) { var r = this.children.splice(index, 1); this._resetCompilationMemos(); if (r.length > 0) { r[0].parent = null; return r[0]; } else { return null; } }; /** Disconnects the child node from its parent, given as a node object * @param {String | Node} id The target child node, or its ID * @returns {Node} The removed child node if located */ SceneJS._Node.prototype.disconnect = function() { if (this.parent) { for (var i = 0; i < this.parent.children.length; i++) { if (this.parent.children[i] === this) { return this.parent.disconnectNodeAt(i); } } } }; /** Removes the child node at the given index * @param {int} index Child node index */ SceneJS._Node.prototype.removeNodeAt = function(index) { var child = this.disconnectNodeAt(index); if (child) { child.destroy(); } }; /** Removes the child node, given as either a node object or an ID string. * @param {String | Node} id The target child node, or its ID * @returns {Node} The removed child node if located */ SceneJS._Node.prototype.removeNode = function(node) { if (!node) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Node#removeNode - node argument undefined"); } if (!node._compile) { if (typeof node == "string") { var gotNode = this.scene.nodeMap.items[node]; if (!gotNode) { throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_NOT_FOUND, "Node#removeNode - node not found anywhere: '" + node + "'"); } node = gotNode; } } if (node._compile) { // instance of node for (var i = 0; i < this.children.length; i++) { if (this.children[i] === node) { //this._resetCompilationMemos(); (removeNodeAt already does this) return this.removeNodeAt(i); } } } throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_NOT_FOUND, "Node#removeNode - child node not found: " + (node._compile ? ": " + node.attr.id : node)); }; /** Disconnects all child nodes from their parent node and returns them in an array. * @returns {Array[Node]} The disconnected child nodes */ SceneJS._Node.prototype.disconnectNodes = function() { for (var i = 0; i < this.children.length; i++) { // Unlink children from this this.children[i].parent = null; } var children = this.children; this.children = []; this._resetCompilationMemos(); return children; }; /** Removes all child nodes and returns them in an array. * @returns {Array[Node]} The removed child nodes */ SceneJS._Node.prototype.removeNodes = function() { var children = this.disconnectNodes(); for (var i = 0; i < children.length; i++) { this.children[i].destroy(); } }; /** Destroys node and moves children up to parent, inserting them where this node resided. * @returns {Node} The parent */ SceneJS._Node.prototype.splice = function() { var i, len; if (this.parent == null) { return null; } var parent = this.parent; var children = this.disconnectNodes(); for (i = 0, len = children.length; i < len; i++) { // Link this node's children to new parent children[i].parent = this.parent; } for (i = 0, len = parent.children.length; i < len; i++) { // Replace node on parent's children with this node's children if (parent.children[i] === this) { parent.children.splice.apply(parent.children, [i, 1].concat(children)); this.parent = null; this.destroy(); parent._resetTreeCompilationMemos(); SceneJS_compileModule.nodeUpdated(parent); return parent; } } }; /** Appends multiple child nodes * @param {Array[Node]} nodes Array of nodes * @return {Node} This node */ SceneJS._Node.prototype.addNodes = function(nodes) { if (!nodes) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Node#addNodes - nodes argument is undefined"); } for (var i = nodes.length - 1; i >= 0; i--) { this.addNode(nodes[i]); } this._resetCompilationMemos(); return this; }; /** Appends a child node * @param {Node} node Child node * @return {Node} The child node */ SceneJS._Node.prototype.addNode = function(node) { if (!node) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Node#addNode - node argument is undefined"); } if (!node._compile) { if (typeof node == "string") { var gotNode = this.scene.nodeMap.items[node]; if (!gotNode) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Node#addNode - node not found: '" + node + "'"); } node = gotNode; } else { node = SceneJS._parseNodeJSON(node, this.scene); } } if (!node._compile) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Node#addNode - node argument is not a Node or subclass!"); } if (node.parent != null) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Node#addNode - node argument is still attached to another parent!"); } this.children.push(node); node.parent = this; node._resetTreeCompilationMemos(); return node; }; /** Inserts a subgraph into child nodes * @param {Node} node Child node * @param {int} i Index for new child node * @return {Node} The child node */ SceneJS._Node.prototype.insertNode = function(node, i) { if (!node) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Node#insertNode - node argument is undefined"); } if (!node._compile) { node = SceneJS._parseNodeJSON(node, this.scene); } if (!node._compile) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Node#insertNode - node argument is not a Node or subclass!"); } if (node.parent != null) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Node#insertNode - node argument is still attached to another parent!"); } if (i == undefined || i == null) { /* Insert node above children when no index given */ var children = this.disconnectNodes(); /* Move children to right-most leaf of inserted graph */ var leaf = node; while (leaf.getNumNodes() > 0) { leaf = leaf.getLastNode(); } leaf.addNodes(children); this.addNode(node); } else if (i < 0) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Node#insertNode - node index out of range: -1"); } else if (i >= this.children.length) { this.children.push(node); } else { this.children.splice(i, 0, node); } node.parent = this; node._resetTreeCompilationMemos(); return node; }; /** Calls the given function on each node in the subgraph rooted by this node, including this node. * The callback takes each node as it's sole argument and traversal stops as soon as the function returns * true and returns the node. * @param {function(Node)} func The function */ SceneJS._Node.prototype.mapNodes = function(func) { if (func(this)) { return this; } var result; for (var i = 0; i < this.children.length; i++) { result = this.children[i].mapNodes(func); if (result) { return result; } } return null; }; /** * Registers a listener for a given event on this node. If the event type * is not supported by this node type, then the listener will never be called. *

Example: *


 * var node = new Node();
 *
 * node.addListener(
 *
 *              // eventName
 *              "some-event",
 *
 *              // handler
 *              function(node,      // Node we are listening to
 *                       params) {  // Whatever params accompany the event type
 *
 *                     // ...
 *              }
 * );
 *
 *
 * 
* * @param {String} eventName One of the event types supported by this node * @param {Function} fn - Handler function that be called as specified * @param options - Optional options for the handler as specified * @return {Node} this */ SceneJS._Node.prototype.addListener = function(eventName, fn, options) { var list = this.listeners[eventName]; if (!list) { list = []; this.listeners[eventName] = list; } list.push({ eventName : eventName, fn: fn, options : options || {} }); this.numListeners++; this._resetCompilationMemos(); // Need re-render - potentially more state changes return this; }; /** * Destroys this node. It is marked for destruction; when the next scene traversal begins (or the current one ends) * it will be destroyed and removed from it's parent. * @return {Node} this */ SceneJS._Node.prototype.destroy = function() { if (!this._destroyed) { this._destroyed = true; this._scheduleNodeDestroy(); } SceneJS_compileModule.nodeUpdated(this, "destroyed"); // Compile again to rebuild display return this; }; /** Schedule the destruction of this node */ SceneJS._Node.prototype._scheduleNodeDestroy = function() { this.disconnect(); this.scene.nodeMap.removeItem(this.attr.id); if (this.children.length > 0) { var children = this.children.slice(0); // destruction will modify this.children for (var i = 0; i < children.length; i++) { children[i]._scheduleNodeDestroy(); } } SceneJS._destroyedNodes.push(this); }; /** * Performs the actual destruction of this node, calling the node's optional template destroy method */ SceneJS._Node.prototype._doDestroy = function() { if (this._destroy) { this._destroy(); } if (--this.core._nodeCount <= 0) { this._cores[this.scene.attr.id][this.attr.type][this.core._coreId] = null; } return this; }; /** * Fires an event at this node, immediately calling listeners registered for the event * @param {String} eventName Event name * @param {Object} params Event parameters * @param {Object} options Event options */ SceneJS._Node.prototype._fireEvent = function(eventName, params, options) { var list = this.listeners[eventName]; if (list) { if (!params) { params = {}; } var event = { name: eventName, params : params, options: options || {} }; var listener; for (var i = 0, len = list.length; i < len; i++) { listener = list[i]; if (listener.options.scope) { listener.fn.call(listener.options.scope, event); } else { listener.fn.call(this, event); } } } }; /** * Removes a handler that is registered for the given event on this node. * Does nothing if no such handler registered. * * @param {String} eventName Event type that handler is registered for * @param {function} fn - Handler function that is registered for the event * @return {function} The handler, or null if not registered */ SceneJS._Node.prototype.removeListener = function(eventName, fn) { var list = this.listeners[eventName]; if (!list) { return null; } for (var i = 0; i < list.length; i++) { if (list[i].fn == fn) { list.splice(i, 1); return fn; } } this.numListeners--; return null; }; /** * Returns true if this node has any listeners for the given event . * * @param {String} eventName Event type * @return {boolean} True if listener present */ SceneJS._Node.prototype.hasListener = function(eventName) { return this.listeners[eventName]; }; /** * Returns true if this node has any listeners at all. * * @return {boolean} True if any listener present */ SceneJS._Node.prototype.hasListeners = function() { return (this.numListeners > 0); }; /** Removes all listeners registered on this node. * @return {Node} this */ SceneJS._Node.prototype.removeListeners = function() { this.listeners = {}; this.numListeners = 0; return this; }; /** Returns the parent node * @return {Node} The parent node */ SceneJS._Node.prototype.getParent = function() { return this.parent; }; /** Returns either all child or all sub-nodes of the given type, depending on whether search is recursive or not. * @param {string} type Node type * @param {boolean} [recursive=false] When true, will return all matching nodes in subgraph, otherwise returns just children (default) * @return {SceneJS.node[]} Array of matching nodes */ SceneJS._Node.prototype.findNodesByType = function(type, recursive) { return this._findNodesByType(type, [], recursive); }; /** @private */ SceneJS._Node.prototype._findNodesByType = function(type, list, recursive) { var i; for (i = 0; i < this.children; i++) { var node = this.children[i]; if (node.type == type) { list.add(node); } } if (recursive) { for (i = 0; i < this.children; i++) { this.children[i]._findNodesByType(type, list, recursive); } } return list; }; /** * Returns an object containing the attributes that were given when creating the node. Obviously, the map will have * the current values, plus any attributes that were later added through set/add methods on the node * */ SceneJS._Node.prototype.getJSON = function() { return this.attr; }; var SceneJS_State = function(cfg) { this.core = cfg.core || {}; this.state = cfg.state || {}; if (cfg.parent) { cfg.parent.addChild(this); } this.children = []; this.dirty = true; this._cleanFunc = cfg.cleanFunc; }; SceneJS_State.prototype.addChild = function(state) { state.parent = this; this.children.push(state); }; SceneJS_State.prototype.setDirty = function() { if (this.dirty) { return; } this.dirty = true; if (this.children.length > 0) { var child; for (var i = 0, len = this.children.length; i < len; i++) { child = this.children[i]; if (!child.dirty) { child.setDirty(); } } } }; SceneJS_State.prototype._cleanStack = []; SceneJS_State.prototype._cleanStackLen = 0; SceneJS_State.prototype.setClean = function() { if (!this.dirty) { return; } if (!this._cleanFunc) { return; } if (!this.parent) { this._cleanFunc(this.parent ? this.parent : null, this); this.dirty = false; return; } this._cleanStackLen = 0; /* Stack dirty states on path to root */ var state = this; while (state && state.dirty) { this._cleanStack[this._cleanStackLen++] = state; state = state.parent; } /* Stack last clean state if existing */ if (state && state.parent) { this._cleanStack[this._cleanStackLen++] = state.parent; } /* Clean states down the path */ var parentState; for (var i = this._cleanStackLen - 1; i > 0; i--) { parentState = this._cleanStack[i - 1]; state = this._cleanStack[i]; this._cleanFunc(parentState, state); parentState.dirty = false; state.dirty = false; } }; SceneJS_State.prototype.reset = function() { this.children = []; this.dirty = true; }; /** * SceneJS IOC service container */ SceneJS.Services = new (function() { this.NODE_LOADER_SERVICE_ID = "node-loader"; this.GEO_LOADER_SERVICE_ID = "geo-loader"; this.MORPH_GEO_LOADER_SERVICE_ID = "morph-geo-loader"; this.COMMAND_SERVICE_ID = "command"; this._services = {}; this.addService = function(name, service) { this._services[name] = service; }; this.hasService = function(name) { var service = this._services[name]; return (service != null && service != undefined); }; this.getService = function(name) { return this._services[name]; }; /*---------------------------------------------------- * Install stub services *---------------------------------------------------*/ this.addService(this.NODE_LOADER_SERVICE_ID, { /** Loads node and attaches to parent */ loadNode: function(parentId, nodeId) { } }); this.addService(this.GEO_LOADER_SERVICE_ID, { loadGeometry: function (id, params, cb) { throw SceneJS_errorModule.fatalError("SceneJS.Services service not installed: SceneJS.Services.GEO_LOADER_SERVICE_ID"); } }); this.addService(this.MORPH_GEO_LOADER_SERVICE_ID, { loadMorphGeometry: function (id, params, cb) { throw SceneJS_errorModule.fatalError("SceneJS.Services service not installed: SceneJS.Services.MORPH_GEO_LOADER_SERVICE_ID"); } }); })(); (function() { var NodeSelector = function(node) { this._targetNode = node; this._methods = { }; }; SceneJS._selectNode = function(node) { if (!node.__selector) { node.__selector = new NodeSelector(node); } return node.__selector; }; /** Selects the parent of the selected node */ NodeSelector.prototype.parent = function() { var parent = this._targetNode.parent; if (!parent) { return null; } return SceneJS._selectNode(parent); }; /** Selects a child node matching given ID or index * @param {Number|String} node Child node index or ID */ NodeSelector.prototype.node = function(node) { if (node === null || node === undefined) { throw SceneJS_errorModule.fatalError("node param 'node' is null or undefined"); } var type = typeof node; var nodeGot; if (type == "number") { nodeGot = this._targetNode.getNodeAt(node); } else if (type == "string") { nodeGot = this._targetNode.getNode(node); } else { throw SceneJS_errorModule.fatalError("node param 'node' should be either an index number or an ID string"); } if (!nodeGot) { throw "node not found: '" + node + "'"; } return SceneJS._selectNode(nodeGot); }; NodeSelector.prototype.findNode = function (nodeId) { if (this._targetNode.attr.type != "scene") { throw SceneJS_errorModule.fatalError("findNode attempted on node that is not a \"scene\" type: '" + this._targetNode.attr.id + "'"); } return this._targetNode.findNode(nodeId); }; /** Find nodes in scene that have IDs matching the given regular expression * */ NodeSelector.prototype.findNodes = function (nodeIdRegex) { if (this._targetNode.attr.type != "scene") { throw SceneJS_errorModule.fatalError("findNode attempted on node that is not a \"scene\" type: '" + this._targetNode.attr.id + "'"); } return this._targetNode.findNodes(nodeIdRegex); }; /** Returns the scene to which the node belongs */ NodeSelector.prototype.scene = function() { return SceneJS._selectNode(this._targetNode.scene); }; /** Returns true if a child node matching given ID or index existis on the selected node * @param {Number|String} node Child node index or ID */ NodeSelector.prototype.hasNode = function(node) { if (node === null || typeof(node) === "undefined") { throw SceneJS_errorModule.fatalError("hasNode param 'node' is null or undefined"); } var type = typeof node; var nodeGot; if (type == "number") { nodeGot = this._targetNode.getNodeAt(node); } else if (type == "string") { nodeGot = this._targetNode.getNode(node); } else { throw SceneJS_errorModule.fatalError("hasNode param 'node' should be either an index number or an ID string"); } return (nodeGot != undefined && nodeGot != null); }; /** * Iterates over parent nodes on the path from the selected node to the root, executing a function * for each. * If the function returns true at any node, then traversal stops and a selector is * returned for that node. * @param {Function(node, index)} fn Function to execute on each instance node * @return {Object} Selector for selected node, if any */ NodeSelector.prototype.eachParent = function(fn) { if (!fn) { throw SceneJS_errorModule.fatalError("eachParent param 'fn' is null or undefined"); } var selector; var count = 0; var node = this._targetNode; while (node.parent) { selector = SceneJS._selectNode(node.parent); if (fn.call(selector, count++) === true) { return selector; } node = node.parent; } return undefined; }; /** * Iterates over sub-nodes of the selected node, executing a function * for each. With the optional options object we can configure is depth-first or breadth-first order. * If neither, then only the child nodes are iterated. * If the function returns true at any node, then traversal stops and a selector is * returned for that node. * @param {Function(index, node)} fn Function to execute on each child node * @return {Object} Selector for selected node, if any */ NodeSelector.prototype.eachNode = function(fn, options) { if (!fn) { throw SceneJS_errorModule.fatalError("eachNode param 'fn' is null or undefined"); } if (typeof fn != "function") { throw SceneJS_errorModule.fatalError("eachNode param 'fn' should be a function"); } var stoppedNode; options = options || {}; var count = 0; if (options.andSelf) { if (fn.call(this, count++) === true) { return this; } } if (!options.depthFirst && !options.breadthFirst) { stoppedNode = this._iterateEachNode(fn, this._targetNode, count); } else if (options.depthFirst) { stoppedNode = this._iterateEachNodeDepthFirst(fn, this._targetNode, count, false); // Not below root yet } else { // TODO: breadth-first } if (stoppedNode) { return stoppedNode; } return undefined; // IDE happy now }; NodeSelector.prototype.numNodes = function() { return this._targetNode.children.length; }; /** Sets an attribute of the selected node */ NodeSelector.prototype.set = function(attr, value) { if (!attr) { throw SceneJS_errorModule.fatalError("set param 'attr' null or undefined"); } if (typeof attr == "string") { this._callNodeMethod("set", attr, value, this._targetNode); } else { this._callNodeMethods("set", attr, this._targetNode); } return this; }; /** Adds an attribute to the selected node */ NodeSelector.prototype.add = function(attr, value) { if (!attr) { throw SceneJS_errorModule.fatalError("add param 'attr' null or undefined"); } if (typeof attr == "string") { this._callNodeMethod("add", attr, value, this._targetNode); } else { this._callNodeMethods("add", attr, this._targetNode); } return this; }; /** Increments an attribute to the selected node */ NodeSelector.prototype.inc = function(attr, value) { if (!attr) { throw SceneJS_errorModule.fatalError("inc param 'attr' null or undefined"); } if (typeof attr == "string") { this._callNodeMethod("inc", attr, value, this._targetNode); } else { this._callNodeMethods("inc", attr, this._targetNode); } return this; }; /** Inserts an attribute or child node into the selected node */ NodeSelector.prototype.insert = function(attr, value) { if (!attr) { throw SceneJS_errorModule.fatalError("insert param 'attr' null or undefined"); } if (typeof attr == "string") { this._callNodeMethod("insert", attr, value, this._targetNode); } else { this._callNodeMethods("insert", attr, this._targetNode); } return this; }; /** Removes an attribute from the selected node */ NodeSelector.prototype.remove = function(attr, value) { if (!attr) { throw SceneJS_errorModule.fatalError("remove param 'attr' null or undefined"); } if (typeof attr == "string") { this._callNodeMethod("remove", attr, value, this._targetNode); } else { this._callNodeMethods("remove", attr, this._targetNode); } return this; }; /** Returns the value of an attribute of the selected node */ NodeSelector.prototype.get = function(attr) { if (!attr) { return this._targetNode.getJSON(); } var funcName = "get" + attr.substr(0, 1).toUpperCase() + attr.substr(1); var func = this._targetNode[funcName]; if (!func) { throw SceneJS_errorModule.fatalError("Attribute '" + attr + "' not found on node '" + this._targetNode.attr.id + "'"); } return func.call(this._targetNode); }; /** Binds a listener to an event on the selected node * * @param {String} name Event name * @param {Function} handler Event handler */ NodeSelector.prototype.bind = function(name, handler) { if (!name) { throw SceneJS_errorModule.fatalError("bind param 'name' null or undefined"); } if (typeof name != "string") { throw SceneJS_errorModule.fatalError("bind param 'name' should be a string"); } if (!handler) { throw SceneJS_errorModule.fatalError("bind param 'handler' null or undefined"); } if (typeof handler != "function") { throw SceneJS_errorModule.fatalError("bind param 'handler' should be a function"); } else { this._targetNode.addListener(name, handler, { scope: this }); SceneJS_compileModule.nodeUpdated(this._targetNode, "bind", name); } //else { // var commandService = SceneJS.Services.getService(SceneJS.Services.COMMAND_SERVICE_ID); // if (!handler.target) { // handler.target = this._targetNode.attr.id; // } // this._targetNode.addListener( // name, // function(params) { // commandService.executeCommand(handler); // }, { scope: this }); // } return this; }; /** Unbinds a listener for an event on the selected node * * @param {String} name Event name * @param {Function} handler Event handler */ NodeSelector.prototype.unbind = function(name, handler) { if (!name) { throw SceneJS_errorModule.fatalError("bind param 'name' null or undefined"); } if (typeof name != "string") { throw SceneJS_errorModule.fatalError("bind param 'name' should be a string"); } if (!handler) { throw SceneJS_errorModule.fatalError("bind param 'handler' null or undefined"); } if (typeof handler != "function") { throw SceneJS_errorModule.fatalError("bind param 'handler' should be a function"); } else { this._targetNode.removeListener(name, handler); // SceneJS_compileModule.nodeUpdated(this._targetNode); SceneJS_compileModule.nodeUpdated(this._targetNode, "unbind", name); } return this; }; /** * Performs pick on the selected scene node, which must be a scene. * @param offsetX Canvas X-coordinate * @param offsetY Canvas Y-coordinate */ NodeSelector.prototype.pick = function(offsetX, offsetY, options) { if (!offsetX) { throw SceneJS_errorModule.fatalError("pick param 'offsetX' null or undefined"); } if (typeof offsetX != "number") { throw SceneJS_errorModule.fatalError("pick param 'offsetX' should be a number"); } if (!offsetY) { throw SceneJS_errorModule.fatalError("pick param 'offsetY' null or undefined"); } if (typeof offsetY != "number") { throw SceneJS_errorModule.fatalError("pick param 'offsetY' should be a number"); } if (this._targetNode.attr.type != "scene") { throw SceneJS_errorModule.fatalError("pick attempted on node that is not a \"scene\" type: '" + this._targetNode.attr.id + "'"); } return this._targetNode.pick(offsetX, offsetY, options); }; /** * Either render frame on selected scene if pending (ie update compilations pending), or * force a new frame to be rendered. * * Returns true if frame was rendered. * * Render if pending: * * var wasRendered = SceneJS.scene("my-scene").renderFrame() * * Force a new frame, returns true: * * SceneJS.scene("my-scene").renderFrame({ force: true }) */ NodeSelector.prototype.renderFrame = function (params) { if (this._targetNode.attr.type != "scene") { throw SceneJS_errorModule.fatalError("renderFrame attempted on node that is not a \"scene\" type: '" + this._targetNode.attr.id + "'"); } return this._targetNode.renderFrame(params); }; /** * Starts the selected scene node, which must be a scene. */ NodeSelector.prototype.start = function (cfg) { if (this._targetNode.attr.type != "scene") { throw SceneJS_errorModule.fatalError("start attempted on node that is not a \"scene\" type: '" + this._targetNode.attr.id + "'"); } cfg = cfg || {}; var self = this; // Wrap callbacks to call on selector as scope if (cfg.idleFunc) { var idleFunc = cfg.idleFunc; cfg.idleFunc = function(params) { idleFunc.call(self, params); }; } if (cfg.frameFunc) { var frameFunc = cfg.frameFunc; cfg.frameFunc = function() { frameFunc.call(self); }; } if (cfg.sleepFunc) { var sleepFunc = cfg.sleepFunc; cfg.sleepFunc = function() { sleepFunc.call(self); }; } this._targetNode.start(cfg); return this; }; /** * Stops the selected scene node, which must be a scene. */ NodeSelector.prototype.stop = function () { if (this._targetNode.attr.type != "scene") { throw SceneJS_errorModule.fatalError("stop attempted on node that is not a \"scene\" '" + this._targetNode.attr.id + "'"); } this._targetNode.stop(); return this; }; /** * Stops the selected scene node, which must be a scene. */ NodeSelector.prototype.pause = function (doPause) { if (this._targetNode.attr.type != "scene") { throw SceneJS_errorModule.fatalError("pause attempted on node that is not a \"scene\" '" + this._targetNode.attr.id + "'"); } this._targetNode.pause(doPause); return this; }; /** * Splices the selected scene node - replaces itself on its parent with its child nodes */ NodeSelector.prototype.splice = function() { this._targetNode.splice(); return this; }; /** * Destroys the selected scene node */ NodeSelector.prototype.destroy = function() { this._targetNode.destroy(); return this; }; /** Allows us to get or set data of any type on the scene node. * Modifying data does not trigger rendering. */ NodeSelector.prototype.data = function(data, value) { if (!data) { return this._targetNode.attr.data; } this._targetNode.attr.data = this._targetNode.attr.data || {}; if (typeof data == "string") { if (value != undefined) { this._targetNode.attr.data[data] = value; return this; } else { return this._targetNode.attr.data[data]; } } else { if (value != undefined) { this._targetNode.attr.data = value; return this; } else { return this._targetNode.attr.data; } } }; NodeSelector.prototype._iterateEachNode = function(fn, node, count) { var len = node.children.length; var selector; for (var i = 0; i < len; i++) { selector = SceneJS._selectNode(node.children[i]); if (fn.call(selector, count++) == true) { return selector; } } return undefined; }; NodeSelector.prototype._iterateEachNodeDepthFirst = function(fn, node, count, belowRoot) { var selector; if (belowRoot) { /* Visit this node - if we are below root, because entry point visits the root */ selector = SceneJS._selectNode(node); if (fn.call(selector, count++) == true) { return selector; } } belowRoot = true; /* Iterate children */ var len = node.children.length; for (var i = 0; i < len; i++) { selector = this._iterateEachNodeDepthFirst(fn, node.children[i], count, belowRoot); if (selector) { return selector; } } return undefined; }; NodeSelector.prototype._callNodeMethod = function(prefix, attr, value, targetNode) { var methods = this._methods[prefix]; if (!methods) { methods = this._methods[prefix] = {}; } var func = methods[attr]; if (!func) { attr = attr.replace(/^\s*/, "").replace(/\s*$/, ""); // trim var funcName = prefix + attr.substr(0, 1).toUpperCase() + attr.substr(1); func = targetNode[funcName]; if (!func) { throw SceneJS_errorModule.fatalError("Attribute '" + attr + "' not found on node '" + targetNode.attr.id + "' for " + prefix); } methods[attr] = func; } //func.call(targetNode, this._parseAttr(attr, value)); func.call(targetNode, value); /* TODO: optimise - dont fire unless listener exists */ var params = {}; params[attr] = value; SceneJS_compileModule.nodeUpdated(targetNode, prefix, attr, value); /* TODO: event should be queued and consumed to avoid many of these events accumulating */ targetNode._fireEvent("updated", params); }; NodeSelector.prototype._callNodeMethods = function(prefix, attr, targetNode) { var methods = this._methods[prefix]; if (!methods) { methods = this._methods[prefix] = {}; } for (var key in attr) { if (attr.hasOwnProperty(key)) { var func = methods[key]; if (!func) { key = key.replace(/^\s*/, "").replace(/\s*$/, ""); // trim var funcName = prefix + key.substr(0, 1).toUpperCase() + key.substr(1); func = targetNode[funcName]; if (!func) { throw SceneJS_errorModule.fatalError("Attribute '" + key + "' not found on node '" + targetNode.attr.id + "' for " + prefix); } methods[key] = func; } //func.call(targetNode, this._parseAttr(key, attr[key])); func.call(targetNode, attr[key]); SceneJS_compileModule.nodeUpdated(targetNode, prefix, key, attr[key]); } } /* TODO: optimise - dont fire unless listener exists */ /* TODO: event should be queued and consumed to avoid many of these events accumulating */ targetNode._fireEvent("updated", { attr: attr }); }; /** Given an attribute name of the form "alpha.beta" and a value, returns this sort of thing: * * { * "alpha": { * "beta": value * } * } * */ NodeSelector.prototype._parseAttr = function(attr, value) { var tokens = attr.split("."); if (tokens.length <= 1) { return value; } var obj = {}; var root = obj; var name; var i = 0; var len = tokens.length - 1; while (i < len) { obj[tokens[i++]] = value; } obj = obj[name] = {}; return root; }; })(); SceneJS.Services.addService( SceneJS.Services.COMMAND_SERVICE_ID, (function() { var commands = {}; return { addCommand: function(commandId, command) { if (!command.execute) { throw SceneJS_errorModule.fatalError("SceneJS Command Service (ID '" + SceneJS.Services.COMMAND_SERVICE_ID + ") requires an 'execute' method on your '" + commandId + " command implementation"); } commands[commandId] = command; }, hasCommand: function(commandId) { var command = commands[commandId]; return (command != null && command != undefined); }, getCommand: function(commandId) { return commands[commandId]; }, executeCommand : function (ctx, params) { if (!params) { throw SceneJS_errorModule.fatalError("sendMessage param 'message' null or undefined"); } var commandId = params.command; if (!commandId) { throw SceneJS_errorModule.fatalError("Message element expected: 'command'"); } var commandService = SceneJS.Services.getService(SceneJS.Services.COMMAND_SERVICE_ID); var command = commandService.getCommand(commandId); if (!command) { throw SceneJS_errorModule.fatalError("Message command not supported: '" + commandId + "' - perhaps this command needs to be added to the SceneJS Command Service?"); } command.execute(ctx, params); } }; })()); (function() { var commandService = SceneJS.Services.getService(SceneJS.Services.COMMAND_SERVICE_ID); function createNodes(scene, target, nodes) { var node; var targetNode; for (var i = 0; i < nodes.length; i++) { node = nodes[i]; if (target) { targetNode = scene.findNode(target); if (!targetNode) { continue; } targetNode.add("node", nodes[i]); } else { scene.addNode(nodes[i]); } } } commandService.addCommand("create", (function() { return { execute: function(ctx, params) { var nodes = params.nodes; if (nodes) { var target = params.target; var scenes; var scene; if (ctx.scenes) { scenes = ctx.scenes; for (var i = 0, len = scenes.length; i < len; i++) { scene = scenes[i]; if (scene) { // Scene might have been blown away by some other command createNodes(scene, target, nodes); } } } else { scenes = SceneJS._scenes; for (var sceneId in scenes) { if (scenes.hasOwnProperty(sceneId)) { scene = scenes[sceneId]; if (scene) { // Scene might have been blown away by some other command createNodes(scene, target, nodes); } } } } } } }; })()); function updateNode(scene, target, params) { var targetNode = scene.findNode(target); if (!targetNode) { // Node might have been blown away by some other command return; } var sett = params["set"]; if (sett) { callNodeMethods("set", sett, targetNode); } if (params.insert) { callNodeMethods("insert", params.insert, targetNode); } if (params.inc) { callNodeMethods("inc", params.inc, targetNode); } if (params.dec) { callNodeMethods("dec", params.dec, targetNode); } if (params.add) { callNodeMethods("add", params.add, targetNode); } if (params.remove) { callNodeMethods("remove", params.remove, targetNode); } } commandService.addCommand("update", { execute: function(ctx, params) { var i, len; var scenes; var target = params.target; var scene; if (ctx.scenes) { scenes = ctx.scenes; for (i = 0, len = scenes.length; i < len; i++) { scene = scenes[i]; if (scene) { // Scene might have been blown away by some other command updateNode(scene, target, params); } } } else { scenes = SceneJS._scenes; for (var sceneId in scenes) { if (scenes.hasOwnProperty(sceneId)) { scene = scenes[sceneId]; if (scene) { // Scene might have been blown away by some other command updateNode(scene, target, params); } } } } /* Further messages */ var messages = params.messages; if (messages && messages.length > 0) { for (i = 0; i < messages.length; i++) { commandService.executeCommand(ctx, messages[i]); } } } }); commandService.addCommand("selectScenes", { execute: function(ctx, params) { var i, len; var scenes = params.scenes; if (scenes) { /* Filter out the scenes that actually exist so sub-commands don't have to check */ var existingScenes = []; var sceneId; var scene; for (i = 0, len = scenes.length; i < len; i++) { sceneId = scenes[i]; if (SceneJS._scenes[sceneId]) { existingScenes.push(SceneJS.scene(sceneId)); } } ctx = SceneJS._shallowClone(ctx); // Feed scenes into command context for sub-messages ctx.scenes = existingScenes; } /* Further messages */ var messages = params.messages; if (messages) { for (i = 0; i < messages.length; i++) { commandService.executeCommand(ctx, messages[i]); } } } }); function callNodeMethods(prefix, attr, targetNode) { for (var key in attr) { if (attr.hasOwnProperty(key)) { targetNode[prefix](attr); } } } })(); /** * Backend that manages configurations. * * @private */ var SceneJS_debugModule = new (function() { this.configs = {}; this.getConfigs = function(path) { if (!path) { return this.configs; } else { var cfg = this.configs; var parts = path.split("."); for (var i = 0; cfg && i < parts.length; i++) { cfg = cfg[parts[i]]; } return cfg || {}; } }; this.setConfigs = function(path, data) { if (!path) { this.configs = data; } else { var parts = path.split("."); var cfg = this.configs; var subCfg; var name; for (var i = 0; i < parts.length - 1; i++) { name = parts[i]; subCfg = cfg[name]; if (!subCfg) { subCfg = cfg[name] = {}; } cfg = subCfg; } cfg[parts.length - 1] = data; } }; })(); /** Sets configurations. */ SceneJS.setConfigs = SceneJS.setDebugConfigs = function () { if (arguments.length == 1) { SceneJS_debugModule.setConfigs(null, arguments[0]); } else if (arguments.length == 2) { SceneJS_debugModule.setConfigs(arguments[0], arguments[1]); } else { throw SceneJS_errorModule.fatalError("Illegal arguments given to SceneJS.setDebugs - should be either ({String}:name, {Object}:cfg) or ({Object}:cfg)"); } }; /** Gets configurations */ SceneJS.getConfigs = SceneJS.getDebugConfigs = function (path) { return SceneJS_debugModule.getConfigs(path); }; SceneJS.errors = {}; SceneJS.errors.ERROR = 0; SceneJS.errors.WEBGL_NOT_SUPPORTED = 1; SceneJS.errors.NODE_CONFIG_EXPECTED = 2; SceneJS.errors.ILLEGAL_NODE_CONFIG = 3; SceneJS.errors.SHADER_COMPILATION_FAILURE = 4; SceneJS.errors.SHADER_LINK_FAILURE = 5; SceneJS.errors.CANVAS_NOT_FOUND = 6; SceneJS.errors.OUT_OF_VRAM = 7; SceneJS.errors.WEBGL_UNSUPPORTED_NODE_CONFIG = 8; SceneJS.errors.INSTANCE_TARGET_NOT_FOUND = 9; SceneJS.errors.INSTANCE_CYCLE = 10; SceneJS.errors.NODE_NOT_FOUND = 11; SceneJS.errors.NODE_ILLEGAL_STATE = 12; SceneJS.errors.ID_CLASH = 13; SceneJS.errors.ILLEGAL_MESSAGE = 14; SceneJS.errors.SCENE_ILLEGAL_UPDATE = 15; SceneJS.errors._getErrorName = function(code) { for (var key in SceneJS.errors) { if (SceneJS.errors.hasOwnProperty(key) && SceneJS.errors[key] == code) { return key; } } return null; } /* * Optimizations made based on glMatrix by Brandon Jones */ /* * Copyright (c) 2010 Brandon Jones * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not * be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ /** * @param u vec3 * @param v vec3 * @param dest vec3 - optional destination * @return {vec3} dest if specified, u otherwise * @private */ var SceneJS_math_divVec3 = function(u, v, dest) { if (!dest) { dest = u; } dest[0] = u[0] / v[0]; dest[1] = u[1] / v[1]; dest[2] = u[2] / v[2]; return dest; }; /** * @param v vec4 * @param dest vec4 - optional destination * @return {vec4} dest if specified, v otherwise * @private */ var SceneJS_math_negateVector4 = function(v, dest) { if (!dest) { dest = v; } dest[0] = -v[0]; dest[1] = -v[1]; dest[2] = -v[2]; dest[3] = -v[3]; return dest; }; /** * @param u vec4 * @param v vec4 * @param dest vec4 - optional destination * @return {vec4} dest if specified, u otherwise * @private */ var SceneJS_math_addVec4 = function(u, v, dest) { if (!dest) { dest = u; } dest[0] = u[0] + v[0]; dest[1] = u[1] + v[1]; dest[2] = u[2] + v[2]; dest[3] = u[3] + v[3]; return dest; }; /** * @param v vec4 * @param s scalar * @param dest vec4 - optional destination * @return {vec4} dest if specified, v otherwise * @private */ var SceneJS_math_addVec4s = function(v, s, dest) { if (!dest) { dest = v; } dest[0] = v[0] + s; dest[1] = v[1] + s; dest[2] = v[2] + s; dest[3] = v[3] + s; return dest; }; /** * @param u vec3 * @param v vec3 * @param dest vec3 - optional destination * @return {vec3} dest if specified, u otherwise * @private */ var SceneJS_math_addVec3 = function(u, v, dest) { if (!dest) { dest = u; } dest[0] = u[0] + v[0]; dest[1] = u[1] + v[1]; dest[2] = u[2] + v[2]; return dest; }; /** * @param v vec3 * @param s scalar * @param dest vec3 - optional destination * @return {vec3} dest if specified, v otherwise * @private */ var SceneJS_math_addVec3s = function(v, s, dest) { if (!dest) { dest = v; } dest[0] = v[0] + s; dest[1] = v[1] + s; dest[2] = v[2] + s; return dest; }; /** @private */ var SceneJS_math_addScalarVec4 = function(s, v, dest) { return SceneJS_math_addVec4s(v, s, dest); }; /** * @param u vec4 * @param v vec4 * @param dest vec4 - optional destination * @return {vec4} dest if specified, u otherwise * @private */ var SceneJS_math_subVec4 = function(u, v, dest) { if (!dest) { dest = u; } dest[0] = u[0] - v[0]; dest[1] = u[1] - v[1]; dest[2] = u[2] - v[2]; dest[3] = u[3] - v[3]; return dest; }; /** * @param u vec3 * @param v vec3 * @param dest vec3 - optional destination * @return {vec3} dest if specified, v otherwise * @private */ var SceneJS_math_subVec3 = function(u, v, dest) { if (!dest) { dest = u; } dest[0] = u[0] - v[0]; dest[1] = u[1] - v[1]; dest[2] = u[2] - v[2]; return dest; }; var SceneJS_math_lerpVec3 = function(t, t1, t2, p1, p2) { var f2 = (t - t1) / (t2 - t1); var f1 = 1.0 - f2; return { x: p1.x * f1 + p2.x * f2, y: p1.y * f1 + p2.y * f2, z: p1.z * f1 + p2.z * f2 }; }; /** * @param u vec2 * @param v vec2 * @param dest vec2 - optional destination * @return {vec2} dest if specified, u otherwise * @private */ var SceneJS_math_subVec2 = function(u, v, dest) { if (!dest) { dest = u; } dest[0] = u[0] - v[0]; dest[1] = u[1] - v[1]; return dest; }; /** * @param v vec4 * @param s scalar * @param dest vec4 - optional destination * @return {vec4} dest if specified, v otherwise * @private */ var SceneJS_math_subVec4Scalar = function(v, s, dest) { if (!dest) { dest = v; } dest[0] = v[0] - s; dest[1] = v[1] - s; dest[2] = v[2] - s; dest[3] = v[3] - s; return dest; }; /** * @param v vec4 * @param s scalar * @param dest vec4 - optional destination * @return {vec4} dest if specified, v otherwise * @private */ var SceneJS_math_subScalarVec4 = function(v, s, dest) { if (!dest) { dest = v; } dest[0] = s - v[0]; dest[1] = s - v[1]; dest[2] = s - v[2]; dest[3] = s - v[3]; return dest; }; /** * @param u vec4 * @param v vec4 * @param dest vec4 - optional destination * @return {vec4} dest if specified, u otherwise * @private */ var SceneJS_math_mulVec4 = function(u, v, dest) { if (!dest) { dest = u; } dest[0] = u[0] * v[0]; dest[1] = u[1] * v[1]; dest[2] = u[2] * v[2]; dest[3] = u[3] * v[3]; return dest; }; /** * @param v vec4 * @param s scalar * @param dest vec4 - optional destination * @return {vec4} dest if specified, v otherwise * @private */ var SceneJS_math_mulVec4Scalar = function(v, s, dest) { if (!dest) { dest = v; } dest[0] = v[0] * s; dest[1] = v[1] * s; dest[2] = v[2] * s; dest[3] = v[3] * s; return dest; }; /** * @param v vec3 * @param s scalar * @param dest vec3 - optional destination * @return {vec3} dest if specified, v otherwise * @private */ var SceneJS_math_mulVec3Scalar = function(v, s, dest) { if (!dest) { dest = v; } dest[0] = v[0] * s; dest[1] = v[1] * s; dest[2] = v[2] * s; return dest; }; /** * @param v vec2 * @param s scalar * @param dest vec2 - optional destination * @return {vec2} dest if specified, v otherwise * @private */ var SceneJS_math_mulVec2Scalar = function(v, s, dest) { if (!dest) { dest = v; } dest[0] = v[0] * s; dest[1] = v[1] * s; return dest; }; /** * @param u vec4 * @param v vec4 * @param dest vec4 - optional destination * @return {vec4} dest if specified, u otherwise * @private */ var SceneJS_math_divVec4 = function(u, v, dest) { if (!dest) { dest = u; } dest[0] = u[0] / v[0]; dest[1] = u[1] / v[1]; dest[2] = u[2] / v[2]; dest[3] = u[3] / v[3]; return dest; }; /** * @param v vec3 * @param s scalar * @param dest vec3 - optional destination * @return {vec3} dest if specified, v otherwise * @private */ var SceneJS_math_divScalarVec3 = function(s, v, dest) { if (!dest) { dest = v; } dest[0] = s / v[0]; dest[1] = s / v[1]; dest[2] = s / v[2]; return dest; }; /** * @param v vec3 * @param s scalar * @param dest vec3 - optional destination * @return {vec3} dest if specified, v otherwise * @private */ var SceneJS_math_divVec3s = function(v, s, dest) { if (!dest) { dest = v; } dest[0] = v[0] / s; dest[1] = v[1] / s; dest[2] = v[2] / s; return dest; }; /** * @param v vec4 * @param s scalar * @param dest vec4 - optional destination * @return {vec4} dest if specified, v otherwise * @private */ var SceneJS_math_divVec4s = function(v, s, dest) { if (!dest) { dest = v; } dest[0] = v[0] / s; dest[1] = v[1] / s; dest[2] = v[2] / s; dest[3] = v[3] / s; return dest; }; /** * @param s scalar * @param v vec4 * @param dest vec4 - optional destination * @return {vec4} dest if specified, v otherwise * @private */ var SceneJS_math_divScalarVec4 = function(s, v, dest) { if (!dest) { dest = v; } dest[0] = s / v[0]; dest[1] = s / v[1]; dest[2] = s / v[2]; dest[3] = s / v[3]; return dest; }; /** @private */ var SceneJS_math_dotVector4 = function(u, v) { return (u[0] * v[0] + u[1] * v[1] + u[2] * v[2] + u[3] * v[3]); }; /** @private */ var SceneJS_math_cross3Vec4 = function(u, v) { var u0 = u[0], u1 = u[1], u2 = u[2]; var v0 = v[0], v1 = v[1], v2 = v[2]; return [ u1 * v2 - u2 * v1, u2 * v0 - u0 * v2, u0 * v1 - u1 * v0, 0.0]; }; /** * @param u vec3 * @param v vec3 * @param dest vec3 - optional destination * @return {vec3} dest if specified, u otherwise * @private */ var SceneJS_math_cross3Vec3 = function(u, v, dest) { if (!dest) { dest = u; } var x = u[0], y = u[1], z = u[2]; var x2 = v[0], y2 = v[1], z2 = v[2]; dest[0] = y * z2 - z * y2; dest[1] = z * x2 - x * z2; dest[2] = x * y2 - y * x2; return dest; }; /** @private */ var SceneJS_math_sqLenVec4 = function(v) { return SceneJS_math_dotVector4(v, v); }; /** @private */ var SceneJS_math_lenVec4 = function(v) { return Math.sqrt(SceneJS_math_sqLenVec4(v)); }; /** @private */ var SceneJS_math_dotVector3 = function(u, v) { return (u[0] * v[0] + u[1] * v[1] + u[2] * v[2]); }; /** @private */ var SceneJS_math_dotVector2 = function(u, v) { return (u[0] * v[0] + u[1] * v[1]); }; /** @private */ var SceneJS_math_sqLenVec3 = function(v) { return SceneJS_math_dotVector3(v, v); }; /** @private */ var SceneJS_math_sqLenVec2 = function(v) { return SceneJS_math_dotVector2(v, v); }; /** @private */ var SceneJS_math_lenVec3 = function(v) { return Math.sqrt(SceneJS_math_sqLenVec3(v)); }; /** @private */ var SceneJS_math_lenVec2 = function(v) { return Math.sqrt(SceneJS_math_sqLenVec2(v)); }; /** * @param v vec3 * @param dest vec3 - optional destination * @return {vec3} dest if specified, v otherwise * @private */ var SceneJS_math_rcpVec3 = function(v, dest) { return SceneJS_math_divScalarVec3(1.0, v, dest); }; /** * @param v vec4 * @param dest vec4 - optional destination * @return {vec4} dest if specified, v otherwise * @private */ var SceneJS_math_normalizeVec4 = function(v, dest) { var f = 1.0 / SceneJS_math_lenVec4(v); return SceneJS_math_mulVec4Scalar(v, f, dest); }; /** @private */ var SceneJS_math_normalizeVec3 = function(v, dest) { var f = 1.0 / SceneJS_math_lenVec3(v); return SceneJS_math_mulVec3Scalar(v, f, dest); }; // @private var SceneJS_math_normalizeVec2 = function(v, dest) { var f = 1.0 / SceneJS_math_lenVec2(v); return SceneJS_math_mulVec2Scalar(v, f, dest); }; /** @private */ var SceneJS_math_mat4 = function() { return new Array(16); }; /** @private */ var SceneJS_math_dupMat4 = function(m) { return m.slice(0, 16); }; /** @private */ var SceneJS_math_getCellMat4 = function(m, row, col) { return m[row + col * 4]; }; /** @private */ var SceneJS_math_setCellMat4 = function(m, row, col, s) { m[row + col * 4] = s; }; /** @private */ var SceneJS_math_getRowMat4 = function(m, r) { return [m[r], m[r + 4], m[r + 8], m[r + 12]]; }; /** @private */ var SceneJS_math_setRowMat4 = function(m, r, v) { m[r] = v[0]; m[r + 4] = v[1]; m[r + 8] = v[2]; m[r + 12] = v[3]; }; /** @private */ var SceneJS_math_setRowMat4c = function(m, r, x, y, z, w) { SceneJS_math_setRowMat4(m, r, [x,y,z,w]); }; /** @private */ var SceneJS_math_setRowMat4s = function(m, r, s) { SceneJS_math_setRowMat4c(m, r, s, s, s, s); }; /** @private */ var SceneJS_math_getColMat4 = function(m, c) { var i = c * 4; return [m[i], m[i + 1],m[i + 2],m[i + 3]]; }; /** @private */ var SceneJS_math_setColMat4v = function(m, c, v) { var i = c * 4; m[i] = v[0]; m[i + 1] = v[1]; m[i + 2] = v[2]; m[i + 3] = v[3]; }; /** @private */ var SceneJS_math_setColMat4c = function(m, c, x, y, z, w) { SceneJS_math_setColMat4v(m, c, [x,y,z,w]); }; /** @private */ var SceneJS_math_setColMat4Scalar = function(m, c, s) { SceneJS_math_setColMat4c(m, c, s, s, s, s); }; /** @private */ var SceneJS_math_mat4To3 = function(m) { return [ m[0],m[1],m[2], m[4],m[5],m[6], m[8],m[9],m[10] ]; }; /** @private */ var SceneJS_math_m4s = function(s) { return [ s,s,s,s, s,s,s,s, s,s,s,s, s,s,s,s ]; }; /** @private */ var SceneJS_math_setMat4ToZeroes = function() { return SceneJS_math_m4s(0.0); }; /** @private */ var SceneJS_math_setMat4ToOnes = function() { return SceneJS_math_m4s(1.0); }; /** @private */ var SceneJS_math_diagonalMat4v = function(v) { return [ v[0], 0.0, 0.0, 0.0, 0.0,v[1], 0.0, 0.0, 0.0, 0.0, v[2],0.0, 0.0, 0.0, 0.0, v[3] ]; }; /** @private */ var SceneJS_math_diagonalMat4c = function(x, y, z, w) { return SceneJS_math_diagonalMat4v([x,y,z,w]); }; /** @private */ var SceneJS_math_diagonalMat4s = function(s) { return SceneJS_math_diagonalMat4c(s, s, s, s); }; /** @private */ var SceneJS_math_identityMat4 = function() { return SceneJS_math_diagonalMat4v([1.0,1.0,1.0,1.0]); }; /** @private */ var SceneJS_math_isIdentityMat4 = function(m) { if (m[0] !== 1.0 || m[1] !== 0.0 || m[2] !== 0.0 || m[3] !== 0.0 || m[4] !== 0.0 || m[5] !== 1.0 || m[6] !== 0.0 || m[7] !== 0.0 || m[8] !== 0.0 || m[9] !== 0.0 || m[10] !== 1.0 || m[11] !== 0.0 || m[12] !== 0.0 || m[13] !== 0.0 || m[14] !== 0.0 || m[15] !== 1.0) { return false; } return true; }; /** * @param m mat4 * @param dest mat4 - optional destination * @return {mat4} dest if specified, m otherwise * @private */ var SceneJS_math_negateMat4 = function(m, dest) { if (!dest) { dest = m; } dest[0] = -m[0]; dest[1] = -m[1]; dest[2] = -m[2]; dest[3] = -m[3]; dest[4] = -m[4]; dest[5] = -m[5]; dest[6] = -m[6]; dest[7] = -m[7]; dest[8] = -m[8]; dest[9] = -m[9]; dest[10] = -m[10]; dest[11] = -m[11]; dest[12] = -m[12]; dest[13] = -m[13]; dest[14] = -m[14]; dest[15] = -m[15]; return dest; }; /** * @param a mat4 * @param b mat4 * @param dest mat4 - optional destination * @return {mat4} dest if specified, a otherwise * @private */ var SceneJS_math_addMat4 = function(a, b, dest) { if (!dest) { dest = a; } dest[0] = a[0] + b[0]; dest[1] = a[1] + b[1]; dest[2] = a[2] + b[2]; dest[3] = a[3] + b[3]; dest[4] = a[4] + b[4]; dest[5] = a[5] + b[5]; dest[6] = a[6] + b[6]; dest[7] = a[7] + b[7]; dest[8] = a[8] + b[8]; dest[9] = a[9] + b[9]; dest[10] = a[10] + b[10]; dest[11] = a[11] + b[11]; dest[12] = a[12] + b[12]; dest[13] = a[13] + b[13]; dest[14] = a[14] + b[14]; dest[15] = a[15] + b[15]; return dest; }; /** * @param m mat4 * @param s scalar * @param dest mat4 - optional destination * @return {mat4} dest if specified, m otherwise * @private */ var SceneJS_math_addMat4Scalar = function(m, s, dest) { if (!dest) { dest = m; } dest[0] = m[0] + s; dest[1] = m[1] + s; dest[2] = m[2] + s; dest[3] = m[3] + s; dest[4] = m[4] + s; dest[5] = m[5] + s; dest[6] = m[6] + s; dest[7] = m[7] + s; dest[8] = m[8] + s; dest[9] = m[9] + s; dest[10] = m[10] + s; dest[11] = m[11] + s; dest[12] = m[12] + s; dest[13] = m[13] + s; dest[14] = m[14] + s; dest[15] = m[15] + s; return dest; }; /** @private */ var SceneJS_math_addScalarMat4 = function(s, m, dest) { return SceneJS_math_addMat4Scalar(m, s, dest); }; /** * @param a mat4 * @param b mat4 * @param dest mat4 - optional destination * @return {mat4} dest if specified, a otherwise * @private */ var SceneJS_math_subMat4 = function(a, b, dest) { if (!dest) { dest = a; } dest[0] = a[0] - b[0]; dest[1] = a[1] - b[1]; dest[2] = a[2] - b[2]; dest[3] = a[3] - b[3]; dest[4] = a[4] - b[4]; dest[5] = a[5] - b[5]; dest[6] = a[6] - b[6]; dest[7] = a[7] - b[7]; dest[8] = a[8] - b[8]; dest[9] = a[9] - b[9]; dest[10] = a[10] - b[10]; dest[11] = a[11] - b[11]; dest[12] = a[12] - b[12]; dest[13] = a[13] - b[13]; dest[14] = a[14] - b[14]; dest[15] = a[15] - b[15]; return dest; }; /** * @param m mat4 * @param s scalar * @param dest mat4 - optional destination * @return {mat4} dest if specified, m otherwise * @private */ var SceneJS_math_subMat4Scalar = function(m, s, dest) { if (!dest) { dest = m; } dest[0] = m[0] - s; dest[1] = m[1] - s; dest[2] = m[2] - s; dest[3] = m[3] - s; dest[4] = m[4] - s; dest[5] = m[5] - s; dest[6] = m[6] - s; dest[7] = m[7] - s; dest[8] = m[8] - s; dest[9] = m[9] - s; dest[10] = m[10] - s; dest[11] = m[11] - s; dest[12] = m[12] - s; dest[13] = m[13] - s; dest[14] = m[14] - s; dest[15] = m[15] - s; return dest; }; /** * @param s scalar * @param m mat4 * @param dest mat4 - optional destination * @return {mat4} dest if specified, m otherwise * @private */ var SceneJS_math_subScalarMat4 = function(s, m, dest) { if (!dest) { dest = m; } dest[0] = s - m[0]; dest[1] = s - m[1]; dest[2] = s - m[2]; dest[3] = s - m[3]; dest[4] = s - m[4]; dest[5] = s - m[5]; dest[6] = s - m[6]; dest[7] = s - m[7]; dest[8] = s - m[8]; dest[9] = s - m[9]; dest[10] = s - m[10]; dest[11] = s - m[11]; dest[12] = s - m[12]; dest[13] = s - m[13]; dest[14] = s - m[14]; dest[15] = s - m[15]; return dest; }; /** * @param a mat4 * @param b mat4 * @param dest mat4 - optional destination * @return {mat4} dest if specified, a otherwise * @private */ var SceneJS_math_mulMat4 = function(a, b, dest) { if (!dest) { dest = a; } // Cache the matrix values (makes for huge speed increases!) var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; var b00 = b[0], b01 = b[1], b02 = b[2], b03 = b[3]; var b10 = b[4], b11 = b[5], b12 = b[6], b13 = b[7]; var b20 = b[8], b21 = b[9], b22 = b[10], b23 = b[11]; var b30 = b[12], b31 = b[13], b32 = b[14], b33 = b[15]; dest[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30; dest[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31; dest[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32; dest[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33; dest[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30; dest[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31; dest[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32; dest[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33; dest[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30; dest[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31; dest[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32; dest[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33; dest[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30; dest[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31; dest[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32; dest[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33; return dest; }; /** * @param m mat4 * @param s scalar * @param dest mat4 - optional destination * @return {mat4} dest if specified, m otherwise * @private */ var SceneJS_math_mulMat4s = function(m, s, dest) { if (!dest) { dest = m; } dest[0] = m[0] * s; dest[1] = m[1] * s; dest[2] = m[2] * s; dest[3] = m[3] * s; dest[4] = m[4] * s; dest[5] = m[5] * s; dest[6] = m[6] * s; dest[7] = m[7] * s; dest[8] = m[8] * s; dest[9] = m[9] * s; dest[10] = m[10] * s; dest[11] = m[11] * s; dest[12] = m[12] * s; dest[13] = m[13] * s; dest[14] = m[14] * s; dest[15] = m[15] * s; return dest; }; /** * @param m mat4 * @param v vec4 * @return {vec4} * @private */ var SceneJS_math_mulMat4v4 = function(m, v) { var v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; return [ m[0] * v0 + m[4] * v1 + m[8] * v2 + m[12] * v3, m[1] * v0 + m[5] * v1 + m[9] * v2 + m[13] * v3, m[2] * v0 + m[6] * v1 + m[10] * v2 + m[14] * v3, m[3] * v0 + m[7] * v1 + m[11] * v2 + m[15] * v3 ]; }; /** * @param mat mat4 * @param dest mat4 - optional destination * @return {mat4} dest if specified, mat otherwise * @private */ var SceneJS_math_transposeMat4 = function(mat, dest) { // If we are transposing ourselves we can skip a few steps but have to cache some values var m4 = mat[4], m14 = mat[14], m8 = mat[8]; var m13 = mat[13], m12 = mat[12], m9 = mat[9]; if (!dest || mat == dest) { var a01 = mat[1], a02 = mat[2], a03 = mat[3]; var a12 = mat[6], a13 = mat[7]; var a23 = mat[11]; mat[1] = m4; mat[2] = m8; mat[3] = m12; mat[4] = a01; mat[6] = m9; mat[7] = m13; mat[8] = a02; mat[9] = a12; mat[11] = m14; mat[12] = a03; mat[13] = a13; mat[14] = a23; return mat; } dest[0] = mat[0]; dest[1] = m4; dest[2] = m8; dest[3] = m12; dest[4] = mat[1]; dest[5] = mat[5]; dest[6] = m9; dest[7] = m13; dest[8] = mat[2]; dest[9] = mat[6]; dest[10] = mat[10]; dest[11] = m14; dest[12] = mat[3]; dest[13] = mat[7]; dest[14] = mat[11]; dest[15] = mat[15]; return dest; }; /** @private */ var SceneJS_math_determinantMat4 = function(mat) { // Cache the matrix values (makes for huge speed increases!) var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3]; var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7]; var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11]; var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15]; return a30 * a21 * a12 * a03 - a20 * a31 * a12 * a03 - a30 * a11 * a22 * a03 + a10 * a31 * a22 * a03 + a20 * a11 * a32 * a03 - a10 * a21 * a32 * a03 - a30 * a21 * a02 * a13 + a20 * a31 * a02 * a13 + a30 * a01 * a22 * a13 - a00 * a31 * a22 * a13 - a20 * a01 * a32 * a13 + a00 * a21 * a32 * a13 + a30 * a11 * a02 * a23 - a10 * a31 * a02 * a23 - a30 * a01 * a12 * a23 + a00 * a31 * a12 * a23 + a10 * a01 * a32 * a23 - a00 * a11 * a32 * a23 - a20 * a11 * a02 * a33 + a10 * a21 * a02 * a33 + a20 * a01 * a12 * a33 - a00 * a21 * a12 * a33 - a10 * a01 * a22 * a33 + a00 * a11 * a22 * a33; }; /** * @param mat mat4 * @param dest mat4 - optional destination * @return {mat4} dest if specified, mat otherwise * @private */ var SceneJS_math_inverseMat4 = function(mat, dest) { if (!dest) { dest = mat; } // Cache the matrix values (makes for huge speed increases!) var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3]; var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7]; var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11]; var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15]; var b00 = a00 * a11 - a01 * a10; var b01 = a00 * a12 - a02 * a10; var b02 = a00 * a13 - a03 * a10; var b03 = a01 * a12 - a02 * a11; var b04 = a01 * a13 - a03 * a11; var b05 = a02 * a13 - a03 * a12; var b06 = a20 * a31 - a21 * a30; var b07 = a20 * a32 - a22 * a30; var b08 = a20 * a33 - a23 * a30; var b09 = a21 * a32 - a22 * a31; var b10 = a21 * a33 - a23 * a31; var b11 = a22 * a33 - a23 * a32; // Calculate the determinant (inlined to avoid double-caching) var invDet = 1 / (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06); dest[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet; dest[1] = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet; dest[2] = (a31 * b05 - a32 * b04 + a33 * b03) * invDet; dest[3] = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet; dest[4] = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet; dest[5] = (a00 * b11 - a02 * b08 + a03 * b07) * invDet; dest[6] = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet; dest[7] = (a20 * b05 - a22 * b02 + a23 * b01) * invDet; dest[8] = (a10 * b10 - a11 * b08 + a13 * b06) * invDet; dest[9] = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet; dest[10] = (a30 * b04 - a31 * b02 + a33 * b00) * invDet; dest[11] = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet; dest[12] = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet; dest[13] = (a00 * b09 - a01 * b07 + a02 * b06) * invDet; dest[14] = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet; dest[15] = (a20 * b03 - a21 * b01 + a22 * b00) * invDet; return dest; }; /** @private */ var SceneJS_math_traceMat4 = function(m) { return (m[0] + m[5] + m[10] + m[15]); }; /** @private */ var SceneJS_math_translationMat4v = function(v) { var m = SceneJS_math_identityMat4(); m[12] = v[0]; m[13] = v[1]; m[14] = v[2]; return m; }; /** @private */ var SceneJS_math_translationMat4c = function(x, y, z) { return SceneJS_math_translationMat4v([x,y,z]); }; /** @private */ var SceneJS_math_translationMat4s = function(s) { return SceneJS_math_translationMat4c(s, s, s); }; /** @private */ var SceneJS_math_rotationMat4v = function(anglerad, axis) { var ax = SceneJS_math_normalizeVec4([axis[0],axis[1],axis[2],0.0]); var s = Math.sin(anglerad); var c = Math.cos(anglerad); var q = 1.0 - c; var x = ax[0]; var y = ax[1]; var z = ax[2]; var xy,yz,zx,xs,ys,zs; //xx = x * x; used once //yy = y * y; used once //zz = z * z; used once xy = x * y; yz = y * z; zx = z * x; xs = x * s; ys = y * s; zs = z * s; var m = SceneJS_math_mat4(); m[0] = (q * x * x) + c; m[1] = (q * xy) + zs; m[2] = (q * zx) - ys; m[3] = 0.0; m[4] = (q * xy) - zs; m[5] = (q * y * y) + c; m[6] = (q * yz) + xs; m[7] = 0.0; m[8] = (q * zx) + ys; m[9] = (q * yz) - xs; m[10] = (q * z * z) + c; m[11] = 0.0; m[12] = 0.0; m[13] = 0.0; m[14] = 0.0; m[15] = 1.0; return m; }; /** @private */ var SceneJS_math_rotationMat4c = function(anglerad, x, y, z) { return SceneJS_math_rotationMat4v(anglerad, [x,y,z]); }; /** @private */ var SceneJS_math_scalingMat4v = function(v) { var m = SceneJS_math_identityMat4(); m[0] = v[0]; m[5] = v[1]; m[10] = v[2]; return m; }; /** @private */ var SceneJS_math_scalingMat4c = function(x, y, z) { return SceneJS_math_scalingMat4v([x,y,z]); }; /** @private */ var SceneJS_math_scalingMat4s = function(s) { return SceneJS_math_scalingMat4c(s, s, s); }; /** * Default lookat properties - eye at 0,0,1, looking at 0,0,0, up vector pointing up Y-axis */ var SceneJS_math_LOOKAT_OBJ = { eye: {x: 0, y:0, z:1.0 }, look: {x:0, y:0, z:0.0 }, up: {x:0, y:1, z:0.0 } }; /** * Default lookat properties in array form - eye at 0,0,1, looking at 0,0,0, up vector pointing up Y-axis */ var SceneJS_math_LOOKAT_ARRAYS = { eye: [0, 0, 1.0], look: [0, 0, 0.0 ], up: [0, 1, 0.0 ] }; /** * Default orthographic projection properties */ var SceneJS_math_ORTHO_OBJ = { left: -1.0, right: 1.0, bottom: -1.0, near: 0.1, top: 1.0, far: 5000.0 }; /** * @param pos vec3 position of the viewer * @param target vec3 point the viewer is looking at * @param up vec3 pointing "up" * @param dest mat4 Optional, mat4 frustum matrix will be written into * * @return {mat4} dest if specified, a new mat4 otherwise */ var SceneJS_math_lookAtMat4v = function(pos, target, up, dest) { if (!dest) { dest = SceneJS_math_mat4(); } var posx = pos[0], posy = pos[1], posz = pos[2], upx = up[0], upy = up[1], upz = up[2], targetx = target[0], targety = target[1], targetz = target[2]; if (posx == targetx && posy == targety && posz == targetz) { return SceneJS_math_identityMat4(); } var z0,z1,z2,x0,x1,x2,y0,y1,y2,len; //vec3.direction(eye, center, z); z0 = posx - targetx; z1 = posy - targety; z2 = posz - targetz; // normalize (no check needed for 0 because of early return) len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2); z0 *= len; z1 *= len; z2 *= len; //vec3.normalize(vec3.cross(up, z, x)); x0 = upy * z2 - upz * z1; x1 = upz * z0 - upx * z2; x2 = upx * z1 - upy * z0; len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2); if (!len) { x0 = 0; x1 = 0; x2 = 0; } else { len = 1 / len; x0 *= len; x1 *= len; x2 *= len; } //vec3.normalize(vec3.cross(z, x, y)); y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0; len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); if (!len) { y0 = 0; y1 = 0; y2 = 0; } else { len = 1 / len; y0 *= len; y1 *= len; y2 *= len; } dest[0] = x0; dest[1] = y0; dest[2] = z0; dest[3] = 0; dest[4] = x1; dest[5] = y1; dest[6] = z1; dest[7] = 0; dest[8] = x2; dest[9] = y2; dest[10] = z2; dest[11] = 0; dest[12] = -(x0 * posx + x1 * posy + x2 * posz); dest[13] = -(y0 * posx + y1 * posy + y2 * posz); dest[14] = -(z0 * posx + z1 * posy + z2 * posz); dest[15] = 1; return dest; }; /** @private */ var SceneJS_math_lookAtMat4c = function(posx, posy, posz, targetx, targety, targetz, upx, upy, upz) { return SceneJS_math_lookAtMat4v([posx,posy,posz], [targetx,targety,targetz], [upx,upy,upz]); }; /** @private */ var SceneJS_math_orthoMat4c = function(left, right, bottom, top, near, far, dest) { if (!dest) { dest = SceneJS_math_mat4(); } var rl = (right - left); var tb = (top - bottom); var fn = (far - near); dest[0] = 2.0 / rl; dest[1] = 0.0; dest[2] = 0.0; dest[3] = 0.0; dest[4] = 0.0; dest[5] = 2.0 / tb; dest[6] = 0.0; dest[7] = 0.0; dest[8] = 0.0; dest[9] = 0.0; dest[10] = -2.0 / fn; dest[11] = 0.0; dest[12] = -(left + right) / rl; dest[13] = -(top + bottom) / tb; dest[14] = -(far + near) / fn; dest[15] = 1.0; return dest; }; /** @private */ var SceneJS_math_frustumMat4v = function(fmin, fmax) { var fmin4 = [fmin[0],fmin[1],fmin[2],0.0]; var fmax4 = [fmax[0],fmax[1],fmax[2],0.0]; var vsum = SceneJS_math_mat4(); SceneJS_math_addVec4(fmax4, fmin4, vsum); var vdif = SceneJS_math_mat4(); SceneJS_math_subVec4(fmax4, fmin4, vdif); var t = 2.0 * fmin4[2]; var m = SceneJS_math_mat4(); var vdif0 = vdif[0], vdif1 = vdif[1], vdif2 = vdif[2]; m[0] = t / vdif0; m[1] = 0.0; m[2] = 0.0; m[3] = 0.0; m[4] = 0.0; m[5] = t / vdif1; m[6] = 0.0; m[7] = 0.0; m[8] = vsum[0] / vdif0; m[9] = vsum[1] / vdif1; m[10] = -vsum[2] / vdif2; m[11] = -1.0; m[12] = 0.0; m[13] = 0.0; m[14] = -t * fmax4[2] / vdif2; m[15] = 0.0; return m; }; /** @private */ var SceneJS_math_frustumMatrix4 = function(left, right, bottom, top, near, far, dest) { if (!dest) { dest = SceneJS_math_mat4(); } var rl = (right - left); var tb = (top - bottom); var fn = (far - near); dest[0] = (near * 2) / rl; dest[1] = 0; dest[2] = 0; dest[3] = 0; dest[4] = 0; dest[5] = (near * 2) / tb; dest[6] = 0; dest[7] = 0; dest[8] = (right + left) / rl; dest[9] = (top + bottom) / tb; dest[10] = -(far + near) / fn; dest[11] = -1; dest[12] = 0; dest[13] = 0; dest[14] = -(far * near * 2) / fn; dest[15] = 0; return dest; }; /** @private */ var SceneJS_math_perspectiveMatrix4 = function(fovyrad, aspectratio, znear, zfar) { var pmin = new Array(4); var pmax = new Array(4); pmin[2] = znear; pmax[2] = zfar; pmax[1] = pmin[2] * Math.tan(fovyrad / 2.0); pmin[1] = -pmax[1]; pmax[0] = pmax[1] * aspectratio; pmin[0] = -pmax[0]; return SceneJS_math_frustumMat4v(pmin, pmax); }; /** @private */ var SceneJS_math_transformPoint3 = function(m, p) { var p0 = p[0], p1 = p[1], p2 = p[2]; return [ (m[0] * p0) + (m[4] * p1) + (m[8] * p2) + m[12], (m[1] * p0) + (m[5] * p1) + (m[9] * p2) + m[13], (m[2] * p0) + (m[6] * p1) + (m[10] * p2) + m[14], (m[3] * p0) + (m[7] * p1) + (m[11] * p2) + m[15] ]; }; /** @private */ var SceneJS_math_transformPoints3 = function(m, points) { var result = new Array(points.length); var len = points.length; var p0, p1, p2; var pi; // cache values var m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3]; var m4 = m[4], m5 = m[5], m6 = m[6], m7 = m[7]; var m8 = m[8], m9 = m[9], m10 = m[10], m11 = m[11]; var m12 = m[12], m13 = m[13], m14 = m[14], m15 = m[15]; for (var i = 0; i < len; ++i) { // cache values pi = points[i]; p0 = pi[0]; p1 = pi[1]; p2 = pi[2]; result[i] = [ (m0 * p0) + (m4 * p1) + (m8 * p2) + m12, (m1 * p0) + (m5 * p1) + (m9 * p2) + m13, (m2 * p0) + (m6 * p1) + (m10 * p2) + m14, (m3 * p0) + (m7 * p1) + (m11 * p2) + m15 ]; } return result; }; /** @private */ var SceneJS_math_transformVector3 = function(m, v) { var v0 = v[0], v1 = v[1], v2 = v[2]; return [ (m[0] * v0) + (m[4] * v1) + (m[8] * v2), (m[1] * v0) + (m[5] * v1) + (m[9] * v2), (m[2] * v0) + (m[6] * v1) + (m[10] * v2) ]; }; var SceneJS_math_transformVector4 = function(m, v) { var v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; return [ m[ 0] * v0 + m[ 4] * v1 + m[ 8] * v2 + m[12] * v3, m[ 1] * v0 + m[ 5] * v1 + m[ 9] * v2 + m[13] * v3, m[ 2] * v0 + m[ 6] * v1 + m[10] * v2 + m[14] * v3, m[ 3] * v0 + m[ 7] * v1 + m[11] * v2 + m[15] * v3 ]; }; /** @private */ var SceneJS_math_projectVec4 = function(v) { var f = 1.0 / v[3]; return [v[0] * f, v[1] * f, v[2] * f, 1.0]; }; /** @private */ var SceneJS_math_Plane3 = function (normal, offset, normalize) { this.normal = [0.0, 0.0, 1.0 ]; this.offset = 0.0; if (normal && offset) { var normal0 = normal[0], normal1 = normal[1], normal2 = normal[2]; this.offset = offset; if (normalize) { var s = Math.sqrt( normal0 * normal0 + normal1 * normal1 + normal2 * normal2 ); if (s > 0.0) { s = 1.0 / s; this.normal[0] = normal0 * s; this.normal[1] = normal1 * s; this.normal[2] = normal2 * s; this.offset *= s; } } } }; /** @private */ var SceneJS_math_MAX_DOUBLE = Number.POSITIVE_INFINITY; /** @private */ var SceneJS_math_MIN_DOUBLE = Number.NEGATIVE_INFINITY; /** @private * */ var SceneJS_math_Box3 = function(min, max) { this.min = min || [ SceneJS_math_MAX_DOUBLE,SceneJS_math_MAX_DOUBLE,SceneJS_math_MAX_DOUBLE ]; this.max = max || [ SceneJS_math_MIN_DOUBLE,SceneJS_math_MIN_DOUBLE,SceneJS_math_MIN_DOUBLE ]; /** @private */ this.init = function(min, max) { this.min[0] = min[0]; this.min[1] = min[1]; this.min[2] = min[2]; this.max[0] = max[0]; this.max[1] = max[1]; this.max[2] = max[2]; return this; }; /** @private */ this.fromPoints = function(points) { var pointsLength = points.length; for (var i = 0; i < pointsLength; ++i) { var points_i3 = points[i][3]; var pDiv0 = points[i][0] / points_i3; var pDiv1 = points[i][1] / points_i3; var pDiv2 = points[i][2] / points_i3; if (pDiv0 < this.min[0]) { this.min[0] = pDiv0; } if (pDiv1 < this.min[1]) { this.min[1] = pDiv1; } if (pDiv2 < this.min[2]) { this.min[2] = pDiv2; } if (pDiv0 > this.max[0]) { this.max[0] = pDiv0; } if (pDiv1 > this.max[1]) { this.max[1] = pDiv1; } if (pDiv2 > this.max[2]) { this.max[2] = pDiv2; } } return this; }; /** @private */ this.isEmpty = function() { return ( (this.min[0] >= this.max[0]) && (this.min[1] >= this.max[1]) && (this.min[2] >= this.max[2]) ); }; /** @private */ this.getCenter = function() { return [ (this.max[0] + this.min[0]) / 2.0, (this.max[1] + this.min[1]) / 2.0, (this.max[2] + this.min[2]) / 2.0 ]; }; /** @private */ this.getSize = function() { return [ (this.max[0] - this.min[0]), (this.max[1] - this.min[1]), (this.max[2] - this.min[2]) ]; }; /** @private */ this.getFacesAreas = function() { var s = this.size; return [ (s[1] * s[2]), (s[0] * s[2]), (s[0] * s[1]) ]; }; /** @private */ this.getSurfaceArea = function() { var a = this.getFacesAreas(); return ((a[0] + a[1] + a[2]) * 2.0); }; /** @private */ this.getVolume = function() { var s = this.size; return (s[0] * s[1] * s[2]); }; /** @private */ this.getOffset = function(half_delta) { this.min[0] -= half_delta; this.min[1] -= half_delta; this.min[2] -= half_delta; this.max[0] += half_delta; this.max[1] += half_delta; this.max[2] += half_delta; return this; }; }; /** @private * * @param min * @param max */ var SceneJS_math_AxisBox3 = function(min, max) { var min0 = min[0], min1 = min[1], min2 = min[2]; var max0 = max[0], max1 = max[1], max2 = max[2]; this.verts = [ [min0, min1, min2], [max0, min1, min2], [max0, max1, min2], [min0, max1, min2], [min0, min1, max2], [max0, min1, max2], [max0, max1, max2], [min0, max1, max2] ]; /** @private */ this.toBox3 = function() { var box = new SceneJS_math_Box3(); for (var i = 0; i < 8; ++i) { var v = this.verts[i]; for (var j = 0; j < 3; ++j) { if (v[j] < box.min[j]) { box.min[j] = v[j]; } if (v[j] > box.max[j]) { box.max[j] = v[j]; } } } }; }; /** @private * * @param center * @param radius */ var SceneJS_math_Sphere3 = function(center, radius) { this.center = [center[0], center[1], center[2] ]; this.radius = radius; /** @private */ this.isEmpty = function() { return (this.radius === 0.0); }; /** @private */ this.surfaceArea = function() { return (4.0 * Math.PI * this.radius * this.radius); }; /** @private */ this.getVolume = function() { var thisRadius = this.radius; return ((4.0 / 3.0) * Math.PI * thisRadius * thisRadius * thisRadius); }; }; /** Creates billboard matrix from given view matrix * @private */ var SceneJS_math_billboardMat = function(viewMatrix) { var rotVec = [ SceneJS_math_getColMat4(viewMatrix, 0), SceneJS_math_getColMat4(viewMatrix, 1), SceneJS_math_getColMat4(viewMatrix, 2) ]; var scaleVec = [ SceneJS_math_lenVec4(rotVec[0]), SceneJS_math_lenVec4(rotVec[1]), SceneJS_math_lenVec4(rotVec[2]) ]; var scaleVecRcp = SceneJS_math_mat4(); SceneJS_math_rcpVec3(scaleVec, scaleVecRcp); var sMat = SceneJS_math_scalingMat4v(scaleVec); //var sMatInv = SceneJS_math_scalingMat4v(scaleVecRcp); SceneJS_math_mulVec4Scalar(rotVec[0], scaleVecRcp[0]); SceneJS_math_mulVec4Scalar(rotVec[1], scaleVecRcp[1]); SceneJS_math_mulVec4Scalar(rotVec[2], scaleVecRcp[2]); var rotMatInverse = SceneJS_math_identityMat4(); SceneJS_math_setRowMat4(rotMatInverse, 0, rotVec[0]); SceneJS_math_setRowMat4(rotMatInverse, 1, rotVec[1]); SceneJS_math_setRowMat4(rotMatInverse, 2, rotVec[2]); //return rotMatInverse; //return SceneJS_math_mulMat4(sMatInv, SceneJS_math_mulMat4(rotMatInverse, sMat)); return SceneJS_math_mulMat4(rotMatInverse, sMat); // return SceneJS_math_mulMat4(sMat, SceneJS_math_mulMat4(rotMatInverse, sMat)); //return SceneJS_math_mulMat4(sMatInv, SceneJS_math_mulMat4(rotMatInverse, sMat)); }; /** @private */ var SceneJS_math_FrustumPlane = function(nx, ny, nz, offset) { var s = 1.0 / Math.sqrt(nx * nx + ny * ny + nz * nz); this.normal = [nx * s, ny * s, nz * s]; this.offset = offset * s; this.testVertex = [ (this.normal[0] >= 0.0) ? (1) : (0), (this.normal[1] >= 0.0) ? (1) : (0), (this.normal[2] >= 0.0) ? (1) : (0)]; }; /** @private */ var SceneJS_math_OUTSIDE_FRUSTUM = 3; /** @private */ var SceneJS_math_INTERSECT_FRUSTUM = 4; /** @private */ var SceneJS_math_INSIDE_FRUSTUM = 5; /** @private */ var SceneJS_math_Frustum = function(viewMatrix, projectionMatrix, viewport) { var m = SceneJS_math_mat4(); SceneJS_math_mulMat4(projectionMatrix, viewMatrix, m); // cache m indexes var m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3]; var m4 = m[4], m5 = m[5], m6 = m[6], m7 = m[7]; var m8 = m[8], m9 = m[9], m10 = m[10], m11 = m[11]; var m12 = m[12], m13 = m[13], m14 = m[14], m15 = m[15]; //var q = [ m[3], m[7], m[11] ]; just reuse m indexes instead of making new var var planes = [ new SceneJS_math_FrustumPlane(m3 - m0, m7 - m4, m11 - m8, m15 - m12), new SceneJS_math_FrustumPlane(m3 + m0, m7 + m4, m11 + m8, m15 + m12), new SceneJS_math_FrustumPlane(m3 - m1, m7 - m5, m11 - m9, m15 - m13), new SceneJS_math_FrustumPlane(m3 + m1, m7 + m5, m11 + m9, m15 + m13), new SceneJS_math_FrustumPlane(m3 - m2, m7 - m6, m11 - m10, m15 - m14), new SceneJS_math_FrustumPlane(m3 + m2, m7 + m6, m11 + m10, m15 + m14) ]; /* Resources for LOD */ var rotVec = [ SceneJS_math_getColMat4(viewMatrix, 0), SceneJS_math_getColMat4(viewMatrix, 1), SceneJS_math_getColMat4(viewMatrix, 2) ]; var scaleVec = [ SceneJS_math_lenVec4(rotVec[0]), SceneJS_math_lenVec4(rotVec[1]), SceneJS_math_lenVec4(rotVec[2]) ]; var scaleVecRcp = SceneJS_math_rcpVec3(scaleVec); var sMat = SceneJS_math_scalingMat4v(scaleVec); var sMatInv = SceneJS_math_scalingMat4v(scaleVecRcp); SceneJS_math_mulVec4Scalar(rotVec[0], scaleVecRcp[0]); SceneJS_math_mulVec4Scalar(rotVec[1], scaleVecRcp[1]); SceneJS_math_mulVec4Scalar(rotVec[2], scaleVecRcp[2]); var rotMatInverse = SceneJS_math_identityMat4(); SceneJS_math_setRowMat4(rotMatInverse, 0, rotVec[0]); SceneJS_math_setRowMat4(rotMatInverse, 1, rotVec[1]); SceneJS_math_setRowMat4(rotMatInverse, 2, rotVec[2]); if (!this.matrix) { this.matrix = SceneJS_math_mat4(); } SceneJS_math_mulMat4(projectionMatrix, viewMatrix, this.matrix); if (!this.billboardMatrix) { this.billboardMatrix = SceneJS_math_mat4(); } SceneJS_math_mulMat4(sMatInv, SceneJS_math_mulMat4(rotMatInverse, sMat), this.billboardMatrix); this.viewport = viewport.slice(0, 4); /** @private */ this.textAxisBoxIntersection = function(box) { var ret = SceneJS_math_INSIDE_FRUSTUM; var bminmax = [ box.min, box.max ]; var plane = null; for (var i = 0; i < 6; ++i) { plane = planes[i]; if (((plane.normal[0] * bminmax[plane.testVertex[0]][0]) + (plane.normal[1] * bminmax[plane.testVertex[1]][1]) + (plane.normal[2] * bminmax[plane.testVertex[2]][2]) + (plane.offset)) < 0.0) { return SceneJS_math_OUTSIDE_FRUSTUM; } if (((plane.normal[0] * bminmax[1 - plane.testVertex[0]][0]) + (plane.normal[1] * bminmax[1 - plane.testVertex[1]][1]) + (plane.normal[2] * bminmax[1 - plane.testVertex[2]][2]) + (plane.offset)) < 0.0) { ret = SceneJS_math_INTERSECT_FRUSTUM; } } return ret; }; /** @private */ this.getProjectedSize = function(box) { var diagVec = SceneJS_math_mat4(); SceneJS_math_subVec3(box.max, box.min, diagVec); var diagSize = SceneJS_math_lenVec3(diagVec); var size = Math.abs(diagSize); var p0 = [ (box.min[0] + box.max[0]) * 0.5, (box.min[1] + box.max[1]) * 0.5, (box.min[2] + box.max[2]) * 0.5, 0.0]; var halfSize = size * 0.5; var p1 = [ -halfSize, 0.0, 0.0, 1.0 ]; var p2 = [ halfSize, 0.0, 0.0, 1.0 ]; p1 = SceneJS_math_mulMat4v4(this.billboardMatrix, p1); p1 = SceneJS_math_addVec4(p1, p0); p1 = SceneJS_math_projectVec4(SceneJS_math_mulMat4v4(this.matrix, p1)); p2 = SceneJS_math_mulMat4v4(this.billboardMatrix, p2); p2 = SceneJS_math_addVec4(p2, p0); p2 = SceneJS_math_projectVec4(SceneJS_math_mulMat4v4(this.matrix, p2)); return viewport[2] * Math.abs(p2[0] - p1[0]); }; this.getProjectedState = function(modelCoords) { var viewCoords = SceneJS_math_transformPoints3(this.matrix, modelCoords); //var canvasBox = { // min: [10000000, 10000000 ], // max: [-10000000, -10000000] //}; // separate variables instead of indexing an array var canvasBoxMin0 = 10000000, canvasBoxMin1 = 10000000; var canvasBoxMax0 = -10000000, canvasBoxMax1 = -10000000; var v, x, y; var arrLen = viewCoords.length; for (var i = 0; i < arrLen; ++i) { v = SceneJS_math_projectVec4(viewCoords[i]); x = v[0]; y = v[1]; if (x < -0.5) { x = -0.5; } if (y < -0.5) { y = -0.5; } if (x > 0.5) { x = 0.5; } if (y > 0.5) { y = 0.5; } if (x < canvasBoxMin0) { canvasBoxMin0 = x; } if (y < canvasBoxMin1) { canvasBoxMin1 = y; } if (x > canvasBoxMax0) { canvasBoxMax0 = x; } if (y > canvasBoxMax1) { canvasBoxMax1 = y; } } canvasBoxMin0 += 0.5; canvasBoxMin1 += 0.5; canvasBoxMax0 += 0.5; canvasBoxMax1 += 0.5; // cache viewport indexes var viewport2 = viewport[2], viewport3 = viewport[3]; canvasBoxMin0 = (canvasBoxMin0 * (viewport2 + 15)); canvasBoxMin1 = (canvasBoxMin1 * (viewport3 + 15)); canvasBoxMax0 = (canvasBoxMax0 * (viewport2 + 15)); canvasBoxMax1 = (canvasBoxMax1 * (viewport3 + 15)); var diagCanvasBoxVec = SceneJS_math_mat4(); SceneJS_math_subVec2([canvasBoxMax0, canvasBoxMax1], [canvasBoxMin0, canvasBoxMin1], diagCanvasBoxVec); var diagCanvasBoxSize = SceneJS_math_lenVec2(diagCanvasBoxVec); if (canvasBoxMin0 < 0) { canvasBoxMin0 = 0; } if (canvasBoxMax0 > viewport2) { canvasBoxMax0 = viewport2; } if (canvasBoxMin1 < 0) { canvasBoxMin1 = 0; } if (canvasBoxMax1 > viewport3) { canvasBoxMax1 = viewport3; } return { canvasBox: { min: [canvasBoxMin0, canvasBoxMin1 ], max: [canvasBoxMax0, canvasBoxMax1 ] }, canvasSize: diagCanvasBoxSize }; }; }; var SceneJS_math_identityQuaternion = function() { return [ 0.0, 0.0, 0.0, 1.0 ]; }; var SceneJS_math_angleAxisQuaternion = function(x, y, z, degrees) { var angleRad = (degrees / 180.0) * Math.PI; var halfAngle = angleRad / 2.0; var fsin = Math.sin(halfAngle); return [ fsin * x, fsin * y, fsin * z, Math.cos(halfAngle) ]; }; var SceneJS_math_mulQuaternions = function(p, q) { var p0 = p[0], p1 = p[1], p2 = p[2], p3 = p[3]; var q0 = q[0], q1 = q[1], q2 = q[2], q3 = q[3]; return [ p3 * q0 + p0 * q3 + p1 * q2 - p2 * q1, p3 * q1 + p1 * q3 + p2 * q0 - p0 * q2, p3 * q2 + p2 * q3 + p0 * q1 - p1 * q0, p3 * q3 - p0 * q0 - p1 * q1 - p2 * q2 ]; }; var SceneJS_math_newMat4FromQuaternion = function(q) { var q0 = q[0], q1 = q[1], q2 = q[2], q3 = q[3]; var tx = 2.0 * q0; var ty = 2.0 * q1; var tz = 2.0 * q2; var twx = tx * q3; var twy = ty * q3; var twz = tz * q3; var txx = tx * q0; var txy = ty * q0; var txz = tz * q0; var tyy = ty * q1; var tyz = tz * q1; var tzz = tz * q2; var m = SceneJS_math_identityMat4(); SceneJS_math_setCellMat4(m, 0, 0, 1.0 - (tyy + tzz)); SceneJS_math_setCellMat4(m, 0, 1, txy - twz); SceneJS_math_setCellMat4(m, 0, 2, txz + twy); SceneJS_math_setCellMat4(m, 1, 0, txy + twz); SceneJS_math_setCellMat4(m, 1, 1, 1.0 - (txx + tzz)); SceneJS_math_setCellMat4(m, 1, 2, tyz - twx); SceneJS_math_setCellMat4(m, 2, 0, txz - twy); SceneJS_math_setCellMat4(m, 2, 1, tyz + twx); SceneJS_math_setCellMat4(m, 2, 2, 1.0 - (txx + tyy)); return m; }; //var SceneJS_math_slerp(t, q1, q2) { // var result = SceneJS_math_identityQuaternion(); // var cosHalfAngle = q1[3] * q2[3] + q1[0] * q2[0] + q1[1] * q2[1] + q1[2] * q2[2]; // if (Math.abs(cosHalfAngle) >= 1) { // return [ q1[0],q1[1], q1[2], q1[3] ]; // } else { // var halfAngle = Math.acos(cosHalfAngle); // var sinHalfAngle = Math.sqrt(1 - cosHalfAngle * cosHalfAngle); // if (Math.abs(sinHalfAngle) < 0.001) { // return [ // q1[0] * 0.5 + q2[0] * 0.5, // q1[1] * 0.5 + q2[1] * 0.5, // q1[2] * 0.5 + q2[2] * 0.5, // q1[3] * 0.5 + q2[3] * 0.5 // ]; // } else { // var a = Math.sin((1 - t) * halfAngle) / sinHalfAngle; // var b = Math.sin(t * halfAngle) / sinHalfAngle; // return [ // q1[0] * a + q2[0] * b, // q1[1] * a + q2[1] * b, // q1[2] * a + q2[2] * b, // q1[3] * a + q2[3] * b // ]; // } // } //} var SceneJS_math_slerp = function(t, q1, q2) { //var result = SceneJS_math_identityQuaternion(); var q13 = q1[3] * 0.0174532925; var q23 = q2[3] * 0.0174532925; var cosHalfAngle = q13 * q23 + q1[0] * q2[0] + q1[1] * q2[1] + q1[2] * q2[2]; if (Math.abs(cosHalfAngle) >= 1) { return [ q1[0],q1[1], q1[2], q1[3] ]; } else { var halfAngle = Math.acos(cosHalfAngle); var sinHalfAngle = Math.sqrt(1 - cosHalfAngle * cosHalfAngle); if (Math.abs(sinHalfAngle) < 0.001) { return [ q1[0] * 0.5 + q2[0] * 0.5, q1[1] * 0.5 + q2[1] * 0.5, q1[2] * 0.5 + q2[2] * 0.5, q1[3] * 0.5 + q2[3] * 0.5 ]; } else { var a = Math.sin((1 - t) * halfAngle) / sinHalfAngle; var b = Math.sin(t * halfAngle) / sinHalfAngle; return [ q1[0] * a + q2[0] * b, q1[1] * a + q2[1] * b, q1[2] * a + q2[2] * b, (q13 * a + q23 * b) * 57.295779579 ]; } } }; var SceneJS_math_normalizeQuaternion = function(q) { var len = SceneJS_math_lenVec4([q[0], q[1], q[2], q[3]]); return [ q[0] / len, q[1] / len, q[2] / len, q[3] / len ]; }; var SceneJS_math_conjugateQuaternion = function(q) { return[-q[0],-q[1],-q[2],q[3]]; }; var SceneJS_math_angleAxisFromQuaternion = function(q) { q = SceneJS_math_normalizeQuaternion(q); var q3 = q[3]; var angle = 2 * Math.acos(q3); var s = Math.sqrt(1 - q3 * q3); if (s < 0.001) { // test to avoid divide by zero, s is always positive due to sqrt return { x : q[0], y : q[1], z : q[2], angle: angle * 57.295779579 }; } else { return { x : q[0] / s, y : q[1] / s, z : q[2] / s, angle: angle * 57.295779579 }; } }; /** Maps SceneJS node parameter names to WebGL enum names * @private */ var SceneJS_webgl_enumMap = { funcAdd: "FUNC_ADD", funcSubtract: "FUNC_SUBTRACT", funcReverseSubtract: "FUNC_REVERSE_SUBTRACT", zero : "ZERO", one : "ONE", srcColor:"SRC_COLOR", oneMinusSrcColor:"ONE_MINUS_SRC_COLOR", dstColor:"DST_COLOR", oneMinusDstColor:"ONE_MINUS_DST_COLOR", srcAlpha:"SRC_ALPHA", oneMinusSrcAlpha:"ONE_MINUS_SRC_ALPHA", dstAlpha:"DST_ALPHA", oneMinusDstAlpha:"ONE_MINUS_DST_ALPHA", contantColor:"CONSTANT_COLOR", oneMinusConstantColor:"ONE_MINUS_CONSTANT_COLOR", constantAlpha:"CONSTANT_ALPHA", oneMinusConstantAlpha:"ONE_MINUS_CONSTANT_ALPHA", srcAlphaSaturate:"SRC_ALPHA_SATURATE", front: "FRONT", back: "BACK", frontAndBack: "FRONT_AND_BACK", never:"NEVER", less:"LESS", equal:"EQUAL", lequal:"LEQUAL", greater:"GREATER", notequal:"NOTEQUAL", gequal:"GEQUAL", always:"ALWAYS", cw:"CW", ccw:"CCW", linear: "LINEAR", nearest: "NEAREST", linearMipMapNearest : "LINEAR_MIPMAP_NEAREST", nearestMipMapNearest : "NEAREST_MIPMAP_NEAREST", nearestMipMapLinear: "NEAREST_MIPMAP_LINEAR", linearMipMapLinear: "LINEAR_MIPMAP_LINEAR", repeat: "REPEAT", clampToEdge: "CLAMP_TO_EDGE", mirroredRepeat: "MIRRORED_REPEAT", alpha:"ALPHA", rgb:"RGB", rgba:"RGBA", luminance:"LUMINANCE", luminanceAlpha:"LUMINANCE_ALPHA", textureBinding2D:"TEXTURE_BINDING_2D", textureBindingCubeMap:"TEXTURE_BINDING_CUBE_MAP", compareRToTexture:"COMPARE_R_TO_TEXTURE", // Hardware Shadowing Z-depth, unsignedByte: "UNSIGNED_BYTE" }; var SceneJS_webgl_ProgramUniform = function(context, program, name, type, size, location, logging) { var func = null; if (type == context.BOOL) { func = function (v) { context.uniform1i(location, v); }; } else if (type == context.BOOL_VEC2) { func = function (v) { context.uniform2iv(location, v); }; } else if (type == context.BOOL_VEC3) { func = function (v) { context.uniform3iv(location, v); }; } else if (type == context.BOOL_VEC4) { func = function (v) { context.uniform4iv(location, v); }; } else if (type == context.INT) { func = function (v) { context.uniform1iv(location, v); }; } else if (type == context.INT_VEC2) { func = function (v) { context.uniform2iv(location, v); }; } else if (type == context.INT_VEC3) { func = function (v) { context.uniform3iv(location, v); }; } else if (type == context.INT_VEC4) { func = function (v) { context.uniform4iv(location, v); }; } else if (type == context.FLOAT) { func = function (v) { context.uniform1f(location, v); }; } else if (type == context.FLOAT_VEC2) { func = function (v) { context.uniform2fv(location, v); }; } else if (type == context.FLOAT_VEC3) { func = function (v) { context.uniform3fv(location, v); }; } else if (type == context.FLOAT_VEC4) { func = function (v) { context.uniform4fv(location, v); }; } else if (type == context.FLOAT_MAT2) { func = function (v) { context.uniformMatrix2fv(location, context.FALSE, v); }; } else if (type == context.FLOAT_MAT3) { func = function (v) { context.uniformMatrix3fv(location, context.FALSE, v); }; } else if (type == context.FLOAT_MAT4) { func = function (v) { context.uniformMatrix4fv(location, context.FALSE, v); }; } else { throw "Unsupported shader uniform type: " + type; } this.setValue = func; this.getValue = function() { return context.getUniform(program, location); }; this.getLocation = function() { return location; }; }; var SceneJS_webgl_ProgramSampler = function(context, program, name, type, size, location) { this.bindTexture = function(texture, unit) { if (texture.bind(unit)) { context.uniform1i(location, unit); return true; } return false; }; }; /** An attribute within a shader */ var SceneJS_webgl_ProgramAttribute = function(context, program, name, type, size, location) { // logging.debug("Program attribute found in shader: " + name); this.bindFloatArrayBuffer = function(buffer) { buffer.bind(); context.enableVertexAttribArray(location); context.vertexAttribPointer(location, buffer.itemSize, context.FLOAT, false, 0, 0); // Vertices are not homogeneous - no w-element }; }; /** * A vertex/fragment shader in a program * * @private * @param context WebGL context * @param gl.VERTEX_SHADER | gl.FRAGMENT_SHADER * @param source Source code for shader * @param logging Shader will write logging's debug channel as it compiles */ var SceneJS_webgl_Shader = function(context, type, source, logging) { this.handle = context.createShader(type); // logging.debug("Creating " + ((type == context.VERTEX_SHADER) ? "vertex" : "fragment") + " shader"); this.valid = true; context.shaderSource(this.handle, source); context.compileShader(this.handle); if (context.getShaderParameter(this.handle, context.COMPILE_STATUS) != 0) { // logging.debug("Shader compile succeeded:" + context.getShaderInfoLog(this.handle)); } else { this.valid = false; logging.error("Shader compile failed:" + context.getShaderInfoLog(this.handle)); } if (!this.valid) { throw SceneJS_errorModule.fatalError( SceneJS.errors.SHADER_COMPILATION_FAILURE, "Shader program failed to compile"); } }; /** * A program on an active WebGL context * * @private * @param hash SceneJS-managed ID for program * @param context WebGL context * @param vertexSources Source codes for vertex shaders * @param fragmentSources Source codes for fragment shaders * @param logging Program and shaders will write to logging's debug channel as they compile and link */ var SceneJS_webgl_Program = function(hash, context, vertexSources, fragmentSources, logging) { var a, i, u, u_name, location, shader; this.hash = hash; /* Create shaders from sources */ var shaders = []; for (i = 0; i < vertexSources.length; i++) { shaders.push(new SceneJS_webgl_Shader(context, context.VERTEX_SHADER, vertexSources[i], logging)); } for (i = 0; i < fragmentSources.length; i++) { shaders.push(new SceneJS_webgl_Shader(context, context.FRAGMENT_SHADER, fragmentSources[i], logging)); } /* Create program, attach shaders, link and validate program */ var handle = context.createProgram(); for (i = 0; i < shaders.length; i++) { shader = shaders[i]; if (shader.valid) { context.attachShader(handle, shader.handle); } } context.linkProgram(handle); this.valid = true; this.valid = this.valid && (context.getProgramParameter(handle, context.LINK_STATUS) != 0); var debugCfg = SceneJS_debugModule.getConfigs("shading"); if (debugCfg.validate !== false) { context.validateProgram(handle); this.valid = this.valid && (context.getProgramParameter(handle, context.VALIDATE_STATUS) != 0); } if (!this.valid) { logging.debug("Program link failed: " + context.getProgramInfoLog(handle)); throw SceneJS_errorModule.fatalError( SceneJS.errors.SHADER_LINK_FAILURE, "Shader program failed to link"); } /* Discover active uniforms and samplers */ var uniforms = {}; var samplers = {}; var numUniforms = context.getProgramParameter(handle, context.ACTIVE_UNIFORMS); /* Patch for http://code.google.com/p/chromium/issues/detail?id=40175) where * gl.getActiveUniform was producing uniform names that had a trailing NUL in Chrome 6.0.466.0 dev * Issue ticket at: https://xeolabs.lighthouseapp.com/projects/50643/tickets/124-076-live-examples-blank-canvas-in-chrome-5037599 */ for (i = 0; i < numUniforms; ++i) { u = context.getActiveUniform(handle, i); if (u) { u_name = u.name; if (u_name[u_name.length - 1] == "\u0000") { u_name = u_name.substr(0, u_name.length - 1); } location = context.getUniformLocation(handle, u_name); if ((u.type == context.SAMPLER_2D) || (u.type == context.SAMPLER_CUBE) || (u.type == 35682)) { samplers[u_name] = new SceneJS_webgl_ProgramSampler( context, handle, u_name, u.type, u.size, location, logging); } else { uniforms[u_name] = new SceneJS_webgl_ProgramUniform( context, handle, u_name, u.type, u.size, location, logging); } } } /* Discover attributes */ var attributes = {}; var numAttribs = context.getProgramParameter(handle, context.ACTIVE_ATTRIBUTES); for (i = 0; i < numAttribs; i++) { a = context.getActiveAttrib(handle, i); if (a) { location = context.getAttribLocation(handle, a.name); attributes[a.name] = new SceneJS_webgl_ProgramAttribute( context, handle, a.name, a.type, a.size, location, logging); } } this.setProfile = function(profile) { this._profile = profile; }; this.bind = function() { context.useProgram(handle); if (this._profile) { this._profile.program++; } }; this.getUniformLocation = function(name) { var u = uniforms[name]; if (u) { return u.getLocation(); } else { // SceneJS_loggingModule.warn("Uniform not found in shader : " + name); } }; this.getUniform = function(name) { var u = uniforms[name]; if (u) { return u; } else { // SceneJS_loggingModule.warn("Shader uniform load failed - uniform not found in shader : " + name); } }; this.setUniform = function(name, value) { var u = uniforms[name]; if (u) { u.setValue(value); if (this._profile) { this._profile.uniform++; } } else { // SceneJS_loggingModule.warn("Shader uniform load failed - uniform not found in shader : " + name); } }; this.getAttribute = function(name) { var attr = attributes[name]; if (attr) { return attr; } else { // logging.warn("Shader attribute bind failed - attribute not found in shader : " + name); } }; this.bindFloatArrayBuffer = function(name, buffer) { var attr = attributes[name]; if (attr) { attr.bindFloatArrayBuffer(buffer); if (this._profile) { this._profile.varying++; } } else { // logging.warn("Shader attribute bind failed - attribute not found in shader : " + name); } }; this.bindTexture = function(name, texture, unit) { var sampler = samplers[name]; if (sampler) { if (this._profile) { this._profile.texture++; } return sampler.bindTexture(texture, unit); } else { return false; } }; this.unbind = function() { // context.useProgram(0); }; this.destroy = function() { if (this.valid) { // logging.debug("Destroying shader program: '" + hash + "'"); context.deleteProgram(handle); for (var s in shaders) { context.deleteShader(shaders[s].handle); } attributes = null; uniforms = null; samplers = null; this.valid = false; } }; }; var SceneJS_webgl_Texture2D = function(context, cfg, onComplete) { this._init = function(image) { image = SceneJS_webgl_ensureImageSizePowerOfTwo(image); this.canvas = cfg.canvas; this.textureId = cfg.textureId; this.handle = context.createTexture(); this.target = context.TEXTURE_2D; this.minFilter = cfg.minFilter; this.magFilter = cfg.magFilter; this.wrapS = cfg.wrapS; this.wrapT = cfg.wrapT; this.update = cfg.update; // For dynamically-sourcing textures (ie movies etc) context.bindTexture(this.target, this.handle); try { context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, image); // New API change } catch (e) { // context.texImage2D(context.TEXTURE_2D, 0, image, cfg.flipY); // Fallback for old browser context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, image, null); } this.format = context.RGBA; this.width = image.width; this.height = image.height; this.isDepth = false; this.depthMode = 0; this.depthCompareMode = 0; this.depthCompareFunc = 0; if (cfg.minFilter) { context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, cfg.minFilter); } if (cfg.magFilter) { context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MAG_FILTER, cfg.magFilter); } if (cfg.wrapS) { context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_S, cfg.wrapS); } if (cfg.wrapT) { context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_T, cfg.wrapT); } if (cfg.minFilter == context.NEAREST_MIPMAP_NEAREST || cfg.minFilter == context.LINEAR_MIPMAP_NEAREST || cfg.minFilter == context.NEAREST_MIPMAP_LINEAR || cfg.minFilter == context.LINEAR_MIPMAP_LINEAR) { context.generateMipmap(context.TEXTURE_2D); } context.bindTexture(this.target, null); if (onComplete) { onComplete(this); } }; if (cfg.image) { this._init(cfg.image); } else { if (!cfg.url) { throw "texture 'image' or 'url' expected"; } var self = this; var img = new Image(); img.crossOrigin = "anonymous"; img.onload = function() { self._init(img); }; img.src = cfg.url; } this.bind = function(unit) { if (this.handle) { context.activeTexture(context["TEXTURE" + unit]); context.bindTexture(this.target, this.handle); if (this.update) { this.update(context); } return true; } return false; }; this.unbind = function(unit) { if (this.handle) { context.activeTexture(context["TEXTURE" + unit]); context.bindTexture(this.target, null); } }; this.generateMipmap = function() { if (this.handle) { context.generateMipmap(context.TEXTURE_2D); } }; this.destroy = function() { if (this.handle) { context.deleteTexture(this.handle); this.handle = null; } }; }; function SceneJS_webgl_ensureImageSizePowerOfTwo(image) { if (!SceneJS_webgl_isPowerOfTwo(image.width) || !SceneJS_webgl_isPowerOfTwo(image.height)) { var canvas = document.createElement("canvas"); canvas.width = SceneJS_webgl_nextHighestPowerOfTwo(image.width); canvas.height = SceneJS_webgl_nextHighestPowerOfTwo(image.height); var ctx = canvas.getContext("2d"); ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height); image = canvas; } return image; } function SceneJS_webgl_isPowerOfTwo(x) { return (x & (x - 1)) == 0; } function SceneJS_webgl_nextHighestPowerOfTwo(x) { --x; for (var i = 1; i < 32; i <<= 1) { x = x | x >> i; } return x + 1; } /** Buffer for vertices and indices * * @private * @param context WebGL context * @param type Eg. ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER * @param values WebGL array wrapper * @param numItems Count of items in array wrapper * @param itemSize Size of each item * @param usage Eg. STATIC_DRAW */ var SceneJS_webgl_ArrayBuffer; (function() { var bufMap = new SceneJS_Map(); SceneJS_webgl_ArrayBuffer = function(context, type, values, numItems, itemSize, usage) { this.handle = context.createBuffer(); this.id = bufMap.addItem(this); context.bindBuffer(type, this.handle); context.bufferData(type, values, usage); this.handle.numItems = numItems; this.handle.itemSize = itemSize; context.bindBuffer(type, null); this.type = type; this.numItems = numItems; this.itemSize = itemSize; this.bind = function() { context.bindBuffer(type, this.handle); }; this.setData = function(data, offset) { if (offset || offset === 0) { context.bufferSubData(type, offset, new Float32Array(data)); } else { context.bufferData(type, new Float32Array(data)); } }; this.unbind = function() { context.bindBuffer(type, null); }; this.destroy = function() { context.deleteBuffer(this.handle); bufMap.removeItem(this.id); }; }; })(); var SceneJS_webgl_VertexBuffer; (function() { var bufMap = new SceneJS_Map(); SceneJS_webgl_VertexBuffer = function(context, values) { this.handle = context.createBuffer(); this.id = bufMap.addItem(this); context.bindBuffer(context.ARRAY_BUFFER, this.handle); context.bufferData(context.ARRAY_BUFFER, new Float32Array(values), context.STATIC_DRAW); context.bindBuffer(context.ARRAY_BUFFER, null); this.bind = function() { context.bindBuffer(context.ARRAY_BUFFER, this.handle); }; this.setData = function(data, offset) { if (offset) { context.bufferSubData(context.ARRAY_BUFFER, offset, new Float32Array(data)); } else { context.bufferData(context.ARRAY_BUFFER, new Float32Array(data)); } // gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementVBO); // gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, elements, gl.STATIC_DRAW); }; this.unbind = function() { context.bindBuffer(context.ARRAY_BUFFER, null); }; this.destroy = function() { context.deleteBuffer(this.handle); bufMap.removeItem(this.id); }; }; })(); //Copyright (c) 2009 The Chromium Authors. All rights reserved. //Use of this source code is governed by a BSD-style license that can be //found in the LICENSE file. // Various functions for helping debug WebGL apps. var WebGLDebugUtils = function() { /** * Wrapped logging function. * @param {string} msg Message to log. */ var log = function(msg) { if (window.console && window.console.log) { window.console.log(msg); } }; /** * Map of valid enum function argument positions. */ var glValidEnumContexts = { // Generic setters and getters 'enable': { 0:true }, 'disable': { 0:true }, 'getParameter': { 0:true }, // Rendering 'drawArrays': { 0:true }, 'drawElements': { 0:true, 2:true }, // Shaders 'createShader': { 0:true }, 'getShaderParameter': { 1:true }, 'getProgramParameter': { 1:true }, // Vertex attributes 'getVertexAttrib': { 1:true }, 'vertexAttribPointer': { 2:true }, // Textures 'bindTexture': { 0:true }, 'activeTexture': { 0:true }, 'getTexParameter': { 0:true, 1:true }, 'texParameterf': { 0:true, 1:true }, 'texParameteri': { 0:true, 1:true, 2:true }, 'texImage2D': { 0:true, 2:true, 6:true, 7:true }, 'texSubImage2D': { 0:true, 6:true, 7:true }, 'copyTexImage2D': { 0:true, 2:true }, 'copyTexSubImage2D': { 0:true }, 'generateMipmap': { 0:true }, // Buffer objects 'bindBuffer': { 0:true }, 'bufferData': { 0:true, 2:true }, 'bufferSubData': { 0:true }, 'getBufferParameter': { 0:true, 1:true }, // Renderbuffers and framebuffers 'pixelStorei': { 0:true, 1:true }, 'readPixels': { 4:true, 5:true }, 'bindRenderbuffer': { 0:true }, 'bindFramebuffer': { 0:true }, 'checkFramebufferStatus': { 0:true }, 'framebufferRenderbuffer': { 0:true, 1:true, 2:true }, 'framebufferTexture2D': { 0:true, 1:true, 2:true }, 'getFramebufferAttachmentParameter': { 0:true, 1:true, 2:true }, 'getRenderbufferParameter': { 0:true, 1:true }, 'renderbufferStorage': { 0:true, 1:true }, // Frame buffer operations (clear, blend, depth test, stencil) 'clear': { 0:true }, 'depthFunc': { 0:true }, 'blendFunc': { 0:true, 1:true }, 'blendFuncSeparate': { 0:true, 1:true, 2:true, 3:true }, 'blendEquation': { 0:true }, 'blendEquationSeparate': { 0:true, 1:true }, 'stencilFunc': { 0:true }, 'stencilFuncSeparate': { 0:true, 1:true }, 'stencilMaskSeparate': { 0:true }, 'stencilOp': { 0:true, 1:true, 2:true }, 'stencilOpSeparate': { 0:true, 1:true, 2:true, 3:true }, // Culling 'cullFace': { 0:true }, 'frontFace': { 0:true } }; /** * Map of numbers to names. * @type {Object} */ var glEnums = null; /** * Initializes this module. Safe to call more than once. * @param {!WebGLRenderingContext} ctx A WebGL context. If * you have more than one context it doesn't matter which one * you pass in, it is only used to pull out constants. */ function init(ctx) { if (glEnums == null) { glEnums = { }; for (var propertyName in ctx) { if (typeof ctx[propertyName] == 'number') { glEnums[ctx[propertyName]] = propertyName; } } } } /** * Checks the utils have been initialized. */ function checkInit() { if (glEnums == null) { throw 'WebGLDebugUtils.init(ctx) not called'; } } /** * Returns true or false if value matches any WebGL enum * @param {*} value Value to check if it might be an enum. * @return {boolean} True if value matches one of the WebGL defined enums */ function mightBeEnum(value) { checkInit(); return (glEnums[value] !== undefined); } /** * Returns true if 'value' matches any WebGL enum, and the i'th parameter * of the WebGL function 'fname' is expected to be (any) enum. Does not * check that 'value' is actually a valid i'th parameter to 'fname', as * that will be checked by the WebGL implementation itself. * * @param {string} fname the GL function to use for screening the enum * @param {integer} i the parameter index to use for screening the enum * @param {any} value the value to check for being a valid i'th parameter to 'fname' * @return {boolean} true if value matches one of the defined WebGL enums, * and the i'th parameter to 'fname' is expected to be an enum * * @author Tomi Aarnio */ function mightBeValidEnum(fname, i, value) { if (!mightBeEnum(value)) return false; return (fname in glValidEnumContexts) && (i in glValidEnumContexts[fname]); } /** * Gets an string version of an WebGL enum. * * Example: * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); * * @param {number} value Value to return an enum for * @return {string} The string version of the enum. */ function glEnumToString(value) { checkInit(); var name = glEnums[value]; return (name !== undefined) ? name : ("*UNKNOWN WebGL ENUM (0x" + value.toString(16) + ")"); } /** * Given a WebGL context returns a wrapped context that calls * gl.getError after every command and calls a function if the * result is not gl.NO_ERROR. * * @param {!WebGLRenderingContext} ctx The webgl context to * wrap. * @param {!function(err, funcName, args): void} opt_onErrorFunc * The function to call when gl.getError returns an * error. If not specified the default function calls * console.log with a message. */ function makeDebugContext(ctx, opt_onErrorFunc) { init(ctx); function formatFunctionCall(functionName, args) { // apparently we can't do args.join(","); var argStr = ""; for (var ii = 0; ii < args.length; ++ii) { argStr += ((ii == 0) ? '' : ', ') + (mightBeEnum(args[ii]) ? glEnumToString(args[ii]) : args[ii]); } return functionName + "(" + argStr + ")"; } opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) { alert("WebGL error "+ glEnumToString(err) + " in "+ formatFunctionCall(functionName, args)); }; // Holds booleans for each GL error so after we get the error ourselves // we can still return it to the client app. var glErrorShadow = { }; var tracing = false; ctx.setTracing = function (newTracing) { if (!tracing && newTracing) { log('gl.setTracing(' + newTracing + ');'); } tracing = newTracing; }; var escapeDict = { '\'' : '\\\'', '\"' : '\\\"', '\\' : '\\\\', '\b' : '\\b', '\f' : '\\f', '\n' : '\\n', '\r' : '\\r', '\t' : '\\t' }; function quote(s) { var q = '\''; var l = s.length; for (var i = 0; i < l; i++) { var c = s.charAt(i); var d = s.charCodeAt(i); var e = escapeDict[c]; if ( e != undefined ) { q += e; } else if ( d < 32 || d >= 128 ) { var h = '000' + d.toString(16); q += '\\u' + h.substring(h.length - 4); } else { q += s.charAt(i); } } q += '\''; return q; } function genSymMaker(name) { var counter = 0; return function() { var sym = name + counter; counter++; return sym; }; } var constructorDict = { "createBuffer" : genSymMaker("buffer"), "createFrameBuffer": genSymMaker("frameBuffer"), "createProgram": genSymMaker("program"), "createRenderbuffer": genSymMaker("renderBuffer"), "createShader": genSymMaker("shader"), "createTexture": genSymMaker("texture"), "getUniformLocation": genSymMaker("uniformLocation"), "readPixels": genSymMaker("pixels") }; var objectNameProperty = '__webgl_trace_name__'; var arrayTypeDict = { "[object WebGLByteArray]" : "WebGLByteArray", "[object WebGLUnsignedByteArray]" : "WebGLUnsignedByteArray", "[object WebGLShortArray]" : "WebGLShortArray", "[object Uint16Array]" : "Uint16Array", "[object WebGLIntArray]" : "WebGLIntArray", "[object WebGLUnsignedIntArray]" : "WebGLUnsignedIntArray", "[object Float32Array]" : "Float32Array" }; function asWebGLArray(a) { var arrayType = arrayTypeDict[a]; if (arrayType === undefined) { return undefined; } var buf = 'new ' + arrayType + '( ['; // for (var i = 0; i < a.length; i++) { // if (i > 0 ) { // buf += ', '; // } // buf += a.get(i); // } buf += '] )'; return buf; } function traceFunctionCall(functionName, args) { var argStr = ""; for (var ii = 0; ii < args.length; ++ii) { var arg = args[ii]; if ( ii > 0 ) { argStr += ', '; } var objectName; try { if (arg !== null && arg !== undefined) { objectName = arg[objectNameProperty]; } } catch (e) { alert(functionName); throw e; } var webGLArray = asWebGLArray(arg); if (objectName != undefined ) { argStr += objectName; } else if (webGLArray != undefined) { argStr += webGLArray; }else if (typeof(arg) == "string") { argStr += quote(arg); } else if ( mightBeValidEnum(functionName, ii, arg) ) { argStr += 'gl.' + glEnumToString(arg); } else { argStr += arg; } } return "gl." + functionName + "(" + argStr + ");"; } // Makes a function that calls a WebGL function and then calls getError. function makeErrorWrapper(ctx, functionName) { return function() { var resultName; if (tracing) { var prefix = ''; // Should we remember the result for later? var objectNamer = constructorDict[functionName]; if (objectNamer != undefined) { resultName = objectNamer(); prefix = 'var ' + resultName + ' = '; } log(prefix + traceFunctionCall(functionName, arguments)); } var result = ctx[functionName].apply(ctx, arguments); if (tracing && resultName != undefined) { result[objectNameProperty] = resultName; } var err = ctx.getError(); if (err != 0) { glErrorShadow[err] = true; opt_onErrorFunc(err, functionName, arguments); } return result; }; } // Make a an object that has a copy of every property of the WebGL context // but wraps all functions. var wrapper = {}; for (var propertyName in ctx) { if (typeof ctx[propertyName] == 'function') { wrapper[propertyName] = makeErrorWrapper(ctx, propertyName); } else { wrapper[propertyName] = ctx[propertyName]; } } // Override the getError function with one that returns our saved results. wrapper.getError = function() { for (var err in glErrorShadow) { if (glErrorShadow[err]) { glErrorShadow[err] = false; return err; } } return ctx.NO_ERROR; }; return wrapper; } return { /** * Initializes this module. Safe to call more than once. * @param {!WebGLRenderingContext} ctx A WebGL context. If * you have more than one context it doesn't matter which one * you pass in, it is only used to pull out constants. */ 'init': init, /** * Returns true or false if value matches any WebGL enum * @param {*} value Value to check if it might be an enum. * @return {boolean} True if value matches one of the WebGL defined enums */ 'mightBeEnum': mightBeEnum, /** * Gets an string version of an WebGL enum. * * Example: * WebGLDebugUtil.init(ctx); * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); * * @param {number} value Value to return an enum for * @return {string} The string version of the enum. */ 'glEnumToString': glEnumToString, /** * Given a WebGL context returns a wrapped context that calls * gl.getError after every command and calls a function if the * result is not NO_ERROR. * * You can supply your own function if you want. For example, if you'd like * an exception thrown on any GL error you could do this * * function throwOnGLError(err, funcName, args) { * throw WebGLDebugUtils.glEnumToString(err) + " was caused by call to" + * funcName; * }; * * ctx = WebGLDebugUtils.makeDebugContext( * canvas.getContext("webgl"), throwOnGLError); * * @param {!WebGLRenderingContext} ctx The webgl context to wrap. * @param {!function(err, funcName, args): void} opt_onErrorFunc The function * to call when gl.getError returns an error. If not specified the default * function calls console.log with a message. */ 'makeDebugContext': makeDebugContext }; }(); /** * Backend module that defines SceneJS events and provides an interface on the backend context through which * backend modules can fire and subscribe to them. * * Events are actually somewhat more like commands; they are always synchronous, and are often used to decouple the * transfer of data between backends, request events in response, and generally trigger some immediate action. * * Event subscription can optionally be prioritised, to control the order in which the subscriber will be notified of * a given event relative to other suscribers. This is useful, for example, when a backend must be the first to handle * an INIT, or the last to handle a RESET. * * @private */ var SceneJS_eventModule = new (function() { this.ERROR = 0; this.INIT = 1; // SceneJS framework initialised this.RESET = 2; // SceneJS framework reset this.TIME_UPDATED = 3; // System time updated this.SCENE_CREATED = 4; // Scene has just been created this.SCENE_IDLE = 5; // Scene maybe about to be compiled and redrawn this.SCENE_COMPILING = 6; // Scene about to be compiled and drawn this.SCENE_COMPILED = 7; // Scene just been completely traversed this.SCENE_DESTROYED = 8; // Scene just been destroyed this.RENDERER_UPDATED = 9; // Current WebGL context has been updated to the given state this.RENDERER_EXPORTED = 10; // Export of the current WebGL context state this.CANVAS_DEACTIVATED = 11; this.GEOMETRY_UPDATED = 13; this.GEOMETRY_EXPORTED = 14; this.MODEL_TRANSFORM_UPDATED = 15; this.MODEL_TRANSFORM_EXPORTED = 16; this.PROJECTION_TRANSFORM_UPDATED = 17; this.PROJECTION_TRANSFORM_EXPORTED = 18; this.VIEW_TRANSFORM_UPDATED = 19; this.VIEW_TRANSFORM_EXPORTED = 20; this.LIGHTS_UPDATED = 21; this.LIGHTS_EXPORTED = 22; this.MATERIAL_UPDATED = 23; this.MATERIAL_EXPORTED = 24; this.TEXTURES_UPDATED = 25; this.TEXTURES_EXPORTED = 26; this.SHADER_ACTIVATE = 27; this.SHADER_ACTIVATED = 28; this.SCENE_RENDERING = 29; this.LOGGING_ELEMENT_ACTIVATED = 37; this.PICK_COLOR_EXPORTED = 38; this.NODE_CREATED = 39; this.NODE_UPDATED = 40; this.NODE_DESTROYED = 41; /* Priority queue for each type of event */ var events = new Array(37); /** * Registers a handler for the given event * * The handler can be registered with an optional priority number which specifies the order it is * called among the other handler already registered for the event. * * So, with n being the number of commands registered for the given event: * * (priority <= 0) - command will be the first called * (priority >= n) - command will be the last called * (0 < priority < n) - command will be called at the order given by the priority * @private * @param type Event type - one of the values in SceneJS_eventModule * @param command - Handler function that will accept whatever parameter object accompanies the event * @param priority - Optional priority number (see above) */ this.addListener = function(type, command, priority) { var list = events[type]; if (!list) { list = []; events[type] = list; } var handler = { command: command, priority : (priority == undefined) ? list.length : priority }; for (var i = 0; i < list.length; i++) { if (list[i].priority > handler.priority) { list.splice(i, 0, handler); return; } } list.push(handler); }; /** * @private */ this.fireEvent = function(type, params) { var list = events[type]; if (list) { if (!params) { params = {}; } for (var i = 0; i < list.length; i++) { list[i].command(params); } } }; })(); SceneJS.bind = function(name, func) { switch (name) { /** * @event error * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a * widget that is using this Store as a Record cache should refresh its view. * @param {Store} this */ case "error" : SceneJS_eventModule.addListener( SceneJS_eventModule.ERROR, function(params) { func({ code: params.code, errorName: params.errorName, exception: params.exception, fatal: params.fatal }); }); break; case "reset" : SceneJS_eventModule.addListener( SceneJS_eventModule.RESET, function() { func(); }); break; case "scene-created" : SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_CREATED, function(params) { func({ sceneId : params.sceneId }); }); break; case "node-created" : SceneJS_eventModule.addListener( SceneJS_eventModule.NODE_CREATED, function(params) { func({ nodeId : params.nodeId, json: params.json }); }); break; case "node-updated" : SceneJS_eventModule.addListener( SceneJS_eventModule.NODE_UPDATED, function(params) { func({ nodeId : params.nodeId }); }); break; case "node-destroyed" : SceneJS_eventModule.addListener( SceneJS_eventModule.NODE_DESTROYED, function(params) { func({ nodeId : params.nodeId }); }); break; case "scene-rendering" : SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function(params) { func({ sceneId : params.sceneId }); }); break; case "scene-rendered" : SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILED, function(params) { func({ sceneId : params.sceneId }); }); break; case "scene-destroyed" : SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_DESTROYED, function(params) { func({ sceneId : params.sceneId }); }); break; default: throw SceneJS_errorModule.fatalError("SceneJS.bind - this event type not supported: '" + name + "'"); } }; SceneJS.addListener = SceneJS.onEvent = SceneJS.bind; var SceneJS_compileCfg = new (function() { /*----------------------------------------------------------------------------------------------------------------- * CONFIGURATION * *----------------------------------------------------------------------------------------------------------------*/ /* Compilation levels - these are also the priorities in which the * compilations are queued (when queuing is applicable). */ this.COMPILE_NOTHING = -2; // Do nothing this.REDRAW = -1; // Compile nothing and redraw the display list this.COMPILE_SCENE = 0; // Compile entire scene graph this.COMPILE_BRANCH = 1; // Compile node, plus path to root, plus subnodes this.COMPILE_PATH = 2; // Compile node plus path to root this.RESORT = 3; // Resort display list and redraw /* Level names for logging */ this.levelNameStrings = ["COMPILE_SCENE","COMPILE_BRANCH","COMPILE_SUBTREE", "COMPILE_PATH","COMPILE_NODE"]; /** * Configures recompilation behaviour on a per-node basis * * Default is always COMPILE_SCENE * * Configs for base node type overrides configs for subtypes * * COMPILE_SCENE for structure updates * * DOCS: http://scenejs.wikispaces.com/Scene+Graph+Compilation */ this.config = { /* Configs for base node type overrides configs for subtypes */ "node": { "add" : { attr: { "node": { level: this.COMPILE_SCENE }, "nodes": { level: this.COMPILE_SCENE } }, level: this.COMPILE_SCENE }, "remove" : { attr: { "node" : { level: this.RESORT }, "nodes" : { level: this.RESORT } }, level: this.COMPILE_SCENE }, "insert" : { attr: { "node" : { level: this.COMPILE_SCENE } }, level: this.COMPILE_SCENE }, "bind": { attr: { "rendered": { level: this.COMPILE_SCENE } } }, "unbind": { attr: { "rendered": { level: this.COMPILE_SCENE } } }, "destroyed": { level: this.COMPILE_SCENE } }, "billboard": { alwaysCompile: true }, "clip": { set: { level: this.COMPILE_BRANCH }, inc: { level: this.COMPILE_BRANCH } }, "colortrans": { set: { level: this.REDRAW }, inc: { level: this.REDRAW }, mul: { level: this.REDRAW } }, "layer": { "set" : { attr: { "priority": { level: this.RESORT }, "enabled": { level: this.RESORT } } } }, "lights": { }, "scene" : { "created" : { level: this.COMPILE_SCENE }, "start" : { level: this.COMPILE_SCENE }, set: { attr: { "tagMask": { /* Enabling/disabling nodes will change the sequence of states * in draw list, so draw list resort needed */ level: this.RESORT } } } }, "scale": { set: { level: this.COMPILE_BRANCH }, inc: { level: this.COMPILE_BRANCH } }, "stationary": { alwaysCompile: true }, "tag" : { set: { attr: { "tag": { /* Enabling/disabling nodes will change the sequence of states * in draw list, so draw list resort needed */ level: this.RESORT } } } }, "rotate": { set: { level: this.COMPILE_BRANCH }, inc: { level: this.COMPILE_BRANCH } }, "translate": { set: { level: this.COMPILE_BRANCH }, inc: { level: this.COMPILE_BRANCH } }, "quaternion": { set: { level: this.COMPILE_BRANCH }, add: { level: this.COMPILE_BRANCH } }, "matrix": { set: { level: this.COMPILE_BRANCH } }, "xform": { set: { level: this.REDRAW } }, "material": { set: { level: this.REDRAW } }, "lookAt": { set: { level: this.COMPILE_PATH }, inc: { level: this.COMPILE_PATH } }, "camera": { set: { level: this.COMPILE_PATH }, inc: { level: this.COMPILE_PATH } }, "morphGeometry": { set: { level: this.REDRAW }, "loaded": { level: this.REDRAW } }, "texture": { set: { attr: { layers: { level: this.REDRAW } } }, "loaded" : { level: this.REDRAW } }, "geometry": { set: { attr: { positions: { level: this.REDRAW }, normals: { level: this.REDRAW }, uv: { level: this.REDRAW }, uv2: { level: this.REDRAW }, indices: { level: this.REDRAW } } }, "loaded": { level: this.COMPILE_SCENE } }, /* Recompile texture node once it has loaded */ "text": { "loadedImage": { level: this.COMPILE_BRANCH } }, "shader": { set: { attr: { params: { level: this.REDRAW } } } }, "shaderParams": { set: { attr: { params: { level: this.REDRAW } } } }, "flags": { "set" : { attr: { "flags": { /* Enabling/disabling nodes will change the sequence of states * in draw list, so draw list resort needed */ level: this.RESORT } } }, "add" : { attr: { /* "add" op is used to overwrite flags */ "flags": { attr: { level: this.RESORT } } } } } }; /** * Gets the level of compilation required after an update of the given type * to an attribute of the given node type. * * @param {String} nodeType Type of node - eg. "rotate", "lookAt" etc. * @param {String} op Type of update (optional) - eg. "set", "get", "inc" * @param {String} name Name of updated attribute (optional) - eg. "angle", "eye", "baseColor" * @param {{String:Object}} value Value for the attribute (optional) - when a map, the lowest compilation level among the keys will be returned * @return {Number} Number from [0..5] indicating level of compilation required */ this.getCompileLevel = function(nodeType, op, name, value) { var config = this.config[nodeType]; if (config) { /*------------------------------------------------------------- * Got config for node *-----------------------------------------------------------*/ if (op) { var opConfig = config[op]; if (opConfig) { /*------------------------------------------------------------- * Got config for [node, op] *-----------------------------------------------------------*/ if (opConfig.attr) { if (name) { var attrConfig = opConfig.attr[name]; if (attrConfig) { /*------------------------------------------------------------- * Got config for [node, op, attribute] *-----------------------------------------------------------*/ if (value) { if (typeof (value) == "object") { var subAttrConfig = attrConfig.attr; if (subAttrConfig) { /*------------------------------------------------------------- * Got config for [node, op, attribute, sub-attributes] * * Try to find the most general (lowest) compilation level * among the levels for sub-attributes. *-----------------------------------------------------------*/ var lowestLevel; var valueConfig; for (var subAttrName in value) { if (value.hasOwnProperty(subAttrName)) { valueConfig = subAttrConfig[subAttrName]; if (valueConfig) { if (valueConfig.level != undefined) { if (lowestLevel == undefined || (valueConfig.level < lowestLevel)) { lowestLevel = valueConfig.level; } } } } } if (lowestLevel) { return lowestLevel; } } } } /*------------------------------------------------------------- * Try fall back to [node, op, attribute] *-----------------------------------------------------------*/ if (attrConfig.level != undefined) { // Level found for attribute return attrConfig.level; } } } } /*------------------------------------------------------------- * Try fall back to [node, op] *-----------------------------------------------------------*/ if (opConfig.level != undefined) { return opConfig.level; } } } /*------------------------------------------------------------- * Try fall back to [node] *-----------------------------------------------------------*/ if (config.level != undefined) { return config.level; } } /*------------------------------------------------------------- * No config found for node *-----------------------------------------------------------*/ return undefined; }; })(); /*---------------------------------------------------------------------------------------------------------------- * Scene Compilation * * DOCS: http://scenejs.wikispaces.com/Scene+Graph+Compilation * *--------------------------------------------------------------------------------------------------------------*/ var SceneJS_compileModule = new (function() { this._debugCfg = null; /* Compile enabled by default */ this._enableCompiler = true; /** Tracks compilation states for each scene */ this._scenes = {}; this._scene = null; /* Stack to track nodes during scene traversal. */ this._nodeStack = []; this._stackLen = 0; /*----------------------------------------------------------------------------------------------------------------- * Priority queue of compilations, optimised for minimal garbage collection and implicit sort. Each entry has * a scene graph node and a compilation level. Entries are ordered in descending order of compilation level. *---------------------------------------------------------------------------------------------------------------*/ var CompilationQueue = function() { this._bins = []; this.size = 0; this.insert = function(level, node) { var bin = this._bins[level]; if (!bin) { bin = this._bins[level] = []; bin.numNodes = 0; } var compilation = bin[bin.numNodes]; if (!compilation) { compilation = bin[bin.numNodes] = {}; } compilation.node = node; compilation.level = level; bin.numNodes++; this.size++; }; this.remove = function() { var bin; for (var level = SceneJS_compileCfg.COMPILE_NOTHING; level <= SceneJS_compileCfg.RESORT; level++) { bin = this._bins[level]; if (bin && bin.numNodes > 0) { this.size--; return bin[--bin.numNodes]; } } return null; }; this.clear = function() { var bin; for (var level = SceneJS_compileCfg.COMPILE_NOTHING; level <= SceneJS_compileCfg.RESORT; level++) { bin = this._bins[level]; if (bin) { this._bins[level].numNodes = 0; } } this.size = 0; }; }; var self = this; SceneJS_eventModule.addListener( SceneJS_eventModule.INIT, function() { self._debugCfg = SceneJS_debugModule.getConfigs("compilation"); }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_CREATED, function(params) { self._scenes[params.sceneId] = { flagSceneCompile: false, dirtyNodes: {}, dirtyNodesWithinBranches : {}, /* Incremented when we traverse into a node that is flagged for * entire subtree compilation, decremented on return */ countTraversedSubtreesToCompile : 0, compilationQueue : new CompilationQueue(), /** * Tracks highest compilation level selected for each node that compiler receives update notification on. * Is emptied after compilation. A notification that would result in lower compilation level than already stored * for each node are ignored. * Array eleared on exit from scheduleCompilations */ nodeCompilationLevels : {}, /* Flag for each node indicating if it must always be recompiled */ nodeAlwaysCompile : {}, stats: { nodes: 0 } }; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_DESTROYED, function(params) { self._scenes[params.sceneId] = null; }); /*----------------------------------------------------------------------------------------------------------------- * NOTIFICATION * *----------------------------------------------------------------------------------------------------------------*/ /** * Notifies compilation module that a redraw of the given scene is required */ this.redraw = function(sceneId) { if (!this._enableCompiler) { return; } var compileScene = this._scenes[sceneId]; if (compileScene.compilingScene) { return; } compileScene.redraw = true; }; /** * Notifies compilation module that a node has been modified * * This function references compileConfig to find the level as * configured there. It falls back on default levels as configured, * falling back on a complete re-compile when no configs can be found. * * @param {String} nodeType Type of node - eg. "rotate", "lookAt" etc. * @param {String} op Type of update (optional) - eg. "set", "get", "inc" * @param {String} attrName Name of updated attribute (optional) - eg. "angle", "eye", "baseColor" * @param {{String:Object}} value Value for the attribute (optional) - when a map, the lowest compilation level among the keys will be returned */ this.nodeUpdated = function(node, op, attrName, value) { if (!this._enableCompiler) { return; } var nodeScene = node.scene; var compileScene = this._scenes[nodeScene.attr.id]; if (compileScene.compilingScene) { return; } var nodeType = node.attr.type; var nodeId = node.attr.id; var level; /* Compilation configs for base node type overrides configs for subtypes */ if (nodeType != "node") { level = SceneJS_compileCfg.getCompileLevel("node", op, attrName, value); } /* When no config found for base type then find for subtype */ if (level === undefined) { level = SceneJS_compileCfg.getCompileLevel(nodeType, op, attrName, value); } if (level == SceneJS_compileCfg.REDRAW) { compileScene.redraw = true; return; } if (level == SceneJS_compileCfg.COMPILE_NOTHING) { return; } if (level === undefined) { level = SceneJS_compileCfg.COMPILE_SCENE; } if (level == SceneJS_compileCfg.COMPILE_SCENE) { compileScene.compilingScene = true; compileScene.compilationQueue.clear(); return; } /* Only reschedule compilation for node if new level is more general. */ if (compileScene.nodeCompilationLevels[nodeId] <= level) { return; } compileScene.nodeCompilationLevels[nodeId] = level; compileScene.compilationQueue.insert(level, node); }; /*----------------------------------------------------------------------------------------------------------------- * SHEDULING * *----------------------------------------------------------------------------------------------------------------*/ this.COMPILE_NOTHING = 0; // No recompilations this.REDRAW = 1; // Redraw display list, resolves render-time node dependencies like texture->frameBuf this.COMPILE_PARTIAL = 2; // Recompile some nodes back into display list this.COMPILE_EVERYTHING = 3; // Recompile all nodes, rebuilding the entire display list /** * Flush compilation queue to set all the flags that will direct the next compilation traversal */ this.beginSceneCompile = function(sceneId) { var compileScene = this._scenes[sceneId]; this._scene = compileScene; this._stackLen = 0; compileScene.countTraversedSubtreesToCompile = 0; if (!this._enableCompiler) { return { level: this.COMPILE_EVERYTHING }; } compileScene.nodeCompilationLevels = {}; if (compileScene.compilingScene) { compileScene.flagSceneCompile = true; compileScene.compilingScene = false; return { level: this.COMPILE_EVERYTHING }; } var compilationQueue = compileScene.compilationQueue; if (compilationQueue.size == 0) { if (compileScene.redraw) { compileScene.redraw = false; return { level: this.REDRAW }; } return { level: this.COMPILE_NOTHING }; } var compilation; var node; var nodeId; var level; compileScene.stats = { scene: 0, branch: 0, path: 0, subtree: 0, nodes: 0 }; var stats = compileScene.stats; // if (this._debugCfg.logTrace) { // SceneJS_loggingModule.info("-------------------------------------------------------------------"); // SceneJS_loggingModule.info("COMPILING ..."); // SceneJS_loggingModule.info(""); // } var result = { level: this.REDRAW, // Just flag display redraw until we know we need any node recompilations resort: false // Don't know if we'll need to sort display list yet }; while (compilationQueue.size > 0) { compilation = compilationQueue.remove(); level = compilation.level; node = compilation.node; nodeId = node.attr.id; // if (this._debugCfg.logTrace) { // var logLevels = this._debugCfg.logTrace.levels || {}; // var minLevel = logLevels.min || SceneJS_compileCfg.COMPILE_SCENE; // var maxLevel = (logLevels.max == undefined || logLevels.max == null) ? SceneJS_compileCfg.COMPILE_NODE : logLevels.max; // if (minLevel <= level && level <= maxLevel) { // SceneJS_loggingModule.info("Compiling - level: " // + SceneJS_compileCfg.levelNameStrings[compilation.level] + ", node: " // + compilation.node.attr.type + ", node ID: " + compilation.node.attr.id + ", op: " // + compilation.op + ", attr: " // + compilation.attrName); // } // } if (level == SceneJS_compileCfg.COMPILE_BRANCH) { // Compile node, nodes on path to root and nodes in subtree this._flagCompilePath(compileScene, node); // Compile nodes on path compileScene.dirtyNodesWithinBranches[nodeId] = true; // Ensures that traversal descends into subnodes // stats.branch++; result.level = this.COMPILE_PARTIAL; } else if (level == SceneJS_compileCfg.COMPILE_PATH) { // Compile node plus nodes on path to root this._flagCompilePath(compileScene, node); // Traversal will not descend below the node // stats.path++; result.level = this.COMPILE_PARTIAL; } else if (level == SceneJS_compileCfg.RESORT) { result.resort = true; } } // if (this._debugCfg.logTrace) { // SceneJS_loggingModule.info("-------------------------------------------------------------------"); // } return result; }; /** Flags node and all nodes on path to root for recompilation */ this._flagCompilePath = function(compileScene, targetNode) { var dirtyNodes = compileScene.dirtyNodes; var id; var node = targetNode; while (node) { id = node.attr.id; // TODO: why do these two lines break compilation within instanced subtrees? if (dirtyNodes[id]) { // Node on path already marked, along with all instances of it return; } if (compileScene.dirtyNodesWithinBranches[id]) { // Node on path already marked, along with all instances of it return; } dirtyNodes[id] = true; node = node.parent; } }; /** * Returns true if the given node requires compilation during this traversal. * * This called when about to visit the node, to determine if that node should * be visited. * * Will always return true if scene compilation was previously forced with * a call to setForceSceneCompile. * */ this.preVisitNode = function(node) { /* Compile indiscriminately if scheduler disabled */ if (!this._enableCompiler) { return true; } var compileScene = this._scene; var config = SceneJS_compileCfg.config[node.attr.type]; var nodeId = node.attr.id; compileScene.stats.nodes++; /* When doing complete indescriminate scene compile, take this opportunity * to flag paths to nodes that must always be compiled */ if (compileScene.flagSceneCompile) { compileScene.nodeAlwaysCompile[nodeId] = false; if (config && config.alwaysCompile) { this._alwaysCompilePath(compileScene, node); } } /* Track compilation path into scene graph */ this._nodeStack[this._stackLen++] = node; /* Compile entire subtree when within a subtree flagged for complete compile, * or when within a node that must always be compiled */ if (compileScene.dirtyNodesWithinBranches[nodeId] === true || (config && config.alwaysCompile)) { compileScene.countTraversedSubtreesToCompile++; } /* Compile every node when doing indiscriminate scene compile */ if (compileScene.flagSceneCompile) { return true; } /* Compile every node flagged for indiscriminate compilation */ if (compileScene.nodeAlwaysCompile[nodeId]) { return true; } /* Compile every node */ if (compileScene.countTraversedSubtreesToCompile > 0) { return true; } if (compileScene.dirtyNodes[nodeId] === true) { return true; } compileScene.stats.nodes--; return false; }; this._alwaysCompilePath = function(compileScene, targetNode) { var id; var node = targetNode; if (compileScene.nodeAlwaysCompile[node.attr.id]) { return; } while (node) { id = node.attr.id; compileScene.nodeAlwaysCompile[id] = true; node = node.parent; } }; this.postVisitNode = function(node) { if (this._stackLen > 0) { var compileScene = this._scene; var nodeId = node.attr.id; var peekNode = this._nodeStack[this._stackLen - 1]; if (peekNode.attr.id == nodeId) { this._stackLen--; var config = SceneJS_compileCfg.config[node.attr.type]; if (compileScene.dirtyNodesWithinBranches[nodeId] === true || (config && config.alwaysCompile)) { compileScene.countTraversedSubtreesToCompile--; } } compileScene.dirtyNodes[nodeId] = false; compileScene.dirtyNodesWithinBranches[nodeId] = false; } }; this.finishSceneCompile = function() { this._scene.flagSceneCompile = false; }; })(); /** * Backend module to provide logging that is aware of the current location of scene traversal. * * There are three "channels" of log message: error, warning, info and debug. * * Provides an interface on the backend context through which other backends may log messages. * * Provides an interface to scene nodes to allow them to log messages, as well as set and get the function * that actually processes messages on each channel. Those getters and setters are used by the SceneJS.logging node, * which may be distributed throughout a scene graph to cause messages to be processed in particular ways for different * parts of the graph. * * Messages are queued. Initially, each channel has no function set for it and will queue messages until a function is * set, at which point the queue flushes. If the function is unset, subsequent messages will queue, then flush when a * function is set again. This allows messages to be logged before any SceneJS.logging node is visited. * * This backend is always the last to handle a RESET * * @private * */ var SceneJS_loggingModule = new (function() { var activeSceneId; var funcs = null; var queues = {}; var indent = 0; var indentStr = ""; /** * @private */ function log(channel, message) { if (SceneJS._isArray(message)) { _logHTML(channel, arrayToHTML(message)); for (var i = 0; i < message.length; i++) { _logToConsole(message[i]); } } else { _logHTML(channel, message); _logToConsole(message); } } function _logHTML(channel, message) { message = activeSceneId ? indentStr + activeSceneId + ": " + message : indentStr + message; var func = funcs ? funcs[channel] : null; if (func) { func(message); } else { var queue = queues[channel]; if (!queue) { queue = queues[channel] = []; } queue.push(message); } } function _logToConsole(message) { if (typeof console == "object") { message = activeSceneId ? indentStr + activeSceneId + ": " + message : indentStr + message; console.log(message); } } function arrayToHTML(array) { var array2 = []; for (var i = 0; i < array.length; i++) { var padding = (i < 10) ? "   " : ((i < 100) ? "  " : (i < 1000 ? " " : "")); array2.push(i + padding + ": " + array[i]); } return array2.join("
"); } function logScript(src) { for (var i = 0; i < src.length; i++) { _logToConsole(src[i]); } } /** * @private */ function flush(channel) { var queue = queues[channel]; if (queue) { var func = funcs ? funcs[channel] : null; if (func) { for (var i = 0; i < queue.length; i++) { func(queue[i]); } queues[channel] = []; } } } SceneJS_eventModule.addListener( SceneJS_eventModule.LOGGING_ELEMENT_ACTIVATED, function(params) { var element = params.loggingElement; if (element) { funcs = { warn : function log(msg) { element.innerHTML += "

" + msg + "

"; }, error : function log(msg) { element.innerHTML += "

" + msg + "

"; }, debug : function log(msg) { element.innerHTML += "

" + msg + "

"; }, info : function log(msg) { element.innerHTML += "

" + msg + "

"; } }; } else { funcs = null; } }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, // Set default logging for scene root function(params) { activeSceneId = params.sceneId; }); SceneJS_eventModule.addListener( SceneJS_eventModule.RESET, function() { queues = {}; funcs = null; }, 100000); // Really low priority - must be reset last // @private this.setIndent = function(_indent) { indent = _indent; var indentArray = []; for (var i = 0; i < indent; i++) { indentArray.push("----"); } indentStr = indentArray.join(""); }; // @private this.error = function(msg) { log("error", msg); }; // @private this.warn = function(msg) { log("warn", msg); }; // @private this.info = function(msg) { log("info", msg); }; // @private this.debug = function(msg) { log("debug", msg); }; // @private this.getFuncs = function() { return funcs; }; // @private this.setFuncs = function(l) { if (l) { funcs = l; for (var channel in queues) { flush(channel); } } }; })(); /** * Backend module that provides single point through which exceptions may be raised * * @private */ var SceneJS_errorModule = new (function() { this.fatalError = function(code, message) { if (typeof code == "string") { message = code; code = SceneJS.errors.ERROR; } SceneJS_eventModule.fireEvent(SceneJS_eventModule.ERROR, { errorName: SceneJS.errors._getErrorName(code) || "ERROR", code: code, exception: message, fatal: true }); return message; }; this.error = function(code, message) { SceneJS_eventModule.fireEvent(SceneJS_eventModule.ERROR, { errorName: SceneJS.errors._getErrorName(code) || "ERROR", code: code, exception: message, fatal: false }); }; })(); (function() { this.DEFAULT_LAYER_NAME = "___default"; var layerStack = []; var idStack = []; var stackLen = 0; var dirty = true; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setLayer(idStack[stackLen - 1], layerStack[stackLen - 1]); } else { SceneJS_DrawList.setLayer(); } dirty = false; } }); var Layer = SceneJS.createNodeType("layer"); Layer.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node defines the resource this.core.priority = params.priority || 0; this.core.enabled = params.enabled !== false; } }; Layer.prototype.setPriority = function(priority) { this.core.priority = priority; }; Layer.prototype.getPriority = function() { return this.core.priority; }; Layer.prototype.setEnabled = function(enabled) { this.core.enabled = enabled; }; Layer.prototype.getEnabled = function() { return this.core.enabled; }; Layer.prototype._compile = function() { layerStack[stackLen] = this.core; idStack[stackLen] = this.attr.id; stackLen++; dirty = true; this._compileNodes(); stackLen--; dirty = true; }; })(); (function() { var Library = SceneJS.createNodeType("library"); Library.prototype._compile = function() { // Bypass child nodes }; })(); new (function() { var initialised = false; // True as soon as first scene registered var scenes = {}; var nScenes = 0; var Scene = SceneJS.createNodeType("scene"); SceneJS_eventModule.addListener( SceneJS_eventModule.RESET, function() { scenes = {}; nScenes = 0; }); function findLoggingElement(loggingElementId) { var element; if (!loggingElementId) { element = document.getElementById(Scene.DEFAULT_LOGGING_ELEMENT_ID); if (!element) { SceneJS_loggingModule.info("SceneJS.Scene config 'loggingElementId' omitted and failed to find default logging element with ID '" + Scene.DEFAULT_LOGGING_ELEMENT_ID + "' - that's OK, logging to browser console instead"); } } else { element = document.getElementById(loggingElementId); if (!element) { element = document.getElementById(Scene.DEFAULT_LOGGING_ELEMENT_ID); if (!element) { SceneJS_loggingModule.info("SceneJS.Scene config 'loggingElementId' unresolved and failed to find default logging element with ID '" + Scene.DEFAULT_LOGGING_ELEMENT_ID + "' - that's OK, logging to browser console instead"); } else { SceneJS_loggingModule.info("SceneJS.Scene config 'loggingElementId' unresolved - found default logging element with ID '" + Scene.DEFAULT_LOGGING_ELEMENT_ID + "' - logging to browser console also"); } } else { SceneJS_loggingModule.info("SceneJS.Scene logging to element with ID '" + loggingElementId + "' - logging to browser console also"); } } return element; } /** Locates canvas in DOM, finds WebGL context on it, sets some default state on the context, then returns * canvas, canvas ID and context wrapped up in an object. * * If canvasId is null, will fall back on Scene.DEFAULT_CANVAS_ID */ function findCanvas(canvasId, contextAttr) { var canvas; if (!canvasId) { SceneJS_loggingModule.info("Scene attribute 'canvasId' omitted - looking for default canvas with ID '" + Scene.DEFAULT_CANVAS_ID + "'"); canvasId = Scene.DEFAULT_CANVAS_ID; canvas = document.getElementById(canvasId); if (!canvas) { throw SceneJS_errorModule.fatalError( SceneJS.errors.CANVAS_NOT_FOUND, "Scene failed to find default canvas with ID '" + Scene.DEFAULT_CANVAS_ID + "'"); } } else { canvas = document.getElementById(canvasId); if (!canvas) { SceneJS_loggingModule.warn("Scene config 'canvasId' unresolved - looking for default canvas with " + "ID '" + Scene.DEFAULT_CANVAS_ID + "'"); canvasId = Scene.DEFAULT_CANVAS_ID; canvas = document.getElementById(canvasId); if (!canvas) { throw SceneJS_errorModule.fatalError(SceneJS.errors.CANVAS_NOT_FOUND, "Scene attribute 'canvasId' does not match any elements in the page and no " + "default canvas found with ID '" + Scene.DEFAULT_CANVAS_ID + "'"); } } } // If the canvas uses css styles to specify the sizes make sure the basic // width and height attributes match or the WebGL context will use 300 x 150 canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; var context; var contextNames = SceneJS.SUPPORTED_WEBGL_CONTEXT_NAMES; for (var i = 0; (!context) && i < contextNames.length; i++) { try { if (SceneJS_debugModule.getConfigs("webgl.logTrace") == true) { context = canvas.getContext(contextNames[i] /*, { antialias: true} */, contextAttr); if (context) { context = WebGLDebugUtils.makeDebugContext( context, function(err, functionName, args) { SceneJS_loggingModule.error( "WebGL error calling " + functionName + " on WebGL canvas context - see console log for details"); }); context.setTracing(true); } } else { context = canvas.getContext(contextNames[i], contextAttr); } } catch (e) { } } if (!context) { throw SceneJS_errorModule.fatalError( SceneJS.errors.WEBGL_NOT_SUPPORTED, 'Canvas document element with ID \'' + canvasId + '\' failed to provide a supported WebGL context'); } try { context.clearColor(0.0, 0.0, 0.0, 1.0); context.clearDepth(1.0); context.enable(context.DEPTH_TEST); context.disable(context.CULL_FACE); context.depthRange(0, 1); context.disable(context.SCISSOR_TEST); } catch (e) { throw SceneJS_errorModule.fatalError(// Just in case we get a context but can't get any functionson it SceneJS.errors.WEBGL_NOT_SUPPORTED, 'Canvas document element with ID \'' + canvasId + '\' provided a supported WebGL context, but functions appear to be missing'); } return { canvas: canvas, context: context, canvasId : canvasId }; } function getAllScenes() { var list = []; for (var id in scenes) { var scene = scenes[id]; if (scene) { list.push(scene.scene); } } return list; } function deactivateScene() { } Scene.prototype._init = function(params) { if (!params.canvasId) { throw SceneJS_errorModule.fatalError(SceneJS.errors.ILLEGAL_NODE_CONFIG, "Scene canvasId expected"); } this._loggingElementId = params.loggingElementId; this._destroyed = false; this.scene = this; this.nodeMap = new SceneJS_Map(); // Can auto-generate IDs when not supplied if (params.tagMask) { this.setTagMask(params.tagMask); } this.canvas = findCanvas(params.canvasId, params.contextAttr); // canvasId can be null var sceneId = this.attr.id; scenes[sceneId] = { sceneId: sceneId, scene: this, canvas: this.canvas, loggingElement: findLoggingElement(this._loggingElementId) // loggingElementId can be null }; nScenes++; if (!initialised) { SceneJS_loggingModule.info("SceneJS V" + SceneJS.VERSION + " initialised"); SceneJS_eventModule.fireEvent(SceneJS_eventModule.INIT); initialised = true; } SceneJS_eventModule.fireEvent(SceneJS_eventModule.SCENE_CREATED, { sceneId : sceneId, canvas: this.canvas }); SceneJS_loggingModule.info("Scene defined: " + sceneId); SceneJS_compileModule.nodeUpdated(this, "created"); }; /** ID of canvas SceneJS looks for when {@link Scene} node does not supply one */ Scene.DEFAULT_CANVAS_ID = "_scenejs-default-canvas"; /** ID ("_scenejs-default-logging") of default element to which {@link Scene} node will log to, if found. */ Scene.DEFAULT_LOGGING_ELEMENT_ID = "_scenejs-default-logging"; /** Returns the Z-buffer depth in bits of the webgl context that this scene is to bound to. */ Scene.prototype.getZBufferDepth = function() { if (this._destroyed) { throw SceneJS_errorModule.fatalError(SceneJS.errors.NODE_ILLEGAL_STATE, "Scene has been destroyed"); } var context = this.canvas.context; return context.getParameter(context.DEPTH_BITS); }; if (! self.Int32Array) { self.Int32Array = Array; self.Float32Array = Array; } // Ripped off from THREE.js - https://github.com/mrdoob/three.js/blob/master/src/Three.js // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating (function() { var lastTime = 0; var vendors = ['ms', 'moz', 'webkit', 'o']; for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'RequestCancelAnimationFrame']; } if (!window.requestAnimationFrame) window.requestAnimationFrame = function(callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function(id) { clearTimeout(id); }; }()); /** Sets regular expression to select "tag" nodes to included in render passes */ Scene.prototype.setTagMask = function(tagMask) { if (!this.tagSelector) { this.tagSelector = {}; } this.tagSelector.mask = tagMask; this.tagSelector.regex = tagMask ? new RegExp(tagMask) : null; }; /** Gets regular expression that selects "tag" nodes to include in render passes */ Scene.prototype.getTagMask = function() { return this.tagSelector ? this.tagSelector.mask : null; }; /** * Perform any scheduled scene compilations and return true if the scene needs a redraw */ Scene.prototype._compileScene = function() { var compileFlags = SceneJS_compileModule.beginSceneCompile(this.attr.id); if (compileFlags.level != SceneJS_compileModule.COMPILE_NOTHING) { // level could be REDRAW SceneJS_DrawList.bindScene({ sceneId: this.attr.id }, { compileMode: compileFlags.level == SceneJS_compileModule.COMPILE_EVERYTHING ? SceneJS_DrawList.COMPILE_SCENE : SceneJS_DrawList.COMPILE_NODES, resort: compileFlags.resort }); this._compile(); SceneJS_compileModule.finishSceneCompile(); return true; // Redraw needed } return false; // No redraw needed }; /** * Render a single frame if new frame pending, or force a new frame * Returns true if frame rendered */ Scene.prototype.renderFrame = function(params) { if (this._compileScene()) { // Try doing pending compile/redraw SceneJS_DrawList.renderFrame({ tagSelector: this.tagSelector }); return true; } if (params && params.force) { SceneJS_DrawList.bindScene({ // Else force redraw sceneId: this.attr.id }, { compileMode: SceneJS_DrawList.COMPILE_NODES, resort: false }); SceneJS_DrawList.renderFrame({ tagSelector: this.tagSelector }); return true; } return false; }; /** * Starts the render loop for this scene */ Scene.prototype.start = function(cfg) { if (this._destroyed) { throw SceneJS_errorModule.fatalError(SceneJS.errors.NODE_ILLEGAL_STATE, "Scene has been destroyed"); } var getFPS = this._setupFps(); if (!this._running) { cfg = cfg || {}; this._running = true; this._paused = false; var self = this; var fnName = "__scenejs_sceneLoop" + this.attr.id; var sleeping = false; SceneJS_compileModule.nodeUpdated(this, "start"); var sceneId = self.attr.id; window[fnName] = function() { if (self._running && !self._paused) { // idleFunc may have paused scene if (cfg.idleFunc) { cfg.idleFunc(); } if (!self._running) { // idleFunc may have destroyed scene return; } SceneJS_eventModule.fireEvent(SceneJS_eventModule.SCENE_IDLE, { sceneId: sceneId }); if (self._compileScene()) { // Attempt pending compile and redraw sleeping = false; SceneJS_DrawList.renderFrame({ profileFunc: cfg.profileFunc, tagSelector: self.tagSelector }); if (cfg.frameFunc) { cfg.frameFunc({ fps: getFPS() }); } window.requestAnimationFrame(window[fnName]); } else { if (!sleeping && cfg.sleepFunc) { cfg.sleepFunc(); } sleeping = true; window.requestAnimationFrame(window[fnName]); } } else { window.requestAnimationFrame(window[fnName]); } }; this._startCfg = cfg; window.requestAnimationFrame(window[fnName]); // Push one frame immediately } }; Scene.prototype._setupFps = function() { var lastTime = new Date(); var hits = 0; var fps = 0; return function() { hits++; var nowTime = new Date(); if ((nowTime.getTime() - lastTime.getTime()) > 1000) { var dt = nowTime.getTime() - lastTime.getTime(); fps = Math.round(hits * 1000 / dt); hits = 0; lastTime = nowTime; } return fps; }; }; /** Pauses/unpauses current render loop that was started with {@link #start}. After this, {@link #isRunning} will return false. */ Scene.prototype.pause = function(doPause) { if (this._destroyed) { throw SceneJS_errorModule.fatalError(SceneJS.errors.NODE_ILLEGAL_STATE, "Scene has been destroyed"); } this._paused = doPause; }; /** Returns true if the scene is currently rendering repeatedly in a loop after being started with {@link #start}. */ Scene.prototype.isRunning = function() { return this._running; }; /** * Picks whatever geometry will be rendered at the given canvas coordinates. */ Scene.prototype.pick = function(canvasX, canvasY, options) { if (this._destroyed) { throw SceneJS_errorModule.fatalError(SceneJS.errors.NODE_ILLEGAL_STATE, "Scene has been destroyed"); } options = options || {}; this._compileScene(); // Do any pending scene recompilations var hit = SceneJS_DrawList.pick({ sceneId: this.attr.id, canvasX : canvasX, canvasY : canvasY, rayPick: options.rayPick, tagSelector: this.tagSelector }); if (hit) { hit.canvasX = canvasX; hit.canvasY = canvasY; } return hit; }; Scene.prototype._compile = function() { SceneJS._actionNodeDestroys(); // Do first to avoid clobbering allocations by node compiles var sceneId = this.attr.id; var scene = scenes[sceneId]; SceneJS_eventModule.fireEvent(SceneJS_eventModule.LOGGING_ELEMENT_ACTIVATED, { loggingElement: scene.loggingElement }); SceneJS_eventModule.fireEvent(SceneJS_eventModule.SCENE_COMPILING, { sceneId: sceneId, nodeId: sceneId, canvas : scene.canvas }); if (SceneJS_compileModule.preVisitNode(this)) { this._compileNodes(); } SceneJS_compileModule.postVisitNode(this); deactivateScene(); }; /** * Scene node's destroy handler, called by {@link SceneJS_node#destroy} * @private */ Scene.prototype.destroy = function() { if (!this._destroyed) { this.stop(); this._destroyed = true; this._scheduleNodeDestroy(); // Schedule all scene nodes for destruction SceneJS._actionNodeDestroys(); // Action the schedule immediately var sceneId = this.attr.id; scenes[sceneId] = null; nScenes--; SceneJS_eventModule.fireEvent(SceneJS_eventModule.SCENE_DESTROYED, {sceneId : sceneId }); SceneJS_loggingModule.info("Scene destroyed: " + sceneId); if (nScenes == 0) { SceneJS_loggingModule.info("SceneJS reset"); SceneJS_eventModule.fireEvent(SceneJS_eventModule.RESET); } } }; /** Returns true if scene active, ie. not destroyed. A destroyed scene becomes active again * when you render it. */ Scene.prototype.isActive = function() { return !this._destroyed; }; /** Stops current render loop that was started with {@link #start}. After this, {@link #isRunning} will return false. */ Scene.prototype.stop = function() { if (this._destroyed) { throw SceneJS_errorModule.fatalError(SceneJS.errors.NODE_ILLEGAL_STATE, "Scene has been destroyed"); } if (this._running) { this._running = false; window["__scenejs_sceneLoop" + this.attr.id] = null; } }; /** Determines if node exists in this scene */ Scene.prototype.containsNode = function(nodeId) { if (this._destroyed) { throw SceneJS_errorModule.fatalError(SceneJS.errors.NODE_ILLEGAL_STATE, "Scene has been destroyed"); } var node = this.nodeMap.items[nodeId]; return (node) ? true : false; }; /** Finds nodes in this scene that have nodes IDs matching the given regular expression */ Scene.prototype.findNodes = function(nodeIdRegex) { var regex = new RegExp(nodeIdRegex); var nodes = []; var nodeMap = this.nodeMap.items; for (var nodeId in nodeMap) { if (nodeMap.hasOwnProperty(nodeId)) { if (regex.test(nodeId)) { nodes.push(nodeMap[nodeId]); } } } return nodes; }; Scene.prototype.findNode = function(nodeId) { // if (!this._created) { // throw SceneJS_errorModule.fatalError( // SceneJS.errors.NODE_ILLEGAL_STATE, // "Scene has been destroyed"); // } var node = this.nodeMap.items[nodeId]; // if (!node) { // throw SceneJS_errorModule.fatalError( // SceneJS.errors.NODE_ILLEGAL_STATE, // "Node '" + nodeId + "' not found in scene '" + this.attr.id + "'"); // } return node ? SceneJS._selectNode(node) : null; }; /** * Returns the current status of this scene. * * When the scene has been destroyed, the returned status will be a map like this: * * { * destroyed: true * } * * Otherwise, the status will be: * * { * numLoading: Number // Number of asset loads (eg. texture, geometry stream etc.) currently in progress * } * */ Scene.prototype.getStatus = function() { return (this._destroyed) ? { destroyed: true } : SceneJS._shallowClone(SceneJS_sceneStatusModule.sceneStatus[this.attr.id]); }; SceneJS.reset = function() { var scenes = getAllScenes(); var temp = []; for (var i = 0; i < scenes.length; i++) { temp.push(scenes[i]); } while (temp.length > 0) { /* Destroy each scene individually so it they can mark itself as destroyed. * A RESET command will be fired after the last one is destroyed. */ temp.pop().destroy(); } }; })(); var SceneJS_PickBuffer = function(cfg) { var canvas = cfg.canvas; var gl = canvas.context; var pickBuf; this.bound = false; this._touch = function() { var width = canvas.canvas.width; var height = canvas.canvas.height; if (pickBuf) { // Currently have a pick buffer if (pickBuf.width == width && pickBuf.height == height) { // Canvas size unchanged, buffer still good return; } else { // Buffer needs reallocation for new canvas size gl.deleteTexture(pickBuf.texture); gl.deleteFramebuffer(pickBuf.frameBuf); gl.deleteRenderbuffer(pickBuf.renderBuf); } } pickBuf = { frameBuf : gl.createFramebuffer(), renderBuf : gl.createRenderbuffer(), texture : gl.createTexture(), width: width, height: height }; gl.bindFramebuffer(gl.FRAMEBUFFER, pickBuf.frameBuf); gl.bindTexture(gl.TEXTURE_2D, pickBuf.texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 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); try { // Do it the way the spec requires gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); } catch (exception) { // Workaround for what appears to be a Minefield bug. var textureStorage = new WebGLUnsignedByteArray(width * height * 3); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, textureStorage); } gl.bindRenderbuffer(gl.RENDERBUFFER, pickBuf.renderBuf); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, pickBuf.texture, 0); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, pickBuf.renderBuf); gl.bindTexture(gl.TEXTURE_2D, null); gl.bindRenderbuffer(gl.RENDERBUFFER, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null); /* Verify framebuffer is OK */ gl.bindFramebuffer(gl.FRAMEBUFFER, pickBuf.frameBuf); if (!gl.isFramebuffer(pickBuf.frameBuf)) { throw SceneJS_errorModule.fatalError("Invalid framebuffer"); } var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); switch (status) { case gl.FRAMEBUFFER_COMPLETE: break; case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: throw SceneJS_errorModule.fatalError("Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: throw SceneJS_errorModule.fatalError("Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: throw SceneJS_errorModule.fatalError("Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_DIMENSIONS"); case gl.FRAMEBUFFER_UNSUPPORTED: throw SceneJS_errorModule.fatalError("Incomplete framebuffer: FRAMEBUFFER_UNSUPPORTED"); default: throw SceneJS_errorModule.fatalError("Incomplete framebuffer: " + status); } this.bound = false; }; this.bind = function() { this._touch(); if (this.bound) { return; } gl.bindFramebuffer(gl.FRAMEBUFFER, pickBuf.frameBuf); this.bound = true; }; this.clear = function() { if (!this.bound) { throw "Pick buffer not bound"; } gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.disable(gl.BLEND); }; /** Reads pick buffer pixel at given coordinates, returns index of associated object else (-1) */ this.read = function(pickX, pickY) { var x = pickX; var y = canvas.canvas.height - pickY; var pix = new Uint8Array(4); gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pix); return pix; }; this.unbind = function() { gl.bindFramebuffer(gl.FRAMEBUFFER, null); this.bound = false; }; }; /** * This module encapsulates the rendering backend behind an event API. * * It's job is to collect the textures, lights, materials etc. as they are exported during scene * traversal by the other modules, then when traversal is finished, sort them into a sequence of * that would involve minimal WebGL state changes, then apply the sequence to WebGL. */ var SceneJS_DrawList = new (function() { /* */ this.SORT_ORDER_TEXTURE = 0; this.SORT_ORDER_VBO = 1; this._sortOrder = [this.SORT_ORDER_TEXTURE, this.SORT_ORDER_VBO]; /*---------------------------------------------------------------------- * ID for each state type *--------------------------------------------------------------------*/ this._GEO = 0; this._CLIPS = 1; this._COLORTRANS = 2; this._FLAGS = 3; this._LAYER = 4; this._FRAMEBUF = 5; this._LIGHTS = 6; this._MATERIAL = 7; this._MORPH = 8; this._PICKCOLOR = 9; this._TEXTURE = 10; this._RENDERER = 11; this._MODEL_TRANSFORM = 12; this._PROJ_TRANSFORM = 13; this._VIEW_TRANSFORM = 14; this._PICK_LISTENERS = 15; this._NAME = 16; this._TAG = 17; this._RENDER_LISTENERS = 18; this._SHADER = 19; this._SHADER_PARAMS = 20; /*---------------------------------------------------------------------- * Default state values *--------------------------------------------------------------------*/ var createState = (function() { var stateId = -1; return function(data) { data._id = stateId; data._stateId = stateId--; data._nodeCount = 0; data._default = true; return data; }; })(); /* Default transform matrices */ this._DEFAULT_MAT = new Float32Array(SceneJS_math_identityMat4()); this._DEFAULT_NORMAL_MAT = new Float32Array( SceneJS_math_transposeMat4( SceneJS_math_inverseMat4( SceneJS_math_identityMat4(), SceneJS_math_mat4()))); this._DEFAULT_CLIP_STATE = createState({ clips: [], hash: "" }); this._DEFAULT_COLORTRANS_STATE = createState({ core : null, hash :"f" }); this._DEFAULT_FLAGS_STATE = createState({ flags : { colortrans : true, // Effect of colortrans enabled picking : true, // Picking enabled clipping : true, // User-defined clipping enabled enabled : true, // Node not culled from traversal transparent: false, // Node transparent - works in conjunction with matarial alpha properties backfaces: true, // Show backfaces frontface: "ccw" // Default vertex winding for front face } }); this._DEFAULT_LAYER_STATE = createState({ core: { priority : 0, enabled: true } }); this._DEFAULT_FRAMEBUF_STATE = createState({ frameBuf: null }); this._DEFAULT_LIGHTS_STATE = createState({ lights: [], hash: "" }); this._DEFAULT_MATERIAL_STATE = createState({ material: { baseColor : [ 0.0, 0.0, 0.0 ], specularColor : [ 0.0, 0.0, 0.0 ], specular : 1.0, shine : 10.0, reflect : 0.8, alpha : 1.0, emit : 0.0 } }); this._DEFAULT_MORPH_STATE = createState({ morph: null, hash: "" }); this._DEFAULT_PICK_COLOR_STATE = createState({ pickColor: null, hash: "" }); this._DEFAULT_PICK_LISTENERS_STATE = createState({ listeners : [] }); this._DEFAULT_NAME_STATE = createState({ core : null }); this._DEFAULT_TAG_STATE = createState({ core : null }); this._DEFAULT_TEXTURE_STATE = createState({ hash: "" }); this._DEFAULT_RENDERER_STATE = createState({ props: null }); this._DEFAULT_MODEL_TRANSFORM_STATE = createState({ mat : this._DEFAULT_MAT, normalMat : this._DEFAULT_NORMAL_MAT }); this._DEFAULT_PROJ_TRANSFORM_STATE = createState({ mat: new Float32Array( SceneJS_math_orthoMat4c( SceneJS_math_ORTHO_OBJ.left, SceneJS_math_ORTHO_OBJ.right, SceneJS_math_ORTHO_OBJ.bottom, SceneJS_math_ORTHO_OBJ.top, SceneJS_math_ORTHO_OBJ.near, SceneJS_math_ORTHO_OBJ.far)), optics: SceneJS_math_ORTHO_OBJ }); this._DEFAULT_VIEW_TRANSFORM_STATE = createState({ mat : this._DEFAULT_MAT, normalMat : this._DEFAULT_NORMAL_MAT, lookAt:SceneJS_math_LOOKAT_ARRAYS }); this._DEFAULT_RENDER_LISTENERS_STATE = createState({ listeners : [] }); this._DEFAULT_SHADER_STATE = createState({ shader : {}, hash: "" }); this._DEFAULT_SHADER_PARAMS_STATE = createState({ params: null }); /** Shader programs currently allocated on all canvases */ this._programs = {}; var debugCfg; // Debugging configuration for this module /* A state set for each scene */ this._sceneStates = {}; /*---------------------------------------------------------------------- * *--------------------------------------------------------------------*/ this._states = null; this._nodeMap = null; this._stateMap = null; var nextStateId; var nextProgramId = 0; this.COMPILE_SCENE = 0; // When we set a state update, rebuilding entire scene from scratch this.COMPILE_BRANCH = 1; // this.COMPILE_NODES = 2; // this.compileMode = null; // DOCS: http://scenejs.wikispaces.com/Scene+Graph+Compilation var picking; // True when picking /* Currently exported states */ var flagsState; var layerState; var rendererState; var lightState; var colortransState; var materialState; var texState; var geoState; var modelXFormState; var viewXFormState; var projXFormState; var pickColorState; var frameBufState; var clipState; var morphState; var nameState; var tagState; var renderListenersState; var shaderState; var shaderParamsState; /** Current scene state hash */ this._stateHash = null; var transparentBin = []; // Temp buffer for transparent nodes, used when rendering /*---------------------------------------------------------------------- * *--------------------------------------------------------------------*/ var self = this; SceneJS_eventModule.addListener( SceneJS_eventModule.INIT, function() { debugCfg = SceneJS_debugModule.getConfigs("shading"); }); SceneJS_eventModule.addListener( SceneJS_eventModule.RESET, function() { var programs = self._programs; var program; for (var programId in programs) { // Just free allocated programs if (programs.hasOwnProperty(programId)) { program = programs[programId]; program.pick.destroy(); program.render.destroy(); } } self._programs = {}; nextProgramId = 0; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_CREATED, function(params) { var sceneId = params.sceneId; if (!self._sceneStates[sceneId]) { self._sceneStates[sceneId] = { sceneId: sceneId, canvas: params.canvas, nodeRenderer: new SceneJS_DrawListRenderer({ canvas: params.canvas.canvas, context: params.canvas.context }), bin: [], // Draw list - state sorting happens here lenBin: 0, visibleCacheBin: [], // Cached draw list containing enabled nodes lenVisibleCacheBin: 0, nodeMap: {}, geoNodesMap : {}, // Display list nodes findable by their geometry scene nodes stateMap: {}, pickCallListDirty: true, pickBufDirty : true }; } }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_DESTROYED, function(params) { }); /** * Sets the number of frames after each complete display list rebuild * at which GL state is re-sorted. To enable sort, set this to a positive * number like 5, to prevent continuous re-sort when display list is * often rebuilt. * * +n - lazy-sort after n frames * 0 - continuously re-sort. * -1 - never sort. * Undefined - select default. * * @param stateSortDelay */ this.setStateSortDelay = function(stateSortDelay) { stateSortDelay = typeof stateSortDelay == "number" ? stateSortDelay == undefined : this._DEFAULT_STATE_SORT_DELAY; }; /** * Prepares renderer for new scene graph compilation pass */ this.bindScene = function(params, options) { options = options || {}; /* Activate states for scene */ this._states = this._sceneStates[params.sceneId]; this._nodeMap = this._states.nodeMap; this._stateMap = this._states.stateMap; this._stateHash = null; if (options.compileMode == SceneJS_DrawList.COMPILE_SCENE // Rebuild from whole scene || options.compileMode == SceneJS_DrawList.COMPILE_BRANCH) { // Rebuild from branch(es) /* Default state soup */ clipState = this._DEFAULT_CLIP_STATE; colortransState = this._DEFAULT_COLORTRANS_STATE; flagsState = this._DEFAULT_FLAGS_STATE; layerState = this._DEFAULT_LAYER_STATE; frameBufState = this._DEFAULT_FRAMEBUF_STATE; lightState = this._DEFAULT_LIGHTS_STATE; materialState = this._DEFAULT_MATERIAL_STATE; morphState = this._DEFAULT_MORPH_STATE; pickColorState = this._DEFAULT_PICK_COLOR_STATE; rendererState = this._DEFAULT_RENDERER_STATE; texState = this._DEFAULT_TEXTURE_STATE; modelXFormState = this._DEFAULT_MODEL_TRANSFORM_STATE; projXFormState = this._DEFAULT_PROJ_TRANSFORM_STATE; viewXFormState = this._DEFAULT_VIEW_TRANSFORM_STATE; nameState = this._DEFAULT_NAME_STATE; tagState = this._DEFAULT_TAG_STATE; renderListenersState = this._DEFAULT_RENDER_LISTENERS_STATE; shaderState = this._DEFAULT_SHADER_STATE; shaderParamsState = this._DEFAULT_SHADER_PARAMS_STATE; this._forceStateSort(true); // Sort display list with orders re-built from layer orders if (options.compileMode == SceneJS_DrawList.COMPILE_SCENE) { this._states.lenBin = 0; nextStateId = 0; // All new states // nextProgramId = 0; // All new programs this._nodeMap = this._states.nodeMap = {}; this._stateMap = this._states.stateMap = {}; } } if (options.compileMode == SceneJS_DrawList.COMPILE_NODES) { // Rebuild display list for subtree /* Going to overwrite selected state graph nodes * as we partially recompile portions of the scene graph. * * We'll preserve the state graph and shaders. */ this._nodeMap = this._states.nodeMap; this._stateMap = this._states.stateMap; if (options.resort) { this._forceStateSort(true); // Sort display list with orders re-built from layer orders } } this.compileMode = options.compileMode; picking = false; }; this.marshallStates = function() { SceneJS_eventModule.fireEvent(SceneJS_eventModule.SCENE_RENDERING, { fullCompile : this.compileMode === SceneJS_DrawList.COMPILE_SCENE }); }; /*----------------------------------------------------------------------------------------------------------------- * State setting/updating *----------------------------------------------------------------------------------------------------------------*/ /** Find or create a new state of the given state type and node ID * @param stateType ID of the type of state, eg. this._TEXTURE etc * @param id ID of scene graph node that is setting this state */ this._getState = function(stateType, id) { var state; var typeMap = this._stateMap[stateType]; if (!typeMap) { typeMap = this._stateMap[stateType] = {}; } if (this.compileMode != SceneJS_DrawList.COMPILE_SCENE) { state = typeMap[id]; if (!state) { state = { _id: id, _stateId : nextStateId++, _stateType: stateType, _nodeCount: 0 }; typeMap[id] = state; } } else { // Recompiling whole scene state = { _id: id, _stateId : nextStateId++, _stateType: stateType, _nodeCount: 0 }; if (id) { typeMap[id] = state; } } return state; }; /** * Release a state after detach from display node. Destroys node if usage count is then zero. */ this._releaseState = function(state) { if (state._stateType == undefined) { return; } if (state._nodeCount <= 0) { return; } if (--state._nodeCount == 0) { var typeMap = this._stateMap[state._stateType]; if (typeMap) { delete typeMap[state.id]; } } }; this.setClips = function(id, clips) { if (!id) { clipState = this._DEFAULT_CLIP_STATE; this._stateHash = null; return; } clipState = this._getState(this._CLIPS, id); clips = clips || []; if (true && this.compileMode == SceneJS_DrawList.COMPILE_SCENE) { // Only make hash for full recompile if (clips.length > 0) { var hash = []; for (var i = 0; i < clips.length; i++) { var clip = clips[i]; hash.push(clip.mode); } clipState.hash = hash.join(""); } else { clipState.hash = ""; } this._stateHash = null; } clipState.clips = clips; }; this.setColortrans = function(id, core) { if (!id) { colortransState = this._DEFAULT_COLORTRANS_STATE; this._stateHash = null; return; } colortransState = this._getState(this._COLORTRANS, id, true); colortransState.core = core; if (true && this.compileMode == SceneJS_DrawList.COMPILE_SCENE) { // Only make hash for full recompile colortransState.hash = core ? "t" : "f"; this._stateHash = null; } }; this.setFlags = function(id, flags) { if (arguments.length == 0) { flagsState = this._DEFAULT_FLAGS_STATE; return; } flagsState = this._getState(this._FLAGS, id); flags = flags || this._DEFAULT_FLAGS_STATE.flags; flagsState.flags = flags || this._DEFAULT_FLAGS_STATE.flags; // this._states.lenEnabledBin = 0; }; this.setLayer = function(id, core) { if (arguments.length == 0) { layerState = this._DEFAULT_LAYER_STATE; return; } layerState = this._getState(this._LAYER, id); layerState.core = core; // this._states.lenEnabledBin = 0; }; this.setFrameBuf = function(id, frameBuf) { if (arguments.length == 0) { frameBufState = this._DEFAULT_FRAMEBUF_STATE; return; } frameBufState = this._getState(this._FRAMEBUF, id); frameBufState.frameBuf = frameBuf; }; this.setLights = function(id, lights) { if (arguments.length == 0) { lightState = this._DEFAULT_LIGHTS_STATE; this._stateHash = null; return; } lightState = this._getState(this._LIGHTS, id); lights = lights || []; if (true && this.compileMode == SceneJS_DrawList.COMPILE_SCENE) { // Only make hash for full recompile var hash = []; for (var i = 0; i < lights.length; i++) { var light = lights[i]; hash.push(light.mode); if (light.specular) { hash.push("s"); } if (light.diffuse) { hash.push("d"); } } lightState.hash = hash.join(""); this._stateHash = null; } lightState.lights = lights; }; this.setMaterial = function(id, material) { if (arguments.length == 0) { materialState = this._DEFAULT_MATERIAL_STATE; return; } materialState = this._getState(this._MATERIAL, id, true); materialState.material = material || this._DEFAULT_MATERIAL_STATE.material; }; this.setMorph = function(id, morph) { if (arguments.length == 0) { morphState = this._DEFAULT_MORPH_STATE; this._stateHash = null; return; } morphState = this._getState(this._MORPH, id); if (true && this.compileMode == SceneJS_DrawList.COMPILE_SCENE) { // Only make hash for full recompile if (morph) { var target0 = morph.targets[0]; // All targets have same arrays morphState.hash = ([ target0.vertexBuf ? "t" : "f", target0.normalBuf ? "t" : "f", target0.uvBuf ? "t" : "f", target0.uvBuf2 ? "t" : "f"]).join("") } else { morphState.hash = ""; } this._stateHash = null; } morphState.morph = morph; }; this.setTexture = function(id, core) { if (arguments.length == 0) { texState = this._DEFAULT_TEXTURE_STATE; this._stateHash = null; return; } texState = this._getState(this._TEXTURE, id); if (true && this.compileMode == SceneJS_DrawList.COMPILE_SCENE) { // Only make hash for full recompile var hashStr; if (core && core.layers.length > 0) { var layers = core.layers; var hash = []; for (var i = 0, len = layers.length; i < len; i++) { var layer = layers[i]; hash.push("/"); hash.push(layer.applyFrom); hash.push("/"); hash.push(layer.applyTo); hash.push("/"); hash.push(layer.blendMode); if (layer.matrix) { hash.push("/anim"); } } hashStr = hash.join(""); } else { hashStr = "__scenejs_no_tex"; } texState.hash = hashStr; this._stateHash = null; } texState.core = core; }; this.setRenderer = function(id, props) { if (arguments.length == 0) { rendererState = this._DEFAULT_RENDERER_STATE; return; } rendererState = this._getState(this._RENDERER, id); rendererState.props = props; this._stateHash = null; }; this.setModelTransform = function(id, mat, normalMat) { if (arguments.length == 0) { modelXFormState = this._DEFAULT_MODEL_TRANSFORM_STATE; return; } modelXFormState = this._getState(this._MODEL_TRANSFORM, id); modelXFormState.mat = mat || this._DEFAULT_MAT; modelXFormState.normalMat = normalMat || this._DEFAULT_NORMAL_MAT; }; this.setProjectionTransform = function(id, transform) { if (arguments.length == 0) { projXFormState = this._DEFAULT_PROJ_TRANSFORM_STATE; return; } projXFormState = this._getState(this._PROJ_TRANSFORM, id); projXFormState.mat = transform.matrixAsArray || this._DEFAULT_PROJ_TRANSFORM_STATE.mat; projXFormState.optics = transform.optics || this._DEFAULT_PROJ_TRANSFORM_STATE.optics; }; this.setViewTransform = function(id, mat, normalMat, lookAt) { if (arguments.length == 0) { viewXFormState = this._DEFAULT_VIEW_TRANSFORM_STATE; return; } viewXFormState = this._getState(this._VIEW_TRANSFORM, id); viewXFormState.mat = mat || this._DEFAULT_MAT; viewXFormState.normalMat = normalMat || this._DEFAULT_NORMAL_MAT; viewXFormState.lookAt = lookAt || SceneJS_math_LOOKAT_ARRAYS; }; this.setName = function(id, name) { if (arguments.length == 0) { nameState = this._DEFAULT_NAME_STATE; return; } nameState = this._getState(this._NAME, id); nameState.name = name; // Can be null }; this.setTag = function(id, core) { if (arguments.length == 0) { tagState = this._DEFAULT_TAG_STATE; return; } tagState = this._getState(this._TAG, id); tagState.core = core; // Can be null }; this.setRenderListeners = function(id, listeners) { if (arguments.length == 0) { renderListenersState = this._DEFAULT_RENDER_LISTENERS_STATE; return; } renderListenersState = this._getState(this._RENDER_LISTENERS, id); renderListenersState.listeners = listeners || []; }; this.setShader = function(id, shader) { if (arguments.length == 0) { shaderState = this._DEFAULT_SHADER_STATE; this._stateHash = null; return; } shaderState = this._getState(this._SHADER, id); shaderState.shader = shader || {}; shaderState.hash = shader.hash; this._stateHash = null; }; this.setShaderParams = function(id, paramsStack) { if (arguments.length == 0) { shaderParamsState = this._DEFAULT_SHADER_PARAMS_STATE; return; } shaderParamsState = this._getState(this._SHADER_PARAMS, id); shaderParamsState.paramsStack = paramsStack; }; /** * Add a geometry and link it to currently active resources (states) * * Geometry is automatically exported when a scene graph geometry node * is rendered. * * Geometry is the central element of a state graph node, so now we * will create a state graph node with pointers to the elements currently * in the state soup. * * But first we need to ensure that the state soup is up to date. Other kinds of state * are not automatically exported by scene traversal, where their modules * require us to notify them that we'll be rendering something (the geometry) and * need an up-to-date set state soup. If they havent yet exported their state * (textures, material and so on) for this scene traversal, they'll do so. * * Next, we'll build a program hash code by concatenating the hashes on * the state soup elements, then use that to create (or re-use) a shader program * that is equipped to render the current state soup elements. * * Then we create the state graph node, which has the program and pointers to * the current state soup elements. * */ this.setGeometry = function(id, geo) { /* Pull in dirty states from other modules */ this.marshallStates(); var node; if (this.compileMode != SceneJS_DrawList.COMPILE_SCENE) { /* Dynamic state reattachment for scene branch compilation. * * Attach new states that have been generated for this display node during scene * graph branch recompilation. The only kinds of states can be regenerated in this * way are those that can be created/destroyed on any node type, such as flags and * listeners. Other state types do not apply because they are bound to particular * node types, and any change to those would have resulted in a complete scene graph * recompilation. * * A reference count is incremented on newly attached states, and decremented on * previously attached states as they are detached. Those states are then destroyed * if their reference count has dropped to zero. * */ node = this._nodeMap[id]; if (node.renderListenersState._stateId != renderListenersState._stateId) { this._releaseState(node.renderListenersState); node.renderListenersState = renderListenersState; renderListenersState._nodeCount++; } if (node.flagsState._stateId != flagsState._stateId) { this._releaseState(node.flagsState); node.flagsState = flagsState; flagsState._nodeCount++; } return; } /* */ geoState = this._getState(this._GEO, id); geoState.geo = geo; geoState.hash = ([ // Safe to build geometry hash here - geometry is immutable geo.normalBuf ? "t" : "f", geo.uvBuf ? "t" : "f", geo.uvBuf2 ? "t" : "f", geo.colorBuf ? "t" : "f", geo.primitive ]).join(""); /* Identify what GLSL is required for the current state soup elements */ if (!this._stateHash) { this._stateHash = this._getSceneHash(); } /* Create or re-use a program */ var program = this._getProgram(this._stateHash); // Touches for LRU cache geoState._nodeCount++; flagsState._nodeCount++; layerState._nodeCount++; rendererState._nodeCount++; lightState._nodeCount++; colortransState._nodeCount++; materialState._nodeCount++; modelXFormState._nodeCount++; viewXFormState._nodeCount++; projXFormState._nodeCount++; texState._nodeCount++; pickColorState._nodeCount++; frameBufState._nodeCount++; clipState._nodeCount++; morphState._nodeCount++; nameState._nodeCount++; tagState._nodeCount++; renderListenersState._nodeCount++; shaderState._nodeCount++; shaderParamsState._nodeCount++; node = { id: id, sortId: 0, // Lazy-create later stateHash : this._stateHash, program : program, geoState: geoState, layerState: layerState, flagsState: flagsState, rendererState: rendererState, lightState: lightState, colortransState : colortransState, materialState: materialState, modelXFormState: modelXFormState, viewXFormState: viewXFormState, projXFormState: projXFormState, texState: texState, pickColorState : pickColorState, frameBufState : frameBufState, clipState : clipState, morphState : morphState, nameState: nameState, tagState: tagState, renderListenersState: renderListenersState, shaderState: shaderState, shaderParamsState: shaderParamsState }; this._states.nodeMap[id] = node; this._states.bin[this._states.lenBin++] = node; /* Make the display list node findable by its geometry scene node */ var geoNodesMap = this._states.geoNodesMap[id]; if (!geoNodesMap) { geoNodesMap = this._states.geoNodesMap[id] = []; } geoNodesMap.push(node); }; this._attachState = function(node, stateName, soupState) { var state = node[stateName]; if (state && state._stateId != soupState._stateId) { this._releaseState(state); } node[stateName] = soupState; soupState._nodeCount++; return soupState; }; /** * Removes a geometry, which deletes the associated display list node. Linked states then get their reference * counts decremented. States whose reference count becomes zero are deleted. * * This may be done outside of a render pass. */ this.removeGeometry = function(sceneId, id) { var sceneState = this._sceneStates[sceneId]; var geoNodesMap = sceneState.geoNodesMap; var nodes = geoNodesMap[id]; if (!nodes) { return; } for (var i = 0, len = nodes.length; i < len; i++) { var node = nodes[i]; node.destroyed = true; // Differ node destruction to bin render time, when we'll know it's bin index this._releaseProgram(node.program); this._releaseState(node.geoState); this._releaseState(node.flagsState); this._releaseState(node.layerState); this._releaseState(node.rendererState); this._releaseState(node.lightState); this._releaseState(node.colortransState); this._releaseState(node.materialState); this._releaseState(node.modelXFormState); this._releaseState(node.viewXFormState); this._releaseState(node.projXFormState); this._releaseState(node.texState); this._releaseState(node.pickColorState); this._releaseState(node.frameBufState); this._releaseState(node.clipState); this._releaseState(node.morphState); this._releaseState(node.nameState); this._releaseState(node.tagState); this._releaseState(node.renderListenersState); this._releaseState(node.shaderState); this._releaseState(node.shaderParamsState); } geoNodesMap[id] = null; sceneState.lenVisibleCacheBin = 0; }; /*----------------------------------------------------------------------------------------------------------------- * Bin pre-sorting *----------------------------------------------------------------------------------------------------------------*/ this._createStateStateSortIDs = function(bin) { if (this._states.stateSortIDsDirty) { var node; for (var i = 0, len = bin.length; i < len; i++) { node = bin[i]; node.sortId = (node.layerState.core.priority * 100000) + node.program.id; } this._states.stateSortIDsDirty = false; } }; this._createStateStateSortIDsNew = function(bin) { if (this._states.stateSortIDsDirty) { var node; var lastProgramId; var lastTextureId; var lastGeometryId; var numPrograms = 0; var numTextures = 0; var numGeometries = 0; var i = bin.length; while (i--) { node = bin[i]; if (node.program.id != lastProgramId) { node.program._sortId = numPrograms; lastProgramId = node.program.id; numPrograms++; } if (node.texState._stateId != lastTextureId) { node.texState._sortId = numTextures; lastTextureId = node.texState._stateId; numTextures++; } if (node.geoState._stateId != lastGeometryId) { node.geoState._sortId = numGeometries; lastGeometryId = node.geoState._stateId; numGeometries++; } } i = bin.length; while (i--) { node = bin[i]; node.sortId = (node.layerState.core.priority * numPrograms * numTextures) // Layer + (node.program.id * numTextures) // Shader + node.texState._sortId * numGeometries // Texture + node.geoState._sortId; // Geometry } this._states.stateSortIDsDirty = false; } }; this._forceStateSort = function(rebuildSortIDs) { if (rebuildSortIDs) { this._states.stateSortIDsDirty = rebuildSortIDs; } this._states.stateSortDirty = true; }; /** * Presorts bins by shader program, layer - shader switches are most * pathological because they force all other state switches. */ this._stateSortBins = function() { var states = this._states; if (states.stateSortIDsDirty) { this._createStateStateSortIDs(states.bin); } states.bin.length = states.lenBin; states.bin.sort(this._stateSortNodes); states.lenVisibleCacheBin = 0; }; this._stateSortNodes = function(a, b) { return a.sortId - b.sortId; }; /*----------------------------------------------------------------------------------------------------------------- * Rendering *----------------------------------------------------------------------------------------------------------------*/ this.renderFrame = function(params) { var states = this._states; if (!states) { throw SceneJS_errorModule.fatalError("No scene bound"); } states.pickBufDirty = true; // Pick buff will now need rendering on next pick params = params || {}; var sorted = false; if (states.stateSortDirty) { this._stateSortBins(); states.stateSortDirty = false; sorted = true; } var drawCallListDirty = sorted // Call list dirty when draw list resorted or scene recompiled || this.compileMode == SceneJS_DrawList.COMPILE_SCENE || this.compileMode == SceneJS_DrawList.COMPILE_BRANCH; // Call funcs dirty when scene recompiled var drawCallFuncsDirty = this.compileMode == SceneJS_DrawList.COMPILE_SCENE; if (drawCallListDirty) { states.pickCallListDirty = drawCallListDirty; // If draw call list dirty, then so is pick call list } if (drawCallListDirty) { states.pickCallFuncsDirty = drawCallFuncsDirty; } var doProfile = params.profileFunc ? true : false; var nodeRenderer = states.nodeRenderer; nodeRenderer.init({ doProfile: doProfile, // Initialise renderer pciking: false, // Drawing mode callListDirty: drawCallListDirty, // Rebuild draw call list? callFuncsDirty: drawCallFuncsDirty // Regenerate draw call funcs? }); this._renderDrawList(// Render draw list states, false, // Not picking params.tagSelector); nodeRenderer.cleanup(); if (doProfile) { params.profileFunc(nodeRenderer.profile); } this._states = null; // Unbind scene }; /*=================================================================================================================== * * PICK * *==================================================================================================================*/ this.pick = function(params) { var states = this._sceneStates[params.sceneId]; if (!states) { throw "No drawList found for scene '" + params.sceneId + "'"; } var canvas = states.canvas.canvas; var hit = null; var canvasX = params.canvasX; var canvasY = params.canvasY; /*------------------------------------------------------------- * Pick object using normal GPU colour-indexed pick *-----------------------------------------------------------*/ var pickBuf = states.pickBuf; // Lazy-create pick buffer if (!pickBuf) { pickBuf = states.pickBuf = new SceneJS_PickBuffer({ canvas: states.canvas }); states.pickBufDirty = true; // Freshly-created pick buffer is dirty } var nodeRenderer = states.nodeRenderer; pickBuf.bind(); // Bind pick buffer // if (states.pickCallListDirty || states.pickBufDirty) { // Render pick buffer pickBuf.clear(); nodeRenderer.init({ picking: true, callListDirty: states.pickCallListDirty, // (Re)create call list if dirty callFuncsDirty: states.pickCallFuncsDirty // (Re)generate functions if dirty }); this._renderDrawList(states, true, params.tagSelector); nodeRenderer.cleanup(); // Flush render states.pickCallListDirty = false; // Pick call list up to date states.pickCallFuncsDirty = false; // Pick function cache up to date states.pickBufDirty = false; // Pick buffer up to date // } var pix = pickBuf.read(canvasX, canvasY); // Read pick buffer var pickedNodeIndex = pix[0] + pix[1] * 256 + pix[2] * 65536; var pickIndex = (pickedNodeIndex >= 1) ? pickedNodeIndex - 1 : -1; pickBuf.unbind(); // Unbind pick buffer var pickNameState = nodeRenderer.pickNameStates[pickIndex]; // Map pixel to name if (pickNameState) { hit = { name: pickNameState.name }; /*------------------------------------------------------------- * Ray pick to find 3D pick position *-----------------------------------------------------------*/ if (params.rayPick) { var rayPickBuf = states.rayPickBuf; // Lazy-create Z-pick buffer if (!rayPickBuf) { rayPickBuf = states.rayPickBuf = new SceneJS_PickBuffer({ canvas: states.canvas }); } rayPickBuf.bind(); rayPickBuf.clear(); nodeRenderer.init({ // Initialise renderer pick: false, rayPick: true, callListDirty: false, // Pick calls clean from pick pass callFuncsDirty: false // Call funcs clean from pick pass }); var visibleCacheBin = states.visibleCacheBin; // Render visible object cache var lenVisibleCacheBin = states.lenVisibleCacheBin; var node; var flags; for (var i = 0; i < lenVisibleCacheBin; i++) { // Need to render all nodes for pick node = visibleCacheBin[i]; // due to frameBuf effects etc. flags = node.flagsState.flags; if (flags.picking === false) { continue; } nodeRenderer.renderNode(node); } nodeRenderer.cleanup(); pix = rayPickBuf.read(canvasX, canvasY); rayPickBuf.unbind(); /* Read normalised device Z coordinate, which will be * in range of [0..1] with z=0 at front */ var screenZ = this._unpackDepth(pix); var w = canvas.width; var h = canvas.height; /* Calculate clip space coordinates, which will be in range * of x=[-1..1] and y=[-1..1], with y=(+1) at top */ var x = (canvasX - w / 2) / (w / 2) ; // Calculate clip space coordinates var y = -(canvasY - h / 2) / (h / 2) ; var viewMat = node.viewXFormState.mat; var projMat = node.projXFormState.mat; var pvMat = SceneJS_math_mulMat4(projMat, viewMat, []); var pvMatInverse = SceneJS_math_inverseMat4(pvMat, []); var world1 = SceneJS_math_transformVector4(pvMatInverse, [x,y,-1,1]); world1 = SceneJS_math_mulVec4Scalar(world1, 1 / world1[3]); var world2 = SceneJS_math_transformVector4(pvMatInverse, [x,y,1,1]); world2 = SceneJS_math_mulVec4Scalar(world2, 1 / world2[3]); var dir = SceneJS_math_subVec3(world2, world1, []); var eye = node.viewXFormState.lookAt.eye; var vWorld = SceneJS_math_addVec4(world1, SceneJS_math_mulVec4Scalar(dir, screenZ, []), []); hit.canvasPos = [canvasX, canvasY]; hit.worldPos = vWorld; } } return hit; }; this._unpackDepth = function(depthZ) { var vec = [depthZ[0] / 256.0, depthZ[1] / 256.0, depthZ[2] / 256.0, depthZ[3] / 256.0]; var bitShift = [1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0]; return SceneJS_math_dotVector4(vec, bitShift); }; this._renderDrawList = function(states, picking, tagSelector) { var context = states.canvas.context; var visibleCacheBin = states.visibleCacheBin; var lenVisibleCacheBin = states.lenVisibleCacheBin; var nodeRenderer = states.nodeRenderer; var nTransparent = 0; var _transparentBin = transparentBin; if (lenVisibleCacheBin > 0) { /*------------------------------------------------------------- * Render visible cache bin * - build transparent bin *-----------------------------------------------------------*/ for (var i = 0; i < lenVisibleCacheBin; i++) { node = visibleCacheBin[i]; flags = node.flagsState.flags; if (picking && flags.picking === false) { // When picking, skip unpickable node continue; } if (!picking && flags.transparent === true) { // Buffer transparent node when not picking _transparentBin[nTransparent++] = node; } else { nodeRenderer.renderNode(node); // Render node if opaque or in picking mode } } } else { /*------------------------------------------------------------- * Render main node bin * - build visible cache bin * - build transparent bin *-----------------------------------------------------------*/ var bin = states.bin; var node; var countDestroyed = 0; var flags; var tagCore; var _picking = picking; // For optimised lookup /* Tag matching */ var tagMask; var tagRegex; if (tagSelector) { tagMask = tagSelector.mask; tagRegex = tagSelector.regex; } /* Render opaque nodes while buffering transparent nodes. * Layer order is preserved independently within opaque and transparent bins. * At each node that is marked destroyed, we'll just slice it out of the bin array instead. */ for (var i = 0, len = states.lenBin; i < len; i++) { node = bin[i]; if (node.destroyed) { if (i < len) { countDestroyed++; bin[i] = bin[i + countDestroyed]; } continue; } if (countDestroyed > 0) { bin[i] = bin[i + countDestroyed]; } flags = node.flagsState.flags; if (flags.enabled === false) { // Skip disabled node continue; } /* Skip unmatched tags. Visible node caching helps prevent this being done on every frame. */ if (tagMask) { tagCore = node.tagState.core; if (tagCore) { if (tagCore.mask != tagMask) { // Scene tag mask was updated since last render tagCore.mask = tagMask; tagCore.matches = tagRegex.test(tagCore.tag); } if (!tagCore.matches) { continue; } } } if (!node.layerState.core.enabled) { // Skip disabled layers continue; } if (_picking && flags.picking === false) { // When picking, skip unpickable node continue; } if (!_picking && flags.transparent === true) { // Buffer transparent node when not picking _transparentBin[nTransparent++] = node; } else { nodeRenderer.renderNode(node); // Render node if opaque or in picking mode } visibleCacheBin[states.lenVisibleCacheBin++] = node; // Cache visible node } if (countDestroyed > 0) { bin.length -= countDestroyed; // TODO: tidy this up states.lenBin = bin.length; len = bin.length; } } /*------------------------------------------------------------- * Render transparent bin * - blending enabled *-----------------------------------------------------------*/ if (nTransparent > 0) { context.enable(context.BLEND); context.blendFunc(context.SRC_ALPHA, context.ONE_MINUS_SRC_ALPHA); // Default - may be overridden by flags for (i = 0,len = nTransparent; i < len; i++) { nodeRenderer.renderNode(transparentBin[i]); } context.disable(context.BLEND); } }; /*=================================================================================================================== * * SHADERS * *==================================================================================================================*/ this._getSceneHash = function() { return ([ // Same hash for both render and pick shaders this._states.canvas.canvasId, clipState.hash, colortransState.hash, lightState.hash, morphState.hash, texState.hash, shaderState.hash, rendererState.hash, geoState.hash ]).join(";"); }; this._getProgram = function(stateHash) { var program = this._programs[stateHash]; if (!program) { if (debugCfg.logScripts === true) { SceneJS_loggingModule.info("Creating render and pick shaders: '" + stateHash + "'"); } program = this._programs[stateHash] = { id: nextProgramId++, render: this._createShader(this._composeRenderingVertexShader(), this._composeRenderingFragmentShader()), pick: this._createShader(this._composePickingVertexShader(), this._composePickingFragmentShader()), // rayPick: this._createShader(this._rayPickVertexShader(), this._rayPickFragmentShader()), stateHash: stateHash, refCount: 0 }; } program.refCount++; return program; }; this._releaseProgram = function(program) { if (--program.refCount <= 0) { // program.render.destroy(); // program.pick.destroy(); // this._programs[program.stateHash] = null; } }; this._createShader = function(vertexShaderSrc, fragmentShaderSrc) { try { return new SceneJS_webgl_Program( this._stateHash, this._states.canvas.context, [vertexShaderSrc.join("\n")], [fragmentShaderSrc.join("\n")], SceneJS_loggingModule); } catch (e) { console.error("-----------------------------------------------------------------------------------------:"); console.error("Failed to create SceneJS Shader"); console.error("SceneJS Version: " + SceneJS.VERSION); console.error("Error message: " + e); console.error(""); console.error("Vertex shader:"); console.error(""); this._logShaderLoggingSource(vertexShaderSrc); console.error(""); console.error("Fragment shader:"); this._logShaderLoggingSource(fragmentShaderSrc); console.error("-----------------------------------------------------------------------------------------:"); throw SceneJS_errorModule.fatalError(SceneJS.errors.ERROR, "Failed to create SceneJS Shader: " + e); } }; this._logShaderLoggingSource = function(src) { for (var i = 0, len = src.length; i < len; i++) { console.error(src[i]); } }; this._writeHooks = function(src, varName, hookName, hooks, returns) { for (var i = 0, len = hooks.length; i < len; i++) { if (returns) { src.push(varName + "=" + hookName + "(" + varName + ");"); } else { src.push(hookName + "(" + varName + ");"); } } }; this._composePickingVertexShader = function() { var customShaders = shaderState.shader.shaders || {}; var customVertexShader = customShaders.vertex || {}; var vertexHooks = customVertexShader.hooks || {}; var customFragmentShader = customShaders.fragment || {}; var fragmentHooks = customFragmentShader.hooks || {}; var clipping = clipState.clips.length > 0; var morphing = morphState.morph && true; var normals = this._hasNormals(); var src = [ "precision mediump float;", "attribute vec3 SCENEJS_aVertex;", "attribute vec3 SCENEJS_aNormal;", "uniform mat4 SCENEJS_uMMatrix;", "uniform mat4 SCENEJS_uMNMatrix;", "uniform mat4 SCENEJS_uVMatrix;", "uniform mat4 SCENEJS_uVNMatrix;", "uniform mat4 SCENEJS_uPMatrix;" ]; if (normals && (fragmentHooks.worldNormal || fragmentHooks.viewNormal)) { src.push("varying vec3 SCENEJS_vWorldNormal;"); // Output world-space vertex normal src.push("varying vec3 SCENEJS_vViewNormal;"); // Output world-space vertex normal } src.push("varying vec4 SCENEJS_vModelVertex;"); // if (clipping || fragmentHooks.worldPosClip) { src.push("varying vec4 SCENEJS_vWorldVertex;"); // } src.push("varying vec4 SCENEJS_vViewVertex;\n"); src.push("varying vec4 SCENEJS_vProjVertex;\n"); src.push("uniform vec3 SCENEJS_uEye;"); // World-space eye position src.push("varying vec3 SCENEJS_vEyeVec;"); if (customVertexShader.code) { src.push("\n" + customVertexShader.code + "\n"); } if (morphing) { src.push("uniform float SCENEJS_uMorphFactor;"); // LERP factor for morph if (morphState.morph.targets[0].vertexBuf) { // target2 has these arrays also src.push("attribute vec3 SCENEJS_aMorphVertex;"); } } src.push("void main(void) {"); src.push(" vec4 tmpVertex=vec4(SCENEJS_aVertex, 1.0); "); if (normals) { src.push(" vec4 modelNormal = vec4(SCENEJS_aNormal, 0.0); "); } src.push(" SCENEJS_vModelVertex = tmpVertex; "); if (vertexHooks.modelPos) { src.push("tmpVertex=" + vertexHooks.modelPos + "(tmpVertex);"); } if (morphing) { if (morphState.morph.targets[0].vertexBuf) { src.push(" vec4 vMorphVertex = vec4(SCENEJS_aMorphVertex, 1.0); "); if (vertexHooks.modelPos) { src.push("vMorphVertex=" + vertexHooks.modelPos + "(vMorphVertex);"); } src.push(" tmpVertex = vec4(mix(tmpVertex.xyz, vMorphVertex.xyz, SCENEJS_uMorphFactor), 1.0); "); } } src.push(" tmpVertex = SCENEJS_uMMatrix * tmpVertex; "); if (vertexHooks.worldPos) { src.push("tmpVertex=" + vertexHooks.worldPos + "(tmpVertex);"); } // if (clipping || fragmentHooks.worldPosClip) { src.push(" SCENEJS_vWorldVertex = tmpVertex; "); // } src.push("SCENEJS_vEyeVec = normalize(SCENEJS_uEye - tmpVertex.xyz);"); src.push(" tmpVertex = SCENEJS_uVMatrix * tmpVertex; "); if (vertexHooks.viewPos) { src.push("tmpVertex=" + vertexHooks.viewPos + "(tmpVertex);"); } src.push(" SCENEJS_vViewVertex = tmpVertex;"); if (normals && (fragmentHooks.worldNormal || fragmentHooks.viewNormal)) { src.push(" vec3 worldNormal = normalize((SCENEJS_uMNMatrix * modelNormal).xyz); "); src.push(" SCENEJS_vWorldNormal = worldNormal;"); src.push(" SCENEJS_vViewNormal = (SCENEJS_uVNMatrix * vec4(worldNormal, 1.0)).xyz;"); } src.push(" SCENEJS_vProjVertex = SCENEJS_uPMatrix * tmpVertex;"); src.push(" gl_Position = SCENEJS_vProjVertex;"); src.push("}"); if (debugCfg.logScripts == true) { SceneJS_loggingModule.info(src); } return src; }; /** * Composes a fragment shader script for rendering mode in current scene state * @private */ this._composePickingFragmentShader = function() { var customShaders = shaderState.shader.shaders || {}; var customFragmentShader = customShaders.fragment || {}; var fragmentHooks = customFragmentShader.hooks || {}; var clipping = clipState && clipState.clips.length > 0; var normals = this._hasNormals(); var src = [ "precision mediump float;" ]; src.push("vec4 packDepth(const in float depth) {"); src.push(" const vec4 bitShift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);"); src.push(" const vec4 bitMask = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);"); src.push(" vec4 res = fract(depth * bitShift);"); src.push(" res -= res.xxyz * bitMask;"); src.push(" return res;"); src.push("}"); src.push("varying vec4 SCENEJS_vModelVertex;"); src.push("varying vec4 SCENEJS_vWorldVertex;"); src.push("varying vec4 SCENEJS_vViewVertex;"); // View-space vertex src.push("varying vec4 SCENEJS_vProjVertex;"); src.push("uniform bool SCENEJS_uRayPickMode;"); // Z-pick mode when true else colour-pick src.push("uniform vec3 SCENEJS_uPickColor;"); // Used in colour-pick mode src.push("uniform float SCENEJS_uZNear;"); // Used in Z-pick mode src.push("uniform float SCENEJS_uZFar;"); // Used in Z-pick mode src.push("varying vec3 SCENEJS_vEyeVec;"); // Direction of view-space vertex from eye if (normals && (fragmentHooks.worldNormal || fragmentHooks.viewNormal)) { src.push("varying vec3 SCENEJS_vWorldNormal;"); // World-space normal src.push("varying vec3 SCENEJS_vViewNormal;"); // View-space normal } /*----------------------------------------------------------------------------------- * Variables - Clipping *----------------------------------------------------------------------------------*/ if (clipping) { for (var i = 0; i < clipState.clips.length; i++) { src.push("uniform float SCENEJS_uClipMode" + i + ";"); src.push("uniform vec4 SCENEJS_uClipNormalAndDist" + i + ";"); } } /*----------------------------------------------------------------------------------- * Custom GLSL *----------------------------------------------------------------------------------*/ if (customFragmentShader.code) { src.push("\n" + customFragmentShader.code + "\n"); } src.push("void main(void) {"); if (fragmentHooks.worldPosClip) { src.push("if (" + fragmentHooks.worldPosClip + "(SCENEJS_vWorldVertex) == false) { discard; };"); } if (fragmentHooks.viewPosClip) { src.push("if (!" + fragmentHooks.viewPosClip + "(SCENEJS_vViewVertex) == false) { discard; };"); } if (clipping) { src.push(" float dist;"); for (var i = 0; i < clipState.clips.length; i++) { src.push(" if (SCENEJS_uClipMode" + i + " != 0.0) {"); src.push(" dist = dot(SCENEJS_vWorldVertex.xyz, SCENEJS_uClipNormalAndDist" + i + ".xyz) - SCENEJS_uClipNormalAndDist" + i + ".w;"); src.push(" if (SCENEJS_uClipMode" + i + " == 1.0) {"); src.push(" if (dist < 0.0) { discard; }"); src.push(" }"); src.push(" if (SCENEJS_uClipMode" + i + " == 2.0) {"); src.push(" if (dist > 0.0) { discard; }"); src.push(" }"); src.push(" }"); } } if (fragmentHooks.worldPos) { src.push(fragmentHooks.worldPos + "(SCENEJS_vWorldVertex);"); } if (fragmentHooks.viewPos) { src.push(fragmentHooks.viewPos + "(SCENEJS_vViewVertex);"); } if (fragmentHooks.worldEyeVec) { src.push(fragmentHooks.worldEyeVec + "(SCENEJS_vEyeVec);"); } if (normals && fragmentHooks.worldNormal) { src.push(fragmentHooks.worldNormal + "(SCENEJS_vWorldNormal);"); } if (normals && fragmentHooks.viewNormal) { src.push(fragmentHooks.viewNormal + "(SCENEJS_vViewNormal);"); } src.push(" if (SCENEJS_uRayPickMode) {"); src.push(" float zNormalizedDepth = abs((SCENEJS_uZNear + SCENEJS_vViewVertex.z) / (SCENEJS_uZFar - SCENEJS_uZNear));"); src.push(" gl_FragColor = packDepth(zNormalizedDepth); "); src.push(" } else {"); src.push(" gl_FragColor = vec4(SCENEJS_uPickColor.rgb, 1.0); "); src.push(" }"); src.push("}"); if (debugCfg.logScripts == true) { SceneJS_loggingModule.info(src); } return src; }; /*=================================================================================================================== * * Rendering vertex shader * *==================================================================================================================*/ this._isTexturing = function() { if (texState.core && texState.core.layers.length > 0) { if (geoState.geo.uvBuf || geoState.geo.uvBuf2) { return true; } if (morphState.morph && (morphState.morph.targets[0].uvBuf || morphState.morph.targets[0].uvBuf2)) { return true; } } return false; }; this._hasNormals = function() { if (geoState.geo.normalBuf) { return true; } if (morphState.morph && morphState.morph.targets[0].normalBuf) { return true; } return false; }; this._composeRenderingVertexShader = function() { var customShaders = shaderState.shader.shaders || {}; /* Do a full custom shader replacement if code supplied without hooks */ if (customShaders.vertex && customShaders.vertex.code && !customShaders.vertex.hooks) { return customShaders.vertex.code; } var customVertexShader = customShaders.vertex || {}; var vertexHooks = customVertexShader.hooks || {}; var customFragmentShader = customShaders.fragment || {}; var fragmentHooks = customFragmentShader.hooks || {}; var texturing = this._isTexturing(); var normals = this._hasNormals(); var clipping = clipState.clips.length > 0; var morphing = morphState.morph && true; var src = [ "precision mediump float;", ]; src.push("attribute vec3 SCENEJS_aVertex;"); // Model coordinates src.push("uniform vec3 SCENEJS_uEye;"); // World-space eye position src.push("varying vec3 SCENEJS_vEyeVec;"); // Output world-space eye vector /*----------------------------------------------------------------------------------- * Variables - normals *----------------------------------------------------------------------------------*/ if (normals) { src.push("attribute vec3 SCENEJS_aNormal;"); // Normal vectors src.push("uniform mat4 SCENEJS_uMNMatrix;"); // Model normal matrix src.push("uniform mat4 SCENEJS_uVNMatrix;"); // View normal matrix src.push("varying vec3 SCENEJS_vWorldNormal;"); // Output world-space vertex normal src.push("varying vec3 SCENEJS_vViewNormal;"); // Output view-space vertex normal for (var i = 0; i < lightState.lights.length; i++) { var light = lightState.lights[i]; if (light.mode == "dir") { src.push("uniform vec3 SCENEJS_uLightDir" + i + ";"); } if (light.mode == "point") { src.push("uniform vec4 SCENEJS_uLightPos" + i + ";"); } if (light.mode == "spot") { src.push("uniform vec4 SCENEJS_uLightPos" + i + ";"); } /* Vector from vertex to light, packaged with the pre-computed length of that vector */ src.push("varying vec4 SCENEJS_vLightVecAndDist" + i + ";"); // varying for fragment lighting } } if (texturing) { if (geoState.geo.uvBuf) { src.push("attribute vec2 SCENEJS_aUVCoord;"); // UV coords } if (geoState.geo.uvBuf2) { src.push("attribute vec2 SCENEJS_aUVCoord2;"); // UV2 coords } } /* Vertex color variables */ if (geoState.geo.colorBuf) { src.push("attribute vec4 SCENEJS_aVertexColor;"); // UV2 coords src.push("varying vec4 SCENEJS_vColor;"); // Varying for fragment texturing } src.push("uniform mat4 SCENEJS_uMMatrix;"); // Model matrix src.push("uniform mat4 SCENEJS_uVMatrix;"); // View matrix src.push("uniform mat4 SCENEJS_uPMatrix;"); // Projection matrix if (clipping || fragmentHooks.worldPos) { src.push("varying vec4 SCENEJS_vWorldVertex;"); // Varying for fragment clip or world pos hook } if (fragmentHooks.viewPos) { src.push("varying vec4 SCENEJS_vViewVertex;"); // Varying for fragment view clip hook } if (texturing) { // Varyings for fragment texturing if (geoState.geo.uvBuf) { src.push("varying vec2 SCENEJS_vUVCoord;"); } if (geoState.geo.uvBuf2) { src.push("varying vec2 SCENEJS_vUVCoord2;"); } } /*----------------------------------------------------------------------------------- * Variables - Morphing *----------------------------------------------------------------------------------*/ if (morphing) { src.push("uniform float SCENEJS_uMorphFactor;"); // LERP factor for morph if (morphState.morph.targets[0].vertexBuf) { // target2 has these arrays also src.push("attribute vec3 SCENEJS_aMorphVertex;"); } if (normals) { if (morphState.morph.targets[0].normalBuf) { src.push("attribute vec3 SCENEJS_aMorphNormal;"); } } } if (customVertexShader.code) { src.push("\n" + customVertexShader.code + "\n"); } src.push("void main(void) {"); src.push("vec4 tmpVertex=vec4(SCENEJS_aVertex, 1.0); "); if (vertexHooks.modelPos) { src.push("tmpVertex=" + vertexHooks.modelPos + "(tmpVertex);"); } src.push(" vec4 modelVertex = tmpVertex; "); if (normals) { src.push(" vec4 modelNormal = vec4(SCENEJS_aNormal, 0.0); "); } /* * Morphing - morph targets are in same model space as the geometry */ if (morphing) { if (morphState.morph.targets[0].vertexBuf) { src.push(" vec4 vMorphVertex = vec4(SCENEJS_aMorphVertex, 1.0); "); src.push(" modelVertex = vec4(mix(modelVertex.xyz, vMorphVertex.xyz, SCENEJS_uMorphFactor), 1.0); "); } if (normals) { if (morphState.morph.targets[0].normalBuf) { src.push(" vec4 vMorphNormal = vec4(SCENEJS_aMorphNormal, 1.0); "); src.push(" modelNormal = vec4( mix(modelNormal.xyz, vMorphNormal.xyz, 0.0), 1.0); "); } } } src.push(" vec4 worldVertex = SCENEJS_uMMatrix * modelVertex; "); if (vertexHooks.worldPos) { src.push("worldVertex=" + vertexHooks.worldPos + "(worldVertex);"); } if (vertexHooks.viewMatrix) { src.push("vec4 viewVertex = " + vertexHooks.viewMatrix + "(SCENEJS_uVMatrix) * worldVertex;"); } else { src.push("vec4 viewVertex = SCENEJS_uVMatrix * worldVertex; "); } if (vertexHooks.viewPos) { src.push("viewVertex=" + vertexHooks.viewPos + "(viewVertex);"); // Vertex hook function } if (normals) { src.push(" vec3 worldNormal = normalize((SCENEJS_uMNMatrix * modelNormal).xyz); "); src.push(" SCENEJS_vWorldNormal = worldNormal;"); src.push(" SCENEJS_vViewNormal = (SCENEJS_uVNMatrix * vec4(worldNormal, 1.0)).xyz;"); } if (clipping || fragmentHooks.worldPos) { src.push(" SCENEJS_vWorldVertex = worldVertex;"); // Varying for fragment world clip or hooks } if (fragmentHooks.viewPos) { src.push(" SCENEJS_vViewVertex = viewVertex;"); // Varying for fragment hooks } if (vertexHooks.projMatrix) { src.push("gl_Position = " + vertexHooks.projMatrix + "(SCENEJS_uPMatrix) * viewVertex;"); } else { src.push(" gl_Position = SCENEJS_uPMatrix * viewVertex;"); } /*----------------------------------------------------------------------------------- * Logic - normals * * Transform the world-space lights into view space *----------------------------------------------------------------------------------*/ src.push(" vec3 tmpVec3;"); if (normals) { for (var i = 0; i < lightState.lights.length; i++) { light = lightState.lights[i]; if (light.mode == "dir") { src.push("SCENEJS_vLightVecAndDist" + i + " = vec4(-normalize(SCENEJS_uLightDir" + i + "), 0.0);"); } if (light.mode == "point") { src.push("tmpVec3 = (SCENEJS_uLightPos" + i + ".xyz - worldVertex.xyz);"); src.push("SCENEJS_vLightVecAndDist" + i + " = vec4(normalize(tmpVec3), length(tmpVec3));"); } if (light.mode == "spot") { } } } src.push("SCENEJS_vEyeVec = normalize(SCENEJS_uEye - worldVertex.xyz);"); if (texturing) { // varyings for fragment texturing if (geoState.geo.uvBuf) { src.push("SCENEJS_vUVCoord = SCENEJS_aUVCoord;"); } if (geoState.geo.uvBuf2) { src.push("SCENEJS_vUVCoord2 = SCENEJS_aUVCoord2;"); } } if (geoState.geo.colorBuf) { src.push("SCENEJS_vColor = SCENEJS_aVertexColor;"); // Varyings for fragment interpolated vertex coloring } src.push("}"); if (debugCfg.logScripts === true) { SceneJS_loggingModule.info(src); } return src; }; /*----------------------------------------------------------------------------------------------------------------- * Rendering Fragment shader *---------------------------------------------------------------------------------------------------------------*/ this._composeRenderingFragmentShader = function() { var customShaders = shaderState.shader.shaders || {}; /* Do a full custom shader replacement if code supplied without hooks */ if (customShaders.fragment && customShaders.fragment.code && !customShaders.fragment.hooks) { return customShaders.fragment.code; } var customFragmentShader = customShaders.fragment || {}; var fragmentHooks = customFragmentShader.hooks || {}; var texturing = this._isTexturing(); var normals = this._hasNormals(); var clipping = clipState && clipState.clips.length > 0; var colortrans = colortransState && colortransState.core; var src = ["\n"]; src.push("precision mediump float;"); if (clipping || fragmentHooks.worldPos) { src.push("varying vec4 SCENEJS_vWorldVertex;"); // World-space vertex } if (fragmentHooks.viewPos) { src.push("varying vec4 SCENEJS_vViewVertex;"); // View-space vertex } /*----------------------------------------------------------------------------------- * Variables - Clipping *----------------------------------------------------------------------------------*/ if (clipping) { for (var i = 0; i < clipState.clips.length; i++) { src.push("uniform float SCENEJS_uClipMode" + i + ";"); src.push("uniform vec4 SCENEJS_uClipNormalAndDist" + i + ";"); } } if (texturing) { if (geoState.geo.uvBuf) { src.push("varying vec2 SCENEJS_vUVCoord;"); } if (geoState.geo.uvBuf2) { src.push("varying vec2 SCENEJS_vUVCoord2;"); } var layer; for (var i = 0, len = texState.core.layers.length; i < len; i++) { layer = texState.core.layers[i]; src.push("uniform sampler2D SCENEJS_uSampler" + i + ";"); if (layer.matrix) { src.push("uniform mat4 SCENEJS_uLayer" + i + "Matrix;"); } src.push("uniform float SCENEJS_uLayer" + i + "BlendFactor;"); } } /* True when lighting */ src.push("uniform bool SCENEJS_uBackfaceTexturing;"); src.push("uniform bool SCENEJS_uBackfaceLighting;"); src.push("uniform bool SCENEJS_uSpecularLighting;"); /* True when rendering transparency */ src.push("uniform bool SCENEJS_uTransparent;"); /* Vertex color variable */ if (geoState.geo.colorBuf) { src.push("varying vec4 SCENEJS_vColor;"); } src.push("uniform vec3 SCENEJS_uAmbient;"); // Scene ambient colour - taken from clear colour src.push("uniform vec3 SCENEJS_uMaterialBaseColor;"); src.push("uniform float SCENEJS_uMaterialAlpha;"); src.push("uniform float SCENEJS_uMaterialEmit;"); src.push("uniform vec3 SCENEJS_uMaterialSpecularColor;"); src.push("uniform float SCENEJS_uMaterialSpecular;"); src.push("uniform float SCENEJS_uMaterialShine;"); src.push(" vec3 ambientValue=SCENEJS_uAmbient;"); src.push(" float emit = SCENEJS_uMaterialEmit;"); src.push("varying vec3 SCENEJS_vEyeVec;"); // Direction of view-space vertex from eye if (normals) { src.push("varying vec3 SCENEJS_vWorldNormal;"); // World-space normal src.push("varying vec3 SCENEJS_vViewNormal;"); // View-space normal var light; for (var i = 0; i < lightState.lights.length; i++) { light = lightState.lights[i]; src.push("uniform vec3 SCENEJS_uLightColor" + i + ";"); if (light.mode == "point") { src.push("uniform vec3 SCENEJS_uLightAttenuation" + i + ";"); } src.push("varying vec4 SCENEJS_vLightVecAndDist" + i + ";"); // Vector from light to vertex } } if (colortrans) { src.push("uniform float SCENEJS_uColorTransMode ;"); src.push("uniform vec4 SCENEJS_uColorTransAdd;"); src.push("uniform vec4 SCENEJS_uColorTransScale;"); src.push("uniform float SCENEJS_uColorTransSaturation;"); } if (customFragmentShader.code) { src.push("\n" + customFragmentShader.code + "\n"); } src.push("void main(void) {"); /*----------------------------------------------------------------------------------- * Logic - Clipping *----------------------------------------------------------------------------------*/ if (clipping) { src.push(" float dist;"); for (var i = 0; i < clipState.clips.length; i++) { src.push(" if (SCENEJS_uClipMode" + i + " != 0.0) {"); src.push(" dist = dot(SCENEJS_vWorldVertex.xyz, SCENEJS_uClipNormalAndDist" + i + ".xyz) - SCENEJS_uClipNormalAndDist" + i + ".w;"); src.push(" if (SCENEJS_uClipMode" + i + " == 1.0) {"); src.push(" if (dist < 0.0) { discard; }"); src.push(" }"); src.push(" if (SCENEJS_uClipMode" + i + " == 2.0) {"); src.push(" if (dist > 0.0) { discard; }"); src.push(" }"); src.push(" }"); } } if (fragmentHooks.worldPos) { src.push(fragmentHooks.worldPos + "(SCENEJS_vWorldVertex);"); } if (fragmentHooks.viewPos) { src.push(fragmentHooks.viewPos + "(SCENEJS_vViewVertex);"); } if (fragmentHooks.worldEyeVec) { src.push(fragmentHooks.worldEyeVec + "(SCENEJS_vEyeVec);"); } if (normals && fragmentHooks.worldNormal) { src.push(fragmentHooks.worldNormal + "(SCENEJS_vWorldNormal);"); } if (normals && fragmentHooks.viewNormal) { src.push(fragmentHooks.viewNormal + "(SCENEJS_vViewNormal);"); } if (geoState.geo.colorBuf) { src.push(" vec3 color = SCENEJS_vColor.rgb;"); } else { src.push(" vec3 color = SCENEJS_uMaterialBaseColor;") } src.push(" float alpha = SCENEJS_uMaterialAlpha;"); src.push(" float emit = SCENEJS_uMaterialEmit;"); src.push(" float specular = SCENEJS_uMaterialSpecular;"); src.push(" vec3 specularColor = SCENEJS_uMaterialSpecularColor;"); src.push(" float shine = SCENEJS_uMaterialShine;"); if (fragmentHooks.materialBaseColor) { src.push("color=" + fragmentHooks.materialBaseColor + "(color);"); } if (fragmentHooks.materialAlpha) { src.push("alpha=" + fragmentHooks.materialAlpha + "(alpha);"); } if (fragmentHooks.materialEmit) { src.push("emit=" + fragmentHooks.materialEmit + "(emit);"); } if (fragmentHooks.materialSpecular) { src.push("specular=" + fragmentHooks.materialSpecular + "(specular);"); } if (fragmentHooks.materialSpecularColor) { src.push("specularColor=" + fragmentHooks.materialSpecularColor + "(specularColor);"); } if (fragmentHooks.materialShine) { src.push("shine=" + fragmentHooks.materialShine + "(shine);"); } if (normals) { src.push(" float attenuation = 1.0;"); src.push(" vec3 normalVec=SCENEJS_vWorldNormal;"); } var layer; if (texturing) { if (normals) { src.push("if (SCENEJS_uBackfaceTexturing || dot(SCENEJS_vWorldNormal, SCENEJS_vEyeVec) > 0.0) {"); } src.push(" vec4 texturePos;"); src.push(" vec2 textureCoord=vec2(0.0,0.0);"); for (var i = 0, len = texState.core.layers.length; i < len; i++) { layer = texState.core.layers[i]; /* Texture input */ if (layer.applyFrom == "normal" && normals) { if (geoState.geo.normalBuf) { src.push("texturePos=vec4(normalVec.xyz, 1.0);"); } else { SceneJS_loggingModule.warn("Texture layer applyFrom='normal' but geo has no normal vectors"); continue; } } if (layer.applyFrom == "uv") { if (geoState.geo.uvBuf) { src.push("texturePos = vec4(SCENEJS_vUVCoord.s, SCENEJS_vUVCoord.t, 1.0, 1.0);"); } else { SceneJS_loggingModule.warn("Texture layer applyTo='uv' but geometry has no UV coordinates"); continue; } } if (layer.applyFrom == "uv2") { if (geoState.geo.uvBuf2) { src.push("texturePos = vec4(SCENEJS_vUVCoord2.s, SCENEJS_vUVCoord2.t, 1.0, 1.0);"); } else { SceneJS_loggingModule.warn("Texture layer applyTo='uv2' but geometry has no UV2 coordinates"); continue; } } /* Texture matrix */ if (layer.matrix) { src.push("textureCoord=(SCENEJS_uLayer" + i + "Matrix * texturePos).xy;"); } else { src.push("textureCoord=texturePos.xy;"); } /* Alpha from Texture * */ if (layer.applyTo == "alpha") { if (layer.blendMode == "multiply") { src.push("alpha = alpha * (SCENEJS_uLayer" + i + "BlendFactor * texture2D(SCENEJS_uSampler" + i + ", vec2(textureCoord.x, 1.0 - textureCoord.y)).b);"); } else if (layer.blendMode == "add") { src.push("alpha = ((1.0 - SCENEJS_uLayer" + i + "BlendFactor) * alpha) + (SCENEJS_uLayer" + i + "BlendFactor * texture2D(SCENEJS_uSampler" + i + ", vec2(textureCoord.x, 1.0 - textureCoord.y)).b);"); } } /* Texture output */ if (layer.applyTo == "baseColor") { if (layer.blendMode == "multiply") { src.push("color = color * (SCENEJS_uLayer" + i + "BlendFactor * texture2D(SCENEJS_uSampler" + i + ", vec2(textureCoord.x, 1.0 - textureCoord.y)).rgb);"); } else { src.push("color = ((1.0 - SCENEJS_uLayer" + i + "BlendFactor) * color) + (SCENEJS_uLayer" + i + "BlendFactor * texture2D(SCENEJS_uSampler" + i + ", vec2(textureCoord.x, 1.0 - textureCoord.y)).rgb);"); } } if (layer.applyTo == "emit") { if (layer.blendMode == "multiply") { src.push("emit = emit * (SCENEJS_uLayer" + i + "BlendFactor * texture2D(SCENEJS_uSampler" + i + ", vec2(textureCoord.x, 1.0 - textureCoord.y)).r);"); } else { src.push("emit = ((1.0 - SCENEJS_uLayer" + i + "BlendFactor) * emit) + (SCENEJS_uLayer" + i + "BlendFactor * texture2D(SCENEJS_uSampler" + i + ", vec2(textureCoord.x, 1.0 - textureCoord.y)).r);"); } } if (layer.applyTo == "specular" && normals) { if (layer.blendMode == "multiply") { src.push("specular = specular * (SCENEJS_uLayer" + i + "BlendFactor * texture2D(SCENEJS_uSampler" + i + ", vec2(textureCoord.x, 1.0 - textureCoord.y)).r);"); } else { src.push("specular = ((1.0 - SCENEJS_uLayer" + i + "BlendFactor) * specular) + (SCENEJS_uLayer" + i + "BlendFactor * texture2D(SCENEJS_uSampler" + i + ", vec2(textureCoord.x, 1.0 - textureCoord.y)).r);"); } } if (layer.applyTo == "normals" && normals) { src.push("vec3 bump = normalize(texture2D(SCENEJS_uSampler" + i + ", textureCoord).xyz * 2.0 - 1.0);"); src.push("normalVec *= -bump;"); } } if (normals) { src.push("}"); } } src.push(" vec4 fragColor;"); if (normals) { src.push("if (SCENEJS_uBackfaceLighting || dot(SCENEJS_vWorldNormal, SCENEJS_vEyeVec) > 0.0) {"); src.push(" vec3 lightValue = SCENEJS_uAmbient;"); src.push(" vec3 specularValue = vec3(0.0, 0.0, 0.0);"); src.push(" vec3 lightVec;"); src.push(" float dotN;"); src.push(" float lightDist;"); var light; for (var i = 0; i < lightState.lights.length; i++) { light = lightState.lights[i]; src.push("lightVec = SCENEJS_vLightVecAndDist" + i + ".xyz;"); if (light.mode == "point") { src.push("dotN = max(dot(normalVec, lightVec) ,0.0);"); //src.push("if (dotN > 0.0) {"); src.push("lightDist = SCENEJS_vLightVecAndDist" + i + ".w;"); src.push(" attenuation = 1.0 / (" + " SCENEJS_uLightAttenuation" + i + "[0] + " + " SCENEJS_uLightAttenuation" + i + "[1] * lightDist + " + " SCENEJS_uLightAttenuation" + i + "[2] * lightDist * lightDist);"); if (light.diffuse) { src.push(" lightValue += dotN * SCENEJS_uLightColor" + i + " * attenuation;"); } if (light.specular) { src.push("if (SCENEJS_uSpecularLighting) specularValue += attenuation * specularColor * SCENEJS_uLightColor" + i + " * specular * pow(max(dot(reflect(lightVec, normalVec), SCENEJS_vEyeVec),0.0), shine);"); } //src.push("}"); } if (light.mode == "dir") { src.push("dotN = max(dot(normalVec,lightVec),0.0);"); //src.push("if (dotN > 0.0) {"); if (light.diffuse) { src.push("lightValue += dotN * SCENEJS_uLightColor" + i + ";"); } if (light.specular) { src.push("if (SCENEJS_uSpecularLighting) specularValue += specularColor * SCENEJS_uLightColor" + i + " * specular * pow(max(dot(reflect(lightVec, normalVec),SCENEJS_vEyeVec),0.0), shine);"); } // src.push("}"); } } src.push(" fragColor = vec4((specularValue.rgb + color.rgb * lightValue.rgb) + (emit * color.rgb), alpha);"); src.push(" } else {"); src.push(" fragColor = vec4(color.rgb + (emit * color.rgb), alpha);"); src.push(" }"); } else { // No normals src.push("fragColor = vec4((emit * color.rgb) + (emit * color.rgb), alpha);"); } /* Color transformations */ if (colortrans) { // src.push(" if (SCENEJS_uColorTransMode != 0.0) {"); // Not disabled // src.push(" if (SCENEJS_uColorTransSaturation < 0.0) {"); // src.push(" float intensity = 0.3 * fragColor.r + 0.59 * fragColor.g + 0.11 * fragColor.b;"); // src.push(" fragColor = vec4((intensity * -SCENEJS_uColorTransSaturation) + fragColor.rgb * (1.0 + SCENEJS_uColorTransSaturation), 1.0);"); // src.push(" }"); // src.push(" fragColor = (fragColor * SCENEJS_uColorTransScale) + SCENEJS_uColorTransAdd;"); // src.push(" }"); } if (fragmentHooks.pixelColor) { src.push("fragColor=" + fragmentHooks.pixelColor + "(fragColor);"); } if (debugCfg.whitewash == true) { src.push(" gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);"); } else { src.push(" gl_FragColor = fragColor;"); } src.push("}"); if (debugCfg.logScripts == true) { SceneJS_loggingModule.info(src); } return src; }; })(); /** * State node renderer */ var SceneJS_DrawListRenderer = function(cfg) { this._canvas = cfg.canvas; this._context = cfg.context; this.pickNameStates = []; this._callCtx = {}; // State retained within a single iteration of the call list /** Facade to support node state queries while node rendering */ var self = this; this._queryFacade = { _node: null, _setNode: function(node) { this._node = node; this._mem = {}; }, getCameraMatrix : function() { return this._mem.camMat ? this._mem.camMat : this._mem.camMat = this._node.projXFormState.mat.slice(0); }, getViewMatrix : function() { return this._mem.viewMat ? this._mem.viewMat : this._mem.viewMat = this._node.viewXFormState.mat.slice(0); }, getModelMatrix : function() { return this._mem.modelMat ? this._mem.modelMat : this._mem.modelMat = this._node.modelXFormState.mat.slice(0); }, getCanvasPos : function(offset) { this.getProjPos(offset); if (!this._mem.canvasWidth) { this._mem.canvasWidth = self._canvas.width; this._mem.canvasHeight = self._canvas.height; } /* Projection division and map to canvas */ var pc = this._mem.pc; var x = (pc[0] / pc[3]) * this._mem.canvasWidth * 0.5; var y = (pc[1] / pc[3]) * this._mem.canvasHeight * 0.5; return this._mem.canvasPos = { x: x + (self._canvas.width * 0.5), y: this._mem.canvasHeight - y - (this._mem.canvasHeight * 0.5) }; }, getCameraPos : function(offset) { this.getProjPos(offset); this._mem.camPos = SceneJS_math_normalizeVec3(this._mem.pc, [0,0,0]); return { x: this._mem.camPos[0], y: this._mem.camPos[1], z: this._mem.camPos[2] }; }, getProjPos : function(offset) { this.getViewPos(offset); this._mem.pc = SceneJS_math_transformPoint3(this._node.projXFormState.mat, this._mem.vc); return { x: this._mem.pc[0], y: this._mem.pc[1], z: this._mem.pc[2], w: this._mem.pc[3] }; }, getViewPos : function(offset) { this.getWorldPos(offset); this._mem.vc = SceneJS_math_transformPoint3(this._node.viewXFormState.mat, this._mem.wc); return { x: this._mem.vc[0], y: this._mem.vc[1], z: this._mem.vc[2], w: this._mem.vc[3] }; }, getWorldPos : function(offset) { this._mem.wc = SceneJS_math_transformPoint3(this._node.modelXFormState.mat, offset || [0,0,0]); return { x: this._mem.wc[0], y: this._mem.wc[1], z: this._mem.wc[2], w: this._mem.wc[3] }; } }; /** * Called before we render all state nodes for a frame. * Forgets any program that was used for the last node rendered, which causes it * to forget all states for that node. */ this.init = function(params) { params = params || {}; this._picking = params.picking; this._rayPicking = params.rayPick; this._callListDirty = params.callListDirty; this._callFuncsDirty = params.callFuncsDirty; this._program = null; this._lastFramebuf = null; this._lastFlagsState = null; this._lastRendererState = null; this._lastRenderListenersState = null; this._pickIndex = 0; /* Initialialise call context */ this._callCtx.picking = this._picking; this._callCtx.rayPicking = this._rayPicking; this._callCtx.lastFrameBuf = null; this._callCtx.lastRendererProps = null; this._callCtx.lastFlagsState = null; this.stateSortProfile = { numTextures: 0, numGeometries: 0 }; }; this.createCall = function(fn) { if (this._picking) { this._node.__pickCallList[this._node.__pickCallListLen++] = fn; } else { this._node.__drawCallList[this._node.__drawCallListLen++] = fn; } fn(); }; /** * Renders a state node. Makes state changes only where the node's states have different IDs * than the states of the last node. If the node has a different program than the last node * rendered, Renderer forgets all states for the previous node and makes a fresh set of transitions * into all states for this node. */ this.renderNode = function(node) { var i, len, k; if (this._picking || this._rayPicking) { // Picking mode - same calls for pick and Z-pick if (!(this._callListDirty || this._callFuncsDirty)) { // Call list and function cache still good var pickList = node.__pickCallList; // Execute call list and return for (i = 0,len = node.__pickCallListLen; i < len; i++) { pickList[i](); } return; } /* Call list or function cache dirty */ if (!node.__pickCallList) { // Lazy-allocate call list and function caches node._pickCallFuncs = node._pickCallFuncs || {}; node.__pickCallList = []; } node.__pickCallListLen = 0; // (Re)building call list if (this._callFuncsDirty) { node._pickCallFuncs = {}; // (Re)building function cache } } else { // Drawing mode this._queryFacade._setNode(node); // Activate query facade for render listeners if (!(this._callListDirty || this._callFuncsDirty)) { // Call list and function cache still good var drawList = node.__drawCallList; // Execute pick call list and return for (i = 0,len = node.__drawCallListLen; i < len; i++) { drawList[i](); } return; } if (!node.__drawCallList) { // Lazy-allocate call list and function caches node._drawCallFuncs = node._drawCallFuncs || {}; node.__drawCallList = []; } node.__drawCallListLen = 0; // (Re)building call list if (this._callFuncsDirty) { // (Re)building function cache node._drawCallFuncs = {}; } } this._node = node; this._callFuncs = this._picking // Draw list is built once for colour pick and z-pick ? node._pickCallFuncs // Activate cache of WebGL call functions for pick or draw : node._drawCallFuncs; /* Cache some often-used node states for fast access */ var nodeFlagsState = node.flagsState; var nodeGeoState = node.geoState; /* Bind program if none bound, or if node uses different program * to that currently bound. * * Also flag all buffers as needing to be bound. */ if ((!this._program) || (node.program.id != this._lastProgramId)) { if (this._picking) { this._program = node.program.pick; } else { this._program = node.program.render; } if (this.stateSortProfile) { this._program.setProfile(this.profile); } /*---------------------------------------------------------------------------------------------------------- * Program for render or pick *--------------------------------------------------------------------------------------------------------*/ if (this._picking) { this.createCall( this._callFuncs["pickShader"] || (this._callFuncs["pickShader"] = (function() { var program = node.program.pick; var context = self._context; var callCtx = self._callCtx; var rayPickModeLocation; return function() { if (callCtx.program) { callCtx.program.unbind(); } program.bind(); if (!rayPickModeLocation) { rayPickModeLocation = program.getUniformLocation("SCENEJS_uRayPickMode"); } context.uniform1i(rayPickModeLocation, !!callCtx.rayPicking); callCtx.program = program; }; })())); } else { this.createCall( this._callFuncs["renderShader"] || (this._callFuncs["renderShader"] = (function() { var program = node.program.render; var callCtx = self._callCtx; return function() { if (callCtx.program) { callCtx.program.unbind(); } program.bind(); callCtx.program = program; }; })())); } /** Track IDs of bound states as we build call list */ this._lastProgramId = node.program.id; this._lastShaderStateId = -1; this._lastShaderParamsStateId = -1; this._lastGeoStateId = -1; this._lastFlagsStateId = -1; this._lastColortransStateId = -1; this._lastLightStateId = -1; this._lastClipStateId = -1; this._lastMorphStateId = -1; this._lastTexStateId = -1; this._lastMaterialStateId = -1; this._lastViewXFormStateId = -1; this._lastModelXFormStateId = -1; this._lastProjXFormStateId = -1; this._lastPickStateId = -1; this._lastFramebufStateId = -1; this._lastRenderListenersStateId = -1; /*---------------------------------------------------------------------------------------------------------- * shader node *--------------------------------------------------------------------------------------------------------*/ if (node.shaderState.shader && node.shaderState.shader.paramsStack) { // Custom shader - set any params we have if (this._lastShaderStateId != node.shaderState._stateId) { /*-------------------------------------------------------------------------------------------------- * A call list node. * * Each WebGL call is wrapped by function that is created by a higher-order function which prepares * the arguments and caches them in a closure. *------------------------------------------------------------------------------------------------*/ this.createCall( this._callFuncs["shader"] || (this._callFuncs["shader"] = (function() { /*------------------------------------------------------------------------- * Code within the closure should be independent of state on any other * draw list node, in the assumption that the draw list may reorder, or * that other nodes may disappear from the draw list. It may however * safely depend on other previous executions of calls belonging to this * draw list node. *-----------------------------------------------------------------------*/ var program = self._program; /* Cache param uniform locations and values */ var paramsStack = node.shaderState.shader.paramsStack; return function() { /*---------------------------------------------------------------------- * Code within the call function depends on state held within the * closure, and may safely depend on state set by any previous other * draw list node or call. *--------------------------------------------------------------------*/ var params; var name; for (var i = 0, len = paramsStack.length; i < len; i++) { params = paramsStack[i]; for (name in params) { if (params.hasOwnProperty(name)) { program.setUniform(name, params[name]); // TODO: cache locations } } } }; })())); this._lastShaderStateId = node.shaderState._stateId; } } } /*---------------------------------------------------------------------------------------------------------- * shaderParams *--------------------------------------------------------------------------------------------------------*/ if (node.shaderParamsState.paramsStack) { if (this._lastShaderParamsStateId != node.shaderParamsState._stateId) { this.createCall( this._callFuncs["shaderParams"] || (this._callFuncs["shaderParams"] = (function() { var program = self._program; var paramsStack = node.shaderParamsState.paramsStack; return function() { var params; var name; for (var i = 0, len = paramsStack.length; i < len; i++) { params = paramsStack[i]; for (name in params) { if (params.hasOwnProperty(name)) { program.setUniform(name, params[name]); // TODO: cache locations } } } }; })())); this._lastShaderParamsStateId = node.shaderParamsState._stateId; } } /*---------------------------------------------------------------------------------------------------------- * frameBuf - maybe unbind old and maybe bind new *--------------------------------------------------------------------------------------------------------*/ if ((!this._lastFrameBuf) || this._lastFramebufStateId != node.frameBufState._stateId) { this.createCall( this._callFuncs["frameBuf"] || (this._callFuncs["frameBuf"] = (function() { var context = self._context; var callCtx = self._callCtx; var frameBuf = node.frameBufState.frameBuf; return function() { if (callCtx.lastFrameBuf) { context.finish(); // Force frameBuf to complete callCtx.lastFrameBuf.unbind(); } if (frameBuf) { frameBuf.bind(); } callCtx.lastFrameBuf = frameBuf; // Must flush on cleanup }; })())); this._lastFramebufStateId = node.frameBufState._stateId; } /*---------------------------------------------------------------------------------------------------------- * texture *--------------------------------------------------------------------------------------------------------*/ if (!this._picking) { if (node.texState._stateId != this._lastTexStateId) { var core = node.texState.core; if (core) { this.createCall( this._callFuncs["texture"] || (this._callFuncs["texture"] = (function() { var program = self._program; var numLayers = core.layers.length; var layers = core.layers; var layerVars = []; for (var j = 0; j < numLayers; j++) { layerVars.push({ texSamplerName : "SCENEJS_uSampler" + j, texMatrixName : "SCENEJS_uLayer" + j + "Matrix", texBlendFactorName : "SCENEJS_uLayer" + j + "BlendFactor" }); } return function() { var layer, vars; for (var j = 0, len = numLayers; j < len; j++) { layer = layers[j]; vars = layerVars[j]; if (layer.texture) { // Lazy-loads program.bindTexture(vars.texSamplerName, layer.texture, j); if (layer.matrixAsArray) { // Must bind matrix in any case program.setUniform(vars.texMatrixName, layer.matrixAsArray); } program.setUniform(vars.texBlendFactorName, layer.blendFactor); } } }; })())); } } this._lastTexStateId = node.texState._stateId; } /*---------------------------------------------------------------------------------------------------------- * geometry or morphGeometry * * 1. Disable VBOs * 2. If new morphGeometry then bind target VBOs and remember which arrays we bound * 3. If new geometry then bind VBOs for whatever is not already bound *--------------------------------------------------------------------------------------------------------*/ if ((nodeGeoState._stateId != this._lastGeoStateId) // New geometry || (node.morphState.morph && node.morphState._stateId != this._lastMorphStateId)) { // New morphGeometry /* Disable all vertex arrays for draw and pick */ this.createCall( this._callFuncs["unbindVBOs"] || (this._callFuncs["unbindVBOs"] = (function() { var context = self._context; return function() { for (var k = 0; k < 8; k++) { context.disableVertexAttribArray(k); } }; })())); var morphVertexBufBound = false; var morphNormalBufBound = false; var morphUVBufBound = false; var morphUV2BufBound = false; if (node.morphState.morph && node.morphState._stateId != this._lastMorphStateId) { /* Bind morph VBOs for draw and pick */ this.createCall( this._callFuncs["morph"] || (this._callFuncs["morph"] = (function() { var program = self._program; var context = self._context; var vertexAttr = program.getAttribute("SCENEJS_aVertex"); var morphVertexAttr = program.getAttribute("SCENEJS_aMorphVertex"); var normalAttr = program.getAttribute("SCENEJS_aNormal"); var morphNormalAttr = program.getAttribute("SCENEJS_aMorphNormal"); var morphUVAttr = program.getAttribute("SCENEJS_aMorphUVCoord"); var uMorphFactor = program.getUniformLocation("SCENEJS_uMorphFactor"); var morph = node.morphState.morph; return function() { var target1 = morph.targets[morph.key1]; // Keys will update var target2 = morph.targets[morph.key2]; if (vertexAttr) { vertexAttr.bindFloatArrayBuffer(target1.vertexBuf); morphVertexAttr.bindFloatArrayBuffer(target2.vertexBuf); morphVertexBufBound = true; } if (morphNormalAttr) { normalAttr.bindFloatArrayBuffer(target1.normalBuf); morphNormalAttr.bindFloatArrayBuffer(target2.normalBuf); morphNormalBufBound = true; } else if (normalAttr && target1.normalBuf) { normalAttr.bindFloatArrayBuffer(target1.normalBuf); morphNormalBufBound = true; } if (morphUVAttr && target1.uvBuf) { program.bindFloatArrayBuffer(target1.uvBuf); program.bindFloatArrayBuffer(target2.uvBuf); morphUVBufBound = true; } if (uMorphFactor) { context.uniform1f(uMorphFactor, morph.factor); // Bind LERP factor } }; })())); this._lastMorphStateId = node.morphState._stateId; } /* Bind any unbound VBOs for draw and pick */ this.createCall( this._callFuncs["geo"] || (this._callFuncs["geo"] = (function() { var program = self._program; var geo = nodeGeoState.geo; var vertexBuf = geo.vertexBuf; var normalBuf = geo.normalBuf; var colorBuf = geo.colorBuf; var vertexAttr = (!morphVertexBufBound && vertexBuf) ? program.getAttribute("SCENEJS_aVertex") : undefined; var normalAttr = (!morphNormalBufBound && normalBuf) ? program.getAttribute("SCENEJS_aNormal") : undefined; var colorAttr = (colorBuf) ? program.getAttribute("SCENEJS_aVertexColor") : undefined; return function() { if (vertexAttr) { vertexAttr.bindFloatArrayBuffer(vertexBuf); } if (normalAttr) { normalAttr.bindFloatArrayBuffer(normalBuf); } if (colorAttr) { colorAttr.bindFloatArrayBuffer(colorBuf); } }; })())); if (!this._picking) { if (node.texState && node.texState.core && node.texState.core.layers.length > 0) { /* Bind UV buffers for draw * TODO: Texturing for pick as well; need to pick through transparent regions of alpha maps */ this.createCall( this._callFuncs["uvs"] || (this._callFuncs["uvs"] = (function() { var program = self._program; var geo = nodeGeoState.geo; var uvBuf = geo.uvBuf; var uvBuf2 = geo.uvBuf2; var uvCoordAttr = (uvBuf) ? program.getAttribute("SCENEJS_aUVCoord") : undefined; var uvCoord2Attr = (uvBuf) ? program.getAttribute("SCENEJS_aUVCoord2") : undefined; return function() { if (uvCoordAttr) { uvCoordAttr.bindFloatArrayBuffer(uvBuf); } if (uvCoord2Attr) { uvCoord2Attr.bindFloatArrayBuffer(uvBuf2); } }; })())); } } /* Bind IBO for draw and pick */ this.createCall( this._callFuncs["ibo"] || (this._callFuncs["ibo"] = (function() { var indexBuf = nodeGeoState.geo.indexBuf; return function() { indexBuf.bind(); }; })())); this.stateSortProfile.numGeometries++; this._lastGeoStateId = nodeGeoState._stateId; } /*---------------------------------------------------------------------------------------------------------- * renderer *--------------------------------------------------------------------------------------------------------*/ if (!this._lastRendererState || node.rendererState._stateId != this._lastRendererState._stateId) { /* Switch properties */ this.createCall( this._callFuncs["bindProps"] || (this._callFuncs["bindProps"] = (function() { var context = self._context; var callCtx = self._callCtx; var rendererState = node.rendererState; return function() { if (callCtx.lastRendererProps) { callCtx.lastRendererProps.restoreProps(context); } if (rendererState.props) { rendererState.props.setProps(context); } callCtx.lastRendererProps = rendererState.props; }; })())); /* Set ambient color */ this.createCall( this._callFuncs["ambient"] || (this._callFuncs["ambient"] = (function() { var context = self._context; var program = self._program; var rendererState = node.rendererState; var defaultColor = [0, 0, 0]; var uColorLocation = program.getUniformLocation("SCENEJS_uAmbient"); return function() { if (uColorLocation) { var props = rendererState.props; if (props && props.clearColor) { var clearColor = props.clearColor; context.uniform3fv(uColorLocation, [clearColor.r, clearColor.g, clearColor.b]); } else { context.uniform3fv(uColorLocation, defaultColor); } } }; })())); this._lastRendererState = node.rendererState; } /*---------------------------------------------------------------------------------------------------------- * lookAt *--------------------------------------------------------------------------------------------------------*/ if (node.viewXFormState._stateId != this._lastViewXFormStateId) { this.createCall( this._callFuncs["lookAt"] || (this._callFuncs["lookAt"] = (function() { var program = self._program; var context = self._context; var viewXFormState = node.viewXFormState; var matLocation = program.getUniformLocation("SCENEJS_uVMatrix"); var normalMatLocation = program.getUniformLocation("SCENEJS_uVNMatrix"); var eyeLocation = program.getUniformLocation("SCENEJS_uEye"); return function() { var mat = viewXFormState.mat; var normalMat = viewXFormState.normalMat; var lookAt = viewXFormState.lookAt; if (matLocation) { context.uniformMatrix4fv(matLocation, context.FALSE, mat); } if (normalMatLocation) { context.uniformMatrix4fv(normalMatLocation, context.FALSE, normalMat); } if (eyeLocation) { context.uniform3fv(eyeLocation, lookAt.eye); } }; })())); this._lastViewXFormStateId = node.viewXFormState._stateId; } /*---------------------------------------------------------------------------------------------------------- * Model transform *--------------------------------------------------------------------------------------------------------*/ if (node.modelXFormState._stateId != this._lastModelXFormStateId) { this.createCall( this._callFuncs["modelXF"] || (this._callFuncs["modelXF"] = (function() { var program = self._program; var context = self._context; var modelXFormState = node.modelXFormState; var matLocation = program.getUniformLocation("SCENEJS_uMMatrix"); var normalMatLocation = program.getUniformLocation("SCENEJS_uMNMatrix"); return function() { var mat = modelXFormState.mat; var normalMat = modelXFormState.normalMat; if (matLocation) { context.uniformMatrix4fv(matLocation, context.FALSE, mat); } if (normalMatLocation) { context.uniformMatrix4fv(normalMatLocation, context.FALSE, normalMat); } }; })())); this._lastModelXFormStateId = node.modelXFormState._stateId; } /*---------------------------------------------------------------------------------------------------------- * projection matrix *--------------------------------------------------------------------------------------------------------*/ if (node.projXFormState._stateId != this._lastProjXFormStateId) { this.createCall( this._callFuncs["camera"] || (this._callFuncs["camera"] = (function() { var program = self._program; var context = self._context; var callCtx = self._callCtx; var projXFormState = node.projXFormState; var matLocation = program.getUniformLocation("SCENEJS_uPMatrix"); var zNearLocation = program.getUniformLocation("SCENEJS_uZNear"); var zFarLocation = program.getUniformLocation("SCENEJS_uZFar"); return function() { var mat = projXFormState.mat; var optics = projXFormState.optics; if (matLocation) { context.uniformMatrix4fv(matLocation, context.FALSE, mat); } if (callCtx.rayPicking) { // Z-pick pass: feed near and far clip planes into shader if (zFarLocation) { context.uniform1f(zNearLocation, optics.near); } if (zFarLocation) { context.uniform1f(zFarLocation, optics.far); } } }; })())); this._lastProjXFormStateId = node.projXFormState._stateId; } /*---------------------------------------------------------------------------------------------------------- * clip *--------------------------------------------------------------------------------------------------------*/ if (node.clipState && (node.clipState._stateId != this._lastClipStateId || nodeFlagsState._stateId != this._lastFlagsStateId)) { // Flags can enable/disable clip /* Load clip planes for draw and pick */ var clips = node.clipState.clips; for (k = 0,len = clips.length; k < len; k++) { this.createCall( (function() { var context = self._context; var program = self._program; var clip = clips[k]; var flags = nodeFlagsState.flags; var uClipModeLocation = program.getUniformLocation("SCENEJS_uClipMode" + k); var uClipNormalAndDist = program.getUniformLocation("SCENEJS_uClipNormalAndDist" + k); return function() { if (uClipModeLocation && uClipNormalAndDist) { if (flags.clipping === false) { // Flags disable/enable clipping context.uniform1f(uClipModeLocation, 0); } else if (clip.mode == "inside") { context.uniform1f(uClipModeLocation, 2); } else if (clip.mode == "outside") { context.uniform1f(uClipModeLocation, 1); } else { // disabled context.uniform1f(uClipModeLocation, 0); } context.uniform4fv(uClipNormalAndDist, clip.normalAndDist); } }; } )()); } this._lastClipStateId = node.clipState._stateId; } /*---------------------------------------------------------------------------------------------------------- * colortrans *--------------------------------------------------------------------------------------------------------*/ if (!this._picking && !this._rayPicking) { if (node.colortransState && node.colortransState.core && (node.colortransState._stateId != this._lastColortransStateId || nodeFlagsState._stateId != this._lastFlagsStateId)) { // Flags can enable/disable colortrans this.createCall( this._callFuncs["colortrans"] || (this._callFuncs["colortrans"] = (function() { var program = self._program; var flags = nodeFlagsState.flags; var core = node.colortransState.core; return function() { if (flags.colortrans === false) { program.setUniform("SCENEJS_uColorTransMode", 0); // Disable } else { var scale = core.scale; var add = core.add; program.setUniform("SCENEJS_uColorTransMode", 1); // Enable program.setUniform("SCENEJS_uColorTransScale", [scale.r, scale.g, scale.b, scale.a]); // Scale program.setUniform("SCENEJS_uColorTransAdd", [add.r, add.g, add.b, add.a]); // Scale program.setUniform("SCENEJS_uColorTransSaturation", core.saturation); // Saturation } }; })())); this._lastColortransStateId = node.colortransState._stateId; } } /*---------------------------------------------------------------------------------------------------------- * material *--------------------------------------------------------------------------------------------------------*/ if (!this._picking) { if (node.materialState && node.materialState != this._lastMaterialStateId) { this.createCall( this._callFuncs["material"] || (this._callFuncs["material"] = (function() { var program = self._program; var context = self._context; var uMaterialBaseColorLocation = program.getUniformLocation("SCENEJS_uMaterialBaseColor"); var uMaterialSpecularColorLocation = program.getUniformLocation("SCENEJS_uMaterialSpecularColor"); var uMaterialSpecularLocation = program.getUniformLocation("SCENEJS_uMaterialSpecular"); var uMaterialShineLocation = program.getUniformLocation("SCENEJS_uMaterialShine"); var uMaterialEmitLocation = program.getUniformLocation("SCENEJS_uMaterialEmit"); var uMaterialAlphaLocation = program.getUniformLocation("SCENEJS_uMaterialAlpha"); return function() { var material = node.materialState.material; if (uMaterialBaseColorLocation) { context.uniform3fv(uMaterialBaseColorLocation, material.baseColor); } if (uMaterialSpecularColorLocation) { context.uniform3fv(uMaterialSpecularColorLocation, material.specularColor); } if (uMaterialSpecularLocation) { context.uniform1f(uMaterialSpecularLocation, material.specular); } if (uMaterialShineLocation) { context.uniform1f(uMaterialShineLocation, material.shine); } if (uMaterialEmitLocation) { context.uniform1f(uMaterialEmitLocation, material.emit); } if (uMaterialAlphaLocation) { context.uniform1f(uMaterialAlphaLocation, material.alpha); } }; })())); this._lastMaterialStateId = node.materialState._stateId; } } /*---------------------------------------------------------------------------------------------------------- * lights *--------------------------------------------------------------------------------------------------------*/ if (!this._picking) { if (node.lightState && node.lightState._stateId != this._lastLightStateId) { /* Load lights for draw */ var lights = node.lightState.lights; for (k = 0,len = lights.length; k < len; k++) { this.createCall( this._callFuncs["light" + k] || (this._callFuncs["light" + k] = (function() { var program = self._program; var context = self._context; var uLightColorLocation = program.getUniformLocation("SCENEJS_uLightColor" + k); var uLightDirLocation = program.getUniformLocation("SCENEJS_uLightDir" + k); var uLightPosLocation = program.getUniformLocation("SCENEJS_uLightPos" + k); var uLightCutOffLocation = program.getUniformLocation("SCENEJS_uLightCutOff" + k); var uLightSpotExpLocation = program.getUniformLocation("SCENEJS_uLightSpotExpOff" + k); var uLightAttenuationLocation = program.getUniformLocation("SCENEJS_uLightAttenuation" + k); var light = lights[k]; switch (light.mode) { case "ambient": return function() { if (uLightColorLocation) { context.uniform3fv(uLightColorLocation, light.color); } }; case "dir": return function() { if (uLightColorLocation) { context.uniform3fv(uLightColorLocation, light.color); } if (uLightDirLocation) { context.uniform3fv(uLightDirLocation, light.worldDir); } }; case "point": return function() { if (uLightColorLocation) { context.uniform3fv(uLightColorLocation, light.color); } if (uLightPosLocation) { context.uniform3fv(uLightPosLocation, light.worldPos); } }; case "spot": return function() { // if (uLightColorLocation) { // context.uniform3fv(uLightColorLocation, light.color); // } // if (uLightPosLocation) { // context.uniform3fv(uLightPosLocation, light.worldPos); // } // if (uLightDirLocation) { // context.uniform3fv(uLightDirLocation, light.worldDir); // } // if (uLightCutOffLocation) { // // } // if (uLightSpotPosExp) { // // } // context.uniform3fv(uLightAttenuationLocation, // [ // light.constantAttenuation, // light.linearAttenuation, // light.quadraticAttenuation // ]); }; } })())); } this._lastLightStateId = node.lightState._stateId; } } /*---------------------------------------------------------------------------------------------------------- * name *--------------------------------------------------------------------------------------------------------*/ if (this._picking) { if (! this._lastNameState || node.nameState._stateId != this._lastNameState._stateId) { this.createCall( this._callFuncs["name"] || (this._callFuncs["name"] = (function() { var program = self._program; var context = self._context; var callCtx = self._callCtx; var nameState = node.nameState; var uPickColorLocation = program.getUniformLocation("SCENEJS_uPickColor"); return function() { if (callCtx.picking) { // Colour-pick pass - feed name's colour into pick shader if (nameState.name) { self.pickNameStates[self._pickIndex++] = nameState; var b = self._pickIndex >> 16 & 0xFF; var g = self._pickIndex >> 8 & 0xFF; var r = self._pickIndex & 0xFF; context.uniform3fv(uPickColorLocation, [r / 255, g / 255, b / 255]); } } }; })())); this._lastNameState = node.nameState; } } /*-------------------------------------------------------------------------------------------------------------- * Render listeners *------------------------------------------------------------------------------------------------------------*/ if (!this._picking) { // TODO: do we ever want matrices during a pick pass? if (! this._lastRenderListenersState || node.renderListenersState._stateId != this._lastRenderListenersState._stateId) { this.createCall( this._callFuncs["rendered"] || (this._callFuncs["rendered"] = (function() { var listeners = node.renderListenersState.listeners; var queryFacade = self._queryFacade; return function() { for (var i = listeners.length - 1; i >= 0; i--) { listeners[i](queryFacade); // Call listener with query facade object as scope } }; })())); this._lastRenderListenersState = node.renderListenersState; } } /*---------------------------------------------------------------------------------------------------------- * flags * * Set these last because we change certain other states when we determine that flags will change *--------------------------------------------------------------------------------------------------------*/ if (! this._lastFlagsState || nodeFlagsState._stateId != this._lastFlagsState._stateId) { this.createCall( this._callFuncs["flags"] || (this._callFuncs["flags"] = (function() { var context = self._context; var program = self._program; var callCtx = self._callCtx; var uBackfaceTexturingLocation = program.getUniformLocation("SCENEJS_uBackfaceTexturing"); var uBackfaceLightingLocation = program.getUniformLocation("SCENEJS_uBackfaceLighting"); var uSpecularLightingLocation = program.getUniformLocation("SCENEJS_uSpecularLighting"); var flagsState = nodeFlagsState; return function() { var newFlags = flagsState.flags; var oldFlagsState = callCtx.lastFlagsState; var oldFlags = oldFlagsState ? oldFlagsState.flags : null; // var clear = newFlags.clear; // if (!oldFlags || (clear && (clear.depth != oldFlags.depth || clear.stencil != oldFlags.stencil))) { // var clear = newFlags.clear || {}; // var mask = 0; // if (clear.depth) { // mask |= gl.DEPTH_BUFFER_BIT; // } // if (clear.stencil) { // mask |= gl.STENCIL_BUFFER_BIT; // } // if (mask != 0) { alert("clear"); // gl.clear(mask); // } // } if (!oldFlags || newFlags.backfaces != oldFlags.backfaces) { if (newFlags.backfaces) { context.disable(context.CULL_FACE); } else { context.enable(context.CULL_FACE); } } if (!oldFlags || newFlags.frontface != oldFlags.frontface) { if (newFlags.frontface == "cw") { context.frontFace(context.CW); } else { context.frontFace(context.CCW); } } if (!oldFlags || newFlags.blendFunc != oldFlags.blendFunc) { if (newFlags.blendFunc) { context.blendFunc(glEnum(context, newFlags.blendFunc.sfactor || "one"), glEnum(context, newFlags.blendFunc.dfactor || "zero")); } else { context.blendFunc(context.SRC_ALPHA, context.ONE_MINUS_SRC_ALPHA); // Not redundant, because of inequality test above } } context.uniform1i(uBackfaceTexturingLocation, newFlags.backfaceTexturing == undefined ? true : !!newFlags.backfaceTexturing); context.uniform1i(uBackfaceLightingLocation, newFlags.backfaceLighting == undefined ? true : !!newFlags.backfaceLighting); context.uniform1i(uSpecularLightingLocation, newFlags.specular == undefined ? true : !!newFlags.specular); // var mask = newFlags.colorMask; // // if (!oldFlags || (mask && (mask.r != oldFlags.r || mask.g != oldFlags.g || mask.b != oldFlags.b || mask.a != oldFlags.a))) { // // if (mask) { // // context.colorMask(mask.r, mask.g, mask.b, mask.a); // // } else { // context.colorMask(true, true, true, true); // } // } }; })())); this._lastFlagsState = nodeFlagsState; } /*---------------------------------------------------------------------------------------------------------- * Draw *--------------------------------------------------------------------------------------------------------*/ this.createCall( this._callFuncs["draw"] || (this._callFuncs["draw"] = (function() { var context = self._context; var primitive = nodeGeoState.geo.primitive; var numItems = nodeGeoState.geo.indexBuf.numItems; return function() { context.drawElements(primitive, numItems, context.UNSIGNED_SHORT, 0); }; })())); }; var glEnum = function(context, name) { if (!name) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Null SceneJS.renderer node config: \"" + name + "\""); } var result = SceneJS_webgl_enumMap[name]; if (!result) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Unrecognised SceneJS.renderer node config value: \"" + name + "\""); } var value = context[result]; if (!value) { throw SceneJS_errorModule.fatalError( SceneJS.errors.WEBGL_UNSUPPORTED_NODE_CONFIG, "This browser's WebGL does not support renderer node config value: \"" + name + "\""); } return value; }; /** * Called after all nodes rendered for the current frame */ this.cleanup = function() { this._context.flush(); var callCtx = this._callCtx; this._lastRendererState = null; // Forget last "renderer" state if (callCtx.lastRendererProps) { // Forget last call-time renderer properties callCtx.lastRendererProps.restoreProps(this._context); } this._lastFlagsState = null; this._lastFrameBufState = null; this._lastNameState = null; if (callCtx.lastFrameBuf) { callCtx.lastFrameBuf.unbind(); callCtx.lastFrameBuf = null; } this._lastProgramId = -1; this._program = null; // if (this._program) { // this._program.unbind(); // // } // this._context.colorMask(true, true, true, true); }; }; new (function() { var canvas; // Currently active canvas var idStack = []; var propStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function(params) { stackLen = 0; dirty = true; canvas = params.canvas; stackLen = 0; var props = createProps({ // Dont set props - just define for restoring to on props pop clear: { depth : true, color : true }, // clearColor: {r: 0, g : 0, b : 0 }, clearDepth: 1.0, enableDepthTest:true, enableCullFace: false, frontFace: "ccw", cullFace: "back", depthFunc: "less", depthRange: { zNear: 0, zFar: 1 }, enableScissorTest: false, viewport:{ x : 1, y : 1, width: canvas.canvas.width, height: canvas.canvas.height }, wireframe: false, highlight: false, enableClip: undefined, enableBlend: false, blendFunc: { sfactor: "srcAlpha", dfactor: "one" } }); // Not sure if needed: setProperties(canvas.context, props.props); pushProps("__scenejs_default_props", props); }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setRenderer(idStack[stackLen - 1], propStack[stackLen - 1]); } else { SceneJS_DrawList.setRenderer(); } dirty = false; } }); function createProps(props) { var restore; if (stackLen > 0) { // can't restore when no previous props set restore = {}; for (var name in props) { if (props.hasOwnProperty(name)) { if (!(props[name] == undefined)) { restore[name] = getSuperProperty(name); } } } } props = processProps(props); return { props: props, setProps: function(context) { setProperties(context, props); }, restoreProps : function(context) { if (restore) { restoreProperties(context, restore); } } }; } var getSuperProperty = function(name) { var props; var prop; for (var i = stackLen - 1; i >= 0; i--) { props = propStack[i].props; prop = props[name]; if (prop != undefined && prop != null) { return props[name]; } } return null; // Cause default to be set }; function processProps(props) { var prop; for (var name in props) { if (props.hasOwnProperty(name)) { prop = props[name]; if (prop != undefined && prop != null) { if (glModeSetters[name]) { props[name] = glModeSetters[name](null, prop); } else if (glStateSetters[name]) { props[name] = glStateSetters[name](null, prop); } } } } return props; } var setProperties = function(context, props) { for (var key in props) { // Set order-insensitive properties (modes) if (props.hasOwnProperty(key)) { var setter = glModeSetters[key]; if (setter) { setter(context, props[key]); } } } if (props.viewport) { // Set order-sensitive properties (states) glStateSetters.viewport(context, props.viewport); } if (props.scissor) { glStateSetters.clear(context, props.scissor); } if (props.clear) { glStateSetters.clear(context, props.clear); } }; /** * Restores previous renderer properties, except for clear - that's the reason we * have a seperate set and restore semantic - we don't want to keep clearing the buffer. */ var restoreProperties = function(context, props) { var value; for (var key in props) { // Set order-insensitive properties (modes) if (props.hasOwnProperty(key)) { value = props[key]; if (value != undefined && value != null) { var setter = glModeSetters[key]; if (setter) { setter(context, value); } } } } if (props.viewport) { // Set order-sensitive properties (states) glStateSetters.viewport(context, props.viewport); } if (props.scissor) { glStateSetters.clear(context, props.scissor); } }; function pushProps(id, props) { idStack[stackLen] = id; propStack[stackLen] = props; stackLen++; dirty = true; } /** * Maps renderer node properties to WebGL context enums * @private */ var glEnum = function(context, name) { if (!name) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Null SceneJS.renderer node config: \"" + name + "\""); } var result = SceneJS_webgl_enumMap[name]; if (!result) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Unrecognised SceneJS.renderer node config value: \"" + name + "\""); } var value = context[result]; if (!value) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "This browser's WebGL does not support renderer node config value: \"" + name + "\""); } return value; }; /** * Order-insensitive functions that set WebGL modes ie. not actually causing an * immediate change. * * These map to renderer properties and are called in whatever order their * property is found on the renderer config. * * Each of these wrap a state-setter function on the WebGL context. Each function * also uses the glEnum map to convert its renderer node property argument to the * WebGL enum constant required by its wrapped function. * * When called with undefined/null context, will condition and return the value given * ie. set it to default if value is undefined. When called with a context, will * set the value on the context using the wrapped function. * * @private */ var glModeSetters = { enableBlend: function(context, flag) { if (!context) { if (flag == null || flag == undefined) { flag = false; } return flag; } if (flag) { context.enable(context.BLEND); } else { context.disable(context.BLEND); } }, blendColor: function(context, color) { if (!context) { color = color || {}; return { r: color.r || 0, g: color.g || 0, b: color.b || 0, a: (color.a == undefined || color.a == null) ? 1 : color.a }; } context.blendColor(color.r, color.g, color.b, color.a); }, blendEquation: function(context, eqn) { if (!context) { return eqn || "funcAdd"; } context.blendEquation(context, glEnum(context, eqn)); }, /** Sets the RGB blend equation and the alpha blend equation separately */ blendEquationSeparate: function(context, eqn) { if (!context) { eqn = eqn || {}; return { rgb : eqn.rgb || "funcAdd", alpha : eqn.alpha || "funcAdd" }; } context.blendEquation(glEnum(context, eqn.rgb), glEnum(context, eqn.alpha)); }, blendFunc: function(context, funcs) { if (!context) { funcs = funcs || {}; return { sfactor : funcs.sfactor || "srcAlpha", dfactor : funcs.dfactor || "oneMinusSrcAlpha" }; } context.blendFunc(glEnum(context, funcs.sfactor || "srcAlpha"), glEnum(context, funcs.dfactor || "oneMinusSrcAlpha")); }, blendFuncSeparate: function(context, func) { if (!context) { func = func || {}; return { srcRGB : func.srcRGB || "zero", dstRGB : func.dstRGB || "zero", srcAlpha : func.srcAlpha || "zero", dstAlpha : func.dstAlpha || "zero" }; } context.blendFuncSeparate( glEnum(context, func.srcRGB || "zero"), glEnum(context, func.dstRGB || "zero"), glEnum(context, func.srcAlpha || "zero"), glEnum(context, func.dstAlpha || "zero")); }, clearColor: function(context, color) { if (!context) { color = color || {}; return { r : color.r || 0, g : color.g || 0, b : color.b || 0, a : (color.a == undefined || color.a == null) ? 1 : color.a }; } context.clearColor(color.r, color.g, color.b, color.a); }, clearDepth: function(context, depth) { if (!context) { return (depth == null || depth == undefined) ? 1 : depth; } context.clearDepth(depth); }, clearStencil: function(context, clearValue) { if (!context) { return clearValue || 0; } context.clearStencil(clearValue); }, colorMask: function(context, color) { if (!context) { color = color || {}; return { r : color.r || 0, g : color.g || 0, b : color.b || 0, a : (color.a == undefined || color.a == null) ? 1 : color.a }; } context.colorMask(color.r, color.g, color.b, color.a); }, enableCullFace: function(context, flag) { if (!context) { return flag; } if (flag) { context.enable(context.CULL_FACE); } else { context.disable(context.CULL_FACE); } }, cullFace: function(context, mode) { if (!context) { return mode || "back"; } context.cullFace(glEnum(context, mode)); }, enableDepthTest: function(context, flag) { if (!context) { if (flag == null || flag == undefined) { flag = true; } return flag; } if (flag) { context.enable(context.DEPTH_TEST); } else { context.disable(context.DEPTH_TEST); } }, depthFunc: function(context, func) { if (!context) { return func || "less"; } context.depthFunc(glEnum(context, func)); }, enableDepthMask: function(context, flag) { if (!context) { if (flag == null || flag == undefined) { flag = true; } return flag; } context.depthMask(flag); }, depthRange: function(context, range) { if (!context) { range = range || {}; return { zNear : (range.zNear == undefined || range.zNear == null) ? 0 : range.zNear, zFar : (range.zFar == undefined || range.zFar == null) ? 1 : range.zFar }; } context.depthRange(range.zNear, range.zFar); } , frontFace: function(context, mode) { if (!context) { return mode || "ccw"; } context.frontFace(glEnum(context, mode)); }, lineWidth: function(context, width) { if (!context) { return width || 1; } context.lineWidth(width); }, enableScissorTest: function(context, flag) { if (!context) { return flag; } if (flag) { context.enable(context.SCISSOR_TEST); } else { flag = false; context.disable(context.SCISSOR_TEST); } } }; /** * Order-sensitive functions that immediately effect WebGL state change. * * These map to renderer properties and are called in a particular order since they * affect one another. * * Each of these wrap a state-setter function on the WebGL context. Each function * also uses the glEnum map to convert its renderer node property argument to the * WebGL enum constant required by its wrapped function. * * @private */ var glStateSetters = { /** Set viewport on the given context */ viewport: function(context, v) { if (!context) { v = v || {}; return { x : v.x || 1, y : v.y || 1, width: v.width || canvas.canvas.width, height: v.height || canvas.canvas.height }; } context.viewport(v.x, v.y, v.width, v.height); }, /** Sets scissor region on the given context */ scissor: function(context, s) { if (!context) { s = s || {}; return { x : s.x || 0, y : s.y || 0, width: s.width || 1.0, height: s.height || 1.0 }; } context.scissor(s.x, s.y, s.width, s.height); }, /** Clears buffers on the given context as specified in mask */ clear:function(context, mask) { if (!context) { mask = mask || {}; return mask; } var m; if (mask.color) { m = context.COLOR_BUFFER_BIT; } if (mask.depth) { m = m | context.DEPTH_BUFFER_BIT; } if (mask.stencil) { m = m | context.STENCIL_BUFFER_BIT; } if (m) { context.clear(m); } } }; function popProps() { var oldProps = propStack[stackLen - 1]; stackLen--; var newProps = propStack[stackLen - 1]; dirty = true; } var Renderer = SceneJS.createNodeType("renderer"); Renderer.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node defines the resource for (var key in params) { if (params.hasOwnProperty(key)) { this.core[key] = params[key]; } } } }; Renderer.prototype.setViewport = function(viewport) { this.core.viewport = viewport ? { x : viewport.x || 1, y : viewport.y || 1, width: viewport.width || 1000, height: viewport.height || 1000 } : undefined; }; Renderer.prototype.getViewport = function() { return this.core.viewport ? { x : this.core.viewport.x, y : this.core.viewport.y, width: this.core.viewport.width, height: this.core.viewport.height } : undefined; }; Renderer.prototype.setScissor = function(scissor) { this.core.scissor = scissor ? { x : scissor.x || 1, y : scissor.y || 1, width: scissor.width || 1000, height: scissor.height || 1000 } : undefined; }; Renderer.prototype.getScissor = function() { return this.core.scissor ? { x : this.core.scissor.x, y : this.core.scissor.y, width: this.core.scissor.width, height: this.core.scissor.height } : undefined; }; Renderer.prototype.setClear = function(clear) { this.core.clear = clear ? { r : clear.r || 0, g : clear.g || 0, b : clear.b || 0 } : undefined; }; Renderer.prototype.getClear = function() { return this.core.clear ? { r : this.core.clear.r, g : this.core.clear.g, b : this.core.clear.b } : null; }; Renderer.prototype.setEnableBlend = function(enableBlend) { this.core.enableBlend = enableBlend; }; Renderer.prototype.getEnableBlend = function() { return this.core.enableBlend; }; Renderer.prototype.setBlendColor = function(color) { this.core.blendColor = color ? { r : color.r || 0, g : color.g || 0, b : color.b || 0, a : (color.a == undefined || color.a == null) ? 1 : color.a } : undefined; }; Renderer.prototype.getBlendColor = function() { return this.core.blendColor ? { r : this.core.blendColor.r, g : this.core.blendColor.g, b : this.core.blendColor.b, a : this.core.blendColor.a } : undefined; }; Renderer.prototype.setBlendEquation = function(eqn) { this.core.blendEquation = eqn; }; Renderer.prototype.getBlendEquation = function() { return this.core.blendEquation; }; Renderer.prototype.setBlendEquationSeparate = function(eqn) { this.core.blendEquationSeparate = eqn ? { rgb : eqn.rgb || "funcAdd", alpha : eqn.alpha || "funcAdd" } : undefined; }; Renderer.prototype.getBlendEquationSeparate = function() { return this.core.blendEquationSeparate ? { rgb : this.core.rgb, alpha : this.core.alpha } : undefined; }; Renderer.prototype.setBlendFunc = function(funcs) { this.core.blendFunc = funcs ? { sfactor : funcs.sfactor || "srcAlpha", dfactor : funcs.dfactor || "one" } : undefined; }; Renderer.prototype.getBlendFunc = function() { return this.core.blendFunc ? { sfactor : this.core.sfactor, dfactor : this.core.dfactor } : undefined; }; Renderer.prototype.setBlendFuncSeparate = function(eqn) { this.core.blendFuncSeparate = eqn ? { srcRGB : eqn.srcRGB || "zero", dstRGB : eqn.dstRGB || "zero", srcAlpha : eqn.srcAlpha || "zero", dstAlpha : eqn.dstAlpha || "zero" } : undefined; }; Renderer.prototype.getBlendFuncSeparate = function() { return this.core.blendFuncSeparate ? { srcRGB : this.core.blendFuncSeparate.srcRGB, dstRGB : this.core.blendFuncSeparate.dstRGB, srcAlpha : this.core.blendFuncSeparate.srcAlpha, dstAlpha : this.core.blendFuncSeparate.dstAlpha } : undefined; }; Renderer.prototype.setEnableCullFace = function(enableCullFace) { this.core.enableCullFace = enableCullFace; }; Renderer.prototype.getEnableCullFace = function() { return this.core.enableCullFace; }; Renderer.prototype.setCullFace = function(cullFace) { this.core.cullFace = cullFace; }; Renderer.prototype.getCullFace = function() { return this.core.cullFace; }; Renderer.prototype.setEnableDepthTest = function(enableDepthTest) { this.core.enableDepthTest = enableDepthTest; }; Renderer.prototype.getEnableDepthTest = function() { return this.core.enableDepthTest; }; Renderer.prototype.setDepthFunc = function(depthFunc) { this.core.depthFunc = depthFunc; }; Renderer.prototype.getDepthFunc = function() { return this.core.depthFunc; }; Renderer.prototype.setEnableDepthMask = function(enableDepthMask) { this.core.enableDepthMask = enableDepthMask; }; Renderer.prototype.getEnableDepthMask = function() { return this.core.enableDepthMask; }; Renderer.prototype.setClearDepth = function(clearDepth) { this.core.clearDepth = clearDepth; }; Renderer.prototype.getClearDepth = function() { return this.core.clearDepth; }; Renderer.prototype.setDepthRange = function(range) { this.core.depthRange = range ? { zNear : (range.zNear == undefined || range.zNear == null) ? 0 : range.zNear, zFar : (range.zFar == undefined || range.zFar == null) ? 1 : range.zFar } : undefined; }; Renderer.prototype.getDepthRange = function() { return this.core.depthRange ? { zNear : this.core.depthRange.zNear, zFar : this.core.depthRange.zFar } : undefined; }; Renderer.prototype.setFrontFace = function(frontFace) { this.core.frontFace = frontFace; }; Renderer.prototype.getFrontFace = function() { return this.core.frontFace; }; Renderer.prototype.setLineWidth = function(lineWidth) { this.core.lineWidth = lineWidth; }; Renderer.prototype.getLineWidth = function() { return this.core.lineWidth; }; Renderer.prototype.setEnableScissorTest = function(enableScissorTest) { this.core.enableScissorTest = enableScissorTest; }; Renderer.prototype.getEnableScissorTest = function() { return this.core.enableScissorTest; }; Renderer.prototype.setClearStencil = function(clearStencil) { this.core.clearStencil = clearStencil; }; Renderer.prototype.getClearStencil = function() { return this.core.clearStencil; }; Renderer.prototype.setColorMask = function(color) { this.core.colorMask = color ? { r : color.r || 0, g : color.g || 0, b : color.b || 0, a : (color.a == undefined || color.a == null) ? 1 : color.a } : undefined; }; Renderer.prototype.getColorMask = function() { return this.core.colorMask ? { r : this.core.colorMask.r, g : this.core.colorMask.g, b : this.core.colorMask.b, a : this.core.colorMask.a } : undefined; }; Renderer.prototype.setWireframe = function(wireframe) { this.core.wireframe = wireframe; }; Renderer.prototype.getWireframe = function() { return this.core.wireframe; }; Renderer.prototype.setHighlight = function(highlight) { this.core.highlight = highlight; }; Renderer.prototype.getHighlight = function() { return this.core.highlight; }; Renderer.prototype.setEnableClip = function(enableClip) { this.core.enableClip = enableClip; }; Renderer.prototype.getEnableClip = function() { return this.core.enableClip; }; Renderer.prototype.setEnableFog = function(enableFog) { this.core.enableFog = enableFog; }; Renderer.prototype.getEnableFog = function() { return this.core.enableFog; }; Renderer.prototype._compile = function() { this._preCompile(); this._compileNodes(); this._postCompile(); }; Renderer.prototype._preCompile = function() { if (this._compileMemoLevel == 0) { this._props = createProps(this.core); this._compileMemoLevel = 1; } pushProps(this.core.id, this._props); }; Renderer.prototype._postCompile = function() { popProps(); }; })(); /** * Backend that manages scene flags. These are pushed and popped by "flags" nodes * to enable/disable features for the subgraph. An important point to note about these * is that they never trigger the generation of new GLSL shaders - flags are designed * to switch things on/of with minimal overhead. * */ (function() { var idStack = []; var flagStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); /* Export flags when renderer needs them - only when current set not exported (dirty) */ SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function(params) { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setFlags(idStack[stackLen - 1], flagStack[stackLen - 1]); } else { SceneJS_DrawList.setFlags(); } dirty = false; } }); var Flags = SceneJS.createNodeType("flags"); Flags.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node defines the resource if (!params.flags) { throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_CONFIG_EXPECTED, "flags node 'flags' attribute missing "); } this.core.flags = {}; this.setFlags(params.flags); } }; Flags.prototype.setFlags = function(flags) { SceneJS._apply(flags, this.core.flags, true); // Node's flags object is shared with drawlist node }; Flags.prototype.addFlags = function(flags) { SceneJS._apply(flags, this.core.flags); }; Flags.prototype.getFlags = function() { return SceneJS._shallowClone(this.core.flags); }; Flags.prototype._compile = function() { idStack[stackLen] = this.attr.id; flagStack[stackLen] = this.core.flags; stackLen++; dirty = true; this._compileNodes(); stackLen--; dirty = true; }; })(); (function() { var idStack = []; var tagStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function(params) { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setTag(idStack[stackLen - 1], tagStack[stackLen - 1]); } else { SceneJS_DrawList.setTag(); } dirty = false; } }); var Tag = SceneJS.createNodeType("tag"); Tag.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node defines the resource if (!params.tag) { throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_CONFIG_EXPECTED, "tag node attribute missing : 'tag'"); } this.core.tag = "enabled"; this.setTag(params.tag); } }; Tag.prototype.setTag = function(tag) { var core = this.core; core.tag = tag; core.pattern = null; core.matched = false; }; Tag.prototype.getTag = function() { return this.core.tag; }; Tag.prototype._compile = function() { idStack[stackLen] = this.attr.id; tagStack[stackLen] = this.core; stackLen++; dirty = true; this._compileNodes(); stackLen--; dirty = true; }; })(); /** * Backend that tracks statistics on loading states of nodes during scene traversal. * * This supports the "loading-status" events that we can listen for on scene nodes. * * When a node with that listener is pre-visited, it will call getStatus on this module to * save a copy of the status. Then when it is post-visited, it will call diffStatus on this * module to find the status for its sub-nodes, which it then reports through the "loading-status" event. * * @private */ var SceneJS_sceneStatusModule = new (function() { this.sceneStatus = {}; var self = this; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_CREATED, function(params) { self.sceneStatus[params.sceneId] = { numLoading: 0 }; }); this.nodeLoading = function(node) { this.sceneStatus[node.scene.attr.id].numLoading++; }; this.nodeLoaded = function(node) { this.sceneStatus[node.scene.attr.id].numLoading--; }; })(); new (function() { var geoStack = []; var stackLen = 0; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_CREATED, function() { stackLen = 0; }); function destroyVBOs(geo) { if (document.getElementById(geo.canvas.canvasId)) { // Context won't exist if canvas has disappeared if (geo.vertexBuf) { geo.vertexBuf.destroy(); } if (geo.normalBuf) { geo.normalBuf.destroy(); } if (geo.indexBuf) { geo.indexBuf.destroy(); } if (geo.uvBuf) { geo.uvBuf.destroy(); } if (geo.uvBuf2) { geo.uvBuf2.destroy(); } if (geo.colorBuf) { geo.colorBuf.destroy(); } } } function getPrimitiveType(context, primitive) { switch (primitive) { case "points": return context.POINTS; case "lines": return context.LINES; case "line-loop": return context.LINE_LOOP; case "line-strip": return context.LINE_STRIP; case "triangles": return context.TRIANGLES; case "triangle-strip": return context.TRIANGLE_STRIP; case "triangle-fan": return context.TRIANGLE_FAN; default: throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "SceneJS.geometry primitive unsupported: '" + primitive + "' - supported types are: 'points', 'lines', 'line-loop', " + "'line-strip', 'triangles', 'triangle-strip' and 'triangle-fan'"); } } function createGeometry(scene, source, callback, options) { if (typeof source == "string") { /* Load from stream */ var geoService = SceneJS.Services.getService(SceneJS.Services.GEO_LOADER_SERVICE_ID); geoService.loadGeometry(source, function(data) { callback(_createGeometry(scene, data, options)); }); } else { /* Create from arrays */ var data = createTypedArrays(source, options); data.primitive = source.primitive; return _createGeometry(scene, data); } } function createTypedArrays(data, options) { return { positions: data.positions ? new Float32Array((options.scale || options.origin) ? applyOptions(data.positions, options) : data.positions) : undefined, normals: data.normals ? new Float32Array(data.normals) : undefined, uv: data.uv ? new Float32Array(data.uv) : undefined, uv2: data.uv2 ? new Float32Array(data.uv2) : undefined, colors: data.colors ? new Float32Array(data.colors) : undefined, indices: data.indices ? new Int32Array(data.indices) : undefined }; } function _createGeometry(scene, data) { var context = scene.canvas.context; if (!data.primitive) { // "points", "lines", "line-loop", "line-strip", "triangles", "triangle-strip" or "triangle-fan" throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_CONFIG_EXPECTED, "SceneJS.geometry node property expected : primitive"); } var usage = context.STATIC_DRAW; //var usage = (!data.fixed) ? context.STREAM_DRAW : context.STATIC_DRAW; var vertexBuf; var normalBuf; var uvBuf; var uvBuf2; var colorBuf; var indexBuf; try { // TODO: Modify usage flags in accordance with how often geometry is evicted if (data.positions && data.positions.length > 0) { vertexBuf = new SceneJS_webgl_ArrayBuffer(context, context.ARRAY_BUFFER, data.positions, data.positions.length, 3, usage); } if (data.normals && data.normals.length > 0) { normalBuf = new SceneJS_webgl_ArrayBuffer(context, context.ARRAY_BUFFER, data.normals, data.normals.length, 3, usage); } if (data.uv && data.uv.length > 0) { if (data.uv) { uvBuf = new SceneJS_webgl_ArrayBuffer(context, context.ARRAY_BUFFER, data.uv, data.uv.length, 2, usage); } } if (data.uv2 && data.uv2.length > 0) { if (data.uv2) { uvBuf2 = new SceneJS_webgl_ArrayBuffer(context, context.ARRAY_BUFFER, data.uv2, data.uv2.length, 2, usage); } } if (data.colors && data.colors.length > 0) { if (data.colors) { colorBuf = new SceneJS_webgl_ArrayBuffer(context, context.ARRAY_BUFFER, data.colors, data.colors.length, 4, usage); } } var primitive; if (data.indices && data.indices.length > 0) { primitive = getPrimitiveType(context, data.primitive); indexBuf = new SceneJS_webgl_ArrayBuffer(context, context.ELEMENT_ARRAY_BUFFER, new Uint16Array(data.indices), data.indices.length, 3, usage); } var geo = { fixed : true, // TODO: support dynamic geometry primitive: primitive, scene: scene, canvas : scene.canvas, context : context, vertexBuf : vertexBuf, normalBuf : normalBuf, indexBuf : indexBuf, uvBuf: uvBuf, uvBuf2: uvBuf2, colorBuf: colorBuf, arrays: data }; if (data.positions) { // geo.boundary = getBoundary(data.positions); } return geo; } catch (e) { // Allocation failure - delete whatever buffers got allocated if (vertexBuf) { vertexBuf.destroy(); } if (normalBuf) { normalBuf.destroy(); } if (uvBuf) { uvBuf.destroy(); } if (uvBuf2) { uvBuf2.destroy(); } if (colorBuf) { colorBuf.destroy(); } if (indexBuf) { indexBuf.destroy(); } throw e; } } function applyOptions(positions, options) { var positions2 = positions.slice(0); if (options.scale) { var scaleX = options.scale.x != undefined ? options.scale.x : 1.0; var scaleY = options.scale.y != undefined ? options.scale.y : 1.0; var scaleZ = options.scale.z != undefined ? options.scale.z : 1.0; for (var i = 0, len = positions2.length; i < len; i += 3) { positions2[i + 0] *= scaleX; positions2[i + 1] *= scaleY; positions2[i + 2] *= scaleZ; } } if (options.origin) { var originX = options.origin.x != undefined ? options.origin.x : 0.0; var originY = options.origin.y != undefined ? options.origin.y : 0.0; var originZ = options.origin.z != undefined ? options.origin.z : 0.0; for (var i = 0, len = positions2.length; i < len; i += 3) { positions2[i + 0] -= originX; positions2[i + 1] -= originY; positions2[i + 2] -= originZ; } } return positions2; } function destroyGeometry(geo) { destroyVBOs(geo); } function pushGeometry(id, geo) { if (!geo.vertexBuf) { /* Geometry has no vertex buffer - it must be therefore be indexing a vertex/uv buffers defined * by a higher Geometry, as part of a composite geometry: * * It must therefore inherit the vertex buffer, along with UV coord buffers. * * We'll leave it to the render state graph traversal to ensure that the * vertex and UV buffers are not needlessly rebound for this geometry. */ geo = inheritVertices(geo); } if (geo.indexBuf) { /* We don't render Geometry's that have no index buffer - they merely define * vertex/uv buffers that are indexed by sub-Geometry's in a composite geometry */ SceneJS_DrawList.setGeometry(id, geo); } geoStack[stackLen++] = geo; } function inheritVertices(geo) { var geo2 = { primitive: geo.primitive, boundary: geo.boundary, normalBuf: geo.normalBuf, uvBuf: geo.uvBuf, uvBuf2: geo.uvBuf2, colorBuf: geo.colorBuf, indexBuf: geo.indexBuf }; for (var i = stackLen - 1; i >= 0; i--) { if (geoStack[i].vertexBuf) { geo2.vertexBuf = geoStack[i].vertexBuf; geo2.boundary = geoStack[i].boundary; geo2.normalBuf = geoStack[i].normalBuf; geo2.uvBuf = geoStack[i].uvBuf; // Vertex and UVs are a package geo2.uvBuf2 = geoStack[i].uvBuf2; geo2.colorBuf = geoStack[i].colorBuf; return geo2; } } return geo2; } function popGeometry() { stackLen--; } /*---------------------------------------------------------------------------------------------------------------- * Geometry node *---------------------------------------------------------------------------------------------------------------*/ window.SceneJS_geometry = SceneJS.createNodeType("geometry"); SceneJS_geometry.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node defines the core var options = { origin : params.origin, scale: params.scale }; if (params.create instanceof Function) { /* Create using factory function * Expose the arrays on the node */ var data = params.create(); SceneJS._apply(createGeometry(this.scene, data, null, options), this.core); } else if (params.stream) { /* Load from stream * TODO: Expose the arrays on the node */ this._stream = params.stream; this.core._loading = true; var self = this; createGeometry( this.scene, this._stream, function(geo) { SceneJS._apply(geo, self.core); self.core._loading = false; SceneJS_compileModule.nodeUpdated(self, "loaded"); // Compile again to apply freshly-loaded geometry }, options); } else { /* Create from arrays * Expose the arrays on the node */ var arrays = { positions : params.positions || [], normals : params.normals || [], colors : params.colors || [], indices : params.indices || [], uv : params.uv || [], uv2 : params.uv2 || [], primitive : params.primitive || "triangles" }; SceneJS._apply(createGeometry(this.scene, arrays, null, options), this.core); } } }; SceneJS_geometry.prototype.getStream = function() { return this._stream; }; SceneJS_geometry.prototype.getPositions = function() { return this._getArrays().positions; }; SceneJS_geometry.prototype.setPositions = function(params) { this.core.vertexBuf.bind(); this.core.vertexBuf.setData(params.positions, params.offset || 0); }; SceneJS_geometry.prototype.getNormals = function() { return this._getArrays().normals; }; SceneJS_geometry.prototype.setNormals = function(params) { this.core.normalBuf.bind(); this.core.normalBuf.setData(params.normals, params.offset || 0); }; SceneJS_geometry.prototype.getColors = function() { return this._getArrays().colors; }; SceneJS_geometry.prototype.setColors = function(params) { this.core.colorBuf.bind(); this.core.colorBuf.setData(params.colors, params.offset || 0); }; SceneJS_geometry.prototype.getIndices = function() { return this._getArrays().indices; }; SceneJS_geometry.prototype.getUv = function() { return this._getArrays().uv; }; SceneJS_geometry.prototype.setUv = function(params) { this.core.uvBuf.bind(); this.core.uvBuf.setData(params.uv, params.offset || 0); }; SceneJS_geometry.prototype.getUv2 = function() { return this._getArrays().uv2; }; SceneJS_geometry.prototype.setUv2 = function(params) { this.core.uvBuf2.bind(); this.core.uvBuf2.setData(params.uv2, params.offset || 0); }; SceneJS_geometry.prototype.getPrimitive = function() { return this.attr.primitive; }; SceneJS_geometry.prototype._getArrays = function() { if (this.attr.positions) { return this.attr; } else { if (!this.core) { throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_ILLEGAL_STATE, "Invalid node state exception: geometry stream not loaded yet - can't query geometry data yet"); } return this.core.arrays; } }; SceneJS_geometry.prototype.getBoundary = function() { if (this._boundary) { return this._boundary; } var positions = this._getArrays().positions; this._boundary = { xmin : SceneJS_math_MAX_DOUBLE, ymin : SceneJS_math_MAX_DOUBLE, zmin : SceneJS_math_MAX_DOUBLE, xmax : SceneJS_math_MIN_DOUBLE, ymax : SceneJS_math_MIN_DOUBLE, zmax : SceneJS_math_MIN_DOUBLE }; var x, y, z; for (var i = 0, len = positions.length - 2; i < len; i += 3) { x = positions[i]; y = positions[i + 1]; z = positions[i + 2]; if (x < this._boundary.xmin) { this._boundary.xmin = x; } if (y < this._boundary.ymin) { this._boundary.ymin = y; } if (z < this._boundary.zmin) { this._boundary.zmin = z; } if (x > this._boundary.xmax) { this._boundary.xmax = x; } if (y > this._boundary.ymax) { this._boundary.ymax = y; } if (z > this._boundary.zmax) { this._boundary.zmax = z; } } return this._boundary; }; SceneJS_geometry.prototype._compile = function() { if (!this.core._loading) { pushGeometry(this.attr.id, this.core); } this._compileNodes(); if (!this.core._loading) { popGeometry(); } }; SceneJS_geometry.prototype._destroy = function() { if (this.core._nodeCount == 1) { // Last core user destroyGeometry(this.core); /* When destroying scene nodes, we only need to notify the rendering module * of each geometry node destruction because that will destroy the display list * node associated with the geometry, which will in turn destroy rendering * states associated with the display list node, and so on. */ SceneJS_DrawList.removeGeometry(this.scene.attr.id, this.attr.id); } }; })(); /** * @class A scene node that defines the geometry of the venerable OpenGL teapot. *

Example Usage

Definition of teapot:


 * var c = new SceneJS_teapot(); // Requires no parameters
 * 
* @extends SceneJS.Geometry * @since Version 0.7.4 * @constructor * Create a new SceneJS_teapot * @param {Object} [cfg] Static configuration object * @param {function(SceneJS.Data):Object} [fn] Dynamic configuration function * @param {...SceneJS_node} [childNodes] Child nodes */ (function() { /* Callback that does the creation when teapot not created yet * @private */ function create() { var positions = [ [-3.000000, 1.650000, 0.000000], [-2.987110, 1.650000, -0.098438], [-2.987110, 1.650000, 0.098438], [-2.985380, 1.567320, -0.049219], [-2.985380, 1.567320, 0.049219], [-2.983500, 1.483080, 0.000000], [-2.981890, 1.723470, -0.049219], [-2.981890, 1.723470, 0.049219], [-2.976560, 1.798530, 0.000000], [-2.970900, 1.486210, -0.098438], [-2.970900, 1.486210, 0.098438], [-2.963880, 1.795340, -0.098438], [-2.963880, 1.795340, 0.098438], [-2.962210, 1.570170, -0.133594], [-2.962210, 1.570170, 0.133594], [-2.958640, 1.720570, -0.133594], [-2.958640, 1.720570, 0.133594], [-2.953130, 1.650000, -0.168750], [-2.953130, 1.650000, 0.168750], [-2.952470, 1.403740, -0.049219], [-2.952470, 1.403740, 0.049219], [-2.937700, 1.494470, -0.168750], [-2.937700, 1.494470, 0.168750], [-2.935230, 1.852150, -0.049219], [-2.935230, 1.852150, 0.049219], [-2.933590, 1.320120, 0.000000], [-2.930450, 1.786930, -0.168750], [-2.930450, 1.786930, 0.168750], [-2.930370, 1.411500, -0.133594], [-2.930370, 1.411500, 0.133594], [-2.921880, 1.325530, -0.098438], [-2.921880, 1.325530, 0.098438], [-2.912780, 1.844170, -0.133594], [-2.912780, 1.844170, 0.133594], [-2.906250, 1.910160, 0.000000], [-2.894230, 1.904570, -0.098438], [-2.894230, 1.904570, 0.098438], [-2.891380, 1.579100, -0.196875], [-2.891380, 1.579100, 0.196875], [-2.890990, 1.339800, -0.168750], [-2.890990, 1.339800, 0.168750], [-2.890650, 1.712080, -0.196875], [-2.890650, 1.712080, 0.196875], [-2.883460, 1.245790, -0.048343], [-2.883460, 1.245790, 0.048343], [-2.863460, 1.257130, -0.132718], [-2.863460, 1.257130, 0.132718], [-2.862660, 1.434830, -0.196875], [-2.862660, 1.434830, 0.196875], [-2.862550, 1.889830, -0.168750], [-2.862550, 1.889830, 0.168750], [-2.850000, 1.650000, -0.225000], [-2.850000, 1.650000, 0.225000], [-2.849710, 1.161550, 0.000000], [-2.847100, 1.820820, -0.196875], [-2.847100, 1.820820, 0.196875], [-2.841940, 1.946920, -0.049219], [-2.841940, 1.946920, 0.049219], [-2.829000, 1.761400, -0.225000], [-2.829000, 1.761400, 0.225000], [-2.828670, 1.175980, -0.094933], [-2.828670, 1.175980, 0.094933], [-2.824700, 1.521940, -0.225000], [-2.824700, 1.521940, 0.225000], [-2.821150, 1.935200, -0.133594], [-2.821150, 1.935200, 0.133594], [-2.812310, 1.187190, -0.168750], [-2.812310, 1.187190, 0.168750], [-2.805010, 1.289970, -0.196875], [-2.805010, 1.289970, 0.196875], [-2.797270, 1.383110, -0.225000], [-2.797270, 1.383110, 0.225000], [-2.789060, 1.990140, 0.000000], [-2.788360, 1.699320, -0.196875], [-2.788360, 1.699320, 0.196875], [-2.778210, 1.982830, -0.098438], [-2.778210, 1.982830, 0.098438], [-2.774420, 1.527380, -0.196875], [-2.774420, 1.527380, 0.196875], [-2.773560, 1.098600, -0.084375], [-2.773560, 1.098600, 0.084375], [-2.766410, 1.845120, -0.225000], [-2.766410, 1.845120, 0.225000], [-2.760340, 1.900900, -0.196875], [-2.760340, 1.900900, 0.196875], [-2.749600, 1.963560, -0.168750], [-2.749600, 1.963560, 0.168750], [-2.748310, 1.785700, -0.196875], [-2.748310, 1.785700, 0.196875], [-2.746880, 1.650000, -0.168750], [-2.746880, 1.650000, 0.168750], [-2.731250, 1.007810, 0.000000], [-2.727560, 1.735870, -0.168750], [-2.727560, 1.735870, 0.168750], [-2.720360, 1.690830, -0.133594], [-2.720360, 1.690830, 0.133594], [-2.719480, 1.249770, -0.225000], [-2.719480, 1.249770, 0.225000], [-2.716780, 1.144680, -0.196875], [-2.716780, 1.144680, 0.196875], [-2.712890, 1.650000, -0.098438], [-2.712890, 1.650000, 0.098438], [-2.708990, 1.541770, -0.133594], [-2.708990, 1.541770, 0.133594], [-2.703540, 1.426410, -0.168750], [-2.703540, 1.426410, 0.168750], [-2.700980, 1.037840, -0.168750], [-2.700980, 1.037840, 0.168750], [-2.700000, 1.650000, 0.000000], [-2.699650, 2.010790, -0.048346], [-2.699650, 2.010790, 0.048346], [-2.697120, 1.687930, -0.049219], [-2.697120, 1.687930, 0.049219], [-2.694130, 1.727460, -0.098438], [-2.694130, 1.727460, 0.098438], [-2.686620, 1.546690, -0.049219], [-2.686620, 1.546690, 0.049219], [-2.682630, 1.762350, -0.133594], [-2.682630, 1.762350, 0.133594], [-2.681480, 1.996460, -0.132721], [-2.681480, 1.996460, 0.132721], [-2.681440, 1.724270, 0.000000], [-2.675740, 1.270850, -0.196875], [-2.675740, 1.270850, 0.196875], [-2.672650, 1.440680, -0.098438], [-2.672650, 1.440680, 0.098438], [-2.670260, 1.800400, -0.168750], [-2.670260, 1.800400, 0.168750], [-2.667800, 1.846230, -0.196875], [-2.667800, 1.846230, 0.196875], [-2.662790, 1.905100, -0.225000], [-2.662790, 1.905100, 0.225000], [-2.660940, 1.446090, 0.000000], [-2.660180, 1.754370, -0.049219], [-2.660180, 1.754370, 0.049219], [-2.638580, 1.785670, -0.098438], [-2.638580, 1.785670, 0.098438], [-2.634380, 1.103910, -0.225000], [-2.634380, 1.103910, 0.225000], [-2.630740, 1.956740, -0.196875], [-2.630740, 1.956740, 0.196875], [-2.626560, 1.780080, 0.000000], [-2.625000, 2.043750, 0.000000], [-2.624640, 1.305020, -0.132813], [-2.624640, 1.305020, 0.132813], [-2.606420, 1.317450, -0.048438], [-2.606420, 1.317450, 0.048438], [-2.606320, 2.026440, -0.094945], [-2.606320, 2.026440, 0.094945], [-2.591800, 2.012990, -0.168750], [-2.591800, 2.012990, 0.168750], [-2.571730, 1.834290, -0.168750], [-2.571730, 1.834290, 0.168750], [-2.567770, 1.169970, -0.168750], [-2.567770, 1.169970, 0.168750], [-2.554600, 1.183040, -0.095315], [-2.554600, 1.183040, 0.095315], [-2.549750, 1.890590, -0.196875], [-2.549750, 1.890590, 0.196875], [-2.549540, 0.878984, -0.084375], [-2.549540, 0.878984, 0.084375], [-2.546430, 1.831970, -0.132721], [-2.546430, 1.831970, 0.132721], [-2.537500, 1.200000, 0.000000], [-2.527210, 1.819200, -0.048346], [-2.527210, 1.819200, 0.048346], [-2.518750, 1.945310, -0.225000], [-2.518750, 1.945310, 0.225000], [-2.516830, 0.932671, -0.196875], [-2.516830, 0.932671, 0.196875], [-2.471840, 1.006490, -0.196875], [-2.471840, 1.006490, 0.196875], [-2.445700, 1.877640, -0.168750], [-2.445700, 1.877640, 0.168750], [-2.439130, 1.060180, -0.084375], [-2.439130, 1.060180, 0.084375], [-2.431180, 1.864180, -0.094945], [-2.431180, 1.864180, 0.094945], [-2.412500, 1.846870, 0.000000], [-2.388280, 0.716602, 0.000000], [-2.382250, 0.737663, -0.095854], [-2.382250, 0.737663, 0.095854], [-2.378840, 2.052020, -0.084375], [-2.378840, 2.052020, 0.084375], [-2.377660, 0.753680, -0.168750], [-2.377660, 0.753680, 0.168750], [-2.364750, 0.798761, -0.199836], [-2.364750, 0.798761, 0.199836], [-2.354300, 0.835254, -0.225000], [-2.354300, 0.835254, 0.225000], [-2.343840, 0.871747, -0.199836], [-2.343840, 0.871747, 0.199836], [-2.341150, 1.999720, -0.196875], [-2.341150, 1.999720, 0.196875], [-2.330930, 0.916827, -0.168750], [-2.330930, 0.916827, 0.168750], [-2.320310, 0.953906, 0.000000], [-2.289320, 1.927820, -0.196875], [-2.289320, 1.927820, 0.196875], [-2.251620, 1.875520, -0.084375], [-2.251620, 1.875520, 0.084375], [-2.247410, 0.882285, -0.084375], [-2.247410, 0.882285, 0.084375], [-2.173630, 0.844043, 0.000000], [-2.168530, 0.826951, -0.097184], [-2.168530, 0.826951, 0.097184], [-2.164770, 0.814364, -0.168750], [-2.164770, 0.814364, 0.168750], [-2.156880, 0.786694, -0.187068], [-2.156880, 0.786694, 0.187068], [-2.156250, 2.092970, 0.000000], [-2.154120, 0.740520, -0.215193], [-2.154120, 0.740520, 0.215193], [-2.150170, 0.694734, -0.215193], [-2.150170, 0.694734, 0.215193], [-2.147420, 0.648560, -0.187068], [-2.147420, 0.648560, 0.187068], [-2.144960, 0.612777, -0.132948], [-2.144960, 0.612777, 0.132948], [-2.143710, 0.591789, -0.048573], [-2.143710, 0.591789, 0.048573], [-2.142330, 2.058360, -0.168750], [-2.142330, 2.058360, 0.168750], [-2.111720, 1.982230, -0.225000], [-2.111720, 1.982230, 0.225000], [-2.084470, 0.789526, -0.048905], [-2.084470, 0.789526, 0.048905], [-2.081100, 1.906090, -0.168750], [-2.081100, 1.906090, 0.168750], [-2.078340, 0.770387, -0.133280], [-2.078340, 0.770387, 0.133280], [-2.067190, 1.871480, 0.000000], [-2.000000, 0.750000, 0.000000], [-1.995700, 0.737109, -0.098438], [-1.995700, 0.737109, 0.098438], [-1.984380, 0.703125, -0.168750], [-1.984380, 0.703125, 0.168750], [-1.978520, 0.591650, 0.000000], [-1.969370, 0.670825, -0.202656], [-1.969370, 0.670825, 0.202656], [-1.968360, 0.655078, -0.210938], [-1.968360, 0.655078, 0.210938], [-1.960000, 0.750000, -0.407500], [-1.960000, 0.750000, 0.407500], [-1.958730, 0.925195, -0.201561], [-1.958730, 0.925195, 0.201561], [-1.957030, 1.100390, 0.000000], [-1.950000, 0.600000, -0.225000], [-1.950000, 0.600000, 0.225000], [-1.938950, 0.591650, -0.403123], [-1.938950, 0.591650, 0.403123], [-1.931640, 0.544922, -0.210938], [-1.931640, 0.544922, 0.210938], [-1.930690, 0.522583, -0.198676], [-1.930690, 0.522583, 0.198676], [-1.921880, 0.453516, 0.000000], [-1.917890, 1.100390, -0.398745], [-1.917890, 1.100390, 0.398745], [-1.915620, 0.496875, -0.168750], [-1.915620, 0.496875, 0.168750], [-1.904300, 0.462891, -0.098438], [-1.904300, 0.462891, 0.098438], [-1.900000, 0.450000, 0.000000], [-1.892280, 0.670825, -0.593047], [-1.892280, 0.670825, 0.593047], [-1.883440, 0.453516, -0.391582], [-1.883440, 0.453516, 0.391582], [-1.882060, 0.925195, -0.589845], [-1.882060, 0.925195, 0.589845], [-1.881390, 1.286130, -0.193602], [-1.881390, 1.286130, 0.193602], [-1.855120, 0.522583, -0.581402], [-1.855120, 0.522583, 0.581402], [-1.845000, 0.750000, -0.785000], [-1.845000, 0.750000, 0.785000], [-1.843750, 1.471870, 0.000000], [-1.833170, 1.890680, -0.084375], [-1.833170, 1.890680, 0.084375], [-1.831800, 1.946490, -0.196875], [-1.831800, 1.946490, 0.196875], [-1.829920, 2.023230, -0.196875], [-1.829920, 2.023230, 0.196875], [-1.828550, 2.079040, -0.084375], [-1.828550, 2.079040, 0.084375], [-1.825180, 0.591650, -0.776567], [-1.825180, 0.591650, 0.776567], [-1.817580, 0.343945, -0.187036], [-1.817580, 0.343945, 0.187036], [-1.807750, 1.286130, -0.566554], [-1.807750, 1.286130, 0.566554], [-1.806870, 1.471870, -0.375664], [-1.806870, 1.471870, 0.375664], [-1.805360, 1.100390, -0.768135], [-1.805360, 1.100390, 0.768135], [-1.772930, 0.453516, -0.754336], [-1.772930, 0.453516, 0.754336], [-1.750000, 0.234375, 0.000000], [-1.746440, 0.343945, -0.547339], [-1.746440, 0.343945, 0.547339], [-1.744330, 0.670825, -0.949871], [-1.744330, 0.670825, 0.949871], [-1.734910, 0.925195, -0.944741], [-1.734910, 0.925195, 0.944741], [-1.715000, 0.234375, -0.356563], [-1.715000, 0.234375, 0.356562], [-1.710080, 0.522583, -0.931218], [-1.710080, 0.522583, 0.931218], [-1.700860, 1.471870, -0.723672], [-1.700860, 1.471870, 0.723672], [-1.666400, 1.286130, -0.907437], [-1.666400, 1.286130, 0.907437], [-1.662500, 0.750000, -1.125000], [-1.662500, 0.750000, 1.125000], [-1.655160, 1.860940, -0.170322], [-1.655160, 1.860940, 0.170322], [-1.647420, 0.159961, -0.169526], [-1.647420, 0.159961, 0.169526], [-1.644640, 0.591650, -1.112920], [-1.644640, 0.591650, 1.112920], [-1.626780, 1.100390, -1.100830], [-1.626780, 1.100390, 1.100830], [-1.614370, 0.234375, -0.686875], [-1.614370, 0.234375, 0.686875], [-1.609890, 0.343945, -0.876660], [-1.609890, 0.343945, 0.876660], [-1.600000, 1.875000, 0.000000], [-1.597560, 0.453516, -1.081060], [-1.597560, 0.453516, 1.081060], [-1.590370, 1.860940, -0.498428], [-1.590370, 1.860940, 0.498428], [-1.584380, 1.910160, -0.168750], [-1.584380, 1.910160, 0.168750], [-1.582940, 0.159961, -0.496099], [-1.582940, 0.159961, 0.496099], [-1.578130, 0.085547, 0.000000], [-1.550000, 1.987500, -0.225000], [-1.550000, 1.987500, 0.225000], [-1.546560, 0.085547, -0.321543], [-1.546560, 0.085547, 0.321543], [-1.532970, 0.670825, -1.265670], [-1.532970, 0.670825, 1.265670], [-1.532620, 1.471870, -1.037110], [-1.532620, 1.471870, 1.037110], [-1.524690, 0.925195, -1.258830], [-1.524690, 0.925195, 1.258830], [-1.523670, 0.042773, -0.156792], [-1.523670, 0.042773, 0.156792], [-1.515630, 2.064840, -0.168750], [-1.515630, 2.064840, 0.168750], [-1.502870, 0.522583, -1.240810], [-1.502870, 0.522583, 1.240810], [-1.500000, 0.000000, 0.000000], [-1.500000, 2.100000, 0.000000], [-1.500000, 2.250000, 0.000000], [-1.470000, 0.000000, -0.305625], [-1.470000, 0.000000, 0.305625], [-1.470000, 2.250000, -0.305625], [-1.470000, 2.250000, 0.305625], [-1.466020, 1.860940, -0.798320], [-1.466020, 1.860940, 0.798320], [-1.464490, 1.286130, -1.209120], [-1.464490, 1.286130, 1.209120], [-1.464030, 0.042773, -0.458833], [-1.464030, 0.042773, 0.458833], [-1.459860, 2.286910, -0.150226], [-1.459860, 2.286910, 0.150226], [-1.459170, 0.159961, -0.794590], [-1.459170, 0.159961, 0.794590], [-1.455820, 0.085547, -0.619414], [-1.455820, 0.085547, 0.619414], [-1.454690, 0.234375, -0.984375], [-1.454690, 0.234375, 0.984375], [-1.449220, 2.323830, 0.000000], [-1.420230, 2.323830, -0.295278], [-1.420230, 2.323830, 0.295278], [-1.420000, 0.750000, -1.420000], [-1.420000, 0.750000, 1.420000], [-1.414820, 0.343945, -1.168120], [-1.414820, 0.343945, 1.168120], [-1.411910, 2.336130, -0.145291], [-1.411910, 2.336130, 0.145291], [-1.404750, 0.591650, -1.404750], [-1.404750, 0.591650, 1.404750], [-1.403130, 2.348440, 0.000000], [-1.402720, 2.286910, -0.439618], [-1.402720, 2.286910, 0.439618], [-1.400000, 2.250000, 0.000000], [-1.389490, 1.100390, -1.389490], [-1.389490, 1.100390, 1.389490], [-1.383750, 0.000000, -0.588750], [-1.383750, 0.000000, 0.588750], [-1.383750, 2.250000, -0.588750], [-1.383750, 2.250000, 0.588750], [-1.380470, 2.323830, 0.000000], [-1.377880, 2.336130, -0.141789], [-1.377880, 2.336130, 0.141789], [-1.376330, 2.286910, -0.141630], [-1.376330, 2.286910, 0.141630], [-1.375060, 2.348440, -0.285887], [-1.375060, 2.348440, 0.285887], [-1.372000, 2.250000, -0.285250], [-1.372000, 2.250000, 0.285250], [-1.364530, 0.453516, -1.364530], [-1.364530, 0.453516, 1.364530], [-1.356650, 2.336130, -0.425177], [-1.356650, 2.336130, 0.425177], [-1.352860, 2.323830, -0.281271], [-1.352860, 2.323830, 0.281271], [-1.349570, 0.042773, -0.734902], [-1.349570, 0.042773, 0.734902], [-1.336900, 2.323830, -0.568818], [-1.336900, 2.323830, 0.568818], [-1.323950, 2.336130, -0.414929], [-1.323950, 2.336130, 0.414929], [-1.322460, 2.286910, -0.414464], [-1.322460, 2.286910, 0.414464], [-1.311820, 0.085547, -0.887695], [-1.311820, 0.085547, 0.887695], [-1.309060, 1.471870, -1.309060], [-1.309060, 1.471870, 1.309060], [-1.300000, 2.250000, 0.000000], [-1.294380, 2.348440, -0.550727], [-1.294380, 2.348440, 0.550727], [-1.293050, 2.286910, -0.704126], [-1.293050, 2.286910, 0.704126], [-1.291500, 2.250000, -0.549500], [-1.291500, 2.250000, 0.549500], [-1.288390, 1.860940, -1.063730], [-1.288390, 1.860940, 1.063730], [-1.282370, 0.159961, -1.058760], [-1.282370, 0.159961, 1.058760], [-1.274000, 2.250000, -0.264875], [-1.274000, 2.250000, 0.264875], [-1.273480, 2.323830, -0.541834], [-1.273480, 2.323830, 0.541834], [-1.267660, 2.274900, -0.130448], [-1.267660, 2.274900, 0.130448], [-1.265670, 0.670825, -1.532970], [-1.265670, 0.670825, 1.532970], [-1.260940, 2.299800, 0.000000], [-1.258830, 0.925195, -1.524690], [-1.258830, 0.925195, 1.524690], [-1.250570, 2.336130, -0.680997], [-1.250570, 2.336130, 0.680997], [-1.246880, 0.000000, -0.843750], [-1.246880, 0.000000, 0.843750], [-1.246880, 2.250000, -0.843750], [-1.246880, 2.250000, 0.843750], [-1.242500, 0.234375, -1.242500], [-1.242500, 0.234375, 1.242500], [-1.240810, 0.522583, -1.502870], [-1.240810, 0.522583, 1.502870], [-1.235720, 2.299800, -0.256916], [-1.235720, 2.299800, 0.256916], [-1.220430, 2.336130, -0.664583], [-1.220430, 2.336130, 0.664583], [-1.219060, 2.286910, -0.663837], [-1.219060, 2.286910, 0.663837], [-1.218050, 2.274900, -0.381740], [-1.218050, 2.274900, 0.381740], [-1.209120, 1.286130, -1.464490], [-1.209120, 1.286130, 1.464490], [-1.204660, 2.323830, -0.815186], [-1.204660, 2.323830, 0.815186], [-1.199250, 2.250000, -0.510250], [-1.199250, 2.250000, 0.510250], [-1.196510, 2.319430, -0.123125], [-1.196510, 2.319430, 0.123125], [-1.186040, 0.042773, -0.979229], [-1.186040, 0.042773, 0.979229], [-1.168120, 0.343945, -1.414820], [-1.168120, 0.343945, 1.414820], [-1.166350, 2.348440, -0.789258], [-1.166350, 2.348440, 0.789258], [-1.163750, 2.250000, -0.787500], [-1.163750, 2.250000, 0.787500], [-1.163220, 2.299800, -0.494918], [-1.163220, 2.299800, 0.494918], [-1.156250, 2.339060, 0.000000], [-1.149680, 2.319430, -0.360312], [-1.149680, 2.319430, 0.360312], [-1.147520, 2.323830, -0.776514], [-1.147520, 2.323830, 0.776514], [-1.136370, 2.286910, -0.938220], [-1.136370, 2.286910, 0.938220], [-1.133120, 2.339060, -0.235586], [-1.133120, 2.339060, 0.235586], [-1.125000, 0.750000, -1.662500], [-1.125000, 0.750000, 1.662500], [-1.122810, 2.274900, -0.611424], [-1.122810, 2.274900, 0.611424], [-1.120470, 0.085547, -1.120470], [-1.120470, 0.085547, 1.120470], [-1.112920, 0.591650, -1.644640], [-1.112920, 0.591650, 1.644640], [-1.100830, 1.100390, -1.626780], [-1.100830, 1.100390, 1.626780], [-1.099040, 2.336130, -0.907402], [-1.099040, 2.336130, 0.907402], [-1.081060, 0.453516, -1.597560], [-1.081060, 0.453516, 1.597560], [-1.080630, 2.250000, -0.731250], [-1.080630, 2.250000, 0.731250], [-1.072550, 2.336130, -0.885531], [-1.072550, 2.336130, 0.885531], [-1.071350, 2.286910, -0.884537], [-1.071350, 2.286910, 0.884537], [-1.066640, 2.339060, -0.453828], [-1.066640, 2.339060, 0.453828], [-1.065000, 0.000000, -1.065000], [-1.065000, 0.000000, 1.065000], [-1.065000, 2.250000, -1.065000], [-1.065000, 2.250000, 1.065000], [-1.063730, 1.860940, -1.288390], [-1.063730, 1.860940, 1.288390], [-1.059790, 2.319430, -0.577104], [-1.059790, 2.319430, 0.577104], [-1.058760, 0.159961, -1.282370], [-1.058760, 0.159961, 1.282370], [-1.048150, 2.299800, -0.709277], [-1.048150, 2.299800, 0.709277], [-1.037110, 1.471870, -1.532620], [-1.037110, 1.471870, 1.532620], [-1.028940, 2.323830, -1.028940], [-1.028940, 2.323830, 1.028940], [-0.996219, 2.348440, -0.996219], [-0.996219, 2.348440, 0.996219], [-0.994000, 2.250000, -0.994000], [-0.994000, 2.250000, 0.994000], [-0.986761, 2.274900, -0.814698], [-0.986761, 2.274900, 0.814698], [-0.984375, 0.234375, -1.454690], [-0.984375, 0.234375, 1.454690], [-0.980719, 2.369530, -0.100920], [-0.980719, 2.369530, 0.100920], [-0.980133, 2.323830, -0.980133], [-0.980133, 2.323830, 0.980133], [-0.979229, 0.042773, -1.186040], [-0.979229, 0.042773, 1.186040], [-0.961133, 2.339060, -0.650391], [-0.961133, 2.339060, 0.650391], [-0.949871, 0.670825, -1.744330], [-0.949871, 0.670825, 1.744330], [-0.944741, 0.925195, -1.734910], [-0.944741, 0.925195, 1.734910], [-0.942332, 2.369530, -0.295330], [-0.942332, 2.369530, 0.295330], [-0.938220, 2.286910, -1.136370], [-0.938220, 2.286910, 1.136370], [-0.931373, 2.319430, -0.768968], [-0.931373, 2.319430, 0.768968], [-0.931218, 0.522583, -1.710080], [-0.931218, 0.522583, 1.710080], [-0.923000, 2.250000, -0.923000], [-0.923000, 2.250000, 0.923000], [-0.907437, 1.286130, -1.666400], [-0.907437, 1.286130, 1.666400], [-0.907402, 2.336130, -1.099040], [-0.907402, 2.336130, 1.099040], [-0.895266, 2.299800, -0.895266], [-0.895266, 2.299800, 0.895266], [-0.887695, 0.085547, -1.311820], [-0.887695, 0.085547, 1.311820], [-0.885531, 2.336130, -1.072550], [-0.885531, 2.336130, 1.072550], [-0.884537, 2.286910, -1.071350], [-0.884537, 2.286910, 1.071350], [-0.876660, 0.343945, -1.609890], [-0.876660, 0.343945, 1.609890], [-0.868654, 2.369530, -0.473023], [-0.868654, 2.369530, 0.473023], [-0.843750, 0.000000, -1.246880], [-0.843750, 0.000000, 1.246880], [-0.843750, 2.250000, -1.246880], [-0.843750, 2.250000, 1.246880], [-0.825000, 2.400000, 0.000000], [-0.820938, 2.339060, -0.820938], [-0.820938, 2.339060, 0.820938], [-0.815186, 2.323830, -1.204660], [-0.815186, 2.323830, 1.204660], [-0.814698, 2.274900, -0.986761], [-0.814698, 2.274900, 0.986761], [-0.808500, 2.400000, -0.168094], [-0.808500, 2.400000, 0.168094], [-0.798320, 1.860940, -1.466020], [-0.798320, 1.860940, 1.466020], [-0.794590, 0.159961, -1.459170], [-0.794590, 0.159961, 1.459170], [-0.789258, 2.348440, -1.166350], [-0.789258, 2.348440, 1.166350], [-0.787500, 2.250000, -1.163750], [-0.787500, 2.250000, 1.163750], [-0.785000, 0.750000, -1.845000], [-0.785000, 0.750000, 1.845000], [-0.776567, 0.591650, -1.825180], [-0.776567, 0.591650, 1.825180], [-0.776514, 2.323830, -1.147520], [-0.776514, 2.323830, 1.147520], [-0.768968, 2.319430, -0.931373], [-0.768968, 2.319430, 0.931373], [-0.768135, 1.100390, -1.805360], [-0.768135, 1.100390, 1.805360], [-0.763400, 2.369530, -0.630285], [-0.763400, 2.369530, 0.630285], [-0.761063, 2.400000, -0.323813], [-0.761063, 2.400000, 0.323813], [-0.754336, 0.453516, -1.772930], [-0.754336, 0.453516, 1.772930], [-0.734902, 0.042773, -1.349570], [-0.734902, 0.042773, 1.349570], [-0.731250, 2.250000, -1.080630], [-0.731250, 2.250000, 1.080630], [-0.723672, 1.471870, -1.700860], [-0.723672, 1.471870, 1.700860], [-0.709277, 2.299800, -1.048150], [-0.709277, 2.299800, 1.048150], [-0.704126, 2.286910, -1.293050], [-0.704126, 2.286910, 1.293050], [-0.686875, 0.234375, -1.614370], [-0.686875, 0.234375, 1.614370], [-0.685781, 2.400000, -0.464063], [-0.685781, 2.400000, 0.464063], [-0.680997, 2.336130, -1.250570], [-0.680997, 2.336130, 1.250570], [-0.664583, 2.336130, -1.220430], [-0.664583, 2.336130, 1.220430], [-0.663837, 2.286910, -1.219060], [-0.663837, 2.286910, 1.219060], [-0.650391, 2.339060, -0.961133], [-0.650391, 2.339060, 0.961133], [-0.631998, 2.430470, -0.064825], [-0.631998, 2.430470, 0.064825], [-0.630285, 2.369530, -0.763400], [-0.630285, 2.369530, 0.763400], [-0.619414, 0.085547, -1.455820], [-0.619414, 0.085547, 1.455820], [-0.611424, 2.274900, -1.122810], [-0.611424, 2.274900, 1.122810], [-0.607174, 2.430470, -0.190548], [-0.607174, 2.430470, 0.190548], [-0.593047, 0.670825, -1.892280], [-0.593047, 0.670825, 1.892280], [-0.589845, 0.925195, -1.882060], [-0.589845, 0.925195, 1.882060], [-0.588750, 0.000000, -1.383750], [-0.588750, 0.000000, 1.383750], [-0.588750, 2.250000, -1.383750], [-0.588750, 2.250000, 1.383750], [-0.585750, 2.400000, -0.585750], [-0.585750, 2.400000, 0.585750], [-0.581402, 0.522583, -1.855120], [-0.581402, 0.522583, 1.855120], [-0.577104, 2.319430, -1.059790], [-0.577104, 2.319430, 1.059790], [-0.568818, 2.323830, -1.336900], [-0.568818, 2.323830, 1.336900], [-0.566554, 1.286130, -1.807750], [-0.566554, 1.286130, 1.807750], [-0.559973, 2.430470, -0.304711], [-0.559973, 2.430470, 0.304711], [-0.550727, 2.348440, -1.294380], [-0.550727, 2.348440, 1.294380], [-0.549500, 2.250000, -1.291500], [-0.549500, 2.250000, 1.291500], [-0.547339, 0.343945, -1.746440], [-0.547339, 0.343945, 1.746440], [-0.541834, 2.323830, -1.273480], [-0.541834, 2.323830, 1.273480], [-0.510250, 2.250000, -1.199250], [-0.510250, 2.250000, 1.199250], [-0.498428, 1.860940, -1.590370], [-0.498428, 1.860940, 1.590370], [-0.496099, 0.159961, -1.582940], [-0.496099, 0.159961, 1.582940], [-0.494918, 2.299800, -1.163220], [-0.494918, 2.299800, 1.163220], [-0.491907, 2.430470, -0.406410], [-0.491907, 2.430470, 0.406410], [-0.473023, 2.369530, -0.868654], [-0.473023, 2.369530, 0.868654], [-0.464063, 2.400000, -0.685781], [-0.464063, 2.400000, 0.685781], [-0.458833, 0.042773, -1.464030], [-0.458833, 0.042773, 1.464030], [-0.456250, 2.460940, 0.000000], [-0.453828, 2.339060, -1.066640], [-0.453828, 2.339060, 1.066640], [-0.439618, 2.286910, -1.402720], [-0.439618, 2.286910, 1.402720], [-0.438241, 2.460940, -0.091207], [-0.438241, 2.460940, 0.091207], [-0.425177, 2.336130, -1.356650], [-0.425177, 2.336130, 1.356650], [-0.420891, 2.460940, -0.179078], [-0.420891, 2.460940, 0.179078], [-0.414929, 2.336130, -1.323950], [-0.414929, 2.336130, 1.323950], [-0.414464, 2.286910, -1.322460], [-0.414464, 2.286910, 1.322460], [-0.407500, 0.750000, -1.960000], [-0.407500, 0.750000, 1.960000], [-0.406410, 2.430470, -0.491907], [-0.406410, 2.430470, 0.491907], [-0.403123, 0.591650, -1.938950], [-0.403123, 0.591650, 1.938950], [-0.398745, 1.100390, -1.917890], [-0.398745, 1.100390, 1.917890], [-0.391582, 0.453516, -1.883440], [-0.391582, 0.453516, 1.883440], [-0.381740, 2.274900, -1.218050], [-0.381740, 2.274900, 1.218050], [-0.375664, 1.471870, -1.806870], [-0.375664, 1.471870, 1.806870], [-0.372159, 2.460940, -0.251889], [-0.372159, 2.460940, 0.251889], [-0.362109, 2.897170, 0.000000], [-0.360312, 2.319430, -1.149680], [-0.360312, 2.319430, 1.149680], [-0.356563, 0.234375, 1.715000], [-0.356562, 0.234375, -1.715000], [-0.340625, 2.950780, 0.000000], [-0.337859, 2.923970, -0.069278], [-0.337859, 2.923970, 0.069278], [-0.334238, 2.897170, -0.142705], [-0.334238, 2.897170, 0.142705], [-0.330325, 2.864210, -0.067672], [-0.330325, 2.864210, 0.067672], [-0.325000, 2.831250, 0.000000], [-0.323938, 2.460940, -0.323938], [-0.323938, 2.460940, 0.323938], [-0.323813, 2.400000, -0.761063], [-0.323813, 2.400000, 0.761063], [-0.321543, 0.085547, -1.546560], [-0.321543, 0.085547, 1.546560], [-0.315410, 2.505470, -0.064395], [-0.315410, 2.505470, 0.064395], [-0.314464, 2.950780, -0.134407], [-0.314464, 2.950780, 0.134407], [-0.305625, 0.000000, -1.470000], [-0.305625, 0.000000, 1.470000], [-0.305625, 2.250000, -1.470000], [-0.305625, 2.250000, 1.470000], [-0.304711, 2.430470, -0.559973], [-0.304711, 2.430470, 0.559973], [-0.299953, 2.831250, -0.127984], [-0.299953, 2.831250, 0.127984], [-0.295330, 2.369530, -0.942332], [-0.295330, 2.369530, 0.942332], [-0.295278, 2.323830, -1.420230], [-0.295278, 2.323830, 1.420230], [-0.287197, 2.923970, -0.194300], [-0.287197, 2.923970, 0.194300], [-0.285887, 2.348440, -1.375060], [-0.285887, 2.348440, 1.375060], [-0.285250, 2.250000, -1.372000], [-0.285250, 2.250000, 1.372000], [-0.281271, 2.323830, -1.352860], [-0.281271, 2.323830, 1.352860], [-0.280732, 2.864210, -0.189856], [-0.280732, 2.864210, 0.189856], [-0.274421, 2.968800, -0.056380], [-0.274421, 2.968800, 0.056380], [-0.267832, 2.505470, -0.180879], [-0.267832, 2.505470, 0.180879], [-0.264875, 2.250000, -1.274000], [-0.264875, 2.250000, 1.274000], [-0.257610, 2.897170, -0.257610], [-0.257610, 2.897170, 0.257610], [-0.256916, 2.299800, -1.235720], [-0.256916, 2.299800, 1.235720], [-0.251889, 2.460940, -0.372159], [-0.251889, 2.460940, 0.372159], [-0.250872, 2.757420, -0.051347], [-0.250872, 2.757420, 0.051347], [-0.242477, 2.950780, -0.242477], [-0.242477, 2.950780, 0.242477], [-0.235586, 2.339060, -1.133120], [-0.235586, 2.339060, 1.133120], [-0.233382, 2.968800, -0.158018], [-0.233382, 2.968800, 0.158018], [-0.231125, 2.831250, -0.231125], [-0.231125, 2.831250, 0.231125], [-0.230078, 2.986820, 0.000000], [-0.213159, 2.757420, -0.144103], [-0.213159, 2.757420, 0.144103], [-0.212516, 2.986820, -0.091113], [-0.212516, 2.986820, 0.091113], [-0.202656, 0.670825, -1.969370], [-0.202656, 0.670825, 1.969370], [-0.201561, 0.925195, -1.958730], [-0.201561, 0.925195, 1.958730], [-0.200000, 2.550000, 0.000000], [-0.198676, 0.522583, -1.930690], [-0.198676, 0.522583, 1.930690], [-0.196875, 2.683590, 0.000000], [-0.194300, 2.923970, -0.287197], [-0.194300, 2.923970, 0.287197], [-0.193602, 1.286130, -1.881390], [-0.193602, 1.286130, 1.881390], [-0.190548, 2.430470, -0.607174], [-0.190548, 2.430470, 0.607174], [-0.189856, 2.864210, -0.280732], [-0.189856, 2.864210, 0.280732], [-0.187036, 0.343945, -1.817580], [-0.187036, 0.343945, 1.817580], [-0.184500, 2.550000, -0.078500], [-0.184500, 2.550000, 0.078500], [-0.181661, 2.683590, -0.077405], [-0.181661, 2.683590, 0.077405], [-0.180879, 2.505470, -0.267832], [-0.180879, 2.505470, 0.267832], [-0.179078, 2.460940, -0.420891], [-0.179078, 2.460940, 0.420891], [-0.176295, 2.581200, -0.036001], [-0.176295, 2.581200, 0.036001], [-0.174804, 2.648000, -0.035727], [-0.174804, 2.648000, 0.035727], [-0.170322, 1.860940, -1.655160], [-0.170322, 1.860940, 1.655160], [-0.169526, 0.159961, -1.647420], [-0.169526, 0.159961, 1.647420], [-0.168094, 2.400000, -0.808500], [-0.168094, 2.400000, 0.808500], [-0.166797, 2.612400, 0.000000], [-0.164073, 2.986820, -0.164073], [-0.164073, 2.986820, 0.164073], [-0.158018, 2.968800, -0.233382], [-0.158018, 2.968800, 0.233382], [-0.156792, 0.042773, -1.523670], [-0.156792, 0.042773, 1.523670], [-0.153882, 2.612400, -0.065504], [-0.153882, 2.612400, 0.065504], [-0.150226, 2.286910, -1.459860], [-0.150226, 2.286910, 1.459860], [-0.149710, 2.581200, -0.101116], [-0.149710, 2.581200, 0.101116], [-0.148475, 2.648000, -0.100316], [-0.148475, 2.648000, 0.100316], [-0.145291, 2.336130, -1.411910], [-0.145291, 2.336130, 1.411910], [-0.144103, 2.757420, -0.213159], [-0.144103, 2.757420, 0.213159], [-0.142705, 2.897170, -0.334238], [-0.142705, 2.897170, 0.334238], [-0.142000, 2.550000, -0.142000], [-0.142000, 2.550000, 0.142000], [-0.141789, 2.336130, -1.377880], [-0.141789, 2.336130, 1.377880], [-0.141630, 2.286910, -1.376330], [-0.141630, 2.286910, 1.376330], [-0.139898, 2.683590, -0.139898], [-0.139898, 2.683590, 0.139898], [-0.134407, 2.950780, -0.314464], [-0.134407, 2.950780, 0.314464], [-0.130448, 2.274900, -1.267660], [-0.130448, 2.274900, 1.267660], [-0.127984, 2.831250, -0.299953], [-0.127984, 2.831250, 0.299953], [-0.123125, 2.319430, -1.196510], [-0.123125, 2.319430, 1.196510], [-0.118458, 2.612400, -0.118458], [-0.118458, 2.612400, 0.118458], [-0.110649, 2.993410, -0.022778], [-0.110649, 2.993410, 0.022778], [-0.101116, 2.581200, -0.149710], [-0.101116, 2.581200, 0.149710], [-0.100920, 2.369530, -0.980719], [-0.100920, 2.369530, 0.980719], [-0.100316, 2.648000, -0.148475], [-0.100316, 2.648000, 0.148475], [-0.094147, 2.993410, -0.063797], [-0.094147, 2.993410, 0.063797], [-0.091207, 2.460940, -0.438241], [-0.091207, 2.460940, 0.438241], [-0.091113, 2.986820, -0.212516], [-0.091113, 2.986820, 0.212516], [-0.078500, 2.550000, -0.184500], [-0.078500, 2.550000, 0.184500], [-0.077405, 2.683590, -0.181661], [-0.077405, 2.683590, 0.181661], [-0.069278, 2.923970, -0.337859], [-0.069278, 2.923970, 0.337859], [-0.067672, 2.864210, -0.330325], [-0.067672, 2.864210, 0.330325], [-0.065504, 2.612400, -0.153882], [-0.065504, 2.612400, 0.153882], [-0.064825, 2.430470, -0.631998], [-0.064825, 2.430470, 0.631998], [-0.064395, 2.505470, -0.315410], [-0.064395, 2.505470, 0.315410], [-0.063797, 2.993410, -0.094147], [-0.063797, 2.993410, 0.094147], [-0.056380, 2.968800, -0.274421], [-0.056380, 2.968800, 0.274421], [-0.051347, 2.757420, -0.250872], [-0.051347, 2.757420, 0.250872], [-0.036001, 2.581200, -0.176295], [-0.036001, 2.581200, 0.176295], [-0.035727, 2.648000, -0.174804], [-0.035727, 2.648000, 0.174804], [-0.022778, 2.993410, -0.110649], [-0.022778, 2.993410, 0.110649], [0.000000, 0.000000, -1.500000], [0.000000, 0.000000, 1.500000], [0.000000, 0.085547, -1.578130], [0.000000, 0.085547, 1.578130], [0.000000, 0.234375, -1.750000], [0.000000, 0.234375, 1.750000], [0.000000, 0.453516, -1.921880], [0.000000, 0.453516, 1.921880], [0.000000, 0.591650, -1.978520], [0.000000, 0.591650, 1.978520], [0.000000, 0.750000, -2.000000], [0.000000, 0.750000, 2.000000], [0.000000, 1.100390, -1.957030], [0.000000, 1.100390, 1.957030], [0.000000, 1.471870, -1.843750], [0.000000, 1.471870, 1.843750], [0.000000, 2.250000, -1.500000], [0.000000, 2.250000, -1.400000], [0.000000, 2.250000, -1.300000], [0.000000, 2.250000, 1.300000], [0.000000, 2.250000, 1.400000], [0.000000, 2.250000, 1.500000], [0.000000, 2.299800, -1.260940], [0.000000, 2.299800, 1.260940], [0.000000, 2.323830, -1.449220], [0.000000, 2.323830, -1.380470], [0.000000, 2.323830, 1.380470], [0.000000, 2.323830, 1.449220], [0.000000, 2.339060, -1.156250], [0.000000, 2.339060, 1.156250], [0.000000, 2.348440, -1.403130], [0.000000, 2.348440, 1.403130], [0.000000, 2.400000, -0.825000], [0.000000, 2.400000, 0.825000], [0.000000, 2.460940, -0.456250], [0.000000, 2.460940, 0.456250], [0.000000, 2.550000, -0.200000], [0.000000, 2.550000, 0.200000], [0.000000, 2.612400, -0.166797], [0.000000, 2.612400, 0.166797], [0.000000, 2.683590, -0.196875], [0.000000, 2.683590, 0.196875], [0.000000, 2.831250, -0.325000], [0.000000, 2.831250, 0.325000], [0.000000, 2.897170, -0.362109], [0.000000, 2.897170, 0.362109], [0.000000, 2.950780, -0.340625], [0.000000, 2.950780, 0.340625], [0.000000, 2.986820, -0.230078], [0.000000, 2.986820, 0.230078], [0.000000, 3.000000, 0.000000], [0.022778, 2.993410, -0.110649], [0.022778, 2.993410, 0.110649], [0.035727, 2.648000, -0.174804], [0.035727, 2.648000, 0.174804], [0.036001, 2.581200, -0.176295], [0.036001, 2.581200, 0.176295], [0.051347, 2.757420, -0.250872], [0.051347, 2.757420, 0.250872], [0.056380, 2.968800, -0.274421], [0.056380, 2.968800, 0.274421], [0.063797, 2.993410, -0.094147], [0.063797, 2.993410, 0.094147], [0.064395, 2.505470, -0.315410], [0.064395, 2.505470, 0.315410], [0.064825, 2.430470, -0.631998], [0.064825, 2.430470, 0.631998], [0.065504, 2.612400, -0.153882], [0.065504, 2.612400, 0.153882], [0.067672, 2.864210, -0.330325], [0.067672, 2.864210, 0.330325], [0.069278, 2.923970, -0.337859], [0.069278, 2.923970, 0.337859], [0.077405, 2.683590, -0.181661], [0.077405, 2.683590, 0.181661], [0.078500, 2.550000, -0.184500], [0.078500, 2.550000, 0.184500], [0.091113, 2.986820, -0.212516], [0.091113, 2.986820, 0.212516], [0.091207, 2.460940, -0.438241], [0.091207, 2.460940, 0.438241], [0.094147, 2.993410, -0.063797], [0.094147, 2.993410, 0.063797], [0.100316, 2.648000, -0.148475], [0.100316, 2.648000, 0.148475], [0.100920, 2.369530, -0.980719], [0.100920, 2.369530, 0.980719], [0.101116, 2.581200, -0.149710], [0.101116, 2.581200, 0.149710], [0.110649, 2.993410, -0.022778], [0.110649, 2.993410, 0.022778], [0.118458, 2.612400, -0.118458], [0.118458, 2.612400, 0.118458], [0.123125, 2.319430, -1.196510], [0.123125, 2.319430, 1.196510], [0.127984, 2.831250, -0.299953], [0.127984, 2.831250, 0.299953], [0.130448, 2.274900, -1.267660], [0.130448, 2.274900, 1.267660], [0.134407, 2.950780, -0.314464], [0.134407, 2.950780, 0.314464], [0.139898, 2.683590, -0.139898], [0.139898, 2.683590, 0.139898], [0.141630, 2.286910, -1.376330], [0.141630, 2.286910, 1.376330], [0.141789, 2.336130, -1.377880], [0.141789, 2.336130, 1.377880], [0.142000, 2.550000, -0.142000], [0.142000, 2.550000, 0.142000], [0.142705, 2.897170, -0.334238], [0.142705, 2.897170, 0.334238], [0.144103, 2.757420, -0.213159], [0.144103, 2.757420, 0.213159], [0.145291, 2.336130, -1.411910], [0.145291, 2.336130, 1.411910], [0.148475, 2.648000, -0.100316], [0.148475, 2.648000, 0.100316], [0.149710, 2.581200, -0.101116], [0.149710, 2.581200, 0.101116], [0.150226, 2.286910, -1.459860], [0.150226, 2.286910, 1.459860], [0.153882, 2.612400, -0.065504], [0.153882, 2.612400, 0.065504], [0.156792, 0.042773, -1.523670], [0.156792, 0.042773, 1.523670], [0.158018, 2.968800, -0.233382], [0.158018, 2.968800, 0.233382], [0.164073, 2.986820, -0.164073], [0.164073, 2.986820, 0.164073], [0.166797, 2.612400, 0.000000], [0.168094, 2.400000, -0.808500], [0.168094, 2.400000, 0.808500], [0.169526, 0.159961, -1.647420], [0.169526, 0.159961, 1.647420], [0.170322, 1.860940, -1.655160], [0.170322, 1.860940, 1.655160], [0.174804, 2.648000, -0.035727], [0.174804, 2.648000, 0.035727], [0.176295, 2.581200, -0.036001], [0.176295, 2.581200, 0.036001], [0.179078, 2.460940, -0.420891], [0.179078, 2.460940, 0.420891], [0.180879, 2.505470, -0.267832], [0.180879, 2.505470, 0.267832], [0.181661, 2.683590, -0.077405], [0.181661, 2.683590, 0.077405], [0.184500, 2.550000, -0.078500], [0.184500, 2.550000, 0.078500], [0.187036, 0.343945, -1.817580], [0.187036, 0.343945, 1.817580], [0.189856, 2.864210, -0.280732], [0.189856, 2.864210, 0.280732], [0.190548, 2.430470, -0.607174], [0.190548, 2.430470, 0.607174], [0.193602, 1.286130, -1.881390], [0.193602, 1.286130, 1.881390], [0.194300, 2.923970, -0.287197], [0.194300, 2.923970, 0.287197], [0.196875, 2.683590, 0.000000], [0.198676, 0.522583, -1.930690], [0.198676, 0.522583, 1.930690], [0.200000, 2.550000, 0.000000], [0.201561, 0.925195, -1.958730], [0.201561, 0.925195, 1.958730], [0.202656, 0.670825, -1.969370], [0.202656, 0.670825, 1.969370], [0.212516, 2.986820, -0.091113], [0.212516, 2.986820, 0.091113], [0.213159, 2.757420, -0.144103], [0.213159, 2.757420, 0.144103], [0.230078, 2.986820, 0.000000], [0.231125, 2.831250, -0.231125], [0.231125, 2.831250, 0.231125], [0.233382, 2.968800, -0.158018], [0.233382, 2.968800, 0.158018], [0.235586, 2.339060, -1.133120], [0.235586, 2.339060, 1.133120], [0.242477, 2.950780, -0.242477], [0.242477, 2.950780, 0.242477], [0.250872, 2.757420, -0.051347], [0.250872, 2.757420, 0.051347], [0.251889, 2.460940, -0.372159], [0.251889, 2.460940, 0.372159], [0.256916, 2.299800, -1.235720], [0.256916, 2.299800, 1.235720], [0.257610, 2.897170, -0.257610], [0.257610, 2.897170, 0.257610], [0.264875, 2.250000, -1.274000], [0.264875, 2.250000, 1.274000], [0.267832, 2.505470, -0.180879], [0.267832, 2.505470, 0.180879], [0.274421, 2.968800, -0.056380], [0.274421, 2.968800, 0.056380], [0.280732, 2.864210, -0.189856], [0.280732, 2.864210, 0.189856], [0.281271, 2.323830, -1.352860], [0.281271, 2.323830, 1.352860], [0.285250, 2.250000, -1.372000], [0.285250, 2.250000, 1.372000], [0.285887, 2.348440, -1.375060], [0.285887, 2.348440, 1.375060], [0.287197, 2.923970, -0.194300], [0.287197, 2.923970, 0.194300], [0.295278, 2.323830, -1.420230], [0.295278, 2.323830, 1.420230], [0.295330, 2.369530, -0.942332], [0.295330, 2.369530, 0.942332], [0.299953, 2.831250, -0.127984], [0.299953, 2.831250, 0.127984], [0.304711, 2.430470, -0.559973], [0.304711, 2.430470, 0.559973], [0.305625, 0.000000, -1.470000], [0.305625, 0.000000, 1.470000], [0.305625, 2.250000, -1.470000], [0.305625, 2.250000, 1.470000], [0.314464, 2.950780, -0.134407], [0.314464, 2.950780, 0.134407], [0.315410, 2.505470, -0.064395], [0.315410, 2.505470, 0.064395], [0.321543, 0.085547, -1.546560], [0.321543, 0.085547, 1.546560], [0.323813, 2.400000, -0.761063], [0.323813, 2.400000, 0.761063], [0.323938, 2.460940, -0.323938], [0.323938, 2.460940, 0.323938], [0.325000, 2.831250, 0.000000], [0.330325, 2.864210, -0.067672], [0.330325, 2.864210, 0.067672], [0.334238, 2.897170, -0.142705], [0.334238, 2.897170, 0.142705], [0.337859, 2.923970, -0.069278], [0.337859, 2.923970, 0.069278], [0.340625, 2.950780, 0.000000], [0.356562, 0.234375, 1.715000], [0.356563, 0.234375, -1.715000], [0.360312, 2.319430, -1.149680], [0.360312, 2.319430, 1.149680], [0.362109, 2.897170, 0.000000], [0.372159, 2.460940, -0.251889], [0.372159, 2.460940, 0.251889], [0.375664, 1.471870, -1.806870], [0.375664, 1.471870, 1.806870], [0.381740, 2.274900, -1.218050], [0.381740, 2.274900, 1.218050], [0.391582, 0.453516, -1.883440], [0.391582, 0.453516, 1.883440], [0.398745, 1.100390, -1.917890], [0.398745, 1.100390, 1.917890], [0.403123, 0.591650, -1.938950], [0.403123, 0.591650, 1.938950], [0.406410, 2.430470, -0.491907], [0.406410, 2.430470, 0.491907], [0.407500, 0.750000, -1.960000], [0.407500, 0.750000, 1.960000], [0.414464, 2.286910, -1.322460], [0.414464, 2.286910, 1.322460], [0.414929, 2.336130, -1.323950], [0.414929, 2.336130, 1.323950], [0.420891, 2.460940, -0.179078], [0.420891, 2.460940, 0.179078], [0.425177, 2.336130, -1.356650], [0.425177, 2.336130, 1.356650], [0.438241, 2.460940, -0.091207], [0.438241, 2.460940, 0.091207], [0.439618, 2.286910, -1.402720], [0.439618, 2.286910, 1.402720], [0.453828, 2.339060, -1.066640], [0.453828, 2.339060, 1.066640], [0.456250, 2.460940, 0.000000], [0.458833, 0.042773, -1.464030], [0.458833, 0.042773, 1.464030], [0.464063, 2.400000, -0.685781], [0.464063, 2.400000, 0.685781], [0.473023, 2.369530, -0.868654], [0.473023, 2.369530, 0.868654], [0.491907, 2.430470, -0.406410], [0.491907, 2.430470, 0.406410], [0.494918, 2.299800, -1.163220], [0.494918, 2.299800, 1.163220], [0.496099, 0.159961, -1.582940], [0.496099, 0.159961, 1.582940], [0.498428, 1.860940, -1.590370], [0.498428, 1.860940, 1.590370], [0.510250, 2.250000, -1.199250], [0.510250, 2.250000, 1.199250], [0.541834, 2.323830, -1.273480], [0.541834, 2.323830, 1.273480], [0.547339, 0.343945, -1.746440], [0.547339, 0.343945, 1.746440], [0.549500, 2.250000, -1.291500], [0.549500, 2.250000, 1.291500], [0.550727, 2.348440, -1.294380], [0.550727, 2.348440, 1.294380], [0.559973, 2.430470, -0.304711], [0.559973, 2.430470, 0.304711], [0.566554, 1.286130, -1.807750], [0.566554, 1.286130, 1.807750], [0.568818, 2.323830, -1.336900], [0.568818, 2.323830, 1.336900], [0.577104, 2.319430, -1.059790], [0.577104, 2.319430, 1.059790], [0.581402, 0.522583, -1.855120], [0.581402, 0.522583, 1.855120], [0.585750, 2.400000, -0.585750], [0.585750, 2.400000, 0.585750], [0.588750, 0.000000, -1.383750], [0.588750, 0.000000, 1.383750], [0.588750, 2.250000, -1.383750], [0.588750, 2.250000, 1.383750], [0.589845, 0.925195, -1.882060], [0.589845, 0.925195, 1.882060], [0.593047, 0.670825, -1.892280], [0.593047, 0.670825, 1.892280], [0.607174, 2.430470, -0.190548], [0.607174, 2.430470, 0.190548], [0.611424, 2.274900, -1.122810], [0.611424, 2.274900, 1.122810], [0.619414, 0.085547, -1.455820], [0.619414, 0.085547, 1.455820], [0.630285, 2.369530, -0.763400], [0.630285, 2.369530, 0.763400], [0.631998, 2.430470, -0.064825], [0.631998, 2.430470, 0.064825], [0.650391, 2.339060, -0.961133], [0.650391, 2.339060, 0.961133], [0.663837, 2.286910, -1.219060], [0.663837, 2.286910, 1.219060], [0.664583, 2.336130, -1.220430], [0.664583, 2.336130, 1.220430], [0.680997, 2.336130, -1.250570], [0.680997, 2.336130, 1.250570], [0.685781, 2.400000, -0.464063], [0.685781, 2.400000, 0.464063], [0.686875, 0.234375, -1.614370], [0.686875, 0.234375, 1.614370], [0.704126, 2.286910, -1.293050], [0.704126, 2.286910, 1.293050], [0.709277, 2.299800, -1.048150], [0.709277, 2.299800, 1.048150], [0.723672, 1.471870, -1.700860], [0.723672, 1.471870, 1.700860], [0.731250, 2.250000, -1.080630], [0.731250, 2.250000, 1.080630], [0.734902, 0.042773, -1.349570], [0.734902, 0.042773, 1.349570], [0.754336, 0.453516, -1.772930], [0.754336, 0.453516, 1.772930], [0.761063, 2.400000, -0.323813], [0.761063, 2.400000, 0.323813], [0.763400, 2.369530, -0.630285], [0.763400, 2.369530, 0.630285], [0.768135, 1.100390, -1.805360], [0.768135, 1.100390, 1.805360], [0.768968, 2.319430, -0.931373], [0.768968, 2.319430, 0.931373], [0.776514, 2.323830, -1.147520], [0.776514, 2.323830, 1.147520], [0.776567, 0.591650, -1.825180], [0.776567, 0.591650, 1.825180], [0.785000, 0.750000, -1.845000], [0.785000, 0.750000, 1.845000], [0.787500, 2.250000, -1.163750], [0.787500, 2.250000, 1.163750], [0.789258, 2.348440, -1.166350], [0.789258, 2.348440, 1.166350], [0.794590, 0.159961, -1.459170], [0.794590, 0.159961, 1.459170], [0.798320, 1.860940, -1.466020], [0.798320, 1.860940, 1.466020], [0.808500, 2.400000, -0.168094], [0.808500, 2.400000, 0.168094], [0.814698, 2.274900, -0.986761], [0.814698, 2.274900, 0.986761], [0.815186, 2.323830, -1.204660], [0.815186, 2.323830, 1.204660], [0.820938, 2.339060, -0.820938], [0.820938, 2.339060, 0.820938], [0.825000, 2.400000, 0.000000], [0.843750, 0.000000, -1.246880], [0.843750, 0.000000, 1.246880], [0.843750, 2.250000, -1.246880], [0.843750, 2.250000, 1.246880], [0.868654, 2.369530, -0.473023], [0.868654, 2.369530, 0.473023], [0.876660, 0.343945, -1.609890], [0.876660, 0.343945, 1.609890], [0.884537, 2.286910, -1.071350], [0.884537, 2.286910, 1.071350], [0.885531, 2.336130, -1.072550], [0.885531, 2.336130, 1.072550], [0.887695, 0.085547, -1.311820], [0.887695, 0.085547, 1.311820], [0.895266, 2.299800, -0.895266], [0.895266, 2.299800, 0.895266], [0.907402, 2.336130, -1.099040], [0.907402, 2.336130, 1.099040], [0.907437, 1.286130, -1.666400], [0.907437, 1.286130, 1.666400], [0.923000, 2.250000, -0.923000], [0.923000, 2.250000, 0.923000], [0.931218, 0.522583, -1.710080], [0.931218, 0.522583, 1.710080], [0.931373, 2.319430, -0.768968], [0.931373, 2.319430, 0.768968], [0.938220, 2.286910, -1.136370], [0.938220, 2.286910, 1.136370], [0.942332, 2.369530, -0.295330], [0.942332, 2.369530, 0.295330], [0.944741, 0.925195, -1.734910], [0.944741, 0.925195, 1.734910], [0.949871, 0.670825, -1.744330], [0.949871, 0.670825, 1.744330], [0.961133, 2.339060, -0.650391], [0.961133, 2.339060, 0.650391], [0.979229, 0.042773, -1.186040], [0.979229, 0.042773, 1.186040], [0.980133, 2.323830, -0.980133], [0.980133, 2.323830, 0.980133], [0.980719, 2.369530, -0.100920], [0.980719, 2.369530, 0.100920], [0.984375, 0.234375, -1.454690], [0.984375, 0.234375, 1.454690], [0.986761, 2.274900, -0.814698], [0.986761, 2.274900, 0.814698], [0.994000, 2.250000, -0.994000], [0.994000, 2.250000, 0.994000], [0.996219, 2.348440, -0.996219], [0.996219, 2.348440, 0.996219], [1.028940, 2.323830, -1.028940], [1.028940, 2.323830, 1.028940], [1.037110, 1.471870, -1.532620], [1.037110, 1.471870, 1.532620], [1.048150, 2.299800, -0.709277], [1.048150, 2.299800, 0.709277], [1.058760, 0.159961, -1.282370], [1.058760, 0.159961, 1.282370], [1.059790, 2.319430, -0.577104], [1.059790, 2.319430, 0.577104], [1.063730, 1.860940, -1.288390], [1.063730, 1.860940, 1.288390], [1.065000, 0.000000, -1.065000], [1.065000, 0.000000, 1.065000], [1.065000, 2.250000, -1.065000], [1.065000, 2.250000, 1.065000], [1.066640, 2.339060, -0.453828], [1.066640, 2.339060, 0.453828], [1.071350, 2.286910, -0.884537], [1.071350, 2.286910, 0.884537], [1.072550, 2.336130, -0.885531], [1.072550, 2.336130, 0.885531], [1.080630, 2.250000, -0.731250], [1.080630, 2.250000, 0.731250], [1.081060, 0.453516, -1.597560], [1.081060, 0.453516, 1.597560], [1.099040, 2.336130, -0.907402], [1.099040, 2.336130, 0.907402], [1.100830, 1.100390, -1.626780], [1.100830, 1.100390, 1.626780], [1.112920, 0.591650, -1.644640], [1.112920, 0.591650, 1.644640], [1.120470, 0.085547, -1.120470], [1.120470, 0.085547, 1.120470], [1.122810, 2.274900, -0.611424], [1.122810, 2.274900, 0.611424], [1.125000, 0.750000, -1.662500], [1.125000, 0.750000, 1.662500], [1.133120, 2.339060, -0.235586], [1.133120, 2.339060, 0.235586], [1.136370, 2.286910, -0.938220], [1.136370, 2.286910, 0.938220], [1.147520, 2.323830, -0.776514], [1.147520, 2.323830, 0.776514], [1.149680, 2.319430, -0.360312], [1.149680, 2.319430, 0.360312], [1.156250, 2.339060, 0.000000], [1.163220, 2.299800, -0.494918], [1.163220, 2.299800, 0.494918], [1.163750, 2.250000, -0.787500], [1.163750, 2.250000, 0.787500], [1.166350, 2.348440, -0.789258], [1.166350, 2.348440, 0.789258], [1.168120, 0.343945, -1.414820], [1.168120, 0.343945, 1.414820], [1.186040, 0.042773, -0.979229], [1.186040, 0.042773, 0.979229], [1.196510, 2.319430, -0.123125], [1.196510, 2.319430, 0.123125], [1.199250, 2.250000, -0.510250], [1.199250, 2.250000, 0.510250], [1.204660, 2.323830, -0.815186], [1.204660, 2.323830, 0.815186], [1.209120, 1.286130, -1.464490], [1.209120, 1.286130, 1.464490], [1.218050, 2.274900, -0.381740], [1.218050, 2.274900, 0.381740], [1.219060, 2.286910, -0.663837], [1.219060, 2.286910, 0.663837], [1.220430, 2.336130, -0.664583], [1.220430, 2.336130, 0.664583], [1.235720, 2.299800, -0.256916], [1.235720, 2.299800, 0.256916], [1.240810, 0.522583, -1.502870], [1.240810, 0.522583, 1.502870], [1.242500, 0.234375, -1.242500], [1.242500, 0.234375, 1.242500], [1.246880, 0.000000, -0.843750], [1.246880, 0.000000, 0.843750], [1.246880, 2.250000, -0.843750], [1.246880, 2.250000, 0.843750], [1.250570, 2.336130, -0.680997], [1.250570, 2.336130, 0.680997], [1.258830, 0.925195, -1.524690], [1.258830, 0.925195, 1.524690], [1.260940, 2.299800, 0.000000], [1.265670, 0.670825, -1.532970], [1.265670, 0.670825, 1.532970], [1.267660, 2.274900, -0.130448], [1.267660, 2.274900, 0.130448], [1.273480, 2.323830, -0.541834], [1.273480, 2.323830, 0.541834], [1.274000, 2.250000, -0.264875], [1.274000, 2.250000, 0.264875], [1.282370, 0.159961, -1.058760], [1.282370, 0.159961, 1.058760], [1.288390, 1.860940, -1.063730], [1.288390, 1.860940, 1.063730], [1.291500, 2.250000, -0.549500], [1.291500, 2.250000, 0.549500], [1.293050, 2.286910, -0.704126], [1.293050, 2.286910, 0.704126], [1.294380, 2.348440, -0.550727], [1.294380, 2.348440, 0.550727], [1.300000, 2.250000, 0.000000], [1.309060, 1.471870, -1.309060], [1.309060, 1.471870, 1.309060], [1.311820, 0.085547, -0.887695], [1.311820, 0.085547, 0.887695], [1.322460, 2.286910, -0.414464], [1.322460, 2.286910, 0.414464], [1.323950, 2.336130, -0.414929], [1.323950, 2.336130, 0.414929], [1.336900, 2.323830, -0.568818], [1.336900, 2.323830, 0.568818], [1.349570, 0.042773, -0.734902], [1.349570, 0.042773, 0.734902], [1.352860, 2.323830, -0.281271], [1.352860, 2.323830, 0.281271], [1.356650, 2.336130, -0.425177], [1.356650, 2.336130, 0.425177], [1.364530, 0.453516, -1.364530], [1.364530, 0.453516, 1.364530], [1.372000, 2.250000, -0.285250], [1.372000, 2.250000, 0.285250], [1.375060, 2.348440, -0.285887], [1.375060, 2.348440, 0.285887], [1.376330, 2.286910, -0.141630], [1.376330, 2.286910, 0.141630], [1.377880, 2.336130, -0.141789], [1.377880, 2.336130, 0.141789], [1.380470, 2.323830, 0.000000], [1.383750, 0.000000, -0.588750], [1.383750, 0.000000, 0.588750], [1.383750, 2.250000, -0.588750], [1.383750, 2.250000, 0.588750], [1.389490, 1.100390, -1.389490], [1.389490, 1.100390, 1.389490], [1.400000, 2.250000, 0.000000], [1.402720, 2.286910, -0.439618], [1.402720, 2.286910, 0.439618], [1.403130, 2.348440, 0.000000], [1.404750, 0.591650, -1.404750], [1.404750, 0.591650, 1.404750], [1.411910, 2.336130, -0.145291], [1.411910, 2.336130, 0.145291], [1.414820, 0.343945, -1.168120], [1.414820, 0.343945, 1.168120], [1.420000, 0.750000, -1.420000], [1.420000, 0.750000, 1.420000], [1.420230, 2.323830, -0.295278], [1.420230, 2.323830, 0.295278], [1.449220, 2.323830, 0.000000], [1.454690, 0.234375, -0.984375], [1.454690, 0.234375, 0.984375], [1.455820, 0.085547, -0.619414], [1.455820, 0.085547, 0.619414], [1.459170, 0.159961, -0.794590], [1.459170, 0.159961, 0.794590], [1.459860, 2.286910, -0.150226], [1.459860, 2.286910, 0.150226], [1.464030, 0.042773, -0.458833], [1.464030, 0.042773, 0.458833], [1.464490, 1.286130, -1.209120], [1.464490, 1.286130, 1.209120], [1.466020, 1.860940, -0.798320], [1.466020, 1.860940, 0.798320], [1.470000, 0.000000, -0.305625], [1.470000, 0.000000, 0.305625], [1.470000, 2.250000, -0.305625], [1.470000, 2.250000, 0.305625], [1.500000, 0.000000, 0.000000], [1.500000, 2.250000, 0.000000], [1.502870, 0.522583, -1.240810], [1.502870, 0.522583, 1.240810], [1.523670, 0.042773, -0.156792], [1.523670, 0.042773, 0.156792], [1.524690, 0.925195, -1.258830], [1.524690, 0.925195, 1.258830], [1.532620, 1.471870, -1.037110], [1.532620, 1.471870, 1.037110], [1.532970, 0.670825, -1.265670], [1.532970, 0.670825, 1.265670], [1.546560, 0.085547, -0.321543], [1.546560, 0.085547, 0.321543], [1.578130, 0.085547, 0.000000], [1.582940, 0.159961, -0.496099], [1.582940, 0.159961, 0.496099], [1.590370, 1.860940, -0.498428], [1.590370, 1.860940, 0.498428], [1.597560, 0.453516, -1.081060], [1.597560, 0.453516, 1.081060], [1.609890, 0.343945, -0.876660], [1.609890, 0.343945, 0.876660], [1.614370, 0.234375, -0.686875], [1.614370, 0.234375, 0.686875], [1.626780, 1.100390, -1.100830], [1.626780, 1.100390, 1.100830], [1.644640, 0.591650, -1.112920], [1.644640, 0.591650, 1.112920], [1.647420, 0.159961, -0.169526], [1.647420, 0.159961, 0.169526], [1.655160, 1.860940, -0.170322], [1.655160, 1.860940, 0.170322], [1.662500, 0.750000, -1.125000], [1.662500, 0.750000, 1.125000], [1.666400, 1.286130, -0.907437], [1.666400, 1.286130, 0.907437], [1.700000, 0.450000, 0.000000], [1.700000, 0.485449, -0.216563], [1.700000, 0.485449, 0.216563], [1.700000, 0.578906, -0.371250], [1.700000, 0.578906, 0.371250], [1.700000, 0.711035, -0.464063], [1.700000, 0.711035, 0.464063], [1.700000, 0.862500, -0.495000], [1.700000, 0.862500, 0.495000], [1.700000, 1.013970, -0.464063], [1.700000, 1.013970, 0.464063], [1.700000, 1.146090, -0.371250], [1.700000, 1.146090, 0.371250], [1.700000, 1.239550, -0.216563], [1.700000, 1.239550, 0.216563], [1.700000, 1.275000, 0.000000], [1.700860, 1.471870, -0.723672], [1.700860, 1.471870, 0.723672], [1.710080, 0.522583, -0.931218], [1.710080, 0.522583, 0.931218], [1.715000, 0.234375, -0.356562], [1.715000, 0.234375, 0.356563], [1.734910, 0.925195, -0.944741], [1.734910, 0.925195, 0.944741], [1.744330, 0.670825, -0.949871], [1.744330, 0.670825, 0.949871], [1.746440, 0.343945, -0.547339], [1.746440, 0.343945, 0.547339], [1.750000, 0.234375, 0.000000], [1.772930, 0.453516, -0.754336], [1.772930, 0.453516, 0.754336], [1.805360, 1.100390, -0.768135], [1.805360, 1.100390, 0.768135], [1.806870, 1.471870, -0.375664], [1.806870, 1.471870, 0.375664], [1.807750, 1.286130, -0.566554], [1.807750, 1.286130, 0.566554], [1.808680, 0.669440, -0.415335], [1.808680, 0.669440, 0.415335], [1.815230, 0.556498, -0.292881], [1.815230, 0.556498, 0.292881], [1.817580, 0.343945, -0.187036], [1.817580, 0.343945, 0.187036], [1.818500, 0.493823, -0.107904], [1.818500, 0.493823, 0.107904], [1.825180, 0.591650, -0.776567], [1.825180, 0.591650, 0.776567], [1.843750, 1.471870, 0.000000], [1.844080, 1.273110, -0.106836], [1.844080, 1.273110, 0.106836], [1.845000, 0.750000, -0.785000], [1.845000, 0.750000, 0.785000], [1.849890, 1.212450, -0.289984], [1.849890, 1.212450, 0.289984], [1.855120, 0.522583, -0.581402], [1.855120, 0.522583, 0.581402], [1.860070, 1.106280, -0.412082], [1.860070, 1.106280, 0.412082], [1.872860, 0.972820, -0.473131], [1.872860, 0.972820, 0.473131], [1.881390, 1.286130, -0.193602], [1.881390, 1.286130, 0.193602], [1.882060, 0.925195, -0.589845], [1.882060, 0.925195, 0.589845], [1.883440, 0.453516, -0.391582], [1.883440, 0.453516, 0.391582], [1.886520, 0.830257, -0.473131], [1.886520, 0.830257, 0.473131], [1.892280, 0.670825, -0.593047], [1.892280, 0.670825, 0.593047], [1.908980, 0.762851, -0.457368], [1.908980, 0.762851, 0.457368], [1.917890, 1.100390, -0.398745], [1.917890, 1.100390, 0.398745], [1.921880, 0.453516, 0.000000], [1.925720, 0.624968, -0.368660], [1.925720, 0.624968, 0.368660], [1.930690, 0.522583, -0.198676], [1.930690, 0.522583, 0.198676], [1.935200, 0.536667, -0.215052], [1.935200, 0.536667, 0.215052], [1.938790, 0.503174, 0.000000], [1.938950, 0.591650, -0.403123], [1.938950, 0.591650, 0.403123], [1.957030, 1.100390, 0.000000], [1.958730, 0.925195, -0.201561], [1.958730, 0.925195, 0.201561], [1.960000, 0.750000, -0.407500], [1.960000, 0.750000, 0.407500], [1.969370, 0.670825, -0.202656], [1.969370, 0.670825, 0.202656], [1.978520, 0.591650, 0.000000], [1.984960, 1.304590, 0.000000], [1.991360, 1.273310, -0.210782], [1.991360, 1.273310, 0.210782], [2.000000, 0.750000, 0.000000], [2.007990, 0.721263, -0.409761], [2.007990, 0.721263, 0.409761], [2.008210, 1.190840, -0.361340], [2.008210, 1.190840, 0.361340], [2.024710, 0.614949, -0.288958], [2.024710, 0.614949, 0.288958], [2.032050, 1.074240, -0.451675], [2.032050, 1.074240, 0.451675], [2.033790, 0.556062, -0.106458], [2.033790, 0.556062, 0.106458], [2.059380, 0.940576, -0.481787], [2.059380, 0.940576, 0.481787], [2.086440, 1.330480, -0.101581], [2.086440, 1.330480, 0.101581], [2.086700, 0.806915, -0.451675], [2.086700, 0.806915, 0.451675], [2.101410, 1.278150, -0.275720], [2.101410, 1.278150, 0.275720], [2.110530, 0.690317, -0.361340], [2.110530, 0.690317, 0.361340], [2.127390, 0.607845, -0.210782], [2.127390, 0.607845, 0.210782], [2.127600, 1.186560, -0.391812], [2.127600, 1.186560, 0.391812], [2.133790, 0.576563, 0.000000], [2.160540, 1.071430, -0.449859], [2.160540, 1.071430, 0.449859], [2.169220, 0.790259, -0.399360], [2.169220, 0.790259, 0.399360], [2.179690, 1.385160, 0.000000], [2.189760, 1.358870, -0.195542], [2.189760, 1.358870, 0.195542], [2.194810, 0.691761, -0.281559], [2.194810, 0.691761, 0.281559], [2.195710, 0.948444, -0.449859], [2.195710, 0.948444, 0.449859], [2.208370, 0.637082, -0.103732], [2.208370, 0.637082, 0.103732], [2.216310, 1.289570, -0.335215], [2.216310, 1.289570, 0.335215], [2.220200, 0.891314, -0.434457], [2.220200, 0.891314, 0.434457], [2.248570, 1.433000, -0.092384], [2.248570, 1.433000, 0.092384], [2.253840, 1.191600, -0.419019], [2.253840, 1.191600, 0.419019], [2.259440, 0.772489, -0.349967], [2.259440, 0.772489, 0.349967], [2.268570, 1.390160, -0.250758], [2.268570, 1.390160, 0.250758], [2.281890, 0.696393, -0.204147], [2.281890, 0.696393, 0.204147], [2.290410, 0.667529, 0.000000], [2.296880, 1.079300, -0.446953], [2.296880, 1.079300, 0.446953], [2.299250, 0.874953, -0.384664], [2.299250, 0.874953, 0.384664], [2.303580, 1.315200, -0.356340], [2.303580, 1.315200, 0.356340], [2.306440, 1.504400, 0.000000], [2.318380, 1.483560, -0.173996], [2.318380, 1.483560, 0.173996], [2.330690, 0.784406, -0.271218], [2.330690, 0.784406, 0.271218], [2.339910, 0.966989, -0.419019], [2.339910, 0.966989, 0.419019], [2.347590, 0.734271, -0.099922], [2.347590, 0.734271, 0.099922], [2.347590, 1.220960, -0.409131], [2.347590, 1.220960, 0.409131], [2.349840, 1.428640, -0.298279], [2.349840, 1.428640, 0.298279], [2.353180, 1.568160, -0.080823], [2.353180, 1.568160, 0.080823], [2.375750, 1.535310, -0.219377], [2.375750, 1.535310, 0.219377], [2.377440, 0.869019, -0.335215], [2.377440, 0.869019, 0.335215], [2.387500, 1.650000, 0.000000], [2.394320, 1.350980, -0.372849], [2.394320, 1.350980, 0.372849], [2.394600, 1.120300, -0.409131], [2.394600, 1.120300, 0.409131], [2.400390, 1.634690, -0.149297], [2.400390, 1.634690, 0.149297], [2.403990, 0.799722, -0.195542], [2.403990, 0.799722, 0.195542], [2.414060, 0.773438, 0.000000], [2.415240, 1.477810, -0.311747], [2.415240, 1.477810, 0.311747], [2.434380, 1.594340, -0.255938], [2.434380, 1.594340, 0.255938], [2.438610, 1.026060, -0.356340], [2.438610, 1.026060, 0.356340], [2.445310, 1.261960, -0.397705], [2.445310, 1.261960, 0.397705], [2.451680, 1.805340, -0.063087], [2.451680, 1.805340, 0.063087], [2.464890, 1.405520, -0.357931], [2.464890, 1.405520, 0.357931], [2.473620, 0.951099, -0.250758], [2.473620, 0.951099, 0.250758], [2.477680, 1.786380, -0.171237], [2.477680, 1.786380, 0.171237], [2.482420, 1.537280, -0.319922], [2.482420, 1.537280, 0.319922], [2.493620, 0.908264, -0.092384], [2.493620, 0.908264, 0.092384], [2.496300, 1.172950, -0.372849], [2.496300, 1.172950, 0.372849], [2.501560, 1.971090, 0.000000], [2.517270, 1.965550, -0.103052], [2.517270, 1.965550, 0.103052], [2.517920, 1.328310, -0.357931], [2.517920, 1.328310, 0.357931], [2.523180, 1.753220, -0.243336], [2.523180, 1.753220, 0.243336], [2.537500, 1.471870, -0.341250], [2.537500, 1.471870, 0.341250], [2.540780, 1.095290, -0.298279], [2.540780, 1.095290, 0.298279], [2.549110, 2.044640, -0.047716], [2.549110, 2.044640, 0.047716], [2.558690, 1.950950, -0.176660], [2.558690, 1.950950, 0.176660], [2.567570, 1.256030, -0.311747], [2.567570, 1.256030, 0.311747], [2.572250, 1.040360, -0.173996], [2.572250, 1.040360, 0.173996], [2.579100, 2.121970, 0.000000], [2.580390, 1.711530, -0.279386], [2.580390, 1.711530, 0.279386], [2.581010, 2.037730, -0.129515], [2.581010, 2.037730, 0.129515], [2.584180, 1.019530, 0.000000], [2.592580, 1.406470, -0.319922], [2.592580, 1.406470, 0.319922], [2.598490, 2.119920, -0.087812], [2.598490, 2.119920, 0.087812], [2.601780, 1.554720, -0.304019], [2.601780, 1.554720, 0.304019], [2.607070, 1.198530, -0.219377], [2.607070, 1.198530, 0.219377], [2.611620, 1.691280, -0.287908], [2.611620, 1.691280, 0.287908], [2.617250, 1.930310, -0.220825], [2.617250, 1.930310, 0.220825], [2.629630, 1.165680, -0.080823], [2.629630, 1.165680, 0.080823], [2.637880, 2.025550, -0.180818], [2.637880, 2.025550, 0.180818], [2.640630, 1.349410, -0.255938], [2.640630, 1.349410, 0.255938], [2.649600, 2.114510, -0.150535], [2.649600, 2.114510, 0.150535], [2.650840, 2.185470, -0.042461], [2.650840, 2.185470, 0.042461], [2.653910, 1.504200, -0.264113], [2.653910, 1.504200, 0.264113], [2.665420, 1.649250, -0.266995], [2.665420, 1.649250, 0.266995], [2.674610, 1.309060, -0.149297], [2.674610, 1.309060, 0.149297], [2.678230, 1.782540, -0.252819], [2.678230, 1.782540, 0.252819], [2.684380, 1.906640, -0.235547], [2.684380, 1.906640, 0.235547], [2.687500, 1.293750, 0.000000], [2.691900, 2.183610, -0.115251], [2.691900, 2.183610, 0.115251], [2.696450, 1.463800, -0.185857], [2.696450, 1.463800, 0.185857], [2.700000, 2.250000, 0.000000], [2.708080, 2.010370, -0.208084], [2.708080, 2.010370, 0.208084], [2.717030, 1.611670, -0.213596], [2.717030, 1.611670, 0.213596], [2.720760, 1.440720, -0.068474], [2.720760, 1.440720, 0.068474], [2.725780, 2.250000, -0.082031], [2.725780, 2.250000, 0.082031], [2.725990, 2.106430, -0.175250], [2.725990, 2.106430, 0.175250], [2.736000, 1.751550, -0.219519], [2.736000, 1.751550, 0.219519], [2.750210, 2.269190, -0.039734], [2.750210, 2.269190, 0.039734], [2.751500, 1.882970, -0.220825], [2.751500, 1.882970, 0.220825], [2.753540, 1.585080, -0.124598], [2.753540, 1.585080, 0.124598], [2.767380, 1.575000, 0.000000], [2.775560, 2.284000, 0.000000], [2.780990, 1.994370, -0.208084], [2.780990, 1.994370, 0.208084], [2.783030, 1.726700, -0.154476], [2.783030, 1.726700, 0.154476], [2.793750, 2.250000, -0.140625], [2.793750, 2.250000, 0.140625], [2.797820, 2.271750, -0.107849], [2.797820, 2.271750, 0.107849], [2.799490, 2.292750, -0.076904], [2.799490, 2.292750, 0.076904], [2.800000, 2.250000, 0.000000], [2.804690, 2.098100, -0.200713], [2.804690, 2.098100, 0.200713], [2.809900, 1.712500, -0.056912], [2.809900, 1.712500, 0.056912], [2.810060, 1.862330, -0.176660], [2.810060, 1.862330, 0.176660], [2.812010, 2.178150, -0.169843], [2.812010, 2.178150, 0.169843], [2.812740, 2.297540, -0.035632], [2.812740, 2.297540, 0.035632], [2.817190, 2.250000, -0.049219], [2.817190, 2.250000, 0.049219], [2.825000, 2.306250, 0.000000], [2.830110, 2.271290, -0.025891], [2.830110, 2.271290, 0.025891], [2.840630, 2.292190, 0.000000], [2.844790, 2.299640, -0.029993], [2.844790, 2.299640, 0.029993], [2.850920, 2.307160, -0.065625], [2.850920, 2.307160, 0.065625], [2.851180, 1.979190, -0.180818], [2.851180, 1.979190, 0.180818], [2.851480, 1.847730, -0.103052], [2.851480, 1.847730, 0.103052], [2.860480, 2.300930, -0.096716], [2.860480, 2.300930, 0.096716], [2.862500, 2.250000, -0.084375], [2.862500, 2.250000, 0.084375], [2.862630, 2.292980, -0.054346], [2.862630, 2.292980, 0.054346], [2.865740, 2.272010, -0.070276], [2.865740, 2.272010, 0.070276], [2.867190, 1.842190, 0.000000], [2.872280, 2.294250, -0.131836], [2.872280, 2.294250, 0.131836], [2.883390, 2.089770, -0.175250], [2.883390, 2.089770, 0.175250], [2.888360, 2.301190, -0.081409], [2.888360, 2.301190, 0.081409], [2.898270, 2.170880, -0.194382], [2.898270, 2.170880, 0.194382], [2.908050, 1.967000, -0.129515], [2.908050, 1.967000, 0.129515], [2.919240, 2.309550, -0.112500], [2.919240, 2.309550, 0.112500], [2.920640, 2.295070, -0.093164], [2.920640, 2.295070, 0.093164], [2.932790, 2.131030, -0.172211], [2.932790, 2.131030, 0.172211], [2.939800, 2.273260, -0.158936], [2.939800, 2.273260, 0.158936], [2.939960, 1.960100, -0.047716], [2.939960, 1.960100, 0.047716], [2.959780, 2.081680, -0.150535], [2.959780, 2.081680, 0.150535], [2.969950, 2.274120, -0.103564], [2.969950, 2.274120, 0.103564], [3.000000, 2.250000, -0.187500], [3.000000, 2.250000, -0.112500], [3.000000, 2.250000, 0.112500], [3.000000, 2.250000, 0.187500], [3.002810, 2.304840, -0.142529], [3.002810, 2.304840, 0.142529], [3.010890, 2.076270, -0.087812], [3.010890, 2.076270, 0.087812], [3.015780, 2.305710, -0.119971], [3.015780, 2.305710, 0.119971], [3.030270, 2.074220, 0.000000], [3.041500, 2.125670, -0.116276], [3.041500, 2.125670, 0.116276], [3.043230, 2.211080, -0.166431], [3.043230, 2.211080, 0.166431], [3.068420, 2.173450, -0.143215], [3.068420, 2.173450, 0.143215], [3.079290, 2.123060, -0.042838], [3.079290, 2.123060, 0.042838], [3.093160, 2.298780, -0.175781], [3.093160, 2.298780, 0.175781], [3.096680, 2.301420, -0.124219], [3.096680, 2.301420, 0.124219], [3.126560, 2.316800, -0.150000], [3.126560, 2.316800, 0.150000], [3.126720, 2.277290, -0.103564], [3.126720, 2.277290, 0.103564], [3.126910, 2.171280, -0.083542], [3.126910, 2.171280, 0.083542], [3.137500, 2.250000, -0.084375], [3.137500, 2.250000, 0.084375], [3.149100, 2.170460, 0.000000], [3.153370, 2.275520, -0.158936], [3.153370, 2.275520, 0.158936], [3.168950, 2.211180, -0.112353], [3.168950, 2.211180, 0.112353], [3.182810, 2.250000, -0.049219], [3.182810, 2.250000, 0.049219], [3.200000, 2.250000, 0.000000], [3.206250, 2.250000, -0.140625], [3.206250, 2.250000, 0.140625], [3.207460, 2.312510, -0.119971], [3.207460, 2.312510, 0.119971], [3.212560, 2.210430, -0.041393], [3.212560, 2.210430, 0.041393], [3.216920, 2.310730, -0.142529], [3.216920, 2.310730, 0.142529], [3.230940, 2.279400, -0.070276], [3.230940, 2.279400, 0.070276], [3.267240, 2.278140, -0.025891], [3.267240, 2.278140, 0.025891], [3.272720, 2.307760, -0.093164], [3.272720, 2.307760, 0.093164], [3.274220, 2.250000, -0.082031], [3.274220, 2.250000, 0.082031], [3.295340, 2.277030, -0.107849], [3.295340, 2.277030, 0.107849], [3.300000, 2.250000, 0.000000], [3.314050, 2.303310, -0.131836], [3.314050, 2.303310, 0.131836], [3.330730, 2.309850, -0.054346], [3.330730, 2.309850, 0.054346], [3.333890, 2.324050, -0.112500], [3.333890, 2.324050, 0.112500], [3.334890, 2.317020, -0.081409], [3.334890, 2.317020, 0.081409], [3.342360, 2.280060, -0.039734], [3.342360, 2.280060, 0.039734], [3.355430, 2.302700, 0.000000], [3.359250, 2.314650, -0.096716], [3.359250, 2.314650, 0.096716], [3.379120, 2.316580, -0.029993], [3.379120, 2.316580, 0.029993], [3.386840, 2.304810, -0.076904], [3.386840, 2.304810, 0.076904], [3.402210, 2.326440, -0.065625], [3.402210, 2.326440, 0.065625], [3.406390, 2.318500, -0.035632], [3.406390, 2.318500, 0.035632], [3.408380, 2.315430, 0.000000], [3.428120, 2.327340, 0.000000] ]; var indices = [ [1454,1468,1458], [1448,1454,1458], [1461,1448,1458], [1468,1461,1458], [1429,1454,1440], [1421,1429,1440], [1448,1421,1440], [1454,1448,1440], [1380,1429,1398], [1373,1380,1398], [1421,1373,1398], [1429,1421,1398], [1327,1380,1349], [1319,1327,1349], [1373,1319,1349], [1380,1373,1349], [1448,1461,1460], [1456,1448,1460], [1471,1456,1460], [1461,1471,1460], [1421,1448,1442], [1433,1421,1442], [1456,1433,1442], [1448,1456,1442], [1373,1421,1400], [1382,1373,1400], [1433,1382,1400], [1421,1433,1400], [1319,1373,1351], [1329,1319,1351], [1382,1329,1351], [1373,1382,1351], [1264,1327,1289], [1258,1264,1289], [1319,1258,1289], [1327,1319,1289], [1192,1264,1228], [1188,1192,1228], [1258,1188,1228], [1264,1258,1228], [1100,1192,1157], [1098,1100,1157], [1188,1098,1157], [1192,1188,1157], [922,1100,1006], [928,922,1006], [1098,928,1006], [1100,1098,1006], [1258,1319,1291], [1266,1258,1291], [1329,1266,1291], [1319,1329,1291], [1188,1258,1230], [1194,1188,1230], [1266,1194,1230], [1258,1266,1230], [1098,1188,1159], [1102,1098,1159], [1194,1102,1159], [1188,1194,1159], [928,1098,1008], [933,928,1008], [1102,933,1008], [1098,1102,1008], [1456,1471,1475], [1481,1456,1475], [1482,1481,1475], [1471,1482,1475], [1433,1456,1450], [1444,1433,1450], [1481,1444,1450], [1456,1481,1450], [1382,1433,1412], [1392,1382,1412], [1444,1392,1412], [1433,1444,1412], [1329,1382,1357], [1331,1329,1357], [1392,1331,1357], [1382,1392,1357], [1481,1482,1490], [1500,1481,1490], [1502,1500,1490], [1482,1502,1490], [1444,1481,1470], [1465,1444,1470], [1500,1465,1470], [1481,1500,1470], [1392,1444,1431], [1410,1392,1431], [1465,1410,1431], [1444,1465,1431], [1331,1392,1371], [1345,1331,1371], [1410,1345,1371], [1392,1410,1371], [1266,1329,1297], [1276,1266,1297], [1331,1276,1297], [1329,1331,1297], [1194,1266,1232], [1200,1194,1232], [1276,1200,1232], [1266,1276,1232], [1102,1194,1163], [1106,1102,1163], [1200,1106,1163], [1194,1200,1163], [933,1102,1016], [929,933,1016], [1106,929,1016], [1102,1106,1016], [1276,1331,1307], [1283,1276,1307], [1345,1283,1307], [1331,1345,1307], [1200,1276,1238], [1210,1200,1238], [1283,1210,1238], [1276,1283,1238], [1106,1200,1167], [1116,1106,1167], [1210,1116,1167], [1200,1210,1167], [929,1106,1022], [923,929,1022], [1116,923,1022], [1106,1116,1022], [755,922,849], [757,755,849], [928,757,849], [922,928,849], [663,755,698], [667,663,698], [757,667,698], [755,757,698], [591,663,627], [597,591,627], [667,597,627], [663,667,627], [528,591,566], [536,528,566], [597,536,566], [591,597,566], [757,928,847], [753,757,847], [933,753,847], [928,933,847], [667,757,696], [661,667,696], [753,661,696], [757,753,696], [597,667,625], [589,597,625], [661,589,625], [667,661,625], [536,597,564], [526,536,564], [589,526,564], [597,589,564], [475,528,506], [482,475,506], [536,482,506], [528,536,506], [426,475,457], [434,426,457], [482,434,457], [475,482,457], [401,426,415], [407,401,415], [434,407,415], [426,434,415], [386,401,397], [393,386,397], [407,393,397], [401,407,397], [482,536,504], [473,482,504], [526,473,504], [536,526,504], [434,482,455], [422,434,455], [473,422,455], [482,473,455], [407,434,413], [399,407,413], [422,399,413], [434,422,413], [393,407,395], [383,393,395], [399,383,395], [407,399,395], [753,933,839], [749,753,839], [929,749,839], [933,929,839], [661,753,692], [655,661,692], [749,655,692], [753,749,692], [589,661,623], [579,589,623], [655,579,623], [661,655,623], [526,589,558], [524,526,558], [579,524,558], [589,579,558], [749,929,833], [741,749,833], [923,741,833], [929,923,833], [655,749,688], [647,655,688], [741,647,688], [749,741,688], [579,655,617], [574,579,617], [647,574,617], [655,647,617], [524,579,548], [512,524,548], [574,512,548], [579,574,548], [473,526,498], [463,473,498], [524,463,498], [526,524,498], [422,473,443], [411,422,443], [463,411,443], [473,463,443], [399,422,405], [374,399,405], [411,374,405], [422,411,405], [383,399,380], [372,383,380], [374,372,380], [399,374,380], [463,524,484], [447,463,484], [512,447,484], [524,512,484], [411,463,424], [392,411,424], [447,392,424], [463,447,424], [374,411,385], [357,374,385], [392,357,385], [411,392,385], [372,374,365], [353,372,365], [357,353,365], [374,357,365], [400,386,396], [406,400,396], [393,406,396], [386,393,396], [425,400,414], [433,425,414], [406,433,414], [400,406,414], [474,425,456], [481,474,456], [433,481,456], [425,433,456], [527,474,505], [535,527,505], [481,535,505], [474,481,505], [406,393,394], [398,406,394], [383,398,394], [393,383,394], [433,406,412], [421,433,412], [398,421,412], [406,398,412], [481,433,454], [472,481,454], [421,472,454], [433,421,454], [535,481,503], [525,535,503], [472,525,503], [481,472,503], [590,527,565], [596,590,565], [535,596,565], [527,535,565], [662,590,626], [666,662,626], [596,666,626], [590,596,626], [754,662,697], [756,754,697], [666,756,697], [662,666,697], [919,754,848], [927,919,848], [756,927,848], [754,756,848], [596,535,563], [588,596,563], [525,588,563], [535,525,563], [666,596,624], [660,666,624], [588,660,624], [596,588,624], [756,666,695], [752,756,695], [660,752,695], [666,660,695], [927,756,846], [932,927,846], [752,932,846], [756,752,846], [398,383,379], [373,398,379], [372,373,379], [383,372,379], [421,398,404], [410,421,404], [373,410,404], [398,373,404], [472,421,442], [462,472,442], [410,462,442], [421,410,442], [525,472,497], [523,525,497], [462,523,497], [472,462,497], [373,372,364], [356,373,364], [353,356,364], [372,353,364], [410,373,384], [391,410,384], [356,391,384], [373,356,384], [462,410,423], [446,462,423], [391,446,423], [410,391,423], [523,462,483], [511,523,483], [446,511,483], [462,446,483], [588,525,557], [578,588,557], [523,578,557], [525,523,557], [660,588,622], [654,660,622], [578,654,622], [588,578,622], [752,660,691], [748,752,691], [654,748,691], [660,654,691], [932,752,838], [926,932,838], [748,926,838], [752,748,838], [578,523,547], [573,578,547], [511,573,547], [523,511,547], [654,578,616], [646,654,616], [573,646,616], [578,573,616], [748,654,687], [740,748,687], [646,740,687], [654,646,687], [926,748,832], [918,926,832], [740,918,832], [748,740,832], [1099,919,1005], [1097,1099,1005], [927,1097,1005], [919,927,1005], [1191,1099,1156], [1187,1191,1156], [1097,1187,1156], [1099,1097,1156], [1263,1191,1227], [1257,1263,1227], [1187,1257,1227], [1191,1187,1227], [1326,1263,1288], [1318,1326,1288], [1257,1318,1288], [1263,1257,1288], [1097,927,1007], [1101,1097,1007], [932,1101,1007], [927,932,1007], [1187,1097,1158], [1193,1187,1158], [1101,1193,1158], [1097,1101,1158], [1257,1187,1229], [1265,1257,1229], [1193,1265,1229], [1187,1193,1229], [1318,1257,1290], [1328,1318,1290], [1265,1328,1290], [1257,1265,1290], [1379,1326,1348], [1372,1379,1348], [1318,1372,1348], [1326,1318,1348], [1428,1379,1397], [1420,1428,1397], [1372,1420,1397], [1379,1372,1397], [1453,1428,1439], [1447,1453,1439], [1420,1447,1439], [1428,1420,1439], [1468,1453,1457], [1461,1468,1457], [1447,1461,1457], [1453,1447,1457], [1372,1318,1350], [1381,1372,1350], [1328,1381,1350], [1318,1328,1350], [1420,1372,1399], [1432,1420,1399], [1381,1432,1399], [1372,1381,1399], [1447,1420,1441], [1455,1447,1441], [1432,1455,1441], [1420,1432,1441], [1461,1447,1459], [1471,1461,1459], [1455,1471,1459], [1447,1455,1459], [1101,932,1015], [1105,1101,1015], [926,1105,1015], [932,926,1015], [1193,1101,1162], [1199,1193,1162], [1105,1199,1162], [1101,1105,1162], [1265,1193,1231], [1275,1265,1231], [1199,1275,1231], [1193,1199,1231], [1328,1265,1296], [1330,1328,1296], [1275,1330,1296], [1265,1275,1296], [1105,926,1021], [1115,1105,1021], [918,1115,1021], [926,918,1021], [1199,1105,1166], [1209,1199,1166], [1115,1209,1166], [1105,1115,1166], [1275,1199,1237], [1282,1275,1237], [1209,1282,1237], [1199,1209,1237], [1330,1275,1306], [1344,1330,1306], [1282,1344,1306], [1275,1282,1306], [1381,1328,1356], [1391,1381,1356], [1330,1391,1356], [1328,1330,1356], [1432,1381,1411], [1443,1432,1411], [1391,1443,1411], [1381,1391,1411], [1455,1432,1449], [1480,1455,1449], [1443,1480,1449], [1432,1443,1449], [1471,1455,1474], [1482,1471,1474], [1480,1482,1474], [1455,1480,1474], [1391,1330,1370], [1409,1391,1370], [1344,1409,1370], [1330,1344,1370], [1443,1391,1430], [1464,1443,1430], [1409,1464,1430], [1391,1409,1430], [1480,1443,1469], [1499,1480,1469], [1464,1499,1469], [1443,1464,1469], [1482,1480,1489], [1502,1482,1489], [1499,1502,1489], [1480,1499,1489], [1500,1502,1533], [1572,1500,1533], [1585,1572,1533], [1502,1585,1533], [1465,1500,1519], [1555,1465,1519], [1572,1555,1519], [1500,1572,1519], [1410,1465,1496], [1510,1410,1496], [1555,1510,1496], [1465,1555,1496], [1345,1410,1427], [1436,1345,1427], [1510,1436,1427], [1410,1510,1427], [1283,1345,1341], [1333,1283,1341], [1436,1333,1341], [1345,1436,1341], [1210,1283,1270], [1242,1210,1270], [1333,1242,1270], [1283,1333,1270], [1116,1210,1184], [1143,1116,1184], [1242,1143,1184], [1210,1242,1184], [923,1116,1037], [917,923,1037], [1143,917,1037], [1116,1143,1037], [1572,1585,1599], [1611,1572,1599], [1622,1611,1599], [1585,1622,1599], [1555,1572,1574], [1570,1555,1574], [1611,1570,1574], [1572,1611,1574], [1510,1555,1537], [1527,1510,1537], [1570,1527,1537], [1555,1570,1537], [1436,1510,1494], [1467,1436,1494], [1527,1467,1494], [1510,1527,1494], [1611,1622,1624], [1626,1611,1624], [1633,1626,1624], [1622,1633,1624], [1570,1611,1601], [1589,1570,1601], [1626,1589,1601], [1611,1626,1601], [1527,1570,1561], [1535,1527,1561], [1589,1535,1561], [1570,1589,1561], [1467,1527,1508], [1479,1467,1508], [1535,1479,1508], [1527,1535,1508], [1333,1436,1394], [1359,1333,1394], [1467,1359,1394], [1436,1467,1394], [1242,1333,1299], [1254,1242,1299], [1359,1254,1299], [1333,1359,1299], [1143,1242,1198], [1149,1143,1198], [1254,1149,1198], [1242,1254,1198], [917,1143,1057], [915,917,1057], [1149,915,1057], [1143,1149,1057], [1359,1467,1414], [1367,1359,1414], [1479,1367,1414], [1467,1479,1414], [1254,1359,1311], [1262,1254,1311], [1367,1262,1311], [1359,1367,1311], [1149,1254,1212], [1155,1149,1212], [1262,1155,1212], [1254,1262,1212], [915,1149,1065], [913,915,1065], [1155,913,1065], [1149,1155,1065], [741,923,818], [712,741,818], [917,712,818], [923,917,818], [647,741,671], [613,647,671], [712,613,671], [741,712,671], [574,647,585], [522,574,585], [613,522,585], [647,613,585], [512,574,514], [419,512,514], [522,419,514], [574,522,514], [447,512,428], [342,447,428], [419,342,428], [512,419,428], [392,447,359], [308,392,359], [342,308,359], [447,342,359], [357,392,329], [291,357,329], [308,291,329], [392,308,329], [353,357,314], [275,353,314], [291,275,314], [357,291,314], [712,917,798], [706,712,798], [915,706,798], [917,915,798], [613,712,657], [601,613,657], [706,601,657], [712,706,657], [522,613,556], [496,522,556], [601,496,556], [613,601,556], [419,522,461], [388,419,461], [496,388,461], [522,496,461], [706,915,790], [700,706,790], [913,700,790], [915,913,790], [601,706,643], [593,601,643], [700,593,643], [706,700,643], [496,601,544], [488,496,544], [593,488,544], [601,593,544], [388,496,441], [376,388,441], [488,376,441], [496,488,441], [342,419,361], [320,342,361], [388,320,361], [419,388,361], [308,342,310], [293,308,310], [320,293,310], [342,320,310], [291,308,289], [257,291,289], [293,257,289], [308,293,289], [275,291,270], [246,275,270], [257,246,270], [291,257,270], [320,388,344], [312,320,344], [376,312,344], [388,376,344], [293,320,302], [274,293,302], [312,274,302], [320,312,302], [257,293,268], [243,257,268], [274,243,268], [293,274,268], [246,257,245], [232,246,245], [243,232,245], [257,243,245], [356,353,313], [290,356,313], [275,290,313], [353,275,313], [391,356,328], [307,391,328], [290,307,328], [356,290,328], [446,391,358], [341,446,358], [307,341,358], [391,307,358], [511,446,427], [418,511,427], [341,418,427], [446,341,427], [573,511,513], [521,573,513], [418,521,513], [511,418,513], [646,573,584], [612,646,584], [521,612,584], [573,521,584], [740,646,670], [711,740,670], [612,711,670], [646,612,670], [918,740,817], [916,918,817], [711,916,817], [740,711,817], [290,275,269], [256,290,269], [246,256,269], [275,246,269], [307,290,288], [292,307,288], [256,292,288], [290,256,288], [341,307,309], [319,341,309], [292,319,309], [307,292,309], [418,341,360], [387,418,360], [319,387,360], [341,319,360], [256,246,244], [242,256,244], [232,242,244], [246,232,244], [292,256,267], [273,292,267], [242,273,267], [256,242,267], [319,292,301], [311,319,301], [273,311,301], [292,273,301], [387,319,343], [375,387,343], [311,375,343], [319,311,343], [521,418,460], [495,521,460], [387,495,460], [418,387,460], [612,521,555], [600,612,555], [495,600,555], [521,495,555], [711,612,656], [705,711,656], [600,705,656], [612,600,656], [916,711,797], [914,916,797], [705,914,797], [711,705,797], [495,387,440], [487,495,440], [375,487,440], [387,375,440], [600,495,543], [592,600,543], [487,592,543], [495,487,543], [705,600,642], [699,705,642], [592,699,642], [600,592,642], [914,705,789], [912,914,789], [699,912,789], [705,699,789], [1115,918,1036], [1142,1115,1036], [916,1142,1036], [918,916,1036], [1209,1115,1183], [1241,1209,1183], [1142,1241,1183], [1115,1142,1183], [1282,1209,1269], [1332,1282,1269], [1241,1332,1269], [1209,1241,1269], [1344,1282,1340], [1435,1344,1340], [1332,1435,1340], [1282,1332,1340], [1409,1344,1426], [1509,1409,1426], [1435,1509,1426], [1344,1435,1426], [1464,1409,1495], [1554,1464,1495], [1509,1554,1495], [1409,1509,1495], [1499,1464,1518], [1571,1499,1518], [1554,1571,1518], [1464,1554,1518], [1502,1499,1532], [1585,1502,1532], [1571,1585,1532], [1499,1571,1532], [1142,916,1056], [1148,1142,1056], [914,1148,1056], [916,914,1056], [1241,1142,1197], [1253,1241,1197], [1148,1253,1197], [1142,1148,1197], [1332,1241,1298], [1358,1332,1298], [1253,1358,1298], [1241,1253,1298], [1435,1332,1393], [1466,1435,1393], [1358,1466,1393], [1332,1358,1393], [1148,914,1064], [1154,1148,1064], [912,1154,1064], [914,912,1064], [1253,1148,1211], [1261,1253,1211], [1154,1261,1211], [1148,1154,1211], [1358,1253,1310], [1366,1358,1310], [1261,1366,1310], [1253,1261,1310], [1466,1358,1413], [1478,1466,1413], [1366,1478,1413], [1358,1366,1413], [1509,1435,1493], [1526,1509,1493], [1466,1526,1493], [1435,1466,1493], [1554,1509,1536], [1569,1554,1536], [1526,1569,1536], [1509,1526,1536], [1571,1554,1573], [1610,1571,1573], [1569,1610,1573], [1554,1569,1573], [1585,1571,1598], [1622,1585,1598], [1610,1622,1598], [1571,1610,1598], [1526,1466,1507], [1534,1526,1507], [1478,1534,1507], [1466,1478,1507], [1569,1526,1560], [1588,1569,1560], [1534,1588,1560], [1526,1534,1560], [1610,1569,1600], [1625,1610,1600], [1588,1625,1600], [1569,1588,1600], [1622,1610,1623], [1633,1622,1623], [1625,1633,1623], [1610,1625,1623], [1626,1633,1628], [1621,1626,1628], [1629,1621,1628], [1633,1629,1628], [1589,1626,1607], [1584,1589,1607], [1621,1584,1607], [1626,1621,1607], [1621,1629,1616], [1603,1621,1616], [1612,1603,1616], [1629,1612,1616], [1584,1621,1593], [1568,1584,1593], [1603,1568,1593], [1621,1603,1593], [1535,1589,1563], [1529,1535,1563], [1584,1529,1563], [1589,1584,1563], [1479,1535,1512], [1473,1479,1512], [1529,1473,1512], [1535,1529,1512], [1529,1584,1557], [1521,1529,1557], [1568,1521,1557], [1584,1568,1557], [1473,1529,1504], [1452,1473,1504], [1521,1452,1504], [1529,1521,1504], [1603,1612,1580], [1559,1603,1580], [1566,1559,1580], [1612,1566,1580], [1568,1603,1565], [1525,1568,1565], [1559,1525,1565], [1603,1559,1565], [1521,1568,1523], [1484,1521,1523], [1525,1484,1523], [1568,1525,1523], [1452,1521,1477], [1406,1452,1477], [1484,1406,1477], [1521,1484,1477], [1367,1479,1417], [1361,1367,1417], [1473,1361,1417], [1479,1473,1417], [1262,1367,1313], [1260,1262,1313], [1361,1260,1313], [1367,1361,1313], [1361,1473,1404], [1355,1361,1404], [1452,1355,1404], [1473,1452,1404], [1260,1361,1303], [1248,1260,1303], [1355,1248,1303], [1361,1355,1303], [1155,1262,1214], [1151,1155,1214], [1260,1151,1214], [1262,1260,1214], [913,1155,1067], [911,913,1067], [1151,911,1067], [1155,1151,1067], [1151,1260,1204], [1147,1151,1204], [1248,1147,1204], [1260,1248,1204], [911,1151,1062], [909,911,1062], [1147,909,1062], [1151,1147,1062], [1355,1452,1384], [1323,1355,1384], [1406,1323,1384], [1452,1406,1384], [1248,1355,1287], [1236,1248,1287], [1323,1236,1287], [1355,1323,1287], [1147,1248,1190], [1135,1147,1190], [1236,1135,1190], [1248,1236,1190], [909,1147,1051], [907,909,1051], [1135,907,1051], [1147,1135,1051], [1559,1566,1531], [1514,1559,1531], [1515,1514,1531], [1566,1515,1531], [1525,1559,1517], [1486,1525,1517], [1514,1486,1517], [1559,1514,1517], [1484,1525,1488], [1438,1484,1488], [1486,1438,1488], [1525,1486,1488], [1406,1484,1425], [1363,1406,1425], [1438,1363,1425], [1484,1438,1425], [1514,1515,1506], [1498,1514,1506], [1501,1498,1506], [1515,1501,1506], [1486,1514,1492], [1463,1486,1492], [1498,1463,1492], [1514,1498,1492], [1438,1486,1446], [1408,1438,1446], [1463,1408,1446], [1486,1463,1446], [1363,1438,1386], [1343,1363,1386], [1408,1343,1386], [1438,1408,1386], [1323,1406,1337], [1293,1323,1337], [1363,1293,1337], [1406,1363,1337], [1236,1323,1268], [1220,1236,1268], [1293,1220,1268], [1323,1293,1268], [1135,1236,1182], [1122,1135,1182], [1220,1122,1182], [1236,1220,1182], [907,1135,1035], [905,907,1035], [1122,905,1035], [1135,1122,1035], [1293,1363,1317], [1281,1293,1317], [1343,1281,1317], [1363,1343,1317], [1220,1293,1246], [1208,1220,1246], [1281,1208,1246], [1293,1281,1246], [1122,1220,1172], [1114,1122,1172], [1208,1114,1172], [1220,1208,1172], [905,1122,1026], [903,905,1026], [1114,903,1026], [1122,1114,1026], [700,913,788], [704,700,788], [911,704,788], [913,911,788], [593,700,641], [595,593,641], [704,595,641], [700,704,641], [704,911,793], [708,704,793], [909,708,793], [911,909,793], [595,704,651], [607,595,651], [708,607,651], [704,708,651], [488,593,542], [494,488,542], [595,494,542], [593,595,542], [376,488,438], [382,376,438], [494,382,438], [488,494,438], [494,595,552], [500,494,552], [607,500,552], [595,607,552], [382,494,451], [403,382,451], [500,403,451], [494,500,451], [708,909,804], [718,708,804], [907,718,804], [909,907,804], [607,708,665], [619,607,665], [718,619,665], [708,718,665], [500,607,568], [532,500,568], [619,532,568], [607,619,568], [403,500,471], [449,403,471], [532,449,471], [500,532,471], [312,376,340], [318,312,340], [382,318,340], [376,382,340], [274,312,300], [285,274,300], [318,285,300], [312,318,300], [318,382,350], [327,318,350], [403,327,350], [382,403,350], [285,318,306], [295,285,306], [327,295,306], [318,327,306], [243,274,264], [250,243,264], [285,250,264], [274,285,264], [232,243,239], [237,232,239], [250,237,239], [243,250,239], [250,285,272], [266,250,272], [295,266,272], [285,295,272], [237,250,254], [255,237,254], [266,255,254], [250,266,254], [327,403,378], [371,327,378], [449,371,378], [403,449,378], [295,327,324], [322,295,324], [371,322,324], [327,371,324], [266,295,298], [304,266,298], [322,304,298], [295,322,298], [255,266,287], [296,255,287], [304,296,287], [266,304,287], [718,907,820], [733,718,820], [905,733,820], [907,905,820], [619,718,673], [635,619,673], [733,635,673], [718,733,673], [532,619,587], [562,532,587], [635,562,587], [619,635,587], [449,532,518], [492,449,518], [562,492,518], [532,562,518], [733,905,829], [739,733,829], [903,739,829], [905,903,829], [635,733,683], [645,635,683], [739,645,683], [733,739,683], [562,635,609], [572,562,609], [645,572,609], [635,645,609], [492,562,538], [510,492,538], [572,510,538], [562,572,538], [371,449,430], [417,371,430], [492,417,430], [449,492,430], [322,371,367], [369,322,367], [417,369,367], [371,417,367], [304,322,333], [338,304,333], [369,338,333], [322,369,333], [296,304,316], [334,296,316], [338,334,316], [304,338,316], [417,492,469], [445,417,469], [510,445,469], [492,510,469], [369,417,409], [390,369,409], [445,390,409], [417,445,409], [338,369,363], [355,338,363], [390,355,363], [369,390,363], [334,338,346], [351,334,346], [355,351,346], [338,355,346], [242,232,238], [249,242,238], [237,249,238], [232,237,238], [273,242,263], [284,273,263], [249,284,263], [242,249,263], [249,237,253], [265,249,253], [255,265,253], [237,255,253], [284,249,271], [294,284,271], [265,294,271], [249,265,271], [311,273,299], [317,311,299], [284,317,299], [273,284,299], [375,311,339], [381,375,339], [317,381,339], [311,317,339], [317,284,305], [326,317,305], [294,326,305], [284,294,305], [381,317,349], [402,381,349], [326,402,349], [317,326,349], [265,255,286], [303,265,286], [296,303,286], [255,296,286], [294,265,297], [321,294,297], [303,321,297], [265,303,297], [326,294,323], [370,326,323], [321,370,323], [294,321,323], [402,326,377], [448,402,377], [370,448,377], [326,370,377], [487,375,437], [493,487,437], [381,493,437], [375,381,437], [592,487,541], [594,592,541], [493,594,541], [487,493,541], [493,381,450], [499,493,450], [402,499,450], [381,402,450], [594,493,551], [606,594,551], [499,606,551], [493,499,551], [699,592,640], [703,699,640], [594,703,640], [592,594,640], [912,699,787], [910,912,787], [703,910,787], [699,703,787], [703,594,650], [707,703,650], [606,707,650], [594,606,650], [910,703,792], [908,910,792], [707,908,792], [703,707,792], [499,402,470], [531,499,470], [448,531,470], [402,448,470], [606,499,567], [618,606,567], [531,618,567], [499,531,567], [707,606,664], [719,707,664], [618,719,664], [606,618,664], [908,707,803], [906,908,803], [719,906,803], [707,719,803], [303,296,315], [337,303,315], [334,337,315], [296,334,315], [321,303,332], [368,321,332], [337,368,332], [303,337,332], [370,321,366], [416,370,366], [368,416,366], [321,368,366], [448,370,429], [491,448,429], [416,491,429], [370,416,429], [337,334,345], [354,337,345], [351,354,345], [334,351,345], [368,337,362], [389,368,362], [354,389,362], [337,354,362], [416,368,408], [444,416,408], [389,444,408], [368,389,408], [491,416,468], [509,491,468], [444,509,468], [416,444,468], [531,448,517], [561,531,517], [491,561,517], [448,491,517], [618,531,586], [634,618,586], [561,634,586], [531,561,586], [719,618,672], [732,719,672], [634,732,672], [618,634,672], [906,719,819], [904,906,819], [732,904,819], [719,732,819], [561,491,537], [571,561,537], [509,571,537], [491,509,537], [634,561,608], [644,634,608], [571,644,608], [561,571,608], [732,634,682], [738,732,682], [644,738,682], [634,644,682], [904,732,828], [902,904,828], [738,902,828], [732,738,828], [1154,912,1066], [1150,1154,1066], [910,1150,1066], [912,910,1066], [1261,1154,1213], [1259,1261,1213], [1150,1259,1213], [1154,1150,1213], [1150,910,1061], [1146,1150,1061], [908,1146,1061], [910,908,1061], [1259,1150,1203], [1247,1259,1203], [1146,1247,1203], [1150,1146,1203], [1366,1261,1312], [1360,1366,1312], [1259,1360,1312], [1261,1259,1312], [1478,1366,1416], [1472,1478,1416], [1360,1472,1416], [1366,1360,1416], [1360,1259,1302], [1354,1360,1302], [1247,1354,1302], [1259,1247,1302], [1472,1360,1403], [1451,1472,1403], [1354,1451,1403], [1360,1354,1403], [1146,908,1050], [1136,1146,1050], [906,1136,1050], [908,906,1050], [1247,1146,1189], [1235,1247,1189], [1136,1235,1189], [1146,1136,1189], [1354,1247,1286], [1322,1354,1286], [1235,1322,1286], [1247,1235,1286], [1451,1354,1383], [1405,1451,1383], [1322,1405,1383], [1354,1322,1383], [1534,1478,1511], [1528,1534,1511], [1472,1528,1511], [1478,1472,1511], [1588,1534,1562], [1583,1588,1562], [1528,1583,1562], [1534,1528,1562], [1528,1472,1503], [1520,1528,1503], [1451,1520,1503], [1472,1451,1503], [1583,1528,1556], [1567,1583,1556], [1520,1567,1556], [1528,1520,1556], [1625,1588,1606], [1620,1625,1606], [1583,1620,1606], [1588,1583,1606], [1633,1625,1627], [1629,1633,1627], [1620,1629,1627], [1625,1620,1627], [1620,1583,1592], [1602,1620,1592], [1567,1602,1592], [1583,1567,1592], [1629,1620,1615], [1612,1629,1615], [1602,1612,1615], [1620,1602,1615], [1520,1451,1476], [1483,1520,1476], [1405,1483,1476], [1451,1405,1476], [1567,1520,1522], [1524,1567,1522], [1483,1524,1522], [1520,1483,1522], [1602,1567,1564], [1558,1602,1564], [1524,1558,1564], [1567,1524,1564], [1612,1602,1579], [1566,1612,1579], [1558,1566,1579], [1602,1558,1579], [1136,906,1034], [1121,1136,1034], [904,1121,1034], [906,904,1034], [1235,1136,1181], [1219,1235,1181], [1121,1219,1181], [1136,1121,1181], [1322,1235,1267], [1292,1322,1267], [1219,1292,1267], [1235,1219,1267], [1405,1322,1336], [1362,1405,1336], [1292,1362,1336], [1322,1292,1336], [1121,904,1025], [1113,1121,1025], [902,1113,1025], [904,902,1025], [1219,1121,1171], [1207,1219,1171], [1113,1207,1171], [1121,1113,1171], [1292,1219,1245], [1280,1292,1245], [1207,1280,1245], [1219,1207,1245], [1362,1292,1316], [1342,1362,1316], [1280,1342,1316], [1292,1280,1316], [1483,1405,1424], [1437,1483,1424], [1362,1437,1424], [1405,1362,1424], [1524,1483,1487], [1485,1524,1487], [1437,1485,1487], [1483,1437,1487], [1558,1524,1516], [1513,1558,1516], [1485,1513,1516], [1524,1485,1516], [1566,1558,1530], [1515,1566,1530], [1513,1515,1530], [1558,1513,1530], [1437,1362,1385], [1407,1437,1385], [1342,1407,1385], [1362,1342,1385], [1485,1437,1445], [1462,1485,1445], [1407,1462,1445], [1437,1407,1445], [1513,1485,1491], [1497,1513,1491], [1462,1497,1491], [1485,1462,1491], [1515,1513,1505], [1501,1515,1505], [1497,1501,1505], [1513,1497,1505], [331,325,277], [228,331,277], [231,228,277], [325,231,277], [336,331,279], [224,336,279], [228,224,279], [331,228,279], [228,231,200], [173,228,200], [178,173,200], [231,178,200], [224,228,198], [167,224,198], [173,167,198], [228,173,198], [348,336,281], [222,348,281], [224,222,281], [336,224,281], [352,348,283], [210,352,283], [222,210,283], [348,222,283], [222,224,193], [150,222,193], [167,150,193], [224,167,193], [210,222,183], [142,210,183], [150,142,183], [222,150,183], [177,178,165], [136,177,165], [141,136,165], [178,141,165], [173,177,162], [127,173,162], [136,127,162], [177,136,162], [167,173,158], [131,167,158], [152,131,158], [173,152,158], [131,152,129], [82,131,129], [127,82,129], [152,127,129], [136,141,134], [114,136,134], [121,114,134], [141,121,134], [127,136,118], [93,127,118], [114,93,118], [136,114,118], [114,121,112], [101,114,112], [108,101,112], [121,108,112], [93,114,95], [90,93,95], [101,90,95], [114,101,95], [82,127,88], [59,82,88], [93,59,88], [127,93,88], [59,93,74], [52,59,74], [90,52,74], [93,90,74], [150,167,140], [86,150,140], [131,86,140], [167,131,140], [86,131,84], [50,86,84], [82,50,84], [131,82,84], [148,150,120], [76,148,120], [86,76,120], [150,86,120], [142,148,110], [72,142,110], [76,72,110], [148,76,110], [76,86,65], [36,76,65], [50,36,65], [86,50,65], [72,76,57], [34,72,57], [36,34,57], [76,36,57], [50,82,55], [27,50,55], [59,27,55], [82,59,55], [27,59,42], [18,27,42], [52,18,42], [59,52,42], [36,50,33], [12,36,33], [27,12,33], [50,27,33], [34,36,24], [8,34,24], [12,8,24], [36,12,24], [12,27,16], [2,12,16], [18,2,16], [27,18,16], [8,12,7], [0,8,7], [2,0,7], [12,2,7], [347,352,282], [221,347,282], [210,221,282], [352,210,282], [335,347,280], [223,335,280], [221,223,280], [347,221,280], [221,210,182], [149,221,182], [142,149,182], [210,142,182], [223,221,192], [166,223,192], [149,166,192], [221,149,192], [330,335,278], [227,330,278], [223,227,278], [335,223,278], [325,330,276], [231,325,276], [227,231,276], [330,227,276], [227,223,197], [172,227,197], [166,172,197], [223,166,197], [231,227,199], [178,231,199], [172,178,199], [227,172,199], [147,142,109], [75,147,109], [72,75,109], [142,72,109], [149,147,119], [85,149,119], [75,85,119], [147,75,119], [75,72,56], [35,75,56], [34,35,56], [72,34,56], [85,75,64], [49,85,64], [35,49,64], [75,35,64], [166,149,139], [130,166,139], [85,130,139], [149,85,139], [130,85,83], [81,130,83], [49,81,83], [85,49,83], [35,34,23], [11,35,23], [8,11,23], [34,8,23], [49,35,32], [26,49,32], [11,26,32], [35,11,32], [11,8,6], [1,11,6], [0,1,6], [8,0,6], [26,11,15], [17,26,15], [1,17,15], [11,1,15], [81,49,54], [58,81,54], [26,58,54], [49,26,54], [58,26,41], [51,58,41], [17,51,41], [26,17,41], [172,166,157], [151,172,157], [130,151,157], [166,130,157], [151,130,128], [126,151,128], [81,126,128], [130,81,128], [176,172,161], [135,176,161], [126,135,161], [172,126,161], [178,176,164], [141,178,164], [135,141,164], [176,135,164], [126,81,87], [92,126,87], [58,92,87], [81,58,87], [92,58,73], [89,92,73], [51,89,73], [58,51,73], [135,126,117], [113,135,117], [92,113,117], [126,92,117], [141,135,133], [121,141,133], [113,121,133], [135,113,133], [113,92,94], [100,113,94], [89,100,94], [92,89,94], [121,113,111], [108,121,111], [100,108,111], [113,100,111], [101,108,116], [125,101,116], [132,125,116], [108,132,116], [90,101,103], [105,90,103], [125,105,103], [101,125,103], [52,90,78], [71,52,78], [105,71,78], [90,105,78], [125,132,146], [156,125,146], [163,156,146], [132,163,146], [105,125,144], [154,105,144], [156,154,144], [125,156,144], [71,105,123], [138,71,123], [154,138,123], [105,154,123], [18,52,38], [22,18,38], [63,22,38], [52,63,38], [22,63,48], [40,22,48], [71,40,48], [63,71,48], [2,18,14], [10,2,14], [22,10,14], [18,22,14], [0,2,4], [5,0,4], [10,5,4], [2,10,4], [10,22,29], [31,10,29], [40,31,29], [22,40,29], [5,10,20], [25,5,20], [31,25,20], [10,31,20], [40,71,69], [67,40,69], [97,67,69], [71,97,69], [67,97,99], [107,67,99], [138,107,99], [97,138,99], [31,40,46], [61,31,46], [67,61,46], [40,67,46], [25,31,44], [53,25,44], [61,53,44], [31,61,44], [53,67,80], [91,53,80], [107,91,80], [67,107,80], [154,163,175], [195,154,175], [196,195,175], [163,196,175], [138,154,171], [189,138,171], [195,189,171], [154,195,171], [195,196,202], [207,195,202], [203,207,202], [196,203,202], [205,203,226], [234,205,226], [232,234,226], [203,232,226], [207,205,230], [236,207,230], [234,236,230], [205,234,230], [191,195,209], [241,191,209], [236,241,209], [195,236,209], [189,191,212], [248,189,212], [241,248,212], [191,241,212], [107,138,169], [185,107,169], [189,185,169], [138,189,169], [91,107,160], [179,91,160], [185,179,160], [107,185,160], [187,189,214], [252,187,214], [248,252,214], [189,248,214], [185,187,216], [259,185,216], [252,259,216], [187,252,216], [181,185,218], [261,181,218], [259,261,218], [185,259,218], [179,181,220], [262,179,220], [261,262,220], [181,261,220], [1,0,3], [9,1,3], [5,9,3], [0,5,3], [17,1,13], [21,17,13], [9,21,13], [1,9,13], [9,5,19], [30,9,19], [25,30,19], [5,25,19], [21,9,28], [39,21,28], [30,39,28], [9,30,28], [51,17,37], [62,51,37], [21,62,37], [17,21,37], [62,21,47], [70,62,47], [39,70,47], [21,39,47], [30,25,43], [60,30,43], [53,60,43], [25,53,43], [39,30,45], [66,39,45], [60,66,45], [30,60,45], [66,53,79], [106,66,79], [91,106,79], [53,91,79], [70,39,68], [96,70,68], [66,96,68], [39,66,68], [96,66,98], [137,96,98], [106,137,98], [66,106,98], [89,51,77], [104,89,77], [70,104,77], [51,70,77], [100,89,102], [124,100,102], [104,124,102], [89,104,102], [108,100,115], [132,108,115], [124,132,115], [100,124,115], [104,70,122], [153,104,122], [137,153,122], [70,137,122], [124,104,143], [155,124,143], [153,155,143], [104,153,143], [132,124,145], [163,132,145], [155,163,145], [124,155,145], [106,91,159], [184,106,159], [179,184,159], [91,179,159], [137,106,168], [188,137,168], [184,188,168], [106,184,168], [180,179,219], [260,180,219], [262,260,219], [179,262,219], [184,180,217], [258,184,217], [260,258,217], [180,260,217], [186,184,215], [251,186,215], [258,251,215], [184,258,215], [188,186,213], [247,188,213], [251,247,213], [186,251,213], [153,137,170], [194,153,170], [188,194,170], [137,188,170], [163,153,174], [196,163,174], [194,196,174], [153,194,174], [190,188,211], [240,190,211], [247,240,211], [188,247,211], [194,190,208], [235,194,208], [240,235,208], [190,240,208], [196,194,201], [203,196,201], [206,203,201], [194,206,201], [204,206,229], [233,204,229], [235,233,229], [206,235,229], [203,204,225], [232,203,225], [233,232,225], [204,233,225], [1552,1553,1587], [1632,1552,1587], [1630,1632,1587], [1553,1630,1587], [1550,1552,1591], [1637,1550,1591], [1632,1637,1591], [1552,1632,1591], [1632,1630,1647], [1665,1632,1647], [1663,1665,1647], [1630,1663,1647], [1637,1632,1651], [1673,1637,1651], [1665,1673,1651], [1632,1665,1651], [1548,1550,1595], [1641,1548,1595], [1637,1641,1595], [1550,1637,1595], [1546,1548,1597], [1645,1546,1597], [1641,1645,1597], [1548,1641,1597], [1641,1637,1657], [1679,1641,1657], [1673,1679,1657], [1637,1673,1657], [1645,1641,1660], [1688,1645,1660], [1679,1688,1660], [1641,1679,1660], [1665,1663,1677], [1695,1665,1677], [1693,1695,1677], [1663,1693,1677], [1673,1665,1683], [1705,1673,1683], [1695,1705,1683], [1665,1695,1683], [1695,1693,1707], [1718,1695,1707], [1712,1718,1707], [1693,1712,1707], [1705,1695,1709], [1725,1705,1709], [1718,1725,1709], [1695,1718,1709], [1679,1673,1692], [1714,1679,1692], [1705,1714,1692], [1673,1705,1692], [1688,1679,1703], [1729,1688,1703], [1714,1729,1703], [1679,1714,1703], [1714,1705,1723], [1739,1714,1723], [1725,1739,1723], [1705,1725,1723], [1729,1714,1733], [1752,1729,1733], [1739,1752,1733], [1714,1739,1733], [1544,1546,1605], [1649,1544,1605], [1645,1649,1605], [1546,1645,1605], [1542,1544,1576], [1614,1542,1576], [1609,1614,1576], [1544,1609,1576], [1614,1609,1635], [1653,1614,1635], [1649,1653,1635], [1609,1649,1635], [1649,1645,1669], [1699,1649,1669], [1688,1699,1669], [1645,1688,1669], [1653,1649,1662], [1681,1653,1662], [1675,1681,1662], [1649,1675,1662], [1681,1675,1690], [1711,1681,1690], [1699,1711,1690], [1675,1699,1690], [1540,1542,1578], [1618,1540,1578], [1614,1618,1578], [1542,1614,1578], [1618,1614,1639], [1655,1618,1639], [1653,1655,1639], [1614,1653,1639], [1538,1540,1582], [1619,1538,1582], [1618,1619,1582], [1540,1618,1582], [1619,1618,1643], [1658,1619,1643], [1655,1658,1643], [1618,1655,1643], [1655,1653,1667], [1685,1655,1667], [1681,1685,1667], [1653,1681,1667], [1685,1681,1697], [1720,1685,1697], [1711,1720,1697], [1681,1711,1697], [1658,1655,1671], [1686,1658,1671], [1685,1686,1671], [1655,1685,1671], [1686,1685,1701], [1721,1686,1701], [1720,1721,1701], [1685,1720,1701], [1699,1688,1716], [1743,1699,1716], [1729,1743,1716], [1688,1729,1716], [1711,1699,1727], [1754,1711,1727], [1743,1754,1727], [1699,1743,1727], [1743,1729,1748], [1770,1743,1748], [1752,1770,1748], [1729,1752,1748], [1754,1743,1760], [1786,1754,1760], [1770,1786,1760], [1743,1770,1760], [1720,1711,1735], [1762,1720,1735], [1754,1762,1735], [1711,1754,1735], [1721,1720,1741], [1768,1721,1741], [1762,1768,1741], [1720,1762,1741], [1762,1754,1776], [1796,1762,1776], [1786,1796,1776], [1754,1786,1776], [1768,1762,1782], [1801,1768,1782], [1796,1801,1782], [1762,1796,1782], [1718,1712,1731], [1746,1718,1731], [1744,1746,1731], [1712,1744,1731], [1725,1718,1737], [1758,1725,1737], [1746,1758,1737], [1718,1746,1737], [1739,1725,1750], [1780,1739,1750], [1758,1780,1750], [1725,1758,1750], [1752,1739,1765], [1800,1752,1765], [1780,1800,1765], [1739,1780,1765], [1746,1744,1756], [1772,1746,1756], [1763,1772,1756], [1744,1763,1756], [1758,1746,1767], [1788,1758,1767], [1772,1788,1767], [1746,1772,1767], [1772,1763,1790], [1814,1772,1790], [1806,1814,1790], [1763,1806,1790], [1788,1772,1803], [1832,1788,1803], [1814,1832,1803], [1772,1814,1803], [1780,1758,1784], [1816,1780,1784], [1788,1816,1784], [1758,1788,1784], [1800,1780,1808], [1839,1800,1808], [1816,1839,1808], [1780,1816,1808], [1839,1788,1845], [1898,1839,1845], [1832,1898,1845], [1788,1832,1845], [1770,1752,1774], [1794,1770,1774], [1778,1794,1774], [1752,1778,1774], [1786,1770,1792], [1810,1786,1792], [1794,1810,1792], [1770,1794,1792], [1794,1778,1798], [1822,1794,1798], [1800,1822,1798], [1778,1800,1798], [1810,1794,1818], [1843,1810,1818], [1822,1843,1818], [1794,1822,1818], [1796,1786,1805], [1824,1796,1805], [1810,1824,1805], [1786,1810,1805], [1801,1796,1812], [1825,1801,1812], [1824,1825,1812], [1796,1824,1812], [1824,1810,1830], [1861,1824,1830], [1843,1861,1830], [1810,1843,1830], [1825,1824,1841], [1870,1825,1841], [1861,1870,1841], [1824,1861,1841], [1822,1800,1828], [1874,1822,1828], [1839,1874,1828], [1800,1839,1828], [1843,1822,1859], [1892,1843,1859], [1874,1892,1859], [1822,1874,1859], [1892,1839,1886], [1911,1892,1886], [1878,1911,1886], [1839,1878,1886], [1911,1878,1909], [1935,1911,1909], [1898,1935,1909], [1878,1898,1909], [1861,1843,1880], [1902,1861,1880], [1892,1902,1880], [1843,1892,1880], [1870,1861,1890], [1905,1870,1890], [1902,1905,1890], [1861,1902,1890], [1902,1892,1907], [1923,1902,1907], [1911,1923,1907], [1892,1911,1907], [1923,1911,1930], [1949,1923,1930], [1935,1949,1930], [1911,1935,1930], [1905,1902,1913], [1926,1905,1913], [1923,1926,1913], [1902,1923,1913], [1926,1923,1939], [1952,1926,1939], [1949,1952,1939], [1923,1949,1939], [1539,1538,1581], [1617,1539,1581], [1619,1617,1581], [1538,1619,1581], [1617,1619,1642], [1654,1617,1642], [1658,1654,1642], [1619,1658,1642], [1541,1539,1577], [1613,1541,1577], [1617,1613,1577], [1539,1617,1577], [1613,1617,1638], [1652,1613,1638], [1654,1652,1638], [1617,1654,1638], [1654,1658,1670], [1684,1654,1670], [1686,1684,1670], [1658,1686,1670], [1684,1686,1700], [1719,1684,1700], [1721,1719,1700], [1686,1721,1700], [1652,1654,1666], [1680,1652,1666], [1684,1680,1666], [1654,1684,1666], [1680,1684,1696], [1710,1680,1696], [1719,1710,1696], [1684,1719,1696], [1543,1541,1575], [1608,1543,1575], [1613,1608,1575], [1541,1613,1575], [1608,1613,1634], [1648,1608,1634], [1652,1648,1634], [1613,1652,1634], [1545,1543,1604], [1644,1545,1604], [1648,1644,1604], [1543,1648,1604], [1648,1652,1661], [1674,1648,1661], [1680,1674,1661], [1652,1680,1661], [1674,1680,1689], [1698,1674,1689], [1710,1698,1689], [1680,1710,1689], [1644,1648,1668], [1687,1644,1668], [1698,1687,1668], [1648,1698,1668], [1719,1721,1740], [1761,1719,1740], [1768,1761,1740], [1721,1768,1740], [1710,1719,1734], [1753,1710,1734], [1761,1753,1734], [1719,1761,1734], [1761,1768,1781], [1795,1761,1781], [1801,1795,1781], [1768,1801,1781], [1753,1761,1775], [1785,1753,1775], [1795,1785,1775], [1761,1795,1775], [1698,1710,1726], [1742,1698,1726], [1753,1742,1726], [1710,1753,1726], [1687,1698,1715], [1728,1687,1715], [1742,1728,1715], [1698,1742,1715], [1742,1753,1759], [1769,1742,1759], [1785,1769,1759], [1753,1785,1759], [1728,1742,1747], [1751,1728,1747], [1769,1751,1747], [1742,1769,1747], [1547,1545,1596], [1640,1547,1596], [1644,1640,1596], [1545,1644,1596], [1549,1547,1594], [1636,1549,1594], [1640,1636,1594], [1547,1640,1594], [1640,1644,1659], [1678,1640,1659], [1687,1678,1659], [1644,1687,1659], [1636,1640,1656], [1672,1636,1656], [1678,1672,1656], [1640,1678,1656], [1551,1549,1590], [1631,1551,1590], [1636,1631,1590], [1549,1636,1590], [1553,1551,1586], [1630,1553,1586], [1631,1630,1586], [1551,1631,1586], [1631,1636,1650], [1664,1631,1650], [1672,1664,1650], [1636,1672,1650], [1630,1631,1646], [1663,1630,1646], [1664,1663,1646], [1631,1664,1646], [1678,1687,1702], [1713,1678,1702], [1728,1713,1702], [1687,1728,1702], [1672,1678,1691], [1704,1672,1691], [1713,1704,1691], [1678,1713,1691], [1713,1728,1732], [1738,1713,1732], [1751,1738,1732], [1728,1751,1732], [1704,1713,1722], [1724,1704,1722], [1738,1724,1722], [1713,1738,1722], [1664,1672,1682], [1694,1664,1682], [1704,1694,1682], [1672,1704,1682], [1663,1664,1676], [1693,1663,1676], [1694,1693,1676], [1664,1694,1676], [1694,1704,1708], [1717,1694,1708], [1724,1717,1708], [1704,1724,1708], [1693,1694,1706], [1712,1693,1706], [1717,1712,1706], [1694,1717,1706], [1795,1801,1811], [1823,1795,1811], [1825,1823,1811], [1801,1825,1811], [1785,1795,1804], [1809,1785,1804], [1823,1809,1804], [1795,1823,1804], [1823,1825,1840], [1860,1823,1840], [1870,1860,1840], [1825,1870,1840], [1809,1823,1829], [1842,1809,1829], [1860,1842,1829], [1823,1860,1829], [1769,1785,1791], [1793,1769,1791], [1809,1793,1791], [1785,1809,1791], [1751,1769,1773], [1777,1751,1773], [1793,1777,1773], [1769,1793,1773], [1793,1809,1817], [1821,1793,1817], [1842,1821,1817], [1809,1842,1817], [1777,1793,1797], [1799,1777,1797], [1821,1799,1797], [1793,1821,1797], [1860,1870,1889], [1901,1860,1889], [1905,1901,1889], [1870,1905,1889], [1842,1860,1879], [1891,1842,1879], [1901,1891,1879], [1860,1901,1879], [1901,1905,1912], [1922,1901,1912], [1926,1922,1912], [1905,1926,1912], [1922,1926,1938], [1948,1922,1938], [1952,1948,1938], [1926,1952,1938], [1891,1901,1906], [1910,1891,1906], [1922,1910,1906], [1901,1922,1906], [1910,1922,1929], [1934,1910,1929], [1948,1934,1929], [1922,1948,1929], [1821,1842,1858], [1873,1821,1858], [1891,1873,1858], [1842,1891,1858], [1799,1821,1827], [1838,1799,1827], [1873,1838,1827], [1821,1873,1827], [1838,1891,1885], [1877,1838,1885], [1910,1877,1885], [1891,1910,1885], [1877,1910,1908], [1895,1877,1908], [1934,1895,1908], [1910,1934,1908], [1738,1751,1764], [1779,1738,1764], [1799,1779,1764], [1751,1799,1764], [1724,1738,1749], [1757,1724,1749], [1779,1757,1749], [1738,1779,1749], [1717,1724,1736], [1745,1717,1736], [1757,1745,1736], [1724,1757,1736], [1712,1717,1730], [1744,1712,1730], [1745,1744,1730], [1717,1745,1730], [1779,1799,1807], [1815,1779,1807], [1838,1815,1807], [1799,1838,1807], [1757,1779,1783], [1787,1757,1783], [1815,1787,1783], [1779,1815,1783], [1787,1838,1844], [1831,1787,1844], [1895,1831,1844], [1838,1895,1844], [1745,1757,1766], [1771,1745,1766], [1787,1771,1766], [1757,1787,1766], [1744,1745,1755], [1763,1744,1755], [1771,1763,1755], [1745,1771,1755], [1771,1787,1802], [1813,1771,1802], [1831,1813,1802], [1787,1831,1802], [1763,1771,1789], [1806,1763,1789], [1813,1806,1789], [1771,1813,1789], [1814,1806,1820], [1836,1814,1820], [1826,1836,1820], [1806,1826,1820], [1832,1814,1834], [1872,1832,1834], [1836,1872,1834], [1814,1836,1834], [1898,1832,1888], [1915,1898,1888], [1872,1915,1888], [1832,1872,1888], [1836,1826,1847], [1857,1836,1847], [1850,1857,1847], [1826,1850,1847], [1872,1836,1863], [1882,1872,1863], [1857,1882,1863], [1836,1857,1863], [1915,1872,1900], [1919,1915,1900], [1882,1919,1900], [1872,1882,1900], [1935,1898,1928], [1954,1935,1928], [1915,1954,1928], [1898,1915,1928], [1949,1935,1951], [1969,1949,1951], [1954,1969,1951], [1935,1954,1951], [1952,1949,1962], [1974,1952,1962], [1969,1974,1962], [1949,1969,1962], [1954,1915,1941], [1958,1954,1941], [1919,1958,1941], [1915,1919,1941], [1969,1954,1965], [1971,1969,1965], [1958,1971,1965], [1954,1958,1965], [1974,1969,1973], [1975,1974,1973], [1971,1975,1973], [1969,1971,1973], [1857,1850,1855], [1867,1857,1855], [1853,1867,1855], [1850,1853,1855], [1882,1857,1876], [1884,1882,1876], [1867,1884,1876], [1857,1867,1876], [1919,1882,1904], [1917,1919,1904], [1884,1917,1904], [1882,1884,1904], [1867,1853,1852], [1849,1867,1852], [1837,1849,1852], [1853,1837,1852], [1884,1867,1869], [1865,1884,1869], [1849,1865,1869], [1867,1849,1869], [1917,1884,1894], [1897,1917,1894], [1865,1897,1894], [1884,1865,1894], [1958,1919,1937], [1947,1958,1937], [1917,1947,1937], [1919,1917,1937], [1971,1958,1960], [1956,1971,1960], [1947,1956,1960], [1958,1947,1960], [1975,1971,1967], [1963,1975,1967], [1956,1963,1967], [1971,1956,1967], [1947,1917,1921], [1925,1947,1921], [1897,1925,1921], [1917,1897,1921], [1956,1947,1943], [1932,1956,1943], [1925,1932,1943], [1947,1925,1943], [1963,1956,1945], [1933,1963,1945], [1932,1933,1945], [1956,1932,1945], [1948,1952,1961], [1968,1948,1961], [1974,1968,1961], [1952,1974,1961], [1934,1948,1950], [1953,1934,1950], [1968,1953,1950], [1948,1968,1950], [1895,1934,1927], [1914,1895,1927], [1953,1914,1927], [1934,1953,1927], [1968,1974,1972], [1970,1968,1972], [1975,1970,1972], [1974,1975,1972], [1953,1968,1964], [1957,1953,1964], [1970,1957,1964], [1968,1970,1964], [1914,1953,1940], [1918,1914,1940], [1957,1918,1940], [1953,1957,1940], [1831,1895,1887], [1871,1831,1887], [1914,1871,1887], [1895,1914,1887], [1813,1831,1833], [1835,1813,1833], [1871,1835,1833], [1831,1871,1833], [1806,1813,1819], [1826,1806,1819], [1835,1826,1819], [1813,1835,1819], [1871,1914,1899], [1881,1871,1899], [1918,1881,1899], [1914,1918,1899], [1835,1871,1862], [1856,1835,1862], [1881,1856,1862], [1871,1881,1862], [1826,1835,1846], [1850,1826,1846], [1856,1850,1846], [1835,1856,1846], [1970,1975,1966], [1955,1970,1966], [1963,1955,1966], [1975,1963,1966], [1957,1970,1959], [1946,1957,1959], [1955,1946,1959], [1970,1955,1959], [1918,1957,1936], [1916,1918,1936], [1946,1916,1936], [1957,1946,1936], [1955,1963,1944], [1931,1955,1944], [1933,1931,1944], [1963,1933,1944], [1946,1955,1942], [1924,1946,1942], [1931,1924,1942], [1955,1931,1942], [1916,1946,1920], [1896,1916,1920], [1924,1896,1920], [1946,1924,1920], [1881,1918,1903], [1883,1881,1903], [1916,1883,1903], [1918,1916,1903], [1856,1881,1875], [1866,1856,1875], [1883,1866,1875], [1881,1883,1875], [1850,1856,1854], [1853,1850,1854], [1866,1853,1854], [1856,1866,1854], [1883,1916,1893], [1864,1883,1893], [1896,1864,1893], [1916,1896,1893], [1866,1883,1868], [1848,1866,1868], [1864,1848,1868], [1883,1864,1868], [1853,1866,1851], [1837,1853,1851], [1848,1837,1851], [1866,1848,1851], [1069,952,992], [1072,1069,992], [952,1072,992], [1069,1072,1094], [1118,1069,1094], [1134,1118,1094], [1072,1134,1094], [1030,952,984], [1069,1030,984], [952,1069,984], [1030,1069,1076], [1080,1030,1076], [1118,1080,1076], [1069,1118,1076], [1118,1134,1133], [1131,1118,1133], [1139,1131,1133], [1134,1139,1133], [1131,1139,1129], [1110,1131,1129], [1127,1110,1129], [1139,1127,1129], [1080,1118,1104], [1088,1080,1104], [1131,1088,1104], [1118,1131,1104], [1088,1131,1096], [1074,1088,1096], [1110,1074,1096], [1131,1110,1096], [980,952,964], [1030,980,964], [952,1030,964], [980,1030,1028], [1002,980,1028], [1080,1002,1028], [1030,1080,1028], [951,952,954], [980,951,954], [952,980,954], [951,980,962], [949,951,962], [1002,949,962], [980,1002,962], [1002,1080,1059], [1012,1002,1059], [1088,1012,1059], [1080,1088,1059], [1012,1088,1053], [998,1012,1053], [1074,998,1053], [1088,1074,1053], [949,1002,974], [947,949,974], [1012,947,974], [1002,1012,974], [947,1012,972], [945,947,972], [998,945,972], [1012,998,972], [1110,1127,1082], [1047,1110,1082], [1060,1047,1082], [1127,1060,1082], [1074,1110,1071], [1004,1074,1071], [1047,1004,1071], [1110,1047,1071], [1047,1060,1039], [1024,1047,1039], [1031,1024,1039], [1060,1031,1039], [1024,1031,1041], [1049,1024,1041], [1063,1049,1041], [1031,1063,1041], [1004,1047,1018], [994,1004,1018], [1024,994,1018], [1047,1024,1018], [994,1024,1020], [1010,994,1020], [1049,1010,1020], [1024,1049,1020], [998,1074,1014], [976,998,1014], [1004,976,1014], [1074,1004,1014], [945,998,960], [943,945,960], [976,943,960], [998,976,960], [976,1004,986], [970,976,986], [994,970,986], [1004,994,986], [970,994,990], [978,970,990], [1010,978,990], [994,1010,990], [943,976,956], [941,943,956], [970,941,956], [976,970,956], [941,970,958], [939,941,958], [978,939,958], [970,978,958], [875,952,901], [951,875,901], [952,951,901], [875,951,893], [853,875,893], [949,853,893], [951,949,893], [825,952,891], [875,825,891], [952,875,891], [825,875,827], [775,825,827], [853,775,827], [875,853,827], [853,949,881], [843,853,881], [947,843,881], [949,947,881], [843,947,883], [857,843,883], [945,857,883], [947,945,883], [775,853,796], [767,775,796], [843,767,796], [853,843,796], [767,843,802], [781,767,802], [857,781,802], [843,857,802], [786,952,871], [825,786,871], [952,825,871], [786,825,779], [737,786,779], [775,737,779], [825,775,779], [782,952,863], [786,782,863], [952,786,863], [782,786,761], [720,782,761], [737,720,761], [786,737,761], [737,775,751], [724,737,751], [767,724,751], [775,767,751], [724,767,759], [745,724,759], [781,745,759], [767,781,759], [720,737,722], [715,720,722], [724,715,722], [737,724,722], [715,724,726], [727,715,726], [745,727,726], [724,745,726], [857,945,895], [879,857,895], [943,879,895], [945,943,895], [781,857,841], [851,781,841], [879,851,841], [857,879,841], [879,943,899], [885,879,899], [941,885,899], [943,941,899], [885,941,897], [877,885,897], [939,877,897], [941,939,897], [851,879,869], [861,851,869], [885,861,869], [879,885,869], [861,885,865], [845,861,865], [877,845,865], [885,877,865], [745,781,784], [808,745,784], [851,808,784], [781,851,784], [727,745,773], [794,727,773], [808,794,773], [745,808,773], [808,851,837], [831,808,837], [861,831,837], [851,861,837], [831,861,835], [806,831,835], [845,806,835], [861,845,835], [794,808,816], [823,794,816], [831,823,816], [808,831,816], [823,831,814], [791,823,814], [806,791,814], [831,806,814], [785,952,862], [782,785,862], [952,782,862], [785,782,760], [736,785,760], [720,736,760], [782,720,760], [824,952,870], [785,824,870], [952,785,870], [824,785,778], [774,824,778], [736,774,778], [785,736,778], [736,720,721], [723,736,721], [715,723,721], [720,715,721], [723,715,725], [744,723,725], [727,744,725], [715,727,725], [774,736,750], [766,774,750], [723,766,750], [736,723,750], [766,723,758], [780,766,758], [744,780,758], [723,744,758], [874,952,890], [824,874,890], [952,824,890], [874,824,826], [852,874,826], [774,852,826], [824,774,826], [950,952,900], [874,950,900], [952,874,900], [950,874,892], [948,950,892], [852,948,892], [874,852,892], [852,774,795], [842,852,795], [766,842,795], [774,766,795], [842,766,801], [856,842,801], [780,856,801], [766,780,801], [948,852,880], [946,948,880], [842,946,880], [852,842,880], [946,842,882], [944,946,882], [856,944,882], [842,856,882], [744,727,772], [807,744,772], [794,807,772], [727,794,772], [780,744,783], [850,780,783], [807,850,783], [744,807,783], [807,794,815], [830,807,815], [823,830,815], [794,823,815], [830,823,813], [805,830,813], [791,805,813], [823,791,813], [850,807,836], [860,850,836], [830,860,836], [807,830,836], [860,830,834], [844,860,834], [805,844,834], [830,805,834], [856,780,840], [878,856,840], [850,878,840], [780,850,840], [944,856,894], [942,944,894], [878,942,894], [856,878,894], [878,850,868], [884,878,868], [860,884,868], [850,860,868], [884,860,864], [876,884,864], [844,876,864], [860,844,864], [942,878,898], [940,942,898], [884,940,898], [878,884,898], [940,884,896], [938,940,896], [876,938,896], [884,876,896], [979,952,953], [950,979,953], [952,950,953], [979,950,961], [1001,979,961], [948,1001,961], [950,948,961], [1029,952,963], [979,1029,963], [952,979,963], [1029,979,1027], [1079,1029,1027], [1001,1079,1027], [979,1001,1027], [1001,948,973], [1011,1001,973], [946,1011,973], [948,946,973], [1011,946,971], [997,1011,971], [944,997,971], [946,944,971], [1079,1001,1058], [1087,1079,1058], [1011,1087,1058], [1001,1011,1058], [1087,1011,1052], [1073,1087,1052], [997,1073,1052], [1011,997,1052], [1068,952,983], [1029,1068,983], [952,1029,983], [1068,1029,1075], [1117,1068,1075], [1079,1117,1075], [1029,1079,1075], [1072,952,991], [1068,1072,991], [952,1068,991], [1072,1068,1093], [1134,1072,1093], [1117,1134,1093], [1068,1117,1093], [1117,1079,1103], [1130,1117,1103], [1087,1130,1103], [1079,1087,1103], [1130,1087,1095], [1109,1130,1095], [1073,1109,1095], [1087,1073,1095], [1134,1117,1132], [1139,1134,1132], [1130,1139,1132], [1117,1130,1132], [1139,1130,1128], [1127,1139,1128], [1109,1127,1128], [1130,1109,1128], [997,944,959], [975,997,959], [942,975,959], [944,942,959], [1073,997,1013], [1003,1073,1013], [975,1003,1013], [997,975,1013], [975,942,955], [969,975,955], [940,969,955], [942,940,955], [969,940,957], [977,969,957], [938,977,957], [940,938,957], [1003,975,985], [993,1003,985], [969,993,985], [975,969,985], [993,969,989], [1009,993,989], [977,1009,989], [969,977,989], [1109,1073,1070], [1046,1109,1070], [1003,1046,1070], [1073,1003,1070], [1127,1109,1081], [1060,1127,1081], [1046,1060,1081], [1109,1046,1081], [1046,1003,1017], [1023,1046,1017], [993,1023,1017], [1003,993,1017], [1023,993,1019], [1048,1023,1019], [1009,1048,1019], [993,1009,1019], [1060,1046,1038], [1031,1060,1038], [1023,1031,1038], [1046,1023,1038], [1031,1023,1040], [1063,1031,1040], [1048,1063,1040], [1023,1048,1040], [1049,1063,1120], [1161,1049,1120], [1170,1161,1120], [1063,1170,1120], [1010,1049,1092], [1126,1010,1092], [1161,1126,1092], [1049,1161,1092], [1165,1170,1224], [1272,1165,1224], [1279,1272,1224], [1170,1279,1224], [1161,1165,1216], [1250,1161,1216], [1272,1250,1216], [1165,1272,1216], [1141,1161,1196], [1234,1141,1196], [1250,1234,1196], [1161,1250,1196], [1126,1141,1178], [1206,1126,1178], [1234,1206,1178], [1141,1234,1178], [978,1010,1045], [1043,978,1045], [1126,1043,1045], [1010,1126,1045], [939,978,966], [937,939,966], [1043,937,966], [978,1043,966], [1084,1126,1153], [1174,1084,1153], [1206,1174,1153], [1126,1206,1153], [1043,1084,1112], [1124,1043,1112], [1174,1124,1112], [1084,1174,1112], [982,1043,1055], [1033,982,1055], [1124,1033,1055], [1043,1124,1055], [937,982,968], [935,937,968], [1033,935,968], [982,1033,968], [1272,1279,1321], [1369,1272,1321], [1376,1369,1321], [1279,1376,1321], [1250,1272,1309], [1347,1250,1309], [1369,1347,1309], [1272,1369,1309], [1234,1250,1285], [1315,1234,1285], [1347,1315,1285], [1250,1347,1285], [1206,1234,1252], [1278,1206,1252], [1315,1278,1252], [1234,1315,1252], [1369,1376,1388], [1402,1369,1388], [1415,1402,1388], [1376,1415,1388], [1347,1369,1375], [1378,1347,1375], [1402,1378,1375], [1369,1402,1375], [1402,1415,1419], [1423,1402,1419], [1434,1423,1419], [1415,1434,1419], [1378,1402,1396], [1390,1378,1396], [1423,1390,1396], [1402,1423,1396], [1315,1347,1339], [1335,1315,1339], [1378,1335,1339], [1347,1378,1339], [1278,1315,1305], [1295,1278,1305], [1335,1295,1305], [1315,1335,1305], [1335,1378,1365], [1353,1335,1365], [1390,1353,1365], [1378,1390,1365], [1295,1335,1325], [1301,1295,1325], [1353,1301,1325], [1335,1353,1325], [1174,1206,1222], [1226,1174,1222], [1278,1226,1222], [1206,1278,1222], [1124,1174,1176], [1169,1124,1176], [1226,1169,1176], [1174,1226,1176], [1033,1124,1108], [1078,1033,1108], [1169,1078,1108], [1124,1169,1108], [935,1033,988], [931,935,988], [1078,931,988], [1033,1078,988], [1226,1278,1256], [1240,1226,1256], [1295,1240,1256], [1278,1295,1256], [1169,1226,1202], [1180,1169,1202], [1240,1180,1202], [1226,1240,1202], [1240,1295,1274], [1244,1240,1274], [1301,1244,1274], [1295,1301,1274], [1180,1240,1218], [1186,1180,1218], [1244,1186,1218], [1240,1244,1218], [1078,1169,1138], [1086,1078,1138], [1180,1086,1138], [1169,1180,1138], [931,1078,996], [925,931,996], [1086,925,996], [1078,1086,996], [1086,1180,1145], [1090,1086,1145], [1186,1090,1145], [1180,1186,1145], [925,1086,1000], [921,925,1000], [1090,921,1000], [1086,1090,1000], [877,939,889], [812,877,889], [937,812,889], [939,937,889], [845,877,810], [729,845,810], [812,729,810], [877,812,810], [873,937,887], [822,873,887], [935,822,887], [937,935,887], [812,873,800], [731,812,800], [822,731,800], [873,822,800], [771,812,743], [681,771,743], [731,681,743], [812,731,743], [729,771,702], [649,729,702], [681,649,702], [771,681,702], [806,845,763], [694,806,763], [729,694,763], [845,729,763], [791,806,735], [684,791,735], [694,684,735], [806,694,735], [714,729,677], [621,714,677], [649,621,677], [729,649,677], [694,714,659], [605,694,659], [621,605,659], [714,621,659], [690,694,639], [583,690,639], [605,583,639], [694,605,639], [684,690,631], [575,684,631], [583,575,631], [690,583,631], [822,935,867], [777,822,867], [931,777,867], [935,931,867], [731,822,747], [686,731,747], [777,686,747], [822,777,747], [681,731,679], [629,681,679], [686,629,679], [731,686,679], [649,681,633], [577,649,633], [629,577,633], [681,629,633], [777,931,859], [769,777,859], [925,769,859], [931,925,859], [686,777,717], [675,686,717], [769,675,717], [777,769,717], [769,925,855], [765,769,855], [921,765,855], [925,921,855], [675,769,710], [669,675,710], [765,669,710], [769,765,710], [629,686,653], [615,629,653], [675,615,653], [686,675,653], [577,629,599], [560,577,599], [615,560,599], [629,615,599], [615,675,637], [611,615,637], [669,611,637], [675,669,637], [560,615,581], [554,560,581], [611,554,581], [615,611,581], [621,649,603], [540,621,603], [577,540,603], [649,577,603], [605,621,570], [508,605,570], [540,508,570], [621,540,570], [583,605,546], [486,583,546], [508,486,546], [605,508,546], [575,583,534], [478,575,534], [486,478,534], [583,486,534], [540,577,550], [520,540,550], [560,520,550], [577,560,550], [508,540,516], [477,508,516], [520,477,516], [540,520,516], [520,560,530], [502,520,530], [554,502,530], [560,554,530], [477,520,490], [465,477,490], [502,465,490], [520,502,490], [486,508,480], [453,486,480], [477,453,480], [508,477,480], [478,486,467], [439,478,467], [453,439,467], [486,453,467], [453,477,459], [432,453,459], [465,432,459], [477,465,459], [439,453,436], [420,439,436], [432,420,436], [453,432,436], [805,791,734], [693,805,734], [684,693,734], [791,684,734], [844,805,762], [728,844,762], [693,728,762], [805,693,762], [689,684,630], [582,689,630], [575,582,630], [684,575,630], [693,689,638], [604,693,638], [582,604,638], [689,582,638], [713,693,658], [620,713,658], [604,620,658], [693,604,658], [728,713,676], [648,728,676], [620,648,676], [713,620,676], [876,844,809], [811,876,809], [728,811,809], [844,728,809], [938,876,888], [936,938,888], [811,936,888], [876,811,888], [770,728,701], [680,770,701], [648,680,701], [728,648,701], [811,770,742], [730,811,742], [680,730,742], [770,680,742], [872,811,799], [821,872,799], [730,821,799], [811,730,799], [936,872,886], [934,936,886], [821,934,886], [872,821,886], [582,575,533], [485,582,533], [478,485,533], [575,478,533], [604,582,545], [507,604,545], [485,507,545], [582,485,545], [620,604,569], [539,620,569], [507,539,569], [604,507,569], [648,620,602], [576,648,602], [539,576,602], [620,539,602], [485,478,466], [452,485,466], [439,452,466], [478,439,466], [507,485,479], [476,507,479], [452,476,479], [485,452,479], [452,439,435], [431,452,435], [420,431,435], [439,420,435], [476,452,458], [464,476,458], [431,464,458], [452,431,458], [539,507,515], [519,539,515], [476,519,515], [507,476,515], [576,539,549], [559,576,549], [519,559,549], [539,519,549], [519,476,489], [501,519,489], [464,501,489], [476,464,489], [559,519,529], [553,559,529], [501,553,529], [519,501,529], [680,648,632], [628,680,632], [576,628,632], [648,576,632], [730,680,678], [685,730,678], [628,685,678], [680,628,678], [821,730,746], [776,821,746], [685,776,746], [730,685,746], [934,821,866], [930,934,866], [776,930,866], [821,776,866], [628,576,598], [614,628,598], [559,614,598], [576,559,598], [685,628,652], [674,685,652], [614,674,652], [628,614,652], [614,559,580], [610,614,580], [553,610,580], [559,553,580], [674,614,636], [668,674,636], [610,668,636], [614,610,636], [776,685,716], [768,776,716], [674,768,716], [685,674,716], [930,776,858], [924,930,858], [768,924,858], [776,768,858], [768,674,709], [764,768,709], [668,764,709], [674,668,709], [924,768,854], [920,924,854], [764,920,854], [768,764,854], [977,938,965], [1042,977,965], [936,1042,965], [938,936,965], [1009,977,1044], [1125,1009,1044], [1042,1125,1044], [977,1042,1044], [981,936,967], [1032,981,967], [934,1032,967], [936,934,967], [1042,981,1054], [1123,1042,1054], [1032,1123,1054], [981,1032,1054], [1083,1042,1111], [1173,1083,1111], [1123,1173,1111], [1042,1123,1111], [1125,1083,1152], [1205,1125,1152], [1173,1205,1152], [1083,1173,1152], [1048,1009,1091], [1160,1048,1091], [1125,1160,1091], [1009,1125,1091], [1063,1048,1119], [1170,1063,1119], [1160,1170,1119], [1048,1160,1119], [1140,1125,1177], [1233,1140,1177], [1205,1233,1177], [1125,1205,1177], [1160,1140,1195], [1249,1160,1195], [1233,1249,1195], [1140,1233,1195], [1164,1160,1215], [1271,1164,1215], [1249,1271,1215], [1160,1249,1215], [1170,1164,1223], [1279,1170,1223], [1271,1279,1223], [1164,1271,1223], [1032,934,987], [1077,1032,987], [930,1077,987], [934,930,987], [1123,1032,1107], [1168,1123,1107], [1077,1168,1107], [1032,1077,1107], [1173,1123,1175], [1225,1173,1175], [1168,1225,1175], [1123,1168,1175], [1205,1173,1221], [1277,1205,1221], [1225,1277,1221], [1173,1225,1221], [1077,930,995], [1085,1077,995], [924,1085,995], [930,924,995], [1168,1077,1137], [1179,1168,1137], [1085,1179,1137], [1077,1085,1137], [1085,924,999], [1089,1085,999], [920,1089,999], [924,920,999], [1179,1085,1144], [1185,1179,1144], [1089,1185,1144], [1085,1089,1144], [1225,1168,1201], [1239,1225,1201], [1179,1239,1201], [1168,1179,1201], [1277,1225,1255], [1294,1277,1255], [1239,1294,1255], [1225,1239,1255], [1239,1179,1217], [1243,1239,1217], [1185,1243,1217], [1179,1185,1217], [1294,1239,1273], [1300,1294,1273], [1243,1300,1273], [1239,1243,1273], [1233,1205,1251], [1314,1233,1251], [1277,1314,1251], [1205,1277,1251], [1249,1233,1284], [1346,1249,1284], [1314,1346,1284], [1233,1314,1284], [1271,1249,1308], [1368,1271,1308], [1346,1368,1308], [1249,1346,1308], [1279,1271,1320], [1376,1279,1320], [1368,1376,1320], [1271,1368,1320], [1314,1277,1304], [1334,1314,1304], [1294,1334,1304], [1277,1294,1304], [1346,1314,1338], [1377,1346,1338], [1334,1377,1338], [1314,1334,1338], [1334,1294,1324], [1352,1334,1324], [1300,1352,1324], [1294,1300,1324], [1377,1334,1364], [1389,1377,1364], [1352,1389,1364], [1334,1352,1364], [1368,1346,1374], [1401,1368,1374], [1377,1401,1374], [1346,1377,1374], [1376,1368,1387], [1415,1376,1387], [1401,1415,1387], [1368,1401,1387], [1401,1377,1395], [1422,1401,1395], [1389,1422,1395], [1377,1389,1395], [1415,1401,1418], [1434,1415,1418], [1422,1434,1418], [1401,1422,1418] ]; var calculateNormals = function(positions, indices) { var nvecs = new Array(positions.length); for (var i = 0; i < indices.length; i++) { var j0 = indices[i][0]; var j1 = indices[i][1]; var j2 = indices[i][2]; var v1 = positions[j0]; var v2 = positions[j1]; var v3 = positions[j2]; v2 = SceneJS_math_subVec4(v2, v1, [0,0,0,0]); v3 = SceneJS_math_subVec4(v3, v1, [0,0,0,0]); var n = SceneJS_math_normalizeVec4(SceneJS_math_cross3Vec4(v2, v3, [0,0,0,0]), [0,0,0,0]); if (!nvecs[j0]) nvecs[j0] = []; if (!nvecs[j1]) nvecs[j1] = []; if (!nvecs[j2]) nvecs[j2] = []; nvecs[j0].push(n); nvecs[j1].push(n); nvecs[j2].push(n); } var normals = new Array(positions.length); // now go through and average out everything for (var i = 0; i < nvecs.length; i++) { var count = nvecs[i].length; var x = 0; var y = 0; var z = 0; for (var j = 0; j < count; j++) { x += nvecs[i][j][0]; y += nvecs[i][j][1]; z += nvecs[i][j][2]; } normals[i] = [-(x / count), -(y / count), -(z / count)]; } return normals; }; // @private var flatten = function (ar, numPerElement) { var result = []; for (var i = 0; i < ar.length; i++) { if (numPerElement && ar[i].length != numPerElement) throw SceneJS_errorModule.fatalError("Bad geometry array element"); for (var j = 0; j < ar[i].length; j++) result.push(ar[i][j]); } return result; }; return { primitive:"triangles", positions: flatten(positions, 3), indices:flatten(indices, 3), normals:flatten(calculateNormals(positions, indices), 3) }; }; var Teapot = SceneJS.createNodeType("teapot", "geometry"); Teapot.prototype._init = function() { this.attr.type = "teapot"; SceneJS_geometry.prototype._init.call(this, { /* Resource ID ensures that we save memory by reusing any teapot that has already been created */ coreId : "teapot", create: create }); }; })(); (function() { var Box = SceneJS.createNodeType("box", "geometry"); Box.prototype._init = function(params) { this.attr.type = "box"; var x = params.xSize || 1; var y = params.ySize || 1; var z = params.zSize || 1; var solid = (params.solid != undefined) ? params.solid : true; SceneJS_geometry.prototype._init.call(this, { /* Resource ID ensures that we reuse any sbox that has already been created with * these parameters instead of wasting memory */ resource : "box_" + x + "_" + y + "_" + z + (solid ? "_solid" : "wire"), /* Callback that does the creation in case we can't find matching box to reuse */ create: function() { var positions = [ x, y, z, -x, y, z, -x,-y, z, x,-y, z, // v0-v1-v2-v3 front x, y, z, x,-y, z, x,-y,-z, x, y,-z, // v0-v3-v4-v5 right x, y, z, x, y,-z, -x, y,-z, -x, y, z, // v0-v5-v6-v1 top -x, y, z, -x, y,-z, -x,-y,-z, -x,-y, z, // v1-v6-v7-v2 left -x,-y,-z, x,-y,-z, x,-y, z, -x,-y, z, // v7-v4-v3-v2 bottom x,-y,-z, -x,-y,-z, -x, y,-z, x, y,-z ]; // v4-v7-v6-v5 back var normals = [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // v0-v1-v2-v3 front 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // v0-v5-v6-v1 top -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // v1-v6-v7-v2 left 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, // v7-v4-v3-v2 bottom 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1 ]; // v4-v7-v6-v5 back var uv = [ x, y, 0, y, 0, 0, x, 0, // v0-v1-v2-v3 front 0, y, 0, 0, x, 0, x, y, // v0-v3-v4-v5 right x, 0, x, y, 0, y, 0, 0, // v0-v5-v6-v1 top x, y, 0, y, 0, 0, x, 0, // v1-v6-v7-v2 left 0, 0, x, 0, x, y, 0, y, // v7-v4-v3-v2 bottom 0, 0, x, 0, x, y, 0, y ]; // v4-v7-v6-v5 back var indices = [ 0, 1, 2, 0, 2, 3, // front 4, 5, 6, 4, 6, 7, // right 8, 9,10, 8,10,11, // top 12,13,14, 12,14,15, // left 16,17,18, 16,18,19, // bottom 20,21,22, 20,22,23 ] ; // back return { primitive : solid ? "triangles" : "lines", positions : positions, normals: normals, uv : uv, indices : indices, colors:[] }; } }); }; SceneJS.createNodeType("cube", "box"); // Alias for backwards compatibility with V0.7.9 })(); (function() { /** * @class A scene node that defines sphere geometry. *

The geometry is complete with normals for shading and one layer of UV coordinates for * texture-mapping.

*

Example Usage

Definition of sphere with a radius of 6 units:


     * var c = new Sphere({
     *            radius: 6
     *          slices: 30,          // Optional number of longitudinal slices (30 is default)
     *          rings: 30,           // Optional number of latitudinal slices (30 is default)
     *          semiMajorAxis: 1.5,  // Optional semiMajorAxis results in elliptical sphere (default of 1 creates sphere)
     *          sweep: 0.75,         // Optional rotational extrusion (1 is default)
     *          sliceDepth: 0.25,    // Optional depth of slices to generate from top to bottom (1 is default)
     (1 is default)
     *     })
     * 
* @extends SceneJS.Geometry * @since Version 0.7.4 * @constructor * Create a new Sphere * @param {Object} [cfg] Static configuration object * @param {float} [cfg.slices=30] Number of longitudinal slices * @param {float} [cfg.rings=30] Number of longitudinal slices * @param {float} [cfg.semiMajorAxis=1.0] values other than one generate an elliptical sphere * @param {float} [cfg.sweep=1] rotational extrusion, default is 1 * @param {float} [cfg.sliceDepth=1] depth of slices to generate, default is 1 * @param {function(SceneJS.Data):Object} [fn] Dynamic configuration function * @param {...SceneJS_node} [childNodes] Child nodes */ var Sphere = SceneJS.createNodeType("sphere", "geometry"); Sphere.prototype._init = function(params) { var slices = params.slices || 30; var rings = params.rings || 30; var radius = params.radius || 1; var semiMajorAxis = params.semiMajorAxis || 1; var semiMinorAxis = 1 / semiMajorAxis; var sweep = params.sweep || 1; if (sweep > 1) { sweep = 1 } var sliceDepth = params.sliceDepth || 1; if (sliceDepth > 1) { sliceDepth = 1 } var ringLimit = rings * sweep; var sliceLimit = slices * sliceDepth; SceneJS_geometry.prototype._init.call(this, { /* Core ID ensures that we reuse any sphere that has already been created with * these parameters instead of wasting memory */ coreId : params.coreId || "sphere_" + radius + "_" + rings + "_" + slices + "_" + semiMajorAxis + "_" + sweep + "_" + sliceDepth, /* Optional pre-applied static model-space transforms */ scale: params.scale, origin: params.origin, /* Callback that does the creation in case we can't find matching sphere to reuse */ create : function() { var positions = []; var normals = []; var uv = []; var ringNum, sliceNum, index; for (sliceNum = 0; sliceNum <= slices; sliceNum++) { if (sliceNum > sliceLimit) break; var theta = sliceNum * Math.PI / slices; var sinTheta = Math.sin(theta); var cosTheta = Math.cos(theta); for (ringNum = 0; ringNum <= rings; ringNum++) { if (ringNum > ringLimit) break; var phi = ringNum * 2 * Math.PI / rings; var sinPhi = semiMinorAxis * Math.sin(phi); var cosPhi = semiMajorAxis * Math.cos(phi); var x = cosPhi * sinTheta; var y = cosTheta; var z = sinPhi * sinTheta; var u = 1 - (ringNum / rings); var v = sliceNum / slices; normals.push(x); normals.push(y); normals.push(z); uv.push(u); uv.push(v); positions.push(radius * x); positions.push(radius * y); positions.push(radius * z); } } // create a center point which is only used when sweep or sliceDepth are less than one if (sliceDepth < 1) { var yPos = Math.cos((sliceNum - 1) * Math.PI / slices) * radius; positions.push(0, yPos, 0); normals.push(1, 1, 1); uv.push(1, 1); } else { positions.push(0, 0, 0); normals.push(1, 1, 1); uv.push(1, 1); } // index of the center position point in the positions array // var centerIndex = (ringLimit + 1) * (sliceLimit); var centerIndex = positions.length / 3 - 1; var indices = []; for (sliceNum = 0; sliceNum < slices; sliceNum++) { if (sliceNum >= sliceLimit) break; for (ringNum = 0; ringNum < rings; ringNum++) { if (ringNum >= ringLimit) break; var first = (sliceNum * (ringLimit + 1)) + ringNum; var second = first + ringLimit + 1; indices.push(first); indices.push(second); indices.push(first + 1); indices.push(second); indices.push(second + 1); indices.push(first + 1); } if (rings >= ringLimit) { // We aren't sweeping the whole way around so ... // indices for a sphere with fours ring-segments when only two are drawn. index = (ringLimit + 1) * sliceNum; // 0,3,15 2,5,15 3,6,15 5,8,15 ... indices.push(index, index + ringLimit + 1, centerIndex); indices.push(index + ringLimit, index + ringLimit * 2 + 1, centerIndex); } } if (slices > sliceLimit) { // We aren't sweeping from the top all the way to the bottom so ... for (ringNum = 1; ringNum <= ringLimit; ringNum++) { index = sliceNum * ringLimit + ringNum; indices.push(index, index + 1, centerIndex); } indices.push(index + 1, sliceNum * ringLimit + 1, centerIndex); } return { primitive : "triangles", positions : positions, normals: normals, uv : uv, indices : indices }; } }); }; })(); (function() { /** * A scene node that defines 2D quad (sprite) geometry. * The geometry is complete with normals for shading and one layer of UV coordinates for * texture-mapping. A Quad may be configured with an optional half-size for X and Y axis. Where * not specified, the half-size on each axis will be 1 by default. It can also be configured as solid (default), * to construct it from triangles with normals for shading and one layer of UV coordinates for texture-mapping * one made of triangles. When not solid, it will be a wireframe drawn as line segments. */ var Quad = SceneJS.createNodeType("quad", "geometry"); Quad.prototype._init = function(params) { this.attr.type = "quad"; var solid = (params.solid != undefined) ? params.solid : true; var x = params.xSize || 1; var y = params.ySize || 1; SceneJS_geometry.prototype._init.call(this, { /* Core ID ensures that we reuse any quad that has already been created with * these parameters instead of wasting memory */ coreId : params.coreId || "quad_" + x + "_" + y + (solid ? "_solid" : "_wire"), /* Factory function used when resource not found */ create : function() { var xDiv = params.xDiv || 1; var yDiv = params.yDiv || 1; var positions, normals, uv, indices; if (xDiv == 1 && yDiv == 1) { positions = [ x, y, 0, -x, y, 0, -x,-y, 0, x,-y, 0 ]; normals = [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1 ]; uv = [ 1, 1, 0, 1, 0, 0, 1, 0 ]; indices = [ 2, 1, 0, 3, 2, 0 ]; } else { if (xDiv < 0) { throw SceneJS_errorModule.fatalError(SceneJS.errors.ILLEGAL_NODE_CONFIG, "quad xDiv should be greater than zero"); } if (yDiv < 0) { throw SceneJS_errorModule.fatalError(SceneJS.errors.ILLEGAL_NODE_CONFIG, "quad yDiv should be greater than zero"); } positions = []; normals = []; uv = []; indices = []; var xStep = (x * 2) / xDiv; var yStep = (y * 2) / yDiv; var xRat = 0.5 / xDiv; var yRat = 0.5 / yDiv; var i = 0; for (var yi = -y, yuv = 0; yi <= y; yi += yStep,yuv += yRat) { for (var xi = -x, xuv = 0; xi <= x; xi += xStep,xuv += xRat) { positions.push(xi); positions.push(yi); positions.push(0); normals.push(0); normals.push(0); normals.push(1); uv.push(xuv); uv.push(yuv); if (yi < y && xi < x) { // Two triangles indices.push(i + 2); indices.push(i + 1); indices.push(i); indices.push(i + 3); indices.push(i + 2); indices.push(i); } i += 3; } } } return { primitive : solid ? "triangles" : "lines", positions : positions, normals: normals, uv : uv, indices : indices, colors:[] }; } }); }; })(); (function() { /** * @class A scene node that defines a disk geometry. *

The geometry is complete with normals for shading and one layer of UV coordinates for * texture-mapping. It can also be configured as solid (default), to construct it from * triangles with normals for shading and one layer of UV coordinates for texture-mapping * When not solid, it will be a wireframe drawn as line segments.

*

Example Usage

Definition of solid disk that extends 6 units radially from the Y-axis, * is elliptical in shape with a normalized semiMajorAxis of 1.5, is 2 units high in the Y-axis and * is made up of 48 longitudinal rings:


     * var c = new Disk({
     *          radius: 6,          // Optional radius (1 is default)
     *          innerRadius: 3     // Optional innerRadius results in ring (default is 0)
     *          semiMajorAxis: 1.5  // Optional semiMajorAxis results in ellipse (default is 1 which is a circle)
     *          height: 2,          // Optional height (1 is default)
     *          rings: 48           // Optional number of longitudinal rings (30 is default)
     *     })
     * 
* @extends SceneJS.Geometry * @since Version 0.7.9 * @constructor * Create a new Disk * @param {Object} [cfg] Static configuration object * @param {float} [cfg.radius=1.0] radius extending from Y-axis * @param {float} [cfg.innerRadius=0] inner radius extending from Y-axis * @param {float} [cfg.innerRadius=0] inner radius extending from Y-axis * @param {float} [cfg.semiMajorAxis=1.0] values other than one generate an ellipse * @param {float} [cfg.rings=30] number of longitudinal rings * @param {...SceneJS_node} [childNodes] Child nodes */ var Disk = SceneJS.createNodeType("disk", "geometry"); Disk.prototype._init = function(params) { this.attr.type = "disk"; var radius = params.radius || 1; var height = params.height || 1; var rings = params.rings || 30; var innerRadius = params.innerRadius || 0; if (innerRadius > radius) { innerRadius = radius } var semiMajorAxis = params.semiMajorAxis || 1; var semiMinorAxis = 1 / semiMajorAxis; var sweep = params.sweep || 1; if (sweep > 1) { sweep = 1 } var ringLimit = rings * sweep; SceneJS_geometry.prototype._init.call(this, { /* Resource ID ensures that we reuse any sphere that has already been created with * these parameters instead of wasting memory */ coreId : params.coreId || "disk_" + radius + "_" + height + "_" + rings + "_" + innerRadius + "_" + semiMajorAxis + "_" + sweep, /* Callback that does the creation in case we can't find matching disk to reuse */ create : function() { var positions = []; var normals = []; var uv = []; var ybot = height * -0.5; var ytop = height * 0.5; if (innerRadius <= 0) { // create the central vertices // bottom vertex normals.push(0); normals.push(-1); normals.push(0); uv.push(u); uv.push(v); positions.push(0); positions.push(ybot); positions.push(0); // top vertex normals.push(0); normals.push(1); normals.push(0); uv.push(u); uv.push(v); positions.push(0); positions.push(ytop); positions.push(0); } for (var ringNum = 0; ringNum <= rings; ringNum++) { if (ringNum > ringLimit) break; var phi = ringNum * 2 * Math.PI / rings; var sinPhi = semiMinorAxis * Math.sin(phi); var cosPhi = semiMajorAxis * Math.cos(phi); var x = cosPhi; var z = sinPhi; var u = 1 - (ringNum / rings); var v = 0.5; // // Create the outer set of vertices, // two for the bottom and two for the top. // // bottom vertex, facing the disk axis normals.push(0); normals.push(-1); normals.push(0); uv.push(u); uv.push(v); positions.push(radius * x); positions.push(ybot); positions.push(radius * z); // bottom vertex, facing outwards normals.push(x); normals.push(0); normals.push(z); uv.push(u); uv.push(v); positions.push(radius * x); positions.push(ybot); positions.push(radius * z); // top vertex, facing the disk axis normals.push(0); normals.push(1); normals.push(0); uv.push(u); uv.push(v); positions.push(radius * x); positions.push(ytop); positions.push(radius * z); // top vertex, facing outwards normals.push(x); normals.push(0); normals.push(z); uv.push(u); uv.push(v); positions.push(radius * x); positions.push(ytop); positions.push(radius * z); if (innerRadius > 0) { // // Creating a disk with a hole in the middle ... // generate the inner set of vertices, // one for the bottom and one for the top // // bottom vertex, facing the disk axis normals.push(0); normals.push(-1); normals.push(0); uv.push(u); uv.push(v); positions.push(innerRadius * x); positions.push(ybot); positions.push(innerRadius * z); // bottom vertex, facing inwards normals.push(-x); normals.push(0); normals.push(-z); uv.push(u); uv.push(v); positions.push(innerRadius * x); positions.push(ybot); positions.push(innerRadius * z); // top vertex, facing the disk axis normals.push(0); normals.push(1); normals.push(0); uv.push(u); uv.push(v); positions.push(innerRadius * x); positions.push(ytop); positions.push(innerRadius * z); // top vertex, facing inwards normals.push(-x); normals.push(0); normals.push(-z); uv.push(u); uv.push(v); positions.push(innerRadius * x); positions.push(ytop); positions.push(innerRadius * z); } if (ringNum + 1 > ringLimit) { // // Create (outer) vertices for end caps. // // bottom vertex for the first end cap normals.push(0); normals.push(0); normals.push(-1); uv.push(u); uv.push(v); positions.push(radius * semiMajorAxis); positions.push(ybot); positions.push(0); // top vertex for the first end cap normals.push(0); normals.push(0); normals.push(-1); uv.push(u); uv.push(v); positions.push(radius * semiMajorAxis); positions.push(ytop); positions.push(0); // bottom vertex for the second end cap normals.push(-z); normals.push(0); normals.push(x); uv.push(u); uv.push(v); positions.push(radius * x); positions.push(ybot); positions.push(radius * z); // top vertex for the second end cap normals.push(-z); normals.push(0); normals.push(x); uv.push(u); uv.push(v); positions.push(radius * x); positions.push(ytop); positions.push(radius * z); if (innerRadius > 0) { // // Disk with a hole. // Create inner vertices for end caps. // // bottom vertex for the first end cap normals.push(0); normals.push(0); normals.push(-1); uv.push(u); uv.push(v); positions.push(innerRadius * semiMajorAxis); positions.push(ybot); positions.push(0); // top vertex for the first end cap normals.push(0); normals.push(0); normals.push(-1); uv.push(u); uv.push(v); positions.push(innerRadius * semiMajorAxis); positions.push(ytop); positions.push(0); // bottom vertex for the second end cap normals.push(-z); normals.push(0); normals.push(x); uv.push(u); uv.push(v); positions.push(innerRadius * x); positions.push(ybot); positions.push(innerRadius * z); // top vertex for the second end cap normals.push(-z); normals.push(0); normals.push(x); uv.push(u); uv.push(v); positions.push(innerRadius * x); positions.push(ytop); positions.push(innerRadius * z); } else { // // Disk without a hole. // End cap vertices at the center of the disk. // // bottom vertex for the first end cap normals.push(0); normals.push(0); normals.push(-1); uv.push(u); uv.push(v); positions.push(0); positions.push(ybot); positions.push(0); // top vertex for the first end cap normals.push(0); normals.push(0); normals.push(-1); uv.push(u); uv.push(v); positions.push(0); positions.push(ytop); positions.push(0); // bottom vertex for the second end cap normals.push(-z); normals.push(0); normals.push(x); uv.push(u); uv.push(v); positions.push(0); positions.push(ybot); positions.push(0); // top vertex for the second end cap normals.push(-z); normals.push(0); normals.push(x); uv.push(u); uv.push(v); positions.push(0); positions.push(ytop); positions.push(0); } break; } } var indices = []; // // Create indices pointing to vertices for the top, bottom // and optional endcaps for the disk // if (innerRadius > 0) { // // Creating a disk with a hole in the middle ... // Each ring sengment rquires a quad surface on the top and bottom // so create two triangles for the top and two for the bottom. // var index; for (var ringNum = 0; ringNum < rings; ringNum++) { if (ringNum + 1 > ringLimit) { // // We aren't sweeping the whole way around so also create triangles to cap the ends. // index = (ringNum + 1) * 8; // the first vertex after the regular vertices // start cap indices.push(index); indices.push(index + 4); indices.push(index + 5); indices.push(index); indices.push(index + 5); indices.push(index + 1); // end cap indices.push(index + 2); indices.push(index + 7); indices.push(index + 6); indices.push(index + 2); indices.push(index + 3); indices.push(index + 7); break; } index = ringNum * 8; // outer ring segment quad indices.push(index + 1); indices.push(index + 3); indices.push(index + 11); indices.push(index + 1); indices.push(index + 11); indices.push(index + 9); // inner ring segment quad indices.push(index + 5); indices.push(index + 7); indices.push(index + 15); indices.push(index + 5); indices.push(index + 15); indices.push(index + 13); // bottom disk segment indices.push(index); indices.push(index + 8); indices.push(index + 12); indices.push(index + 0); indices.push(index + 12); indices.push(index + 4); // top disk segment indices.push(index + 2); indices.push(index + 6); indices.push(index + 14); indices.push(index + 2); indices.push(index + 14); indices.push(index + 10); } } else { // // Create a solid disk without a hole in the middle. // for (var ringNum = 0; ringNum < rings; ringNum++) { if (ringNum + 1 > ringLimit) { index = 2 + (ringNum + 1) * 4; // the first after the regular vertices // start cap indices.push(index); indices.push(index + 4); indices.push(index + 5); indices.push(index + 0); indices.push(index + 5); indices.push(index + 1); // end cap indices.push(index + 2); indices.push(index + 7); indices.push(index + 6); indices.push(index + 2); indices.push(index + 3); indices.push(index + 7); break; } // // generate the two outer-facing triangles for each ring segment // var index = ringNum * 4 + 2; // outer ring segment quad indices.push(index + 1); indices.push(index + 3); indices.push(index + 7); indices.push(index + 1); indices.push(index + 7); indices.push(index + 5); // bottom disk segment indices.push(index); indices.push(0); indices.push(index + 4); // top disk segment indices.push(index + 2); indices.push(1); indices.push(index + 6); } } return { primitive : "triangles", positions : positions, normals: normals, uv : uv, indices : indices }; } }); }; })(); /** Backend module that creates vector geometry repreentations of text * @private */ var SceneJS_vectorTextModule = new (function() { var letters = { ' ': { width: 16, points: [] }, '!': { width: 10, points: [ [5,21], [5,7], [-1,-1], [5,2], [4,1], [5,0], [6,1], [5,2] ] }, '"': { width: 16, points: [ [4,21], [4,14], [-1,-1], [12,21], [12,14] ] }, '#': { width: 21, points: [ [11,25], [4,-7], [-1,-1], [17,25], [10,-7], [-1,-1], [4,12], [18,12], [-1,-1], [3,6], [17,6] ] }, '$': { width: 20, points: [ [8,25], [8,-4], [-1,-1], [12,25], [12,-4], [-1,-1], [17,18], [15,20], [12,21], [8,21], [5,20], [3,18], [3,16], [4,14], [5,13], [7,12], [13,10], [15,9], [16,8], [17,6], [17,3], [15,1], [12,0], [8,0], [5,1], [3,3] ] }, '%': { width: 24, points: [ [21,21], [3,0], [-1,-1], [8,21], [10,19], [10,17], [9,15], [7,14], [5,14], [3,16], [3,18], [4,20], [6,21], [8,21], [10,20], [13,19], [16,19], [19,20], [21,21], [-1,-1], [17,7], [15,6], [14,4], [14,2], [16,0], [18,0], [20,1], [21,3], [21,5], [19,7], [17,7] ] }, '&': { width: 26, points: [ [23,12], [23,13], [22,14], [21,14], [20,13], [19,11], [17,6], [15,3], [13,1], [11,0], [7,0], [5,1], [4,2], [3,4], [3,6], [4,8], [5,9], [12,13], [13,14], [14,16], [14,18], [13,20], [11,21], [9,20], [8,18], [8,16], [9,13], [11,10], [16,3], [18,1], [20,0], [22,0], [23,1], [23,2] ] }, '\'': { width: 10, points: [ [5,19], [4,20], [5,21], [6,20], [6,18], [5,16], [4,15] ] }, '(': { width: 14, points: [ [11,25], [9,23], [7,20], [5,16], [4,11], [4,7], [5,2], [7,-2], [9,-5], [11,-7] ] }, ')': { width: 14, points: [ [3,25], [5,23], [7,20], [9,16], [10,11], [10,7], [9,2], [7,-2], [5,-5], [3,-7] ] }, '*': { width: 16, points: [ [8,21], [8,9], [-1,-1], [3,18], [13,12], [-1,-1], [13,18], [3,12] ] }, '+': { width: 26, points: [ [13,18], [13,0], [-1,-1], [4,9], [22,9] ] }, ',': { width: 10, points: [ [6,1], [5,0], [4,1], [5,2], [6,1], [6,-1], [5,-3], [4,-4] ] }, '-': { width: 26, points: [ [4,9], [22,9] ] }, '.': { width: 10, points: [ [5,2], [4,1], [5,0], [6,1], [5,2] ] }, '/': { width: 22, points: [ [20,25], [2,-7] ] }, '0': { width: 20, points: [ [9,21], [6,20], [4,17], [3,12], [3,9], [4,4], [6,1], [9,0], [11,0], [14,1], [16,4], [17,9], [17,12], [16,17], [14,20], [11,21], [9,21] ] }, '1': { width: 20, points: [ [6,17], [8,18], [11,21], [11,0] ] }, '2': { width: 20, points: [ [4,16], [4,17], [5,19], [6,20], [8,21], [12,21], [14,20], [15,19], [16,17], [16,15], [15,13], [13,10], [3,0], [17,0] ] }, '3': { width: 20, points: [ [5,21], [16,21], [10,13], [13,13], [15,12], [16,11], [17,8], [17,6], [16,3], [14,1], [11,0], [8,0], [5,1], [4,2], [3,4] ] }, '4': { width: 20, points: [ [13,21], [3,7], [18,7], [-1,-1], [13,21], [13,0] ] }, '5': { width: 20, points: [ [15,21], [5,21], [4,12], [5,13], [8,14], [11,14], [14,13], [16,11], [17,8], [17,6], [16,3], [14,1], [11,0], [8,0], [5,1], [4,2], [3,4] ] }, '6': { width: 20, points: [ [16,18], [15,20], [12,21], [10,21], [7,20], [5,17], [4,12], [4,7], [5,3], [7,1], [10,0], [11,0], [14,1], [16,3], [17,6], [17,7], [16,10], [14,12], [11,13], [10,13], [7,12], [5,10], [4,7] ] }, '7': { width: 20, points: [ [17,21], [7,0], [-1,-1], [3,21], [17,21] ] }, '8': { width: 20, points: [ [8,21], [5,20], [4,18], [4,16], [5,14], [7,13], [11,12], [14,11], [16,9], [17,7], [17,4], [16,2], [15,1], [12,0], [8,0], [5,1], [4,2], [3,4], [3,7], [4,9], [6,11], [9,12], [13,13], [15,14], [16,16], [16,18], [15,20], [12,21], [8,21] ] }, '9': { width: 20, points: [ [16,14], [15,11], [13,9], [10,8], [9,8], [6,9], [4,11], [3,14], [3,15], [4,18], [6,20], [9,21], [10,21], [13,20], [15,18], [16,14], [16,9], [15,4], [13,1], [10,0], [8,0], [5,1], [4,3] ] }, ':': { width: 10, points: [ [5,14], [4,13], [5,12], [6,13], [5,14], [-1,-1], [5,2], [4,1], [5,0], [6,1], [5,2] ] }, ';': { width: 10, points: [ [5,14], [4,13], [5,12], [6,13], [5,14], [-1,-1], [6,1], [5,0], [4,1], [5,2], [6,1], [6,-1], [5,-3], [4,-4] ] }, '<': { width: 24, points: [ [20,18], [4,9], [20,0] ] }, '=': { width: 26, points: [ [4,12], [22,12], [-1,-1], [4,6], [22,6] ] }, '>': { width: 24, points: [ [4,18], [20,9], [4,0] ] }, '?': { width: 18, points: [ [3,16], [3,17], [4,19], [5,20], [7,21], [11,21], [13,20], [14,19], [15,17], [15,15], [14,13], [13,12], [9,10], [9,7], [-1,-1], [9,2], [8,1], [9,0], [10,1], [9,2] ] }, '@': { width: 27, points: [ [18,13], [17,15], [15,16], [12,16], [10,15], [9,14], [8,11], [8,8], [9,6], [11,5], [14,5], [16,6], [17,8], [-1,-1], [12,16], [10,14], [9,11], [9,8], [10,6], [11,5], [-1,-1], [18,16], [17,8], [17,6], [19,5], [21,5], [23,7], [24,10], [24,12], [23,15], [22,17], [20,19], [18,20], [15,21], [12,21], [9,20], [7,19], [5,17], [4,15], [3,12], [3,9], [4,6], [5,4], [7,2], [9,1], [12,0], [15,0], [18,1], [20,2], [21,3], [-1,-1], [19,16], [18,8], [18,6], [19,5] ] }, 'A': { width: 18, points: [ [9,21], [1,0], [-1,-1], [9,21], [17,0], [-1,-1], [4,7], [14,7] ] }, 'B': { width: 21, points: [ [4,21], [4,0], [-1,-1], [4,21], [13,21], [16,20], [17,19], [18,17], [18,15], [17,13], [16,12], [13,11], [-1,-1], [4,11], [13,11], [16,10], [17,9], [18,7], [18,4], [17,2], [16,1], [13,0], [4,0] ] }, 'C': { width: 21, points: [ [18,16], [17,18], [15,20], [13,21], [9,21], [7,20], [5,18], [4,16], [3,13], [3,8], [4,5], [5,3], [7,1], [9,0], [13,0], [15,1], [17,3], [18,5] ] }, 'D': { width: 21, points: [ [4,21], [4,0], [-1,-1], [4,21], [11,21], [14,20], [16,18], [17,16], [18,13], [18,8], [17,5], [16,3], [14,1], [11,0], [4,0] ] }, 'E': { width: 19, points: [ [4,21], [4,0], [-1,-1], [4,21], [17,21], [-1,-1], [4,11], [12,11], [-1,-1], [4,0], [17,0] ] }, 'F': { width: 18, points: [ [4,21], [4,0], [-1,-1], [4,21], [17,21], [-1,-1], [4,11], [12,11] ] }, 'G': { width: 21, points: [ [18,16], [17,18], [15,20], [13,21], [9,21], [7,20], [5,18], [4,16], [3,13], [3,8], [4,5], [5,3], [7,1], [9,0], [13,0], [15,1], [17,3], [18,5], [18,8], [-1,-1], [13,8], [18,8] ] }, 'H': { width: 22, points: [ [4,21], [4,0], [-1,-1], [18,21], [18,0], [-1,-1], [4,11], [18,11] ] }, 'I': { width: 8, points: [ [4,21], [4,0] ] }, 'J': { width: 16, points: [ [12,21], [12,5], [11,2], [10,1], [8,0], [6,0], [4,1], [3,2], [2,5], [2,7] ] }, 'K': { width: 21, points: [ [4,21], [4,0], [-1,-1], [18,21], [4,7], [-1,-1], [9,12], [18,0] ] }, 'L': { width: 17, points: [ [4,21], [4,0], [-1,-1], [4,0], [16,0] ] }, 'M': { width: 24, points: [ [4,21], [4,0], [-1,-1], [4,21], [12,0], [-1,-1], [20,21], [12,0], [-1,-1], [20,21], [20,0] ] }, 'N': { width: 22, points: [ [4,21], [4,0], [-1,-1], [4,21], [18,0], [-1,-1], [18,21], [18,0] ] }, 'O': { width: 22, points: [ [9,21], [7,20], [5,18], [4,16], [3,13], [3,8], [4,5], [5,3], [7,1], [9,0], [13,0], [15,1], [17,3], [18,5], [19,8], [19,13], [18,16], [17,18], [15,20], [13,21], [9,21] ] }, 'P': { width: 21, points: [ [4,21], [4,0], [-1,-1], [4,21], [13,21], [16,20], [17,19], [18,17], [18,14], [17,12], [16,11], [13,10], [4,10] ] }, 'Q': { width: 22, points: [ [9,21], [7,20], [5,18], [4,16], [3,13], [3,8], [4,5], [5,3], [7,1], [9,0], [13,0], [15,1], [17,3], [18,5], [19,8], [19,13], [18,16], [17,18], [15,20], [13,21], [9,21], [-1,-1], [12,4], [18,-2] ] }, 'R': { width: 21, points: [ [4,21], [4,0], [-1,-1], [4,21], [13,21], [16,20], [17,19], [18,17], [18,15], [17,13], [16,12], [13,11], [4,11], [-1,-1], [11,11], [18,0] ] }, 'S': { width: 20, points: [ [17,18], [15,20], [12,21], [8,21], [5,20], [3,18], [3,16], [4,14], [5,13], [7,12], [13,10], [15,9], [16,8], [17,6], [17,3], [15,1], [12,0], [8,0], [5,1], [3,3] ] }, 'T': { width: 16, points: [ [8,21], [8,0], [-1,-1], [1,21], [15,21] ] }, 'U': { width: 22, points: [ [4,21], [4,6], [5,3], [7,1], [10,0], [12,0], [15,1], [17,3], [18,6], [18,21] ] }, 'V': { width: 18, points: [ [1,21], [9,0], [-1,-1], [17,21], [9,0] ] }, 'W': { width: 24, points: [ [2,21], [7,0], [-1,-1], [12,21], [7,0], [-1,-1], [12,21], [17,0], [-1,-1], [22,21], [17,0] ] }, 'X': { width: 20, points: [ [3,21], [17,0], [-1,-1], [17,21], [3,0] ] }, 'Y': { width: 18, points: [ [1,21], [9,11], [9,0], [-1,-1], [17,21], [9,11] ] }, 'Z': { width: 20, points: [ [17,21], [3,0], [-1,-1], [3,21], [17,21], [-1,-1], [3,0], [17,0] ] }, '[': { width: 14, points: [ [4,25], [4,-7], [-1,-1], [5,25], [5,-7], [-1,-1], [4,25], [11,25], [-1,-1], [4,-7], [11,-7] ] }, '\\': { width: 14, points: [ [0,21], [14,-3] ] }, ']': { width: 14, points: [ [9,25], [9,-7], [-1,-1], [10,25], [10,-7], [-1,-1], [3,25], [10,25], [-1,-1], [3,-7], [10,-7] ] }, '^': { width: 16, points: [ [6,15], [8,18], [10,15], [-1,-1], [3,12], [8,17], [13,12], [-1,-1], [8,17], [8,0] ] }, '_': { width: 16, points: [ [0,-2], [16,-2] ] }, '`': { width: 10, points: [ [6,21], [5,20], [4,18], [4,16], [5,15], [6,16], [5,17] ] }, 'a': { width: 19, points: [ [15,14], [15,0], [-1,-1], [15,11], [13,13], [11,14], [8,14], [6,13], [4,11], [3,8], [3,6], [4,3], [6,1], [8,0], [11,0], [13,1], [15,3] ] }, 'b': { width: 19, points: [ [4,21], [4,0], [-1,-1], [4,11], [6,13], [8,14], [11,14], [13,13], [15,11], [16,8], [16,6], [15,3], [13,1], [11,0], [8,0], [6,1], [4,3] ] }, 'c': { width: 18, points: [ [15,11], [13,13], [11,14], [8,14], [6,13], [4,11], [3,8], [3,6], [4,3], [6,1], [8,0], [11,0], [13,1], [15,3] ] }, 'd': { width: 19, points: [ [15,21], [15,0], [-1,-1], [15,11], [13,13], [11,14], [8,14], [6,13], [4,11], [3,8], [3,6], [4,3], [6,1], [8,0], [11,0], [13,1], [15,3] ] }, 'e': { width: 18, points: [ [3,8], [15,8], [15,10], [14,12], [13,13], [11,14], [8,14], [6,13], [4,11], [3,8], [3,6], [4,3], [6,1], [8,0], [11,0], [13,1], [15,3] ] }, 'f': { width: 12, points: [ [10,21], [8,21], [6,20], [5,17], [5,0], [-1,-1], [2,14], [9,14] ] }, 'g': { width: 19, points: [ [15,14], [15,-2], [14,-5], [13,-6], [11,-7], [8,-7], [6,-6], [-1,-1], [15,11], [13,13], [11,14], [8,14], [6,13], [4,11], [3,8], [3,6], [4,3], [6,1], [8,0], [11,0], [13,1], [15,3] ] }, 'h': { width: 19, points: [ [4,21], [4,0], [-1,-1], [4,10], [7,13], [9,14], [12,14], [14,13], [15,10], [15,0] ] }, 'i': { width: 8, points: [ [3,21], [4,20], [5,21], [4,22], [3,21], [-1,-1], [4,14], [4,0] ] }, 'j': { width: 10, points: [ [5,21], [6,20], [7,21], [6,22], [5,21], [-1,-1], [6,14], [6,-3], [5,-6], [3,-7], [1,-7] ] }, 'k': { width: 17, points: [ [4,21], [4,0], [-1,-1], [14,14], [4,4], [-1,-1], [8,8], [15,0] ] }, 'l': { width: 8, points: [ [4,21], [4,0] ] }, 'm': { width: 30, points: [ [4,14], [4,0], [-1,-1], [4,10], [7,13], [9,14], [12,14], [14,13], [15,10], [15,0], [-1,-1], [15,10], [18,13], [20,14], [23,14], [25,13], [26,10], [26,0] ] }, 'n': { width: 19, points: [ [4,14], [4,0], [-1,-1], [4,10], [7,13], [9,14], [12,14], [14,13], [15,10], [15,0] ] }, 'o': { width: 19, points: [ [8,14], [6,13], [4,11], [3,8], [3,6], [4,3], [6,1], [8,0], [11,0], [13,1], [15,3], [16,6], [16,8], [15,11], [13,13], [11,14], [8,14] ] }, 'p': { width: 19, points: [ [4,14], [4,-7], [-1,-1], [4,11], [6,13], [8,14], [11,14], [13,13], [15,11], [16,8], [16,6], [15,3], [13,1], [11,0], [8,0], [6,1], [4,3] ] }, 'q': { width: 19, points: [ [15,14], [15,-7], [-1,-1], [15,11], [13,13], [11,14], [8,14], [6,13], [4,11], [3,8], [3,6], [4,3], [6,1], [8,0], [11,0], [13,1], [15,3] ] }, 'r': { width: 13, points: [ [4,14], [4,0], [-1,-1], [4,8], [5,11], [7,13], [9,14], [12,14] ] }, 's': { width: 17, points: [ [14,11], [13,13], [10,14], [7,14], [4,13], [3,11], [4,9], [6,8], [11,7], [13,6], [14,4], [14,3], [13,1], [10,0], [7,0], [4,1], [3,3] ] }, 't': { width: 12, points: [ [5,21], [5,4], [6,1], [8,0], [10,0], [-1,-1], [2,14], [9,14] ] }, 'u': { width: 19, points: [ [4,14], [4,4], [5,1], [7,0], [10,0], [12,1], [15,4], [-1,-1], [15,14], [15,0] ] }, 'v': { width: 16, points: [ [2,14], [8,0], [-1,-1], [14,14], [8,0] ] }, 'w': { width: 22, points: [ [3,14], [7,0], [-1,-1], [11,14], [7,0], [-1,-1], [11,14], [15,0], [-1,-1], [19,14], [15,0] ] }, 'x': { width: 17, points: [ [3,14], [14,0], [-1,-1], [14,14], [3,0] ] }, 'y': { width: 16, points: [ [2,14], [8,0], [-1,-1], [14,14], [8,0], [6,-4], [4,-6], [2,-7], [1,-7] ] }, 'z': { width: 17, points: [ [14,14], [3,0], [-1,-1], [3,14], [14,14], [-1,-1], [3,0], [14,0] ] }, '{': { width: 14, points: [ [9,25], [7,24], [6,23], [5,21], [5,19], [6,17], [7,16], [8,14], [8,12], [6,10], [-1,-1], [7,24], [6,22], [6,20], [7,18], [8,17], [9,15], [9,13], [8,11], [4,9], [8,7], [9,5], [9,3], [8,1], [7,0], [6,-2], [6,-4], [7,-6], [-1,-1], [6,8], [8,6], [8,4], [7,2], [6,1], [5,-1], [5,-3], [6,-5], [7,-6], [9,-7] ] }, '|': { width: 8, points: [ [4,25], [4,-7] ] }, '}': { width: 14, points: [ [5,25], [7,24], [8,23], [9,21], [9,19], [8,17], [7,16], [6,14], [6,12], [8,10], [-1,-1], [7,24], [8,22], [8,20], [7,18], [6,17], [5,15], [5,13], [6,11], [10,9], [6,7], [5,5], [5,3], [6,1], [7,0], [8,-2], [8,-4], [7,-6], [-1,-1], [8,8], [6,6], [6,4], [7,2], [8,1], [9,-1], [9,-3], [8,-5], [7,-6], [5,-7] ] }, '~': { width: 24, points: [ [3,6], [3,8], [4,11], [6,12], [8,12], [10,11], [14,8], [16,7], [18,7], [20,8], [21,10], [-1,-1], [3,8], [4,10], [6,11], [8,11], [10,10], [14,7], [16,6], [18,6], [20,7], [21,10], [21,12] ] } }; // @private function letter(ch) { return letters[ch]; } // @private function ascent(font, size) { return size; } // @private function descent(font, size) { return 7.0 * size / 25.0; } // @private function measure(font, size, str) { var total = 0; var len = str.length; for (var i = 0; i < len; i++) { var c = letter(str.charAt(i)); if (c) total += c.width * size / 25.0; } return total; } // @private this.getGeometry = function(size, xPos, yPos, text) { var geo = { positions : [], indices : [] }; var lines = text.split("\n"); var countVerts = 0; var y = yPos; for (var iLine = 0; iLine < lines.length; iLine++) { var x = xPos; var str = lines[iLine]; var len = str.length; var mag = size / 25.0; for (var i = 0; i < len; i++) { var c = letter(str.charAt(i)); if (c == '\n') { alert("newline"); } if (!c) { continue; } var penUp = 1; var p1 = -1; var p2 = -1; var needLine = false; for (var j = 0; j < c.points.length; j++) { var a = c.points[j]; if (a[0] == -1 && a[1] == -1) { penUp = 1; needLine = false; continue; } geo.positions.push(x + a[0] * mag); geo.positions.push(y + a[1] * mag); geo.positions.push(0); if (p1 == -1) { p1 = countVerts; } else if (p2 == -1) { p2 = countVerts; } else { p1 = p2; p2 = countVerts; } countVerts++; if (penUp) { penUp = false; } else { geo.indices.push(p1); geo.indices.push(p2); } needLine = true; } x += c.width * mag; } y += 25 * mag; } return geo; }; })(); /** Backend module that creates bitmapp text textures * @private */ var SceneJS_bitmapTextModule = new (function() { SceneJS_eventModule.addListener( SceneJS_eventModule.INIT, function() { }); function getHMTLColor(color) { if (color.length != 4) { return color; } for (var i = 0; i < color.length; i++) { color[i] *= 255; } return 'rgba(' + color.join(',') + ')'; } this.createText = function(font, size, text) { var canvas = document.createElement("canvas"); var cx = canvas.getContext('2d'); cx.font = size + "px " + font; var width = cx.measureText(text).width; canvas.width = width; canvas.height = size; cx.font = size + "px " + font; cx.textBaseline = "middle"; cx.fillStyle = getHMTLColor([.5, 10, 30, .5]); cx.fillStyle = "#FFFF00"; var x = 0; var y = (size / 2); cx.fillText(text, x, y); return { image: canvas, width: canvas.width, height: canvas.height }; }; })(); SceneJS.Text = SceneJS.createNodeType("text"); // @private SceneJS.Text.prototype._init = function(params) { var mode = params.mode || "bitmap"; if (mode != "vector" && mode != "bitmap") { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "SceneJS.Text unsupported mode - should be 'vector' or 'bitmap'"); } this._mode = mode; if (this._mode == "bitmap") { var text = SceneJS_bitmapTextModule.createText("Helvetica", params.size || 1, params.text || ""); var w = text.width / 16; var h = text.height / 16; var positions = [ w, h, 0.01, 0, h, 0.1, 0,0, 0.1, w,0, 0.01 ]; var normals = [ 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1 ]; var uv = [1, 1, 0, 1, 0, 0, 1, 0]; var indices = [0, 1, 2, 0, 2, 3]; if (params.doubleSided) { var z = 0.01; positions = positions.concat([w,0,-z, 0,0,-z, 0, h,-z, w, h,-z]); normals = normals.concat([0, 0,1, 0, 0,1, 0, 0,1, 0, 0,1]); uv = uv.concat([0, 0, 1, 0, 1, 1, 0, 1]); indices = indices.concat([4,5,6, 4,6,7]); } this.addNode({ type: "texture", layers: [ { image: text.image, minFilter: "linear", magFilter: "linear", wrapS: "repeat", wrapT: "repeat", isDepth: false, depthMode:"luminance", depthCompareMode: "compareRToTexture", depthCompareFunc: "lequal", flipY: false, width: 1, height: 1, internalFormat:"lequal", sourceFormat:"alpha", sourceType: "unsignedByte", applyTo:"baseColor", blendMode:"add" } ], nodes: [ { type: "material", emit: 1, baseColor: { r: 1.0, g: 0.0, b: 0.0 }, specularColor: { r: 0.9, g: 0.9, b: 0.9 }, specular: 0.9, shine: 100.0, nodes: [ { type: "geometry", primitive: "triangles", positions : positions, normals : normals, uv : uv, indices : indices } ] } ] }); } else { var self = this; this.addNode({ type: "geometry", create: function() { var geo = SceneJS_vectorTextModule.getGeometry(3, 0, 0, params.text); // Unit size return { resource: self.attr.id, // Assuming text geometry varies a lot - don't try to share VBOs primitive : "lines", positions : geo.positions, normals: [], uv : [], indices : geo.indices, colors:[] }; } }); } }; SceneJS.Text.prototype._compile = function() { if (this._mode == "bitmap") { this._compileNodes(); } }; /** * Backend that manages the current modelling transform matrices (modelling and normal). * * Services the scene modelling transform nodes, such as SceneJS.rotate, providing them with methods to set and * get the current modelling transform matrices. * * Interacts with the shading backend through events; on a SCENE_RENDERING event it will respond with a * MODEL_TRANSFORM_EXPORTED to pass the modelling matrix and inverse normal matrix as Float32Arrays to the * shading backend. * * Normal matrix and Float32Arrays are lazy-computed and cached on export to avoid repeatedly regenerating them. * * Avoids redundant export of the matrices with a dirty flag; they are only exported when that is set, which occurs * when transform is set by scene node, or on SCENE_COMPILING, SHADER_ACTIVATED and SHADER_DEACTIVATED events. * * Whenever a scene node sets the matrix, this backend publishes it with a MODEL_TRANSFORM_UPDATED to allow other * dependent backends to synchronise their resources. * * @private */ var SceneJS_modelTransformModule = new (function() { var DEFAULT_TRANSFORM = { matrix : SceneJS_math_identityMat4(), fixed: true, identity : true }; var idStack = []; var transformStack = []; var stackLen = 0; var nodeId; this.transform = DEFAULT_TRANSFORM; var dirty; var self = this; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; nodeId = null; self.transform = DEFAULT_TRANSFORM; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function (params) { if (dirty) { if (stackLen > 0) { var t = self.transform; if (!t.matrixAsArray) { t.matrixAsArray = new Float32Array(t.matrix); t.normalMatrixAsArray = new Float32Array( SceneJS_math_transposeMat4( SceneJS_math_inverseMat4(t.matrix, SceneJS_math_mat4()))); } else { t.matrixAsArray.set(t.matrix); t.normalMatrixAsArray.set( SceneJS_math_transposeMat4( SceneJS_math_inverseMat4(t.matrix, SceneJS_math_mat4()))); } SceneJS_DrawList.setModelTransform(nodeId, t.matrixAsArray, t.normalMatrixAsArray); } else { SceneJS_DrawList.setModelTransform(); } dirty = false; } }); this.pushTransform = function(id, t) { idStack[stackLen] = id; transformStack[stackLen] = t; stackLen++; nodeId = id; this.transform = t; dirty = true; }; this.popTransform = function() { stackLen--; if (stackLen > 0) { nodeId = idStack[stackLen - 1]; this.transform = transformStack[stackLen - 1]; } else { nodeId = null; this.transform = DEFAULT_TRANSFORM; } dirty = true; }; })(); (function () { var Rotate = SceneJS.createNodeType("rotate"); Rotate.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node is the resource definer this.setMultOrder(params.multOrder); this.setAngle(params.angle); this.setXYZ({x : params.x, y: params.y, z: params.z }); } this._mat = null; this._xf = {}; }; Rotate.prototype.setMultOrder = function(multOrder) { multOrder = multOrder || "post"; if (multOrder != "post" && multOrder != "pre") { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Illegal rotate multOrder - '" + multOrder + "' should be 'pre' or 'post'"); } this.core.multOrder = multOrder; this._postMult = (multOrder == "post"); this._compileMemoLevel = 0; }; Rotate.prototype.setAngle = function(angle) { this.core.angle = angle || 0; this._compileMemoLevel = 0; }; Rotate.prototype.getAngle = function() { return this.core.angle; }; Rotate.prototype.setXYZ = function(xyz) { xyz = xyz || {}; this.core.x = xyz.x || 0; this.core.y = xyz.y || 0; this.core.z = xyz.z || 0; this._compileMemoLevel = 0; }; Rotate.prototype.getXYZ = function() { return { x: this.core.x, y: this.core.y, z: this.core.z }; }; Rotate.prototype.setX = function(x) { this.core.x = x; this._compileMemoLevel = 0; }; Rotate.prototype.getX = function() { return this.core.x; }; Rotate.prototype.setY = function(y) { this.core.y = y; this._compileMemoLevel = 0; }; Rotate.prototype.getY = function() { return this.core.y; }; Rotate.prototype.setZ = function(z) { this.core.z = z; this._compileMemoLevel = 0; }; Rotate.prototype.getZ = function() { return this.core.z; }; Rotate.prototype.incX = function(x) { this.core.x += x; this._compileMemoLevel = 0; }; Rotate.prototype.incY = function(y) { this.core.y += y; }; Rotate.prototype.incZ = function(z) { this.core.z += z; this._compileMemoLevel = 0; }; Rotate.prototype.incAngle = function(angle) { this.core.angle += angle; this._compileMemoLevel = 0; }; Rotate.prototype.getMatrix = function() { return (this._compileMemoLevel > 0) ? this._mat.slice(0) : SceneJS_math_rotationMat4v(this.core.angle * Math.PI / 180.0, [this.core.x, this.core.y, this.core.z]); }; Rotate.prototype.getAttributes = function() { return { x: this.core.x, y: this.core.y, z: this.core.z, angle : this.core.angle }; }; Rotate.prototype._compile = function() { this._preCompile(); this._compileNodes(); this._postCompile(); }; Rotate.prototype._preCompile = function() { var origMemoLevel = this._compileMemoLevel; if (this._compileMemoLevel == 0) { this._compileMemoLevel = 1; if (this.core.x != 0 || this.core.y != 0 || this.core.z != 0) { /* When building a view transform, apply the negated rotation angle * to correctly transform the SceneJS.Camera */ var angle = this.core.angle; this._mat = SceneJS_math_rotationMat4v(angle * Math.PI / 180.0, [this.core.x, this.core.y, this.core.z]); } else { this._mat = SceneJS_math_identityMat4(); } } var superXForm = SceneJS_modelTransformModule.transform; if (origMemoLevel < 2 || (!superXForm.fixed)) { var tempMat = SceneJS_math_mat4(); if (this._postMult) { SceneJS_math_mulMat4(superXForm.matrix, this._mat, tempMat); } else { SceneJS_math_mulMat4(this._mat, superXForm.matrix, tempMat); } this._xf.localMatrix = this._mat; this._xf.matrix = tempMat; this._xf.fixed = origMemoLevel == 2; if (this._compileMemoLevel == 1 && superXForm.fixed) { // Bump up memoization level if model-space fixed this._compileMemoLevel = 2; } } SceneJS_modelTransformModule.pushTransform(this.attr.id, this._xf); }; Rotate.prototype._postCompile = function() { SceneJS_modelTransformModule.popTransform(); }; })(); (function () { var Translate = SceneJS.createNodeType("translate"); Translate.prototype._init = function(params) { this._mat = null; this._xf = {}; if (this.core._nodeCount == 1) { // This node is the resource definer this.setMultOrder(params.multOrder); this.setXyz({x : params.x, y: params.y, z: params.z }); } }; Translate.prototype.setMultOrder = function(multOrder) { multOrder = multOrder || "post"; if (multOrder != "post" && multOrder != "pre") { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Illegal translate multOrder - '" + multOrder + "' should be 'pre' or 'post'"); } this.core.multOrder = multOrder; this._postMult = (multOrder == "post"); this._compileMemoLevel = 0; }; Translate.prototype.setXyz = function(xyz) { xyz = xyz || {}; var x = xyz.x || 0; var y = xyz.y || 0; var z = xyz.z || 0; this.core.x = x; this.core.y = y; this.core.z = z; this._compileMemoLevel = 0; }; Translate.prototype.getXyz = function() { return { x: this.core.x, y: this.core.y, z: this.core.z }; }; Translate.prototype.setX = function(x) { this.core.x = x; this._compileMemoLevel = 0; }; Translate.prototype.getX = function() { return this.core.x; }; Translate.prototype.setY = function(y) { this.core.y = y; this._compileMemoLevel = 0; }; Translate.prototype.getY = function() { return this.core.y; }; Translate.prototype.setZ = function(z) { this.core.z = z; this._compileMemoLevel = 0; }; Translate.prototype.getZ = function() { return this.core.z; }; Translate.prototype.incX = function(x) { this.core.x += x; this._compileMemoLevel = 0; }; Translate.prototype.incY = function(y) { this.core.y += y; }; Translate.prototype.incZ = function(z) { this.core.z += z; this._compileMemoLevel = 0; }; Translate.prototype.getMatrix = function() { return (this._compileMemoLevel > 0) ? this._mat.slice(0) : SceneJS_math_translationMat4v([this.core.x, this.core.y, this.core.z]); }; Translate.prototype.getAttributes = function() { return { x: this.core.x, y: this.core.y, z: this.core.z }; }; Translate.prototype._compile = function() { this._preCompile(); this._compileNodes(); this._postCompile(); }; Translate.prototype._preCompile = function() { var origMemoLevel = this._compileMemoLevel; if (this._compileMemoLevel == 0) { this._mat = SceneJS_math_translationMat4v([this.core.x, this.core.y, this.core.z]); this._compileMemoLevel = 1; } var superXForm = SceneJS_modelTransformModule.transform; if (origMemoLevel < 2 || (!superXForm.fixed)) { var tempMat = SceneJS_math_mat4(); if (this._postMult) { SceneJS_math_mulMat4(superXForm.matrix, this._mat, tempMat); } else { SceneJS_math_mulMat4(this._mat, superXForm.matrix, tempMat); } this._xf.localMatrix = this._mat; this._xf.matrix = tempMat; this._xf.fixed = origMemoLevel == 2; if (this._compileMemoLevel == 1 && superXForm.fixed) { this._compileMemoLevel = 2; } } SceneJS_modelTransformModule.pushTransform(this.attr.id, this._xf); }; Translate.prototype._postCompile = function() { SceneJS_modelTransformModule.popTransform(); }; })(); (function () { var Scale = SceneJS.createNodeType("scale"); Scale.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node is the resource definer this.setMultOrder(params.multOrder); this.setXYZ({x : params.x, y: params.y, z: params.z }); } this._mat = null; this._xf = {}; }; Scale.prototype.setMultOrder = function(multOrder) { multOrder = multOrder || "post"; if (multOrder != "post" && multOrder != "pre") { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Illegal scale multOrder - '" + multOrder + "' should be 'pre' or 'post'"); } this.core.multOrder = multOrder; this._postMult = (multOrder == "post"); this._compileMemoLevel = 0; }; Scale.prototype.setXYZ = function(xyz) { xyz = xyz || {}; this.core.x = (xyz.x != undefined) ? xyz.x : 1; this.core.y = (xyz.y != undefined) ? xyz.y : 1; this.core.z = (xyz.z != undefined) ? xyz.z : 1; this._resetCompilationMemos(); return this; }; Scale.prototype.getXYZ = function() { return { x: this.core.x, y: this.core.y, z: this.core.z }; }; Scale.prototype.setX = function(x) { this.core.x = (x != undefined && x != null) ? x : 1.0; this._resetCompilationMemos(); return this; }; Scale.prototype.getX = function() { return this.core.x; }; Scale.prototype.setY = function(y) { this.core.y = (y != undefined && y != null) ? y : 1.0; this._resetCompilationMemos(); return this; }; Scale.prototype.getY = function() { return this.core.y; }; Scale.prototype.setZ = function(z) { this.core.z = (z != undefined && z != null) ? z : 1.0; this._resetCompilationMemos(); return this; }; Scale.prototype.getZ = function() { return this.core.z; }; Scale.prototype.incX = function(x) { this.core.x += x; this._compileMemoLevel = 0; }; Scale.prototype.incY = function(y) { this.core.y += y; }; Scale.prototype.incZ = function(z) { this.core.z += z; this._compileMemoLevel = 0; }; Scale.prototype.getMatrix = function() { return (this._compileMemoLevel > 0) ? this._mat.slice(0) : SceneJS_math_scalingMat4v([this.core.x, this.core.y, this.core.z]); }; Scale.prototype.getAttributes = function() { return { x: this.core.x, y: this.core.y, z: this.core.z }; }; Scale.prototype._compile = function() { this._preCompile(); this._compileNodes(); this._postCompile(); }; Scale.prototype._preCompile = function() { var origMemoLevel = this._compileMemoLevel; if (this._compileMemoLevel == 0) { this._compileMemoLevel = 1; this._mat = SceneJS_math_scalingMat4v([this.core.x, this.core.y, this.core.z]); } var superXform = SceneJS_modelTransformModule.transform; if (origMemoLevel < 2 || (!superXform.fixed)) { var tempMat = SceneJS_math_mat4(); if (this._postMult) { SceneJS_math_mulMat4(superXform.matrix, this._mat, tempMat); } else { SceneJS_math_mulMat4(this._mat, superXform.matrix, tempMat); } this._xf.localMatrix = this._mat; this._xf.matrix = tempMat; this._xf.fixed = origMemoLevel == 2; if (this._compileMemoLevel == 1 && superXform.fixed) { // Bump up memoization level if model-space fixed this._compileMemoLevel = 2; } } SceneJS_modelTransformModule.pushTransform(this.attr.id, this._xf); }; Scale.prototype._postCompile = function(traversalContext) { SceneJS_modelTransformModule.popTransform(); }; })(); (function() { var Matrix = SceneJS.createNodeType("matrix"); Matrix.prototype._init = function(params) { this._xf = {}; if (this.core._nodeCount == 1) { // This node is the resource definer this.setMultOrder(params.multOrder); this.setElements(params.elements); } }; Matrix.prototype.setMultOrder = function(multOrder) { multOrder = multOrder || "post"; if (multOrder != "post" && multOrder != "pre") { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Illegal matrix multOrder - '" + multOrder + "' should be 'pre' or 'post'"); } this.attr.multOrder = multOrder; this._postMult = (multOrder == "post"); this._compileMemoLevel = 0; }; /** * Sets the matrix elements * @param {Array} elements One-dimensional array of matrix elements * @returns {Matrix} this */ Matrix.prototype.setElements = function(elements) { elements = elements || SceneJS_math_identityMat4(); if (!elements) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Matrix elements undefined"); } if (elements.length != 16) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Matrix elements should number 16"); } if (!this.core.mat) { this.core.mat = elements; } else { for (var i = 0; i < 16; i++) { this.core.mat[i] = elements[i]; } } this._resetCompilationMemos(); return this; }; /** Returns the matrix elements * @returns {Object} One-dimensional array of matrix elements */ Matrix.prototype.getElements = function() { var elements = new Array(16); for (var i = 0; i < 16; i++) { elements[i] = this.core.mat[i]; } return elements; }; /** * Returns a copy of the matrix as a 1D array of 16 elements * @returns {Number[16]} The matrix elements */ Matrix.prototype.getMatrix = function() { return this.core.mat.slice(0); }; Matrix.prototype._compile = function() { this._preCompile(); this._compileNodes(); this._postCompile(); }; Matrix.prototype._preCompile = function() { var origMemoLevel = this._compileMemoLevel; if (this._compileMemoLevel == 0) { this._compileMemoLevel = 1; } var superXform = SceneJS_modelTransformModule.transform; if (origMemoLevel < 2 || (!superXform.fixed)) { /* When building a view transform, apply the inverse of the matrix * to correctly transform the SceneJS.Camera */ var tempMat = SceneJS_math_mat4(); if (this._postMult) { SceneJS_math_mulMat4(superXform.matrix, this.core.mat, tempMat); } else { SceneJS_math_mulMat4(this.core.mat, superXform.matrix, tempMat); } this._xf.localMatrix = this.core.mat; this._xf.matrix = tempMat; this._xf.fixed = origMemoLevel == 2; if (this._compileMemoLevel == 1 && superXform.fixed) { // Bump up memoization level if model-space fixed this._compileMemoLevel = 2; } } SceneJS_modelTransformModule.pushTransform(this.attr.id, this._xf); }; Matrix.prototype._postCompile = function() { SceneJS_modelTransformModule.popTransform(); }; })(); (function () { var Quaternion = SceneJS.createNodeType("quaternion"); Quaternion.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node is the resource definer this.setMultOrder(params.multOrder); this.core.q = SceneJS_math_identityQuaternion(); if (params.x || params.y || params.x || params.angle || params.w) { this.setRotation(params); } if (params.rotations) { for (var i = 0; i < params.rotations.length; i++) { this.addRotation(params.rotations[i]); } } } this._mat = null; this._xf = {}; }; Quaternion.prototype.setMultOrder = function(multOrder) { multOrder = multOrder || "post"; if (multOrder != "post" && multOrder != "pre") { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Illegal quaternion multOrder - '" + multOrder + "' should be 'pre' or 'post'"); } this.core.multOrder = multOrder; this._postMult = (multOrder == "post"); this._compileMemoLevel = 0; }; Quaternion.prototype.setRotation = function(q) { q = q || {}; this.core.q = SceneJS_math_angleAxisQuaternion(q.x || 0, q.y || 0, q.z || 0, q.angle || 0); this._resetCompilationMemos(); return this; }; Quaternion.prototype.getRotation = function() { return SceneJS_math_angleAxisFromQuaternion(this.core.q); }; Quaternion.prototype.addRotation = function(q) { this.core.q = SceneJS_math_mulQuaternions(SceneJS_math_angleAxisQuaternion(q.x || 0, q.y || 0, q.z || 0, q.angle || 0), this.core.q); this._resetCompilationMemos(); return this; }; Quaternion.prototype.getMatrix = function() { return (this._compileMemoLevel > 0) ? this._mat.slice(0) : SceneJS_math_newMat4FromQuaternion(this.core.q); }; Quaternion.prototype.normalize = function() { this.core.q = SceneJS_math_normalizeQuaternion(this.core.q); this._resetCompilationMemos(); return this; }; Quaternion.prototype.getAttributes = function() { return { x: this.core.q[0], y: this.core.q[1], z: this.core.q[2], w: this.core.q[3] }; }; Quaternion.prototype._compile = function() { this._preCompile(); this._compileNodes(); this._postCompile(); }; Quaternion.prototype._preCompile = function() { var origMemoLevel = this._compileMemoLevel; if (this._compileMemoLevel == 0) { this._mat = SceneJS_math_newMat4FromQuaternion(this.core.q); this._compileMemoLevel = 1; } var superXform = SceneJS_modelTransformModule.transform; if (origMemoLevel < 2 || (!superXform.fixed)) { var tempMat = SceneJS_math_mat4(); if (this._postMult) { SceneJS_math_mulMat4(superXform.matrix, this._mat, tempMat); } else { SceneJS_math_mulMat4(this._mat, superXform.matrix, tempMat); } this._xf.localMatrix = this._mat; this._xf.matrix = tempMat; this._xf.fixed = origMemoLevel == 2; if (this._compileMemoLevel == 1 && superXform.fixed) { // Bump up memoization level if model-space fixed this._compileMemoLevel = 2; } } SceneJS_modelTransformModule.pushTransform(this.attr.id, this._xf); }; Quaternion.prototype._postCompile = function() { SceneJS_modelTransformModule.popTransform(); }; })(); var SceneJS_viewTransformModule = new (function() { var DEFAULT_TRANSFORM = { matrix : SceneJS_math_identityMat4(), fixed: true, identity : true, lookAt:SceneJS_math_LOOKAT_ARRAYS }; var idStack = []; var transformStack = []; var stackLen = 0; var nodeId; this.transform = DEFAULT_TRANSFORM; var dirty; var self = this; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; nodeId = null; self.transform = DEFAULT_TRANSFORM; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { loadTransform(); }); function loadTransform() { if (dirty) { if (stackLen > 0) { var t = self.transform; if (!t.matrixAsArray) { t.matrixAsArray = new Float32Array(t.matrix); t.normalMatrixAsArray = new Float32Array( SceneJS_math_transposeMat4(SceneJS_math_inverseMat4(t.matrix, SceneJS_math_mat4()))); } else { t.matrixAsArray.set(t.matrix); t.normalMatrixAsArray.set(SceneJS_math_transposeMat4(SceneJS_math_inverseMat4(t.matrix, SceneJS_math_mat4()))); } SceneJS_DrawList.setViewTransform(nodeId, t.matrixAsArray, t.normalMatrixAsArray, t.lookAt); } else { SceneJS_DrawList.setViewTransform(); } dirty = false; } } this.pushTransform = function(id, t) { idStack[stackLen] = id; transformStack[stackLen] = t; stackLen++; nodeId = id; this.transform = t; dirty = true; SceneJS_eventModule.fireEvent(SceneJS_eventModule.VIEW_TRANSFORM_UPDATED, this.transform); loadTransform(); }; this.popTransform = function() { stackLen--; if (stackLen > 0) { nodeId = idStack[stackLen - 1]; this.transform = transformStack[stackLen - 1]; } else { nodeId = null; this.transform = DEFAULT_TRANSFORM; } SceneJS_eventModule.fireEvent(SceneJS_eventModule.VIEW_TRANSFORM_UPDATED, this.transform); dirty = true; /*-------------------------------------------------------------- * TODO: Vital to reload transform here for some reason. * * When removed, then there are mysterious cases when only * the lights are transformed by the lookAt. *------------------------------------------------------------*/ loadTransform(); }; })(); (function() { var Lookat = SceneJS.createNodeType("lookAt"); Lookat.prototype._init = function(params) { this._mat = null; this._xf = { type: "lookat" }; if (this.core._nodeCount == 1) { // This node is the resource definer if (!params.eye && !params.look && !params.up) { this.setEye({x: 0, y: 0, z: 1.0 }); this.setLook({x: 0, y: 0, z: 0 }); this.setUp({x: 0, y: 1.0, z: 0 }); } else { this.setEye(params.eye); this.setLook(params.look); this.setUp(params.up); } } }; Lookat.prototype.setEye = function(eye) { eye = eye || {}; this.core.eyeX = (eye.x != undefined && eye.x != null) ? eye.x : 0; this.core.eyeY = (eye.y != undefined && eye.y != null) ? eye.y : 0; this.core.eyeZ = (eye.z != undefined && eye.z != null) ? eye.z : 0; this._compileMemoLevel = 0; }; Lookat.prototype.setEyeX = function(x) { this.core.eyeX = x || 0; this._compileMemoLevel = 0; }; Lookat.prototype.setEyeY = function(y) { this.core.eyeY = y || 0; this._compileMemoLevel = 0; }; Lookat.prototype.incEye = function(eye) { eye = eye || {}; this.core.eyeX += (eye.x != undefined && eye.x != null) ? eye.x : 0; this.core.eyeY += (eye.y != undefined && eye.y != null) ? eye.y : 0; this.core.eyeZ += (eye.z != undefined && eye.z != null) ? eye.z : 0; this._compileMemoLevel = 0; }; Lookat.prototype.incEyeX = function(x) { this.core.eyeX += x; this._compileMemoLevel = 0; }; Lookat.prototype.incEyeY = function(y) { this.core.eyeY += y; this._compileMemoLevel = 0; }; Lookat.prototype.incEyeZ = function(z) { this.core.eyeZ += z; this._compileMemoLevel = 0; }; Lookat.prototype.setEyeZ = function(z) { this.core.eyeZ = z || 0; this._compileMemoLevel = 0; }; Lookat.prototype.getEye = function() { return { x: this.core.eyeX, y: this.core.eyeY, z: this.core.eyeZ }; }; Lookat.prototype.setLook = function(look) { look = look || {}; this.core.lookX = (look.x != undefined && look.x != null) ? look.x : 0; this.core.lookY = (look.y != undefined && look.y != null) ? look.y : 0; this.core.lookZ = (look.z != undefined && look.z != null) ? look.z : 0; this._compileMemoLevel = 0; }; Lookat.prototype.setLookX = function(x) { this.core.lookX = x || 0; this._compileMemoLevel = 0; }; Lookat.prototype.setLookY = function(y) { this.core.lookY = y || 0; this._compileMemoLevel = 0; }; Lookat.prototype.setLookZ = function(z) { this.core.lookZ = z || 0; this._compileMemoLevel = 0; }; Lookat.prototype.incLook = function(look) { look = look || {}; this.core.lookX += (look.x != undefined && look.x != null) ? look.x : 0; this.core.lookY += (look.y != undefined && look.y != null) ? look.y : 0; this.core.lookZ += (look.z != undefined && look.z != null) ? look.z : 0; this._compileMemoLevel = 0; }; Lookat.prototype.getLook = function() { return { x: this.core.lookX, y: this.core.lookY, z: this.core.lookZ }; }; Lookat.prototype.setUp = function(up) { up = up || { y: 1.0 }; var x = (up.x != undefined && up.x != null) ? up.x : 0; var y = (up.y != undefined && up.y != null) ? up.y : 0; var z = (up.z != undefined && up.z != null) ? up.z : 0; if (x == 0 && y == 0 && z == 0) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Lookat up vector is zero length - at least one of its x,y and z components must be non-zero"); } this.core.upX = x; this.core.upY = y; this.core.upZ = z; this._compileMemoLevel = 0; }; Lookat.prototype.setUpX = function(x) { this.core.upX = x || 0; this._compileMemoLevel = 0; }; Lookat.prototype.setUpY = function(x) { this.core.upY = y || 0; this._compileMemoLevel = 0; }; Lookat.prototype.setUpZ = function(x) { this.core.upZ = z || 0; this._compileMemoLevel = 0; }; Lookat.prototype.getUp = function() { return { x: this.core.upX, y: this.core.upY, z: this.core.upZ }; }; Lookat.prototype.incUp = function(up) { up = up || {}; this.core.upX += (up.x != undefined && up.x != null) ? up.x : 0; this.core.upY += (up.y != undefined && up.y != null) ? up.y : 0; this.core.upZ += (up.z != undefined && up.z != null) ? up.z : 0; this._compileMemoLevel = 0; }; /** * Returns a copy of the matrix as a 1D array of 16 elements * @returns {Number[16]} */ Lookat.prototype.getMatrix = function() { return (this._compileMemoLevel > 0) ? this._mat.slice(0) : SceneJS_math_lookAtMat4c( this.core.eyeX, this.core.eyeY, this.core.eyeZ, this.core.lookX, this.core.lookY, this.core.lookZ, this.core.upX, this.core.upY, this.core.upZ); }; Lookat.prototype.getAttributes = function() { return { look: { x: this.core.lookX, y: this.core.lookY, z: this.core.lookZ }, eye: { x: this.core.eyeX, y: this.core.eyeY, z: this.core.eyeZ }, up: { x: this.core.upX, y: this.core.upY, z: this.core.upZ } }; }; Lookat.prototype._compile = function() { if (this._compileMemoLevel == 0) { this._mat = SceneJS_math_lookAtMat4c( this.core.eyeX, this.core.eyeY, this.core.eyeZ, this.core.lookX, this.core.lookY, this.core.lookZ, this.core.upX, this.core.upY, this.core.upZ); this._compileMemoLevel = 1; this._xf.matrix = this._mat; this._xf.lookAt = { eye: [this.core.eyeX, this.core.eyeY, this.core.eyeZ ], look: [this.core.lookX, this.core.lookY, this.core.lookZ ], up: [this.core.upX, this.core.upY, this.core.upZ ] }; } SceneJS_viewTransformModule.pushTransform(this.attr.id, this._xf); this._compileNodes(); SceneJS_viewTransformModule.popTransform(); }; })(); (function () { var Stationary = SceneJS.createNodeType("stationary"); Stationary.prototype._compile = function() { var origMemoLevel = this._compileMemoLevel; var superXform = SceneJS_viewTransformModule.transform; var lookAt = superXform.lookAt; if (lookAt) { if (this._compileMemoLevel == 0 || (!superXform.fixed)) { var tempMat = SceneJS_math_mat4(); SceneJS_math_mulMat4(superXform.matrix, SceneJS_math_translationMat4v(lookAt.eye), tempMat); this._xform = { matrix: tempMat, lookAt: lookAt, fixed: origMemoLevel == 1 }; if (superXform.fixed) { this._compileMemoLevel = 1; } } SceneJS_viewTransformModule.pushTransform(this.attr.id, this._xform); this._compileNodes(); SceneJS_viewTransformModule.popTransform(); } else { this._compileNodes(); } }; })(); (function () { var Billboard = SceneJS.createNodeType("billboard"); Billboard.prototype._compile = function() { this._preCompile(); this._compileNodes(); this._postCompile(); }; Billboard.prototype._preCompile = function() { // 0. The base variable var superViewXForm = SceneJS_viewTransformModule.transform; var lookAt = superViewXForm.lookAt; var superModelXForm = SceneJS_modelTransformModule.transform; var matrix = superModelXForm.matrix.slice(0); // 1. Invert the model rotation matrix, which will reset the subnodes rotation var rotMatrix = [ matrix[0], matrix[1], matrix[2], 0, matrix[4], matrix[5], matrix[6], 0, matrix[8], matrix[9], matrix[10], 0, 0, 0, 0, 1 ]; SceneJS_math_inverseMat4(rotMatrix); SceneJS_math_mulMat4(matrix, rotMatrix, matrix); // 2. Get the billboard Z vector var ZZ = []; SceneJS_math_subVec3(lookAt.eye, lookAt.look, ZZ); SceneJS_math_normalizeVec3(ZZ); // 3. Get the billboard X vector var XX = []; SceneJS_math_cross3Vec3(lookAt.up, ZZ, XX); SceneJS_math_normalizeVec3(XX); // 4. Get the billboard Y vector var YY = []; SceneJS_math_cross3Vec3(ZZ, XX, YY); SceneJS_math_normalizeVec3(YY); // 5. Multiply those billboard vector to the matrix SceneJS_math_mulMat4(matrix, [ XX[0], XX[1], XX[2], 0, YY[0], YY[1], YY[2], 0, ZZ[0], ZZ[1], ZZ[2], 0, 0, 0, 0, 1 ], matrix); // 6. Render SceneJS_modelTransformModule.pushTransform(this.attr.id, { matrix: matrix }); // TODO : memoize! }; Billboard.prototype._postCompile = function() { SceneJS_modelTransformModule.popTransform(); }; })(); (function() { var idStack = []; var transformStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setProjectionTransform(idStack[stackLen - 1], transformStack[stackLen - 1]); } else { SceneJS_DrawList.setProjectionTransform(); } dirty = false; } }); var Camera = SceneJS.createNodeType("camera"); Camera.prototype._init = function(params) { if (this.core._nodeCount == 1) { this.setOptics(params.optics); // Can be undefined } }; Camera.prototype.setOptics = function(optics) { var core = this.core; if (!optics) { core.optics = { type: optics.type, left : optics.left || -1.0, bottom : optics.bottom || -1.0, near : optics.near || 0.1, right : optics.right || 1.00, top : optics.top || 1.0, far : optics.far || 5000.0 }; } else { if (optics.type == "ortho") { core.optics = SceneJS._applyIf(SceneJS_math_ORTHO_OBJ, { type: optics.type, left : optics.left, bottom : optics.bottom, near : optics.near, right : optics.right, top : optics.top, far : optics.far }); } else if (optics.type == "frustum") { core.optics = { type: optics.type, left : optics.left || -1.0, bottom : optics.bottom || -1.0, near : optics.near || 0.1, right : optics.right || 1.00, top : optics.top || 1.0, far : optics.far || 5000.0 }; } else if (optics.type == "perspective") { core.optics = { type: optics.type, fovy : optics.fovy || 60.0, aspect: optics.aspect || 1.0, near : optics.near || 0.1, far : optics.far || 5000.0 }; } else if (!optics.type) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Camera configuration invalid: optics type not specified - " + "supported types are 'perspective', 'frustum' and 'ortho'"); } else { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Camera configuration invalid: optics type not supported - " + "supported types are 'perspective', 'frustum' and 'ortho'"); } } this._rebuild(); }; Camera.prototype._rebuild = function () { var optics = this.core.optics; if (optics.type == "ortho") { this.core.matrix = SceneJS_math_orthoMat4c( optics.left, optics.right, optics.bottom, optics.top, optics.near, optics.far); } else if (optics.type == "frustum") { this.core.matrix = SceneJS_math_frustumMatrix4( optics.left, optics.right, optics.bottom, optics.top, optics.near, optics.far); } else if (optics.type == "perspective") { this.core.matrix = SceneJS_math_perspectiveMatrix4( optics.fovy * Math.PI / 180.0, optics.aspect, optics.near, optics.far); } if (!this.core.matrixAsArray) { this.core.matrixAsArray = new Float32Array(this.core.matrix); } else { this.core.matrixAsArray.set(this.core.matrix); } }; Camera.prototype.getOptics = function() { var optics = {}; for (var key in this.core.optics) { if (this.core.optics.hasOwnProperty(key)) { optics[key] = this.core.optics[key]; } } return optics; }; Camera.prototype.getMatrix = function() { if (this._compileMemoLevel == 0) { this._rebuild(); } return this.core.matrix.slice(0); }; Camera.prototype._compile = function() { idStack[stackLen] = this.attr.id; transformStack[stackLen] = this.core; stackLen++; dirty = true; this._compileNodes(); stackLen--; dirty = true; }; })(); (function() { var Matrix = SceneJS.createNodeType("xform"); Matrix.prototype._init = function(params) { this._xf = {}; if (this.core._nodeCount == 1) { // This node is the resource definer this.setElements(params.elements); } }; /** * Sets the matrix elements * @param {Array} elements One-dimensional array of matrix elements */ Matrix.prototype.setElements = function(elements) { elements = elements || SceneJS_math_identityMat4(); if (elements.length != 16) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Matrix elements should number 16"); } var core = this.core; if (!core.matrix) { core.matrix = elements; } else { for (var i = 0; i < 16; i++) { core.matrix[i] = elements[i]; } } if (!core.matrixAsArray) { core.matrixAsArray = new Float32Array(elements); core.normalMatrixAsArray = new Float32Array( SceneJS_math_transposeMat4(SceneJS_math_inverseMat4(elements, SceneJS_math_mat4()))); } else { core.matrixAsArray.set(elements); core.normalMatrixAsArray.set( SceneJS_math_transposeMat4(SceneJS_math_inverseMat4(elements, SceneJS_math_mat4()))); } return this; }; Matrix.prototype._compile = function() { SceneJS_modelTransformModule.pushTransform(this.attr.id, this.core); this._compileNodes(); SceneJS_modelTransformModule.popTransform(); }; })(); new (function() { var idStack = []; var lightStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setLights(idStack[stackLen - 1], lightStack.slice(0, stackLen)); } else { // Full compile supplies it's own default states SceneJS_DrawList.setLights(); } dirty = false; } }); var Light = SceneJS.createNodeType("light"); Light.prototype._init = function(params) { params = params || {}; if (this.core._nodeCount == 1) { // This node is the resource definer this.core.light = { viewSpace: !!params.viewSpace }; this.setMode(params.mode); this.setColor(params.color); this.setDiffuse(params.diffuse); this.setSpecular(params.specular); this.setPos(params.pos); this.setDir(params.dir); this.setConstantAttenuation(params.constantAttenuation); this.setLinearAttenuation(params.linearAttenuation); this.setQuadraticAttenuation(params.quadraticAttenuation); } }; Light.prototype.setMode = function(mode) { mode = mode || "dir"; if (mode != "dir" && mode != "point") { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Light unsupported mode - should be 'dir' or 'point' or 'ambient'"); } this.core.light.mode = mode; }; Light.prototype.getMode = function() { return this.core.light.mode; }; Light.prototype.setColor = function(color) { color = color || {}; this.core.light.color = [ color.r != undefined ? color.r : 1.0, color.g != undefined ? color.g : 1.0, color.b != undefined ? color.b : 1.0 ]; }; Light.prototype.getColor = function() { return { r: this.core.light.color[0], g: this.core.light.color[1], b: this.core.light.color[2] }; }; Light.prototype.setDiffuse = function (diffuse) { this.core.light.diffuse = (diffuse != undefined) ? diffuse : true; }; Light.prototype.getDiffuse = function() { return this.core.light.diffuse; }; Light.prototype.setSpecular = function (specular) { this.core.light.specular = specular || true; }; Light.prototype.getSpecular = function() { return this.core.light.specular; }; Light.prototype.setPos = function(pos) { pos = pos || {}; this.core.light.pos = [ pos.x || 0.0, pos.y || 0.0, pos.z || 0.0 ]; }; Light.prototype.getPos = function() { return { x: this.core.light.pos[0], y: this.core.light.pos[1], z: this.core.light.pos[2] }; }; Light.prototype.setDir = function(dir) { dir = dir || {}; this.core.light.dir = [ dir.x || 0.0, dir.y || 0.0, (dir.z == undefined || dir.z == null) ? -1 : dir.z ]; }; Light.prototype.getDir = function() { return { x: this.core.light.dir[0], y: this.core.light.dir[1], z: this.core.light.dir[2] }; }; Light.prototype.setConstantAttenuation = function (constantAttenuation) { this.core.light.constantAttenuation = (constantAttenuation != undefined) ? constantAttenuation : 1.0; }; Light.prototype.getConstantAttenuation = function() { return this.core.light.constantAttenuation; }; Light.prototype.setLinearAttenuation = function (linearAttenuation) { this.core.light.linearAttenuation = linearAttenuation || 0.0; }; Light.prototype.getLinearAttenuation = function() { return this.core.light.linearAttenuation; }; Light.prototype.setQuadraticAttenuation = function (quadraticAttenuation) { this.core.light.quadraticAttenuation = quadraticAttenuation || 0.0; }; Light.prototype.getQuadraticAttenuation = function() { return this.core.light.quadraticAttenuation; }; Light.prototype._compile = function() { var modelMat = SceneJS_modelTransformModule.transform.matrix; if (this.core.light.mode == "point") { this.core.light.worldPos = SceneJS_math_transformPoint3(modelMat, this.core.light.pos); } else if (this.core.light.mode == "dir") { this.core.light.worldDir = SceneJS_math_transformVector3(modelMat, this.core.light.dir); } idStack[stackLen] = this.attr.id; lightStack[stackLen] = this.core.light; stackLen++; dirty = true; this._compileNodes(); }; })(); new (function() { var DEFAULT_MATERIAL = { baseColor : [ 0.0, 0.0, 0.0 ], specularColor : [ 0.0, 0.0, 0.0 ], specular : 1.0, shine : 10.0, reflect : 0.8, alpha : 1.0, emit : 0.0 }; var idStack = []; var materialStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setMaterial(idStack[stackLen - 1], materialStack[stackLen - 1]); } else { SceneJS_DrawList.setMaterial(); } dirty = false; } }); var Material = SceneJS.createNodeType("material"); Material.prototype._init = function(params) { if (this.core._nodeCount == 1) { this.setBaseColor(params.baseColor); this.setSpecularColor(params.specularColor); this.setSpecular(params.specular); this.setShine(params.shine); this.setReflect(params.reflect); this.setEmit(params.emit); this.setAlpha(params.alpha); this.setOpacity(params.opacity); } }; Material.prototype.setBaseColor = function(color) { this.core.baseColor = color ? [ color.r != undefined && color.r != null ? color.r : 0.0, color.g != undefined && color.g != null ? color.g : 0.0, color.b != undefined && color.b != null ? color.b : 0.0 ] : DEFAULT_MATERIAL.baseColor; }; Material.prototype.getBaseColor = function() { return { r: this.core.baseColor[0], g: this.core.baseColor[1], b: this.core.baseColor[2] }; }; Material.prototype.setSpecularColor = function(color) { this.core.specularColor = color ? [ color.r != undefined && color.r != null ? color.r : 0.0, color.g != undefined && color.g != null ? color.g : 0.0, color.b != undefined && color.b != null ? color.b : 0.0 ] : DEFAULT_MATERIAL.specularColor; }; Material.prototype.getSpecularColor = function() { return { r: this.core.specularColor[0], g: this.core.specularColor[1], b: this.core.specularColor[2] }; }; Material.prototype.setSpecular = function(specular) { this.core.specular = (specular != undefined && specular != null) ? specular : DEFAULT_MATERIAL.specular; }; Material.prototype.getSpecular = function() { return this.core.specular; }; Material.prototype.setShine = function(shine) { this.core.shine = (shine != undefined && shine != null) ? shine : DEFAULT_MATERIAL.shine; }; Material.prototype.getShine = function() { return this.core.shine; }; Material.prototype.setReflect = function(reflect) { this.core.reflect = (reflect != undefined && reflect != null) ? reflect : DEFAULT_MATERIAL.reflect; }; Material.prototype.getReflect = function() { return this.core.reflect; }; Material.prototype.setEmit = function(emit) { this.core.emit = (emit != undefined && emit != null) ? emit : DEFAULT_MATERIAL.emit; }; Material.prototype.getEmit = function() { return this.core.emit; }; Material.prototype.setAlpha = function(alpha) { this.core.alpha = (alpha != undefined && alpha != null) ? alpha : DEFAULT_MATERIAL.alpha; }; Material.prototype.getAlpha = function() { return this.core.alpha; }; Material.prototype.setOpacity = function(opacity) { this.core.opacity = (opacity != undefined && opacity != null) ? opacity : DEFAULT_MATERIAL.opacity; }; Material.prototype.getOpacity = function() { return this.core.opacity; }; Material.prototype._compile = function() { this._preCompile(); this._compileNodes(); this._postCompile(); }; Material.prototype._preCompile = function() { // var top = stackLen > 0 ? materialStack[stackLen - 1] : null; // var origMemoLevel = this._compileMemoLevel; // if (this._compileMemoLevel == 0 || (top && !top.fixed)) { // var m = this.attr; // top = top || DEFAULT_MATERIAL; // this._material = { // baseColor : m.baseColor ? [ m.baseColor.r, m.baseColor.g, m.baseColor.b ] : top.baseColor, // specularColor: m.specularColor ? [ m.specularColor.r, m.specularColor.g, m.specularColor.b ] : top.specularColor, // specular : (m.specular != undefined ) ? m.specular : top.specular, // shine : (m.shine != undefined ) ? m.shine : top.shine, // reflect : (m.reflect != undefined) ? m.reflect : top.reflect, // alpha : (m.alpha != undefined) ? m.alpha : top.alpha, // emit : (m.emit != undefined) ? m.emit : top.emit, // opacity : (m.opacity != undefined) ? m.opacity : top.opacity // }; // this._compileMemoLevel = 1; // } // this._material.fixed = (origMemoLevel == 1); // State not changed because of update to this node idStack[stackLen] = this.core._coreId; // Tie draw list state to core, not to scene node - reduces amount of state materialStack[stackLen] = this.core; stackLen++; dirty = true; }; Material.prototype._postCompile = function() { stackLen--; // Pop material dirty = true; }; Material.prototype._destroy = function() {}; })(); /** * @class A scene node that defines color transforms to apply to materials. * */ new (function() { var idStack = []; var colortransStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setColortrans(idStack[stackLen - 1], colortransStack[stackLen - 1]); } else { SceneJS_DrawList.setColortrans(); } dirty = false; } }); var Colortrans = SceneJS.createNodeType("colortrans"); Colortrans.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node defines the resource this.setScale(params.scale); this.setAdd(params.add); this.setSaturation(params.saturation); } }; Colortrans.prototype.setSaturation = function(saturation) { this.core.saturation = saturation; }; Colortrans.prototype.mulSaturation = function(saturation) { this.core.saturation *= saturation; }; Colortrans.prototype.incSaturation = function(saturation) { this.core.saturation += saturation; }; Colortrans.prototype.getSaturation = function() { return this.core.saturation; }; Colortrans.prototype.setScale = function(scale) { scale = scale || {}; this.core.scale = { r: scale.r != undefined ? scale.r : 1, g: scale.g != undefined ? scale.g : 1, b: scale.b != undefined ? scale.b : 1, a: scale.a != undefined ? scale.a : 1 }; }; Colortrans.prototype.incScale = function(scale) { scale = scale || {}; var s = this.core.scale; if (scale.r) { s.r += scale.r; } if (scale.g) { s.g += scale.g; } if (scale.b) { s.b += scale.b; } if (scale.a) { s.a += scale.a; } }; Colortrans.prototype.mulScale = function(scale) { scale = scale || {}; var s = this.core.scale; if (scale.r) { s.r *= scale.r; } if (scale.g) { s.g *= scale.g; } if (scale.b) { s.b *= scale.b; } if (scale.a) { s.a *= scale.a; } }; Colortrans.prototype.getScale = function() { return this.core.scale; }; Colortrans.prototype.setAdd = function(add) { add = add || {}; this.core.add = { r: add.r != undefined ? add.r : 0, g: add.g != undefined ? add.g : 0, b: add.b != undefined ? add.b : 0, a: add.a != undefined ? add.a : 0 }; }; Colortrans.prototype.incAdd = function(add) { add = add || {}; var s = this.core.add; if (add.r) { s.r += add.r; } if (add.g) { s.g += add.g; } if (add.b) { s.b += add.b; } if (add.a) { s.a += add.a; } }; Colortrans.prototype.mulAdd = function(add) { add = add || {}; var s = this.core.add; if (add.r) { s.r *= add.r; } if (add.g) { s.g *= add.g; } if (add.b) { s.b *= add.b; } if (add.a) { s.a *= add.a; } }; Colortrans.prototype.getAdd = function() { return this.core.add; }; Colortrans.prototype._compile = function() { idStack[stackLen] = this.core._coreId; // Tie draw list state to core, not to scene node colortransStack[stackLen] = this.core; stackLen++; dirty = true; this._compileNodes(); stackLen--; dirty = true; }; })(); /** * A scene node that defines one or more layers of texture to apply to geometries within its subgraph * that have UV coordinates. */ var SceneJS_textureModule = new (function() { var idStack = []; var textureStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.INIT, function() { }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setTexture(idStack[stackLen - 1], textureStack[stackLen - 1]); } else { SceneJS_DrawList.setTexture(); } dirty = false; } }); /** Creates texture from either image URL or image object */ function createTexture(scene, cfg, onComplete) { var context = scene.canvas.context; var textureId = SceneJS._createUUID(); var update; try { if (cfg.autoUpdate) { update = function() { //TODO: fix this when minefield is upto spec try { context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, image); } catch(e) { context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, image, null); } context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MAG_FILTER, context.LINEAR); context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.LINEAR); //context.generateMipmap(context.TEXTURE_2D); }; } return new SceneJS_webgl_Texture2D(context, { textureId : textureId, canvas: scene.canvas, image : cfg.image, url: cfg.uri, texels :cfg.texels, minFilter : getGLOption("minFilter", context, cfg, context.NEAREST_MIPMAP_NEAREST), magFilter : getGLOption("magFilter", context, cfg, context.LINEAR), wrapS : getGLOption("wrapS", context, cfg, context.CLAMP_TO_EDGE), wrapT : getGLOption("wrapT", context, cfg, context.CLAMP_TO_EDGE), isDepth : getOption(cfg.isDepth, false), depthMode : getGLOption("depthMode", context, cfg, context.LUMINANCE), depthCompareMode : getGLOption("depthCompareMode", context, cfg, context.COMPARE_R_TO_TEXTURE), depthCompareFunc : getGLOption("depthCompareFunc", context, cfg, context.LEQUAL), flipY : getOption(cfg.flipY, true), width: getOption(cfg.width, 1), height: getOption(cfg.height, 1), internalFormat : getGLOption("internalFormat", context, cfg, context.LEQUAL), sourceFormat : getGLOption("sourceType", context, cfg, context.ALPHA), sourceType : getGLOption("sourceType", context, cfg, context.UNSIGNED_BYTE), logging: SceneJS_loggingModule , update: update }, onComplete); } catch (e) { throw SceneJS_errorModule.fatalError(SceneJS.errors.ERROR, "Failed to create texture: " + e.message || e); } } function getGLOption(name, context, cfg, defaultVal) { var value = cfg[name]; if (value == undefined) { return defaultVal; } var glName = SceneJS_webgl_enumMap[value]; if (glName == undefined) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Unrecognised value for texture node property '" + name + "' value: '" + value + "'"); } var glValue = context[glName]; // if (!glValue) { // throw new SceneJS.errors.WebGLUnsupportedNodeConfigException( // "This browser's WebGL does not support value of SceneJS.texture node property '" + name + "' value: '" + value + "'"); // } return glValue; } function getOption(value, defaultVal) { return (value == undefined) ? defaultVal : value; } function destroyTexture(texture) { texture.destroy(); } /*---------------------------------------------------------------------------------------------------------------- * Texture node *---------------------------------------------------------------------------------------------------------------*/ var Texture = SceneJS.createNodeType("texture"); Texture.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node is the resource definer this.core.layers = []; this.core.params = {}; var config = SceneJS_debugModule.getConfigs("texturing") || {}; var waitForLoad = (config.waitForLoad != undefined && config.waitForLoad != null) ? config.waitForLoad : params.waitForLoad; if (!params.layers) { throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_CONFIG_EXPECTED, "texture layers missing"); } if (!SceneJS._isArray(params.layers)) { throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_CONFIG_EXPECTED, "texture layers should be an array"); } for (var i = 0; i < params.layers.length; i++) { var layerParam = params.layers[i]; if (!layerParam.uri && !layerParam.frameBuf && !layerParam.video && !layerParam.image && !layerParam.canvasId) { throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_CONFIG_EXPECTED, "texture layer " + i + " has no uri, frameBuf, video or canvasId specified"); } if (layerParam.applyFrom) { if (layerParam.applyFrom != "uv" && layerParam.applyFrom != "uv2" && layerParam.applyFrom != "normal" && layerParam.applyFrom != "geometry") { throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_CONFIG_EXPECTED, "texture layer " + i + " applyFrom value is unsupported - " + "should be either 'uv', 'uv2', 'normal' or 'geometry'"); } } if (layerParam.applyTo) { if (layerParam.applyTo != "baseColor" && // Colour map layerParam.applyTo != "specular" && // Specular map layerParam.applyTo != "emit" && // Emission map layerParam.applyTo != "alpha" && // Alpha map // layerParam.applyTo != "diffuseColor" && layerParam.applyTo != "normals") { throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_CONFIG_EXPECTED, "texture layer " + i + " applyTo value is unsupported - " + "should be either 'baseColor', 'specular' or 'normals'"); } } if (layerParam.blendMode) { if (layerParam.blendMode != "add" && layerParam.blendMode != "multiply") { throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_CONFIG_EXPECTED, "texture layer " + i + " blendMode value is unsupported - " + "should be either 'add' or 'multiply'"); } } var layer = { image : null, // Initialised when state == IMAGE_LOADED creationParams: layerParam, // Create texture using this waitForLoad: waitForLoad, texture: null, // Initialised when state == TEXTURE_LOADED applyFrom: layerParam.applyFrom || "uv", applyTo: layerParam.applyTo || "baseColor", blendMode: layerParam.blendMode || "add", blendFactor: (layerParam.blendFactor != undefined && layerParam.blendFactor != null) ? layerParam.blendFactor : 1.0, translate: { x:0, y: 0}, scale: { x: 1, y: 1 }, rotate: { z: 0.0 } }; this.core.layers.push(layer); this._setLayerTransform(layerParam, layer); if (layer.creationParams.frameBuf) { layer.texture = SceneJS._compilationStates.getState("frameBuf", this.scene.attr.id, layer.creationParams.frameBuf); } else if (layer.creationParams.video) { layer.texture = SceneJS._compilationStates.getState("video", this.scene.attr.id, layer.creationParams.video); } else { SceneJS_sceneStatusModule.nodeLoading(this); var self = this; layer.texture = createTexture( this.scene, layer.creationParams, function() { // onComplete SceneJS_sceneStatusModule.nodeLoaded(self); if (self._destroyed) { destroyTexture(layer.texture); } SceneJS_compileModule.nodeUpdated(self, "loaded"); // Trigger display list redraw }); } } } }; Texture.prototype.setLayer = function(cfg) { if (cfg.index == undefined || cfg.index == null) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Invalid texture set layer argument: index null or undefined"); } if (cfg.index < 0 || cfg.index >= this.core.layers.length) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Invalid texture set layer argument: index out of range (" + this.core.layers.length + " layers defined)"); } this._setLayer(parseInt(cfg.index), cfg); }; Texture.prototype.setLayers = function(layers) { var indexNum; for (var index in layers) { if (layers.hasOwnProperty(index)) { if (index != undefined || index != null) { indexNum = parseInt(index); if (indexNum < 0 || indexNum >= this.core.layers.length) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "Invalid texture set layer argument: index out of range (" + this.core.layers.length + " layers defined)"); } this._setLayer(indexNum, layers[index] || {}); } } } }; Texture.prototype._setLayer = function(index, cfg) { cfg = cfg || {}; var layer = this.core.layers[index]; if (cfg.blendFactor != undefined && cfg.blendFactor != null) { layer.blendFactor = cfg.blendFactor; } this._setLayerTransform(cfg, layer); }; Texture.prototype._setLayerTransform = function(cfg, layer) { var matrix; var t; if (cfg.translate) { var translate = cfg.translate; if (translate.x != undefined) { layer.translate.x = translate.x; } if (translate.y != undefined) { layer.translate.y = translate.y; } matrix = SceneJS_math_translationMat4v([ translate.x || 0, translate.y || 0, 0]); } if (cfg.scale) { var scale = cfg.scale; if (scale.x != undefined) { layer.scale.x = scale.x; } if (scale.y != undefined) { layer.scale.y = scale.y; } t = SceneJS_math_scalingMat4v([ scale.x || 1, scale.y || 1, 1]); matrix = matrix ? SceneJS_math_mulMat4(matrix, t) : t; } if (cfg.rotate) { var rotate = cfg.rotate; if (rotate.z != undefined) { layer.rotate.z = rotate.z || 0; } t = SceneJS_math_rotationMat4v(rotate.z * 0.0174532925, [0,0,1]); matrix = matrix ? SceneJS_math_mulMat4(matrix, t) : t; } if (matrix) { layer.matrix = matrix; if (!layer.matrixAsArray) { layer.matrixAsArray = new Float32Array(layer.matrix); } else { layer.matrixAsArray.set(layer.matrix); } layer.matrixAsArray = new Float32Array(layer.matrix); // TODO - reinsert into array } }; Texture.prototype._compile = function() { idStack[stackLen] = this.core._coreId; // Tie draw list state to core, not to scene node textureStack[stackLen] = this.core; stackLen++; dirty = true; this._compileNodes(); stackLen--; dirty = true; }; Texture.prototype._destroy = function() { if (this.core._nodeCount == 1) { // Last resource user var layer; for (var i = 0, len = this.core.layers.length; i < len; i++) { layer = this.core.layers[i]; if (layer.texture) { destroyTexture(layer.texture); } } } }; })(); /** * @class A scene node that defines an arbitrary clipping plane for nodes in its sub graph. */ new (function() { var idStack = []; var clipStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setClips(idStack[stackLen - 1], clipStack.slice(0, stackLen)); } else { SceneJS_DrawList.setClips(); } dirty = false; } }); var Clip = SceneJS.createNodeType("clip"); Clip.prototype._init = function(params) { if (this.core._nodeCount == 1) { this.setMode(params.mode); this.setA(params.a); this.setB(params.b); this.setC(params.c); // this.core.doClean = function() { // var modelMat = SceneJS_modelTransformModule.transform.matrix; // var worldA = SceneJS_math_transformPoint3(modelMat, this.a); // var worldB = SceneJS_math_transformPoint3(modelMat, this.b); // var worldC = SceneJS_math_transformPoint3(modelMat, this.c); // var normal = SceneJS_math_normalizeVec3( // SceneJS_math_cross3Vec4( // SceneJS_math_normalizeVec3( // SceneJS_math_subVec3(worldB, worldA, [0,0,0]), [0,0,0]), // SceneJS_math_normalizeVec3( // SceneJS_math_subVec3(worldB, worldC, [0,0,0]), [0,0,0]))); // var dist = SceneJS_math_dotVector3(normal, worldA); // this.normalAndDist = [normal[0], normal[1], normal[2], dist]; // }; } }; /** Sets the clipping mode. Default is "disabled". */ Clip.prototype.setMode = function(mode) { mode = mode || "outside"; if (mode != "disabled" && mode != "inside" && mode != "outside") { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "SceneJS.clip has a mode of unsupported type: '" + mode + " - should be 'disabled', 'inside' or 'outside'"); } this.core.mode = mode; }; Clip.prototype.getMode = function() { return this.core.mode; }; Clip.prototype.setAbc = function(abc) { abc = abc || {}; this.setA(abc.a); this.setB(abc.b); this.setC(abc.c); }; Clip.prototype.getAbc = function() { return { a: this.getA(), b: this.getB(), c: this.getC() }; }; Clip.prototype.setA = function(a) { a = a || {}; this.core.a = [ a.x != undefined ? a.x : 0.0, a.y != undefined ? a.y : 0.0, a.z != undefined ? a.z : 0.0, 1 ]; }; Clip.prototype.getA = function() { return { x: this.core.a[0], y: this.core.a[1], z: this.core.a[2] }; }; Clip.prototype.setB = function(b) { b = b || {}; this.core.b = [ b.x != undefined ? b.x : 0.0, b.y != undefined ? b.y : 0.0, b.z != undefined ? b.z : 0.0, 1 ]; }; Clip.prototype.getB = function() { return { x: this.core.b[0], y: this.core.b[1], z: this.core.b[2] }; }; Clip.prototype.setC = function(c) { c = c || {}; this.core.c = [ c.x != undefined ? c.x : 0.0, c.y != undefined ? c.y : 0.0, c.z != undefined ? c.z : 0.0, 1 ]; }; Clip.prototype.getC = function() { return { x: this.core.c[0], y: this.core.c[1], z: this.core.c[2] }; }; Clip.prototype.getAttributes = function() { return { mode: this.core.mode, a: this.getA(), b: this.getB(), c: this.getC() }; }; Clip.prototype._compile = function() { var core = this.core; var modelMat = SceneJS_modelTransformModule.transform.matrix; var worldA = SceneJS_math_transformPoint3(modelMat, core.a); var worldB = SceneJS_math_transformPoint3(modelMat, core.b); var worldC = SceneJS_math_transformPoint3(modelMat, core.c); var normal = SceneJS_math_normalizeVec3( SceneJS_math_cross3Vec4( SceneJS_math_normalizeVec3( SceneJS_math_subVec3(worldB, worldA, [0,0,0]), [0,0,0]), SceneJS_math_normalizeVec3( SceneJS_math_subVec3(worldB, worldC, [0,0,0]), [0,0,0]))); var dist = SceneJS_math_dotVector3(normal, worldA); core.normalAndDist = [normal[0], normal[1], normal[2], dist]; clipStack[stackLen] = core; idStack[stackLen] = this.attr.id; stackLen++; dirty = true; this._compileNodes(); }; })(); new (function() { var idStack = []; var morphStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setMorph(idStack[stackLen - 1], morphStack[stackLen - 1]); } else { SceneJS_DrawList.setMorph(); } dirty = false; } }); /** * Destroys morph, returning true if memory freed, else false * where canvas not found and morph was implicitly destroyed */ function destroyMorph(morph) { if (document.getElementById(morph.canvas.canvasId)) { // Context won't exist if canvas has disappeared var target; for (var i = 0, len = morph.targets.length; i < len; i++) { target = morph.targets[i]; if (target.vertexBuf) { target.vertexBuf.destroy(); } if (target.normalBuf) { target.normalBuf.destroy(); } if (target.uvBuf) { target.uvBuf.destroy(); } if (target.uvBuf2) { target.uvBuf2.destroy(); } } } } function createMorphGeometry(scene, source, options, callback) { if (typeof source == "string") { /* Load from stream - http://scenejs.wikispaces.com/MorphGeoLoaderService */ var geoService = SceneJS.Services.getService(SceneJS.Services.MORPH_GEO_LOADER_SERVICE_ID); geoService.loadMorphGeometry(source, function(data) { callback(_createMorph(scene, data, options)); }); } else { /* Create from arrays */ return _createMorph(scene, createTypedMorph(source), options); } } function createTypedMorph(data) { var typedMorph = { keys: data.keys, targets: [] }; for (var i = 0, len = data.targets.length; i < len; i++) { typedMorph.targets.push(createTypedArrays(data.targets[i])); } return typedMorph; } function createTypedArrays(data) { return { positions: data.positions ? new Float32Array(data.positions) : undefined, normals: data.normals ? new Float32Array(data.normals) : undefined, uv: data.uv ? new Float32Array(data.uv) : undefined, uv2: data.uv2 ? new Float32Array(data.uv2) : undefined }; } function _createMorph(scene, data, options) { var context = scene.canvas.context; var morph = { scene: scene, canvas: scene.canvas, keys: data.keys, targets: [], key1: 0, key2: 1 }; try { var usage = context.STATIC_DRAW; var target; var newTarget; for (var i = 0, len = data.targets.length; i < len; i++) { target = data.targets[i]; newTarget = {}; morph.targets.push(newTarget); // We'll iterate this to destroy targets when we recover from error if (target.positions && target.positions.length > 0) { newTarget.vertexBuf = new SceneJS_webgl_ArrayBuffer(context, context.ARRAY_BUFFER, target.positions, target.positions.length, 3, usage); } if (target.normals && target.normals.length > 0) { newTarget.normalBuf = new SceneJS_webgl_ArrayBuffer(context, context.ARRAY_BUFFER, target.normals, target.normals.length, 3, usage); } if (target.uv && target.uv.length > 0) { newTarget.uvBuf = new SceneJS_webgl_ArrayBuffer(context, context.ARRAY_BUFFER, target.uv, target.uv.length, 2, usage); } if (target.uv2 && target.uv2.length > 0) { newTarget.uvBuf2 = new SceneJS_webgl_ArrayBuffer(context, context.ARRAY_BUFFER, target.uv2, target.uv2.length, 2, usage); } } return morph; } catch (e) { /* Allocation failure - deallocate all target VBOs */ for (var i = 0, len = morph.targets.length; i < len; i++) { target = morph.targets[i]; if (target.vertexBuf) { target.vertexBuf.destroy(); } if (target.normalBuf) { target.normalBuf.destroy(); } if (target.uvBuf) { target.uvBuf.destroy(); } if (target.uvBuf2) { target.uvBuf2.destroy(); } } throw e; } } var MorphGeometry = SceneJS.createNodeType("morphGeometry"); MorphGeometry.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node defines the resource if (params.create instanceof Function) { var data = this._createMorphData(params.create()); SceneJS._apply(createMorphGeometry(this.scene, data, params.options), this.core); } else if (params.stream) { /* Load from stream * TODO: Expose the arrays on the node */ this._stream = params.stream; this.core._loading = true; var self = this; createMorphGeometry( this.scene, this._stream, params.options, function(morph) { SceneJS._apply(morph, self.core); self.core._loading = false; SceneJS_compileModule.nodeUpdated(self, "loaded"); // Compile again to apply freshly-loaded geometry }); } else { /* Create from arrays */ SceneJS._apply(createMorphGeometry(this.scene, this._createMorphData(params), params.options), this.core); } this.setFactor(params.factor); } this.core.factor = params.factor || 0; this.core.clamp = !!params.clamp; }; MorphGeometry.prototype._createMorphData = function(params) { var targets = params.targets || []; if (targets.length < 2) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "morphGeometry node should have at least two targets"); } var keys = params.keys || []; if (keys.length != targets.length) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "morphGeometry node mismatch in number of keys and targets"); } /* First target's arrays are defaults for where not given on subsequent targets */ var positions; var normals; var uv; var uv2; var target; for (var i = 0, len = targets.length; i < len; i++) { target = targets[i]; if (!positions && target.positions) { positions = target.positions.slice(0); } if (!normals && target.normals) { normals = target.normals.slice(0); } if (!uv && target.uv) { uv = target.uv.slice(0); } if (!uv2 && target.uv2) { uv2 = target.uv2.slice(0); } } for (var i = 0, len = targets.length; i < len; i++) { target = targets[i]; if (!target.positions) { target.positions = positions; // Can be undefined } if (!target.normals) { target.normals = normals; } if (!target.uv) { target.uv = uv; } if (!target.uv2) { target.uv2 = uv2; } } return { keys : keys, targets : targets, key1 : 0, key2 : 1, factor: 0 }; }; MorphGeometry.prototype.getState = function() { return this.core; }; MorphGeometry.prototype.getStream = function() { return this._stream; }; MorphGeometry.prototype.setFactor = function(factor) { factor = factor || 0.0; var core = this.core; var keys = core.keys; var key1 = core.key1; var key2 = core.key2; if (factor < keys[0]) { key1 = 0; key2 = 1; } else if (factor > keys[keys.length - 1]) { key1 = keys.length - 2; key2 = key1 + 1; } else { while (keys[key1] > factor) { key1--; key2--; } while (keys[key2] < factor) { key1++; key2++; } } /* Normalise factor to range [0.0..1.0] for the target frame */ core.factor = (factor - keys[key1]) / (keys[key2] - keys[key1]); core.key1 = key1; core.key2 = key2; }; MorphGeometry.prototype.getFactor = function() { return this.core.factor; }; MorphGeometry.prototype._compile = function() { this._preCompile(); this._compileNodes(); this._postCompile(); }; MorphGeometry.prototype._preCompile = function() { if (!this.core._loading) { idStack[stackLen] = this.attr.id; morphStack[stackLen] = this.core; stackLen++; dirty = true; } }; MorphGeometry.prototype._postCompile = function() { if (!this.core._loading) { stackLen--; dirty = true; } }; MorphGeometry.prototype._destroy = function() { if (this.core._nodeCount == 1) { // Last resource user destroyMorph(this.core); } }; })(); (function() { var idStack = []; var nameStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function(params) { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setName(idStack[stackLen - 1], nameStack[stackLen - 1]); } else { SceneJS_DrawList.setName(); } dirty = false; } }); var Name = SceneJS.createNodeType("name"); Name.prototype._init = function(params) { if (this.core._nodeCount == 1) { this.setName(params.name); } }; Name.prototype.setName = function(name) { this.core.name = name || "unnamed"; }; Name.prototype._compile = function() { var id = this.attr.id; idStack[stackLen] = id; nameStack[stackLen] = this.core.name; stackLen++; dirty = true; this._compileNodes(); stackLen--; dirty = true; }; })(); new (function() { var sceneBufs = {}; //------------------------------------------------------------ // TODO: remove bufs when all video nodes deleted for a scene otherwise we'll have inifite redraw //-------------------------------------------------------- SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_IDLE, function(params) { var bufs = sceneBufs[params.sceneId]; if (bufs) { for (var bufId in bufs) { // Update video textures for each scene if (bufs.hasOwnProperty(bufId)) { bufs[bufId].update(); } } SceneJS_compileModule.redraw(params.sceneId); // Trigger scene redraw after texture update } }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_DESTROYED, function(params) { sceneBufs[params.sceneId] = null; }); var Video = SceneJS.createNodeType("video"); Video.prototype._init = function(params) { if (!params.src) { throw SceneJS_errorModule.fatalError( SceneJS.errors.NODE_CONFIG_EXPECTED, "video node parameter expected: 'src'"); } /* Create hidden video canvas */ var video = this._video = document.createElement("video"); video.style.display = "none"; video.setAttribute("loop", "loop"); video.autoplay = true; video.addEventListener("ended", // looping broken in FF function() { this.play(); }, true); document.getElementsByTagName("body")[0].appendChild(video); this._canvas = document.createElement("canvas"); // for webkit this._ctx = this._canvas.getContext("2d"); video.src = params.src; /* Create video texture */ var gl = this._gl = this.scene.canvas.context; this._texture = gl.createTexture(); /* Create handle to video texture */ var bufId = this.attr.id; var self = this; var buf = { id: bufId, update: function() { var video = self._video; var gl = self._gl; var canvas = self._canvas; var texture = self._texture; var ctx = self._ctx; gl.bindTexture(gl.TEXTURE_2D, texture); // TODO: fix when minefield is up to spec - thanks GLGE if (video.readyState > 0) { if (video.height <= 0) { video.style.display = ""; video.height = video.offsetHeight; video.width = video.offsetWidth; video.style.display = "none"; } canvas.height = video.height; canvas.width = video.width; ctx.drawImage(video, 0, 0); try { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); } catch(e) { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas, null); } gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.generateMipmap(gl.TEXTURE_2D); /* For when webkit works - thanks GLGE try{gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);} catch(e){gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video,null);} gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.generateMipmap(gl.TEXTURE_2D); */ } }, getTexture: function() { return { bind: function(unit) { var gl = self._gl; gl.activeTexture(gl["TEXTURE" + unit]); gl.bindTexture(gl.TEXTURE_2D, self._texture); }, unbind : function(unit) { var gl = self._gl; gl.activeTexture(gl["TEXTURE" + unit]); gl.bindTexture(gl.TEXTURE_2D, null); } }; } }; buf.update(); /* Register the buffer */ var sceneId = this.scene.attr.id; var bufs = sceneBufs[sceneId]; if (!bufs) { bufs = sceneBufs[sceneId] = {}; } this._buf = bufs[bufId] = buf; }; SceneJS._compilationStates.setSupplier("video", { get: function(sceneId, id) { var bufs = sceneBufs[sceneId]; return { bind: function(unit) { var buf = bufs[id]; if (buf) { // Lazy-bind when video node shows up buf.getTexture().bind(unit); } }, unbind: function(unit) { var buf = bufs[id]; if (buf) { buf.getTexture().unbind(unit); } } }; } }); Video.prototype._compile = function() { this._compileNodes(); }; Video.prototype._destroy = function() { if (this._buf) { var bufs = sceneBufs[this.scene.attr.id]; } }; })(); new (function() { var sceneBufs = {}; var idStack = []; var bufStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setFrameBuf(idStack[stackLen - 1], bufStack[stackLen - 1]); } else { // Full compile supplies it's own default states SceneJS_DrawList.setFrameBuf(); // No frameBuf } dirty = false; } }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_DESTROYED, function(params) { sceneBufs[params.sceneId] = null; }); /** Creates image buffer, registers it under the given ID */ function createFrameBuffer(scene, bufId) { var canvas = scene.canvas; var gl = canvas.context; var width = canvas.canvas.width; var height = canvas.canvas.height; var frameBuf = gl.createFramebuffer(); var renderBuf = gl.createRenderbuffer(); var texture = gl.createTexture() ; var rendered = false; gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuf); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 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); try { // Do it the way the spec requires gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); } catch (exception) { // Workaround for what appears to be a Minefield bug. var textureStorage = new WebGLUnsignedByteArray(width * height * 4); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, textureStorage); } gl.bindRenderbuffer(gl.RENDERBUFFER, renderBuf); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderBuf); gl.bindTexture(gl.TEXTURE_2D, null); gl.bindRenderbuffer(gl.RENDERBUFFER, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null); /* Verify framebuffer is OK */ gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuf); if (!gl.isFramebuffer(frameBuf)) { throw SceneJS_errorModule.fatalError("Invalid framebuffer"); } var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); switch (status) { case gl.FRAMEBUFFER_COMPLETE: break; case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: throw SceneJS_errorModule.fatalError("Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: throw SceneJS_errorModule.fatalError("Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: throw SceneJS_errorModule.fatalError("Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_DIMENSIONS"); case gl.FRAMEBUFFER_UNSUPPORTED: throw SceneJS_errorModule.fatalError("Incomplete framebuffer: FRAMEBUFFER_UNSUPPORTED"); default: throw SceneJS_errorModule.fatalError("Incomplete framebuffer: " + status); } /* Create handle to image buffer */ var buf = { id: bufId, /** Binds the image buffer as target for subsequent geometry renders */ bind: function() { // gl.bindRenderbuffer(gl.RENDERBUFFER, renderBuf); gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuf); gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clearDepth(1.0); gl.enable(gl.DEPTH_TEST); gl.disable(gl.CULL_FACE); gl.depthRange(0, 1); gl.disable(gl.SCISSOR_TEST); // gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.disable(gl.BLEND); }, /** Unbinds image buffer, the default buffer then becomes the rendering target */ unbind:function() { gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) gl.bindRenderbuffer(gl.RENDERBUFFER, renderBuf); rendered = true; }, /** Returns true if this texture has been rendered */ isRendered: function() { return rendered; }, /** Gets the texture from this image buffer */ getTexture: function() { return { bind: function(unit) { gl.activeTexture(gl["TEXTURE" + unit]); gl.bindTexture(gl.TEXTURE_2D, texture); }, unbind : function(unit) { gl.activeTexture(gl["TEXTURE" + unit]); gl.bindTexture(gl.TEXTURE_2D, null); } }; } }; /* Register the buffer */ var bufs = sceneBufs[scene.attr.id]; if (!bufs) { bufs = sceneBufs[scene.attr.id] = {}; } bufs[bufId] = buf; return buf; } function destroyFrameBuffer(buf) { } SceneJS._compilationStates.setSupplier("frameBuf", { get: function(sceneId, id) { var bufs = sceneBufs[sceneId]; return { bind: function(unit) { var buf = bufs[id]; if (buf && buf.isRendered()) { buf.getTexture().bind(unit); } }, unbind: function(unit) { var buf = bufs[id]; if (buf && buf.isRendered()) { buf.getTexture().unbind(unit); } } }; } }); var FrameBuf = SceneJS.createNodeType("frameBuf"); FrameBuf.prototype._init = function() { this._buf = createFrameBuffer(this.scene, this.attr.id); }; FrameBuf.prototype._compile = function() { idStack[stackLen] = this.attr.id; bufStack[stackLen] = this._buf; stackLen++; dirty = true; this._compileNodes(); stackLen--; dirty = true; }; FrameBuf.prototype._destroy = function() { if (this._buf) { destroyFrameBuffer(this._buf); } }; })(); new (function() { var idStack = []; var shaderVertexCodeStack = []; var shaderVertexHooksStack = []; var shaderVertexHooksStacks = {}; var shaderFragmentCodeStack = []; var shaderFragmentHooksStack = []; var shaderParamsStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { var shader = { shaders: { fragment: { code: shaderFragmentCodeStack.slice(0, stackLen).join("\n"), hooks: combineMapStack(shaderFragmentHooksStack) }, vertex: { code: shaderVertexCodeStack.slice(0, stackLen).join("\n"), hooks: combineMapStack(shaderVertexHooksStack) } }, paramsStack: shaderParamsStack.slice(0, stackLen), hash: idStack.slice(0, stackLen).join(".") }; SceneJS_DrawList.setShader(idStack[stackLen - 1], shader); } else { SceneJS_DrawList.setShader(); } dirty = false; } }); function combineMapStack(maps) { var map1; var map2 = {}; var name; for (var i = 0; i < stackLen; i++) { map1 = maps[i]; for (name in map1) { if (map1.hasOwnProperty(name)) { map2[name] = map1[name]; } } } return map2; } function pushHooks(hooks, hookStacks) { var stack; for (var key in hooks) { if (hooks.hasOwnProperty(key)) { stack = hookStacks[key]; if (!stack) { stack = hookStacks[key] = []; } stack.push(hooks[key]); } } } var Shader = SceneJS.createNodeType("shader"); Shader.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node is the resource definer this._setShaders(params.shaders); this.setParams(params.params); } }; Shader.prototype._setShaders = function(shaders) { shaders = shaders || []; this.core.shaders = {}; var shader; for (var i = 0, len = shaders.length; i < len; i++) { shader = shaders[i]; if (!shader.stage) { throw SceneJS_errorModule.fatalError( SceneJS.errors.ILLEGAL_NODE_CONFIG, "shader 'stage' attribute expected"); } var code; if (shader.code) { if (SceneJS._isArray(shader.code)) { code = shader.code.join(""); } else { code = shader.code; } } this.core.shaders[shader.stage] = { code: code, hooks: shader.hooks }; } }; Shader.prototype.setParams = function(params) { params = params || {}; var coreParams = this.core.params; if (!coreParams) { coreParams = this.core.params = {}; } for (var name in params) { if (params.hasOwnProperty(name)) { coreParams[name] = params[name]; } } }; Shader.prototype.getParams = function() { var coreParams = this.core.params; if (!coreParams) { return {}; } var params = {}; for (var name in coreParams) { if (coreParams.hasOwnProperty(name)) { params[name] = coreParams[name]; } } return params; }; Shader.prototype._compile = function() { /* Push */ idStack[stackLen] = this.core._coreId; // Draw list node tied to core, not node var shaders = this.core.shaders; var fragment = shaders.fragment || {}; var vertex = shaders.vertex || {}; shaderFragmentCodeStack[stackLen] = fragment.code || ""; shaderFragmentHooksStack[stackLen] = fragment.hooks || {}; // if (fragment.hooks) { // pushHooks(fragment.hooks, shaderFragmentHooksStack); // } shaderVertexCodeStack[stackLen] = vertex.code || ""; shaderVertexHooksStack[stackLen] = vertex.hooks || {}; // if (vertex.hooks) { // pushHooks(vertex.hooks, shaderVertexHooksStack); // } shaderParamsStack[stackLen] = this.core.params || {}; stackLen++; dirty = true; this._compileNodes(); /* Pop */ stackLen--; dirty = true; }; })(); new (function() { var idStack = []; var shaderParamsStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function() { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setShaderParams(idStack[stackLen - 1], shaderParamsStack.slice(0, stackLen)); } else { // Full compile supplies it's own default states SceneJS_DrawList.setShaderParams(); } dirty = false; } }); var ShaderParams = SceneJS.createNodeType("shaderParams"); ShaderParams.prototype._init = function(params) { if (this.core._nodeCount == 1) { // This node is the resource definer this.setParams(params.params); } }; ShaderParams.prototype.setParams = function(params) { params = params || {}; var core = this.core; if (!core.params) { core.params = {}; } for (var name in params) { if (params.hasOwnProperty(name)) { core.params[name] = params[name]; } } }; ShaderParams.prototype._compile = function() { idStack[stackLen] = this.core._coreId; // Tie draw list state to core, not to scene node shaderParamsStack[stackLen] = this.core.params; stackLen++; dirty = true; this._compileNodes(); stackLen--; // pop params dirty = true; }; })(); var SceneJS_nodeEventsModule = new (function() { var idStack = []; var listenerStack = []; var stackLen = 0; var dirty; SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_COMPILING, function() { stackLen = 0; dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SHADER_ACTIVATED, function() { dirty = true; }); SceneJS_eventModule.addListener( SceneJS_eventModule.SCENE_RENDERING, function(params) { if (dirty) { if (stackLen > 0) { SceneJS_DrawList.setRenderListeners(idStack[stackLen - 1], listenerStack.slice(0, stackLen)); } else { SceneJS_DrawList.setRenderListeners(); } dirty = false; } }); this.preVisitNode = function(node) { var listeners = node.listeners["rendered"]; if (listeners && listeners.length > 0) { idStack[stackLen] = node.attr.id; listenerStack[stackLen] = function (params) { node._fireEvent("rendered", params); }; stackLen++; dirty = true; } }; this.postVisitNode = function(node) { if (node.attr.id == idStack[stackLen - 1]) { stackLen--; dirty = true; } }; })();