topical media & game development

talk show tell print

graphic-webgl-scenejs-lib-scenejs.js / js



  /*
   * 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.
parameter: {string} type Name of new subtype
parameter: {string} superType Optional name of super-type - {gray SceneJS_node} by default
returns: {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 {gray 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
parameter: 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: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 {gray #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.
parameter: {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.
parameter: {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.
parameter: {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
parameter: {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
parameter: {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
parameter: {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.
parameter: {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
parameter: {Array[Node]} nodes Array of nodes
returns: {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
parameter: {Node} node Child node
returns: {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
parameter: {Node} node Child node
parameter: {int} i Index for new child node
returns: {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.
parameter: {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. <p><b>Example:</b> <pre><code> var node = new Node(); node.addListener( // eventName "some-event", // handler function(node, // Node we are listening to params) { // Whatever params accompany the event type // ... } ); </code></pre>
parameter: {String} eventName One of the event types supported by this node
parameter: {Function} fn - Handler function that be called as specified
parameter: options - Optional options for the handler as specified
returns: {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.
returns: {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
parameter: {String} eventName Event name
parameter: {Object} params Event parameters
parameter: {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.
parameter: {String} eventName Event type that handler is registered for
parameter: {function} fn - Handler function that is registered for the event
returns: {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 .
parameter: {String} eventName Event type
returns: {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.
returns: {boolean} True if any listener present

  
  SceneJS._Node.prototype.hasListeners = function() {
      return (this.numListeners > 0);
  };
  
  
Removes all listeners registered on this node.
returns: {Node} this

  
  SceneJS._Node.prototype.removeListeners = function() {
      this.listeners = {};
      this.numListeners = 0;
      return this;
  };
  
  
Returns the parent node
returns: {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.
parameter: {string} type Node type
parameter: {boolean} [recursive=false] When true, will return all matching nodes in subgraph, otherwise returns just children (default)
returns: {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
parameter: {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
parameter: {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.
parameter: {Function(node, index)} fn Function to execute on each instance node
returns: {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.
parameter: {Function(index, node)} fn Function to execute on each child node
returns: {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
parameter: {String} name Event name
parameter: {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
parameter: {String} name Event name
parameter: {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.
parameter: offsetX Canvas X-coordinate
parameter: 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.
   */
  
  

parameter: u vec3
parameter: v vec3
parameter: dest vec3 - optional destination
returns: {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;
  };
  
  

parameter: v vec4
parameter: dest vec4 - optional destination
returns: {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;
  };
  
  

parameter: u vec4
parameter: v vec4
parameter: dest vec4 - optional destination
returns: {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;
  };
  
  

parameter: v vec4
parameter: s scalar
parameter: dest vec4 - optional destination
returns: {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;
  };
  
  

parameter: u vec3
parameter: v vec3
parameter: dest vec3 - optional destination
returns: {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;
  };
  
  

parameter: v vec3
parameter: s scalar
parameter: dest vec3 - optional destination
returns: {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);
  };
  
  

parameter: u vec4
parameter: v vec4
parameter: dest vec4 - optional destination
returns: {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;
  };
  
  

parameter: u vec3
parameter: v vec3
parameter: dest vec3 - optional destination
returns: {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
      };
  };
  
  

parameter: u vec2
parameter: v vec2
parameter: dest vec2 - optional destination
returns: {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;
  };
  
  

parameter: v vec4
parameter: s scalar
parameter: dest vec4 - optional destination
returns: {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;
  };
  
  

parameter: v vec4
parameter: s scalar
parameter: dest vec4 - optional destination
returns: {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;
  };
  
  

parameter: u vec4
parameter: v vec4
parameter: dest vec4 - optional destination
returns: {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;
  };
  
  

parameter: v vec4
parameter: s scalar
parameter: dest vec4 - optional destination
returns: {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;
  };
  
  

parameter: v vec3
parameter: s scalar
parameter: dest vec3 - optional destination
returns: {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;
  };
  
  

parameter: v vec2
parameter: s scalar
parameter: dest vec2 - optional destination
returns: {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;
  };
  
  

parameter: u vec4
parameter: v vec4
parameter: dest vec4 - optional destination
returns: {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;
  };
  
  

parameter: v vec3
parameter: s scalar
parameter: dest vec3 - optional destination
returns: {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;
  };
  
  

parameter: v vec3
parameter: s scalar
parameter: dest vec3 - optional destination
returns: {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;
  };
  
  

parameter: v vec4
parameter: s scalar
parameter: dest vec4 - optional destination
returns: {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;
  };
  
  

parameter: s scalar
parameter: v vec4
parameter: dest vec4 - optional destination
returns: {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];
  };
  
  

parameter: u vec3
parameter: v vec3
parameter: dest vec3 - optional destination
returns: {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));
  };
  
  

parameter: v vec3
parameter: dest vec3 - optional destination
returns: {vec3} dest if specified, v otherwise @private

  
  var SceneJS_math_rcpVec3 = function(v, dest) {
      return SceneJS_math_divScalarVec3(1.0, v, dest);
  };
  
  

parameter: v vec4
parameter: dest vec4 - optional destination
returns: {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;
  };
  
  

parameter: m mat4
parameter: dest mat4 - optional destination
returns: {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;
  };
  
  

parameter: a mat4
parameter: b mat4
parameter: dest mat4 - optional destination
returns: {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;
  };
  
  

parameter: m mat4
parameter: s scalar
parameter: dest mat4 - optional destination
returns: {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);
  };
  
  

parameter: a mat4
parameter: b mat4
parameter: dest mat4 - optional destination
returns: {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;
  };
  
  

parameter: m mat4
parameter: s scalar
parameter: dest mat4 - optional destination
returns: {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;
  };
  
  

parameter: s scalar
parameter: m mat4
parameter: dest mat4 - optional destination
returns: {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;
  };
  
  

parameter: a mat4
parameter: b mat4
parameter: dest mat4 - optional destination
returns: {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;
  };
  
  

parameter: m mat4
parameter: s scalar
parameter: dest mat4 - optional destination
returns: {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;
  };
  
  

parameter: m mat4
parameter: v vec4
returns: {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
      ];
  };
  
  

parameter: mat mat4
parameter: dest mat4 - optional destination
returns: {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;
  };
  
  

parameter: mat mat4
parameter: dest mat4 - optional destination
returns: {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
  };
  
  

parameter: pos vec3 position of the viewer
parameter: target vec3 point the viewer is looking at
parameter: up vec3 pointing "up"
parameter: dest mat4 Optional, mat4 frustum matrix will be written into
returns: {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
parameter: min
parameter: 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
parameter: center
parameter: 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
parameter: context WebGL context
parameter: gl.VERTEX_SHADER | gl.FRAGMENT_SHADER
parameter: source Source code for shader
parameter: 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
parameter: hash SceneJS-managed ID for program
parameter: context WebGL context
parameter: vertexSources Source codes for vertex shaders
parameter: fragmentSources Source codes for fragment shaders
parameter: 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: 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
parameter: context WebGL context
parameter: type Eg. ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER
parameter: values WebGL array wrapper
parameter: numItems Count of items in array wrapper
parameter: itemSize Size of each item
parameter: 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.
parameter: {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.
parameter: {!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
parameter: {*} value Value to check if it might be an enum.
returns: {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.
parameter: {string} fname the GL function to use for screening the enum
parameter: {integer} i the parameter index to use for screening the enum
parameter: {any} value the value to check for being a valid i'th parameter to 'fname'
returns: {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());
parameter: {number} value Value to return an enum for
returns: {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.
parameter: {!WebGLRenderingContext} ctx The webgl context to wrap.
parameter: {!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.
parameter: {!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
parameter: {*} value Value to check if it might be an enum.
returns: {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());
parameter: {number} value Value to return an enum for
returns: {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);
parameter: {!WebGLRenderingContext} ctx The webgl context to wrap.
parameter: {!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
parameter: type Event type - one of the values in SceneJS_eventModule
parameter: command - Handler function that will accept whatever parameter object accompanies the event
parameter: 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.
parameter: {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.
parameter: {String} nodeType Type of node - eg. "rotate", "lookAt" etc.
parameter: {String} op Type of update (optional) - eg. "set", "get", "inc"
parameter: {String} name Name of updated attribute (optional) - eg. "angle", "eye", "baseColor"
parameter: {{String:Object}} value Value for the attribute (optional) - when a map, the lowest compilation level among the keys will be returned
returns: {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.
parameter: {String} nodeType Type of node - eg. "rotate", "lookAt" etc.
parameter: {String} op Type of update (optional) - eg. "set", "get", "inc"
parameter: {String} attrName Name of updated attribute (optional) - eg. "angle", "eye", "baseColor"
parameter: {{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) ? "&nbsp;&nbsp;&nbsp;" : ((i < 100) ? "&nbsp;&nbsp;" : (i < 1000 ? "&nbsp;" : ""));
              array2.push(i + padding + ": " + array[i]);
          }
          return array2.join("<br/>");
      }
  
      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 += "<p style=\"color:orange;\">" + msg + "</p>";
                          },
                          error : function log(msg) {
                              element.innerHTML += "<p style=\"color:darkred;\">" + msg + "</p>";
                          },
                          debug : function log(msg) {
                              element.innerHTML += "<p style=\"color:darkblue;\">" + msg + "</p>";
                          },
                          info : function log(msg) {
                              element.innerHTML += "<p style=\"color:darkgreen;\">" + msg + "</p>";
                          }
                      };
                  } 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 {gray Scene} node does not supply one

  
      Scene.DEFAULT_CANVAS_ID = "_scenejs-default-canvas";
  
      
ID ("_scenejs-default-logging") of default element to which {gray 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 - 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 {gray #start}. After this, {gray #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 {gray #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 {gray 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 {gray #start}. After this, {gray #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.
parameter: 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
parameter: stateType ID of the type of state, eg. this._TEXTURE etc
parameter: 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. <p><b>Example Usage</b></p><p>Definition of teapot:</b></p><pre><code> var c = new SceneJS_teapot(); // Requires no parameters </pre></code> @extends SceneJS.Geometry @since Version 0.7.4 @constructor Create a new SceneJS_teapot
parameter: {Object} [cfg] Static configuration object
parameter: {function(SceneJS.Data):Object} [fn] Dynamic configuration function
parameter: {...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. <p>The geometry is complete with normals for shading and one layer of UV coordinates for texture-mapping.</p> <p><b>Example Usage</b></p><p>Definition of sphere with a radius of 6 units:</b></p><pre><code> 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) }) </pre></code> @extends SceneJS.Geometry @since Version 0.7.4 @constructor Create a new Sphere
parameter: {Object} [cfg] Static configuration object
parameter: {float} [cfg.slices=30] Number of longitudinal slices
parameter: {float} [cfg.rings=30] Number of longitudinal slices
parameter: {float} [cfg.semiMajorAxis=1.0] values other than one generate an elliptical sphere
parameter: {float} [cfg.sweep=1] rotational extrusion, default is 1
parameter: {float} [cfg.sliceDepth=1] depth of slices to generate, default is 1
parameter: {function(SceneJS.Data):Object} [fn] Dynamic configuration function
parameter: {...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. <p>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.</p> <p><b>Example Usage</b></p><p>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:</b></p><pre><code> 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) }) </pre></code> @extends SceneJS.Geometry @since Version 0.7.9 @constructor Create a new Disk
parameter: {Object} [cfg] Static configuration object
parameter: {float} [cfg.radius=1.0] radius extending from Y-axis
parameter: {float} [cfg.innerRadius=0] inner radius extending from Y-axis
parameter: {float} [cfg.innerRadius=0] inner radius extending from Y-axis
parameter: {float} [cfg.semiMajorAxis=1.0] values other than one generate an ellipse
parameter: {float} [cfg.rings=30] number of longitudinal rings
parameter: {...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
parameter: {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
parameter: {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;
          }
      };
  })();
  
  


(C) Æliens 04/09/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.